cloudapi: Add endpoints to clone aws images across regions
Support for creating multiple amis from a single compose. It uses the AWSEC2* jobs to push images to new regions, and share them with new accounts. The compose it depends upon has to have succeeded.
This commit is contained in:
parent
599829a3b8
commit
d13347e1ca
8 changed files with 794 additions and 117 deletions
|
|
@ -44,6 +44,10 @@ const (
|
||||||
ErrorTenantNotFound ServiceErrorCode = 28
|
ErrorTenantNotFound ServiceErrorCode = 28
|
||||||
ErrorNoGPGKey ServiceErrorCode = 29
|
ErrorNoGPGKey ServiceErrorCode = 29
|
||||||
ErrorValidationFailed ServiceErrorCode = 30
|
ErrorValidationFailed ServiceErrorCode = 30
|
||||||
|
ErrorComposeBadState ServiceErrorCode = 31
|
||||||
|
ErrorUnsupportedImage ServiceErrorCode = 32
|
||||||
|
ErrorInvalidImageFromComposeId ServiceErrorCode = 33
|
||||||
|
ErrorImageNotFound ServiceErrorCode = 34
|
||||||
|
|
||||||
// Internal errors, these are bugs
|
// Internal errors, these are bugs
|
||||||
ErrorFailedToInitializeBlueprint ServiceErrorCode = 1000
|
ErrorFailedToInitializeBlueprint ServiceErrorCode = 1000
|
||||||
|
|
@ -63,6 +67,9 @@ const (
|
||||||
ErrorDepsolveJobCanceled ServiceErrorCode = 1014
|
ErrorDepsolveJobCanceled ServiceErrorCode = 1014
|
||||||
ErrorUnexpectedNumberOfImageBuilds ServiceErrorCode = 1015
|
ErrorUnexpectedNumberOfImageBuilds ServiceErrorCode = 1015
|
||||||
ErrorGettingBuildDependencyStatus ServiceErrorCode = 1016
|
ErrorGettingBuildDependencyStatus ServiceErrorCode = 1016
|
||||||
|
ErrorGettingOSBuildJobStatus ServiceErrorCode = 1017
|
||||||
|
ErrorGettingAWSEC2JobStatus ServiceErrorCode = 1018
|
||||||
|
ErrorGettingJobType ServiceErrorCode = 1019
|
||||||
|
|
||||||
// Errors contained within this file
|
// Errors contained within this file
|
||||||
ErrorUnspecified ServiceErrorCode = 10000
|
ErrorUnspecified ServiceErrorCode = 10000
|
||||||
|
|
@ -106,12 +113,16 @@ func getServiceErrors() serviceErrors {
|
||||||
serviceError{ErrorMethodNotAllowed, http.StatusMethodNotAllowed, "Requested method isn't supported for resource"},
|
serviceError{ErrorMethodNotAllowed, http.StatusMethodNotAllowed, "Requested method isn't supported for resource"},
|
||||||
serviceError{ErrorNotAcceptable, http.StatusNotAcceptable, "Only 'application/json' content is supported"},
|
serviceError{ErrorNotAcceptable, http.StatusNotAcceptable, "Only 'application/json' content is supported"},
|
||||||
serviceError{ErrorNoBaseURLInPayloadRepository, http.StatusBadRequest, "BaseURL must be specified for payload repositories"},
|
serviceError{ErrorNoBaseURLInPayloadRepository, http.StatusBadRequest, "BaseURL must be specified for payload repositories"},
|
||||||
serviceError{ErrorInvalidJobType, http.StatusNotFound, "Requested job has invalid type"},
|
serviceError{ErrorInvalidJobType, http.StatusNotFound, "Job with given id has an invalid type"},
|
||||||
serviceError{ErrorInvalidNumberOfImageBuilds, http.StatusBadRequest, "Compose request has unsupported number of image builds"},
|
serviceError{ErrorInvalidNumberOfImageBuilds, http.StatusBadRequest, "Compose request has unsupported number of image builds"},
|
||||||
serviceError{ErrorInvalidOSTreeParams, http.StatusBadRequest, "Invalid OSTree parameters or parameter combination"},
|
serviceError{ErrorInvalidOSTreeParams, http.StatusBadRequest, "Invalid OSTree parameters or parameter combination"},
|
||||||
serviceError{ErrorTenantNotFound, http.StatusBadRequest, "Tenant not found in JWT claims"},
|
serviceError{ErrorTenantNotFound, http.StatusBadRequest, "Tenant not found in JWT claims"},
|
||||||
serviceError{ErrorNoGPGKey, http.StatusBadRequest, "Invalid repository, when check_gpg is set, gpgkey must be specified"},
|
serviceError{ErrorNoGPGKey, http.StatusBadRequest, "Invalid repository, when check_gpg is set, gpgkey must be specified"},
|
||||||
serviceError{ErrorValidationFailed, http.StatusBadRequest, "Request could not be validated"},
|
serviceError{ErrorValidationFailed, http.StatusBadRequest, "Request could not be validated"},
|
||||||
|
serviceError{ErrorComposeBadState, http.StatusBadRequest, "Compose is running or has failed"},
|
||||||
|
serviceError{ErrorUnsupportedImage, http.StatusBadRequest, "This compose doesn't support the creation of multiple images"},
|
||||||
|
serviceError{ErrorInvalidImageFromComposeId, http.StatusBadRequest, "Invalid format for image id"},
|
||||||
|
serviceError{ErrorImageNotFound, http.StatusBadRequest, "Image with given id not found"},
|
||||||
|
|
||||||
serviceError{ErrorFailedToInitializeBlueprint, http.StatusInternalServerError, "Failed to initialize blueprint"},
|
serviceError{ErrorFailedToInitializeBlueprint, http.StatusInternalServerError, "Failed to initialize blueprint"},
|
||||||
serviceError{ErrorFailedToGenerateManifestSeed, http.StatusInternalServerError, "Failed to generate manifest seed"},
|
serviceError{ErrorFailedToGenerateManifestSeed, http.StatusInternalServerError, "Failed to generate manifest seed"},
|
||||||
|
|
@ -130,6 +141,9 @@ func getServiceErrors() serviceErrors {
|
||||||
serviceError{ErrorDepsolveJobCanceled, http.StatusInternalServerError, "Depsolve job was cancelled"},
|
serviceError{ErrorDepsolveJobCanceled, http.StatusInternalServerError, "Depsolve job was cancelled"},
|
||||||
serviceError{ErrorUnexpectedNumberOfImageBuilds, http.StatusInternalServerError, "Compose has unexpected number of image builds"},
|
serviceError{ErrorUnexpectedNumberOfImageBuilds, http.StatusInternalServerError, "Compose has unexpected number of image builds"},
|
||||||
serviceError{ErrorGettingBuildDependencyStatus, http.StatusInternalServerError, "Error checking status of build job dependencies"},
|
serviceError{ErrorGettingBuildDependencyStatus, http.StatusInternalServerError, "Error checking status of build job dependencies"},
|
||||||
|
serviceError{ErrorGettingOSBuildJobStatus, http.StatusInternalServerError, "Unable to get osbuild job status"},
|
||||||
|
serviceError{ErrorGettingAWSEC2JobStatus, http.StatusInternalServerError, "Unable to get ec2 job status"},
|
||||||
|
serviceError{ErrorGettingJobType, http.StatusInternalServerError, "Unable to get job type of existing job"},
|
||||||
|
|
||||||
serviceError{ErrorUnspecified, http.StatusInternalServerError, "Unspecified internal error "},
|
serviceError{ErrorUnspecified, http.StatusInternalServerError, "Unspecified internal error "},
|
||||||
serviceError{ErrorNotHTTPError, http.StatusInternalServerError, "Error is not an instance of HTTPError"},
|
serviceError{ErrorNotHTTPError, http.StatusInternalServerError, "Error is not an instance of HTTPError"},
|
||||||
|
|
|
||||||
|
|
@ -648,8 +648,7 @@ func (h *apiHandlers) getComposeStatusImpl(ctx echo.Context, id string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return HTTPError(ErrorUnknownUploadTarget)
|
return HTTPError(ErrorUnknownUploadTarget)
|
||||||
}
|
}
|
||||||
// TODO: determine upload status based on the target results, not job results
|
us.Status = uploadStatusFromJobStatus(jobInfo.JobStatus, result.JobError)
|
||||||
us.Status = UploadStatusValue(result.UploadStatus)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.JSON(http.StatusOK, ComposeStatus{
|
return ctx.JSON(http.StatusOK, ComposeStatus{
|
||||||
|
|
@ -705,8 +704,7 @@ func (h *apiHandlers) getComposeStatusImpl(ctx echo.Context, id string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return HTTPError(ErrorUnknownUploadTarget)
|
return HTTPError(ErrorUnknownUploadTarget)
|
||||||
}
|
}
|
||||||
// TODO: determine upload status based on the target results, not job results
|
us.Status = uploadStatusFromJobStatus(buildInfo.JobStatus, result.JobError)
|
||||||
us.Status = UploadStatusValue(buildJobResult.UploadStatus)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1234,3 +1232,227 @@ func genRepoConfig(repo Repository) (*rpmmd.RepoConfig, error) {
|
||||||
|
|
||||||
return repoConfig, nil
|
return repoConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *apiHandlers) PostCloneCompose(ctx echo.Context, id string) error {
|
||||||
|
return h.server.EnsureJobChannel(h.postCloneComposeImpl)(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *apiHandlers) postCloneComposeImpl(ctx echo.Context, id string) error {
|
||||||
|
channel, err := h.server.getTenantChannel(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return HTTPErrorWithInternal(ErrorTenantNotFound, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jobId, err := uuid.Parse(id)
|
||||||
|
if err != nil {
|
||||||
|
return HTTPError(ErrorInvalidComposeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
jobType, err := h.server.workers.JobType(jobId)
|
||||||
|
if err != nil {
|
||||||
|
return HTTPError(ErrorComposeNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jobType != worker.JobTypeOSBuild {
|
||||||
|
return HTTPError(ErrorInvalidJobType)
|
||||||
|
}
|
||||||
|
|
||||||
|
var osbuildResult worker.OSBuildJobResult
|
||||||
|
osbuildInfo, err := h.server.workers.OSBuildJobInfo(jobId, &osbuildResult)
|
||||||
|
if err != nil {
|
||||||
|
return HTTPErrorWithInternal(ErrorGettingOSBuildJobStatus, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if osbuildInfo.JobStatus.Finished.IsZero() || !osbuildResult.Success {
|
||||||
|
return HTTPError(ErrorComposeBadState)
|
||||||
|
}
|
||||||
|
|
||||||
|
if osbuildResult.TargetResults == nil {
|
||||||
|
return HTTPError(ErrorMalformedOSBuildJobResult)
|
||||||
|
}
|
||||||
|
// Only single upload target is allowed, therefore only a single upload target result is allowed as well
|
||||||
|
if len(osbuildResult.TargetResults) != 1 {
|
||||||
|
return HTTPError(ErrorSeveralUploadTargets)
|
||||||
|
}
|
||||||
|
var us *UploadStatus
|
||||||
|
us, err = targetResultToUploadStatus(osbuildResult.TargetResults[0])
|
||||||
|
if err != nil {
|
||||||
|
return HTTPError(ErrorUnknownUploadTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
var osbuildJob worker.OSBuildJob
|
||||||
|
err = h.server.workers.OSBuildJob(jobId, &osbuildJob)
|
||||||
|
if err != nil {
|
||||||
|
return HTTPErrorWithInternal(ErrorComposeNotFound, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(osbuildJob.Targets) != 1 {
|
||||||
|
return HTTPError(ErrorSeveralUploadTargets)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the id of the last job in the dependency chain which users should wait on
|
||||||
|
finalJob := jobId
|
||||||
|
// look at the upload status of the osbuild dependency to decide what to do
|
||||||
|
if us.Type == UploadTypesAws {
|
||||||
|
options := us.Options.(AWSEC2UploadStatus)
|
||||||
|
var img AWSEC2CloneCompose
|
||||||
|
err := ctx.Bind(&img)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
shareAmi := options.Ami
|
||||||
|
shareRegion := img.Region
|
||||||
|
if img.Region != options.Region {
|
||||||
|
// Let the share job use dynArgs
|
||||||
|
shareAmi = ""
|
||||||
|
shareRegion = ""
|
||||||
|
|
||||||
|
// Check dependents if we need to do a copyjob
|
||||||
|
foundDep := false
|
||||||
|
for _, d := range osbuildInfo.Dependents {
|
||||||
|
jt, err := h.server.workers.JobType(d)
|
||||||
|
if err != nil {
|
||||||
|
return HTTPErrorWithInternal(ErrorGettingJobType, err)
|
||||||
|
}
|
||||||
|
if jt == worker.JobTypeAWSEC2Copy {
|
||||||
|
var cjResult worker.AWSEC2CopyJobResult
|
||||||
|
_, err := h.server.workers.AWSEC2CopyJobInfo(d, &cjResult)
|
||||||
|
if err != nil {
|
||||||
|
return HTTPErrorWithInternal(ErrorGettingAWSEC2JobStatus, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cjResult.JobError == nil && options.Region == cjResult.Region {
|
||||||
|
finalJob = d
|
||||||
|
foundDep = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundDep {
|
||||||
|
copyJob := &worker.AWSEC2CopyJob{
|
||||||
|
Ami: options.Ami,
|
||||||
|
SourceRegion: options.Region,
|
||||||
|
TargetRegion: img.Region,
|
||||||
|
TargetName: fmt.Sprintf("composer-api-%s", uuid.New().String()),
|
||||||
|
}
|
||||||
|
finalJob, err = h.server.workers.EnqueueAWSEC2CopyJob(copyJob, finalJob, channel)
|
||||||
|
if err != nil {
|
||||||
|
return HTTPErrorWithInternal(ErrorEnqueueingJob, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var shares []string
|
||||||
|
awsT, ok := (osbuildJob.Targets[0].Options).(*target.AWSTargetOptions)
|
||||||
|
if !ok {
|
||||||
|
return HTTPError(ErrorUnknownUploadTarget)
|
||||||
|
}
|
||||||
|
if len(awsT.ShareWithAccounts) > 0 {
|
||||||
|
shares = append(shares, awsT.ShareWithAccounts...)
|
||||||
|
}
|
||||||
|
if img.ShareWithAccounts != nil && len(*img.ShareWithAccounts) > 0 {
|
||||||
|
shares = append(shares, (*img.ShareWithAccounts)...)
|
||||||
|
}
|
||||||
|
if len(shares) > 0 {
|
||||||
|
shareJob := &worker.AWSEC2ShareJob{
|
||||||
|
Ami: shareAmi,
|
||||||
|
Region: shareRegion,
|
||||||
|
ShareWithAccounts: shares,
|
||||||
|
}
|
||||||
|
finalJob, err = h.server.workers.EnqueueAWSEC2ShareJob(shareJob, finalJob, channel)
|
||||||
|
if err != nil {
|
||||||
|
return HTTPErrorWithInternal(ErrorEnqueueingJob, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return HTTPError(ErrorUnsupportedImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.JSON(http.StatusCreated, CloneComposeResponse{
|
||||||
|
ObjectReference: ObjectReference{
|
||||||
|
Href: fmt.Sprintf("/api/image-builder-composer/v2/composes/%v/clone", jobId),
|
||||||
|
Id: finalJob.String(),
|
||||||
|
Kind: "CloneComposeId",
|
||||||
|
},
|
||||||
|
Id: finalJob.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *apiHandlers) GetCloneStatus(ctx echo.Context, id string) error {
|
||||||
|
return h.server.EnsureJobChannel(h.getCloneStatus)(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *apiHandlers) getCloneStatus(ctx echo.Context, id string) error {
|
||||||
|
jobId, err := uuid.Parse(id)
|
||||||
|
if err != nil {
|
||||||
|
return HTTPError(ErrorInvalidComposeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
jobType, err := h.server.workers.JobType(jobId)
|
||||||
|
if err != nil {
|
||||||
|
return HTTPError(ErrorComposeNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
var us UploadStatus
|
||||||
|
switch jobType {
|
||||||
|
case worker.JobTypeAWSEC2Copy:
|
||||||
|
var result worker.AWSEC2CopyJobResult
|
||||||
|
info, err := h.server.workers.AWSEC2CopyJobInfo(jobId, &result)
|
||||||
|
if err != nil {
|
||||||
|
return HTTPError(ErrorGettingAWSEC2JobStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
us = UploadStatus{
|
||||||
|
Status: uploadStatusFromJobStatus(info.JobStatus, result.JobError),
|
||||||
|
Type: UploadTypesAws,
|
||||||
|
Options: AWSEC2UploadStatus{
|
||||||
|
Ami: result.Ami,
|
||||||
|
Region: result.Region,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case worker.JobTypeAWSEC2Share:
|
||||||
|
var result worker.AWSEC2ShareJobResult
|
||||||
|
info, err := h.server.workers.AWSEC2ShareJobInfo(jobId, &result)
|
||||||
|
if err != nil {
|
||||||
|
return HTTPError(ErrorGettingAWSEC2JobStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
us = UploadStatus{
|
||||||
|
Status: uploadStatusFromJobStatus(info.JobStatus, result.JobError),
|
||||||
|
Type: UploadTypesAws,
|
||||||
|
Options: AWSEC2UploadStatus{
|
||||||
|
Ami: result.Ami,
|
||||||
|
Region: result.Region,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return HTTPError(ErrorInvalidJobType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.JSON(http.StatusOK, CloneStatus{
|
||||||
|
ObjectReference: ObjectReference{
|
||||||
|
Href: fmt.Sprintf("/api/image-builder-composer/v2/clones/%v", jobId),
|
||||||
|
Id: jobId.String(),
|
||||||
|
Kind: "CloneComposeStatus",
|
||||||
|
},
|
||||||
|
UploadStatus: us,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: determine upload status based on the target results, not job results
|
||||||
|
func uploadStatusFromJobStatus(js *worker.JobStatus, je *clienterrors.Error) UploadStatusValue {
|
||||||
|
if je != nil || js.Canceled {
|
||||||
|
return UploadStatusValueFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
if js.Started.IsZero() {
|
||||||
|
return UploadStatusValuePending
|
||||||
|
}
|
||||||
|
|
||||||
|
if js.Finished.IsZero() {
|
||||||
|
return UploadStatusValueRunning
|
||||||
|
}
|
||||||
|
return UploadStatusValueSuccess
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,12 @@ const (
|
||||||
UploadTypesGcp UploadTypes = "gcp"
|
UploadTypesGcp UploadTypes = "gcp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AWSEC2CloneCompose defines model for AWSEC2CloneCompose.
|
||||||
|
type AWSEC2CloneCompose struct {
|
||||||
|
Region string `json:"region"`
|
||||||
|
ShareWithAccounts *[]string `json:"share_with_accounts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// AWSEC2UploadOptions defines model for AWSEC2UploadOptions.
|
// AWSEC2UploadOptions defines model for AWSEC2UploadOptions.
|
||||||
type AWSEC2UploadOptions struct {
|
type AWSEC2UploadOptions struct {
|
||||||
Region string `json:"region"`
|
Region string `json:"region"`
|
||||||
|
|
@ -153,6 +159,25 @@ type AzureUploadStatus struct {
|
||||||
ImageName string `json:"image_name"`
|
ImageName string `json:"image_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CloneComposeBody defines model for CloneComposeBody.
|
||||||
|
type CloneComposeBody interface{}
|
||||||
|
|
||||||
|
// CloneComposeResponse defines model for CloneComposeResponse.
|
||||||
|
type CloneComposeResponse struct {
|
||||||
|
// Embedded struct due to allOf(#/components/schemas/ObjectReference)
|
||||||
|
ObjectReference `yaml:",inline"`
|
||||||
|
// Embedded fields due to inline allOf schema
|
||||||
|
Id string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneStatus defines model for CloneStatus.
|
||||||
|
type CloneStatus struct {
|
||||||
|
// Embedded struct due to allOf(#/components/schemas/ObjectReference)
|
||||||
|
ObjectReference `yaml:",inline"`
|
||||||
|
// Embedded struct due to allOf(#/components/schemas/UploadStatus)
|
||||||
|
UploadStatus `yaml:",inline"`
|
||||||
|
}
|
||||||
|
|
||||||
// ComposeId defines model for ComposeId.
|
// ComposeId defines model for ComposeId.
|
||||||
type ComposeId struct {
|
type ComposeId struct {
|
||||||
// Embedded struct due to allOf(#/components/schemas/ObjectReference)
|
// Embedded struct due to allOf(#/components/schemas/ObjectReference)
|
||||||
|
|
@ -481,6 +506,9 @@ type Size string
|
||||||
// PostComposeJSONBody defines parameters for PostCompose.
|
// PostComposeJSONBody defines parameters for PostCompose.
|
||||||
type PostComposeJSONBody ComposeRequest
|
type PostComposeJSONBody ComposeRequest
|
||||||
|
|
||||||
|
// PostCloneComposeJSONBody defines parameters for PostCloneCompose.
|
||||||
|
type PostCloneComposeJSONBody CloneComposeBody
|
||||||
|
|
||||||
// GetErrorListParams defines parameters for GetErrorList.
|
// GetErrorListParams defines parameters for GetErrorList.
|
||||||
type GetErrorListParams struct {
|
type GetErrorListParams struct {
|
||||||
// Page index
|
// Page index
|
||||||
|
|
@ -493,14 +521,23 @@ type GetErrorListParams struct {
|
||||||
// PostComposeJSONRequestBody defines body for PostCompose for application/json ContentType.
|
// PostComposeJSONRequestBody defines body for PostCompose for application/json ContentType.
|
||||||
type PostComposeJSONRequestBody PostComposeJSONBody
|
type PostComposeJSONRequestBody PostComposeJSONBody
|
||||||
|
|
||||||
|
// PostCloneComposeJSONRequestBody defines body for PostCloneCompose for application/json ContentType.
|
||||||
|
type PostCloneComposeJSONRequestBody PostCloneComposeJSONBody
|
||||||
|
|
||||||
// ServerInterface represents all server handlers.
|
// ServerInterface represents all server handlers.
|
||||||
type ServerInterface interface {
|
type ServerInterface interface {
|
||||||
|
// The status of a cloned compose
|
||||||
|
// (GET /clones/{id})
|
||||||
|
GetCloneStatus(ctx echo.Context, id string) error
|
||||||
// Create compose
|
// Create compose
|
||||||
// (POST /compose)
|
// (POST /compose)
|
||||||
PostCompose(ctx echo.Context) error
|
PostCompose(ctx echo.Context) error
|
||||||
// The status of a compose
|
// The status of a compose
|
||||||
// (GET /composes/{id})
|
// (GET /composes/{id})
|
||||||
GetComposeStatus(ctx echo.Context, id string) error
|
GetComposeStatus(ctx echo.Context, id string) error
|
||||||
|
// Clone an existing compose
|
||||||
|
// (POST /composes/{id}/clone)
|
||||||
|
PostCloneCompose(ctx echo.Context, id string) error
|
||||||
// Get logs for a compose.
|
// Get logs for a compose.
|
||||||
// (GET /composes/{id}/logs)
|
// (GET /composes/{id}/logs)
|
||||||
GetComposeLogs(ctx echo.Context, id string) error
|
GetComposeLogs(ctx echo.Context, id string) error
|
||||||
|
|
@ -526,6 +563,24 @@ type ServerInterfaceWrapper struct {
|
||||||
Handler ServerInterface
|
Handler ServerInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCloneStatus converts echo context to params.
|
||||||
|
func (w *ServerInterfaceWrapper) GetCloneStatus(ctx echo.Context) error {
|
||||||
|
var err error
|
||||||
|
// ------------- Path parameter "id" -------------
|
||||||
|
var id string
|
||||||
|
|
||||||
|
err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, ctx.Param("id"), &id)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Set(BearerScopes, []string{""})
|
||||||
|
|
||||||
|
// Invoke the callback with all the unmarshalled arguments
|
||||||
|
err = w.Handler.GetCloneStatus(ctx, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// PostCompose converts echo context to params.
|
// PostCompose converts echo context to params.
|
||||||
func (w *ServerInterfaceWrapper) PostCompose(ctx echo.Context) error {
|
func (w *ServerInterfaceWrapper) PostCompose(ctx echo.Context) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
@ -555,6 +610,22 @@ func (w *ServerInterfaceWrapper) GetComposeStatus(ctx echo.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostCloneCompose converts echo context to params.
|
||||||
|
func (w *ServerInterfaceWrapper) PostCloneCompose(ctx echo.Context) error {
|
||||||
|
var err error
|
||||||
|
// ------------- Path parameter "id" -------------
|
||||||
|
var id string
|
||||||
|
|
||||||
|
err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, ctx.Param("id"), &id)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke the callback with all the unmarshalled arguments
|
||||||
|
err = w.Handler.PostCloneCompose(ctx, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// GetComposeLogs converts echo context to params.
|
// GetComposeLogs converts echo context to params.
|
||||||
func (w *ServerInterfaceWrapper) GetComposeLogs(ctx echo.Context) error {
|
func (w *ServerInterfaceWrapper) GetComposeLogs(ctx echo.Context) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
@ -689,8 +760,10 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
||||||
Handler: si,
|
Handler: si,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
router.GET(baseURL+"/clones/:id", wrapper.GetCloneStatus)
|
||||||
router.POST(baseURL+"/compose", wrapper.PostCompose)
|
router.POST(baseURL+"/compose", wrapper.PostCompose)
|
||||||
router.GET(baseURL+"/composes/:id", wrapper.GetComposeStatus)
|
router.GET(baseURL+"/composes/:id", wrapper.GetComposeStatus)
|
||||||
|
router.POST(baseURL+"/composes/:id/clone", wrapper.PostCloneCompose)
|
||||||
router.GET(baseURL+"/composes/:id/logs", wrapper.GetComposeLogs)
|
router.GET(baseURL+"/composes/:id/logs", wrapper.GetComposeLogs)
|
||||||
router.GET(baseURL+"/composes/:id/manifests", wrapper.GetComposeManifests)
|
router.GET(baseURL+"/composes/:id/manifests", wrapper.GetComposeManifests)
|
||||||
router.GET(baseURL+"/composes/:id/metadata", wrapper.GetComposeMetadata)
|
router.GET(baseURL+"/composes/:id/metadata", wrapper.GetComposeMetadata)
|
||||||
|
|
@ -703,114 +776,116 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
||||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||||
var swaggerSpec = []string{
|
var swaggerSpec = []string{
|
||||||
|
|
||||||
"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",
|
||||||
"R6B2l8FfAAA=",
|
"bf2ZdI+yzQdFxiugts54Qz0+f4T84X9q1y/8zXv43qVUL0D42U8r/mnFn7FiFNUgabnrJp/9O+SNPyRe",
|
||||||
|
"77eJ9cEpe5YnaykD/8z83xhbfMjO93UfdJwn6vlfN6G6q3mf5Flfvd5u44IOTks83MT+/0UBdLD3ddOU",
|
||||||
|
"yh4glgo+rZSZ51XEsdNcJqCBifERAi6ggf4kGiVEEnx9ZY3mEJxv3/9/AAAA//94/6Zd/WkAAA==",
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSwagger returns the content of the embedded swagger specification file
|
// GetSwagger returns the content of the embedded swagger specification file
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,101 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
|
/composes/{id}/clone:
|
||||||
|
post:
|
||||||
|
operationId: postCloneCompose
|
||||||
|
summary: Clone an existing compose
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
example: 123e4567-e89b-12d3-a456-426655440000
|
||||||
|
required: true
|
||||||
|
description: ID of the compose
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CloneComposeBody'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: The new image is being created
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CloneComposeResponse'
|
||||||
|
'400':
|
||||||
|
description: Invalid compose id
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
'404':
|
||||||
|
description: Unknown compose id
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
/clones/{id}:
|
||||||
|
get:
|
||||||
|
operationId: getCloneStatus
|
||||||
|
summary: The status of a cloned compose
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
example: '123e4567-e89b-12d3-a456-426655440000'
|
||||||
|
required: true
|
||||||
|
description: ID of image status to get
|
||||||
|
description: |-
|
||||||
|
Get the status of a running or completed image from a compose.
|
||||||
|
This includes whether or not the image creation succeeded.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: image status
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CloneStatus'
|
||||||
|
'400':
|
||||||
|
description: Invalid compose id
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
'401':
|
||||||
|
description: Auth token is invalid
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
'403':
|
||||||
|
description: Unauthorized to perform operation
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
'404':
|
||||||
|
description: Unknown compose id
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
'500':
|
||||||
|
description: Unexpected error occurred
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
|
||||||
/compose:
|
/compose:
|
||||||
post:
|
post:
|
||||||
operationId: postCompose
|
operationId: postCompose
|
||||||
|
|
@ -1030,6 +1125,40 @@ components:
|
||||||
format: uuid
|
format: uuid
|
||||||
example: '123e4567-e89b-12d3-a456-426655440000'
|
example: '123e4567-e89b-12d3-a456-426655440000'
|
||||||
|
|
||||||
|
CloneComposeBody:
|
||||||
|
oneOf:
|
||||||
|
- $ref: '#/components/schemas/AWSEC2CloneCompose'
|
||||||
|
|
||||||
|
AWSEC2CloneCompose:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- region
|
||||||
|
properties:
|
||||||
|
region:
|
||||||
|
type: string
|
||||||
|
share_with_accounts:
|
||||||
|
type: array
|
||||||
|
example: ['123456789012']
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
CloneComposeResponse:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/ObjectReference'
|
||||||
|
- type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
example: '123e4567-e89b-12d3-a456-426655440000'
|
||||||
|
|
||||||
|
CloneStatus:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/ObjectReference'
|
||||||
|
- $ref: '#/components/schemas/UploadStatus'
|
||||||
|
|
||||||
parameters:
|
parameters:
|
||||||
page:
|
page:
|
||||||
name: page
|
name: page
|
||||||
|
|
|
||||||
|
|
@ -539,7 +539,7 @@ func TestKojiJobTypeValidation(t *testing.T) {
|
||||||
test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", finalizeID, path), ``, http.StatusOK, "*")
|
test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", finalizeID, path), ``, http.StatusOK, "*")
|
||||||
|
|
||||||
// The other IDs should fail
|
// The other IDs should fail
|
||||||
test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", initID, path), ``, http.StatusNotFound, `{"code":"IMAGE-BUILDER-COMPOSER-26", "details": "", "href":"/api/image-builder-composer/v2/errors/26","id":"26","kind":"Error","reason":"Requested job has invalid type"}`, `operation_id`)
|
test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", initID, path), ``, http.StatusNotFound, `{"code":"IMAGE-BUILDER-COMPOSER-26", "details": "", "href":"/api/image-builder-composer/v2/errors/26","id":"26","kind":"Error","reason":"Job with given id has an invalid type"}`, `operation_id`)
|
||||||
|
|
||||||
for _, buildID := range buildJobIDs {
|
for _, buildID := range buildJobIDs {
|
||||||
test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", buildID, path), ``, http.StatusOK, "*")
|
test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", buildID, path), ``, http.StatusOK, "*")
|
||||||
|
|
|
||||||
|
|
@ -951,7 +951,7 @@ func TestComposeTargetErrors(t *testing.T) {
|
||||||
"ami": "",
|
"ami": "",
|
||||||
"region": ""
|
"region": ""
|
||||||
},
|
},
|
||||||
"status": "",
|
"status": "failure",
|
||||||
"type": "aws"
|
"type": "aws"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1198,3 +1198,132 @@ func TestImageTypes(t *testing.T) {
|
||||||
"kind": "ComposeId"
|
"kind": "ComposeId"
|
||||||
}`, "id")
|
}`, "id")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImageFromCompose(t *testing.T) {
|
||||||
|
srv, wrksrv, _, cancel := newV2Server(t, t.TempDir(), []string{""}, 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.TestDistroName, 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{""})
|
||||||
|
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",
|
||||||
|
})
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`, 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{""})
|
||||||
|
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{""})
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -284,7 +284,8 @@ curl \
|
||||||
# Prepare a request to be sent to the composer API.
|
# Prepare a request to be sent to the composer API.
|
||||||
#
|
#
|
||||||
|
|
||||||
REQUEST_FILE="${WORKDIR}/request.json"
|
REQUEST_FILE="${WORKDIR}/compose_request.json"
|
||||||
|
IMG_COMPOSE_REQ_FILE="${WORKDIR}/img_compose_request.json"
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
SSH_USER=
|
SSH_USER=
|
||||||
TEST_ID="$(uuidgen)"
|
TEST_ID="$(uuidgen)"
|
||||||
|
|
@ -418,6 +419,64 @@ function waitForState() {
|
||||||
export UPLOAD_OPTIONS
|
export UPLOAD_OPTIONS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendImgFromCompose() {
|
||||||
|
OUTPUT=$(mktemp)
|
||||||
|
HTTPSTATUS=$(curl \
|
||||||
|
--silent \
|
||||||
|
--show-error \
|
||||||
|
--cacert /etc/osbuild-composer/ca-crt.pem \
|
||||||
|
--key /etc/osbuild-composer/client-key.pem \
|
||||||
|
--cert /etc/osbuild-composer/client-crt.pem \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--request POST \
|
||||||
|
--data @"$1" \
|
||||||
|
--write-out '%{http_code}' \
|
||||||
|
--output "$OUTPUT" \
|
||||||
|
https://localhost/api/image-builder-composer/v2/composes/"$COMPOSE_ID"/clone)
|
||||||
|
|
||||||
|
test "$HTTPSTATUS" = "201"
|
||||||
|
IMG_ID=$(jq -r '.id' "$OUTPUT")
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForImgState() {
|
||||||
|
while true
|
||||||
|
do
|
||||||
|
OUTPUT=$(curl \
|
||||||
|
--silent \
|
||||||
|
--show-error \
|
||||||
|
--cacert /etc/osbuild-composer/ca-crt.pem \
|
||||||
|
--key /etc/osbuild-composer/client-key.pem \
|
||||||
|
--cert /etc/osbuild-composer/client-crt.pem \
|
||||||
|
"https://localhost/api/image-builder-composer/v2/clones/$IMG_ID")
|
||||||
|
|
||||||
|
IMG_UPLOAD_STATUS=$(echo "$OUTPUT" | jq -r '.status')
|
||||||
|
IMG_UPLOAD_OPTIONS=$(echo "$OUTPUT" | jq -r '.options')
|
||||||
|
|
||||||
|
case "$IMG_UPLOAD_STATUS" in
|
||||||
|
"success")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
# all valid status values for a compose which hasn't finished yet
|
||||||
|
"pending"|"running")
|
||||||
|
;;
|
||||||
|
# default undesired state
|
||||||
|
"failure")
|
||||||
|
echo "Image compose failed"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "API returned unexpected image status value: '$IMG_UPLOAD_STATUS'"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
sleep 30
|
||||||
|
done
|
||||||
|
|
||||||
|
# export for use in subcases
|
||||||
|
export IMG_UPLOAD_OPTIONS
|
||||||
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
# Make sure that requesting a non existing paquet results in failure
|
# Make sure that requesting a non existing paquet results in failure
|
||||||
#
|
#
|
||||||
|
|
@ -427,7 +486,6 @@ jq '.customizations.packages = [ "jesuisunpaquetquinexistepas" ]' "$REQUEST_FILE
|
||||||
sendCompose "$REQUEST_FILE2"
|
sendCompose "$REQUEST_FILE2"
|
||||||
waitForState "failure"
|
waitForState "failure"
|
||||||
|
|
||||||
|
|
||||||
# crashed/stopped/killed worker should result in a failed state
|
# crashed/stopped/killed worker should result in a failed state
|
||||||
sendCompose "$REQUEST_FILE"
|
sendCompose "$REQUEST_FILE"
|
||||||
waitForState "building"
|
waitForState "building"
|
||||||
|
|
@ -449,6 +507,12 @@ fi
|
||||||
test "$UPLOAD_TYPE" = "$EXPECTED_UPLOAD_TYPE"
|
test "$UPLOAD_TYPE" = "$EXPECTED_UPLOAD_TYPE"
|
||||||
test $((INIT_COMPOSES+1)) = "$SUBS_COMPOSES"
|
test $((INIT_COMPOSES+1)) = "$SUBS_COMPOSES"
|
||||||
|
|
||||||
|
|
||||||
|
if [ -s "$IMG_COMPOSE_REQ_FILE" ]; then
|
||||||
|
sendImgFromCompose "$IMG_COMPOSE_REQ_FILE"
|
||||||
|
waitForImgState
|
||||||
|
fi
|
||||||
|
|
||||||
#
|
#
|
||||||
# Verify the Cloud-provider specific upload_status options
|
# Verify the Cloud-provider specific upload_status options
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,17 @@ function cleanup() {
|
||||||
AWS_INSTANCE_ID="${AWS_INSTANCE_ID:-}"
|
AWS_INSTANCE_ID="${AWS_INSTANCE_ID:-}"
|
||||||
AMI_IMAGE_ID="${AMI_IMAGE_ID:-}"
|
AMI_IMAGE_ID="${AMI_IMAGE_ID:-}"
|
||||||
AWS_SNAPSHOT_ID="${AWS_SNAPSHOT_ID:-}"
|
AWS_SNAPSHOT_ID="${AWS_SNAPSHOT_ID:-}"
|
||||||
|
AMI_ID_2="${AMI_ID_2:-}"
|
||||||
|
SNAPSHOT_ID_2="${SNAPSHOT_ID_2:-}"
|
||||||
|
|
||||||
if [ -n "$AWS_CMD" ]; then
|
if [ -n "$AWS_CMD" ]; then
|
||||||
$AWS_CMD ec2 terminate-instances --instance-ids "$AWS_INSTANCE_ID"
|
$AWS_CMD ec2 terminate-instances --instance-ids "$AWS_INSTANCE_ID"
|
||||||
$AWS_CMD ec2 deregister-image --image-id "$AMI_IMAGE_ID"
|
$AWS_CMD ec2 deregister-image --image-id "$AMI_IMAGE_ID"
|
||||||
$AWS_CMD ec2 delete-snapshot --snapshot-id "$AWS_SNAPSHOT_ID"
|
$AWS_CMD ec2 delete-snapshot --snapshot-id "$AWS_SNAPSHOT_ID"
|
||||||
$AWS_CMD ec2 delete-key-pair --key-name "key-for-$AMI_IMAGE_ID"
|
$AWS_CMD ec2 delete-key-pair --key-name "key-for-$AMI_IMAGE_ID"
|
||||||
|
|
||||||
|
$AWS_CMD ec2 deregister-image --region "$REGION_2" --image-id "$AMI_2"
|
||||||
|
$AWS_CMD ec2 delete-snapshot --region "$REGION_2" --snapshot-id "$SNAPSHOT_ID_2"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,6 +73,13 @@ function createReqFile() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$IMG_COMPOSE_REQ_FILE" <<EOF
|
||||||
|
{
|
||||||
|
"region": "${AWS_REGION_2}",
|
||||||
|
"share_with_accounts": ["${AWS_API_TEST_SHARE_ACCOUNT_2}"]
|
||||||
|
}
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,6 +134,38 @@ function verify() {
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Verify that the 2nd image from the same compose was copied and shared with existing and new account
|
||||||
|
AMI_ID_2=$(echo "$IMG_UPLOAD_OPTIONS" | jq -r .ami)
|
||||||
|
REGION_2=$(echo "$IMG_UPLOAD_OPTIONS" | jq -r .region)
|
||||||
|
$AWS_CMD ec2 describe-images --owners self --region "$REGION_2" --image-ids "$AMI_ID_2" \
|
||||||
|
> "$WORKDIR/ami2.json"
|
||||||
|
|
||||||
|
SNAPSHOT_ID_2=$(jq -r '.Images[].BlockDeviceMappings[].Ebs.SnapshotId' "$WORKDIR/ami2.json")
|
||||||
|
$AWS_CMD ec2 describe-snapshot-attribute --region "$REGION_2" --snapshot-id "$SNAPSHOT_ID_2" \
|
||||||
|
--attribute createVolumePermission > "$WORKDIR/snapshot-attributes2.json"
|
||||||
|
SHARED_ID_2=$(jq -r ".CreateVolumePermissions[] | select(.UserId==\"$AWS_API_TEST_SHARE_ACCOUNT\").UserId" "$WORKDIR/snapshot-attributes2.json")
|
||||||
|
if [ "$AWS_API_TEST_SHARE_ACCOUNT" != "$SHARED_ID_2" ]; then
|
||||||
|
echo "EC2 Snapshot wasn't shared with AWS_API_TEST_SHARE_ACCOUNT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
SHARED_ID_2=$(jq -r ".CreateVolumePermissions[] | select(.UserId==\"$AWS_API_TEST_SHARE_ACCOUNT_2\").UserId" "$WORKDIR/snapshot-attributes2.json")
|
||||||
|
if [ "$AWS_API_TEST_SHARE_ACCOUNT_2" != "$SHARED_ID_2" ]; then
|
||||||
|
echo "EC2 Snapshot wasn't shared with AWS_API_TEST_SHARE_ACCOUNT_2"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
$AWS_CMD ec2 describe-image-attribute --attribute launchPermission --region "$REGION_2" --image-id "$AMI_ID_2" > "$WORKDIR/ami-attributes2.json"
|
||||||
|
SHARED_ID_2=$(jq -r ".LaunchPermissions[] | select(.UserId==\"$AWS_API_TEST_SHARE_ACCOUNT\").UserId" "$WORKDIR/ami-attributes2.json")
|
||||||
|
if [ "$AWS_API_TEST_SHARE_ACCOUNT" != "$SHARED_ID_2" ]; then
|
||||||
|
echo "EC2 ami wasn't shared with AWS_API_TEST_SHARE_ACCOUNT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
SHARED_ID_2=$(jq -r ".LaunchPermissions[] | select(.UserId==\"$AWS_API_TEST_SHARE_ACCOUNT_2\").UserId" "$WORKDIR/ami-attributes2.json")
|
||||||
|
if [ "$AWS_API_TEST_SHARE_ACCOUNT_2" != "$SHARED_ID_2" ]; then
|
||||||
|
echo "EC2 ami wasn't shared with AWS_API_TEST_SHARE_ACCOUNT_2"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Create key-pair
|
# Create key-pair
|
||||||
$AWS_CMD ec2 create-key-pair --key-name "key-for-$AMI_IMAGE_ID" --query 'KeyMaterial' --output text > keypair.pem
|
$AWS_CMD ec2 create-key-pair --key-name "key-for-$AMI_IMAGE_ID" --query 'KeyMaterial' --output text > keypair.pem
|
||||||
chmod 400 ./keypair.pem
|
chmod 400 ./keypair.pem
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue