From 12fa99db5eb2237b587db547b22bff6241b736fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Leszko?= Date: Fri, 12 Aug 2022 09:41:31 +0200 Subject: [PATCH 01/11] Process Upload VOD --- cmd/http-server/http-server.go | 27 +++++-- cmd/http-server/http-server_test.go | 5 +- handlers/client.go | 28 +++++-- handlers/client_test.go | 10 +++ handlers/handlers.go | 112 +++++++++++++++++----------- handlers/handlers_test.go | 33 ++++---- handlers/mock_client.go | 23 ++++++ middleware/middleware_test.go | 6 +- 8 files changed, 166 insertions(+), 78 deletions(-) create mode 100644 handlers/mock_client.go diff --git a/cmd/http-server/http-server.go b/cmd/http-server/http-server.go index 39b9616e5..37e388a66 100644 --- a/cmd/http-server/http-server.go +++ b/cmd/http-server/http-server.go @@ -4,20 +4,22 @@ import ( "flag" "fmt" stdlog "log" - "net/http" "os" - log "github.com/go-kit/kit/log" + "github.com/go-kit/kit/log" "github.com/julienschmidt/httprouter" "github.com/livepeer/catalyst-api/config" "github.com/livepeer/catalyst-api/handlers" "github.com/livepeer/catalyst-api/middleware" "github.com/livepeer/livepeer-data/pkg/mistconnector" + + "net/http" ) func main() { port := flag.Int("port", 4949, "Port to listen on") + mistPort := flag.Int("mist-port", 4242, "Port to listen on") mistJson := flag.Bool("j", false, "Print application info as JSON. Used by Mist to present flags in its UI.") flag.Parse() @@ -26,8 +28,14 @@ func main() { return } - listen := fmt.Sprintf("0.0.0.0:%d", *port) - router := StartCatalystAPIRouter() + mc := &handlers.MistClient{ + ApiUrl: fmt.Sprintf("http://localhost:%d/api2", *mistPort), + TriggerCallback: "http://host.docker.internal:4949/api/mist/trigger", + //TriggerCallback: fmt.Sprintf("http://localhost:%d/api/mist/trigger", *port), + } + + listen := fmt.Sprintf("localhost:%d", *port) + router := StartCatalystAPIRouter(mc) stdlog.Println("Starting Catalyst API version", config.Version, "listening on", listen) err := http.ListenAndServe(listen, router) @@ -35,7 +43,7 @@ func main() { } -func StartCatalystAPIRouter() *httprouter.Router { +func StartCatalystAPIRouter(mc *handlers.MistClient) *httprouter.Router { router := httprouter.New() var logger log.Logger @@ -43,9 +51,12 @@ func StartCatalystAPIRouter() *httprouter.Router { logger = log.With(logger, "ts", log.DefaultTimestampUTC) withLogging := middleware.LogRequest(logger) - router.GET("/ok", withLogging(middleware.IsAuthorized(handlers.CatalystAPIHandlers.Ok()))) - router.POST("/api/vod", withLogging(middleware.IsAuthorized(handlers.CatalystAPIHandlers.UploadVOD()))) - router.POST("/api/mist/trigger", withLogging(handlers.MistCallbackHandlers.Trigger())) + catalystApiHandlers := &handlers.CatalystAPIHandlersCollection{MistClient: mc} + mistCallbackHandlers := &handlers.MistCallbackHandlersCollection{MistClient: mc} + + router.GET("/ok", withLogging(middleware.IsAuthorized(catalystApiHandlers.Ok()))) + router.POST("/api/vod", withLogging(middleware.IsAuthorized(catalystApiHandlers.UploadVOD()))) + router.POST("/api/mist/trigger", withLogging(mistCallbackHandlers.Trigger())) return router } diff --git a/cmd/http-server/http-server_test.go b/cmd/http-server/http-server_test.go index e3fc26646..6eabf78d4 100644 --- a/cmd/http-server/http-server_test.go +++ b/cmd/http-server/http-server_test.go @@ -1,14 +1,13 @@ package main import ( - "testing" - "github.com/stretchr/testify/require" + "testing" ) func TestInitServer(t *testing.T) { require := require.New(t) - router := StartCatalystAPIRouter() + router := StartCatalystAPIRouter(nil) handle, _, _ := router.Lookup("GET", "/ok") require.NotNil(handle) diff --git a/handlers/client.go b/handlers/client.go index ad3c37a43..39fa5dc4e 100644 --- a/handlers/client.go +++ b/handlers/client.go @@ -11,9 +11,17 @@ import ( "sync" ) +type MistAPIClient interface { + AddStream(streamName, sourceUrl string) error + PushStart(streamName, targetURL string) error + DeleteStream(streamName string) error + AddTrigger(streamName, triggerName string) error + DeleteTrigger(streamName, triggerName string) error +} + type MistClient struct { - apiUrl string - triggerCallback string + ApiUrl string + TriggerCallback string configMu sync.Mutex } @@ -47,7 +55,7 @@ func (mc *MistClient) AddTrigger(streamName, triggerName string) error { if err != nil { return err } - c := commandAddTrigger(streamName, triggerName, mc.triggerCallback, triggers) + c := commandAddTrigger(streamName, triggerName, mc.TriggerCallback, triggers) resp, err := mc.sendCommand(c) return validateAddTrigger(streamName, triggerName, resp, err) } @@ -96,8 +104,8 @@ func (mc *MistClient) sendCommand(command interface{}) (string, error) { if err != nil { return "", err } - payload := payloadFor(c) - resp, err := http.Post(mc.apiUrl, "application/x-www-form-urlencoded", bytes.NewBuffer([]byte(payload))) + payload := payloadFor(auth(c)) + resp, err := http.Post(mc.ApiUrl, "application/json", bytes.NewBuffer([]byte(payload))) if err != nil { return "", err } @@ -200,7 +208,7 @@ func commandUpdateTrigger(streamName, triggerName string, currentTriggers Trigge triggersMap := currentTriggers triggers := triggersMap[triggerName] - triggers = filterTriggersWithoutStream(triggers, streamName) + triggers = deleteAllTriggersFor(triggers, streamName) if len(replaceTrigger.Streams) != 0 { triggers = append(triggers, replaceTrigger) } @@ -209,7 +217,7 @@ func commandUpdateTrigger(streamName, triggerName string, currentTriggers Trigge return MistConfig{Config{Triggers: triggersMap}} } -func filterTriggersWithoutStream(triggers []ConfigTrigger, streamName string) []ConfigTrigger { +func deleteAllTriggersFor(triggers []ConfigTrigger, streamName string) []ConfigTrigger { var res []ConfigTrigger for _, t := range triggers { f := false @@ -227,7 +235,6 @@ func filterTriggersWithoutStream(triggers []ConfigTrigger, streamName string) [] } func commandGetTriggers() MistConfig { - // send an empty config struct returns the current Mist configuration return MistConfig{} } @@ -335,3 +342,8 @@ func wrapErr(err error, streamName string) error { } return nil } + +// TODO: Delete auth, since DMS API will run on localhost, so it does not need authentation +func auth(command string) string { + return fmt.Sprintf("{%s,%s}", `"authorize":{"username":"test","password":"45bef56e3d0ed618571f52e9a07a448a"}`, command) +} diff --git a/handlers/client_test.go b/handlers/client_test.go index daf1f7b50..b24247220 100644 --- a/handlers/client_test.go +++ b/handlers/client_test.go @@ -146,3 +146,13 @@ func TestResponseValidation(t *testing.T) { require.Error(validateAddTrigger("catalyst_vod_gedhbdhc", "PUSH_END", `{"LTS":1,"authorize":{"status":"OK"},"config":{"accesslog":"LOG","controller":{"interface":null,"port":null,"username":null},"debug":null,"defaultStream":null,"iid":"IIcEj|Z\\|^lbDbjg","limits":null,"location":{"lat":0.0000000000,"lon":0.0000000000,"name":""},"prometheus":"koekjes","protocols":[{"connector":"AAC","online":"Enabled"},{"connector":"CMAF","online":"Enabled"},{"connector":"DTSC","online":1},{"connector":"EBML","online":"Enabled"},{"connector":"FLV","online":"Enabled"},{"connector":"H264","online":"Enabled"},{"connector":"HDS","online":"Enabled"},{"connector":"HLS","online":1},{"connector":"HTTP","online":1},{"connector":"HTTPTS","online":"Enabled"},{"connector":"JSON","online":"Enabled"},{"connector":"MP3","online":"Enabled"},{"connector":"MP4","online":"Enabled"},{"connector":"OGG","online":"Enabled"},{"connector":"RTMP","online":1},{"connector":"RTSP","online":1},{"connector":"SDP","online":"Enabled"},{"connector":"SRT","online":"Enabled"},{"connector":"TSSRT","online":1},{"connector":"WAV","online":"Enabled"},{"connector":"WebRTC","online":"Enabled"},{"connector":null,"online":"Missing connector name"}],"serverid":null,"sessionInputMode":"14","sessionOutputMode":"14","sessionStreamInfoMode":"1","sessionUnspecifiedMode":"0","sessionViewerMode":"14","sidMode":"0","time":1660027761,"triggers":{"PUSH_END":[{"handler":"http://host.docker.internal:8080/api/mist/trigger","streams":["some-other-stream"],"sync":false}],"RECORDING_END":null},"trustedproxy":[],"version":"eb84bc4ba743885734c60b312ca97ed07311d86f Generic_64"}}`, nil)) require.Error(validateDeleteTrigger("catalyst_vod_gedhbdhc", "PUSH_END", `{"LTS":1,"authorize":{"status":"OK"},"config":{"accesslog":"LOG","controller":{"interface":null,"port":null,"username":null},"debug":null,"defaultStream":null,"iid":"IIcEj|Z\\|^lbDbjg","limits":null,"location":{"lat":0.0000000000,"lon":0.0000000000,"name":""},"prometheus":"koekjes","protocols":[{"connector":"AAC","online":"Enabled"},{"connector":"CMAF","online":"Enabled"},{"connector":"DTSC","online":1},{"connector":"EBML","online":"Enabled"},{"connector":"FLV","online":"Enabled"},{"connector":"H264","online":"Enabled"},{"connector":"HDS","online":"Enabled"},{"connector":"HLS","online":1},{"connector":"HTTP","online":1},{"connector":"HTTPTS","online":"Enabled"},{"connector":"JSON","online":"Enabled"},{"connector":"MP3","online":"Enabled"},{"connector":"MP4","online":"Enabled"},{"connector":"OGG","online":"Enabled"},{"connector":"RTMP","online":1},{"connector":"RTSP","online":1},{"connector":"SDP","online":"Enabled"},{"connector":"SRT","online":"Enabled"},{"connector":"TSSRT","online":1},{"connector":"WAV","online":"Enabled"},{"connector":"WebRTC","online":"Enabled"},{"connector":null,"online":"Missing connector name"}],"serverid":null,"sessionInputMode":"14","sessionOutputMode":"14","sessionStreamInfoMode":"1","sessionUnspecifiedMode":"0","sessionViewerMode":"14","sidMode":"0","time":1660027761,"triggers":{"PUSH_END":[{"handler":"http://host.docker.internal:8080/api/mist/trigger","streams":["catalyst_vod_gedhbdhc"],"sync":false}],"RECORDING_END":null},"trustedproxy":[],"version":"eb84bc4ba743885734c60b312ca97ed07311d86f Generic_64"}}`, nil)) } + +// TODO: Remove after initial testing +func TestWorkflow(t *testing.T) { + // first copy file into /home/Big_Buck_Bunny_1080_10s_1MB.mp4 + catalystHandlers := CatalystAPIHandlersCollection{MistClient: &MistClient{ + ApiUrl: "http://localhost:4242/api2", + TriggerCallback: "http://host.docker.internal:4949/api/mist/trigger"}, + } + require.NoError(t, catalystHandlers.processUploadVOD("/home/Sample-Video-File-For-Testing.mp4", "/media/recording/out.m3u8")) +} diff --git a/handlers/handlers.go b/handlers/handlers.go index 0b2c3de9c..332b5d165 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -3,9 +3,9 @@ package handlers import ( "encoding/json" "fmt" + "github.com/livepeer/catalyst-api/clients" "io" "io/ioutil" - "log" "math/rand" "mime" "net/http" @@ -17,9 +17,10 @@ import ( "github.com/xeipuuv/gojsonschema" ) -type CatalystAPIHandlersCollection struct{} - -var CatalystAPIHandlers = CatalystAPIHandlersCollection{} +type CatalystAPIHandlersCollection struct { + MistClient MistAPIClient + CallbackURL string +} func (d *CatalystAPIHandlersCollection) Ok() httprouter.Handle { return func(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { @@ -27,15 +28,13 @@ func (d *CatalystAPIHandlersCollection) Ok() httprouter.Handle { } } -var processUpload = processUploadVOD - func (d *CatalystAPIHandlersCollection) UploadVOD() httprouter.Handle { + // TODO: Update schema with "outputs" schemaLoader := gojsonschema.NewStringLoader(`{ "type": "object", "properties": { "url": { "type": "string", "format": "uri" }, "callback_url": { "type": "string", "format": "uri" }, - "mp4_output": { "type": "boolean" }, "output_locations": { "type": "array", "items": { @@ -75,17 +74,22 @@ func (d *CatalystAPIHandlersCollection) UploadVOD() httprouter.Handle { type UploadVODRequest struct { Url string `json:"url"` CallbackUrl string `json:"callback_url"` - Mp4Output bool `json:"mp4_output"` OutputLocations []struct { Type string `json:"type"` URL string `json:"url"` PinataAccessKey string `json:"pinata_access_key"` + Outputs struct { + SourceMp4 bool `json:"source_mp4"` + SourceSegments bool `json:"source_segments"` + TranscodedSegments bool `json:"transcoded_segments"` + } `json:"outputs,omitempty"` } `json:"output_locations,omitempty"` } return func(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { var uploadVODRequest UploadVODRequest + // TODO: Send Errors to Callback URL if !HasContentType(req, "application/json") { errors.WriteHTTPUnsupportedMediaType(w, "Requires application/json content type", nil) return @@ -103,10 +107,29 @@ func (d *CatalystAPIHandlersCollection) UploadVOD() httprouter.Handle { return } - if err := processUpload(uploadVODRequest.Url); err != nil { + // find source segment URL + var tURL string + for _, o := range uploadVODRequest.OutputLocations { + if o.Outputs.SourceSegments { + tURL = o.URL + break + } + } + if tURL == "" { + errors.WriteHTTPBadRequest(w, "Invalid request payload", fmt.Errorf("no source segment URL in request")) + return + } + + // process the request + if err := d.processUploadVOD(uploadVODRequest.Url, tURL); err != nil { errors.WriteHTTPInternalServerError(w, "Cannot process upload VOD request", err) } + callbackClient := clients.NewCallbackClient() + if err := callbackClient.SendTranscodeStatus(d.CallbackURL, clients.TranscodeStatusPreparing, 0.0); err != nil { + errors.WriteHTTPInternalServerError(w, "Cannot send transcode status", err) + } + io.WriteString(w, fmt.Sprint(len(uploadVODRequest.OutputLocations))) } } @@ -129,41 +152,18 @@ func HasContentType(r *http.Request, mimetype string) bool { return false } -func processUploadVOD(url string) error { - // TODO: This function is only a scaffold for now - - // TODO: Update hostnames and ports - mc := MistClient{apiUrl: "http://localhost:4242/api2", triggerCallback: "http://host.docker.internal:8080/api/mist/trigger"} - +func (d *CatalystAPIHandlersCollection) processUploadVOD(sourceUrl, targetUrl string) error { streamName := randomStreamName("catalyst_vod_") - if err := mc.AddStream(streamName, url); err != nil { + if err := d.MistClient.AddStream(streamName, sourceUrl); err != nil { return err } - - // TODO: Move it to `Trigger()` - defer mc.DeleteStream(streamName) - - if err := mc.AddTrigger(streamName, "PUSH_END"); err != nil { + if err := d.MistClient.AddTrigger(streamName, "PUSH_END"); err != nil { return err } - // TODO: Move it to `Trigger()` - defer mc.DeleteTrigger(streamName, "PUSH_END") - - if err := mc.AddTrigger(streamName, "RECORDING_END"); err != nil { + if err := d.MistClient.PushStart(streamName, targetUrl); err != nil { return err } - // TODO: Move it to `Trigger()` - defer mc.DeleteTrigger(streamName, "RECORDING_END") - - // TODO: Change the output to the value from the request instead of the hardcoded "/media/recording/result.ts" - if err := mc.PushStart(streamName, "/media/recording/result.ts"); err != nil { - return err - } - - // TODO: After moving cleanup to `Trigger()`, this is no longer needed - time.Sleep(10 * time.Second) - return nil } @@ -179,21 +179,47 @@ func randomStreamName(prefix string) string { return fmt.Sprintf("%s%s", prefix, string(res)) } -type MistCallbackHandlersCollection struct{} - -var MistCallbackHandlers = MistCallbackHandlersCollection{} +type MistCallbackHandlersCollection struct { + MistClient MistAPIClient + CallbackURL string +} func (d *MistCallbackHandlersCollection) Trigger() httprouter.Handle { return func(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { - log.Println("Received Mist Trigger") + if t := req.Header.Get("X-Trigger"); t != "PUSH_END" { + errors.WriteHTTPBadRequest(w, "Unsupported X-Trigger", fmt.Errorf("unknown trigger '%s'", t)) + return + } payload, err := ioutil.ReadAll(req.Body) if err != nil { errors.WriteHTTPInternalServerError(w, "Cannot read payload", err) return } + lines := strings.Split(strings.TrimSuffix(string(payload), "\n"), "\n") + if len(lines) < 2 { + errors.WriteHTTPBadRequest(w, "Bad request payload", fmt.Errorf("unknown payload '%s'", string(payload))) + return + } - // TODO: Handle trigger results: 1) Check the trigger name, 2) Call callbackURL, 3) Perform stream cleanup - fmt.Println(string(payload)) - io.WriteString(w, "OK") + // stream name is the second line in the Mist Trigger payload + s := lines[1] + // when uploading is done, remove trigger and stream from Mist + errT := d.MistClient.DeleteTrigger(s, "PUSH_END") + errS := d.MistClient.DeleteStream(s) + if errT != nil { + errors.WriteHTTPInternalServerError(w, fmt.Sprintf("Cannot remove PUSH_END trigger for stream '%s'", s), errT) + return + } + if errS != nil { + errors.WriteHTTPInternalServerError(w, fmt.Sprintf("Cannot remove stream '%s'", s), errS) + return + } + + callbackClient := clients.NewCallbackClient() + if err := callbackClient.SendTranscodeStatus(d.CallbackURL, clients.TranscodeStatusTranscoding, 0.0); err != nil { + errors.WriteHTTPInternalServerError(w, "Cannot send transcode status", err) + } + + // TODO: start transcoding } } diff --git a/handlers/handlers_test.go b/handlers/handlers_test.go index e56b85af9..6d5e3176b 100644 --- a/handlers/handlers_test.go +++ b/handlers/handlers_test.go @@ -2,21 +2,21 @@ package handlers import ( "bytes" + "github.com/julienschmidt/httprouter" + "github.com/stretchr/testify/require" "net/http" "net/http/httptest" "testing" - - "github.com/julienschmidt/httprouter" - "github.com/stretchr/testify/require" ) func TestOKHandler(t *testing.T) { require := require.New(t) + catalystApiHandlers := CatalystAPIHandlersCollection{} router := httprouter.New() req, _ := http.NewRequest("GET", "/ok", nil) rr := httptest.NewRecorder() - router.GET("/ok", CatalystAPIHandlers.Ok()) + router.GET("/ok", catalystApiHandlers.Ok()) router.ServeHTTP(rr, req) require.Equal(rr.Body.String(), "OK") @@ -25,21 +25,24 @@ func TestOKHandler(t *testing.T) { func TestSuccessfulVODUploadHandler(t *testing.T) { require := require.New(t) - replaceProcessVod := processUpload - processUpload = func(url string) error { return nil } - defer func() { processUpload = replaceProcessVod }() - + catalystApiHandlers := CatalystAPIHandlersCollection{MistClient: StubMistClient{}} var jsonData = []byte(`{ "url": "http://localhost/input", "callback_url": "http://localhost/callback", "output_locations": [ { "type": "object_store", - "url": "memory://localhost/output" + "url": "memory://localhost/output", + "outputs": { + "source_segments": true + } }, { "type": "pinata", - "pinata_access_key": "abc" + "pinata_access_key": "abc", + "outputs": { + "transcoded_segments": true + } } ] }`) @@ -50,7 +53,7 @@ func TestSuccessfulVODUploadHandler(t *testing.T) { req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() - router.POST("/api/vod", CatalystAPIHandlers.UploadVOD()) + router.POST("/api/vod", catalystApiHandlers.UploadVOD()) router.ServeHTTP(rr, req) require.Equal(rr.Result().StatusCode, 200) @@ -60,6 +63,7 @@ func TestSuccessfulVODUploadHandler(t *testing.T) { func TestInvalidPayloadVODUploadHandler(t *testing.T) { require := require.New(t) + catalystApiHandlers := CatalystAPIHandlersCollection{MistClient: StubMistClient{}} badRequests := [][]byte{ // missing url []byte(`{ @@ -71,7 +75,7 @@ func TestInvalidPayloadVODUploadHandler(t *testing.T) { "url": "http://localhost/input", "output_locations": [ { "type": "object_store", "url": "memory://localhost/output" } ] }`), - // missing output_locatoins + // missing output_locations []byte(`{ "url": "http://localhost/input", "callback_url": "http://localhost/callback" @@ -110,7 +114,7 @@ func TestInvalidPayloadVODUploadHandler(t *testing.T) { router := httprouter.New() - router.POST("/api/vod", CatalystAPIHandlers.UploadVOD()) + router.POST("/api/vod", catalystApiHandlers.UploadVOD()) for _, payload := range badRequests { req, _ := http.NewRequest("POST", "/api/vod", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") @@ -124,6 +128,7 @@ func TestInvalidPayloadVODUploadHandler(t *testing.T) { func TestWrongContentTypeVODUploadHandler(t *testing.T) { require := require.New(t) + catalystApiHandlers := CatalystAPIHandlersCollection{MistClient: StubMistClient{}} var jsonData = []byte(`{ "url": "http://localhost/input", "callback_url": "http://localhost/callback", @@ -140,7 +145,7 @@ func TestWrongContentTypeVODUploadHandler(t *testing.T) { req.Header.Set("Content-Type", "json") rr := httptest.NewRecorder() - router.POST("/api/vod", CatalystAPIHandlers.UploadVOD()) + router.POST("/api/vod", catalystApiHandlers.UploadVOD()) router.ServeHTTP(rr, req) require.Equal(rr.Result().StatusCode, 415) diff --git a/handlers/mock_client.go b/handlers/mock_client.go new file mode 100644 index 000000000..d4839954f --- /dev/null +++ b/handlers/mock_client.go @@ -0,0 +1,23 @@ +package handlers + +type StubMistClient struct{} + +func (s StubMistClient) AddStream(streamName, sourceUrl string) error { + return nil +} + +func (s StubMistClient) PushStart(streamName, targetURL string) error { + return nil +} + +func (s StubMistClient) DeleteStream(streamName string) error { + return nil +} + +func (s StubMistClient) AddTrigger(streamName, triggerName string) error { + return nil +} + +func (s StubMistClient) DeleteTrigger(streamName, triggerName string) error { + return nil +} diff --git a/middleware/middleware_test.go b/middleware/middleware_test.go index 749ead846..f71cbd37e 100644 --- a/middleware/middleware_test.go +++ b/middleware/middleware_test.go @@ -16,7 +16,8 @@ func TestNoAuthHeader(t *testing.T) { router := httprouter.New() req, _ := http.NewRequest("GET", "/ok", nil) rr := httptest.NewRecorder() - router.GET("/ok", IsAuthorized(handlers.CatalystAPIHandlers.Ok())) + catalystApiHandlers := handlers.CatalystAPIHandlersCollection{} + router.GET("/ok", IsAuthorized(catalystApiHandlers.Ok())) router.ServeHTTP(rr, req) require.Equal(rr.Code, 401, "should return 401") @@ -31,7 +32,8 @@ func TestWrongKey(t *testing.T) { req.Header.Set("Authorization", "Bearer gibberish") rr := httptest.NewRecorder() - router.GET("/ok", IsAuthorized(handlers.CatalystAPIHandlers.Ok())) + catalystApiHandlers := handlers.CatalystAPIHandlersCollection{} + router.GET("/ok", IsAuthorized(catalystApiHandlers.Ok())) router.ServeHTTP(rr, req) require.Equal(rr.Code, 401, "should return 401") From 5eb713f13e0cd39f74986d51a4bcdcbe9a4e1e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Leszko?= Date: Fri, 12 Aug 2022 09:49:29 +0200 Subject: [PATCH 02/11] Process Upload VOD --- cmd/http-server/http-server.go | 3 +-- cmd/http-server/http-server_test.go | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/http-server/http-server.go b/cmd/http-server/http-server.go index 37e388a66..3c915026e 100644 --- a/cmd/http-server/http-server.go +++ b/cmd/http-server/http-server.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" stdlog "log" + "net/http" "os" "github.com/go-kit/kit/log" @@ -13,8 +14,6 @@ import ( "github.com/livepeer/catalyst-api/handlers" "github.com/livepeer/catalyst-api/middleware" "github.com/livepeer/livepeer-data/pkg/mistconnector" - - "net/http" ) func main() { diff --git a/cmd/http-server/http-server_test.go b/cmd/http-server/http-server_test.go index 6eabf78d4..568e6def5 100644 --- a/cmd/http-server/http-server_test.go +++ b/cmd/http-server/http-server_test.go @@ -1,8 +1,9 @@ package main import ( - "github.com/stretchr/testify/require" "testing" + + "github.com/stretchr/testify/require" ) func TestInitServer(t *testing.T) { From a48e3ced4924d5b71ee62656c5046c6936950474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Leszko?= Date: Fri, 12 Aug 2022 11:29:41 +0200 Subject: [PATCH 03/11] Minor updates --- handlers/client.go | 2 +- handlers/handlers.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/handlers/client.go b/handlers/client.go index 39fa5dc4e..0a28dfbd3 100644 --- a/handlers/client.go +++ b/handlers/client.go @@ -345,5 +345,5 @@ func wrapErr(err error, streamName string) error { // TODO: Delete auth, since DMS API will run on localhost, so it does not need authentation func auth(command string) string { - return fmt.Sprintf("{%s,%s}", `"authorize":{"username":"test","password":"45bef56e3d0ed618571f52e9a07a448a"}`, command) + return fmt.Sprintf("{%s,%s}", `"authorize":{"username":"test","password":"f9ff7ff5eafc90be25d5a4fba79f0fa0"}`, command) } diff --git a/handlers/handlers.go b/handlers/handlers.go index 332b5d165..d7cb8b134 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -29,7 +29,6 @@ func (d *CatalystAPIHandlersCollection) Ok() httprouter.Handle { } func (d *CatalystAPIHandlersCollection) UploadVOD() httprouter.Handle { - // TODO: Update schema with "outputs" schemaLoader := gojsonschema.NewStringLoader(`{ "type": "object", "properties": { @@ -220,6 +219,7 @@ func (d *MistCallbackHandlersCollection) Trigger() httprouter.Handle { errors.WriteHTTPInternalServerError(w, "Cannot send transcode status", err) } + // TODO: add timeout for the upload // TODO: start transcoding } } From bc58726c5dc62a009315658bb7cd0d951ecbefe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Leszko?= Date: Fri, 12 Aug 2022 11:31:31 +0200 Subject: [PATCH 04/11] Minor updates --- handlers/handlers.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/handlers/handlers.go b/handlers/handlers.go index d7cb8b134..bf5fdf385 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -88,7 +88,6 @@ func (d *CatalystAPIHandlersCollection) UploadVOD() httprouter.Handle { return func(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { var uploadVODRequest UploadVODRequest - // TODO: Send Errors to Callback URL if !HasContentType(req, "application/json") { errors.WriteHTTPUnsupportedMediaType(w, "Requires application/json content type", nil) return @@ -219,7 +218,7 @@ func (d *MistCallbackHandlersCollection) Trigger() httprouter.Handle { errors.WriteHTTPInternalServerError(w, "Cannot send transcode status", err) } - // TODO: add timeout for the upload + // TODO: add timeout for the stream upload // TODO: start transcoding } } From 83c0264ade9e16e63b774395e13963c6807510d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Leszko?= Date: Fri, 12 Aug 2022 15:27:25 +0200 Subject: [PATCH 05/11] Add stream cache --- cmd/http-server/http-server.go | 5 +++-- handlers/client_test.go | 4 +++- handlers/handlers.go | 26 +++++++++++++++++--------- handlers/handlers_test.go | 2 +- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/cmd/http-server/http-server.go b/cmd/http-server/http-server.go index 3c915026e..01ca91e01 100644 --- a/cmd/http-server/http-server.go +++ b/cmd/http-server/http-server.go @@ -50,8 +50,9 @@ func StartCatalystAPIRouter(mc *handlers.MistClient) *httprouter.Router { logger = log.With(logger, "ts", log.DefaultTimestampUTC) withLogging := middleware.LogRequest(logger) - catalystApiHandlers := &handlers.CatalystAPIHandlersCollection{MistClient: mc} - mistCallbackHandlers := &handlers.MistCallbackHandlersCollection{MistClient: mc} + sc := make(map[string]handlers.StreamInfo) + catalystApiHandlers := &handlers.CatalystAPIHandlersCollection{MistClient: mc, StreamCache: sc} + mistCallbackHandlers := &handlers.MistCallbackHandlersCollection{MistClient: mc, StreamCache: sc} router.GET("/ok", withLogging(middleware.IsAuthorized(catalystApiHandlers.Ok()))) router.POST("/api/vod", withLogging(middleware.IsAuthorized(catalystApiHandlers.UploadVOD()))) diff --git a/handlers/client_test.go b/handlers/client_test.go index b24247220..dabd53071 100644 --- a/handlers/client_test.go +++ b/handlers/client_test.go @@ -154,5 +154,7 @@ func TestWorkflow(t *testing.T) { ApiUrl: "http://localhost:4242/api2", TriggerCallback: "http://host.docker.internal:4949/api/mist/trigger"}, } - require.NoError(t, catalystHandlers.processUploadVOD("/home/Sample-Video-File-For-Testing.mp4", "/media/recording/out.m3u8")) + streamName := randomStreamName("catalyst_vod_") + catalystHandlers.StreamCache[streamName] = StreamInfo{callbackUrl: "http://some-handler.com"} + require.NoError(t, catalystHandlers.processUploadVOD(streamName, "/home/Sample-Video-File-For-Testing.mp4", "/media/recording/out.m3u8")) } diff --git a/handlers/handlers.go b/handlers/handlers.go index bf5fdf385..8f25009cf 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -17,9 +17,13 @@ import ( "github.com/xeipuuv/gojsonschema" ) +type StreamInfo struct { + callbackUrl string +} + type CatalystAPIHandlersCollection struct { MistClient MistAPIClient - CallbackURL string + StreamCache map[string]StreamInfo } func (d *CatalystAPIHandlersCollection) Ok() httprouter.Handle { @@ -118,13 +122,16 @@ func (d *CatalystAPIHandlersCollection) UploadVOD() httprouter.Handle { return } + streamName := randomStreamName("catalyst_vod_") + d.StreamCache[streamName] = StreamInfo{callbackUrl: uploadVODRequest.CallbackUrl} + // process the request - if err := d.processUploadVOD(uploadVODRequest.Url, tURL); err != nil { + if err := d.processUploadVOD(streamName, uploadVODRequest.Url, tURL); err != nil { errors.WriteHTTPInternalServerError(w, "Cannot process upload VOD request", err) } callbackClient := clients.NewCallbackClient() - if err := callbackClient.SendTranscodeStatus(d.CallbackURL, clients.TranscodeStatusPreparing, 0.0); err != nil { + if err := callbackClient.SendTranscodeStatus(uploadVODRequest.CallbackUrl, clients.TranscodeStatusPreparing, 0.0); err != nil { errors.WriteHTTPInternalServerError(w, "Cannot send transcode status", err) } @@ -150,15 +157,14 @@ func HasContentType(r *http.Request, mimetype string) bool { return false } -func (d *CatalystAPIHandlersCollection) processUploadVOD(sourceUrl, targetUrl string) error { - streamName := randomStreamName("catalyst_vod_") - if err := d.MistClient.AddStream(streamName, sourceUrl); err != nil { +func (d *CatalystAPIHandlersCollection) processUploadVOD(streamName, sourceURL, targetURL string) error { + if err := d.MistClient.AddStream(streamName, sourceURL); err != nil { return err } if err := d.MistClient.AddTrigger(streamName, "PUSH_END"); err != nil { return err } - if err := d.MistClient.PushStart(streamName, targetUrl); err != nil { + if err := d.MistClient.PushStart(streamName, targetURL); err != nil { return err } @@ -179,7 +185,7 @@ func randomStreamName(prefix string) string { type MistCallbackHandlersCollection struct { MistClient MistAPIClient - CallbackURL string + StreamCache map[string]StreamInfo } func (d *MistCallbackHandlersCollection) Trigger() httprouter.Handle { @@ -214,10 +220,12 @@ func (d *MistCallbackHandlersCollection) Trigger() httprouter.Handle { } callbackClient := clients.NewCallbackClient() - if err := callbackClient.SendTranscodeStatus(d.CallbackURL, clients.TranscodeStatusTranscoding, 0.0); err != nil { + if err := callbackClient.SendTranscodeStatus(d.StreamCache[s].callbackUrl, clients.TranscodeStatusTranscoding, 0.0); err != nil { errors.WriteHTTPInternalServerError(w, "Cannot send transcode status", err) } + delete(d.StreamCache, s) + // TODO: add timeout for the stream upload // TODO: start transcoding } diff --git a/handlers/handlers_test.go b/handlers/handlers_test.go index 6d5e3176b..6b3327f71 100644 --- a/handlers/handlers_test.go +++ b/handlers/handlers_test.go @@ -25,7 +25,7 @@ func TestOKHandler(t *testing.T) { func TestSuccessfulVODUploadHandler(t *testing.T) { require := require.New(t) - catalystApiHandlers := CatalystAPIHandlersCollection{MistClient: StubMistClient{}} + catalystApiHandlers := CatalystAPIHandlersCollection{MistClient: StubMistClient{}, StreamCache: make(map[string]StreamInfo)} var jsonData = []byte(`{ "url": "http://localhost/input", "callback_url": "http://localhost/callback", From c1c78931d17602957e53de8dfc6d50bd65a952b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Leszko?= Date: Fri, 12 Aug 2022 15:28:43 +0200 Subject: [PATCH 06/11] Remove parts for local testing --- handlers/client.go | 7 +------ handlers/client_test.go | 12 ------------ 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/handlers/client.go b/handlers/client.go index 0a28dfbd3..81862eecd 100644 --- a/handlers/client.go +++ b/handlers/client.go @@ -104,7 +104,7 @@ func (mc *MistClient) sendCommand(command interface{}) (string, error) { if err != nil { return "", err } - payload := payloadFor(auth(c)) + payload := payloadFor(c) resp, err := http.Post(mc.ApiUrl, "application/json", bytes.NewBuffer([]byte(payload))) if err != nil { return "", err @@ -342,8 +342,3 @@ func wrapErr(err error, streamName string) error { } return nil } - -// TODO: Delete auth, since DMS API will run on localhost, so it does not need authentation -func auth(command string) string { - return fmt.Sprintf("{%s,%s}", `"authorize":{"username":"test","password":"f9ff7ff5eafc90be25d5a4fba79f0fa0"}`, command) -} diff --git a/handlers/client_test.go b/handlers/client_test.go index dabd53071..daf1f7b50 100644 --- a/handlers/client_test.go +++ b/handlers/client_test.go @@ -146,15 +146,3 @@ func TestResponseValidation(t *testing.T) { require.Error(validateAddTrigger("catalyst_vod_gedhbdhc", "PUSH_END", `{"LTS":1,"authorize":{"status":"OK"},"config":{"accesslog":"LOG","controller":{"interface":null,"port":null,"username":null},"debug":null,"defaultStream":null,"iid":"IIcEj|Z\\|^lbDbjg","limits":null,"location":{"lat":0.0000000000,"lon":0.0000000000,"name":""},"prometheus":"koekjes","protocols":[{"connector":"AAC","online":"Enabled"},{"connector":"CMAF","online":"Enabled"},{"connector":"DTSC","online":1},{"connector":"EBML","online":"Enabled"},{"connector":"FLV","online":"Enabled"},{"connector":"H264","online":"Enabled"},{"connector":"HDS","online":"Enabled"},{"connector":"HLS","online":1},{"connector":"HTTP","online":1},{"connector":"HTTPTS","online":"Enabled"},{"connector":"JSON","online":"Enabled"},{"connector":"MP3","online":"Enabled"},{"connector":"MP4","online":"Enabled"},{"connector":"OGG","online":"Enabled"},{"connector":"RTMP","online":1},{"connector":"RTSP","online":1},{"connector":"SDP","online":"Enabled"},{"connector":"SRT","online":"Enabled"},{"connector":"TSSRT","online":1},{"connector":"WAV","online":"Enabled"},{"connector":"WebRTC","online":"Enabled"},{"connector":null,"online":"Missing connector name"}],"serverid":null,"sessionInputMode":"14","sessionOutputMode":"14","sessionStreamInfoMode":"1","sessionUnspecifiedMode":"0","sessionViewerMode":"14","sidMode":"0","time":1660027761,"triggers":{"PUSH_END":[{"handler":"http://host.docker.internal:8080/api/mist/trigger","streams":["some-other-stream"],"sync":false}],"RECORDING_END":null},"trustedproxy":[],"version":"eb84bc4ba743885734c60b312ca97ed07311d86f Generic_64"}}`, nil)) require.Error(validateDeleteTrigger("catalyst_vod_gedhbdhc", "PUSH_END", `{"LTS":1,"authorize":{"status":"OK"},"config":{"accesslog":"LOG","controller":{"interface":null,"port":null,"username":null},"debug":null,"defaultStream":null,"iid":"IIcEj|Z\\|^lbDbjg","limits":null,"location":{"lat":0.0000000000,"lon":0.0000000000,"name":""},"prometheus":"koekjes","protocols":[{"connector":"AAC","online":"Enabled"},{"connector":"CMAF","online":"Enabled"},{"connector":"DTSC","online":1},{"connector":"EBML","online":"Enabled"},{"connector":"FLV","online":"Enabled"},{"connector":"H264","online":"Enabled"},{"connector":"HDS","online":"Enabled"},{"connector":"HLS","online":1},{"connector":"HTTP","online":1},{"connector":"HTTPTS","online":"Enabled"},{"connector":"JSON","online":"Enabled"},{"connector":"MP3","online":"Enabled"},{"connector":"MP4","online":"Enabled"},{"connector":"OGG","online":"Enabled"},{"connector":"RTMP","online":1},{"connector":"RTSP","online":1},{"connector":"SDP","online":"Enabled"},{"connector":"SRT","online":"Enabled"},{"connector":"TSSRT","online":1},{"connector":"WAV","online":"Enabled"},{"connector":"WebRTC","online":"Enabled"},{"connector":null,"online":"Missing connector name"}],"serverid":null,"sessionInputMode":"14","sessionOutputMode":"14","sessionStreamInfoMode":"1","sessionUnspecifiedMode":"0","sessionViewerMode":"14","sidMode":"0","time":1660027761,"triggers":{"PUSH_END":[{"handler":"http://host.docker.internal:8080/api/mist/trigger","streams":["catalyst_vod_gedhbdhc"],"sync":false}],"RECORDING_END":null},"trustedproxy":[],"version":"eb84bc4ba743885734c60b312ca97ed07311d86f Generic_64"}}`, nil)) } - -// TODO: Remove after initial testing -func TestWorkflow(t *testing.T) { - // first copy file into /home/Big_Buck_Bunny_1080_10s_1MB.mp4 - catalystHandlers := CatalystAPIHandlersCollection{MistClient: &MistClient{ - ApiUrl: "http://localhost:4242/api2", - TriggerCallback: "http://host.docker.internal:4949/api/mist/trigger"}, - } - streamName := randomStreamName("catalyst_vod_") - catalystHandlers.StreamCache[streamName] = StreamInfo{callbackUrl: "http://some-handler.com"} - require.NoError(t, catalystHandlers.processUploadVOD(streamName, "/home/Sample-Video-File-For-Testing.mp4", "/media/recording/out.m3u8")) -} From 02918e0f95f9b2fe0463dd988769a7d3f2c75ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Leszko?= Date: Fri, 12 Aug 2022 15:29:55 +0200 Subject: [PATCH 07/11] Remove parts for local testing --- cmd/http-server/http-server.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/http-server/http-server.go b/cmd/http-server/http-server.go index 01ca91e01..992c0e7b2 100644 --- a/cmd/http-server/http-server.go +++ b/cmd/http-server/http-server.go @@ -29,8 +29,7 @@ func main() { mc := &handlers.MistClient{ ApiUrl: fmt.Sprintf("http://localhost:%d/api2", *mistPort), - TriggerCallback: "http://host.docker.internal:4949/api/mist/trigger", - //TriggerCallback: fmt.Sprintf("http://localhost:%d/api/mist/trigger", *port), + TriggerCallback: fmt.Sprintf("http://localhost:%d/api/mist/trigger", *port), } listen := fmt.Sprintf("localhost:%d", *port) From 170884e43d17cc61b2f3ea64b486b8ce46a3c95d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Leszko?= Date: Fri, 12 Aug 2022 15:30:34 +0200 Subject: [PATCH 08/11] Remove parts for local testing --- handlers/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/handlers/client.go b/handlers/client.go index 81862eecd..119bb8dbc 100644 --- a/handlers/client.go +++ b/handlers/client.go @@ -235,6 +235,7 @@ func deleteAllTriggersFor(triggers []ConfigTrigger, streamName string) []ConfigT } func commandGetTriggers() MistConfig { + // send an empty config struct returns the current Mist configuration return MistConfig{} } From 941f5d581e4abd3b52ae7cfcc4464d76cd3839f4 Mon Sep 17 00:00:00 2001 From: Thom Shutt Date: Fri, 12 Aug 2022 16:22:17 +0100 Subject: [PATCH 09/11] Fix failing test (no callback server to send to) --- Makefile | 10 +++++++++- handlers/handlers_test.go | 22 ++++++++++++++-------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index f036929d0..23f3ef27b 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,16 @@ GO_BUILD_DIR?=build/ ldflags := -X 'github.com/livepeer/catalyst-api/config.Version=$(shell git rev-parse HEAD)' .PHONY: all -all: build-server +all: build-server fmt test .PHONY: build-server build-server: go build -ldflags="$(ldflags)" -o "$(GO_BUILD_DIR)catalyst-api" cmd/http-server/http-server.go + +.PHONY: fmt +fmt: + go fmt ./... + +.PHONY: test +test: + go test -race ./... diff --git a/handlers/handlers_test.go b/handlers/handlers_test.go index 6b3327f71..52346ee42 100644 --- a/handlers/handlers_test.go +++ b/handlers/handlers_test.go @@ -2,11 +2,13 @@ package handlers import ( "bytes" - "github.com/julienschmidt/httprouter" - "github.com/stretchr/testify/require" "net/http" "net/http/httptest" + "strings" "testing" + + "github.com/julienschmidt/httprouter" + "github.com/stretchr/testify/require" ) func TestOKHandler(t *testing.T) { @@ -25,10 +27,13 @@ func TestOKHandler(t *testing.T) { func TestSuccessfulVODUploadHandler(t *testing.T) { require := require.New(t) + callbackServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + defer callbackServer.Close() + catalystApiHandlers := CatalystAPIHandlersCollection{MistClient: StubMistClient{}, StreamCache: make(map[string]StreamInfo)} - var jsonData = []byte(`{ + var jsonData = `{ "url": "http://localhost/input", - "callback_url": "http://localhost/callback", + "callback_url": "CALLBACK_URL", "output_locations": [ { "type": "object_store", @@ -45,19 +50,20 @@ func TestSuccessfulVODUploadHandler(t *testing.T) { } } ] - }`) + }` + jsonData = strings.ReplaceAll(jsonData, "CALLBACK_URL", callbackServer.URL) router := httprouter.New() - req, _ := http.NewRequest("POST", "/api/vod", bytes.NewBuffer(jsonData)) + req, _ := http.NewRequest("POST", "/api/vod", bytes.NewBuffer([]byte(jsonData))) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() router.POST("/api/vod", catalystApiHandlers.UploadVOD()) router.ServeHTTP(rr, req) - require.Equal(rr.Result().StatusCode, 200) - require.Equal(rr.Body.String(), "2") + require.Equal(http.StatusOK, rr.Result().StatusCode) + require.Equal("2", rr.Body.String()) } func TestInvalidPayloadVODUploadHandler(t *testing.T) { From f2f7103f8e93c100637c440685a1741231e1452c Mon Sep 17 00:00:00 2001 From: Thom Shutt Date: Fri, 12 Aug 2022 16:25:15 +0100 Subject: [PATCH 10/11] Remove race detection for now --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 23f3ef27b..46790385d 100644 --- a/Makefile +++ b/Makefile @@ -15,4 +15,4 @@ fmt: .PHONY: test test: - go test -race ./... + go test ./... From fbc38e3aaf73e1999ebc092a6bbd6704b0fa8d07 Mon Sep 17 00:00:00 2001 From: Thom Shutt Date: Fri, 12 Aug 2022 16:38:06 +0100 Subject: [PATCH 11/11] Specify build Makefile target in build workflow --- .github/workflows/build.yaml | 6 +++--- Makefile | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 383b6f08b..196d71ecf 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -55,7 +55,7 @@ jobs: - name: Build run: | mkdir -p build/ releases/ - make all GO_BUILD_DIR="build/" + make build GO_BUILD_DIR="build/" - name: Archive binaries for windows if: matrix.platform == 'windows' @@ -126,7 +126,7 @@ jobs: - name: Build run: | mkdir -p build/ releases/ - GOARCH="${{ matrix.arch }}" make -j4 all GO_BUILD_DIR="build/" + GOARCH="${{ matrix.arch }}" make -j4 build GO_BUILD_DIR="build/" cd build/ for file in $(find . -type f -perm -a+x) do @@ -167,4 +167,4 @@ jobs: uses: actions/upload-artifact@master with: name: release-artifacts - path: releases/ \ No newline at end of file + path: releases/ diff --git a/Makefile b/Makefile index 46790385d..931cfd43e 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,10 @@ GO_BUILD_DIR?=build/ ldflags := -X 'github.com/livepeer/catalyst-api/config.Version=$(shell git rev-parse HEAD)' .PHONY: all -all: build-server fmt test +all: build fmt test -.PHONY: build-server -build-server: +.PHONY: build +build: go build -ldflags="$(ldflags)" -o "$(GO_BUILD_DIR)catalyst-api" cmd/http-server/http-server.go .PHONY: fmt