From 6bbc89d5f3ef86400c5bbf89284dae205c41190e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Budai?= Date: Fri, 6 Dec 2019 16:56:22 +0100 Subject: [PATCH] api: implement /compose/delete route --- internal/jobqueue/job.go | 18 ++++++++--- internal/store/store.go | 23 ++++++++++++++ internal/weldr/api.go | 63 ++++++++++++++++++++++++++++++++++++++ internal/weldr/api_test.go | 36 ++++++++++++++++++++++ 4 files changed, 136 insertions(+), 4 deletions(-) diff --git a/internal/jobqueue/job.go b/internal/jobqueue/job.go index e1820b2b0..db04aa019 100644 --- a/internal/jobqueue/job.go +++ b/internal/jobqueue/job.go @@ -99,10 +99,13 @@ func (job *Job) Run(d distro.Distro) (*store.Image, error, []error) { for _, t := range job.Targets { switch options := t.Options.(type) { case *target.LocalTargetOptions: - cp := exec.Command("cp", "-a", "-L", "/var/cache/osbuild-composer/store/refs/"+result.OutputID+"/.", options.Location) - cp.Stderr = os.Stderr - cp.Stdout = os.Stdout - err = cp.Run() + err = runCommand("cp", "-a", "-L", "/var/cache/osbuild-composer/store/refs/"+result.OutputID+"/.", options.Location) + if err != nil { + r = append(r, err) + continue + } + + err = runCommand("chown", "-R", "_osbuild-composer:_osbuild-composer", options.Location) if err != nil { r = append(r, err) continue @@ -158,3 +161,10 @@ func (job *Job) Run(d distro.Distro) (*store.Image, error, []error) { return &image, nil, r } + +func runCommand(command string, params ...string) error { + cp := exec.Command(command, params...) + cp.Stderr = os.Stderr + cp.Stdout = os.Stdout + return cp.Run() +} diff --git a/internal/store/store.go b/internal/store/store.go index 318073bc8..6d1c86c86 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -454,6 +454,29 @@ func (s *Store) PushCompose(composeID uuid.UUID, bp *blueprint.Blueprint, compos return nil } +func (s *Store) DeleteCompose(id uuid.UUID) error { + return s.change(func() error { + compose, exists := s.Composes[id] + + if !exists { + return &NotFoundError{} + } + + if compose.QueueStatus != "FINISHED" && compose.QueueStatus != "FAILED" { + return &InvalidRequestError{fmt.Sprintf("Compose %s is not in FINISHED or FAILED.", id)} + } + + delete(s.Composes, id) + + var err error + if s.stateDir != nil { + err = os.RemoveAll(*s.stateDir + "/outputs/" + id.String()) + } + + return err + }) +} + func (s *Store) PopCompose() Job { job := <-s.pendingJobs s.change(func() error { diff --git a/internal/weldr/api.go b/internal/weldr/api.go index f9c266abc..6838f9c7a 100644 --- a/internal/weldr/api.go +++ b/internal/weldr/api.go @@ -86,6 +86,7 @@ func New(rpmmd rpmmd.RPMMD, distro distro.Distro, logger *log.Logger, store *sto api.router.DELETE("/api/v:version/blueprints/workspace/:blueprint", api.blueprintDeleteWorkspaceHandler) api.router.POST("/api/v:version/compose", api.composeHandler) + api.router.DELETE("/api/v:version/compose/delete/:uuids", api.composeDeleteHandler) api.router.GET("/api/v:version/compose/types", api.composeTypesHandler) api.router.GET("/api/v:version/compose/queue", api.composeQueueHandler) api.router.GET("/api/v:version/compose/status/:uuids", api.composeStatusHandler) @@ -1267,6 +1268,68 @@ func (api *API) composeHandler(writer http.ResponseWriter, request *http.Request json.NewEncoder(writer).Encode(reply) } +func (api *API) composeDeleteHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) { + if !verifyRequestVersion(writer, params, 0) { + return + } + + type composeDeleteStatus struct { + UUID uuid.UUID `json:"uuid"` + Status bool `json:"status"` + } + + type composeDeleteError struct { + ID string `json:"id"` + Msg string `json:"msg"` + } + + uuidsParam := params.ByName("uuids") + + results := []composeDeleteStatus{} + errors := []composeDeleteError{} + uuidStrings := strings.Split(uuidsParam, ",") + for _, uuidString := range uuidStrings { + id, err := uuid.Parse(uuidString) + if err != nil { + errors = append(errors, composeDeleteError{ + "UnknownUUID", + fmt.Sprintf("%s is not a valid uuid", uuidString), + }) + } + + err = api.store.DeleteCompose(id) + + if err != nil { + switch err.(type) { + case *store.NotFoundError: + errors = append(errors, composeDeleteError{ + "UnknownUUID", + fmt.Sprintf("compose %s doesn't exist", id), + }) + case *store.InvalidRequestError: + errors = append(errors, composeDeleteError{ + "BuildInWrongState", + err.Error(), + }) + default: + errors = append(errors, composeDeleteError{ + "ComposeError", + fmt.Sprintf("%s: %s", id, err.Error()), + }) + } + } + + results = append(results, composeDeleteStatus{id, true}) + } + + reply := struct { + UUIDs []composeDeleteStatus `json:"uuids"` + Errors []composeDeleteError `json:"errors"` + }{results, errors} + + json.NewEncoder(writer).Encode(reply) +} + func (api *API) composeTypesHandler(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 6c4f947b7..398b9c669 100644 --- a/internal/weldr/api_test.go +++ b/internal/weldr/api_test.go @@ -23,6 +23,7 @@ import ( "github.com/BurntSushi/toml" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" ) func createWeldrAPI(fixtureGenerator rpmmd_mock.FixtureGenerator) (*weldr.API, *store.Store) { @@ -330,6 +331,41 @@ func TestCompose(t *testing.T) { } } +func TestComposeDelete(t *testing.T) { + if len(os.Getenv("OSBUILD_COMPOSER_TEST_EXTERNAL")) > 0 { + t.Skip("This test is for internal testing only") + } + + var cases = []struct { + Path string + ExpectedJSON string + ExpectedIDsInStore []string + }{ + {"/api/v0/compose/delete/30000000-0000-0000-0000-000000000002", `{"uuids":[{"uuid":"30000000-0000-0000-0000-000000000002","status":true}],"errors":[]}`, []string{"30000000-0000-0000-0000-000000000000", "30000000-0000-0000-0000-000000000001", "30000000-0000-0000-0000-000000000003"}}, + {"/api/v0/compose/delete/30000000-0000-0000-0000-000000000002,30000000-0000-0000-0000-000000000003", `{"uuids":[{"uuid":"30000000-0000-0000-0000-000000000002","status":true},{"uuid":"30000000-0000-0000-0000-000000000003","status":true}],"errors":[]}`, []string{"30000000-0000-0000-0000-000000000000", "30000000-0000-0000-0000-000000000001"}}, + {"/api/v0/compose/delete/30000000-0000-0000-0000-000000000003,30000000-0000-0000-0000-000000000000", `{"uuids":[{"uuid":"30000000-0000-0000-0000-000000000003","status":true},{"uuid":"30000000-0000-0000-0000-000000000000","status":true}],"errors":[{"id":"BuildInWrongState","msg":"Compose 30000000-0000-0000-0000-000000000000 is not in FINISHED or FAILED."}]}`, []string{"30000000-0000-0000-0000-000000000000", "30000000-0000-0000-0000-000000000001", "30000000-0000-0000-0000-000000000002"}}, + {"/api/v0/compose/delete/30000000-0000-0000-0000-000000000003,30000000-0000-0000-0000", `{"uuids":[{"uuid":"30000000-0000-0000-0000-000000000003","status":true},{"uuid":"00000000-0000-0000-0000-000000000000","status":true}],"errors":[{"id":"UnknownUUID","msg":"30000000-0000-0000-0000 is not a valid uuid"},{"id":"UnknownUUID","msg":"compose 00000000-0000-0000-0000-000000000000 doesn't exist"}]}`, []string{"30000000-0000-0000-0000-000000000000", "30000000-0000-0000-0000-000000000001", "30000000-0000-0000-0000-000000000002"}}, + {"/api/v0/compose/delete/30000000-0000-0000-0000-000000000003,42000000-0000-0000-0000-000000000000", `{"uuids":[{"uuid":"30000000-0000-0000-0000-000000000003","status":true},{"uuid":"42000000-0000-0000-0000-000000000000","status":true}],"errors":[{"id":"UnknownUUID","msg":"compose 42000000-0000-0000-0000-000000000000 doesn't exist"}]}`, []string{"30000000-0000-0000-0000-000000000000", "30000000-0000-0000-0000-000000000001", "30000000-0000-0000-0000-000000000002"}}, + } + + for _, c := range cases { + api, s := createWeldrAPI(rpmmd_mock.BaseFixture) + test.TestRoute(t, api, false, "DELETE", c.Path, "", http.StatusOK, c.ExpectedJSON) + + idsInStore := []string{} + + for id, _ := range s.Composes { + idsInStore = append(idsInStore, id.String()) + } + + diff := cmp.Diff(idsInStore, c.ExpectedIDsInStore, cmpopts.SortSlices(func(a, b string) bool { return a < b })) + + if diff != "" { + t.Errorf("%s: composes in store are different, expected: %v, got: %v, diff:\n%s", c.Path, c.ExpectedIDsInStore, idsInStore, diff) + } + } +} + func TestComposeStatus(t *testing.T) { var cases = []struct { Fixture rpmmd_mock.FixtureGenerator