debian-forge-composer/internal/worker/json_test.go
Tomas Hozza 77a1672b79 worker/koji-finalize: handle multiple upload targets
Enhance the `koji-finalize` job implementation to be able to cope with
multiple upload targets being specified for an `OSBuildJob`.

Implement a convenience method `OSBuildJobResult.TargetResultsByName()`
for filtering the target results attached to the job result by their
name. Cover the method with an unit test. And lastly use this method in
the `koji-finalize` job to find the appropriate Koji upload target
results.

This is a preparation for enabling cloud uploads for Koji composes.
2022-07-22 11:39:49 +01:00

437 lines
15 KiB
Go

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"
)
func TestOSBuildJobResultTargetErrors(t *testing.T) {
testCases := []struct {
jobResult OSBuildJobResult
targetErrors []*clienterrors.Error
}{
{
jobResult: OSBuildJobResult{
TargetResults: []*target.TargetResult{
{
Name: target.TargetNameAWS,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorInvalidTargetConfig, "can't login to AWS"),
},
{
Name: target.TargetNameVMWare,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
},
{
Name: target.TargetNameAWSS3,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "failed to upload image to AWS S3"),
},
},
},
targetErrors: []*clienterrors.Error{
clienterrors.WorkerClientError(clienterrors.ErrorInvalidTargetConfig, "can't login to AWS", target.TargetNameAWS),
clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare", target.TargetNameVMWare),
clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "failed to upload image to AWS S3", target.TargetNameAWSS3),
},
},
{
jobResult: OSBuildJobResult{
TargetResults: []*target.TargetResult{
{
Name: target.TargetNameAWS,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorInvalidTargetConfig, "can't login to AWS"),
},
{
Name: target.TargetNameVMWare,
},
{
Name: target.TargetNameAWSS3,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "failed to upload image to AWS S3"),
},
},
},
targetErrors: []*clienterrors.Error{
clienterrors.WorkerClientError(clienterrors.ErrorInvalidTargetConfig, "can't login to AWS", target.TargetNameAWS),
clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "failed to upload image to AWS S3", target.TargetNameAWSS3),
},
},
{
jobResult: OSBuildJobResult{
TargetResults: []*target.TargetResult{
{
Name: target.TargetNameAWS,
},
{
Name: target.TargetNameVMWare,
},
{
Name: target.TargetNameAWSS3,
},
},
},
targetErrors: []*clienterrors.Error{},
},
{
jobResult: OSBuildJobResult{},
targetErrors: []*clienterrors.Error{},
},
}
for _, testCase := range testCases {
assert.EqualValues(t, testCase.targetErrors, testCase.jobResult.TargetErrors())
}
}
func TestOSBuildJobResultTargetResultsByName(t *testing.T) {
testCases := []struct {
jobResult OSBuildJobResult
targetName target.TargetName
targetResults []*target.TargetResult
}{
// one target results of a given name
{
jobResult: OSBuildJobResult{
TargetResults: []*target.TargetResult{
{
Name: target.TargetNameAWS,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorInvalidTargetConfig, "can't login to AWS"),
},
{
Name: target.TargetNameVMWare,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
},
{
Name: target.TargetNameVMWare,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
},
{
Name: target.TargetNameAWSS3,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "failed to upload image to AWS S3"),
},
},
},
targetName: target.TargetNameAWS,
targetResults: []*target.TargetResult{
{
Name: target.TargetNameAWS,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorInvalidTargetConfig, "can't login to AWS"),
},
},
},
// multiple target results of a given name
{
jobResult: OSBuildJobResult{
TargetResults: []*target.TargetResult{
{
Name: target.TargetNameAWS,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorInvalidTargetConfig, "can't login to AWS"),
},
{
Name: target.TargetNameVMWare,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
},
{
Name: target.TargetNameVMWare,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
},
{
Name: target.TargetNameAWSS3,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "failed to upload image to AWS S3"),
},
},
},
targetName: target.TargetNameVMWare,
targetResults: []*target.TargetResult{
{
Name: target.TargetNameVMWare,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
},
{
Name: target.TargetNameVMWare,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
},
},
},
// no target result of a given name
{
jobResult: OSBuildJobResult{
TargetResults: []*target.TargetResult{
{
Name: target.TargetNameAWS,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorInvalidTargetConfig, "can't login to AWS"),
},
{
Name: target.TargetNameVMWare,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
},
{
Name: target.TargetNameVMWare,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
},
{
Name: target.TargetNameAWSS3,
TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "failed to upload image to AWS S3"),
},
},
},
targetName: target.TargetNameKoji,
targetResults: []*target.TargetResult{},
},
}
for _, testCase := range testCases {
assert.EqualValues(t, testCase.targetResults, testCase.jobResult.TargetResultsByName(testCase.targetName))
}
}
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)
})
}
}