diff --git a/internal/cloudapi/v2/errors.go b/internal/cloudapi/v2/errors.go index 4f1cbd0836..36b335a694 100644 --- a/internal/cloudapi/v2/errors.go +++ b/internal/cloudapi/v2/errors.go @@ -73,6 +73,7 @@ const ( ErrorGettingAWSEC2JobStatus ServiceErrorCode = 1018 ErrorGettingJobType ServiceErrorCode = 1019 ErrorTenantNotInContext ServiceErrorCode = 1020 + ErrorGettingComposeList ServiceErrorCode = 1021 // Errors contained within this file ErrorUnspecified ServiceErrorCode = 10000 @@ -153,6 +154,7 @@ func getServiceErrors() serviceErrors { serviceError{ErrorGettingAWSEC2JobStatus, http.StatusInternalServerError, "Unable to get ec2 job status"}, serviceError{ErrorGettingJobType, http.StatusInternalServerError, "Unable to get job type of existing job"}, serviceError{ErrorTenantNotInContext, http.StatusInternalServerError, "Unable to retrieve tenant from request context"}, + serviceError{ErrorGettingComposeList, http.StatusInternalServerError, "Unable to get list of composes"}, serviceError{ErrorUnspecified, http.StatusInternalServerError, "Unspecified internal error "}, serviceError{ErrorNotHTTPError, http.StatusInternalServerError, "Error is not an instance of HTTPError"}, diff --git a/internal/cloudapi/v2/handler.go b/internal/cloudapi/v2/handler.go index f37491a0d4..d92d9c8f7e 100644 --- a/internal/cloudapi/v2/handler.go +++ b/internal/cloudapi/v2/handler.go @@ -2,6 +2,7 @@ package v2 import ( + "cmp" "encoding/json" "fmt" "net/http" @@ -314,6 +315,30 @@ func targetResultToUploadStatus(t *target.TargetResult) (*UploadStatus, error) { return us, nil } +// GetComposeList returns a list of the root job UUIDs +func (h *apiHandlers) GetComposeList(ctx echo.Context) error { + jobs, err := h.server.workers.AllRootJobIDs() + if err != nil { + return HTTPErrorWithInternal(ErrorGettingComposeList, err) + } + + // Gather up the details of each job + var stats []ComposeStatus + for _, jid := range jobs { + s, err := h.getJobIDComposeStatus(jid) + if err != nil { + // TODO log this error? + continue + } + stats = append(stats, s) + } + slices.SortFunc(stats, func(a, b ComposeStatus) int { + return cmp.Compare(a.Id, b.Id) + }) + + return ctx.JSON(http.StatusOK, stats) +} + func (h *apiHandlers) GetComposeStatus(ctx echo.Context, id string) error { return h.server.EnsureJobChannel(h.getComposeStatusImpl)(ctx, id) } diff --git a/internal/cloudapi/v2/v2_test.go b/internal/cloudapi/v2/v2_test.go index eb64fa2d34..064d250769 100644 --- a/internal/cloudapi/v2/v2_test.go +++ b/internal/cloudapi/v2/v2_test.go @@ -1545,3 +1545,43 @@ func TestImageFromCompose(t *testing.T) { } }`, imgJobId, imgJobId)) } + +func TestComposesRoute(t *testing.T) { + srv, _, _, cancel := newV2Server(t, t.TempDir(), []string{""}, false, false) + defer cancel() + + // Make a compose so it has something to list + reply := test.TestRouteWithReply(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(` + { + "distribution": "%s", + "image_request":{ + "architecture": "%s", + "image_type": "%s", + "repositories": [{ + "baseurl": "somerepo.org", + "rhsm": false + }], + "upload_options": { + "region": "eu-central-1", + "snapshot_name": "name", + "share_with_accounts": ["123456789012","234567890123"] + } + } + }`, test_distro.TestDistro1Name, test_distro.TestArch3Name, string(v2.ImageTypesAws)), http.StatusCreated, ` + { + "href": "/api/image-builder-composer/v2/compose", + "kind": "ComposeId" + }`, "id") + + // Extract the compose ID to use to test the list response + var composeReply v2.ComposeId + err := json.Unmarshal(reply, &composeReply) + require.NoError(t, err) + jobID, err := uuid.Parse(composeReply.Id) + require.NoError(t, err) + + // List root composes + test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", "/api/image-builder-composer/v2/composes/", ``, + http.StatusOK, fmt.Sprintf(`[{"href":"/api/image-builder-composer/v2/composes/%[1]s", "id":"%[1]s", "image_status":{"status":"pending"}, "kind":"ComposeStatus", "status":"pending"}]`, + jobID.String())) +} diff --git a/internal/worker/server.go b/internal/worker/server.go index a5e2c8d5e7..55215e9bb9 100644 --- a/internal/worker/server.go +++ b/internal/worker/server.go @@ -335,6 +335,11 @@ func (s *Server) JobDependencyChainErrors(id uuid.UUID) (*clienterrors.Error, er return nil, nil } +// AllRootJobIDs returns a list of top level job UUIDs that the worker knows about +func (s *Server) AllRootJobIDs() ([]uuid.UUID, error) { + return s.jobs.AllRootJobIDs() +} + func (s *Server) OSBuildJobInfo(id uuid.UUID, result *OSBuildJobResult) (*JobInfo, error) { jobInfo, err := s.jobInfo(id, result) if err != nil {