From 85f9f07a1fb762ea0df83bbbfb94e57550583846 Mon Sep 17 00:00:00 2001 From: Tomas Hozza Date: Fri, 1 Jul 2022 21:31:52 +0200 Subject: [PATCH] Cloud API: support cloud upload for Koji composes Add support to handle upload options in image requests for Koji composes. The image is always uploaded to Koji, but now it can be uploaded to the cloud environment in addition to Koji as part of the build. The image name used for Koji image can't be used as is for uploading to the cloud, because each cloud provider has its own requirements for the valid characters. For now, let the Cloud API implementation generate a random image name. The name is always returned in the compose status's upload status, so it should be possible to attach it to the Koji build to allow users to find the image. --- internal/cloudapi/v2/handler.go | 117 ++++++++++++++++++++------------ internal/cloudapi/v2/server.go | 8 ++- 2 files changed, 79 insertions(+), 46 deletions(-) diff --git a/internal/cloudapi/v2/handler.go b/internal/cloudapi/v2/handler.go index 9eb0c774d..111f99717 100644 --- a/internal/cloudapi/v2/handler.go +++ b/internal/cloudapi/v2/handler.go @@ -299,10 +299,6 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error { return HTTPError(ErrorJSONUnMarshallingError) } } else { - // TODO: support uploads also for koji - if request.Koji != nil { - return HTTPError(ErrorJSONUnMarshallingError) - } /* oneOf is not supported by the openapi generator so marshal and unmarshal the uploadrequest based on the type */ switch ir.ImageType { case ImageTypesAws: @@ -516,6 +512,53 @@ func imageTypeFromApiImageType(it ImageTypes, arch distro.Arch) string { return "" } +func targetResultToUploadStatus(t *target.TargetResult) (*UploadStatus, error) { + var us *UploadStatus + var uploadType UploadTypes + var uploadOptions interface{} + + switch t.Name { + case target.TargetNameAWS: + uploadType = UploadTypesAws + awsOptions := t.Options.(*target.AWSTargetResultOptions) + uploadOptions = AWSEC2UploadStatus{ + Ami: awsOptions.Ami, + Region: awsOptions.Region, + } + case target.TargetNameAWSS3: + uploadType = UploadTypesAwsS3 + awsOptions := t.Options.(*target.AWSS3TargetResultOptions) + uploadOptions = AWSS3UploadStatus{ + Url: awsOptions.URL, + } + case target.TargetNameGCP: + uploadType = UploadTypesGcp + gcpOptions := t.Options.(*target.GCPTargetResultOptions) + uploadOptions = GCPUploadStatus{ + ImageName: gcpOptions.ImageName, + ProjectId: gcpOptions.ProjectID, + } + case target.TargetNameAzureImage: + uploadType = UploadTypesAzure + gcpOptions := t.Options.(*target.AzureImageTargetResultOptions) + uploadOptions = AzureUploadStatus{ + ImageName: gcpOptions.ImageName, + } + default: + return nil, fmt.Errorf("unknown upload target: %s", t.Name) + } + + us = &UploadStatus{ + // TODO: determine upload status based on the target results, not job results + // Don't set the status here for now, but let it be set by the caller. + //Status: UploadStatusValue(result.UploadStatus), + Type: uploadType, + Options: uploadOptions, + } + + return us, nil +} + func (h *apiHandlers) GetComposeStatus(ctx echo.Context, id string) error { return h.server.EnsureJobChannel(h.getComposeStatusImpl)(ctx, id) } @@ -549,47 +592,13 @@ func (h *apiHandlers) getComposeStatusImpl(ctx echo.Context, id string) error { if len(result.TargetResults) != 1 { return HTTPError(ErrorSeveralUploadTargets) } - tr := *result.TargetResults[0] - - var uploadType UploadTypes - var uploadOptions interface{} - - switch tr.Name { - case target.TargetNameAWS: - uploadType = UploadTypesAws - awsOptions := tr.Options.(*target.AWSTargetResultOptions) - uploadOptions = AWSEC2UploadStatus{ - Ami: awsOptions.Ami, - Region: awsOptions.Region, - } - case target.TargetNameAWSS3: - uploadType = UploadTypesAwsS3 - awsOptions := tr.Options.(*target.AWSS3TargetResultOptions) - uploadOptions = AWSS3UploadStatus{ - Url: awsOptions.URL, - } - case target.TargetNameGCP: - uploadType = UploadTypesGcp - gcpOptions := tr.Options.(*target.GCPTargetResultOptions) - uploadOptions = GCPUploadStatus{ - ImageName: gcpOptions.ImageName, - ProjectId: gcpOptions.ProjectID, - } - case target.TargetNameAzureImage: - uploadType = UploadTypesAzure - gcpOptions := tr.Options.(*target.AzureImageTargetResultOptions) - uploadOptions = AzureUploadStatus{ - ImageName: gcpOptions.ImageName, - } - default: + tr := result.TargetResults[0] + us, err = targetResultToUploadStatus(tr) + if err != nil { return HTTPError(ErrorUnknownUploadTarget) } - - us = &UploadStatus{ - Status: UploadStatusValue(result.UploadStatus), - Type: uploadType, - Options: uploadOptions, - } + // TODO: determine upload status based on the target results, not job results + us.Status = UploadStatusValue(result.UploadStatus) } return ctx.JSON(http.StatusOK, ComposeStatus{ @@ -631,10 +640,28 @@ func (h *apiHandlers) getComposeStatusImpl(ctx echo.Context, id string) error { if err != nil { return HTTPError(ErrorGettingBuildDependencyStatus) } + + var us *UploadStatus + // only single upload target in addition to Koji is allowed + if len(buildJobResult.TargetResults) > 2 { + return HTTPError(ErrorSeveralUploadTargets) + } + for _, tr := range buildJobResult.TargetResults { + if tr.Name != target.TargetNameKoji { + us, err = targetResultToUploadStatus(tr) + if err != nil { + return HTTPError(ErrorUnknownUploadTarget) + } + // TODO: determine upload status based on the target results, not job results + us.Status = UploadStatusValue(buildJobResult.UploadStatus) + } + } + buildJobResults = append(buildJobResults, buildJobResult) buildJobStatuses = append(buildJobStatuses, ImageStatus{ - Status: imageStatusFromKojiJobStatus(buildJobStatus, &initResult, &buildJobResult), - Error: composeStatusErrorFromJobError(buildJobError), + Status: imageStatusFromKojiJobStatus(buildJobStatus, &initResult, &buildJobResult), + Error: composeStatusErrorFromJobError(buildJobError), + UploadStatus: us, }) } response := ComposeStatus{ diff --git a/internal/cloudapi/v2/server.go b/internal/cloudapi/v2/server.go index b2ddba870..4d7a6671e 100644 --- a/internal/cloudapi/v2/server.go +++ b/internal/cloudapi/v2/server.go @@ -185,12 +185,18 @@ func (s *Server) enqueueKojiCompose(taskID uint64, server, name, version, releas kojiTarget.OsbuildArtifact.ExportName = ir.imageType.Exports()[0] kojiTarget.ImageName = kojiFilename + targets := []*target.Target{kojiTarget} + // add any cloud upload target if defined + if ir.target != nil { + targets = append(targets, ir.target) + } + buildID, err := s.workers.EnqueueOSBuildAsDependency(ir.arch.Name(), &worker.OSBuildJob{ PipelineNames: &worker.PipelineNames{ Build: ir.imageType.BuildPipelines(), Payload: ir.imageType.PayloadPipelines(), }, - Targets: []*target.Target{kojiTarget}, + Targets: targets, ManifestDynArgsIdx: common.IntToPtr(1), }, []uuid.UUID{initID, manifestJobID}, channel) if err != nil {