Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cloud API: Support selecting multiple upload targets and add Pulp OSTree uploads #3744

Merged
merged 16 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/cloudapi/v2/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const (
ErrorInvalidCustomization ServiceErrorCode = 35
ErrorLocalSaveNotEnabled ServiceErrorCode = 36
ErrorInvalidPartitioningMode ServiceErrorCode = 37
ErrorInvalidUploadTarget ServiceErrorCode = 38

// Internal errors, these are bugs
ErrorFailedToInitializeBlueprint ServiceErrorCode = 1000
Expand Down Expand Up @@ -127,6 +128,7 @@ func getServiceErrors() serviceErrors {
serviceError{ErrorInvalidCustomization, http.StatusBadRequest, "Invalid image customization"},
serviceError{ErrorLocalSaveNotEnabled, http.StatusBadRequest, "local_save is not enabled"},
serviceError{ErrorInvalidPartitioningMode, http.StatusBadRequest, "Requested partitioning mode is invalid"},
serviceError{ErrorInvalidUploadTarget, http.StatusBadRequest, "Invalid upload target for image type"},

serviceError{ErrorFailedToInitializeBlueprint, http.StatusInternalServerError, "Failed to initialize blueprint"},
serviceError{ErrorFailedToGenerateManifestSeed, http.StatusInternalServerError, "Failed to generate manifest seed"},
Expand Down
100 changes: 62 additions & 38 deletions internal/cloudapi/v2/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ type imageRequest struct {
arch distro.Arch
repositories []rpmmd.RepoConfig
imageOptions distro.ImageOptions
target *target.Target
targets []*target.Target
}

func (h *apiHandlers) PostCompose(ctx echo.Context) error {
Expand Down Expand Up @@ -236,22 +236,23 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
return err
}

var irTarget *target.Target
if ir.UploadOptions == nil {
var irTargets []*target.Target
if ir.UploadOptions == nil && (ir.UploadTargets == nil || len(*ir.UploadTargets) == 0) {
// nowhere to put the image, this is a user error
if request.Koji == nil {
return HTTPError(ErrorJSONUnMarshallingError)
}
} else if localSave {
// Override the image type upload selection and save it locally
// Final image is in /var/lib/osbuild-composer/artifacts/UUID/
irTarget = target.NewWorkerServerTarget()
irTarget.ImageName = imageType.Filename()
irTarget.OsbuildArtifact.ExportFilename = imageType.Filename()
irTarget.OsbuildArtifact.ExportName = imageType.Exports()[0]
srvTarget := target.NewWorkerServerTarget()
srvTarget.ImageName = imageType.Filename()
srvTarget.OsbuildArtifact.ExportFilename = imageType.Filename()
srvTarget.OsbuildArtifact.ExportName = imageType.Exports()[0]
irTargets = []*target.Target{srvTarget}
} else {
// Get the target for the selected image type
irTarget, err = ir.GetTarget(&request, imageType)
irTargets, err = ir.GetTargets(&request, imageType)
if err != nil {
return err
}
Expand All @@ -262,7 +263,7 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
arch: arch,
repositories: repos,
imageOptions: imageOptions,
target: irTarget,
targets: irTargets,
})
}

Expand Down Expand Up @@ -390,7 +391,12 @@ func targetResultToUploadStatus(t *target.TargetResult) (*UploadStatus, error) {
uploadOptions = OCIUploadStatus{
Url: ociOptions.URL,
}

case target.TargetNamePulpOSTree:
uploadType = UploadTypesPulpOstree
pulpOSTreeOptions := t.Options.(*target.PulpOSTreeTargetResultOptions)
uploadOptions = PulpOSTreeUploadStatus{
RepoUrl: pulpOSTreeOptions.RepoURL,
}
default:
return nil, fmt.Errorf("unknown upload target: %s", t.Name)
}
Expand Down Expand Up @@ -433,18 +439,26 @@ func (h *apiHandlers) getComposeStatusImpl(ctx echo.Context, id string) error {
return HTTPError(ErrorGettingBuildDependencyStatus)
}

var us *UploadStatus
var uploadStatuses *[]UploadStatus
var us0 *UploadStatus
if result.TargetResults != nil {
// Only single upload target is allowed, therefore only a single upload target result is allowed as well
if len(result.TargetResults) != 1 {
return HTTPError(ErrorSeveralUploadTargets)
statuses := make([]UploadStatus, len(result.TargetResults))
for idx := range result.TargetResults {
tr := result.TargetResults[idx]
us, err := targetResultToUploadStatus(tr)
if err != nil {
return HTTPError(ErrorUnknownUploadTarget)
}
us.Status = uploadStatusFromJobStatus(jobInfo.JobStatus, result.JobError)
statuses[idx] = *us
}
tr := result.TargetResults[0]
us, err = targetResultToUploadStatus(tr)
if err != nil {
return HTTPError(ErrorUnknownUploadTarget)

if len(statuses) > 0 {
// make sure uploadStatuses remains nil if the array is empty but not nill
uploadStatuses = &statuses
// get first upload status if there's at least one
us0 = &statuses[0]
}
us.Status = uploadStatusFromJobStatus(jobInfo.JobStatus, result.JobError)
}

return ctx.JSON(http.StatusOK, ComposeStatus{
Expand All @@ -455,9 +469,10 @@ func (h *apiHandlers) getComposeStatusImpl(ctx echo.Context, id string) error {
},
Status: composeStatusFromOSBuildJobStatus(jobInfo.JobStatus, &result),
ImageStatus: ImageStatus{
Status: imageStatusFromOSBuildJobStatus(jobInfo.JobStatus, &result),
Error: composeStatusErrorFromJobError(jobError),
UploadStatus: us,
Status: imageStatusFromOSBuildJobStatus(jobInfo.JobStatus, &result),
Error: composeStatusErrorFromJobError(jobError),
UploadStatus: us0, // add the first upload status to the old top-level field
UploadStatuses: uploadStatuses,
},
})
} else if jobType == worker.JobTypeKojiFinalize {
Expand Down Expand Up @@ -487,28 +502,37 @@ func (h *apiHandlers) getComposeStatusImpl(ctx echo.Context, id string) error {
return HTTPError(ErrorGettingBuildDependencyStatus)
}

var us *UploadStatus
// Only a single upload target in addition to Koji is allowed.
// Koji target is always added to osbuild jobs for Koji compose
// by the enqueueKojiCompose() function.
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)
var uploadStatuses *[]UploadStatus
var us0 *UploadStatus
if buildJobResult.TargetResults != nil {
// can't set the array size because koji targets wont be counted
statuses := make([]UploadStatus, 0, len(buildJobResult.TargetResults))
for idx := range buildJobResult.TargetResults {
tr := buildJobResult.TargetResults[idx]
if tr.Name != target.TargetNameKoji {
us, err := targetResultToUploadStatus(tr)
if err != nil {
return HTTPError(ErrorUnknownUploadTarget)
}
us.Status = uploadStatusFromJobStatus(buildInfo.JobStatus, result.JobError)
statuses = append(statuses, *us)
}
us.Status = uploadStatusFromJobStatus(buildInfo.JobStatus, result.JobError)
}

if len(statuses) > 0 {
// make sure uploadStatuses remains nil if the array is empty but not nill
uploadStatuses = &statuses
// get first upload status if there's at least one
us0 = &statuses[0]
}
}

buildJobResults = append(buildJobResults, buildJobResult)
buildJobStatuses = append(buildJobStatuses, ImageStatus{
Status: imageStatusFromKojiJobStatus(buildInfo.JobStatus, &initResult, &buildJobResult),
Error: composeStatusErrorFromJobError(buildJobError),
UploadStatus: us,
Status: imageStatusFromKojiJobStatus(buildInfo.JobStatus, &initResult, &buildJobResult),
Error: composeStatusErrorFromJobError(buildJobError),
UploadStatus: us0, // add the first upload status to the old top-level field
UploadStatuses: uploadStatuses,
})
}
response := ComposeStatus{
Expand Down
Loading
Loading