From 11e2ae45284bfb0d89ef1c1e0d2aa4ae230ea573 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Fri, 28 Jan 2022 14:57:14 +0000 Subject: [PATCH] cloudapi/v2: add koji-specific tests These are based on the existing tests for the koji API, making sure the coverage is the same. --- internal/cloudapi/v2/v2_internal_test.go | 25 + internal/cloudapi/v2/v2_koji_test.go | 564 +++++++++++++++++++++++ osbuild-composer.spec | 1 + test/cases/koji.sh | 9 +- tools/koji-compose-v2.py | 110 +++++ 5 files changed, 707 insertions(+), 2 deletions(-) create mode 100644 internal/cloudapi/v2/v2_internal_test.go create mode 100644 internal/cloudapi/v2/v2_koji_test.go create mode 100755 tools/koji-compose-v2.py diff --git a/internal/cloudapi/v2/v2_internal_test.go b/internal/cloudapi/v2/v2_internal_test.go new file mode 100644 index 000000000..07695118a --- /dev/null +++ b/internal/cloudapi/v2/v2_internal_test.go @@ -0,0 +1,25 @@ +package v2 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSplitExtension(t *testing.T) { + tests := []struct { + filename string + extension string + }{ + {filename: "image.qcow2", extension: ".qcow2"}, + {filename: "image.tar.gz", extension: ".tar.gz"}, + {filename: "", extension: ""}, + {filename: ".htaccess", extension: ""}, + {filename: ".weirdfile.txt", extension: ".txt"}, + } + for _, tt := range tests { + t.Run(tt.filename, func(t *testing.T) { + require.Equal(t, tt.extension, splitExtension(tt.filename)) + }) + } +} diff --git a/internal/cloudapi/v2/v2_koji_test.go b/internal/cloudapi/v2/v2_koji_test.go new file mode 100644 index 000000000..24d4d46ef --- /dev/null +++ b/internal/cloudapi/v2/v2_koji_test.go @@ -0,0 +1,564 @@ +package v2_test + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "testing" + "time" + + "github.com/google/uuid" + 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" + osbuild "github.com/osbuild/osbuild-composer/internal/osbuild2" + "github.com/osbuild/osbuild-composer/internal/test" + "github.com/osbuild/osbuild-composer/internal/worker" + "github.com/osbuild/osbuild-composer/internal/worker/clienterrors" + "github.com/stretchr/testify/require" +) + +type jobResult struct { + Result interface{} `json:"result"` +} + +func TestKojiCompose(t *testing.T) { + dir, err := ioutil.TempDir("", "osbuild-composer-test-api-v2-") + require.NoError(t, err) + defer os.RemoveAll(dir) + kojiServer, workerServer, cancel := newV2Server(t, dir) + handler := kojiServer.Handler("/api/image-builder-composer/v2") + workerHandler := workerServer.Handler() + defer cancel() + + type kojiCase struct { + initResult worker.KojiInitJobResult + buildResult worker.OSBuildKojiJobResult + finalizeResult worker.KojiFinalizeJobResult + composeReplyCode int + composeReply string + composeStatus string + } + + var cases = []kojiCase{ + { + initResult: worker.KojiInitJobResult{ + BuildID: 42, + Token: `"foobar"`, + }, + buildResult: worker.OSBuildKojiJobResult{ + Arch: test_distro.TestArchName, + HostOS: test_distro.TestDistroName, + ImageHash: "browns", + ImageSize: 42, + OSBuildOutput: &osbuild.Result{ + Success: true, + }, + }, + composeReplyCode: http.StatusCreated, + composeReply: `{"href":"/api/image-builder-composer/v2/compose", "kind":"ComposeId"}`, + composeStatus: `{ + "kind": "ComposeStatus", + "image_status": { + "status": "success" + }, + "image_statuses": [ + { + "status": "success" + }, + { + "status": "success" + } + ], + "koji_status": { + "build_id": 42 + }, + "status": "success" + }`, + }, + { + initResult: worker.KojiInitJobResult{ + KojiError: "failure", + }, + buildResult: worker.OSBuildKojiJobResult{ + Arch: test_distro.TestArchName, + HostOS: test_distro.TestDistroName, + ImageHash: "browns", + ImageSize: 42, + OSBuildOutput: &osbuild.Result{ + Success: true, + }, + }, + composeReplyCode: http.StatusCreated, + composeReply: `{"href":"/api/image-builder-composer/v2/compose", "kind":"ComposeId"}`, + composeStatus: `{ + "kind": "ComposeStatus", + "image_status": { + "status": "failure" + }, + "image_statuses": [ + { + "status": "failure" + }, + { + "status": "failure" + } + ], + "koji_status": {}, + "status": "failure" + }`, + }, + { + initResult: worker.KojiInitJobResult{ + JobResult: worker.JobResult{ + JobError: clienterrors.WorkerClientError(clienterrors.ErrorKojiInit, "Koji init error"), + }, + }, + buildResult: worker.OSBuildKojiJobResult{ + Arch: test_distro.TestArchName, + HostOS: test_distro.TestDistroName, + ImageHash: "browns", + ImageSize: 42, + OSBuildOutput: &osbuild.Result{ + Success: true, + }, + }, + composeReplyCode: http.StatusCreated, + composeReply: `{"href":"/api/image-builder-composer/v2/compose", "kind":"ComposeId"}`, + composeStatus: `{ + "kind": "ComposeStatus", + "image_status": { + "status": "failure" + }, + "image_statuses": [ + { + "status": "failure" + }, + { + "status": "failure" + } + ], + "koji_status": {}, + "status": "failure" + }`, + }, + { + initResult: worker.KojiInitJobResult{ + BuildID: 42, + Token: `"foobar"`, + }, + buildResult: worker.OSBuildKojiJobResult{ + Arch: test_distro.TestArchName, + HostOS: test_distro.TestDistroName, + ImageHash: "browns", + ImageSize: 42, + OSBuildOutput: &osbuild.Result{ + Success: false, + }, + }, + composeReplyCode: http.StatusCreated, + composeReply: `"href":"/api/image-builder-composer/v2/compose", "kind":"ComposeId"`, + composeStatus: `{ + "kind": "ComposeStatus", + "image_status": { + "status": "failure" + }, + "image_statuses": [ + { + "status": "failure" + }, + { + "status": "success" + } + ], + "koji_status": { + "build_id": 42 + }, + "status": "failure" + }`, + }, + { + initResult: worker.KojiInitJobResult{ + BuildID: 42, + Token: `"foobar"`, + }, + buildResult: worker.OSBuildKojiJobResult{ + Arch: test_distro.TestArchName, + HostOS: test_distro.TestDistroName, + ImageHash: "browns", + ImageSize: 42, + OSBuildOutput: &osbuild.Result{ + Success: true, + }, + KojiError: "failure", + }, + composeReplyCode: http.StatusCreated, + composeReply: `"href":"/api/image-builder-composer/v2/compose", "kind":"ComposeId"`, + composeStatus: `{ + "kind": "ComposeStatus", + "image_status": { + "status": "failure" + }, + "image_statuses": [ + { + "status": "failure" + }, + { + "status": "success" + } + ], + "koji_status": { + "build_id": 42 + }, + "status": "failure" + }`, + }, + { + initResult: worker.KojiInitJobResult{ + BuildID: 42, + Token: `"foobar"`, + }, + buildResult: worker.OSBuildKojiJobResult{ + Arch: test_distro.TestArchName, + HostOS: test_distro.TestDistroName, + ImageHash: "browns", + ImageSize: 42, + OSBuildOutput: &osbuild.Result{ + Success: true, + }, + JobResult: worker.JobResult{ + JobError: clienterrors.WorkerClientError(clienterrors.ErrorBuildJob, "Koji build error"), + }, + }, + composeReplyCode: http.StatusCreated, + composeReply: `"href":"/api/image-builder-composer/v2/compose", "kind":"ComposeId"`, + composeStatus: `{ + "kind": "ComposeStatus", + "image_status": { + "status": "failure" + }, + "image_statuses": [ + { + "status": "failure" + }, + { + "status": "success" + } + ], + "koji_status": { + "build_id": 42 + }, + "status": "failure" + }`, + }, + { + initResult: worker.KojiInitJobResult{ + BuildID: 42, + Token: `"foobar"`, + }, + buildResult: worker.OSBuildKojiJobResult{ + Arch: test_distro.TestArchName, + HostOS: test_distro.TestDistroName, + ImageHash: "browns", + ImageSize: 42, + OSBuildOutput: &osbuild.Result{ + Success: true, + }, + }, + finalizeResult: worker.KojiFinalizeJobResult{ + KojiError: "failure", + }, + composeReplyCode: http.StatusCreated, + composeReply: `"href":"/api/image-builder-composer/v2/compose", "kind":"ComposeId"`, + composeStatus: `{ + "kind": "ComposeStatus", + "image_status": { + "status": "success" + }, + "image_statuses": [ + { + "status": "success" + }, + { + "status": "success" + } + ], + "koji_status": { + "build_id": 42 + }, + "status": "failure" + }`, + }, + { + initResult: worker.KojiInitJobResult{ + BuildID: 42, + Token: `"foobar"`, + }, + buildResult: worker.OSBuildKojiJobResult{ + Arch: test_distro.TestArchName, + HostOS: test_distro.TestDistroName, + ImageHash: "browns", + ImageSize: 42, + OSBuildOutput: &osbuild.Result{ + Success: true, + }, + }, + finalizeResult: worker.KojiFinalizeJobResult{ + JobResult: worker.JobResult{ + JobError: clienterrors.WorkerClientError(clienterrors.ErrorKojiFinalize, "Koji finalize error"), + }, + }, + composeReplyCode: http.StatusCreated, + composeReply: `"href":"/api/image-builder-composer/v2/compose", "kind":"ComposeId"`, + composeStatus: `{ + "kind": "ComposeStatus", + "image_status": { + "status": "success" + }, + "image_statuses": [ + { + "status": "success" + }, + { + "status": "success" + } + ], + "koji_status": { + "build_id": 42 + }, + "status": "failure" + }`, + }, + } + for _, c := range cases[2:3] { + test.TestRoute(t, handler, false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(` + { + "distribution":"%[1]s", + "image_requests": [ + { + "architecture": "%[2]s", + "image_type": "%[3]s", + "repositories": [ + { + "baseurl": "https://repo.example.com/" + } + ] + }, + { + "architecture": "%[2]s", + "image_type": "%[3]s", + "repositories": [ + { + "baseurl": "https://repo.example.com/" + } + ] + } + ], + "koji": { + "server": "koji.example.com", + "name":"foo", + "version":"1", + "release":"2" + } + }`, test_distro.TestDistroName, test_distro.TestArch3Name, string(v2.ImageTypesGuestImage)), + c.composeReplyCode, c.composeReply, "id", "operation_id") + + // handle koji-init + _, token, jobType, rawJob, _, err := workerServer.RequestJob(context.Background(), test_distro.TestArch3Name, []string{"koji-init"}) + require.NoError(t, err) + require.Equal(t, "koji-init", jobType) + + var initJob worker.KojiInitJob + err = json.Unmarshal(rawJob, &initJob) + require.NoError(t, err) + require.Equal(t, "koji.example.com", initJob.Server) + require.Equal(t, "foo", initJob.Name) + require.Equal(t, "1", initJob.Version) + require.Equal(t, "2", initJob.Release) + + initJobResult, err := json.Marshal(&jobResult{Result: c.initResult}) + require.NoError(t, err) + test.TestRoute(t, workerHandler, false, "PATCH", fmt.Sprintf("/api/worker/v1/jobs/%v", token), string(initJobResult), http.StatusOK, + fmt.Sprintf(`{"href":"/api/worker/v1/jobs/%v","id":"%v","kind":"UpdateJobResponse"}`, token, token)) + + // handle osbuild-koji #1 + _, token, jobType, rawJob, _, err = workerServer.RequestJob(context.Background(), test_distro.TestArch3Name, []string{"osbuild-koji"}) + require.NoError(t, err) + require.Equal(t, "osbuild-koji", jobType) + + var osbuildJob worker.OSBuildKojiJob + err = json.Unmarshal(rawJob, &osbuildJob) + require.NoError(t, err) + require.Equal(t, "koji.example.com", osbuildJob.KojiServer) + require.Equal(t, "test.img", osbuildJob.ImageName) + require.NotEmpty(t, osbuildJob.KojiDirectory) + + buildJobResult, err := json.Marshal(&jobResult{Result: c.buildResult}) + require.NoError(t, err) + test.TestRoute(t, workerHandler, false, "PATCH", fmt.Sprintf("/api/worker/v1/jobs/%v", token), string(buildJobResult), http.StatusOK, + fmt.Sprintf(`{"href":"/api/worker/v1/jobs/%v","id":"%v","kind":"UpdateJobResponse"}`, token, token)) + + // handle osbuild-koji #2 + _, token, jobType, rawJob, _, err = workerServer.RequestJob(context.Background(), test_distro.TestArch3Name, []string{"osbuild-koji"}) + require.NoError(t, err) + require.Equal(t, "osbuild-koji", jobType) + + err = json.Unmarshal(rawJob, &osbuildJob) + require.NoError(t, err) + require.Equal(t, "koji.example.com", osbuildJob.KojiServer) + require.Equal(t, "test.img", osbuildJob.ImageName) + require.NotEmpty(t, osbuildJob.KojiDirectory) + + test.TestRoute(t, workerHandler, false, "PATCH", fmt.Sprintf("/api/worker/v1/jobs/%v", token), fmt.Sprintf(`{ + "result": { + "arch": "%s", + "host_os": "%s", + "image_hash": "browns", + "image_size": 42, + "osbuild_output": { + "success": true + } + } + }`, test_distro.TestArch3Name, test_distro.TestDistroName), http.StatusOK, + fmt.Sprintf(`{"href":"/api/worker/v1/jobs/%v","id":"%v","kind":"UpdateJobResponse"}`, token, token)) + + // handle koji-finalize + finalizeID, token, jobType, rawJob, _, err := workerServer.RequestJob(context.Background(), test_distro.TestArch3Name, []string{"koji-finalize"}) + require.NoError(t, err) + require.Equal(t, "koji-finalize", jobType) + + var kojiFinalizeJob worker.KojiFinalizeJob + err = json.Unmarshal(rawJob, &kojiFinalizeJob) + require.NoError(t, err) + require.Equal(t, "koji.example.com", kojiFinalizeJob.Server) + require.Equal(t, "1", kojiFinalizeJob.Version) + require.Equal(t, "2", kojiFinalizeJob.Release) + require.ElementsMatch(t, []string{ + fmt.Sprintf("foo-1-2.%s.img", test_distro.TestArch3Name), + fmt.Sprintf("foo-1-2.%s.img", test_distro.TestArch3Name), + }, kojiFinalizeJob.KojiFilenames) + require.NotEmpty(t, kojiFinalizeJob.KojiDirectory) + + finalizeResult, err := json.Marshal(&jobResult{Result: c.finalizeResult}) + require.NoError(t, err) + test.TestRoute(t, workerHandler, false, "PATCH", fmt.Sprintf("/api/worker/v1/jobs/%v", token), string(finalizeResult), http.StatusOK, + fmt.Sprintf(`{"href":"/api/worker/v1/jobs/%v","id":"%v","kind":"UpdateJobResponse"}`, token, token)) + + // get the status + 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`) + + // 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`) + } +} + +func TestKojiRequest(t *testing.T) { + dir, err := ioutil.TempDir("", "osbuild-composer-test-api-v2-") + require.NoError(t, err) + defer os.RemoveAll(dir) + server, _, cancel := newV2Server(t, dir) + handler := server.Handler("/api/image-builder-composer/v2") + defer cancel() + + // Make request to an invalid route + req := httptest.NewRequest("GET", "/invalidroute", nil) + + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + resp := rec.Result() + + var status api.Status + err = json.NewDecoder(resp.Body).Decode(&status) + require.NoError(t, err) + require.Equal(t, http.StatusNotFound, resp.StatusCode) + + // Trigger an error 400 code + req = httptest.NewRequest("GET", "/api/image-builder-composer/v2/composes/badid", nil) + + rec = httptest.NewRecorder() + handler.ServeHTTP(rec, req) + resp = rec.Result() + + err = json.NewDecoder(resp.Body).Decode(&status) + require.NoError(t, err) + require.Equal(t, http.StatusBadRequest, resp.StatusCode) +} + +func TestKojiJobTypeValidation(t *testing.T) { + dir, err := ioutil.TempDir("", "osbuild-composer-test-api-v2-") + require.NoError(t, err) + defer os.RemoveAll(dir) + server, workers, cancel := newV2Server(t, dir) + handler := server.Handler("/api/image-builder-composer/v2") + defer cancel() + + // Enqueue a compose job with N images (+ an Init and a Finalize job) + // Enqueuing them manually gives us access to the job IDs to use in + // requests. + // TODO: set to 4 + nImages := 1 + initJob := worker.KojiInitJob{ + Server: "test-server", + Name: "test-job", + Version: "42", + Release: "1", + } + initID, err := workers.EnqueueKojiInit(&initJob) + require.NoError(t, err) + + buildJobs := make([]worker.OSBuildKojiJob, nImages) + buildJobIDs := make([]uuid.UUID, nImages) + filenames := make([]string, nImages) + for idx := 0; idx < nImages; idx++ { + fname := fmt.Sprintf("image-file-%04d", idx) + buildJob := worker.OSBuildKojiJob{ + ImageName: fmt.Sprintf("build-job-%04d", idx), + KojiServer: "test-server", + KojiDirectory: "koji-server-test-dir", + KojiFilename: fname, + } + buildID, err := workers.EnqueueOSBuildKoji(fmt.Sprintf("fake-arch-%d", idx), &buildJob, initID) + require.NoError(t, err) + + buildJobs[idx] = buildJob + buildJobIDs[idx] = buildID + filenames[idx] = fname + } + + finalizeJob := worker.KojiFinalizeJob{ + Server: "test-server", + Name: "test-job", + Version: "42", + Release: "1", + KojiFilenames: filenames, + KojiDirectory: "koji-server-test-dir", + TaskID: 0, + StartTime: uint64(time.Now().Unix()), + } + finalizeID, err := workers.EnqueueKojiFinalize(&finalizeJob, initID, buildJobIDs) + require.NoError(t, err) + + // ----- Jobs queued - Test API endpoints (status, manifests, logs) ----- // + + 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, "*") + + // The other IDs should fail + test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", initID, path), ``, http.StatusNotFound, `{"code":"IMAGE-BUILDER-COMPOSER-26","href":"/api/image-builder-composer/v2/errors/26","id":"26","kind":"Error","reason":"Requested job has invalid type"}`, `operation_id`) + + for _, buildID := range buildJobIDs { + test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", buildID, path), ``, http.StatusNotFound, `{"code":"IMAGE-BUILDER-COMPOSER-26","href":"/api/image-builder-composer/v2/errors/26","id":"26","kind":"Error","reason":"Requested job has invalid type"}`, `operation_id`) + } + + badID := uuid.New() + test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", badID, path), ``, http.StatusNotFound, `{"code":"IMAGE-BUILDER-COMPOSER-15","href":"/api/image-builder-composer/v2/errors/15","id":"15","kind":"Error","reason":"Compose with given id not found"}`, `operation_id`) + } +} diff --git a/osbuild-composer.spec b/osbuild-composer.spec index 414ae4ed5..06bf44a01 100644 --- a/osbuild-composer.spec +++ b/osbuild-composer.spec @@ -220,6 +220,7 @@ install -m 0755 -vp tools/gen-ssh.sh %{buildroot}% install -m 0755 -vp tools/image-info %{buildroot}%{_libexecdir}/osbuild-composer-test/ install -m 0755 -vp tools/run-koji-container.sh %{buildroot}%{_libexecdir}/osbuild-composer-test/ install -m 0755 -vp tools/koji-compose.py %{buildroot}%{_libexecdir}/osbuild-composer-test/ +install -m 0755 -vp tools/koji-compose-v2.py %{buildroot}%{_libexecdir}/osbuild-composer-test/ install -m 0755 -vp tools/libvirt_test.sh %{buildroot}%{_libexecdir}/osbuild-composer-test/ install -m 0755 -vp tools/set-env-variables.sh %{buildroot}%{_libexecdir}/osbuild-composer-test/ install -m 0755 -vp tools/test-case-generators/generate-test-cases %{buildroot}%{_libexecdir}/osbuild-composer-test/ diff --git a/test/cases/koji.sh b/test/cases/koji.sh index e5b8a4fd6..31386d883 100755 --- a/test/cases/koji.sh +++ b/test/cases/koji.sh @@ -57,8 +57,13 @@ if [[ "$DISTRO_CODE" == rhel-8* ]]; then DISTRO_CODE=rhel-86 fi -greenprint "Pushing compose to Koji" -sudo /usr/libexec/osbuild-composer-test/koji-compose.py "$DISTRO_CODE" "${ARCH}" +if [ "${COMPOSER_API:=true}" == "true" ]; then + greenprint "Pushing compose to Koji (/api/image-builder-comoser/v2/" + sudo /usr/libexec/osbuild-composer-test/koji-compose-v2.py "$DISTRO_CODE" "${ARCH}" +else + greenprint "Pushing compose to Koji (/api/comoser-koji/v1/" + sudo /usr/libexec/osbuild-composer-test/koji-compose.py "$DISTRO_CODE" "${ARCH}" +fi greenprint "Show Koji task" koji --server=http://localhost:8080/kojihub taskinfo 1 diff --git a/tools/koji-compose-v2.py b/tools/koji-compose-v2.py new file mode 100755 index 000000000..bbde97e5a --- /dev/null +++ b/tools/koji-compose-v2.py @@ -0,0 +1,110 @@ +#!/usr/bin/python3 +import json +import sys +import time + +import requests + + +# Composer API for Koji uses a slightly different repository format +# that osbuild-composer does in /usr/share/osbuild-composer/repositories. +# +# This function does the conversion. +def composer_repository_to_koji_repository(repository): + koji_repository = { + "baseurl": repository["baseurl"] + } + + if repository.get("check_gpg", False): + koji_repository["gpgkey"] = repository["gpgkey"] + + return koji_repository + + +def compose_request(distro, koji, arch): + with open(f"/usr/share/tests/osbuild-composer/repositories/{distro}.json") as f: + test_repositories = json.load(f) + + repositories = [composer_repository_to_koji_repository(repo) for repo in test_repositories[arch]] + image_requests = [ + { + "architecture": "x86_64", + "image_type": "guest-image", + "repositories": repositories + }, + { + "architecture": "x86_64", + "image_type": "aws", + "repositories": repositories + } + ] + + req = { + "distribution": distro, + "koji": { + "server": koji, + "task_id": 1, + "name": "name", + "version": "version", + "release": "release", + }, + "image_requests": image_requests + } + + return req + + +def main(distro, arch): + cr = compose_request(distro, "https://localhost:4343/kojihub", arch) + print(json.dumps(cr)) + + r = requests.post("https://localhost/api/image-builder-composer/v2/compose", json=cr, + cert=("/etc/osbuild-composer/worker-crt.pem", "/etc/osbuild-composer/worker-key.pem"), + verify="/etc/osbuild-composer/ca-crt.pem") + if r.status_code != 201: + print("Failed to create compose") + print(r.text) + sys.exit(1) + + print(r.text) + compose_id = r.json()["id"] + + while True: + r = requests.get(f"https://localhost/api/image-builder-composer/v2/composes/{compose_id}", + cert=("/etc/osbuild-composer/worker-crt.pem", "/etc/osbuild-composer/worker-key.pem"), + verify="/etc/osbuild-composer/ca-crt.pem") + if r.status_code != 200: + print("Failed to get compose status") + print(r.text) + sys.exit(1) + status = r.json()["status"] + print(status) + if status == "success": + print("Compose worked!") + print(r.text) + break + elif status == "failure": + print("compose failed!") + print(r.text) + sys.exit(1) + elif status != "pending" and status != "running": + print(f"unexpected status: {status}") + print(r.text) + sys.exit(1) + + time.sleep(10) + + r = requests.get(f"https://localhost/api/image-builder-composer/v2/composes/{compose_id}/logs", + cert=("/etc/osbuild-composer/worker-crt.pem", "/etc/osbuild-composer/worker-key.pem"), + verify="/etc/osbuild-composer/ca-crt.pem") + logs = r.json() + assert "image_builds" in logs + assert type(logs["image_builds"]) == list + assert len(logs["image_builds"]) == len(cr["image_requests"]) + + +if __name__ == "__main__": + if len(sys.argv) != 3: + print(f"usage: {sys.argv[0]} DISTRO ARCH", file=sys.stderr) + sys.exit(1) + main(sys.argv[1], sys.argv[2])