diff --git a/internal/cloudapi/v2/handler.go b/internal/cloudapi/v2/handler.go index 0c5698185..1e0d90aeb 100644 --- a/internal/cloudapi/v2/handler.go +++ b/internal/cloudapi/v2/handler.go @@ -917,6 +917,26 @@ func (h *apiHandlers) GetComposeLogs(ctx echo.Context, id string) error { return ctx.JSON(http.StatusOK, resp) } +func manifestJobResultsFromJobDeps(w *worker.Server, deps []uuid.UUID) (*worker.ManifestJobByIDResult, error) { + var manifestResult worker.ManifestJobByIDResult + + for i := 0; i < len(deps); i++ { + depType, err := w.JobType(deps[i]) + if err != nil { + return nil, err + } + if depType == worker.JobTypeManifestIDOnly { + _, _, err = w.ManifestJobStatus(deps[i], &manifestResult) + if err != nil { + return nil, err + } + return &manifestResult, nil + } + } + + return nil, fmt.Errorf("no %q job found in the dependencies", worker.JobTypeManifestIDOnly) +} + // GetComposeIdManifests returns the Manifests for a given Compose (one for each image). func (h *apiHandlers) GetComposeManifests(ctx echo.Context, id string) error { jobId, err := uuid.Parse(id) @@ -929,43 +949,65 @@ func (h *apiHandlers) GetComposeManifests(ctx echo.Context, id string) error { return HTTPError(ErrorComposeNotFound) } - // TODO: support non-koji builds - if jobType != worker.JobTypeKojiFinalize { - return HTTPError(ErrorInvalidJobType) - } - - var finalizeResult worker.KojiFinalizeJobResult - _, deps, err := h.server.workers.KojiFinalizeJobStatus(jobId, &finalizeResult) - if err != nil { - return HTTPErrorWithInternal(ErrorComposeNotFound, err) - } - var manifestBlobs []interface{} - for _, id := range deps[1:] { - var buildJob worker.OSBuildKojiJob - err = h.server.workers.OSBuildKojiJob(id, &buildJob) + + switch jobType { + case worker.JobTypeKojiFinalize: + var finalizeResult worker.KojiFinalizeJobResult + _, deps, err := h.server.workers.KojiFinalizeJobStatus(jobId, &finalizeResult) if err != nil { return HTTPErrorWithInternal(ErrorComposeNotFound, err) } + + for i := 1; i < len(deps); i++ { + var buildJob worker.OSBuildKojiJob + err = h.server.workers.OSBuildKojiJob(deps[i], &buildJob) + if err != nil { + return HTTPErrorWithInternal(ErrorComposeNotFound, err) + } + + var manifest distro.Manifest + if len(buildJob.Manifest) != 0 { + manifest = buildJob.Manifest + } else { + _, buildDeps, err := h.server.workers.OSBuildKojiJobStatus(deps[i], &worker.OSBuildKojiJobResult{}) + if err != nil { + return HTTPErrorWithInternal(ErrorComposeNotFound, err) + } + manifestResult, err := manifestJobResultsFromJobDeps(h.server.workers, buildDeps) + if err != nil { + return HTTPErrorWithInternal(ErrorComposeNotFound, fmt.Errorf("job %q: %v", jobId, err)) + } + manifest = manifestResult.Manifest + } + manifestBlobs = append(manifestBlobs, manifest) + } + + case worker.JobTypeOSBuild: + var buildJob worker.OSBuildJob + err = h.server.workers.OSBuildJob(jobId, &buildJob) + if err != nil { + return HTTPErrorWithInternal(ErrorComposeNotFound, err) + } + var manifest distro.Manifest - if len(buildJob.Manifest) == 0 { + if len(buildJob.Manifest) != 0 { manifest = buildJob.Manifest } else { - _, deps, err := h.server.workers.OSBuildKojiJobStatus(id, nil) + _, deps, err := h.server.workers.OSBuildJobStatus(jobId, &worker.OSBuildJobResult{}) if err != nil { return HTTPErrorWithInternal(ErrorComposeNotFound, err) } - if len(deps) < 2 { - return HTTPErrorWithInternal(ErrorComposeNotFound, err) - } - var manifestResult worker.ManifestJobByIDResult - _, _, err = h.server.workers.ManifestJobStatus(deps[1], &manifestResult) + manifestResult, err := manifestJobResultsFromJobDeps(h.server.workers, deps) if err != nil { - return HTTPErrorWithInternal(ErrorComposeNotFound, err) + return HTTPErrorWithInternal(ErrorComposeNotFound, fmt.Errorf("job %q: %v", jobId, err)) } manifest = manifestResult.Manifest } manifestBlobs = append(manifestBlobs, manifest) + + default: + return HTTPError(ErrorInvalidJobType) } resp := &ComposeManifests{ diff --git a/internal/cloudapi/v2/v2_koji_test.go b/internal/cloudapi/v2/v2_koji_test.go index 330c7d13d..63288b611 100644 --- a/internal/cloudapi/v2/v2_koji_test.go +++ b/internal/cloudapi/v2/v2_koji_test.go @@ -15,6 +15,7 @@ import ( v2 "github.com/osbuild/osbuild-composer/internal/cloudapi/v2" "github.com/osbuild/osbuild-composer/internal/distro/test_distro" "github.com/osbuild/osbuild-composer/internal/kojiapi/api" + "github.com/osbuild/osbuild-composer/internal/osbuild2" osbuild "github.com/osbuild/osbuild-composer/internal/osbuild2" "github.com/osbuild/osbuild-composer/internal/test" "github.com/osbuild/osbuild-composer/internal/worker" @@ -510,7 +511,7 @@ func TestKojiCompose(t *testing.T) { test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v", finalizeID), ``, http.StatusOK, c.composeStatus, `href`, `id`) // get the manifests - test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v/manifests", finalizeID), ``, http.StatusOK, `{"manifests":[null,null],"kind":"ComposeManifests"}`, `href`, `id`) + test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v/manifests", finalizeID), ``, http.StatusOK, `{"manifests":[{"pipeline":{},"sources":{}},{"pipeline":{},"sources":{}}],"kind":"ComposeManifests"}`, `href`, `id`) // get the logs test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v/logs", finalizeID), ``, http.StatusOK, `{"kind":"ComposeLogs"}`, `koji`, `image_builds`, `href`, `id`) @@ -565,6 +566,9 @@ func TestKojiJobTypeValidation(t *testing.T) { initID, err := workers.EnqueueKojiInit(&initJob, "") require.NoError(t, err) + manifest, err := json.Marshal(osbuild2.Manifest{}) + require.NoErrorf(t, err, "error marshalling empty Manifest to JSON") + buildJobs := make([]worker.OSBuildKojiJob, nImages) buildJobIDs := make([]uuid.UUID, nImages) filenames := make([]string, nImages) @@ -575,6 +579,12 @@ func TestKojiJobTypeValidation(t *testing.T) { KojiServer: "test-server", KojiDirectory: "koji-server-test-dir", KojiFilename: fname, + // Add an empty manifest as a static job argument to make the test pass. + // Becasue of a bug in the API, the test was passing even without + // any manifest being attached to the job (static or dynamic). + // In reality, cloudapi never adds the manifest as a static job argument. + // TODO: use dependent depsolve and manifests jobs instead + Manifest: manifest, } buildID, err := workers.EnqueueOSBuildKoji(fmt.Sprintf("fake-arch-%d", idx), &buildJob, initID, "") require.NoError(t, err) @@ -599,6 +609,9 @@ func TestKojiJobTypeValidation(t *testing.T) { // ----- Jobs queued - Test API endpoints (status, manifests, logs) ----- // + t.Logf("%q job ID: %s", worker.JobTypeKojiInit, initID) + t.Logf("%q job ID: %s", worker.JobTypeKojiFinalize, finalizeID) + t.Logf("%q job IDs: %v", worker.JobTypeOSBuildKoji, buildJobIDs) for _, path := range []string{"", "/manifests", "/logs"} { // should return OK - actual result should be tested elsewhere test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", finalizeID, path), ``, http.StatusOK, "*") diff --git a/internal/cloudapi/v2/v2_test.go b/internal/cloudapi/v2/v2_test.go index 776f7dd67..1cb18790d 100644 --- a/internal/cloudapi/v2/v2_test.go +++ b/internal/cloudapi/v2/v2_test.go @@ -646,6 +646,19 @@ func TestComposeStatusSuccess(t *testing.T) { } ] }`, jobId, jobId)) + + test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v/manifests", jobId), ``, http.StatusOK, fmt.Sprintf(` + { + "href": "/api/image-builder-composer/v2/composes/%v/manifests", + "id": "%v", + "kind": "ComposeManifests", + "manifests": [ + { + "pipeline": {}, + "sources": {} + } + ] + }`, jobId, jobId)) } func TestComposeStatusFailure(t *testing.T) {