From 4e62f181fa87d6473e7c526a4c448093cae15c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Budai?= Date: Fri, 6 Dec 2019 11:07:13 +0100 Subject: [PATCH] api: implement /compose/logs route The implementation is just a stub returning always the same tar archive. The ability to return actual logs will be implemented in the future - osbuild isn't currently returning any logs. --- internal/weldr/api.go | 55 ++++++++++++++++++++++++++++++ internal/weldr/api_test.go | 69 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/internal/weldr/api.go b/internal/weldr/api.go index 742c44a55..f9c266abc 100644 --- a/internal/weldr/api.go +++ b/internal/weldr/api.go @@ -1,6 +1,7 @@ package weldr import ( + "archive/tar" "encoding/json" "errors" "fmt" @@ -92,6 +93,7 @@ func New(rpmmd rpmmd.RPMMD, distro distro.Distro, logger *log.Logger, store *sto api.router.GET("/api/v:version/compose/finished", api.composeFinishedHandler) api.router.GET("/api/v:version/compose/failed", api.composeFailedHandler) api.router.GET("/api/v:version/compose/image/:uuid", api.composeImageHandler) + api.router.GET("/api/v:version/compose/logs/:uuid", api.composeLogsHandler) api.router.POST("/api/v:version/compose/uploads/schedule/:uuid", api.uploadsScheduleHandler) api.router.DELETE("/api/v:version/upload/delete/:uuid", api.uploadsDeleteHandler) @@ -1463,6 +1465,59 @@ func (api *API) composeImageHandler(writer http.ResponseWriter, request *http.Re io.Copy(writer, file) } +func (api *API) composeLogsHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) { + if !verifyRequestVersion(writer, params, 0) { + return + } + + uuidString := params.ByName("uuid") + id, err := uuid.Parse(uuidString) + if err != nil { + errors := responseError{ + ID: "UnknownUUID", + Msg: fmt.Sprintf("%s is not a valid build uuid", uuidString), + } + statusResponseError(writer, http.StatusBadRequest, errors) + return + } + + compose, exists := api.store.GetCompose(id) + if !exists { + errors := responseError{ + ID: "UnknownUUID", + Msg: fmt.Sprintf("Compose %s doesn't exist", uuidString), + } + statusResponseError(writer, http.StatusBadRequest, errors) + return + } + + if compose.QueueStatus != "FINISHED" && compose.QueueStatus != "FAILED" { + errors := responseError{ + ID: "BuildInWrongState", + Msg: fmt.Sprintf("Build %s not in FINISHED or FAILED state.", uuidString), + } + statusResponseError(writer, http.StatusBadRequest, errors) + return + } + + writer.Header().Set("Content-Disposition", "attachment; filename="+id.String()+"-logs.tar") + writer.Header().Set("Content-Type", "application/x-tar") + + tw := tar.NewWriter(writer) + + // TODO: return real log from osbuild + fileContents := []byte("SUCCESS\n") + + header := &tar.Header{ + Name: "logs/osbuild.log", + Mode: 0644, + Size: int64(len(fileContents)), + } + + tw.WriteHeader(header) + tw.Write(fileContents) +} + func (api *API) composeFinishedHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) { if !verifyRequestVersion(writer, params, 0) { return diff --git a/internal/weldr/api_test.go b/internal/weldr/api_test.go index 893be40e6..36bb1da88 100644 --- a/internal/weldr/api_test.go +++ b/internal/weldr/api_test.go @@ -1,7 +1,9 @@ package weldr_test import ( + "archive/tar" "bytes" + "io" "math/rand" "net/http" "net/http/httptest" @@ -389,6 +391,73 @@ func TestComposeInfo(t *testing.T) { } } +func TestComposeLogs(t *testing.T) { + if len(os.Getenv("OSBUILD_COMPOSER_TEST_EXTERNAL")) > 0 { + t.Skip("This test is for internal testing only") + } + + var successCases = []struct { + Path string + ExpectedContentDisposition string + ExpectedContentType string + ExpectedFileName string + ExpectedFileContent string + }{ + {"/api/v0/compose/logs/30000000-0000-0000-0000-000000000002", "attachment; filename=30000000-0000-0000-0000-000000000002-logs.tar", "application/x-tar", "logs/osbuild.log", "SUCCESS\n"}, + {"/api/v1/compose/logs/30000000-0000-0000-0000-000000000002", "attachment; filename=30000000-0000-0000-0000-000000000002-logs.tar", "application/x-tar", "logs/osbuild.log", "SUCCESS\n"}, + } + + for _, c := range successCases { + api, _ := createWeldrAPI(rpmmd_mock.BaseFixture) + + response := test.SendHTTP(api, false, "GET", c.Path, "") + if response.Header.Get("content-disposition") != c.ExpectedContentDisposition { + t.Errorf("%s: expected content-disposition: %s, but got: %s", c.Path, c.ExpectedContentDisposition, response.Header.Get("content-disposition")) + } + + if response.Header.Get("content-type") != c.ExpectedContentType { + t.Errorf("%s: expected content-type: %s, but got: %s", c.Path, c.ExpectedContentType, response.Header.Get("content-type")) + } + + tr := tar.NewReader(response.Body) + h, err := tr.Next() + + if err != nil { + t.Errorf("untarring failed with error: %s", err.Error()) + } + + if h.Name != c.ExpectedFileName { + t.Errorf("%s: expected log content: %s, but got: %s", c.Path, c.ExpectedFileName, h.Name) + } + + var buffer bytes.Buffer + + io.Copy(&buffer, tr) + + if buffer.String() != c.ExpectedFileContent { + t.Errorf("%s: expected log content: %s, but got: %s", c.Path, c.ExpectedFileContent, buffer.String()) + } + } + + var failureCases = []struct { + Path string + ExpectedJSON string + }{ + {"/api/v1/compose/logs/30000000-0000-0000-0000", `{"status":false,"errors":[{"id":"UnknownUUID","msg":"30000000-0000-0000-0000 is not a valid build uuid"}]}`}, + {"/api/v1/compose/logs/42000000-0000-0000-0000-000000000000", `{"status":false,"errors":[{"id":"UnknownUUID","msg":"Compose 42000000-0000-0000-0000-000000000000 doesn't exist"}]}`}, + {"/api/v1/compose/logs/30000000-0000-0000-0000-000000000000", `{"status":false,"errors":[{"id":"BuildInWrongState","msg":"Build 30000000-0000-0000-0000-000000000000 not in FINISHED or FAILED state."}]}`}, + } + + if len(os.Getenv("OSBUILD_COMPOSER_TEST_EXTERNAL")) > 0 { + t.Skip("This test is for internal testing only") + } + + for _, c := range failureCases { + api, _ := createWeldrAPI(rpmmd_mock.BaseFixture) + test.TestRoute(t, api, false, "GET", c.Path, "", http.StatusBadRequest, c.ExpectedJSON) + } +} + func TestComposeQueue(t *testing.T) { var cases = []struct { Fixture rpmmd_mock.FixtureGenerator