debian-forge-composer/internal/cloudapi/v2/v2_test.go
Michael Vogt 35167ce83f many: update for new reporegistry.New() api (c.f. pr#1179)
This commit updates osbuild-composer for the new API in images
for the `reporegistry.New()`. The main incompatible change is
that the `/repositories` part is not longer automatically added
inside the library so we need to add it on the call-site.

This needs https://github.com/osbuild/images/pull/1179
2025-02-19 19:19:42 +01:00

2070 lines
62 KiB
Go

package v2_test
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"sync"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/osbuild/osbuild-composer/pkg/jobqueue"
"github.com/osbuild/images/pkg/distro/test_distro"
"github.com/osbuild/images/pkg/distrofactory"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/ostree/mock_ostree_repo"
"github.com/osbuild/images/pkg/reporegistry"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/images/pkg/sbom"
v2 "github.com/osbuild/osbuild-composer/internal/cloudapi/v2"
"github.com/osbuild/osbuild-composer/internal/jobqueue/fsjobqueue"
"github.com/osbuild/osbuild-composer/internal/target"
"github.com/osbuild/osbuild-composer/internal/test"
"github.com/osbuild/osbuild-composer/internal/worker"
"github.com/osbuild/osbuild-composer/internal/worker/clienterrors"
)
var sbomDoc = json.RawMessage(`{
"SPDXID": "SPDXRef-DOCUMENT",
"creationInfo": {
"created": "2024-09-06T08:12:28Z",
"creators": [
"Tool: osbuild-128"
]
},
"dataLicense": "CC0-1.0",
"name": "sbom-by-osbuild-128",
"spdxVersion": "SPDX-2.3",
"documentNamespace": "https://osbuild.org/spdxdocs/sbom-by-osbuild-128-6bb434da-523a-463f-bc4c-69460af00040",
"packages": [
{
"SPDXID": "SPDXRef-356f4620-c03e-3830-b013-ddbfee6665aa",
"name": "pkg1",
"downloadLocation": "http://example.org/pkg1",
"filesAnalyzed": false,
"versionInfo": "1",
"checksums": [
{
"algorithm": "SHA256",
"checksumValue": "e50ddb78a37f5851d1a5c37a4c77d59123153c156e628e064b9daa378f45a2fe"
}
],
"sourceInfo": "pkg1.src",
"licenseDeclared": "MIT",
"summary": "pkg1 summary",
"description": "pkg1 description",
"builtDate": "2024-01-23T00:11:15Z"
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-DOCUMENT",
"relationshipType": "DESCRIBES",
"relatedSpdxElement": "SPDXRef-356f4620-c03e-3830-b013-ddbfee6665aa"
}
]
}`)
// mockDepsolve starts a routine which just completes depsolve jobs
// It requires some of the test framework to operate
// And the optional fail parameter will cause it to return an error as if the depsolve failed
func mockDepsolve(t *testing.T, workerServer *worker.Server, wg *sync.WaitGroup, fail bool) func() {
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go func() {
defer wg.Done()
for {
_, token, _, _, _, err := workerServer.RequestJob(ctx, test_distro.TestArchName, []string{worker.JobTypeDepsolve}, []string{""}, uuid.Nil)
select {
case <-ctx.Done():
return
default:
}
if err != nil {
continue
}
dJR := &worker.DepsolveJobResult{
PackageSpecs: map[string][]rpmmd.PackageSpec{
// Used when depsolving a list of packages outside the manifest
// Including it in other responses currently doesn't cause a problem
"depsolve": {
{
Name: "dep-package1",
Version: "1.33",
Release: "2.fc30",
Arch: "x86_64",
Checksum: "sha256:fe3951d112c3b1c84dc8eac57afe0830df72df1ca0096b842f4db5d781189893",
},
},
// Used when depsolving a manifest
"build": {
{
Name: "pkg1",
Checksum: "sha256:e50ddb78a37f5851d1a5c37a4c77d59123153c156e628e064b9daa378f45a2fe",
},
},
"os": {
{
Name: "pkg1",
Checksum: "sha256:e50ddb78a37f5851d1a5c37a4c77d59123153c156e628e064b9daa378f45a2fe",
},
},
},
SbomDocs: map[string]worker.SbomDoc{
"build": {
DocType: sbom.StandardTypeSpdx,
Document: sbomDoc,
},
"os": {
DocType: sbom.StandardTypeSpdx,
Document: sbomDoc,
},
},
}
if fail {
dJR.JobResult.JobError = clienterrors.New(clienterrors.ErrorDNFOtherError, "DNF Error", nil)
}
rawMsg, err := json.Marshal(dJR)
require.NoError(t, err)
err = workerServer.FinishJob(token, rawMsg)
if err != nil {
return
}
}
}()
return cancel
}
// mockOSTreeResolve starts a routine which completes a dummy ostree job
// It requires some of the test framework to operate
// And the optional fail parameter will cause it to return an error as if the ostree job failed
func mockOSTreeResolve(t *testing.T, workerServer *worker.Server, wg *sync.WaitGroup, fail bool) func() {
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go func() {
defer wg.Done()
for {
_, token, _, _, _, err := workerServer.RequestJob(ctx, test_distro.TestDistro1Name, []string{worker.JobTypeOSTreeResolve}, []string{""}, uuid.Nil)
select {
case <-ctx.Done():
return
default:
}
if err != nil {
continue
}
oJR := &worker.OSTreeResolveJobResult{
Specs: []worker.OSTreeResolveResultSpec{
{
URL: "",
Ref: "",
Checksum: "",
},
},
}
if fail {
oJR.JobResult.JobError = clienterrors.New(clienterrors.ErrorOSTreeParamsInvalid, "ostree error", nil)
}
rawMsg, err := json.Marshal(oJR)
require.NoError(t, err)
err = workerServer.FinishJob(token, rawMsg)
if err != nil {
return
}
}
}()
return cancel
}
// mockSearch starts a routine which just completes search jobs
// It requires some of the test framework to operate
// And the optional fail parameter will cause it to return an error as if the search failed
func mockSearch(t *testing.T, workerServer *worker.Server, wg *sync.WaitGroup, fail bool) func() {
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go func() {
defer wg.Done()
for {
_, token, _, _, _, err := workerServer.RequestJob(ctx, test_distro.TestArchName, []string{worker.JobTypeSearchPackages}, []string{""}, uuid.Nil)
select {
case <-ctx.Done():
return
default:
}
if err != nil {
continue
}
result := &worker.SearchPackagesJobResult{
Packages: rpmmd.PackageList{
{
Name: "package1",
Summary: "The package you searched for",
Description: "A verbose paragraph about the package",
Version: "1.33",
Release: "2.fc42",
Arch: "x86_64",
URL: "https://example.com/package1",
License: "GPLv3",
BuildTime: time.Date(1985, time.October, 26, 9, 24, 0, 0, time.UTC),
},
},
}
// fail returns an empty list of matches
if fail {
result.Packages = nil
}
rawMsg, err := json.Marshal(result)
require.NoError(t, err)
err = workerServer.FinishJob(token, rawMsg)
if err != nil {
return
}
}
}()
return cancel
}
func newV2Server(t *testing.T, dir string, enableJWT bool, fail bool) (*v2.Server, *worker.Server, jobqueue.JobQueue, context.CancelFunc) {
q, err := fsjobqueue.New(dir)
require.NoError(t, err)
workerServer := worker.NewServer(nil, q,
worker.Config{
ArtifactsDir: t.TempDir(),
BasePath: "/api/worker/v1",
JWTEnabled: enableJWT,
TenantProviderFields: []string{"rh-org-id", "account_id"},
})
distros := distrofactory.NewTestDefault()
require.NotNil(t, distros)
repos, err := reporegistry.New([]string{"../../../test/data/repositories"}, nil)
require.Nil(t, err)
require.NotNil(t, repos)
require.Greater(t, len(repos.ListDistros()), 0)
config := v2.ServerConfig{
JWTEnabled: enableJWT,
TenantProviderFields: []string{"rh-org-id", "account_id"},
}
v2Server := v2.NewServer(workerServer, distros, repos, config)
require.NotNil(t, v2Server)
t.Cleanup(v2Server.Shutdown)
// Setup the depsolve and ostree resolve job handlers
// These are mocked functions that return a static set of results for testing
var wg sync.WaitGroup
var cancelFuncs []context.CancelFunc
cancelFuncs = append(cancelFuncs, mockDepsolve(t, workerServer, &wg, fail))
cancelFuncs = append(cancelFuncs, mockOSTreeResolve(t, workerServer, &wg, fail))
cancelFuncs = append(cancelFuncs, mockSearch(t, workerServer, &wg, fail))
cancelWithWait := func() {
for _, cancel := range cancelFuncs {
cancel()
}
wg.Wait()
}
return v2Server, workerServer, q, cancelWithWait
}
func TestUnknownRoute(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", "/api/image-builder-composer/v2/badroute", ``, http.StatusNotFound, `
{
"href": "/api/image-builder-composer/v2/errors/21",
"id": "21",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-21",
"reason": "Requested resource doesn't exist"
}`, "operation_id", "details")
}
func TestGetError(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", "/api/image-builder-composer/v2/errors/4", ``, http.StatusOK, `
{
"href": "/api/image-builder-composer/v2/errors/4",
"id": "4",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-4",
"reason": "Unsupported distribution"
}`, "operation_id", "details")
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", "/api/image-builder-composer/v2/errors/3000", ``, http.StatusNotFound, `
{
"href": "/api/image-builder-composer/v2/errors/17",
"id": "17",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-17",
"reason": "Error with given id not found"
}`, "operation_id", "details")
}
func TestGetErrorList(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", "/api/image-builder-composer/v2/errors?page=3&size=1", ``, http.StatusOK, `
{
"kind": "ErrorList",
"page": 3,
"size": 1,
"items": [{
"href": "/api/image-builder-composer/v2/errors/4",
"id": "4",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-4",
"reason": "Unsupported distribution"
}]
}`, "operation_id", "total", "details")
}
func TestGetDistributionList(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET",
"/api/image-builder-composer/v2/distributions", ``, http.StatusOK, `
{
"test-distro-1":{
"test_arch":{
"test_ostree_type":[
{"baseurls":["https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-x86_64-rawhide-20240101"], "check_gpg":true, "name":"test-distro"}
],
"test_type":[{"baseurls":["https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-x86_64-rawhide-20240101"], "check_gpg":true, "name":"test-distro"}]
},
"test_arch2":{
"test_type":[{"baseurls":["https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-aarch64-rawhide-20240101"], "check_gpg":true, "name":"test-distro"}],
"test_type2":[{"baseurls":["https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-aarch64-rawhide-20240101"], "check_gpg":true, "name":"test-distro"}]},
"test_arch3":{
"ami":[{"baseurls":["https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-ppc64le-rawhide-20240101"], "check_gpg":true, "name":"test-distro"}],
"gce":[{"baseurls":["https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-ppc64le-rawhide-20240101"], "check_gpg":true, "name":"test-distro"}],
"image-installer":[{"baseurls":["https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-ppc64le-rawhide-20240101"], "check_gpg":true, "name":"test-distro"}],
"qcow2":[{"baseurls":["https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-ppc64le-rawhide-20240101"], "check_gpg":true, "name":"test-distro"}],
"rhel-edge-commit":[{"baseurls":["https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-ppc64le-rawhide-20240101"], "check_gpg":true, "name":"test-distro"}],
"rhel-edge-installer":[{"baseurls":["https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-ppc64le-rawhide-20240101"], "check_gpg":true, "name":"test-distro"}],
"vhd":[{"baseurls":["https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-ppc64le-rawhide-20240101"], "check_gpg":true, "name":"test-distro"}],
"vmdk":[{"baseurls":["https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-ppc64le-rawhide-20240101"], "check_gpg":true, "name":"test-distro"}]
}
}
}`, "gpgkeys", "baseurl")
}
func TestCompose(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
testDistro := test_distro.DistroFactory(test_distro.TestDistro1Name)
require.NotNil(t, testDistro)
// create two ostree repos, one to serve the default test_distro ref (for fallback tests) and one to serve a custom ref
ostreeRepoDefault := mock_ostree_repo.Setup(testDistro.OSTreeRef())
defer ostreeRepoDefault.TearDown()
ostreeRepoOther := mock_ostree_repo.Setup("some/other/ref")
defer ostreeRepoOther.TearDown()
// unsupported distribution
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
{
"distribution": "unsupported_distro",
"image_request":{
"architecture": "%s",
"image_type": "aws.ec2",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1",
"share_with_accounts": ["123456789012"]
}
}
}`, test_distro.TestArch3Name), http.StatusBadRequest, `
{
"href": "/api/image-builder-composer/v2/errors/30",
"id": "30",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-30",
"reason": "Request could not be validated"
}`, "operation_id", "details")
// unsupported architecture
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
{
"distribution": "%s",
"image_request":{
"architecture": "unsupported_arch",
"image_type": "aws",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
}
}
}`, testDistro.Name()), http.StatusBadRequest, `
{
"href": "/api/image-builder-composer/v2/errors/5",
"id": "5",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-5",
"reason": "Unsupported architecture"
}`, "operation_id", "details")
// unsupported imagetype
test.TestRoute(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": "unsupported_image_type",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
}
}
}`, testDistro.Name(), test_distro.TestArch3Name), http.StatusBadRequest, `
{
"href": "/api/image-builder-composer/v2/errors/30",
"id": "30",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-30",
"reason": "Request could not be validated"
}`, "operation_id", "details")
// Returns 404, but should be 405; see https://github.com/labstack/echo/issues/1981
// test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
// {
// "distribution": "%s",
// "image_requests":[{
// "architecture": "%s",
// "image_type": "%s",
// "repositories": [{
// "baseurl": "somerepo.org",
// "rhsm": false
// }],
// "upload_options": {
// "region": "eu-central-1"
// }
// }]
// }`, testDistro.Name(), test_distro.TestArch3Name, test_distro.TestImageTypeName), http.StatusMethodNotAllowed, `
// {
// "href": "/api/image-builder-composer/v2/errors/22",
// "id": "22",
// "kind": "Error",
// "code": "IMAGE-BUILDER-COMPOSER-22",
// "reason": "Requested method isn't supported for resource"
// }`, "operation_id")
// With upload options for default target
test.TestRoute(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": "aws",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
}
}
}`, testDistro.Name(), test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
// With upload options for specific upload target
test.TestRoute(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": "aws",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_targets": [{
"type": "aws",
"upload_options": {
"region": "eu-central-1"
}
}]
}
}`, testDistro.Name(), test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
// With both upload options for default target and a specific target
test.TestRoute(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": "aws",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_targets": [{
"type": "aws",
"upload_options": {
"region": "eu-central-1"
}
}],
"upload_options": {
"region": "eu-central-1"
}
}
}`, testDistro.Name(), test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
// Koji
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
{
"distribution": "%s",
"koji":{
"name": "name",
"version": "version",
"release": "release",
"server": "https://koji.example.com",
"task_id": 42
},
"image_request":{
"architecture": "%s",
"image_type": "aws",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}]
}
}`, testDistro.Name(), test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
// ostree parameters (success)
// ref only
test.TestRoute(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": "edge-commit",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
},
"ostree": {
"ref": "rhel/10/x86_64/edge"
}
}
}`, testDistro.Name(), test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
// ref only with secondary pulp upload target
test.TestRoute(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": "edge-commit",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_targets": [{
"type": "pulp.ostree",
"upload_options": {
"basepath": "edge/rhel10"
}
}],
"upload_options": {
"region": "eu-central-1"
},
"ostree": {
"ref": "rhel/10/x86_64/edge"
}
}
}`, testDistro.Name(), test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
// url only (must use ostreeRepoDefault for default ref)
test.TestRoute(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": "edge-commit",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
},
"ostree": {
"url": "%s"
}
}
}`, testDistro.Name(), test_distro.TestArch3Name, ostreeRepoDefault.Server.URL), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
// ref + url
test.TestRoute(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": "edge-commit",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
},
"ostree": {
"ref": "%s",
"url": "%s"
}
}
}`, testDistro.Name(), test_distro.TestArch3Name, ostreeRepoDefault.OSTreeRef, ostreeRepoDefault.Server.URL), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
// parent + url
test.TestRoute(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": "edge-commit",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
},
"ostree": {
"parent": "%s",
"url": "%s"
}
}
}`, testDistro.Name(), test_distro.TestArch3Name, ostreeRepoDefault.OSTreeRef, ostreeRepoDefault.Server.URL), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
// ref + parent + url
test.TestRoute(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": "edge-commit",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
},
"ostree": {
"parent": "%s",
"url": "%s",
"ref": "a/new/ref"
}
}
}`, testDistro.Name(), test_distro.TestArch3Name, ostreeRepoOther.OSTreeRef, ostreeRepoOther.Server.URL), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
// ref + parent + url + contenturl + rhsm
test.TestRoute(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": "edge-commit",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
},
"ostree": {
"parent": "%s",
"url": "%s",
"ref": "a/new/ref",
"contenturl": "%s",
"rhsm": true
}
}
}`, testDistro.Name(), test_distro.TestArch3Name, ostreeRepoOther.OSTreeRef, ostreeRepoOther.Server.URL, fmt.Sprintf("%s/content", ostreeRepoOther.Server.URL)), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
}
func TestComposeStatusSuccess(t *testing.T) {
srv, wrksrv, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(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": "aws",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
}
}
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
jobId, token, jobType, args, dynArgs, err := wrksrv.RequestJob(context.Background(), test_distro.TestArch3Name, []string{worker.JobTypeOSBuild}, []string{""}, uuid.Nil)
require.NoError(t, err)
require.Equal(t, worker.JobTypeOSBuild, jobType)
var osbuildJob worker.OSBuildJob
err = json.Unmarshal(args, &osbuildJob)
require.NoError(t, err)
require.Equal(t, 0, len(osbuildJob.Manifest))
require.NotEqual(t, 0, len(dynArgs[0]))
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v",
"kind": "ComposeStatus",
"id": "%v",
"image_status": {"status": "building"},
"status": "pending"
}`, jobId, jobId))
res, err := json.Marshal(&worker.OSBuildJobResult{
Success: true,
OSBuildOutput: &osbuild.Result{Success: true},
})
require.NoError(t, err)
err = wrksrv.FinishJob(token, res)
require.NoError(t, err)
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v",
"kind": "ComposeStatus",
"id": "%v",
"image_status": {"status": "success"},
"status": "success"
}`, jobId, jobId))
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v/metadata", jobId), ``, http.StatusInternalServerError, `
{
"href": "/api/image-builder-composer/v2/errors/1012",
"id": "1012",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-1012",
"reason": "OSBuildJobResult does not have expected fields set"
}`, "operation_id", "details")
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v/logs", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v/logs",
"id": "%v",
"kind": "ComposeLogs",
"image_builds": [
{
"arch": "",
"host_os": "",
"osbuild_output": {
"log": null,
"metadata": null,
"success": true,
"type": ""
},
"pipeline_names": {
"build": [
"build"
],
"payload": [
"os",
"assembler"
]
},
"success": true,
"upload_status": ""
}
]
}`, jobId, jobId))
emptyManifest := `{"version":"2","pipelines":[{"name":"build"},{"name":"os"}],"sources":{"org.osbuild.curl":{"items":{"sha256:e50ddb78a37f5851d1a5c37a4c77d59123153c156e628e064b9daa378f45a2fe":{"url":""}}}}}`
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": [
%s
]
}`, jobId, jobId, emptyManifest), "details")
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v/sboms", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v/sboms",
"id": "%v",
"kind": "ComposeSBOMs",
"items": [
[
{
"pipeline_name": "build",
"pipeline_purpose": "buildroot",
"sbom": %[3]s,
"sbom_type": %[4]q
},
{
"pipeline_name": "os",
"pipeline_purpose": "image",
"sbom": %[3]s,
"sbom_type": %[4]q
}
]
]
}`, jobId, jobId, sbomDoc, v2.ImageSBOMSbomTypeSpdx), "details")
}
func TestComposeStatusFailure(t *testing.T) {
srv, wrksrv, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(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": "aws",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
}
}
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
jobId, token, jobType, _, _, err := wrksrv.RequestJob(context.Background(), test_distro.TestArch3Name, []string{worker.JobTypeOSBuild}, []string{""}, uuid.Nil)
require.NoError(t, err)
require.Equal(t, worker.JobTypeOSBuild, jobType)
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v",
"kind": "ComposeStatus",
"id": "%v",
"image_status": {"status": "building"},
"status": "pending"
}`, jobId, jobId))
err = wrksrv.FinishJob(token, nil)
require.NoError(t, err)
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v",
"kind": "ComposeStatus",
"id": "%v",
"image_status": {
"error": {
"id": 10,
"reason": "osbuild build failed"
},
"status": "failure"
},
"status": "failure"
}`, jobId, jobId))
}
func TestComposeStatusInvalidUUID(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", "/api/image-builder-composer/v2/composes/abcdef", ``, http.StatusBadRequest, `
{
"code": "IMAGE-BUILDER-COMPOSER-14",
"details": "",
"href": "/api/image-builder-composer/v2/errors/14",
"id": "14",
"kind": "Error",
"reason": "Invalid format for compose id"
}
`, "operation_id")
}
func TestComposeJobError(t *testing.T) {
srv, wrksrv, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(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": "aws",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
}
}
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
jobId, token, jobType, _, _, err := wrksrv.RequestJob(context.Background(), test_distro.TestArch3Name, []string{worker.JobTypeOSBuild}, []string{""}, uuid.Nil)
require.NoError(t, err)
require.Equal(t, worker.JobTypeOSBuild, jobType)
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v",
"kind": "ComposeStatus",
"id": "%v",
"image_status": {"status": "building"},
"status": "pending"
}`, jobId, jobId))
jobErr := worker.JobResult{
JobError: clienterrors.New(clienterrors.ErrorBuildJob, "Error building image", nil),
}
jobResult, err := json.Marshal(worker.OSBuildJobResult{JobResult: jobErr})
require.NoError(t, err)
err = wrksrv.FinishJob(token, jobResult)
require.NoError(t, err)
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v",
"kind": "ComposeStatus",
"id": "%v",
"image_status": {
"error": {
"id": 10,
"reason": "Error building image"
},
"status": "failure"
},
"status": "failure"
}`, jobId, jobId))
}
func TestComposeDependencyError(t *testing.T) {
srv, wrksrv, _, cancel := newV2Server(t, t.TempDir(), false, true)
defer cancel()
test.TestRoute(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": "edge-commit",
"ostree": {
"url": "somerepo.org",
"ref": "test"
},
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
}
}
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
jobId, token, jobType, _, _, err := wrksrv.RequestJob(context.Background(), test_distro.TestArch3Name, []string{worker.JobTypeOSBuild}, []string{""}, uuid.Nil)
require.NoError(t, err)
require.Equal(t, worker.JobTypeOSBuild, jobType)
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v",
"kind": "ComposeStatus",
"id": "%v",
"image_status": {"status": "building"},
"status": "pending"
}`, jobId, jobId))
jobErr := worker.JobResult{
JobError: clienterrors.New(clienterrors.ErrorManifestDependency, "Manifest dependency failed", nil),
}
jobResult, err := json.Marshal(worker.OSBuildJobResult{JobResult: jobErr})
require.NoError(t, err)
err = wrksrv.FinishJob(token, jobResult)
require.NoError(t, err)
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v",
"kind": "ComposeStatus",
"id": "%v",
"image_status": {
"error": {
"details": [{
"id": 5,
"reason": "Error in depsolve job dependency",
"details": [{
"id": 22,
"reason": "DNF Error"
},
{
"id": 34,
"reason": "ostree error"
}]
}],
"id": 9,
"reason": "Manifest dependency failed"
},
"status": "failure"
},
"status": "failure"
}`, jobId, jobId))
}
func TestComposeTargetErrors(t *testing.T) {
srv, wrksrv, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(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": "aws",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
}
}
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
jobId, token, jobType, _, _, err := wrksrv.RequestJob(context.Background(), test_distro.TestArch3Name, []string{worker.JobTypeOSBuild}, []string{""}, uuid.Nil)
require.NoError(t, err)
require.Equal(t, worker.JobTypeOSBuild, jobType)
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v",
"kind": "ComposeStatus",
"id": "%v",
"image_status": {"status": "building"},
"status": "pending"
}`, jobId, jobId))
oJR := worker.OSBuildJobResult{
TargetResults: []*target.TargetResult{
{
Name: "org.osbuild.aws",
Options: target.AWSTargetResultOptions{Ami: "", Region: ""},
TargetError: clienterrors.New(clienterrors.ErrorImportingImage, "error importing image", nil),
},
},
}
jobErr := worker.JobResult{
JobError: clienterrors.New(clienterrors.ErrorTargetError, "at least one target failed", oJR.TargetErrors()),
}
oJR.JobResult = jobErr
jobResult, err := json.Marshal(oJR)
require.NoError(t, err)
err = wrksrv.FinishJob(token, jobResult)
require.NoError(t, err)
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v",
"kind": "ComposeStatus",
"id": "%v",
"image_status": {
"error": {
"details": [{
"id": 12,
"reason": "error importing image",
"details": "org.osbuild.aws"
}],
"id": 28,
"reason": "at least one target failed"
},
"status": "failure",
"upload_status": {
"options": {
"ami": "",
"region": ""
},
"status": "failure",
"type": "aws"
},
"upload_statuses": [{
"options": {
"ami": "",
"region": ""
},
"status": "failure",
"type": "aws"
}]
},
"status": "failure"
}`, jobId, jobId))
}
func TestComposeCustomizations(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
{
"distribution": "%s",
"customizations": {
"subscription": {
"organization": "2040324",
"activation_key": "my-secret-key",
"server_url": "subscription.rhsm.redhat.com",
"base_url": "http://cdn.redhat.com/",
"insights": true
},
"packages": [ "pkg1", "pkg2" ],
"users": [{
"name": "user1",
"groups": [ "wheel" ],
"key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrGKErMYi+MMUwuHaRAJmRLoIzRf2qD2dD5z0BTx/6x"
}],
"payload_repositories": [{
"baseurl": "some-custom-repo.org",
"check_gpg": false,
"ignore_ssl": false,
"gpg_key": "some-gpg-key"
}],
"custom_repositories": [{
"name": "hello",
"id": "hello",
"baseurl": [ "http://hello.com" ],
"gpg_key": [ "somekey" ],
"check_gpg": true,
"enabled": true
}],
"services": {
"enabled": [
"nftables"
],
"disabled": [
"firewalld"
]
},
"directories": [
{
"path": "/etc/my/dir",
"mode": "0700"
},
{
"path": "/etc/my/dir1",
"mode": "0700",
"user": "user1",
"group": "user1",
"ensure_parents": true
},
{
"path": "/etc/my/dir2",
"mode": "0700",
"user": 1000,
"group": 1000,
"ensure_parents": true
}
],
"files": [
{
"path": "/etc/my/dir/file",
"mode": "0600",
"data": "Hello world!"
},
{
"path": "/etc/my/dir/file2",
"mode": "0600",
"user": "user1",
"group": "user1",
"data": "Hello world!"
},
{
"path": "/etc/my/dir/file3",
"mode": "0600",
"user": 1000,
"group": 1000,
"data": "Hello world!"
}
],
"openscap": {
"profile_id": "test_profile"
}
},
"image_request":{
"architecture": "%s",
"image_type": "aws",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
}
}
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
}
func TestComposeRhcSubscription(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
{
"distribution": "%s",
"customizations": {
"subscription": {
"organization": "2040324",
"activation_key": "my-secret-key",
"server_url": "subscription.rhsm.redhat.com",
"base_url": "http://cdn.redhat.com/",
"insights": false,
"rhc": true
},
"packages": [ "pkg1", "pkg2" ]
},
"image_request":{
"architecture": "%s",
"image_type": "aws",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
}
}
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
}
func TestImageTypes(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(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")
test.TestRoute(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")
test.TestRoute(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"
}
}
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name, string(v2.ImageTypesEdgeCommit)), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
test.TestRoute(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"
}
}
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name, string(v2.ImageTypesEdgeInstaller)), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
test.TestRoute(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": {
"subscription_id": "4e5d8b2c-ab24-4413-90c5-612306e809e2",
"tenant_id": "5c7ef5b6-1c3f-4da0-a622-0b060239d7d7",
"resource_group": "ToucanResourceGroup",
"location": "westeurope",
"hyper_v_generation": "V2"
}
}
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name, string(v2.ImageTypesAzure)), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
test.TestRoute(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",
"bucket": "some-eu-bucket",
"share_with_accounts": ["user:alice@example.com"]
}
}
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name, string(v2.ImageTypesGcp)), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
test.TestRoute(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"
}
}
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name, string(v2.ImageTypesImageInstaller)), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
test.TestRoute(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"
}
}
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name, string(v2.ImageTypesGuestImage)), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
test.TestRoute(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"
}
}
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name, string(v2.ImageTypesVsphere)), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
}
func TestImageFromCompose(t *testing.T) {
srv, wrksrv, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(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": "aws",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
}
}
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
jobId, token, jobType, _, _, err := wrksrv.RequestJob(context.Background(), test_distro.TestArch3Name, []string{worker.JobTypeOSBuild}, []string{""}, uuid.Nil)
require.NoError(t, err)
require.Equal(t, worker.JobTypeOSBuild, jobType)
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v",
"kind": "ComposeStatus",
"id": "%v",
"image_status": {"status": "building"},
"status": "pending"
}`, jobId, jobId))
tr := target.NewAWSTargetResult(&target.AWSTargetResultOptions{
Ami: "ami-abc123",
Region: "eu-central-1",
}, &target.OsbuildArtifact{
ExportFilename: "image.raw",
ExportName: "image",
})
res, err := json.Marshal(&worker.OSBuildJobResult{
Success: true,
OSBuildOutput: &osbuild.Result{Success: true},
TargetResults: []*target.TargetResult{
tr,
},
})
require.NoError(t, err)
err = wrksrv.FinishJob(token, res)
require.NoError(t, err)
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v",
"kind": "ComposeStatus",
"id": "%v",
"status": "success",
"image_status": {
"status": "success",
"upload_status": {
"type": "aws",
"status": "success",
"options": {
"ami": "ami-abc123",
"region": "eu-central-1"
}
},
"upload_statuses": [{
"type": "aws",
"status": "success",
"options": {
"ami": "ami-abc123",
"region": "eu-central-1"
}
}]
}
}`, jobId, jobId))
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST",
fmt.Sprintf("/api/image-builder-composer/v2/composes/%v/clone", jobId), `
{
"region": "eu-central-2",
"share_with_accounts": ["123456789012"]
}`, http.StatusCreated, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v/clone",
"kind": "CloneComposeId"
}`, jobId), "id")
_, token, jobType, _, _, err = wrksrv.RequestJob(context.Background(), test_distro.TestArch3Name, []string{worker.JobTypeAWSEC2Copy}, []string{""}, uuid.Nil)
require.NoError(t, err)
require.Equal(t, worker.JobTypeAWSEC2Copy, jobType)
res, err = json.Marshal(&worker.AWSEC2CopyJobResult{
Ami: "ami-def456",
Region: "eu-central-2",
})
require.NoError(t, err)
err = wrksrv.FinishJob(token, res)
require.NoError(t, err)
imgJobId, token, jobType, _, _, err := wrksrv.RequestJob(context.Background(), test_distro.TestArch3Name, []string{worker.JobTypeAWSEC2Share}, []string{""}, uuid.Nil)
require.NoError(t, err)
require.Equal(t, worker.JobTypeAWSEC2Share, jobType)
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/clones/%v", imgJobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/clones/%v",
"kind": "CloneComposeStatus",
"id": "%v",
"status": "running",
"type": "aws"
}`, imgJobId, imgJobId), "options")
res, err = json.Marshal(&worker.AWSEC2ShareJobResult{
Ami: "ami-def456",
Region: "eu-central-2",
})
require.NoError(t, err)
err = wrksrv.FinishJob(token, res)
require.NoError(t, err)
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/clones/%v", imgJobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/clones/%v",
"kind": "CloneComposeStatus",
"id": "%v",
"status": "success",
"type": "aws",
"options": {
"ami": "ami-def456",
"region": "eu-central-2"
}
}`, imgJobId, imgJobId))
}
func TestDepsolveBlueprint(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST",
"/api/image-builder-composer/v2/depsolve/blueprint", fmt.Sprintf(`
{
"blueprint": {
"name": "deptest1",
"version": "0.0.1",
"distro": "%[1]s",
"packages": [
{ "name": "dep-package", "version": "*" }
]},
"distribution": "%[1]s",
"architecture": "%[2]s"
}`, test_distro.TestDistro1Name, test_distro.TestArchName),
http.StatusOK,
`{
"packages": [
{
"name": "dep-package1",
"type": "rpm",
"version": "1.33",
"release": "2.fc30",
"arch": "x86_64",
"checksum": "sha256:fe3951d112c3b1c84dc8eac57afe0830df72df1ca0096b842f4db5d781189893"
}
]
}`)
}
func TestDepsolveDistroErrors(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
// matching distros, but not supported
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST",
"/api/image-builder-composer/v2/depsolve/blueprint", fmt.Sprintf(`
{
"blueprint": {
"name": "deptest1",
"version": "0.0.1",
"distro": "bart",
"packages": [
{ "name": "dep-package", "version": "*" }
]},
"distribution": "bart",
"architecture": "%[2]s"
}`, test_distro.TestDistro1Name, test_distro.TestArchName),
http.StatusBadRequest, `
{
"href": "/api/image-builder-composer/v2/errors/4",
"id": "4",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-4",
"reason": "Unsupported distribution"
}`, "operation_id", "details")
// Mismatched distros
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST",
"/api/image-builder-composer/v2/depsolve/blueprint", fmt.Sprintf(`
{
"blueprint": {
"name": "deptest1",
"version": "0.0.1",
"distro": "bart",
"packages": [
{ "name": "dep-package", "version": "*" }
]},
"distribution": "%[1]s",
"architecture": "%[2]s"
}`, test_distro.TestDistro1Name, test_distro.TestArchName),
http.StatusBadRequest, `
{
"href": "/api/image-builder-composer/v2/errors/40",
"id": "40",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-40",
"reason": "Invalid request, Blueprint and Cloud API request Distribution must match."
}`, "operation_id", "details")
// Bad distro in request, none in blueprint
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST",
"/api/image-builder-composer/v2/depsolve/blueprint", fmt.Sprintf(`
{
"blueprint": {
"name": "deptest1",
"version": "0.0.1",
"packages": [
{ "name": "dep-package", "version": "*" }
]},
"distribution": "bart",
"architecture": "%[2]s"
}`, test_distro.TestDistro1Name, test_distro.TestArchName),
http.StatusBadRequest, `
{
"href": "/api/image-builder-composer/v2/errors/4",
"id": "4",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-4",
"reason": "Unsupported distribution"
}`, "operation_id", "details")
}
func TestDepsolveArchErrors(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
// Unsupported architecture
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST",
"/api/image-builder-composer/v2/depsolve/blueprint", fmt.Sprintf(`
{
"blueprint": {
"name": "deptest1",
"version": "0.0.1",
"packages": [
{ "name": "dep-package", "version": "*" }
]},
"distribution": "%[1]s",
"architecture": "MOS6502",
}`, test_distro.TestDistro1Name),
http.StatusBadRequest, `
{
"href": "/api/image-builder-composer/v2/errors/30",
"id": "30",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-30",
"reason": "Request could not be validated"
}`, "operation_id", "details")
}
func TestSearchPackages(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST",
"/api/image-builder-composer/v2/search/packages", fmt.Sprintf(`
{
"packages": ["package1"],
"distribution": "%[1]s",
"architecture": "%[2]s"
}`, test_distro.TestDistro1Name, test_distro.TestArchName),
http.StatusOK,
`{
"packages": [
{
"name": "package1",
"summary": "The package you searched for",
"description": "A verbose paragraph about the package",
"version": "1.33",
"release": "2.fc42",
"arch": "x86_64",
"url": "https://example.com/package1",
"license": "GPLv3",
"buildtime": "1985-10-26T09:24:00Z"
}
]
}`)
}
func TestSearchDistroErrors(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
// Bad distro in request
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST",
"/api/image-builder-composer/v2/search/packages", fmt.Sprintf(`
{
"packages": ["package1"],
"distribution": "bart",
"architecture": "%[2]s"
}`, test_distro.TestDistro1Name, test_distro.TestArchName),
http.StatusBadRequest, `
{
"href": "/api/image-builder-composer/v2/errors/4",
"id": "4",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-4",
"reason": "Unsupported distribution"
}`, "operation_id", "details")
}
func TestSearchArchErrors(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
// Unsupported architecture
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST",
"/api/image-builder-composer/v2/search/packages", fmt.Sprintf(`
{
"packages": ["package1"],
"distribution": "%[1]s",
"architecture": "MOS6502",
}`, test_distro.TestDistro1Name),
http.StatusBadRequest, `
{
"href": "/api/image-builder-composer/v2/errors/30",
"id": "30",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-30",
"reason": "Request could not be validated"
}`, "operation_id", "details")
}
func TestComposesRoute(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), 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()))
}
func TestDownload(t *testing.T) {
srv, wrksrv, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
{
"distribution": "%s",
"image_requests": [{
"architecture": "%s",
"image_type": "guest-image",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_targets": [{
"type": "local",
"upload_options": {}
}]
}]
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
jobId, token, jobType, args, dynArgs, err := wrksrv.RequestJob(context.Background(), test_distro.TestArch3Name, []string{worker.JobTypeOSBuild}, []string{""}, uuid.Nil)
require.NoError(t, err)
require.Equal(t, worker.JobTypeOSBuild, jobType)
var osbuildJob worker.OSBuildJob
err = json.Unmarshal(args, &osbuildJob)
require.NoError(t, err)
require.Equal(t, 0, len(osbuildJob.Manifest))
require.NotEqual(t, 0, len(dynArgs[0]))
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v",
"kind": "ComposeStatus",
"id": "%v",
"image_status": {"status": "building"},
"status": "pending"
}`, jobId, jobId))
// Mock up a local target result
tr := target.NewWorkerServerTargetResult(
&target.WorkerServerTargetResultOptions{},
&target.OsbuildArtifact{
ExportFilename: "disk.qcow2",
ExportName: "qcow2",
})
res, err := json.Marshal(&worker.OSBuildJobResult{
Success: true,
OSBuildOutput: &osbuild.Result{Success: true},
TargetResults: []*target.TargetResult{
tr,
},
})
require.NoError(t, err)
err = wrksrv.FinishJob(token, res)
require.NoError(t, err)
// Write a fake disk.qcow2 file to the artifact directory
file, err := wrksrv.JobArtifactLocation(jobId, tr.OsbuildArtifact.ExportFilename)
// Error is expected, file doesn't exist yet
require.Error(t, err)
// Yes, the dummy file is json to make TestRoute happy
err = os.WriteFile(file, []byte("{\"msg\":\"This is the disk.qcow2 you are looking for\"}"), 0600)
require.NoError(t, err)
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET",
fmt.Sprintf("/api/image-builder-composer/v2/composes/%v/download", jobId),
``,
http.StatusOK,
`{
"msg": "This is the disk.qcow2 you are looking for"
}`)
}
func TestDownloadNotFinished(t *testing.T) {
srv, wrksrv, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
{
"distribution": "%s",
"image_requests": [{
"architecture": "%s",
"image_type": "guest-image",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_targets": [{
"type": "local",
"upload_options": {}
}]
}]
}`, test_distro.TestDistro1Name, test_distro.TestArch3Name), http.StatusCreated, `
{
"href": "/api/image-builder-composer/v2/compose",
"kind": "ComposeId"
}`, "id")
jobId, _, jobType, args, dynArgs, err := wrksrv.RequestJob(context.Background(), test_distro.TestArch3Name, []string{worker.JobTypeOSBuild}, []string{""}, uuid.Nil)
require.NoError(t, err)
require.Equal(t, worker.JobTypeOSBuild, jobType)
var osbuildJob worker.OSBuildJob
err = json.Unmarshal(args, &osbuildJob)
require.NoError(t, err)
require.Equal(t, 0, len(osbuildJob.Manifest))
require.NotEqual(t, 0, len(dynArgs[0]))
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
"href": "/api/image-builder-composer/v2/composes/%v",
"kind": "ComposeStatus",
"id": "%v",
"image_status": {"status": "building"},
"status": "pending"
}`, jobId, jobId))
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET",
fmt.Sprintf("/api/image-builder-composer/v2/composes/%v/download", jobId),
``,
http.StatusBadRequest,
fmt.Sprintf(`{
"href": "/api/image-builder-composer/v2/errors/1022",
"id": "1022",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-1022",
"details": "Cannot access artifacts before job is finished: %s",
"reason": "Artifact not found"
}`, jobId), "operation_id")
}
func TestDownloadUnknown(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET",
"/api/image-builder-composer/v2/composes/80977a35-1b27-4604-b998-9cd331f9089e/download",
``,
http.StatusNotFound,
`{
"href": "/api/image-builder-composer/v2/errors/15",
"id": "15",
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-15",
"details": "job does not exist",
"reason":"Compose with given id not found"
}`, "operation_id")
}