From 64d04176cf11fa2bc260956ef99198520173c82f Mon Sep 17 00:00:00 2001 From: Simon Steinbeiss Date: Thu, 22 Jan 2026 09:41:22 +0100 Subject: [PATCH] Remove AWS region cloning support Remove the clone endpoints and related functionality: - Remove /composes/{composeId}/clone, /composes/{composeId}/clones, and /clones/{id} API endpoints from OpenAPI spec and regenerate api.go - Remove CloneCompose, GetCloneStatus, GetComposeClones handlers - Remove clone-related DB interface methods (InsertClone, GetClonesForCompose, GetClone) and SQL queries - Remove CloneCompose and CloneStatus methods from composer client - Remove clone-related tests from handler_test.go and main_test.go - Add migration to drop the clones table The clone feature allowed copying AWS AMIs to other regions, but this functionality is being removed in favor of direct uploads to the desired region. --- cmd/image-builder-db-test/main_test.go | 74 ---- cmd/image-builder-maintenance/db.go | 24 +- cmd/image-builder-maintenance/db_test.go | 19 - internal/clients/composer/client.go | 12 - internal/db/db.go | 117 ------ .../migrations-tern/015_drop_table_clones.sql | 2 + internal/v1/api.go | 334 +----------------- internal/v1/api.yaml | 172 --------- internal/v1/handler.go | 229 ------------ .../v1/handler_get_compose_status_test.go | 10 +- internal/v1/handler_test.go | 189 ---------- 11 files changed, 12 insertions(+), 1170 deletions(-) create mode 100644 internal/db/migrations-tern/015_drop_table_clones.sql diff --git a/cmd/image-builder-db-test/main_test.go b/cmd/image-builder-db-test/main_test.go index 8cb292032..816c15555 100644 --- a/cmd/image-builder-db-test/main_test.go +++ b/cmd/image-builder-db-test/main_test.go @@ -227,79 +227,6 @@ func testDeleteCompose(ctx context.Context, t *testing.T) { require.Equal(t, 1, count) } -func testClones(ctx context.Context, t *testing.T) { - d, err := db.InitDBConnectionPool(ctx, tutils.ConnStr(t)) - require.NoError(t, err) - conn := tutils.Connect(t) - defer conn.Close(ctx) - - composeId := uuid.New() - cloneId := uuid.New() - cloneId2 := uuid.New() - - // fkey constraint on compose id - require.Error(t, d.InsertClone(ctx, composeId, cloneId, []byte(` -{ - "region": "us-east-2" -} -`))) - - require.NoError(t, d.InsertCompose(ctx, composeId, ANR1, EMAIL1, ORGID1, nil, []byte(` -{ - "customizations": { - }, - "distribution": "rhel-8", - "image_requests": [ - { - "architecture": "x86_64", - "image_type": "guest-image", - "upload_request": { - "type": "aws.s3", - "options": { - } - } - } - ] -}`), nil, nil)) - - require.NoError(t, d.InsertClone(ctx, composeId, cloneId, []byte(` -{ - "region": "us-east-2" -} -`))) - require.NoError(t, d.InsertClone(ctx, composeId, cloneId2, []byte(` -{ - "region": "eu-central-1" -} -`))) - - clones, count, err := d.GetClonesForCompose(ctx, composeId, ORGID2, 100, 0) - require.NoError(t, err) - require.Empty(t, clones) - require.Equal(t, 0, count) - - clones, count, err = d.GetClonesForCompose(ctx, composeId, ORGID1, 1, 0) - require.NoError(t, err) - require.Len(t, clones, 1) - require.Equal(t, 2, count) - require.Equal(t, cloneId2, clones[0].Id) - - clones, count, err = d.GetClonesForCompose(ctx, composeId, ORGID1, 100, 0) - require.NoError(t, err) - require.Len(t, clones, 2) - require.Equal(t, 2, count) - require.Equal(t, cloneId2, clones[0].Id) - require.Equal(t, cloneId, clones[1].Id) - - entry, err := d.GetClone(ctx, cloneId, ORGID2) - require.ErrorIs(t, err, db.ErrCloneNotFound) - require.Nil(t, entry) - - entry, err = d.GetClone(ctx, cloneId, ORGID1) - require.NoError(t, err) - require.Equal(t, clones[1], *entry) -} - func testBlueprints(ctx context.Context, t *testing.T) { d, err := db.InitDBConnectionPool(ctx, tutils.ConnStr(t)) require.NoError(t, err) @@ -692,7 +619,6 @@ func TestAll(t *testing.T) { testCountComposesSince, testGetComposeImageType, testDeleteCompose, - testClones, testBlueprints, testGetBlueprintComposes, } diff --git a/cmd/image-builder-maintenance/db.go b/cmd/image-builder-maintenance/db.go index 070883ea2..0beb6493f 100644 --- a/cmd/image-builder-maintenance/db.go +++ b/cmd/image-builder-maintenance/db.go @@ -13,13 +13,6 @@ const ( sqlDeleteComposes = ` DELETE FROM composes WHERE created_at < $1` - sqlExpiredClonesCount = ` - SELECT COUNT(*) FROM clones - WHERE compose_id in ( - SELECT job_id - FROM composes - WHERE created_at < $1 - )` sqlExpiredComposesCount = ` SELECT COUNT(*) FROM composes WHERE created_at < $1` @@ -60,15 +53,6 @@ func (d *maintenanceDB) DeleteComposes(ctx context.Context, emailRetentionDate t return tag.RowsAffected(), nil } -func (d *maintenanceDB) ExpiredClonesCount(ctx context.Context, emailRetentionDate time.Time) (int64, error) { - var count int64 - err := d.Conn.QueryRow(ctx, sqlExpiredClonesCount, emailRetentionDate).Scan(&count) - if err != nil { - return 0, err - } - return count, nil -} - func (d *maintenanceDB) ExpiredComposesCount(ctx context.Context, emailRetentionDate time.Time) (int64, error) { var count int64 err := d.Conn.QueryRow(ctx, sqlExpiredComposesCount, emailRetentionDate).Scan(&count) @@ -159,7 +143,6 @@ func DBCleanup(ctx context.Context, dbURL string, dryRun bool, ComposesRetention slog.ErrorContext(ctx, "error running vacuum stats", "err", err) } - var rowsClones int64 var rows int64 emailRetentionDate := time.Now().AddDate(0, ComposesRetentionMonths*-1, 0) @@ -175,16 +158,11 @@ func DBCleanup(ctx context.Context, dbURL string, dryRun bool, ComposesRetention // so `break` works as expected } if dryRun { - rowsClones, err = db.ExpiredClonesCount(ctx, emailRetentionDate) - if err != nil { - slog.ErrorContext(ctx, "error querying expired clones", "err", err) - } - rows, err = db.ExpiredComposesCount(ctx, emailRetentionDate) if err != nil { slog.WarnContext(ctx, "error querying expired composes", "err", err) } - slog.InfoContext(ctx, "dryrun", "expired_composes_count", rows, "expired_clones_count", rowsClones) + slog.InfoContext(ctx, "dryrun", "expired_composes_count", rows) break } diff --git a/cmd/image-builder-maintenance/db_test.go b/cmd/image-builder-maintenance/db_test.go index adf2fc5e3..73ea0e0db 100644 --- a/cmd/image-builder-maintenance/db_test.go +++ b/cmd/image-builder-maintenance/db_test.go @@ -8,7 +8,6 @@ import ( "time" "github.com/google/uuid" - "github.com/osbuild/image-builder-crc/internal/db" "github.com/osbuild/image-builder-crc/internal/tutils" "github.com/stretchr/testify/require" ) @@ -80,9 +79,6 @@ func testExpireByCallingDBCleanup(ctx context.Context, t *testing.T) { d, err := newDB(ctx, connStr) require.NoError(t, err) - internalDB, err := db.InitDBConnectionPool(ctx, connStr) - require.NoError(t, err) - dbComposesRetentionMonths := 5 notYetExpiredTime := time.Now() @@ -97,32 +93,17 @@ func testExpireByCallingDBCleanup(ctx context.Context, t *testing.T) { insert = "INSERT INTO composes(job_id, request, created_at, account_number, org_id) VALUES ($1, $2, $3, $4, $5)" _, err = d.Conn.Exec(ctx, insert, composeIdExpired, "{}", alreadyExpiredTime, ANR1, ORGID1) - cloneId := uuid.New() - require.NoError(t, internalDB.InsertClone(ctx, composeIdExpired, cloneId, []byte(` -{ - "region": "us-east-2" -} -`))) - // two rows inserted, only one is expired rows, err := d.ExpiredComposesCount(ctx, emailRetentionDate) require.NoError(t, err) require.Equal(t, int64(1), rows) - rows, err = d.ExpiredClonesCount(ctx, emailRetentionDate) - require.NoError(t, err) - require.Equal(t, int64(1), rows) - err = DBCleanup(ctx, connStr, false, dbComposesRetentionMonths) require.NoError(t, err) rows, err = d.ExpiredComposesCount(ctx, emailRetentionDate) require.NoError(t, err) require.Equal(t, int64(0), rows) - - rows, err = d.ExpiredClonesCount(ctx, emailRetentionDate) - require.NoError(t, err) - require.Equal(t, int64(0), rows) } // testVacuum test if no vacuum is performed on a clean database diff --git a/internal/clients/composer/client.go b/internal/clients/composer/client.go index e26778e52..bc49bc872 100644 --- a/internal/clients/composer/client.go +++ b/internal/clients/composer/client.go @@ -140,15 +140,3 @@ func (cc *ComposerClient) Compose(ctx context.Context, compose ComposeRequest) ( func (cc *ComposerClient) OpenAPI(ctx context.Context) (*http.Response, error) { return cc.request(ctx, "GET", fmt.Sprintf("%s/openapi", cc.composerURL), nil, nil) } - -func (cc *ComposerClient) CloneCompose(ctx context.Context, id uuid.UUID, clone CloneComposeBody) (*http.Response, error) { - buf, err := json.Marshal(clone) - if err != nil { - return nil, err - } - return cc.request(ctx, "POST", fmt.Sprintf("%s/composes/%s/clone", cc.composerURL, id), contentHeaders, bytes.NewReader(buf)) -} - -func (cc *ComposerClient) CloneStatus(ctx context.Context, id uuid.UUID) (*http.Response, error) { - return cc.request(ctx, "GET", fmt.Sprintf("%s/clones/%s", cc.composerURL, id), nil, nil) -} diff --git a/internal/db/db.go b/internal/db/db.go index 4620acfc1..a835ec438 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -15,7 +15,6 @@ import ( // ErrComposeEntryNotFound occurs when no compose request is found for a user. var ErrComposeEntryNotFound = errors.New("compose entry not found") -var ErrCloneNotFound = errors.New("clone not found") var ErrBlueprintNotFound = errors.New("blueprint not found") var ErrAffectedRowsMismatch = errors.New("unexpected affected rows") @@ -37,13 +36,6 @@ type ComposeWithBlueprintVersion struct { BlueprintVersion *int } -type CloneEntry struct { - Id uuid.UUID - ComposeId uuid.UUID - Request json.RawMessage - CreatedAt time.Time -} - type BlueprintEntry struct { Id uuid.UUID VersionId uuid.UUID @@ -83,10 +75,6 @@ type DB interface { CountBlueprintComposesSince(ctx context.Context, orgId string, blueprintId uuid.UUID, blueprintVersion *int, since time.Duration, ignoreImageTypes []string) (int, error) DeleteCompose(ctx context.Context, jobId uuid.UUID, orgId string) error - InsertClone(ctx context.Context, composeId, cloneId uuid.UUID, request json.RawMessage) error - GetClonesForCompose(ctx context.Context, composeId uuid.UUID, orgId string, limit, offset int) ([]CloneEntry, int, error) - GetClone(ctx context.Context, id uuid.UUID, orgId string) (*CloneEntry, error) - InsertBlueprint(ctx context.Context, id uuid.UUID, versionId uuid.UUID, orgID, accountNumber, name, description string, body json.RawMessage, metadata json.RawMessage, serviceSnapshots json.RawMessage) error GetBlueprint(ctx context.Context, id uuid.UUID, orgID string, version *int) (*BlueprintEntry, error) UpdateBlueprint(ctx context.Context, id uuid.UUID, blueprintId uuid.UUID, orgId string, name string, description string, body json.RawMessage, serviceSnapshots json.RawMessage) error @@ -137,36 +125,6 @@ const ( SET deleted = TRUE WHERE org_id=$1 AND job_id=$2 ` - - sqlInsertClone = ` - INSERT INTO clones(id, compose_id, request, created_at) - VALUES($1, $2, $3, CURRENT_TIMESTAMP)` - - sqlGetClonesForCompose = ` - SELECT clones.id, clones.compose_id, clones.request, clones.created_at - FROM clones - WHERE clones.compose_id=$1 AND $1 in ( - SELECT composes.job_id - FROM composes - WHERE composes.org_id=$2) - ORDER BY created_at DESC - LIMIT $3 OFFSET $4` - - sqlCountClonesForCompose = ` - SELECT COUNT(*) - FROM clones - WHERE clones.compose_id=$1 AND $1 in ( - SELECT composes.job_id - FROM composes - WHERE composes.org_id=$2)` - - sqlGetClone = ` - SELECT clones.id, clones.compose_id, clones.request, clones.created_at - FROM clones - WHERE clones.id=$1 AND clones.compose_id in ( - SELECT composes.job_id - FROM composes - WHERE composes.org_id=$2)` ) func InitDBConnectionPool(ctx context.Context, connStr string) (DB, error) { @@ -323,78 +281,3 @@ func (db *dB) DeleteCompose(ctx context.Context, jobId uuid.UUID, orgId string) return err } - -func (db *dB) InsertClone(ctx context.Context, composeId, cloneId uuid.UUID, request json.RawMessage) error { - conn, err := db.Pool.Acquire(ctx) - if err != nil { - return err - } - defer conn.Release() - - _, err = conn.Exec(ctx, sqlInsertClone, cloneId, composeId, request) - return err -} - -func (db *dB) GetClonesForCompose(ctx context.Context, composeId uuid.UUID, orgId string, limit, offset int) ([]CloneEntry, int, error) { - conn, err := db.Pool.Acquire(ctx) - if err != nil { - return nil, 0, err - } - defer conn.Release() - - rows, err := conn.Query(ctx, sqlGetClonesForCompose, composeId, orgId, limit, offset) - if err != nil { - return nil, 0, err - } - defer rows.Close() - - var clones []CloneEntry - for rows.Next() { - var id uuid.UUID - var composeID uuid.UUID - var request json.RawMessage - var createdAt time.Time - err = rows.Scan(&id, &composeID, &request, &createdAt) - if err != nil { - return nil, 0, err - } - clones = append(clones, CloneEntry{ - id, - composeID, - request, - createdAt, - }) - } - if err = rows.Err(); err != nil { - return nil, 0, err - } - - var count int - err = conn.QueryRow(ctx, sqlCountClonesForCompose, composeId, orgId).Scan(&count) - if err != nil { - return nil, 0, err - } - - return clones, count, nil - -} - -func (db *dB) GetClone(ctx context.Context, id uuid.UUID, orgId string) (*CloneEntry, error) { - conn, err := db.Pool.Acquire(ctx) - if err != nil { - return nil, err - } - defer conn.Release() - - var clone CloneEntry - err = conn.QueryRow(ctx, sqlGetClone, id, orgId).Scan(&clone.Id, &clone.ComposeId, &clone.Request, &clone.CreatedAt) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return nil, ErrCloneNotFound - } else { - return nil, err - } - } - - return &clone, nil -} diff --git a/internal/db/migrations-tern/015_drop_table_clones.sql b/internal/db/migrations-tern/015_drop_table_clones.sql new file mode 100644 index 000000000..7b82b57fb --- /dev/null +++ b/internal/db/migrations-tern/015_drop_table_clones.sql @@ -0,0 +1,2 @@ +-- Drop the clones table as cloning support is being removed +DROP TABLE IF EXISTS clones; diff --git a/internal/v1/api.go b/internal/v1/api.go index 3efce721c..e24e7ff50 100644 --- a/internal/v1/api.go +++ b/internal/v1/api.go @@ -27,14 +27,6 @@ const ( Ui ClientId = "ui" ) -// Defines values for CloneStatusResponseStatus. -const ( - CloneStatusResponseStatusFailure CloneStatusResponseStatus = "failure" - CloneStatusResponseStatusPending CloneStatusResponseStatus = "pending" - CloneStatusResponseStatusRunning CloneStatusResponseStatus = "running" - CloneStatusResponseStatusSuccess CloneStatusResponseStatus = "success" -) - // Defines values for CustomizationsPartitioningMode. const ( AutoLvm CustomizationsPartitioningMode = "auto-lvm" @@ -158,10 +150,10 @@ const ( // Defines values for UploadStatusStatus. const ( - Failure UploadStatusStatus = "failure" - Pending UploadStatusStatus = "pending" - Running UploadStatusStatus = "running" - Success UploadStatusStatus = "success" + UploadStatusStatusFailure UploadStatusStatus = "failure" + UploadStatusStatusPending UploadStatusStatus = "pending" + UploadStatusStatusRunning UploadStatusStatus = "running" + UploadStatusStatusSuccess UploadStatusStatus = "success" ) // Defines values for UploadTypes. @@ -189,18 +181,6 @@ type AAPRegistration struct { TlsCertificateAuthority string `json:"tls_certificate_authority,omitempty"` } -// AWSEC2Clone defines model for AWSEC2Clone. -type AWSEC2Clone struct { - // Region A region as described in - // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-regions - Region string `json:"region"` - - // ShareWithAccounts An array of AWS account IDs as described in - // https://docs.aws.amazon.com/IAM/latest/UserGuide/console_account-alias.html - ShareWithAccounts *[]string `json:"share_with_accounts,omitempty"` - ShareWithSources *[]string `json:"share_with_sources,omitempty"` -} - // AWSS3UploadRequestOptions defines model for AWSS3UploadRequestOptions. type AWSS3UploadRequestOptions = map[string]interface{} @@ -357,48 +337,6 @@ type CACertsCustomization struct { // ClientId defines model for ClientId. type ClientId string -// CloneRequest defines model for CloneRequest. -type CloneRequest struct { - union json.RawMessage -} - -// CloneResponse defines model for CloneResponse. -type CloneResponse struct { - Id openapi_types.UUID `json:"id"` -} - -// CloneStatusResponse defines model for CloneStatusResponse. -type CloneStatusResponse struct { - ComposeId *openapi_types.UUID `json:"compose_id,omitempty"` - Options CloneStatusResponse_Options `json:"options"` - Status CloneStatusResponseStatus `json:"status"` - Type UploadTypes `json:"type"` -} - -// CloneStatusResponse_Options defines model for CloneStatusResponse.Options. -type CloneStatusResponse_Options struct { - union json.RawMessage -} - -// CloneStatusResponseStatus defines model for CloneStatusResponse.Status. -type CloneStatusResponseStatus string - -// ClonesResponse defines model for ClonesResponse. -type ClonesResponse struct { - Data []ClonesResponseItem `json:"data"` - Links ListResponseLinks `json:"links"` - Meta ListResponseMeta `json:"meta"` -} - -// ClonesResponseItem defines model for ClonesResponseItem. -type ClonesResponseItem struct { - // ComposeId UUID of the parent compose of the clone - ComposeId openapi_types.UUID `json:"compose_id"` - CreatedAt string `json:"created_at"` - Id openapi_types.UUID `json:"id"` - Request CloneRequest `json:"request"` -} - // ComposeMetadata defines model for ComposeMetadata. type ComposeMetadata struct { // OstreeCommit ID (hash) of the built commit @@ -1177,15 +1115,6 @@ type GetComposesParams struct { IgnoreImageTypes *[]ImageTypes `form:"ignoreImageTypes,omitempty" json:"ignoreImageTypes,omitempty"` } -// GetComposeClonesParams defines parameters for GetComposeClones. -type GetComposeClonesParams struct { - // Limit max amount of clones, default 100 - Limit *int `form:"limit,omitempty" json:"limit,omitempty"` - - // Offset clones page offset, default 0 - Offset *int `form:"offset,omitempty" json:"offset,omitempty"` -} - // GetPackagesParams defines parameters for GetPackages. type GetPackagesParams struct { // Distribution distribution to look up packages for @@ -1219,188 +1148,9 @@ type ComposeBlueprintJSONRequestBody ComposeBlueprintJSONBody // ComposeImageJSONRequestBody defines body for ComposeImage for application/json ContentType. type ComposeImageJSONRequestBody = ComposeRequest -// CloneComposeJSONRequestBody defines body for CloneCompose for application/json ContentType. -type CloneComposeJSONRequestBody = CloneRequest - // RecommendPackageJSONRequestBody defines body for RecommendPackage for application/json ContentType. type RecommendPackageJSONRequestBody = RecommendPackageRequest -// AsAWSEC2Clone returns the union data inside the CloneRequest as a AWSEC2Clone -func (t CloneRequest) AsAWSEC2Clone() (AWSEC2Clone, error) { - var body AWSEC2Clone - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromAWSEC2Clone overwrites any union data inside the CloneRequest as the provided AWSEC2Clone -func (t *CloneRequest) FromAWSEC2Clone(v AWSEC2Clone) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeAWSEC2Clone performs a merge with any union data inside the CloneRequest, using the provided AWSEC2Clone -func (t *CloneRequest) MergeAWSEC2Clone(v AWSEC2Clone) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -func (t CloneRequest) MarshalJSON() ([]byte, error) { - b, err := t.union.MarshalJSON() - return b, err -} - -func (t *CloneRequest) UnmarshalJSON(b []byte) error { - err := t.union.UnmarshalJSON(b) - return err -} - -// AsAWSUploadStatus returns the union data inside the CloneStatusResponse_Options as a AWSUploadStatus -func (t CloneStatusResponse_Options) AsAWSUploadStatus() (AWSUploadStatus, error) { - var body AWSUploadStatus - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromAWSUploadStatus overwrites any union data inside the CloneStatusResponse_Options as the provided AWSUploadStatus -func (t *CloneStatusResponse_Options) FromAWSUploadStatus(v AWSUploadStatus) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeAWSUploadStatus performs a merge with any union data inside the CloneStatusResponse_Options, using the provided AWSUploadStatus -func (t *CloneStatusResponse_Options) MergeAWSUploadStatus(v AWSUploadStatus) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -// AsAWSS3UploadStatus returns the union data inside the CloneStatusResponse_Options as a AWSS3UploadStatus -func (t CloneStatusResponse_Options) AsAWSS3UploadStatus() (AWSS3UploadStatus, error) { - var body AWSS3UploadStatus - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromAWSS3UploadStatus overwrites any union data inside the CloneStatusResponse_Options as the provided AWSS3UploadStatus -func (t *CloneStatusResponse_Options) FromAWSS3UploadStatus(v AWSS3UploadStatus) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeAWSS3UploadStatus performs a merge with any union data inside the CloneStatusResponse_Options, using the provided AWSS3UploadStatus -func (t *CloneStatusResponse_Options) MergeAWSS3UploadStatus(v AWSS3UploadStatus) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -// AsGCPUploadStatus returns the union data inside the CloneStatusResponse_Options as a GCPUploadStatus -func (t CloneStatusResponse_Options) AsGCPUploadStatus() (GCPUploadStatus, error) { - var body GCPUploadStatus - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromGCPUploadStatus overwrites any union data inside the CloneStatusResponse_Options as the provided GCPUploadStatus -func (t *CloneStatusResponse_Options) FromGCPUploadStatus(v GCPUploadStatus) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeGCPUploadStatus performs a merge with any union data inside the CloneStatusResponse_Options, using the provided GCPUploadStatus -func (t *CloneStatusResponse_Options) MergeGCPUploadStatus(v GCPUploadStatus) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -// AsAzureUploadStatus returns the union data inside the CloneStatusResponse_Options as a AzureUploadStatus -func (t CloneStatusResponse_Options) AsAzureUploadStatus() (AzureUploadStatus, error) { - var body AzureUploadStatus - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromAzureUploadStatus overwrites any union data inside the CloneStatusResponse_Options as the provided AzureUploadStatus -func (t *CloneStatusResponse_Options) FromAzureUploadStatus(v AzureUploadStatus) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeAzureUploadStatus performs a merge with any union data inside the CloneStatusResponse_Options, using the provided AzureUploadStatus -func (t *CloneStatusResponse_Options) MergeAzureUploadStatus(v AzureUploadStatus) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -// AsOCIUploadStatus returns the union data inside the CloneStatusResponse_Options as a OCIUploadStatus -func (t CloneStatusResponse_Options) AsOCIUploadStatus() (OCIUploadStatus, error) { - var body OCIUploadStatus - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromOCIUploadStatus overwrites any union data inside the CloneStatusResponse_Options as the provided OCIUploadStatus -func (t *CloneStatusResponse_Options) FromOCIUploadStatus(v OCIUploadStatus) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeOCIUploadStatus performs a merge with any union data inside the CloneStatusResponse_Options, using the provided OCIUploadStatus -func (t *CloneStatusResponse_Options) MergeOCIUploadStatus(v OCIUploadStatus) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -func (t CloneStatusResponse_Options) MarshalJSON() ([]byte, error) { - b, err := t.union.MarshalJSON() - return b, err -} - -func (t *CloneStatusResponse_Options) UnmarshalJSON(b []byte) error { - err := t.union.UnmarshalJSON(b) - return err -} - // AsDirectoryGroup0 returns the union data inside the Directory_Group as a DirectoryGroup0 func (t Directory_Group) AsDirectoryGroup0() (DirectoryGroup0, error) { var body DirectoryGroup0 @@ -2020,9 +1770,6 @@ type ServerInterface interface { // export a blueprint // (GET /blueprints/{id}/export) ExportBlueprint(ctx echo.Context, id openapi_types.UUID) error - // get status of a compose clone - // (GET /clones/{id}) - GetCloneStatus(ctx echo.Context, id openapi_types.UUID) error // compose image // (POST /compose) ComposeImage(ctx echo.Context) error @@ -2035,12 +1782,6 @@ type ServerInterface interface { // get status of an image compose // (GET /composes/{composeId}) GetComposeStatus(ctx echo.Context, composeId openapi_types.UUID) error - // clone a compose - // (POST /composes/{composeId}/clone) - CloneCompose(ctx echo.Context, composeId openapi_types.UUID) error - // get clones of a compose - // (GET /composes/{composeId}/clones) - GetComposeClones(ctx echo.Context, composeId openapi_types.UUID, params GetComposeClonesParams) error // get metadata of an image compose // (GET /composes/{composeId}/metadata) GetComposeMetadata(ctx echo.Context, composeId openapi_types.UUID) error @@ -2277,22 +2018,6 @@ func (w *ServerInterfaceWrapper) ExportBlueprint(ctx echo.Context) error { return err } -// GetCloneStatus converts echo context to params. -func (w *ServerInterfaceWrapper) GetCloneStatus(ctx echo.Context) error { - var err error - // ------------- Path parameter "id" ------------- - var id openapi_types.UUID - - err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) - } - - // Invoke the callback with all the unmarshaled arguments - err = w.Handler.GetCloneStatus(ctx, id) - return err -} - // ComposeImage converts echo context to params. func (w *ServerInterfaceWrapper) ComposeImage(ctx echo.Context) error { var err error @@ -2366,54 +2091,6 @@ func (w *ServerInterfaceWrapper) GetComposeStatus(ctx echo.Context) error { return err } -// CloneCompose converts echo context to params. -func (w *ServerInterfaceWrapper) CloneCompose(ctx echo.Context) error { - var err error - // ------------- Path parameter "composeId" ------------- - var composeId openapi_types.UUID - - err = runtime.BindStyledParameterWithOptions("simple", "composeId", ctx.Param("composeId"), &composeId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter composeId: %s", err)) - } - - // Invoke the callback with all the unmarshaled arguments - err = w.Handler.CloneCompose(ctx, composeId) - return err -} - -// GetComposeClones converts echo context to params. -func (w *ServerInterfaceWrapper) GetComposeClones(ctx echo.Context) error { - var err error - // ------------- Path parameter "composeId" ------------- - var composeId openapi_types.UUID - - err = runtime.BindStyledParameterWithOptions("simple", "composeId", ctx.Param("composeId"), &composeId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter composeId: %s", err)) - } - - // Parameter object where we will unmarshal all parameters from the context - var params GetComposeClonesParams - // ------------- Optional query parameter "limit" ------------- - - err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) - } - - // ------------- Optional query parameter "offset" ------------- - - err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) - } - - // Invoke the callback with all the unmarshaled arguments - err = w.Handler.GetComposeClones(ctx, composeId, params) - return err -} - // GetComposeMetadata converts echo context to params. func (w *ServerInterfaceWrapper) GetComposeMetadata(ctx echo.Context) error { var err error @@ -2629,13 +2306,10 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.POST(baseURL+"/blueprints/:id/compose", wrapper.ComposeBlueprint) router.GET(baseURL+"/blueprints/:id/composes", wrapper.GetBlueprintComposes) router.GET(baseURL+"/blueprints/:id/export", wrapper.ExportBlueprint) - router.GET(baseURL+"/clones/:id", wrapper.GetCloneStatus) router.POST(baseURL+"/compose", wrapper.ComposeImage) router.GET(baseURL+"/composes", wrapper.GetComposes) router.DELETE(baseURL+"/composes/:composeId", wrapper.DeleteCompose) router.GET(baseURL+"/composes/:composeId", wrapper.GetComposeStatus) - router.POST(baseURL+"/composes/:composeId/clone", wrapper.CloneCompose) - router.GET(baseURL+"/composes/:composeId/clones", wrapper.GetComposeClones) router.GET(baseURL+"/composes/:composeId/metadata", wrapper.GetComposeMetadata) router.GET(baseURL+"/distributions", wrapper.GetDistributions) router.POST(baseURL+"/experimental/blueprints/:id/fixup", wrapper.FixupBlueprint) diff --git a/internal/v1/api.yaml b/internal/v1/api.yaml index 4c3839e41..a935840b3 100644 --- a/internal/v1/api.yaml +++ b/internal/v1/api.yaml @@ -477,99 +477,6 @@ paths: application/json: schema: $ref: '#/components/schemas/ComposeMetadata' - /composes/{composeId}/clone: - post: - summary: clone a compose - description: | - Clones a compose. Only composes with the 'aws' image type currently support cloning. - parameters: - - in: path - name: composeId - schema: - type: string - format: uuid - example: '123e4567-e89b-12d3-a456-426655440000' - required: true - description: Id of compose to clone - operationId: cloneCompose - tags: - - compose - requestBody: - required: true - description: details of the new clone - content: - application/json: - schema: - $ref: "#/components/schemas/CloneRequest" - responses: - '201': - description: cloning has started - content: - application/json: - schema: - $ref: "#/components/schemas/CloneResponse" - /composes/{composeId}/clones: - get: - summary: get clones of a compose - parameters: - - in: path - name: composeId - schema: - type: string - format: uuid - example: '123e4567-e89b-12d3-a456-426655440000' - required: true - description: Id of compose to get the clones of - - in: query - name: limit - schema: - type: integer - default: 100 - minimum: 1 - maximum: 100 - description: max amount of clones, default 100 - - in: query - name: offset - schema: - type: integer - default: 0 - minimum: 0 - description: clones page offset, default 0 - description: | - Returns a list of all the clones which were started for a compose - operationId: getComposeClones - tags: - - compose - responses: - '200': - description: compose clones - content: - application/json: - schema: - $ref: '#/components/schemas/ClonesResponse' - /clones/{id}: - get: - summary: get status of a compose clone - parameters: - - in: path - name: id - schema: - type: string - format: uuid - example: '123e4567-e89b-12d3-a456-426655440000' - required: true - description: Id of clone status to get - description: status of a clone - operationId: getCloneStatus - tags: - - compose - responses: - '200': - description: clone status - content: - application/json: - schema: - $ref: '#/components/schemas/CloneStatusResponse' /compose: post: summary: compose image @@ -918,16 +825,6 @@ components: total: type: integer description: Total amount of steps in the build. - CloneStatusResponse: - required: - - compose_id - allOf: - - type: object - properties: - compose_id: - type: string - format: uuid - - $ref: '#/components/schemas/UploadStatus' UploadStatus: required: - status @@ -1608,75 +1505,6 @@ components: type: string modelVersion: type: string - ClonesResponse: - required: - - meta - - links - - data - properties: - meta: - $ref: '#/components/schemas/ListResponseMeta' - links: - $ref: '#/components/schemas/ListResponseLinks' - data: - type: array - items: - $ref: '#/components/schemas/ClonesResponseItem' - ClonesResponseItem: - required: - - id - - compose_id - - request - - created_at - properties: - id: - type: string - format: uuid - compose_id: - type: string - format: uuid - description: 'UUID of the parent compose of the clone' - request: - $ref: '#/components/schemas/CloneRequest' - created_at: - type: string - CloneRequest: - oneOf: - - $ref: '#/components/schemas/AWSEC2Clone' - AWSEC2Clone: - type: object - required: - - region - properties: - region: - type: string - description: | - A region as described in - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-regions - share_with_accounts: - type: array - maxItems: 100 - example: ['123456789012'] - description: | - An array of AWS account IDs as described in - https://docs.aws.amazon.com/IAM/latest/UserGuide/console_account-alias.html - items: - type: string - pattern: '^[0-9]{12}$' - share_with_sources: - type: array - example: ['12345'] - items: - type: string - uniqueItems: true - CloneResponse: - required: - - id - properties: - id: - type: string - format: uuid - example: '123e4567-e89b-12d3-a456-426655440000' DistributionProfileResponse: type: array description: | diff --git a/internal/v1/handler.go b/internal/v1/handler.go index e8eebbf0e..4c302172a 100644 --- a/internal/v1/handler.go +++ b/internal/v1/handler.go @@ -8,13 +8,11 @@ import ( "net/http" "net/url" "strconv" - "strings" "time" "github.com/google/uuid" "github.com/labstack/echo/v4" "github.com/osbuild/image-builder-crc/internal/clients/composer" - "github.com/osbuild/image-builder-crc/internal/clients/provisioning" "github.com/osbuild/image-builder-crc/internal/common" "github.com/osbuild/image-builder-crc/internal/db" "github.com/osbuild/image-builder-crc/internal/distribution" @@ -605,233 +603,6 @@ func (h *Handlers) GetComposes(ctx echo.Context, params GetComposesParams) error }) } -func (h *Handlers) CloneCompose(ctx echo.Context, composeId uuid.UUID) error { - err := h.canUserAccessComposeId(ctx, composeId) - if err != nil { - return err - } - - userID, err := h.server.getIdentity(ctx) - if err != nil { - return err - } - imageType, err := h.server.db.GetComposeImageType(ctx.Request().Context(), composeId, userID.OrgID()) - if err != nil { - if errors.Is(err, db.ErrComposeEntryNotFound) { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unable to find compose %v", composeId)) - } - ctx.Logger().Errorf("Error querying image type for compose %v: %v", composeId, err) - return echo.NewHTTPError(http.StatusInternalServerError, "Something went wrong querying the compose") - } - - var resp *http.Response - var rawCR json.RawMessage - if ImageTypes(imageType) == ImageTypesAws || ImageTypes(imageType) == ImageTypesAmi { - var awsEC2CloneReq AWSEC2Clone - err = ctx.Bind(&awsEC2CloneReq) - if err != nil { - return err - } - - rawCR, err = json.Marshal(awsEC2CloneReq) - if err != nil { - return err - } - - var shareWithAccounts []string - if awsEC2CloneReq.ShareWithAccounts != nil { - shareWithAccounts = append(shareWithAccounts, *awsEC2CloneReq.ShareWithAccounts...) - } - - if awsEC2CloneReq.ShareWithSources != nil { - for _, source := range *awsEC2CloneReq.ShareWithSources { - resp, err := h.server.pClient.GetUploadInfo(ctx.Request().Context(), source) - if err != nil { - ctx.Logger().Error(err) - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unable to request source: %s", source)) - } - defer closeBody(ctx, resp.Body) - - var uploadInfo provisioning.V1SourceUploadInfoResponse - err = json.NewDecoder(resp.Body).Decode(&uploadInfo) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Unable to resolve source: %s", source)) - } - - if uploadInfo.Aws == nil || uploadInfo.Aws.AccountId == nil || len(*uploadInfo.Aws.AccountId) != 12 { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unable to resolve source %s to an aws account id: %v", source, uploadInfo.Aws.AccountId)) - } - - ctx.Logger().Info(fmt.Sprintf("Resolved source %s, to account id %s", strings.Replace(source, "\n", "", -1), *uploadInfo.Aws.AccountId)) - shareWithAccounts = append(shareWithAccounts, *uploadInfo.Aws.AccountId) - } - } - - var ccb composer.CloneComposeBody - err = ccb.FromAWSEC2CloneCompose(composer.AWSEC2CloneCompose{ - Region: awsEC2CloneReq.Region, - ShareWithAccounts: &shareWithAccounts, - }) - if err != nil { - return err - } - - resp, err = h.server.cClient.CloneCompose(ctx.Request().Context(), composeId, ccb) - if err != nil { - return err - } - } else { - return echo.NewHTTPError(http.StatusBadRequest, "Cloning a compose is only available for AWS composes") - } - - if resp == nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Something went wrong creating the clone") - } - defer closeBody(ctx, resp.Body) - if resp.StatusCode != http.StatusCreated { - var cError composer.Error - err = json.NewDecoder(resp.Body).Decode(&cError) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Unable to parse error returned by image-builder-composer service") - } - if cError.Code == ComposeRunningOrFailedError { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("image-builder-composer compose failed: %s", cError.Reason)) - } - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("image-builder-composer service returned an error: %s", cError.Reason)) - } - - var cloneResponse composer.CloneComposeResponse - err = json.NewDecoder(resp.Body).Decode(&cloneResponse) - if err != nil { - ctx.Logger().Errorf("unable to decode CloneComposeResponse: %v", err) - return err - } - - err = h.server.db.InsertClone(ctx.Request().Context(), composeId, cloneResponse.Id, rawCR) - if err != nil { - ctx.Logger().Errorf("Error inserting clone into db for compose %v: %v", err, composeId) - return echo.NewHTTPError(http.StatusInternalServerError, "Something went wrong saving the clone") - } - - return ctx.JSON(http.StatusCreated, CloneResponse{ - Id: cloneResponse.Id, - }) -} - -func (h *Handlers) GetCloneStatus(ctx echo.Context, id uuid.UUID) error { - userID, err := h.server.getIdentity(ctx) - if err != nil { - return err - } - - cloneEntry, err := h.server.db.GetClone(ctx.Request().Context(), id, userID.OrgID()) - if err != nil { - if errors.Is(err, db.ErrCloneNotFound) { - return echo.NewHTTPError(http.StatusNotFound, err) - } - ctx.Logger().Errorf("Error querying clone %v: %v", id, err) - return echo.NewHTTPError(http.StatusInternalServerError, "Something went wrong querying this clone") - } - if cloneEntry == nil { - return echo.NewHTTPError(http.StatusBadRequest, "Requested clone cannot be found") - } - - resp, err := h.server.cClient.CloneStatus(ctx.Request().Context(), id) - if err != nil { - ctx.Logger().Errorf("Error requesting clone status for clone %v: %v", id, err) - return err - } - defer closeBody(ctx, resp.Body) - if resp.StatusCode != http.StatusOK { - var cErr composer.Error - err = json.NewDecoder(resp.Body).Decode(&cErr) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Unable to parse composer error") - } - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Unable to create clone job: %v", cErr.Reason)) - } - - var cloudStat composer.CloneStatus - err = json.NewDecoder(resp.Body).Decode(&cloudStat) - if err != nil { - ctx.Logger().Errorf("unable to decode clone status: %v", err) - return err - } - - var options CloneStatusResponse_Options - uo, err := cloudStat.Options.AsAWSEC2UploadStatus() - if err != nil { - ctx.Logger().Errorf("unable to decode clone status: %v", err) - return err - } - - err = options.FromAWSUploadStatus(AWSUploadStatus{ - Ami: uo.Ami, - Region: uo.Region, - }) - if err != nil { - ctx.Logger().Errorf("unable to encode clone status: %v", err) - return err - } - - return ctx.JSON(http.StatusOK, CloneStatusResponse{ - ComposeId: &cloneEntry.ComposeId, - Status: CloneStatusResponseStatus(cloudStat.Status), - Type: UploadTypes(cloudStat.Type), - Options: options, - }) -} - -func (h *Handlers) GetComposeClones(ctx echo.Context, composeId uuid.UUID, params GetComposeClonesParams) error { - err := h.canUserAccessComposeId(ctx, composeId) - if err != nil { - return err - } - - userID, err := h.server.getIdentity(ctx) - if err != nil { - return err - } - - limit := 100 - if params.Limit != nil && *params.Limit > 0 { - limit = *params.Limit - } - - offset := 0 - if params.Offset != nil { - offset = *params.Offset - } - - cloneEntries, count, err := h.server.db.GetClonesForCompose(ctx.Request().Context(), composeId, userID.OrgID(), limit, offset) - if err != nil { - ctx.Logger().Errorf("Error querying clones for compose %v: %v", composeId, err) - return echo.NewHTTPError(http.StatusInternalServerError, "Something went wrong querying clones for this compose") - } - - data := []ClonesResponseItem{} - for _, c := range cloneEntries { - var cr CloneRequest - err = json.Unmarshal(c.Request, &cr) - if err != nil { - return echo.NewHTTPError( - http.StatusInternalServerError, "Something went wrong querying clones for this compose") - } - data = append(data, ClonesResponseItem{ - Id: c.Id, - ComposeId: composeId, - Request: cr, - CreatedAt: c.CreatedAt.Format(time.RFC3339), - }) - } - - return ctx.JSON(http.StatusOK, ClonesResponse{ - Meta: ListResponseMeta{count}, - Links: h.newLinksWithExtraParams(fmt.Sprintf("composes/%v/clones", composeId), count, limit, url.Values{}), - Data: data, - }) -} - func closeBody(ctx echo.Context, body io.Closer) { err := body.Close() if err != nil { diff --git a/internal/v1/handler_get_compose_status_test.go b/internal/v1/handler_get_compose_status_test.go index cd0093d11..a97ee8d55 100644 --- a/internal/v1/handler_get_compose_status_test.go +++ b/internal/v1/handler_get_compose_status_test.go @@ -136,7 +136,7 @@ func TestComposeStatus(t *testing.T) { imageStatus: v1.ImageStatus{ Status: v1.ImageStatusStatusSuccess, UploadStatus: &v1.UploadStatus{ - Status: v1.Success, + Status: v1.UploadStatusStatusSuccess, Type: v1.UploadTypesAws, Options: ibAwsUS, }, @@ -165,7 +165,7 @@ func TestComposeStatus(t *testing.T) { imageStatus: v1.ImageStatus{ Status: v1.ImageStatusStatusSuccess, UploadStatus: &v1.UploadStatus{ - Status: v1.Success, + Status: v1.UploadStatusStatusSuccess, Type: v1.UploadTypesAwsS3, Options: ibAwsS3US, }, @@ -186,7 +186,7 @@ func TestComposeStatus(t *testing.T) { imageStatus: v1.ImageStatus{ Status: v1.ImageStatusStatusSuccess, UploadStatus: &v1.UploadStatus{ - Status: v1.Success, + Status: v1.UploadStatusStatusSuccess, Type: v1.UploadTypesAzure, Options: ibAzureUS, }, @@ -207,7 +207,7 @@ func TestComposeStatus(t *testing.T) { imageStatus: v1.ImageStatus{ Status: v1.ImageStatusStatusSuccess, UploadStatus: &v1.UploadStatus{ - Status: v1.Success, + Status: v1.UploadStatusStatusSuccess, Type: v1.UploadTypesGcp, Options: ibGcpUS, }, @@ -228,7 +228,7 @@ func TestComposeStatus(t *testing.T) { imageStatus: v1.ImageStatus{ Status: v1.ImageStatusStatusSuccess, UploadStatus: &v1.UploadStatus{ - Status: v1.Success, + Status: v1.UploadStatusStatusSuccess, Type: v1.UploadTypesOciObjectstorage, Options: ibOciUS, }, diff --git a/internal/v1/handler_test.go b/internal/v1/handler_test.go index ccd477232..bce6b9306 100644 --- a/internal/v1/handler_test.go +++ b/internal/v1/handler_test.go @@ -19,7 +19,6 @@ import ( "github.com/stretchr/testify/require" "github.com/osbuild/image-builder-crc/internal/clients/composer" - "github.com/osbuild/image-builder-crc/internal/clients/provisioning" "github.com/osbuild/image-builder-crc/internal/common" "github.com/osbuild/image-builder-crc/internal/tutils" v1 "github.com/osbuild/image-builder-crc/internal/v1" @@ -461,194 +460,6 @@ func TestMetrics(t *testing.T) { require.Contains(t, body, "image_builder_crc_compose_errors") } -func TestGetClones(t *testing.T) { - ctx := context.Background() - id := uuid.New() - cloneId := uuid.New() - awsAccountId := "123456123456" - - apiSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("Authorization") == "Bearer" { - w.WriteHeader(http.StatusUnauthorized) - return - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusCreated) - - var cloneReq composer.AWSEC2CloneCompose - err := json.NewDecoder(r.Body).Decode(&cloneReq) - require.NoError(t, err) - require.Equal(t, awsAccountId, (*cloneReq.ShareWithAccounts)[0]) - - result := composer.CloneComposeResponse{ - Id: cloneId, - } - err = json.NewEncoder(w).Encode(result) - require.NoError(t, err) - })) - defer apiSrv.Close() - - provSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - awsId := struct { - AccountId *string `json:"account_id,omitempty"` - }{ - AccountId: &awsAccountId, - } - result := provisioning.V1SourceUploadInfoResponse{ - Aws: &awsId, - } - - require.Equal(t, tutils.AuthString0, r.Header.Get("x-rh-identity")) - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - err := json.NewEncoder(w).Encode(result) - require.NoError(t, err) - })) - defer provSrv.Close() - - srv := startServer(t, &testServerClientsConf{ComposerURL: apiSrv.URL, ProvURL: provSrv.URL}, &v1.ServerConfig{ - DistributionsDir: "../../distributions", - }) - defer srv.Shutdown(t) - - err := srv.DB.InsertCompose(ctx, id, "500000", "user500000@test.test", "000000", nil, json.RawMessage(` -{ - "image_requests": [ - { - "image_type": "aws" - } - ] -}`), nil, nil) - require.NoError(t, err) - - var csResp v1.ClonesResponse - respStatusCode, body := tutils.GetResponseBody(t, srv.URL+fmt.Sprintf("/api/image-builder/v1/composes/%s/clones", id), &tutils.AuthString0) - require.Equal(t, http.StatusOK, respStatusCode) - err = json.Unmarshal([]byte(body), &csResp) - require.NoError(t, err) - require.Equal(t, 0, len(csResp.Data)) - require.Contains(t, body, "\"data\":[]") - - cloneReq := v1.AWSEC2Clone{ - Region: "us-east-2", - ShareWithSources: &[]string{"1"}, - } - respStatusCode, body = tutils.PostResponseBody(t, srv.URL+fmt.Sprintf("/api/image-builder/v1/composes/%s/clone", id), cloneReq) - require.Equal(t, http.StatusCreated, respStatusCode) - - var cResp v1.CloneResponse - err = json.Unmarshal([]byte(body), &cResp) - require.NoError(t, err) - require.Equal(t, cloneId, cResp.Id) - - respStatusCode, body = tutils.GetResponseBody(t, srv.URL+fmt.Sprintf("/api/image-builder/v1/composes/%s/clones", id), &tutils.AuthString0) - require.Equal(t, http.StatusOK, respStatusCode) - err = json.Unmarshal([]byte(body), &csResp) - require.NoError(t, err) - require.Equal(t, 1, len(csResp.Data)) - require.Equal(t, cloneId, csResp.Data[0].Id) - - cloneReqExp, err := json.Marshal(cloneReq) - require.NoError(t, err) - cloneReqRecv, err := json.Marshal(csResp.Data[0].Request) - require.NoError(t, err) - require.Equal(t, cloneReqExp, cloneReqRecv) -} - -func TestGetCloneStatus(t *testing.T) { - ctx := context.Background() - cloneId := uuid.New() - id := uuid.New() - apiSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("Authorization") == "Bearer" { - w.WriteHeader(http.StatusUnauthorized) - return - } - w.Header().Set("Content-Type", "application/json") - - if strings.HasSuffix(r.URL.Path, fmt.Sprintf("/clones/%v", cloneId)) && r.Method == "GET" { - w.WriteHeader(http.StatusOK) - var uo composer.CloneStatus_Options - require.NoError(t, uo.FromAWSEC2UploadStatus(composer.AWSEC2UploadStatus{ - Ami: "ami-1", - Region: "us-east-2", - })) - result := composer.CloneStatus{ - Options: uo, - Status: composer.Success, - Type: composer.UploadTypesAws, - } - err := json.NewEncoder(w).Encode(result) - require.NoError(t, err) - } else if strings.HasSuffix(r.URL.Path, fmt.Sprintf("%v/clone", id)) && r.Method == "POST" { - w.WriteHeader(http.StatusCreated) - result := composer.CloneComposeResponse{ - Id: cloneId, - } - err := json.NewEncoder(w).Encode(result) - require.NoError(t, err) - } else { - require.FailNowf(t, "Unexpected request to mocked composer, path: %s", r.URL.Path) - } - })) - defer apiSrv.Close() - - srv := startServer(t, &testServerClientsConf{ComposerURL: apiSrv.URL}, &v1.ServerConfig{ - DistributionsDir: "../../distributions", - }) - defer srv.Shutdown(t) - - err := srv.DB.InsertCompose(ctx, id, "500000", "user500000@test.test", "000000", nil, json.RawMessage(` -{ - "image_requests": [ - { - "image_type": "aws" - } - ] -}`), nil, nil) - require.NoError(t, err) - - cloneReq := v1.AWSEC2Clone{ - Region: "us-east-2", - } - respStatusCode, body := tutils.PostResponseBody(t, srv.URL+fmt.Sprintf("/api/image-builder/v1/composes/%s/clone", id), cloneReq) - require.Equal(t, http.StatusCreated, respStatusCode) - - var cResp v1.CloneResponse - err = json.Unmarshal([]byte(body), &cResp) - require.NoError(t, err) - require.Equal(t, cloneId, cResp.Id) - - var usResp v1.CloneStatusResponse - respStatusCode, body = tutils.GetResponseBody(t, srv.URL+fmt.Sprintf("/api/image-builder/v1/clones/%s", cloneId), &tutils.AuthString0) - - require.Equal(t, http.StatusOK, respStatusCode) - err = json.Unmarshal([]byte(body), &usResp) - require.NoError(t, err) - require.Equal(t, v1.CloneStatusResponseStatusSuccess, usResp.Status) - require.Equal(t, v1.UploadTypesAws, usResp.Type) - require.Equal(t, id, *usResp.ComposeId) - - var awsUS v1.AWSUploadStatus - jsonUO, err := json.Marshal(usResp.Options) - require.NoError(t, err) - err = json.Unmarshal(jsonUO, &awsUS) - require.NoError(t, err) - require.Equal(t, "ami-1", awsUS.Ami) - require.Equal(t, "us-east-2", awsUS.Region) -} - -func TestGetCloneEntryNotFoundResponse(t *testing.T) { - id := uuid.New().String() - srv := startServer(t, &testServerClientsConf{}, nil) - defer srv.Shutdown(t) - - respStatusCode, body := tutils.GetResponseBody(t, srv.URL+fmt.Sprintf("/api/image-builder/v1/clones/%s", - id), &tutils.AuthString0) - require.Equal(t, http.StatusNotFound, respStatusCode) - require.Contains(t, body, "clone not found") -} - func TestValidateSpec(t *testing.T) { spec, err := v1.GetSwagger() require.NoError(t, err)