diff --git a/cmd/osbuild-worker/jobimpl-osbuild.go b/cmd/osbuild-worker/jobimpl-osbuild.go index 624de30f5..b2d00d63a 100644 --- a/cmd/osbuild-worker/jobimpl-osbuild.go +++ b/cmd/osbuild-worker/jobimpl-osbuild.go @@ -298,7 +298,8 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error { // copy pipeline info to the result osbuildJobResult.PipelineNames = jobArgs.PipelineNames - exports := jobArgs.Exports + // get exports for all job's targets + exports := jobArgs.OsbuildExports() if len(exports) == 0 { // job did not define exports, likely coming from an older version of composer // fall back to default "assembler" @@ -344,14 +345,13 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error { return nil } - exportPath := exports[0] for _, jobTarget := range jobArgs.Targets { var targetResult *target.TargetResult switch targetOptions := jobTarget.Options.(type) { case *target.WorkerServerTargetOptions: targetResult = target.NewWorkerServerTargetResult() var f *os.File - imagePath := path.Join(outputDirectory, exportPath, jobTarget.OsbuildArtifact.ExportFilename) + imagePath := path.Join(outputDirectory, jobTarget.OsbuildArtifact.ExportName, jobTarget.OsbuildArtifact.ExportFilename) f, err = os.Open(imagePath) if err != nil { targetResult.TargetError = clienterrors.WorkerClientError(clienterrors.ErrorInvalidTargetConfig, err.Error()) @@ -392,7 +392,7 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error { imageName := jobTarget.ImageName + ".vmdk" imagePath := path.Join(tempDirectory, imageName) - exportedImagePath := path.Join(outputDirectory, exportPath, jobTarget.OsbuildArtifact.ExportFilename) + exportedImagePath := path.Join(outputDirectory, jobTarget.OsbuildArtifact.ExportName, jobTarget.OsbuildArtifact.ExportFilename) err = os.Symlink(exportedImagePath, imagePath) if err != nil { targetResult.TargetError = clienterrors.WorkerClientError(clienterrors.ErrorInvalidConfig, err.Error()) @@ -422,7 +422,7 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error { if impl.AWSBucket != "" { bucket = impl.AWSBucket } - _, err = a.Upload(path.Join(outputDirectory, exportPath, jobTarget.OsbuildArtifact.ExportFilename), bucket, key) + _, err = a.Upload(path.Join(outputDirectory, jobTarget.OsbuildArtifact.ExportName, jobTarget.OsbuildArtifact.ExportFilename), bucket, key) if err != nil { targetResult.TargetError = clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, err.Error()) break @@ -451,7 +451,7 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error { break } - url, targetError := uploadToS3(a, outputDirectory, exportPath, bucket, targetOptions.Key, jobTarget.OsbuildArtifact.ExportFilename) + url, targetError := uploadToS3(a, outputDirectory, jobTarget.OsbuildArtifact.ExportName, bucket, targetOptions.Key, jobTarget.OsbuildArtifact.ExportFilename) if targetError != nil { targetResult.TargetError = targetError break @@ -477,7 +477,7 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error { const azureMaxUploadGoroutines = 4 err = azureStorageClient.UploadPageBlob( metadata, - path.Join(outputDirectory, exportPath, jobTarget.OsbuildArtifact.ExportFilename), + path.Join(outputDirectory, jobTarget.OsbuildArtifact.ExportName, jobTarget.OsbuildArtifact.ExportFilename), azureMaxUploadGoroutines, ) @@ -497,7 +497,7 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error { } logWithId.Infof("[GCP] 🚀 Uploading image to: %s/%s", targetOptions.Bucket, targetOptions.Object) - _, err = g.StorageObjectUpload(ctx, path.Join(outputDirectory, exportPath, jobTarget.OsbuildArtifact.ExportFilename), + _, err = g.StorageObjectUpload(ctx, path.Join(outputDirectory, jobTarget.OsbuildArtifact.ExportName, jobTarget.OsbuildArtifact.ExportFilename), targetOptions.Bucket, targetOptions.Object, map[string]string{gcp.MetadataKeyImageName: jobTarget.ImageName}) if err != nil { targetResult.TargetError = clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, err.Error()) @@ -625,7 +625,7 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error { ContainerName: storageContainer, BlobName: blobName, }, - path.Join(outputDirectory, exportPath, jobTarget.OsbuildArtifact.ExportFilename), + path.Join(outputDirectory, jobTarget.OsbuildArtifact.ExportName, jobTarget.OsbuildArtifact.ExportFilename), azure.DefaultUploadThreads, ) if err != nil { @@ -682,7 +682,7 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error { } }() - file, err := os.Open(path.Join(outputDirectory, exportPath, jobTarget.OsbuildArtifact.ExportFilename)) + file, err := os.Open(path.Join(outputDirectory, jobTarget.OsbuildArtifact.ExportName, jobTarget.OsbuildArtifact.ExportFilename)) if err != nil { targetResult.TargetError = clienterrors.WorkerClientError(clienterrors.ErrorKojiBuild, fmt.Sprintf("failed to open the image for reading: %v", err)) break @@ -718,7 +718,7 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error { } logWithId.Info("[OCI] 🔑 Logged in OCI") logWithId.Info("[OCI] ⬆ Uploading the image") - file, err := os.Open(path.Join(outputDirectory, exportPath, jobTarget.OsbuildArtifact.ExportFilename)) + file, err := os.Open(path.Join(outputDirectory, jobTarget.OsbuildArtifact.ExportName, jobTarget.OsbuildArtifact.ExportFilename)) if err != nil { targetResult.TargetError = clienterrors.WorkerClientError(clienterrors.ErrorInvalidConfig, err.Error()) break @@ -760,7 +760,7 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error { client.TlsVerify = *targetOptions.TlsVerify } - sourcePath := path.Join(outputDirectory, exportPath, jobTarget.OsbuildArtifact.ExportFilename) + sourcePath := path.Join(outputDirectory, jobTarget.OsbuildArtifact.ExportName, jobTarget.OsbuildArtifact.ExportFilename) // TODO: get the container type from the metadata of the osbuild job sourceRef := fmt.Sprintf("oci-archive:%s", sourcePath) diff --git a/internal/cloudapi/v2/handler.go b/internal/cloudapi/v2/handler.go index a48388625..87dfc0bad 100644 --- a/internal/cloudapi/v2/handler.go +++ b/internal/cloudapi/v2/handler.go @@ -435,6 +435,8 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error { default: return HTTPError(ErrorUnsupportedImageType) } + + irTarget.OsbuildArtifact.ExportName = imageType.Exports()[0] } irs = append(irs, imageRequest{ diff --git a/internal/cloudapi/v2/server.go b/internal/cloudapi/v2/server.go index 7e5570066..7ba674822 100644 --- a/internal/cloudapi/v2/server.go +++ b/internal/cloudapi/v2/server.go @@ -119,7 +119,6 @@ func (s *Server) enqueueCompose(distribution distro.Distro, bp blueprint.Bluepri id, err = s.workers.EnqueueOSBuildAsDependency(ir.arch.Name(), &worker.OSBuildJob{ Targets: []*target.Target{ir.target}, - Exports: ir.imageType.Exports(), PipelineNames: &worker.PipelineNames{ Build: ir.imageType.BuildPipelines(), Payload: ir.imageType.PayloadPipelines(), @@ -183,10 +182,10 @@ func (s *Server) enqueueKojiCompose(taskID uint64, server, name, version, releas UploadDirectory: kojiDirectory, }) kojiTarget.OsbuildArtifact.ExportFilename = ir.imageType.Filename() + kojiTarget.OsbuildArtifact.ExportName = ir.imageType.Exports()[0] kojiTarget.ImageName = kojiFilename buildID, err := s.workers.EnqueueOSBuildAsDependency(ir.arch.Name(), &worker.OSBuildJob{ - Exports: ir.imageType.Exports(), PipelineNames: &worker.PipelineNames{ Build: ir.imageType.BuildPipelines(), Payload: ir.imageType.PayloadPipelines(), diff --git a/internal/target/target.go b/internal/target/target.go index b93310fb0..65c814f20 100644 --- a/internal/target/target.go +++ b/internal/target/target.go @@ -16,6 +16,8 @@ type TargetName string type OsbuildArtifact struct { // Filename of the image as produced by osbuild for a given export ExportFilename string `json:"export_filename"` + // Name of the osbuild pipeline, which should be exported for this target + ExportName string `json:"export_name"` } type Target struct { diff --git a/internal/target/target_test.go b/internal/target/target_test.go index 6007aa8f3..216bd1362 100644 --- a/internal/target/target_test.go +++ b/internal/target/target_test.go @@ -212,7 +212,7 @@ func TestTargetOptionsFilenameCompatibilityMarshal(t *testing.T) { target *Target }{ { - targetJSON: []byte(`{"uuid":"00000000-0000-0000-0000-000000000000","image_name":"my-image","name":"org.osbuild.aws","created":"0001-01-01T00:00:00Z","status":"WAITING","options":{"region":"us","accessKeyID":"id","secretAccessKey":"key","sessionToken":"token","bucket":"bkt","key":"key","shareWithAccounts":["123456789"],"filename":"image.qcow2"},"osbuild_artifact":{"export_filename":"image.qcow2"}}`), + targetJSON: []byte(`{"uuid":"00000000-0000-0000-0000-000000000000","image_name":"my-image","name":"org.osbuild.aws","created":"0001-01-01T00:00:00Z","status":"WAITING","options":{"region":"us","accessKeyID":"id","secretAccessKey":"key","sessionToken":"token","bucket":"bkt","key":"key","shareWithAccounts":["123456789"],"filename":"image.qcow2"},"osbuild_artifact":{"export_filename":"image.qcow2","export_name":""}}`), target: &Target{ ImageName: "my-image", OsbuildArtifact: OsbuildArtifact{ diff --git a/internal/weldr/api.go b/internal/weldr/api.go index 3535c59c4..91ee1e921 100644 --- a/internal/weldr/api.go +++ b/internal/weldr/api.go @@ -2251,6 +2251,7 @@ func (api *API) composeHandler(writer http.ResponseWriter, request *http.Request workerServerTarget := target.NewWorkerServerTarget() workerServerTarget.ImageName = imageType.Filename() workerServerTarget.OsbuildArtifact.ExportFilename = imageType.Filename() + workerServerTarget.OsbuildArtifact.ExportName = imageType.Exports()[0] targets = append(targets, workerServerTarget) if isRequestVersionAtLeast(params, 1) && cr.Upload != nil { t := uploadRequestToTarget(*cr.Upload, imageType) @@ -2356,7 +2357,6 @@ func (api *API) composeHandler(writer http.ResponseWriter, request *http.Request jobId, err = api.workers.EnqueueOSBuild(api.archName, &worker.OSBuildJob{ Manifest: manifest, Targets: targets, - Exports: imageType.Exports(), PipelineNames: &worker.PipelineNames{ Build: imageType.BuildPipelines(), Payload: imageType.PayloadPipelines(), diff --git a/internal/weldr/api_test.go b/internal/weldr/api_test.go index 1dc71d670..d79160e95 100644 --- a/internal/weldr/api_test.go +++ b/internal/weldr/api_test.go @@ -690,6 +690,7 @@ func TestCompose(t *testing.T) { ImageName: imgType.Filename(), OsbuildArtifact: target.OsbuildArtifact{ ExportFilename: imgType.Filename(), + ExportName: imgType.Exports()[0], }, Name: target.TargetNameWorkerServer, Options: &target.WorkerServerTargetOptions{}, @@ -716,6 +717,7 @@ func TestCompose(t *testing.T) { ImageName: imgType.Filename(), OsbuildArtifact: target.OsbuildArtifact{ ExportFilename: imgType.Filename(), + ExportName: imgType.Exports()[0], }, Name: target.TargetNameWorkerServer, Options: &target.WorkerServerTargetOptions{}, @@ -726,6 +728,7 @@ func TestCompose(t *testing.T) { ImageName: "test_upload", OsbuildArtifact: target.OsbuildArtifact{ ExportFilename: imgType.Filename(), + ExportName: imgType.Exports()[0], }, Options: &target.AWSTargetOptions{ Region: "frankfurt", @@ -757,6 +760,7 @@ func TestCompose(t *testing.T) { ImageName: imgType.Filename(), OsbuildArtifact: target.OsbuildArtifact{ ExportFilename: imgType.Filename(), + ExportName: imgType.Exports()[0], }, Name: target.TargetNameWorkerServer, Options: &target.WorkerServerTargetOptions{}, @@ -793,6 +797,7 @@ func TestCompose(t *testing.T) { ImageName: imgType2.Filename(), OsbuildArtifact: target.OsbuildArtifact{ ExportFilename: imgType2.Filename(), + ExportName: imgType2.Exports()[0], }, Name: target.TargetNameWorkerServer, Options: &target.WorkerServerTargetOptions{}, @@ -1733,6 +1738,7 @@ func TestComposePOST_ImageTypeDenylist(t *testing.T) { ImageName: imgType.Filename(), OsbuildArtifact: target.OsbuildArtifact{ ExportFilename: imgType.Filename(), + ExportName: imgType.Exports()[0], }, Name: target.TargetNameWorkerServer, Options: &target.WorkerServerTargetOptions{}, @@ -1760,6 +1766,7 @@ func TestComposePOST_ImageTypeDenylist(t *testing.T) { ImageName: imgType2.Filename(), OsbuildArtifact: target.OsbuildArtifact{ ExportFilename: imgType2.Filename(), + ExportName: imgType2.Exports()[0], }, Name: target.TargetNameWorkerServer, Options: &target.WorkerServerTargetOptions{}, diff --git a/internal/weldr/upload.go b/internal/weldr/upload.go index fbceff5ab..df7e2a70b 100644 --- a/internal/weldr/upload.go +++ b/internal/weldr/upload.go @@ -244,6 +244,7 @@ func uploadRequestToTarget(u uploadRequest, imageType distro.ImageType) *target. t.Uuid = uuid.New() t.ImageName = u.ImageName t.OsbuildArtifact.ExportFilename = imageType.Filename() + t.OsbuildArtifact.ExportName = imageType.Exports()[0] t.Status = common.IBWaiting t.Created = time.Now() diff --git a/internal/worker/json.go b/internal/worker/json.go index 46831d549..a0fd3d1af 100644 --- a/internal/worker/json.go +++ b/internal/worker/json.go @@ -20,10 +20,25 @@ type OSBuildJob struct { // Index of the ManifestJobByIDResult instance in the job's dynamic arguments slice ManifestDynArgsIdx *int `json:"manifest_dyn_args_idx,omitempty"` Targets []*target.Target `json:"targets,omitempty"` - Exports []string `json:"export_stages,omitempty"` PipelineNames *PipelineNames `json:"pipeline_names,omitempty"` } +// OsbuildExports returns a slice of osbuild pipeline names, which should be +// exported as part of running osbuild image build for the job. The pipeline +// names are gathered from the targets specified in the job. +func (j OSBuildJob) OsbuildExports() []string { + exports := []string{} + seenExports := map[string]bool{} + for _, target := range j.Targets { + exists := seenExports[target.OsbuildArtifact.ExportName] + if !exists { + seenExports[target.OsbuildArtifact.ExportName] = true + exports = append(exports, target.OsbuildArtifact.ExportName) + } + } + return exports +} + type JobResult struct { JobError *clienterrors.Error `json:"job_error,omitempty"` } @@ -239,21 +254,64 @@ type updateJobRequest struct { func (j *OSBuildJob) UnmarshalJSON(data []byte) error { // handles unmarshalling old jobs in the queue that don't contain newer fields // adds default/fallback values to missing data - type aliastype OSBuildJob - var alias aliastype - if err := json.Unmarshal(data, &alias); err != nil { + type aliasType OSBuildJob + type compatType struct { + aliasType + // Deprecated: Exports should not be used. The export is set in the `Target.OsbuildExport` + Exports []string `json:"export_stages,omitempty"` + } + var compat compatType + if err := json.Unmarshal(data, &compat); err != nil { return err } - if alias.PipelineNames == nil { - alias.PipelineNames = &PipelineNames{ + if compat.PipelineNames == nil { + compat.PipelineNames = &PipelineNames{ Build: distro.BuildPipelinesFallback(), Payload: distro.PayloadPipelinesFallback(), } } - *j = OSBuildJob(alias) + + // Exports used to be specified in the job, but there could be always only a single export specified. + if len(compat.Exports) != 0 { + if len(compat.Exports) > 1 { + return fmt.Errorf("osbuild job has more than one exports specified") + } + export := compat.Exports[0] + // add the single export to each target + for idx := range compat.Targets { + target := compat.Targets[idx] + if target.OsbuildArtifact.ExportName == "" { + target.OsbuildArtifact.ExportName = export + } else if target.OsbuildArtifact.ExportName != export { + return fmt.Errorf("osbuild job has different global exports and export in the target specified at the same time") + } + compat.Targets[idx] = target + } + } + + *j = OSBuildJob(compat.aliasType) return nil } +func (j OSBuildJob) MarshalJSON() ([]byte, error) { + type aliasType OSBuildJob + type compatType struct { + aliasType + // Depredated: Exports should not be used. The export is set in the `Target.OsbuildExport` + Exports []string `json:"export_stages,omitempty"` + } + compat := compatType{ + aliasType: aliasType(j), + } + compat.Exports = j.OsbuildExports() + + data, err := json.Marshal(&compat) + if err != nil { + return nil, err + } + return data, nil +} + func (j *OSBuildJobResult) UnmarshalJSON(data []byte) error { // handles unmarshalling old jobs in the queue that don't contain newer fields // adds default/fallback values to missing data diff --git a/internal/worker/json_test.go b/internal/worker/json_test.go index ba1089495..6f629e27f 100644 --- a/internal/worker/json_test.go +++ b/internal/worker/json_test.go @@ -1,8 +1,11 @@ package worker import ( + "encoding/json" + "fmt" "testing" + "github.com/osbuild/osbuild-composer/internal/distro" "github.com/osbuild/osbuild-composer/internal/target" "github.com/osbuild/osbuild-composer/internal/worker/clienterrors" "github.com/stretchr/testify/assert" @@ -83,3 +86,250 @@ func TestOSBuildJobResultTargetErrors(t *testing.T) { assert.EqualValues(t, testCase.targetErrors, testCase.jobResult.TargetErrors()) } } + +func TestOSBuildJobExports(t *testing.T) { + testCases := []struct { + job *OSBuildJob + expectedExports []string + }{ + // one target with export set + { + job: &OSBuildJob{ + Manifest: []byte("manifest"), + Targets: []*target.Target{ + { + Name: target.TargetNameAWS, + OsbuildArtifact: target.OsbuildArtifact{ + ExportName: "archive", + }, + }, + }, + }, + expectedExports: []string{"archive"}, + }, + // multiple targets with different exports set + { + job: &OSBuildJob{ + Manifest: []byte("manifest"), + Targets: []*target.Target{ + { + Name: target.TargetNameAWS, + OsbuildArtifact: target.OsbuildArtifact{ + ExportName: "archive", + }, + }, + { + Name: target.TargetNameAWSS3, + OsbuildArtifact: target.OsbuildArtifact{ + ExportName: "image", + }, + }, + }, + }, + expectedExports: []string{"archive", "image"}, + }, + // multiple targets with the same export + { + job: &OSBuildJob{ + Manifest: []byte("manifest"), + Targets: []*target.Target{ + { + Name: target.TargetNameAWS, + OsbuildArtifact: target.OsbuildArtifact{ + ExportName: "archive", + }, + }, + { + Name: target.TargetNameAWSS3, + OsbuildArtifact: target.OsbuildArtifact{ + ExportName: "archive", + }, + }, + }, + }, + expectedExports: []string{"archive"}, + }, + } + + for idx, testCase := range testCases { + t.Run(fmt.Sprintf("case #%d", idx), func(t *testing.T) { + assert.EqualValues(t, testCase.expectedExports, testCase.job.OsbuildExports()) + }) + } +} + +// Test that that exports set in the OSBuildJob get added to all targets +// defined in the job. +// This covers the case when new worker receives a job from old composer. +// This covers the case when new worker receives a job from new composer. +func TestOSBuildJobExportsCompatibilityUnmarshal(t *testing.T) { + testCases := []struct { + jobJSON []byte + job *OSBuildJob + expectedExports []string + err bool + }{ + // Test that one export specified on the job level gets added to each target + { + jobJSON: []byte(`{"export_stages":["archive"],"targets":[{"name":"org.osbuild.aws","options":{}}]}`), + job: &OSBuildJob{ + Targets: []*target.Target{ + { + Name: target.TargetNameAWS, + OsbuildArtifact: target.OsbuildArtifact{ + ExportName: "archive", + }, + Options: &target.AWSTargetOptions{}, + }, + }, + PipelineNames: &PipelineNames{ + Build: distro.BuildPipelinesFallback(), + Payload: distro.PayloadPipelinesFallback(), + }, + }, + expectedExports: []string{"archive"}, + }, + // Test that one export specified on the job level gets added to each target + { + jobJSON: []byte(`{"export_stages":["archive"],"targets":[{"name":"org.osbuild.aws","options":{}},{"name":"org.osbuild.aws.s3","options":{}}]}`), + job: &OSBuildJob{ + Targets: []*target.Target{ + { + Name: target.TargetNameAWS, + OsbuildArtifact: target.OsbuildArtifact{ + ExportName: "archive", + }, + Options: &target.AWSTargetOptions{}, + }, + { + Name: target.TargetNameAWSS3, + OsbuildArtifact: target.OsbuildArtifact{ + ExportName: "archive", + }, + Options: &target.AWSS3TargetOptions{}, + }, + }, + PipelineNames: &PipelineNames{ + Build: distro.BuildPipelinesFallback(), + Payload: distro.PayloadPipelinesFallback(), + }, + }, + expectedExports: []string{"archive"}, + }, + // Test that the job as Marshalled by the current compatibility code is also acceptable/ + // Such job has exports set on the job level, but also in the targets + { + jobJSON: []byte(`{"export_stages":["archive"],"targets":[{"name":"org.osbuild.aws","options":{},"osbuild_export":"archive"},{"name":"org.osbuild.aws.s3","options":{},"osbuild_export":"archive"}]}`), + job: &OSBuildJob{ + Targets: []*target.Target{ + { + Name: target.TargetNameAWS, + OsbuildArtifact: target.OsbuildArtifact{ + ExportName: "archive", + }, + Options: &target.AWSTargetOptions{}, + }, + { + Name: target.TargetNameAWSS3, + OsbuildArtifact: target.OsbuildArtifact{ + ExportName: "archive", + }, + Options: &target.AWSS3TargetOptions{}, + }, + }, + PipelineNames: &PipelineNames{ + Build: distro.BuildPipelinesFallback(), + Payload: distro.PayloadPipelinesFallback(), + }, + }, + expectedExports: []string{"archive"}, + }, + // Test that different exports defined on the job and target level generate an error + { + jobJSON: []byte(`{"export_stages":["archive"],"targets":[{"name":"org.osbuild.aws","options":{},"osbuild_artifact":{"export_name":"image"}},{"name":"org.osbuild.aws.s3","options":{},"osbuild_artifact":{"export_name":"archive"}}]}`), + err: true, + }, + // Test that different exports defined on the job and target level generate an error + { + jobJSON: []byte(`{"export_stages":["image"],"targets":[{"name":"org.osbuild.aws","options":{},"osbuild_artifact":{"export_name":"archive"}},{"name":"org.osbuild.aws.s3","options":{},"osbuild_artifact":{"export_name":"archive"}}]}`), + err: true, + }, + // Test that multiple exports defined on the job level generate an error + { + jobJSON: []byte(`{"export_stages":["archive","image"],"targets":[{"name":"org.osbuild.aws","options":{},"osbuild_export":"archive"},{"name":"org.osbuild.aws.s3","options":{},"osbuild_export":"archive"}]}`), + err: true, + }, + } + + for idx, testCase := range testCases { + t.Run(fmt.Sprintf("Case #%d", idx), func(t *testing.T) { + gotJob := OSBuildJob{} + err := json.Unmarshal(testCase.jobJSON, &gotJob) + if testCase.err { + assert.Error(t, err) + } else { + assert.Nil(t, err) + assert.EqualValues(t, testCase.job, &gotJob) + assert.EqualValues(t, testCase.expectedExports, gotJob.OsbuildExports()) + } + }) + } +} + +// Test that that exports set in the OSBuildJob target get added to the job +// definition itself. +// This covers the case when jobs from new composer are to be picked by old worker. +// This covers the case when new worker receives a job from new composer. +func TestOSBuildJobExportsCompatibilityMarshal(t *testing.T) { + testCases := []struct { + jobJSON []byte + job *OSBuildJob + }{ + // Test that the export specified in the target is set also on the job level as it used to be in the past + { + jobJSON: []byte(`{"targets":[{"uuid":"00000000-0000-0000-0000-000000000000","image_name":"","name":"org.osbuild.aws","created":"0001-01-01T00:00:00Z","status":"WAITING","options":{"region":"","accessKeyID":"","secretAccessKey":"","sessionToken":"","bucket":"","key":"","shareWithAccounts":null,"filename":""},"osbuild_artifact":{"export_filename":"","export_name":"archive"}}],"export_stages":["archive"]}`), + job: &OSBuildJob{ + Targets: []*target.Target{ + { + Name: target.TargetNameAWS, + OsbuildArtifact: target.OsbuildArtifact{ + ExportName: "archive", + }, + Options: &target.AWSTargetOptions{}, + }, + }, + }, + }, + // Test that the export specified in multiple targets is set also on the job level as it used to be in the past. + // We do not test with multiple different exports, because multiple exports in job definition were never allowed. + { + jobJSON: []byte(`{"targets":[{"uuid":"00000000-0000-0000-0000-000000000000","image_name":"","name":"org.osbuild.aws","created":"0001-01-01T00:00:00Z","status":"WAITING","options":{"region":"","accessKeyID":"","secretAccessKey":"","sessionToken":"","bucket":"","key":"","shareWithAccounts":null,"filename":""},"osbuild_artifact":{"export_filename":"","export_name":"archive"}},{"uuid":"00000000-0000-0000-0000-000000000000","image_name":"","name":"org.osbuild.aws.s3","created":"0001-01-01T00:00:00Z","status":"WAITING","options":{"region":"","accessKeyID":"","secretAccessKey":"","sessionToken":"","bucket":"","key":"","endpoint":"","ca_bundle":"","skip_ssl_verification":false,"filename":""},"osbuild_artifact":{"export_filename":"","export_name":"archive"}}],"export_stages":["archive"]}`), + job: &OSBuildJob{ + Targets: []*target.Target{ + { + Name: target.TargetNameAWS, + OsbuildArtifact: target.OsbuildArtifact{ + ExportName: "archive", + }, + Options: &target.AWSTargetOptions{}, + }, + { + Name: target.TargetNameAWSS3, + OsbuildArtifact: target.OsbuildArtifact{ + ExportName: "archive", + }, + Options: &target.AWSS3TargetOptions{}, + }, + }, + }, + }, + } + + for idx, testCase := range testCases { + t.Run(fmt.Sprintf("Case #%d", idx), func(t *testing.T) { + gotJSON, err := json.Marshal(testCase.job) + assert.Nil(t, err) + assert.EqualValues(t, testCase.jobJSON, gotJSON) + }) + } +} diff --git a/internal/worker/server_test.go b/internal/worker/server_test.go index c8363fe13..f6b3b9575 100644 --- a/internal/worker/server_test.go +++ b/internal/worker/server_test.go @@ -225,17 +225,19 @@ func TestArgs(t *testing.T) { job := worker.OSBuildJob{ Manifest: manifest, - Exports: []string{"assembler"}, PipelineNames: &worker.PipelineNames{ Build: []string{"b"}, Payload: []string{"x", "y", "z"}, }, Targets: []*target.Target{ { - Name: target.TargetNameWorkerServer, - ImageName: "test-image", - ExportFilename: "test-image", - Options: &target.WorkerServerTargetOptions{}, + Name: target.TargetNameWorkerServer, + ImageName: "test-image", + OsbuildArtifact: target.OsbuildArtifact{ + ExportFilename: "test-image", + ExportName: "assembler", + }, + Options: &target.WorkerServerTargetOptions{}, }, }, } @@ -409,13 +411,15 @@ func TestMixedOSBuildJob(t *testing.T) { oldJob := worker.OSBuildJob{ Manifest: emptyManifestV2, - Exports: []string{"assembler"}, Targets: []*target.Target{ { - Name: target.TargetNameWorkerServer, - ImageName: "no-pipeline-names", - ExportFilename: "no-pipeline-names", - Options: &target.WorkerServerTargetOptions{}, + Name: target.TargetNameWorkerServer, + ImageName: "no-pipeline-names", + OsbuildArtifact: target.OsbuildArtifact{ + ExportFilename: "no-pipeline-names", + ExportName: "assembler", + }, + Options: &target.WorkerServerTargetOptions{}, }, }, } @@ -424,17 +428,19 @@ func TestMixedOSBuildJob(t *testing.T) { newJob := worker.OSBuildJob{ Manifest: emptyManifestV2, - Exports: []string{"assembler"}, PipelineNames: &worker.PipelineNames{ Build: []string{"build"}, Payload: []string{"other", "pipelines"}, }, Targets: []*target.Target{ { - Name: target.TargetNameWorkerServer, - ImageName: "with-pipeline-names", - ExportFilename: "with-pipeline-names", - Options: &target.WorkerServerTargetOptions{}, + Name: target.TargetNameWorkerServer, + ImageName: "with-pipeline-names", + OsbuildArtifact: target.OsbuildArtifact{ + ExportFilename: "with-pipeline-names", + ExportName: "assembler", + }, + Options: &target.WorkerServerTargetOptions{}, }, }, }