From cb71f63a0dbb55a57282a838ab874a812b04cbd7 Mon Sep 17 00:00:00 2001 From: Juho Saarinen Date: Sat, 9 Nov 2024 09:56:19 +0200 Subject: [PATCH] feat: allow inclusion of image copy to pipeline Support both codepipeline and pipelines --- API.md | 322 ++++ lambda/go.mod | 4 +- lambda/go.sum | 17 +- lambda/main.go | 173 +- src/ecr-deployment-step.ts | 31 + src/index.ts | 174 +- src/lambda.ts | 120 ++ src/types.ts | 131 ++ test/__snapshots__/index.test.ts.snap | 2299 +++++++++++++++++++++++++ test/index.test.ts | 86 +- 10 files changed, 3124 insertions(+), 233 deletions(-) create mode 100644 src/ecr-deployment-step.ts create mode 100644 src/lambda.ts create mode 100644 src/types.ts create mode 100644 test/__snapshots__/index.test.ts.snap diff --git a/API.md b/API.md index 397cdbd0..a2abd3ae 100644 --- a/API.md +++ b/API.md @@ -128,6 +128,119 @@ The tree node. --- +### ECRDeploymentStep + +#### Initializers + +```typescript +import { ECRDeploymentStep } from 'cdk-ecr-deployment' + +new ECRDeploymentStep(scope: Construct, id: string, props: ECRDeploymentStepProps) +``` + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| scope | constructs.Construct | *No description.* | +| id | string | *No description.* | +| props | ECRDeploymentStepProps | *No description.* | + +--- + +##### `scope`Required + +- *Type:* constructs.Construct + +--- + +##### `id`Required + +- *Type:* string + +--- + +##### `props`Required + +- *Type:* ECRDeploymentStepProps + +--- + +#### Methods + +| **Name** | **Description** | +| --- | --- | +| toString | Returns a string representation of this construct. | + +--- + +##### `toString` + +```typescript +public toString(): string +``` + +Returns a string representation of this construct. + +#### Static Functions + +| **Name** | **Description** | +| --- | --- | +| isConstruct | Checks if `x` is a construct. | + +--- + +##### `isConstruct` + +```typescript +import { ECRDeploymentStep } from 'cdk-ecr-deployment' + +ECRDeploymentStep.isConstruct(x: any) +``` + +Checks if `x` is a construct. + +Use this method instead of `instanceof` to properly detect `Construct` +instances, even when the construct library is symlinked. + +Explanation: in JavaScript, multiple copies of the `constructs` library on +disk are seen as independent, completely different libraries. As a +consequence, the class `Construct` in each copy of the `constructs` library +is seen as a different class, and an instance of one class will not test as +`instanceof` the other class. `npm install` will not create installations +like this, but users may manually symlink construct libraries together or +use a monorepo tool: in those cases, multiple copies of the `constructs` +library can be accidentally installed, and `instanceof` will behave +unpredictably. It is safest to avoid using `instanceof`, and using +this type-testing method instead. + +###### `x`Required + +- *Type:* any + +Any object. + +--- + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| node | constructs.Node | The tree node. | + +--- + +##### `node`Required + +```typescript +public readonly node: Node; +``` + +- *Type:* constructs.Node + +The tree node. + +--- + + ## Structs ### ECRDeploymentProps @@ -309,6 +422,215 @@ Only used if 'vpc' is supplied. --- +### ECRDeploymentStepProps + +#### Initializer + +```typescript +import { ECRDeploymentStepProps } from 'cdk-ecr-deployment' + +const eCRDeploymentStepProps: ECRDeploymentStepProps = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| dest | IImageName | The destination of the docker image. | +| src | IImageName | The source of the docker image. | +| buildImage | string | Image to use to build Golang lambda for custom resource, if download fails or is not wanted. | +| environment | {[ key: string ]: string} | The environment variable to set. | +| lambdaHandler | string | The name of the lambda handler. | +| lambdaRuntime | aws-cdk-lib.aws_lambda.Runtime | The lambda function runtime environment. | +| memoryLimit | number | The amount of memory (in MiB) to allocate to the AWS Lambda function which replicates the files from the CDK bucket to the destination bucket. | +| role | aws-cdk-lib.aws_iam.IRole | Execution role associated with this function. | +| securityGroups | aws-cdk-lib.aws_ec2.SecurityGroup[] | The list of security groups to associate with the Lambda's network interfaces. | +| vpc | aws-cdk-lib.aws_ec2.IVpc | The VPC network to place the deployment lambda handler in. | +| vpcSubnets | aws-cdk-lib.aws_ec2.SubnetSelection | Where in the VPC to place the deployment lambda handler. | +| stage | aws-cdk-lib.aws_codepipeline.IStage | CodePipeline Stage to include lambda to. | +| wave | aws-cdk-lib.pipelines.Wave | Pipelines Wave to include lambda to. | + +--- + +##### `dest`Required + +```typescript +public readonly dest: IImageName; +``` + +- *Type:* IImageName + +The destination of the docker image. + +--- + +##### `src`Required + +```typescript +public readonly src: IImageName; +``` + +- *Type:* IImageName + +The source of the docker image. + +--- + +##### `buildImage`Optional + +```typescript +public readonly buildImage: string; +``` + +- *Type:* string +- *Default:* public.ecr.aws/sam/build-go1.x:latest + +Image to use to build Golang lambda for custom resource, if download fails or is not wanted. + +Might be needed for local build if all images need to come from own registry. + +Note that image should use yum as a package manager and have golang available. + +--- + +##### `environment`Optional + +```typescript +public readonly environment: {[ key: string ]: string}; +``` + +- *Type:* {[ key: string ]: string} + +The environment variable to set. + +--- + +##### `lambdaHandler`Optional + +```typescript +public readonly lambdaHandler: string; +``` + +- *Type:* string +- *Default:* bootstrap + +The name of the lambda handler. + +--- + +##### `lambdaRuntime`Optional + +```typescript +public readonly lambdaRuntime: Runtime; +``` + +- *Type:* aws-cdk-lib.aws_lambda.Runtime +- *Default:* lambda.Runtime.PROVIDED_AL2023 + +The lambda function runtime environment. + +--- + +##### `memoryLimit`Optional + +```typescript +public readonly memoryLimit: number; +``` + +- *Type:* number +- *Default:* 512 + +The amount of memory (in MiB) to allocate to the AWS Lambda function which replicates the files from the CDK bucket to the destination bucket. + +If you are deploying large files, you will need to increase this number +accordingly. + +--- + +##### `role`Optional + +```typescript +public readonly role: IRole; +``` + +- *Type:* aws-cdk-lib.aws_iam.IRole +- *Default:* A role is automatically created + +Execution role associated with this function. + +--- + +##### `securityGroups`Optional + +```typescript +public readonly securityGroups: SecurityGroup[]; +``` + +- *Type:* aws-cdk-lib.aws_ec2.SecurityGroup[] +- *Default:* If the function is placed within a VPC and a security group is not specified, either by this or securityGroup prop, a dedicated security group will be created for this function. + +The list of security groups to associate with the Lambda's network interfaces. + +Only used if 'vpc' is supplied. + +--- + +##### `vpc`Optional + +```typescript +public readonly vpc: IVpc; +``` + +- *Type:* aws-cdk-lib.aws_ec2.IVpc +- *Default:* None + +The VPC network to place the deployment lambda handler in. + +--- + +##### `vpcSubnets`Optional + +```typescript +public readonly vpcSubnets: SubnetSelection; +``` + +- *Type:* aws-cdk-lib.aws_ec2.SubnetSelection +- *Default:* the Vpc default strategy if not specified + +Where in the VPC to place the deployment lambda handler. + +Only used if 'vpc' is supplied. + +--- + +##### `stage`Optional + +```typescript +public readonly stage: IStage; +``` + +- *Type:* aws-cdk-lib.aws_codepipeline.IStage + +CodePipeline Stage to include lambda to. + +If this is set, lambda is invoked in pipeline instead of custom resource. + +--- + +##### `wave`Optional + +```typescript +public readonly wave: Wave; +``` + +- *Type:* aws-cdk-lib.pipelines.Wave + +Pipelines Wave to include lambda to. + +If this is set, lambda is invoked in pipeline instead of custom resource. + +--- + ## Classes ### DockerImageName diff --git a/lambda/go.mod b/lambda/go.mod index 2aaa90b7..285cc203 100644 --- a/lambda/go.mod +++ b/lambda/go.mod @@ -4,8 +4,9 @@ go 1.15 require ( github.com/aws/aws-lambda-go v1.29.0 - github.com/aws/aws-sdk-go-v2 v1.21.0 + github.com/aws/aws-sdk-go-v2 v1.32.4 github.com/aws/aws-sdk-go-v2/config v1.18.37 + github.com/aws/aws-sdk-go-v2/service/codepipeline v1.36.3 github.com/aws/aws-sdk-go-v2/service/ecr v1.17.3 github.com/aws/aws-sdk-go-v2/service/s3 v1.35.0 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.10 @@ -15,5 +16,4 @@ require ( github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 - github.com/tchap/go-patricia v2.3.0+incompatible // indirect ) diff --git a/lambda/go.sum b/lambda/go.sum index 4a4ca50e..249feb8f 100644 --- a/lambda/go.sum +++ b/lambda/go.sum @@ -1097,8 +1097,9 @@ github.com/aws/aws-sdk-go-v2 v1.17.5/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3eP github.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2 v1.18.1/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc= github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M= +github.com/aws/aws-sdk-go-v2 v1.32.4 h1:S13INUiTxgrPueTmrm5DZ+MiAo99zYzHEFh1UNkOxNE= +github.com/aws/aws-sdk-go-v2 v1.32.4/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.3/go.mod h1:gNsR5CaXKmQSSzrmGxmwmct/r+ZBfbxorAuXYsj/M5Y= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno= @@ -1141,8 +1142,9 @@ github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29/go.mod h1:Dip3sIGv48 github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31/go.mod h1:QT0BqUvX1Bh2ABdTGnjqEjvjzrCfIniM9Sc8zn9Yndo= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34/go.mod h1:wZpTEecJe0Btj3IYnDx/VlUzor9wm3fJHyvLpQF0VwY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.23 h1:A2w6m6Tmr+BNXjDsr7M90zkWjsu4JXHwrzPg235STs4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.23/go.mod h1:35EVp9wyeANdujZruvHiQUAo9E3vbhnIO1mTCAxMlY0= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8/go.mod h1:ZIV8GYoC6WLBW5KGs+o4rsc65/ozd+eQ0L31XF5VDwk= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.12/go.mod h1:ckaCVTEdGAxO6KwTGzgskxR1xM+iJW4lxMyDFVda2Fc= @@ -1152,8 +1154,9 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23/go.mod h1:mr6c4cHC+S/ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25/go.mod h1:zBHOPwhBc3FlQjQJE/D3IfPWiWaQmT06Vq9aNukDo0k= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28/go.mod h1:7VRpKQQedkfIEXb4k52I7swUnZP0wohVajJMRn3vsUw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.23 h1:pgYW9FCabt2M25MoHYCfMrVY2ghiiBKYWUVXfwZs+sU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.23/go.mod h1:c48kLgzO19wAu3CPkDWC28JbaJ+hfQlsdl7I2+oqIbk= github.com/aws/aws-sdk-go-v2/internal/ini v1.1.1/go.mod h1:Zy8smImhTdOETZqfyn01iNOe0CNggVbPjCajyaz6Gvg= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.19/go.mod h1:cVHo8KTuHjShb9V8/VjH3S/8+xPu16qx8fdGwmotJhE= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28/go.mod h1:yRZVr/iT0AqyHeep00SZ4YfBAKojXz08w3XMBscdi0c= @@ -1169,6 +1172,8 @@ github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.23/go.mod h1:uIiFgURZbACBEQJfqTZP github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26 h1:wscW+pnn3J1OYnanMnza5ZVYXLX4cKk5rAvUAl4Qu+c= github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26/go.mod h1:MtYiox5gvyB+OyP0Mr0Sm/yzbEAIPL9eijj/ouHAPw0= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= +github.com/aws/aws-sdk-go-v2/service/codepipeline v1.36.3 h1:m681OIwRDxyqTjRZR7uR9ZqCyhpXVnLu0tm8ytbZiCY= +github.com/aws/aws-sdk-go-v2/service/codepipeline v1.36.3/go.mod h1:smc6EfxYZ9b9xD7ll/jcPimS7BuFmwnlsFv/zyUgSj0= github.com/aws/aws-sdk-go-v2/service/ecr v1.17.3 h1:izPPh0CPwbJMF+KkiOG30+Ptm90VXw15CI4Ipj5cP8M= github.com/aws/aws-sdk-go-v2/service/ecr v1.17.3/go.mod h1:Yf1qbCbx9ds6+R5R7rXj5c04FSRjpTYEewce6nG9TIc= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.2.1/go.mod h1:v33JQ57i2nekYTA70Mb+O18KeH4KqhdqxTJZNK1zdRE= @@ -1244,8 +1249,9 @@ github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J github.com/aws/smithy-go v1.12.1/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aws/smithy-go v1.13.1/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= -github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ= github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= +github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beeker1121/goque v1.0.3-0.20191103205551-d618510128af/go.mod h1:84CWnaDz4g1tEVnFLnuBigmGK15oPohy0RfvSN8d4eg= @@ -3341,9 +3347,8 @@ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtse github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= +github.com/tchap/go-patricia v2.2.6+incompatible h1:JvoDL7JSoIP2HDE8AbDH3zC8QBPxmzYe32HHy5yQ+Ck= github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= -github.com/tchap/go-patricia v2.3.0+incompatible h1:GkY4dP3cEfEASBPPkWd+AmjYxhmDkqO9/zg7R0lSQRs= -github.com/tchap/go-patricia v2.3.0+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/tedsuo/ifrit v0.0.0-20180802180643-bea94bb476cc/go.mod h1:eyZnKCc955uh98WQvzOm0dgAeLnf2O0Rz0LPoC5ze+0= diff --git a/lambda/main.go b/lambda/main.go index 639d52c6..44b74e9a 100644 --- a/lambda/main.go +++ b/lambda/main.go @@ -5,6 +5,7 @@ package main import ( "context" + "encoding/json" "fmt" "log" "os" @@ -15,7 +16,12 @@ import ( "github.com/sirupsen/logrus" "github.com/aws/aws-lambda-go/cfn" + "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/codepipeline" + "github.com/aws/aws-sdk-go-v2/service/codepipeline/types" _ "cdk-ecr-deployment-handler/s3" // Install s3 transport plugin ) @@ -45,80 +51,129 @@ func handler(ctx context.Context, event cfn.Event) (physicalResourceID string, d return physicalResourceID, data, nil } if event.RequestType == cfn.RequestCreate || event.RequestType == cfn.RequestUpdate { - srcImage, err := getStrProps(event.ResourceProperties, SRC_IMAGE) + err := copyImage(event.ResourceProperties) if err != nil { - return physicalResourceID, data, err - } - destImage, err := getStrProps(event.ResourceProperties, DEST_IMAGE) - if err != nil { - return physicalResourceID, data, err - } - srcCreds, err := getStrPropsDefault(event.ResourceProperties, SRC_CREDS, "") - if err != nil { - return physicalResourceID, data, err - } - destCreds, err := getStrPropsDefault(event.ResourceProperties, DEST_CREDS, "") - if err != nil { - return physicalResourceID, data, err + // log.Printf("Copy image failed: %v", err.Error()) + // return physicalResourceID, data, nil + return physicalResourceID, data, fmt.Errorf("copy image failed: %s", err.Error()) } + } - srcCreds, err = parseCreds(srcCreds) - if err != nil { - return physicalResourceID, data, err + return physicalResourceID, data, nil +} + +// Codepipeline has to get also information about execution back to known if run succeeded or failed +func codePipelineHandler(ctx context.Context, event events.CodePipelineJobEvent) { + cfg, err := config.LoadDefaultConfig( + context.Background(), + ) + if err != nil { + fmt.Errorf("api client configuration error: %v", err.Error()) + } + c := codepipeline.NewFromConfig(cfg) + userParameters := make(map[string]interface{}) + json.Unmarshal([]byte(event.CodePipelineJob.Data.ActionConfiguration.Configuration.UserParameters), &userParameters) + err = copyImage(userParameters) + if err != nil { + log.Printf("copy image failed: %s", err.Error()) + results := codepipeline.PutJobFailureResultInput{ + JobId: &event.CodePipelineJob.ID, + FailureDetails: &types.FailureDetails{ + Message: aws.String(err.Error()), + Type: types.FailureTypeJobFailed, + }, } - destCreds, err = parseCreds(destCreds) - if err != nil { - return physicalResourceID, data, err + _, updateErr := c.PutJobFailureResult(context.TODO(), &results) + if updateErr != nil { + log.Printf("putting job failure results failed: %s", err.Error()) } + return + } - log.Printf("SrcImage: %v DestImage: %v", srcImage, destImage) + results := codepipeline.PutJobSuccessResultInput{ + JobId: &event.CodePipelineJob.ID, + ExecutionDetails: &types.ExecutionDetails{ + Summary: aws.String(fmt.Sprintf("Copied image successfully")), + }, + } + _, err = c.PutJobSuccessResult(context.TODO(), &results) + if err != nil { + log.Printf("putting job success results failed: %s", err.Error()) + } +} - srcRef, err := alltransports.ParseImageName(srcImage) - if err != nil { - return physicalResourceID, data, err - } - destRef, err := alltransports.ParseImageName(destImage) - if err != nil { - return physicalResourceID, data, err - } +func copyImage(properties map[string]interface{}) error { + srcImage, err := getStrProps(properties, SRC_IMAGE) + if err != nil { + return err + } + destImage, err := getStrProps(properties, DEST_IMAGE) + if err != nil { + return err + } + srcCreds, err := getStrPropsDefault(properties, SRC_CREDS, "") + if err != nil { + return err + } + destCreds, err := getStrPropsDefault(properties, DEST_CREDS, "") + if err != nil { + return err + } - srcOpts := NewImageOpts(srcImage) - srcOpts.SetCreds(srcCreds) - srcCtx, err := srcOpts.NewSystemContext() - if err != nil { - return physicalResourceID, data, err - } - destOpts := NewImageOpts(destImage) - destOpts.SetCreds(destCreds) - destCtx, err := destOpts.NewSystemContext() - if err != nil { - return physicalResourceID, data, err - } + srcCreds, err = parseCreds(srcCreds) + if err != nil { + return err + } + destCreds, err = parseCreds(destCreds) + if err != nil { + return err + } - ctx, cancel := newTimeoutContext() - defer cancel() - policyContext, err := newPolicyContext() - if err != nil { - return physicalResourceID, data, err - } - defer policyContext.Destroy() + log.Printf("SrcImage: %v DestImage: %v", srcImage, destImage) - _, err = copy.Image(ctx, policyContext, destRef, srcRef, ©.Options{ - ReportWriter: os.Stdout, - DestinationCtx: destCtx, - SourceCtx: srcCtx, - }) - if err != nil { - // log.Printf("Copy image failed: %v", err.Error()) - // return physicalResourceID, data, nil - return physicalResourceID, data, fmt.Errorf("copy image failed: %s", err.Error()) - } + srcRef, err := alltransports.ParseImageName(srcImage) + if err != nil { + return err + } + destRef, err := alltransports.ParseImageName(destImage) + if err != nil { + return err } - return physicalResourceID, data, nil + srcOpts := NewImageOpts(srcImage) + srcOpts.SetCreds(srcCreds) + srcCtx, err := srcOpts.NewSystemContext() + if err != nil { + return err + } + destOpts := NewImageOpts(destImage) + destOpts.SetCreds(destCreds) + destCtx, err := destOpts.NewSystemContext() + if err != nil { + return err + } + + ctx, cancel := newTimeoutContext() + defer cancel() + policyContext, err := newPolicyContext() + if err != nil { + return err + } + defer policyContext.Destroy() + + _, err = copy.Image(ctx, policyContext, destRef, srcRef, ©.Options{ + ReportWriter: os.Stdout, + DestinationCtx: destCtx, + SourceCtx: srcCtx, + }) + return err } func main() { + invoker := os.Getenv("INVOKER") + if invoker == "CODEPIPELINE" { + lambda.Start(codePipelineHandler) + } lambda.Start(cfn.LambdaWrap(handler)) } diff --git a/src/ecr-deployment-step.ts b/src/ecr-deployment-step.ts new file mode 100644 index 00000000..fcd8b55a --- /dev/null +++ b/src/ecr-deployment-step.ts @@ -0,0 +1,31 @@ +import { aws_lambda as lambda } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import { addFunctionPermissions, getFunctionProps, LambdaInvokeStep } from './lambda'; +import { ECRDeploymentStepProps } from './types'; + +export class ECRDeploymentStep extends Construct { + + private handler: lambda.Function; + + constructor(scope: Construct, id: string, props: ECRDeploymentStepProps) { + super(scope, id); + + this.handler = new lambda.Function(this, 'ImageCopyHandler', { + ...getFunctionProps(props), + environment: { + ...props.environment, + INVOKER: 'CODEPIPELINE', + }, + }); + + const handlerRole = this.handler.role; + if (!handlerRole) { throw new Error('lambda.Function should have created a Role'); } + + addFunctionPermissions(handlerRole); + + const factory = new LambdaInvokeStep(this.handler, props, 'ImageCopy'); + + props.stage?.addAction(factory.getAction()); + props.wave?.addPost(factory); + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 9ab65116..9ba4b2dc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,133 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 -import * as child_process from 'child_process'; -import * as path from 'path'; -import { aws_ec2 as ec2, aws_iam as iam, aws_lambda as lambda, Duration, CustomResource, Token } from 'aws-cdk-lib'; +import { aws_lambda as lambda, CustomResource, Token } from 'aws-cdk-lib'; import { PolicyStatement, AddToPrincipalPolicyResult } from 'aws-cdk-lib/aws-iam'; -import { RuntimeFamily } from 'aws-cdk-lib/aws-lambda'; import { Construct } from 'constructs'; -import { shouldUsePrebuiltLambda } from './config'; - -export interface ECRDeploymentProps { - - /** - * Image to use to build Golang lambda for custom resource, if download fails or is not wanted. - * - * Might be needed for local build if all images need to come from own registry. - * - * Note that image should use yum as a package manager and have golang available. - * - * @default - public.ecr.aws/sam/build-go1.x:latest - */ - readonly buildImage?: string; - /** - * The source of the docker image. - */ - readonly src: IImageName; - - /** - * The destination of the docker image. - */ - readonly dest: IImageName; - - /** - * The amount of memory (in MiB) to allocate to the AWS Lambda function which - * replicates the files from the CDK bucket to the destination bucket. - * - * If you are deploying large files, you will need to increase this number - * accordingly. - * - * @default - 512 - */ - readonly memoryLimit?: number; - - /** - * Execution role associated with this function - * - * @default - A role is automatically created - */ - readonly role?: iam.IRole; - - /** - * The VPC network to place the deployment lambda handler in. - * - * @default - None - */ - readonly vpc?: ec2.IVpc; - - /** - * Where in the VPC to place the deployment lambda handler. - * Only used if 'vpc' is supplied. - * - * @default - the Vpc default strategy if not specified - */ - readonly vpcSubnets?: ec2.SubnetSelection; - - /** - * The list of security groups to associate with the Lambda's network interfaces. - * - * Only used if 'vpc' is supplied. - * - * @default - If the function is placed within a VPC and a security group is - * not specified, either by this or securityGroup prop, a dedicated security - * group will be created for this function. - */ - readonly securityGroups?: ec2.SecurityGroup[]; - - /** - * The lambda function runtime environment. - * - * @default - lambda.Runtime.PROVIDED_AL2023 - */ - readonly lambdaRuntime?: lambda.Runtime; - - /** - * The name of the lambda handler. - * - * @default - bootstrap - */ - readonly lambdaHandler?: string; - - /** - * The environment variable to set - */ - readonly environment?: { [key: string]: string }; -} - -export interface IImageName { - /** - * The uri of the docker image. - * - * The uri spec follows https://github.com/containers/skopeo - */ - readonly uri: string; - - /** - * The credentials of the docker image. Format `user:password` or `AWS Secrets Manager secret arn` or `AWS Secrets Manager secret name` - */ - creds?: string; -} - -function getCode(buildImage: string): lambda.AssetCode { - if (shouldUsePrebuiltLambda()) { - try { - const installScript = path.join(__dirname, '../lambda/install.js'); - const prebuiltPath = path.join(__dirname, '../lambda/out'); - child_process.execFileSync(process.argv0, [installScript, prebuiltPath]); - - return lambda.Code.fromAsset(prebuiltPath); - } catch (err) { - console.warn(`Can not get prebuilt lambda: ${err}`); - } - } - - return lambda.Code.fromDockerBuild(path.join(__dirname, '../lambda'), { - buildArgs: { - buildImage, - }, - }); -} +import { addFunctionPermissions, getFunctionProps } from './lambda'; +import { ECRDeploymentProps, IImageName } from './types'; +export * from './ecr-deployment-step'; +export * from './types'; export class DockerImageName implements IImageName { public constructor(private name: string, public creds?: string) { } @@ -151,53 +31,17 @@ export class ECRDeployment extends Construct { constructor(scope: Construct, id: string, props: ECRDeploymentProps) { super(scope, id); - const memoryLimit = props.memoryLimit ?? 512; + const functionProps = getFunctionProps(props); this.handler = new lambda.SingletonFunction(this, 'CustomResourceHandler', { - uuid: this.renderSingletonUuid(memoryLimit), - code: getCode(props.buildImage ?? 'public.ecr.aws/docker/library/golang:1'), - runtime: props.lambdaRuntime ?? new lambda.Runtime('provided.al2023', RuntimeFamily.OTHER), // not using Runtime.PROVIDED_AL2023 to support older CDK versions (< 2.105.0) - handler: props.lambdaHandler ?? 'bootstrap', - environment: props.environment, + uuid: this.renderSingletonUuid(functionProps.memorySize), lambdaPurpose: 'Custom::CDKECRDeployment', - timeout: Duration.minutes(15), - role: props.role, - memorySize: memoryLimit, - vpc: props.vpc, - vpcSubnets: props.vpcSubnets, - securityGroups: props.securityGroups, + ...functionProps, }); const handlerRole = this.handler.role; if (!handlerRole) { throw new Error('lambda.SingletonFunction should have created a Role'); } - handlerRole.addToPrincipalPolicy( - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: [ - 'ecr:GetAuthorizationToken', - 'ecr:BatchCheckLayerAvailability', - 'ecr:GetDownloadUrlForLayer', - 'ecr:GetRepositoryPolicy', - 'ecr:DescribeRepositories', - 'ecr:ListImages', - 'ecr:DescribeImages', - 'ecr:BatchGetImage', - 'ecr:ListTagsForResource', - 'ecr:DescribeImageScanFindings', - 'ecr:InitiateLayerUpload', - 'ecr:UploadLayerPart', - 'ecr:CompleteLayerUpload', - 'ecr:PutImage', - ], - resources: ['*'], - })); - handlerRole.addToPrincipalPolicy(new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: [ - 's3:GetObject', - ], - resources: ['*'], - })); + addFunctionPermissions(handlerRole); new CustomResource(this, 'CustomResource', { serviceToken: this.handler.functionArn, diff --git a/src/lambda.ts b/src/lambda.ts new file mode 100644 index 00000000..9a7e0249 --- /dev/null +++ b/src/lambda.ts @@ -0,0 +1,120 @@ +import * as child_process from 'child_process'; +import * as path from 'path'; +import { + aws_codepipeline as codepipeline, + aws_codepipeline_actions as codepipeline_actions, + Duration, + aws_lambda as lambda, + pipelines, + aws_iam as iam, +} from 'aws-cdk-lib'; +import { FunctionProps, RuntimeFamily } from 'aws-cdk-lib/aws-lambda'; +import { shouldUsePrebuiltLambda } from './config'; +import { ECRDeploymentProps } from './types'; + +export class LambdaInvokeStep extends pipelines.Step implements pipelines.ICodePipelineActionFactory { + constructor( + private readonly handler: lambda.IFunction, + private readonly props: ECRDeploymentProps, + private readonly lambdaPurpose: string, + ) { + super('LambdaInvokeStep'); + + // This is necessary if your step accepts parametres, like environment variables, + // that may contain outputs from other steps. It doesn't matter what the + // structure is, as long as it contains the values that may contain outputs. + this.discoverReferencedOutputs({ + env: { /* ... */ }, + }); + } + + public produceAction(stage: codepipeline.IStage, _options: pipelines.ProduceActionOptions): pipelines.CodePipelineActionFactoryResult { + + + // This is where you control what type of Action gets added to the + // CodePipeline + stage.addAction(this.getAction()); + + return { runOrdersConsumed: 1 }; + } + + public getAction() { + return new codepipeline_actions.LambdaInvokeAction({ + lambda: this.handler, + actionName: this.lambdaPurpose, + userParameters: { + SrcImage: this.props.src.uri, + SrcCreds: this.props.src.creds, + DestImage: this.props.dest.uri, + DestCreds: this.props.dest.creds, + }, + }); + } +} + +function getCode(buildImage: string): lambda.AssetCode { + if (shouldUsePrebuiltLambda()) { + try { + const installScript = path.join(__dirname, '../lambda/install.js'); + const prebuiltPath = path.join(__dirname, '../lambda/out'); + child_process.execFileSync(process.argv0, [installScript, prebuiltPath]); + + return lambda.Code.fromAsset(prebuiltPath); + } catch (err) { + console.warn(`Can not get prebuilt lambda: ${err}`); + } + } + + return lambda.Code.fromDockerBuild(path.join(__dirname, '../lambda'), { + buildArgs: { + buildImage, + }, + }); +} + +export function getFunctionProps(props: ECRDeploymentProps): FunctionProps { + const memoryLimit = props.memoryLimit ?? 512; + return { + code: getCode(props.buildImage ?? 'public.ecr.aws/docker/library/golang:1'), + runtime: props.lambdaRuntime ?? new lambda.Runtime('provided.al2023', RuntimeFamily.OTHER), // not using Runtime.PROVIDED_AL2023 to support older CDK versions (< 2.105.0) + handler: props.lambdaHandler ?? 'bootstrap', + environment: props.environment, + timeout: Duration.minutes(15), + role: props.role, + memorySize: memoryLimit, + vpc: props.vpc, + vpcSubnets: props.vpcSubnets, + securityGroups: props.securityGroups, + }; +} + +export function addFunctionPermissions(role: iam.IRole): void { + role.addToPrincipalPolicy( + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + 'ecr:GetAuthorizationToken', + 'ecr:BatchCheckLayerAvailability', + 'ecr:GetDownloadUrlForLayer', + 'ecr:GetRepositoryPolicy', + 'ecr:DescribeRepositories', + 'ecr:ListImages', + 'ecr:DescribeImages', + 'ecr:BatchGetImage', + 'ecr:ListTagsForResource', + 'ecr:DescribeImageScanFindings', + 'ecr:InitiateLayerUpload', + 'ecr:UploadLayerPart', + 'ecr:CompleteLayerUpload', + 'ecr:PutImage', + ], + resources: ['*'], + })); + role.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + 's3:GetObject', + ], + resources: ['*'], + })); +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..1fdcf95a --- /dev/null +++ b/src/types.ts @@ -0,0 +1,131 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { aws_ec2 as ec2, aws_iam as iam, aws_lambda as lambda, aws_codepipeline as codepipeline, pipelines } from 'aws-cdk-lib'; + +export interface ECRDeploymentProps { + + /** + * Image to use to build Golang lambda for custom resource, if download fails or is not wanted. + * + * Might be needed for local build if all images need to come from own registry. + * + * Note that image should use yum as a package manager and have golang available. + * + * @default - public.ecr.aws/sam/build-go1.x:latest + */ + readonly buildImage?: string; + /** + * The source of the docker image. + */ + readonly src: IImageName; + + /** + * The destination of the docker image. + */ + readonly dest: IImageName; + + /** + * The amount of memory (in MiB) to allocate to the AWS Lambda function which + * replicates the files from the CDK bucket to the destination bucket. + * + * If you are deploying large files, you will need to increase this number + * accordingly. + * + * @default - 512 + */ + readonly memoryLimit?: number; + + /** + * Execution role associated with this function + * + * @default - A role is automatically created + */ + readonly role?: iam.IRole; + + /** + * The VPC network to place the deployment lambda handler in. + * + * @default - None + */ + readonly vpc?: ec2.IVpc; + + /** + * Where in the VPC to place the deployment lambda handler. + * Only used if 'vpc' is supplied. + * + * @default - the Vpc default strategy if not specified + */ + readonly vpcSubnets?: ec2.SubnetSelection; + + /** + * The list of security groups to associate with the Lambda's network interfaces. + * + * Only used if 'vpc' is supplied. + * + * @default - If the function is placed within a VPC and a security group is + * not specified, either by this or securityGroup prop, a dedicated security + * group will be created for this function. + */ + readonly securityGroups?: ec2.SecurityGroup[]; + + /** + * The lambda function runtime environment. + * + * @default - lambda.Runtime.PROVIDED_AL2023 + */ + readonly lambdaRuntime?: lambda.Runtime; + + /** + * The name of the lambda handler. + * + * @default - bootstrap + */ + readonly lambdaHandler?: string; + + /** + * The environment variable to set + */ + readonly environment?: { [key: string]: string }; +} + +export interface IImageName { + /** + * The uri of the docker image. + * + * The uri spec follows https://github.com/containers/skopeo + */ + readonly uri: string; + + /** + * The credentials of the docker image. Format `user:password` or `AWS Secrets Manager secret arn` or `AWS Secrets Manager secret name` + */ + creds?: string; +} + +export class DockerImageName implements IImageName { + public constructor(private name: string, public creds?: string) { } + public get uri(): string { return `docker://${this.name}`; } +} + +export class S3ArchiveName implements IImageName { + private name: string; + public constructor(p: string, ref?: string, public creds?: string) { + this.name = p; + if (ref) { + this.name += ':' + ref; + } + } + public get uri(): string { return `s3://${this.name}`; } +} + +export interface ECRDeploymentStepProps extends ECRDeploymentProps { + /** + * CodePipeline Stage to include lambda to. If this is set, lambda is invoked in pipeline instead of custom resource. + */ + readonly stage?: codepipeline.IStage; + /** + * Pipelines Wave to include lambda to. If this is set, lambda is invoked in pipeline instead of custom resource. + */ + readonly wave?: pipelines.Wave; +} \ No newline at end of file diff --git a/test/__snapshots__/index.test.ts.snap b/test/__snapshots__/index.test.ts.snap new file mode 100644 index 00000000..4152ba77 --- /dev/null +++ b/test/__snapshots__/index.test.ts.snap @@ -0,0 +1,2299 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`lambda in codepipeline matches snapshot 1`] = ` +Object { + "Parameters": Object { + "BootstrapVersion": Object { + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]", + "Type": "AWS::SSM::Parameter::Value", + }, + }, + "Resources": Object { + "ImageCopyImageCopyHandlerFBE2CA9D": Object { + "DependsOn": Array [ + "ImageCopyImageCopyHandlerServiceRoleDefaultPolicyD42D3CD6", + "ImageCopyImageCopyHandlerServiceRoleDB5F37CC", + ], + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", + }, + "S3Key": "dbd7959e3abb97721dfe7f2ae14a4fc2849228b919d3cfdfbfc529026a06d193.zip", + }, + "Environment": Object { + "Variables": Object { + "INVOKER": "CODEPIPELINE", + }, + }, + "Handler": "bootstrap", + "MemorySize": 512, + "Role": Object { + "Fn::GetAtt": Array [ + "ImageCopyImageCopyHandlerServiceRoleDB5F37CC", + "Arn", + ], + }, + "Runtime": "provided.al2023", + "Timeout": 900, + }, + "Type": "AWS::Lambda::Function", + }, + "ImageCopyImageCopyHandlerServiceRoleDB5F37CC": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "ImageCopyImageCopyHandlerServiceRoleDefaultPolicyD42D3CD6": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:GetRepositoryPolicy", + "ecr:DescribeRepositories", + "ecr:ListImages", + "ecr:DescribeImages", + "ecr:BatchGetImage", + "ecr:ListTagsForResource", + "ecr:DescribeImageScanFindings", + "ecr:InitiateLayerUpload", + "ecr:UploadLayerPart", + "ecr:CompleteLayerUpload", + "ecr:PutImage", + ], + "Effect": "Allow", + "Resource": "*", + }, + Object { + "Action": "s3:GetObject", + "Effect": "Allow", + "Resource": "*", + }, + Object { + "Action": Array [ + "codepipeline:PutJobSuccessResult", + "codepipeline:PutJobFailureResult", + ], + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "ImageCopyImageCopyHandlerServiceRoleDefaultPolicyD42D3CD6", + "Roles": Array [ + Object { + "Ref": "ImageCopyImageCopyHandlerServiceRoleDB5F37CC", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "NginxRepo42B51311": Object { + "DeletionPolicy": "Delete", + "Properties": Object { + "RepositoryName": "nginx", + }, + "Type": "AWS::ECR::Repository", + "UpdateReplacePolicy": "Delete", + }, + "PipelineWithLambdaArtifactsBucket47E296C2": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "BucketEncryption": Object { + "ServerSideEncryptionConfiguration": Array [ + Object { + "ServerSideEncryptionByDefault": Object { + "KMSMasterKeyID": Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaArtifactsBucketEncryptionKey18F9C71E", + "Arn", + ], + }, + "SSEAlgorithm": "aws:kms", + }, + }, + ], + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "PipelineWithLambdaArtifactsBucketEncryptionKey18F9C71E": Object { + "DeletionPolicy": "Delete", + "Properties": Object { + "KeyPolicy": Object { + "Statement": Array [ + Object { + "Action": "kms:*", + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::KMS::Key", + "UpdateReplacePolicy": "Delete", + }, + "PipelineWithLambdaArtifactsBucketEncryptionKeyAliasD027891B": Object { + "DeletionPolicy": "Delete", + "Properties": Object { + "AliasName": "alias/codepipeline-pipelinewithlambda", + "TargetKeyId": Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaArtifactsBucketEncryptionKey18F9C71E", + "Arn", + ], + }, + }, + "Type": "AWS::KMS::Alias", + "UpdateReplacePolicy": "Delete", + }, + "PipelineWithLambdaArtifactsBucketPolicy8219A052": Object { + "Properties": Object { + "Bucket": Object { + "Ref": "PipelineWithLambdaArtifactsBucket47E296C2", + }, + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "s3:*", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": Object { + "AWS": "*", + }, + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaArtifactsBucket47E296C2", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaArtifactsBucket47E296C2", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::S3::BucketPolicy", + }, + "PipelineWithLambdaCopyImageImageCopyCodePipelineActionRoleB102070F": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "PipelineWithLambdaCopyImageImageCopyCodePipelineActionRoleDefaultPolicy27B08A0A": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "lambda:ListFunctions", + "Effect": "Allow", + "Resource": "*", + }, + Object { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "ImageCopyImageCopyHandlerFBE2CA9D", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "ImageCopyImageCopyHandlerFBE2CA9D", + "Arn", + ], + }, + ":*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "PipelineWithLambdaCopyImageImageCopyCodePipelineActionRoleDefaultPolicy27B08A0A", + "Roles": Array [ + Object { + "Ref": "PipelineWithLambdaCopyImageImageCopyCodePipelineActionRoleB102070F", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "PipelineWithLambdaEventsRole9045137E": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "events.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "PipelineWithLambdaEventsRoleDefaultPolicyDFA38DEA": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "codepipeline:StartPipelineExecution", + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":codepipeline:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "PipelineWithLambdaF3D188C7", + }, + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "PipelineWithLambdaEventsRoleDefaultPolicyDFA38DEA", + "Roles": Array [ + Object { + "Ref": "PipelineWithLambdaEventsRole9045137E", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "PipelineWithLambdaF3D188C7": Object { + "DependsOn": Array [ + "PipelineWithLambdaRoleDefaultPolicy93F71456", + "PipelineWithLambdaRole23C1246E", + ], + "Properties": Object { + "ArtifactStore": Object { + "EncryptionKey": Object { + "Id": Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaArtifactsBucketEncryptionKey18F9C71E", + "Arn", + ], + }, + "Type": "KMS", + }, + "Location": Object { + "Ref": "PipelineWithLambdaArtifactsBucket47E296C2", + }, + "Type": "S3", + }, + "RoleArn": Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaRole23C1246E", + "Arn", + ], + }, + "Stages": Array [ + Object { + "Actions": Array [ + Object { + "ActionTypeId": Object { + "Category": "Source", + "Owner": "AWS", + "Provider": "CodeCommit", + "Version": "1", + }, + "Configuration": Object { + "BranchName": "master", + "PollForSourceChanges": false, + "RepositoryName": Object { + "Fn::GetAtt": Array [ + "Repo02AC86CF", + "Name", + ], + }, + }, + "Name": "Source", + "OutputArtifacts": Array [ + Object { + "Name": "Artifact_Source_Source", + }, + ], + "RoleArn": Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaSourceCodePipelineActionRole8A892282", + "Arn", + ], + }, + "RunOrder": 1, + }, + ], + "Name": "Source", + }, + Object { + "Actions": Array [ + Object { + "ActionTypeId": Object { + "Category": "Invoke", + "Owner": "AWS", + "Provider": "Lambda", + "Version": "1", + }, + "Configuration": Object { + "FunctionName": Object { + "Ref": "ImageCopyImageCopyHandlerFBE2CA9D", + }, + "UserParameters": Object { + "Fn::Join": Array [ + "", + Array [ + "{\\"SrcImage\\":\\"docker://", + Object { + "Fn::Select": Array [ + 4, + Object { + "Fn::Split": Array [ + ":", + Object { + "Fn::GetAtt": Array [ + "NginxRepo42B51311", + "Arn", + ], + }, + ], + }, + ], + }, + ".dkr.ecr.", + Object { + "Fn::Select": Array [ + 3, + Object { + "Fn::Split": Array [ + ":", + Object { + "Fn::GetAtt": Array [ + "NginxRepo42B51311", + "Arn", + ], + }, + ], + }, + ], + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "NginxRepo42B51311", + }, + ":stable\\",\\"DestImage\\":\\"docker://", + Object { + "Fn::Select": Array [ + 4, + Object { + "Fn::Split": Array [ + ":", + Object { + "Fn::GetAtt": Array [ + "NginxRepo42B51311", + "Arn", + ], + }, + ], + }, + ], + }, + ".dkr.ecr.", + Object { + "Fn::Select": Array [ + 3, + Object { + "Fn::Split": Array [ + ":", + Object { + "Fn::GetAtt": Array [ + "NginxRepo42B51311", + "Arn", + ], + }, + ], + }, + ], + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "NginxRepo42B51311", + }, + ":latest\\"}", + ], + ], + }, + }, + "Name": "ImageCopy", + "RoleArn": Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaCopyImageImageCopyCodePipelineActionRoleB102070F", + "Arn", + ], + }, + "RunOrder": 1, + }, + ], + "Name": "CopyImage", + }, + ], + }, + "Type": "AWS::CodePipeline::Pipeline", + }, + "PipelineWithLambdaRole23C1246E": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "codepipeline.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "PipelineWithLambdaRoleDefaultPolicy93F71456": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaArtifactsBucket47E296C2", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaArtifactsBucket47E296C2", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaArtifactsBucketEncryptionKey18F9C71E", + "Arn", + ], + }, + }, + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaSourceCodePipelineActionRole8A892282", + "Arn", + ], + }, + }, + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaCopyImageImageCopyCodePipelineActionRoleB102070F", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "PipelineWithLambdaRoleDefaultPolicy93F71456", + "Roles": Array [ + Object { + "Ref": "PipelineWithLambdaRole23C1246E", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "PipelineWithLambdaSourceCodePipelineActionRole8A892282": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "PipelineWithLambdaSourceCodePipelineActionRoleDefaultPolicy66644772": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaArtifactsBucket47E296C2", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaArtifactsBucket47E296C2", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaArtifactsBucketEncryptionKey18F9C71E", + "Arn", + ], + }, + }, + Object { + "Action": Array [ + "codecommit:GetBranch", + "codecommit:GetCommit", + "codecommit:UploadArchive", + "codecommit:GetUploadArchiveStatus", + "codecommit:CancelUploadArchive", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "Repo02AC86CF", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "PipelineWithLambdaSourceCodePipelineActionRoleDefaultPolicy66644772", + "Roles": Array [ + Object { + "Ref": "PipelineWithLambdaSourceCodePipelineActionRole8A892282", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "Repo02AC86CF": Object { + "Properties": Object { + "RepositoryName": "test-repo", + }, + "Type": "AWS::CodeCommit::Repository", + }, + "RepoPipelineWithLambdaEventRuleDE3CEAA4": Object { + "Properties": Object { + "EventPattern": Object { + "detail": Object { + "event": Array [ + "referenceCreated", + "referenceUpdated", + ], + "referenceName": Array [ + "master", + ], + }, + "detail-type": Array [ + "CodeCommit Repository State Change", + ], + "resources": Array [ + Object { + "Fn::GetAtt": Array [ + "Repo02AC86CF", + "Arn", + ], + }, + ], + "source": Array [ + "aws.codecommit", + ], + }, + "State": "ENABLED", + "Targets": Array [ + Object { + "Arn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":codepipeline:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "PipelineWithLambdaF3D188C7", + }, + ], + ], + }, + "Id": "Target0", + "RoleArn": Object { + "Fn::GetAtt": Array [ + "PipelineWithLambdaEventsRole9045137E", + "Arn", + ], + }, + }, + ], + }, + "Type": "AWS::Events::Rule", + }, + }, + "Rules": Object { + "CheckBootstrapVersion": Object { + "Assertions": Array [ + Object { + "Assert": Object { + "Fn::Not": Array [ + Object { + "Fn::Contains": Array [ + Array [ + "1", + "2", + "3", + "4", + "5", + ], + Object { + "Ref": "BootstrapVersion", + }, + ], + }, + ], + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.", + }, + ], + }, + }, +} +`; + +exports[`lambda in pipelines pipeline matches snapshot 1`] = ` +Object { + "Parameters": Object { + "BootstrapVersion": Object { + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]", + "Type": "AWS::SSM::Parameter::Value", + }, + }, + "Resources": Object { + "ImageCopyImageCopyHandlerFBE2CA9D": Object { + "DependsOn": Array [ + "ImageCopyImageCopyHandlerServiceRoleDefaultPolicyD42D3CD6", + "ImageCopyImageCopyHandlerServiceRoleDB5F37CC", + ], + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", + }, + "S3Key": "dbd7959e3abb97721dfe7f2ae14a4fc2849228b919d3cfdfbfc529026a06d193.zip", + }, + "Environment": Object { + "Variables": Object { + "INVOKER": "CODEPIPELINE", + }, + }, + "Handler": "bootstrap", + "MemorySize": 512, + "Role": Object { + "Fn::GetAtt": Array [ + "ImageCopyImageCopyHandlerServiceRoleDB5F37CC", + "Arn", + ], + }, + "Runtime": "provided.al2023", + "Timeout": 900, + }, + "Type": "AWS::Lambda::Function", + }, + "ImageCopyImageCopyHandlerServiceRoleDB5F37CC": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "ImageCopyImageCopyHandlerServiceRoleDefaultPolicyD42D3CD6": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:GetRepositoryPolicy", + "ecr:DescribeRepositories", + "ecr:ListImages", + "ecr:DescribeImages", + "ecr:BatchGetImage", + "ecr:ListTagsForResource", + "ecr:DescribeImageScanFindings", + "ecr:InitiateLayerUpload", + "ecr:UploadLayerPart", + "ecr:CompleteLayerUpload", + "ecr:PutImage", + ], + "Effect": "Allow", + "Resource": "*", + }, + Object { + "Action": "s3:GetObject", + "Effect": "Allow", + "Resource": "*", + }, + Object { + "Action": Array [ + "codepipeline:PutJobSuccessResult", + "codepipeline:PutJobFailureResult", + ], + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "ImageCopyImageCopyHandlerServiceRoleDefaultPolicyD42D3CD6", + "Roles": Array [ + Object { + "Ref": "ImageCopyImageCopyHandlerServiceRoleDB5F37CC", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "NginxRepo42B51311": Object { + "DeletionPolicy": "Delete", + "Properties": Object { + "RepositoryName": "nginx", + }, + "Type": "AWS::ECR::Repository", + "UpdateReplacePolicy": "Delete", + }, + "Repo02AC86CF": Object { + "Properties": Object { + "RepositoryName": "test-repo", + }, + "Type": "AWS::CodeCommit::Repository", + }, + "RepopipelinesPipeline19AAB9B8EventRule6357EE85": Object { + "Properties": Object { + "EventPattern": Object { + "detail": Object { + "event": Array [ + "referenceCreated", + "referenceUpdated", + ], + "referenceName": Array [ + "master", + ], + }, + "detail-type": Array [ + "CodeCommit Repository State Change", + ], + "resources": Array [ + Object { + "Fn::GetAtt": Array [ + "Repo02AC86CF", + "Arn", + ], + }, + ], + "source": Array [ + "aws.codecommit", + ], + }, + "State": "ENABLED", + "Targets": Array [ + Object { + "Arn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":codepipeline:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "pipelinesPipelineEAE3F1AF", + }, + ], + ], + }, + "Id": "Target0", + "RoleArn": Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineEventsRoleF14AE9B6", + "Arn", + ], + }, + }, + ], + }, + "Type": "AWS::Events::Rule", + }, + "pipelinesCodeBuildActionRole0A34FFD8": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineRole3396D699", + "Arn", + ], + }, + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "pipelinesCodeBuildActionRoleDefaultPolicy1075D2D6": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "codebuild:BatchGetBuilds", + "codebuild:StartBuild", + "codebuild:StopBuild", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineBuildsynthCdkBuildProjectC3D1D808", + "Arn", + ], + }, + }, + Object { + "Action": Array [ + "codebuild:BatchGetBuilds", + "codebuild:StartBuild", + "codebuild:StopBuild", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "pipelinesUpdatePipelineSelfMutationFA94E664", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "pipelinesCodeBuildActionRoleDefaultPolicy1075D2D6", + "Roles": Array [ + Object { + "Ref": "pipelinesCodeBuildActionRole0A34FFD8", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "pipelinesPipelineArtifactsBucket27498DB8": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "BucketEncryption": Object { + "ServerSideEncryptionConfiguration": Array [ + Object { + "ServerSideEncryptionByDefault": Object { + "SSEAlgorithm": "aws:kms", + }, + }, + ], + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "pipelinesPipelineArtifactsBucketPolicy2E6820BE": Object { + "Properties": Object { + "Bucket": Object { + "Ref": "pipelinesPipelineArtifactsBucket27498DB8", + }, + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "s3:*", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": Object { + "AWS": "*", + }, + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineArtifactsBucket27498DB8", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineArtifactsBucket27498DB8", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::S3::BucketPolicy", + }, + "pipelinesPipelineBuildsynthCdkBuildProjectC3D1D808": Object { + "Properties": Object { + "Artifacts": Object { + "Type": "CODEPIPELINE", + }, + "Cache": Object { + "Type": "NO_CACHE", + }, + "Description": "Pipeline step Default/Pipeline/Build/synth", + "EncryptionKey": "alias/aws/s3", + "Environment": Object { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/standard:7.0", + "ImagePullCredentialsType": "CODEBUILD", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER", + }, + "ServiceRole": Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineBuildsynthCdkBuildProjectRole501C3E99", + "Arn", + ], + }, + "Source": Object { + "BuildSpec": "{ + \\"version\\": \\"0.2\\", + \\"phases\\": { + \\"build\\": { + \\"commands\\": [ + \\"mkdir cdk.out\\", + \\"touch cdk.out/test\\" + ] + } + }, + \\"artifacts\\": { + \\"base-directory\\": \\"cdk.out\\", + \\"files\\": \\"**/*\\" + } +}", + "Type": "CODEPIPELINE", + }, + }, + "Type": "AWS::CodeBuild::Project", + }, + "pipelinesPipelineBuildsynthCdkBuildProjectRole501C3E99": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "codebuild.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "pipelinesPipelineBuildsynthCdkBuildProjectRoleDefaultPolicy0CFABA24": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/codebuild/", + Object { + "Ref": "pipelinesPipelineBuildsynthCdkBuildProjectC3D1D808", + }, + ], + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/codebuild/", + Object { + "Ref": "pipelinesPipelineBuildsynthCdkBuildProjectC3D1D808", + }, + ":*", + ], + ], + }, + ], + }, + Object { + "Action": Array [ + "codebuild:CreateReportGroup", + "codebuild:CreateReport", + "codebuild:UpdateReport", + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":codebuild:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":report-group/", + Object { + "Ref": "pipelinesPipelineBuildsynthCdkBuildProjectC3D1D808", + }, + "-*", + ], + ], + }, + }, + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineArtifactsBucket27498DB8", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineArtifactsBucket27498DB8", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "pipelinesPipelineBuildsynthCdkBuildProjectRoleDefaultPolicy0CFABA24", + "Roles": Array [ + Object { + "Ref": "pipelinesPipelineBuildsynthCdkBuildProjectRole501C3E99", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "pipelinesPipelineCopyImageImageCopyCodePipelineActionRoleBCA09BEC": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "pipelinesPipelineCopyImageImageCopyCodePipelineActionRoleDefaultPolicyF21FF0CC": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "lambda:ListFunctions", + "Effect": "Allow", + "Resource": "*", + }, + Object { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "ImageCopyImageCopyHandlerFBE2CA9D", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "ImageCopyImageCopyHandlerFBE2CA9D", + "Arn", + ], + }, + ":*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "pipelinesPipelineCopyImageImageCopyCodePipelineActionRoleDefaultPolicyF21FF0CC", + "Roles": Array [ + Object { + "Ref": "pipelinesPipelineCopyImageImageCopyCodePipelineActionRoleBCA09BEC", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "pipelinesPipelineEAE3F1AF": Object { + "DependsOn": Array [ + "pipelinesPipelineRoleDefaultPolicy8A18F220", + "pipelinesPipelineRole3396D699", + ], + "Properties": Object { + "ArtifactStore": Object { + "Location": Object { + "Ref": "pipelinesPipelineArtifactsBucket27498DB8", + }, + "Type": "S3", + }, + "PipelineType": "V1", + "RestartExecutionOnUpdate": true, + "RoleArn": Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineRole3396D699", + "Arn", + ], + }, + "Stages": Array [ + Object { + "Actions": Array [ + Object { + "ActionTypeId": Object { + "Category": "Source", + "Owner": "AWS", + "Provider": "CodeCommit", + "Version": "1", + }, + "Configuration": Object { + "BranchName": "master", + "PollForSourceChanges": false, + "RepositoryName": Object { + "Fn::GetAtt": Array [ + "Repo02AC86CF", + "Name", + ], + }, + }, + "Name": Object { + "Fn::GetAtt": Array [ + "Repo02AC86CF", + "Name", + ], + }, + "OutputArtifacts": Array [ + Object { + "Name": "c8d064061d1c8680a574cd5a9f9c9c69b475d41907_Source", + }, + ], + "RoleArn": Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineSourceCodeCommitCodePipelineActionRoleF943FC72", + "Arn", + ], + }, + "RunOrder": 1, + }, + ], + "Name": "Source", + }, + Object { + "Actions": Array [ + Object { + "ActionTypeId": Object { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1", + }, + "Configuration": Object { + "EnvironmentVariables": "[{\\"name\\":\\"_PROJECT_CONFIG_HASH\\",\\"type\\":\\"PLAINTEXT\\",\\"value\\":\\"9469ae51f04167eba9cd5c396c8cb8f88d45ac5945a4d807ad180024e03b7a84\\"}]", + "ProjectName": Object { + "Ref": "pipelinesPipelineBuildsynthCdkBuildProjectC3D1D808", + }, + }, + "InputArtifacts": Array [ + Object { + "Name": "c8d064061d1c8680a574cd5a9f9c9c69b475d41907_Source", + }, + ], + "Name": "synth", + "OutputArtifacts": Array [ + Object { + "Name": "synth_Output", + }, + ], + "RoleArn": Object { + "Fn::GetAtt": Array [ + "pipelinesCodeBuildActionRole0A34FFD8", + "Arn", + ], + }, + "RunOrder": 1, + }, + ], + "Name": "Build", + }, + Object { + "Actions": Array [ + Object { + "ActionTypeId": Object { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1", + }, + "Configuration": Object { + "EnvironmentVariables": "[{\\"name\\":\\"_PROJECT_CONFIG_HASH\\",\\"type\\":\\"PLAINTEXT\\",\\"value\\":\\"f5c136b42f09ed582b9273672c6585264fb7142640d362897bd6649d732584c2\\"}]", + "ProjectName": Object { + "Ref": "pipelinesUpdatePipelineSelfMutationFA94E664", + }, + }, + "InputArtifacts": Array [ + Object { + "Name": "synth_Output", + }, + ], + "Name": "SelfMutate", + "RoleArn": Object { + "Fn::GetAtt": Array [ + "pipelinesCodeBuildActionRole0A34FFD8", + "Arn", + ], + }, + "RunOrder": 1, + }, + ], + "Name": "UpdatePipeline", + }, + Object { + "Actions": Array [ + Object { + "ActionTypeId": Object { + "Category": "Invoke", + "Owner": "AWS", + "Provider": "Lambda", + "Version": "1", + }, + "Configuration": Object { + "FunctionName": Object { + "Ref": "ImageCopyImageCopyHandlerFBE2CA9D", + }, + "UserParameters": Object { + "Fn::Join": Array [ + "", + Array [ + "{\\"SrcImage\\":\\"docker://", + Object { + "Fn::Select": Array [ + 4, + Object { + "Fn::Split": Array [ + ":", + Object { + "Fn::GetAtt": Array [ + "NginxRepo42B51311", + "Arn", + ], + }, + ], + }, + ], + }, + ".dkr.ecr.", + Object { + "Fn::Select": Array [ + 3, + Object { + "Fn::Split": Array [ + ":", + Object { + "Fn::GetAtt": Array [ + "NginxRepo42B51311", + "Arn", + ], + }, + ], + }, + ], + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "NginxRepo42B51311", + }, + ":stable\\",\\"DestImage\\":\\"docker://", + Object { + "Fn::Select": Array [ + 4, + Object { + "Fn::Split": Array [ + ":", + Object { + "Fn::GetAtt": Array [ + "NginxRepo42B51311", + "Arn", + ], + }, + ], + }, + ], + }, + ".dkr.ecr.", + Object { + "Fn::Select": Array [ + 3, + Object { + "Fn::Split": Array [ + ":", + Object { + "Fn::GetAtt": Array [ + "NginxRepo42B51311", + "Arn", + ], + }, + ], + }, + ], + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "NginxRepo42B51311", + }, + ":latest\\"}", + ], + ], + }, + }, + "Name": "ImageCopy", + "RoleArn": Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineCopyImageImageCopyCodePipelineActionRoleBCA09BEC", + "Arn", + ], + }, + "RunOrder": 1, + }, + ], + "Name": "CopyImage", + }, + ], + }, + "Type": "AWS::CodePipeline::Pipeline", + }, + "pipelinesPipelineEventsRoleDefaultPolicy9E5668AB": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "codepipeline:StartPipelineExecution", + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":codepipeline:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "pipelinesPipelineEAE3F1AF", + }, + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "pipelinesPipelineEventsRoleDefaultPolicy9E5668AB", + "Roles": Array [ + Object { + "Ref": "pipelinesPipelineEventsRoleF14AE9B6", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "pipelinesPipelineEventsRoleF14AE9B6": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "events.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "pipelinesPipelineRole3396D699": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "codepipeline.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "pipelinesPipelineRoleDefaultPolicy8A18F220": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineArtifactsBucket27498DB8", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineArtifactsBucket27498DB8", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineSourceCodeCommitCodePipelineActionRoleF943FC72", + "Arn", + ], + }, + }, + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "pipelinesCodeBuildActionRole0A34FFD8", + "Arn", + ], + }, + }, + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineCopyImageImageCopyCodePipelineActionRoleBCA09BEC", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "pipelinesPipelineRoleDefaultPolicy8A18F220", + "Roles": Array [ + Object { + "Ref": "pipelinesPipelineRole3396D699", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "pipelinesPipelineSourceCodeCommitCodePipelineActionRoleDefaultPolicy3A67ACE9": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineArtifactsBucket27498DB8", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineArtifactsBucket27498DB8", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + Object { + "Action": Array [ + "codecommit:GetBranch", + "codecommit:GetCommit", + "codecommit:UploadArchive", + "codecommit:GetUploadArchiveStatus", + "codecommit:CancelUploadArchive", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "Repo02AC86CF", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "pipelinesPipelineSourceCodeCommitCodePipelineActionRoleDefaultPolicy3A67ACE9", + "Roles": Array [ + Object { + "Ref": "pipelinesPipelineSourceCodeCommitCodePipelineActionRoleF943FC72", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "pipelinesPipelineSourceCodeCommitCodePipelineActionRoleF943FC72": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":root", + ], + ], + }, + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "pipelinesUpdatePipelineSelfMutationFA94E664": Object { + "Properties": Object { + "Artifacts": Object { + "Type": "CODEPIPELINE", + }, + "Cache": Object { + "Type": "NO_CACHE", + }, + "Description": "Pipeline step Default/Pipeline/UpdatePipeline/SelfMutate", + "EncryptionKey": "alias/aws/s3", + "Environment": Object { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/standard:7.0", + "ImagePullCredentialsType": "CODEBUILD", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER", + }, + "ServiceRole": Object { + "Fn::GetAtt": Array [ + "pipelinesUpdatePipelineSelfMutationRole6B594EB5", + "Arn", + ], + }, + "Source": Object { + "BuildSpec": "{ + \\"version\\": \\"0.2\\", + \\"phases\\": { + \\"install\\": { + \\"commands\\": [ + \\"npm install -g aws-cdk@2\\" + ] + }, + \\"build\\": { + \\"commands\\": [ + \\"cdk -a . deploy Default --require-approval=never --verbose\\" + ] + } + } +}", + "Type": "CODEPIPELINE", + }, + }, + "Type": "AWS::CodeBuild::Project", + }, + "pipelinesUpdatePipelineSelfMutationRole6B594EB5": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "codebuild.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "pipelinesUpdatePipelineSelfMutationRoleDefaultPolicyD785AFC1": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/codebuild/", + Object { + "Ref": "pipelinesUpdatePipelineSelfMutationFA94E664", + }, + ], + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/codebuild/", + Object { + "Ref": "pipelinesUpdatePipelineSelfMutationFA94E664", + }, + ":*", + ], + ], + }, + ], + }, + Object { + "Action": Array [ + "codebuild:CreateReportGroup", + "codebuild:CreateReport", + "codebuild:UpdateReport", + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":codebuild:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":report-group/", + Object { + "Ref": "pipelinesUpdatePipelineSelfMutationFA94E664", + }, + "-*", + ], + ], + }, + }, + Object { + "Action": "sts:AssumeRole", + "Condition": Object { + "ForAnyValue:StringEquals": Object { + "iam:ResourceTag/aws-cdk:bootstrap-role": Array [ + "image-publishing", + "file-publishing", + "deploy", + ], + }, + }, + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:*:iam::", + Object { + "Ref": "AWS::AccountId", + }, + ":role/*", + ], + ], + }, + }, + Object { + "Action": "cloudformation:DescribeStacks", + "Effect": "Allow", + "Resource": "*", + }, + Object { + "Action": "s3:ListBucket", + "Effect": "Allow", + "Resource": "*", + }, + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineArtifactsBucket27498DB8", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "pipelinesPipelineArtifactsBucket27498DB8", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "pipelinesUpdatePipelineSelfMutationRoleDefaultPolicyD785AFC1", + "Roles": Array [ + Object { + "Ref": "pipelinesUpdatePipelineSelfMutationRole6B594EB5", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + }, + "Rules": Object { + "CheckBootstrapVersion": Object { + "Assertions": Array [ + Object { + "Assert": Object { + "Fn::Not": Array [ + Object { + "Fn::Contains": Array [ + Array [ + "1", + "2", + "3", + "4", + "5", + ], + Object { + "Ref": "BootstrapVersion", + }, + ], + }, + ], + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.", + }, + ], + }, + }, +} +`; diff --git a/test/index.test.ts b/test/index.test.ts index 425ad4b6..25438b4c 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -2,7 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 -import { DockerImageName, S3ArchiveName } from '../src'; +import { + Stack, + RemovalPolicy, + aws_codepipeline as codepipeline, + aws_ecr as ecr, + aws_codepipeline_actions as codepipeline_actions, + aws_codecommit as codecommit, + pipelines, +} from 'aws-cdk-lib'; +import { Template } from 'aws-cdk-lib/assertions'; +import { DockerImageName, ECRDeploymentStep, S3ArchiveName } from '../src'; test(`${DockerImageName.name}`, () => { const name = new DockerImageName('nginx:latest'); @@ -14,4 +24,78 @@ test(`${S3ArchiveName.name}`, () => { const name = new S3ArchiveName('bucket/nginx.tar', 'nginx:latest'); expect(name.uri).toBe('s3://bucket/nginx.tar:nginx:latest'); +}); + +describe('lambda in codepipeline', () => { + const stack = new Stack(); + const pipeline = new codepipeline.Pipeline(stack, 'PipelineWithLambda', {}); + + const output = new codepipeline.Artifact(); + const repository = new codecommit.Repository(stack, 'Repo', { + repositoryName: 'test-repo', + }); + pipeline.addStage({ + stageName: 'Source', + actions: [ + new codepipeline_actions.CodeCommitSourceAction({ + actionName: 'Source', + output, + repository, + }), + ], + }); + const stage = pipeline.addStage({ + stageName: 'CopyImage', + }); + + const repo = new ecr.Repository(stack, 'NginxRepo', { + repositoryName: 'nginx', + removalPolicy: RemovalPolicy.DESTROY, + }); + + new ECRDeploymentStep(stack, 'ImageCopy', { + dest: new DockerImageName(`${repo.repositoryUri}:latest`), + src: new DockerImageName(`${repo.repositoryUri}:stable`), + stage, + }); + const template = Template.fromStack(stack); + + test('matches snapshot', () => { + expect(template).toMatchSnapshot(); + }); +}); + +describe('lambda in pipelines pipeline', () => { + const stack = new Stack(); + const repository = new codecommit.Repository(stack, 'Repo', { + repositoryName: 'test-repo', + }); + + const pipeline = new pipelines.CodePipeline(stack, 'pipelines', { + synth: new pipelines.ShellStep('synth', { + input: pipelines.CodePipelineSource.codeCommit(repository, 'master'), + commands: [ + 'mkdir cdk.out', + 'touch cdk.out/test', + ], + }), + }); + + const wave = pipeline.addWave('CopyImage'); + + const repo = new ecr.Repository(stack, 'NginxRepo', { + repositoryName: 'nginx', + removalPolicy: RemovalPolicy.DESTROY, + }); + + new ECRDeploymentStep(stack, 'ImageCopy', { + dest: new DockerImageName(`${repo.repositoryUri}:latest`), + src: new DockerImageName(`${repo.repositoryUri}:stable`), + wave, + }); + const template = Template.fromStack(stack); + + test('matches snapshot', () => { + expect(template).toMatchSnapshot(); + }); }); \ No newline at end of file