From 90050c209ee4429936861e8d921bfb9031d88837 Mon Sep 17 00:00:00 2001 From: hupe1980 Date: Sun, 17 Oct 2021 15:23:33 +0200 Subject: [PATCH 1/2] fix: Support multiple pipeline in one stack --- .../blue-green-container-deployment-stack.ts | 7 +- .../API.md | 10 +- .../lambda-file-sizes.json | 2 +- .../package.json | 5 + .../dummy-task-definition.test.ts.snap | 156 ++++++++++++++++++ .../__tests__/dummy-task-definition.test.ts | 16 ++ .../src/dummy-task-definition.ts | 78 +++++---- .../src/ecs-deployment-group.ts | 63 ++++--- .../src/ecs-service.ts | 47 ++++-- .../lambdas/dummy-task-definition/index.ts | 86 ---------- .../src/lambdas/ecs-deployment-group/index.ts | 50 +++--- .../src/lambdas/ecs-service/index.ts | 57 +++---- 12 files changed, 345 insertions(+), 232 deletions(-) create mode 100644 packages/cdk-blue-green-container-deployment/src/__tests__/__snapshots__/dummy-task-definition.test.ts.snap create mode 100644 packages/cdk-blue-green-container-deployment/src/__tests__/dummy-task-definition.test.ts delete mode 100644 packages/cdk-blue-green-container-deployment/src/lambdas/dummy-task-definition/index.ts diff --git a/examples/blue-green-container-deployment-example/src/blue-green-container-deployment-stack.ts b/examples/blue-green-container-deployment-example/src/blue-green-container-deployment-stack.ts index ec32b5b93..47be2d583 100644 --- a/examples/blue-green-container-deployment-example/src/blue-green-container-deployment-stack.ts +++ b/examples/blue-green-container-deployment-example/src/blue-green-container-deployment-stack.ts @@ -4,7 +4,7 @@ import { CodeBuildAction, CodeCommitSourceAction, CodeDeployEcsDeployAction } fr import { Vpc, Port } from '@aws-cdk/aws-ec2'; import { Cluster } from '@aws-cdk/aws-ecs'; import { ApplicationLoadBalancer, ApplicationTargetGroup, TargetType } from '@aws-cdk/aws-elasticloadbalancingv2'; -import { Construct, Stack, StackProps } from '@aws-cdk/core'; +import { Construct, Duration, Stack, StackProps } from '@aws-cdk/core'; import { EcsService, DummyTaskDefinition, EcsDeploymentGroup, PushImageProject } from '@cloudcomponents/cdk-blue-green-container-deployment'; import { ImageRepository } from '@cloudcomponents/cdk-container-registry'; @@ -67,6 +67,7 @@ export class BlueGreenContainerDeploymentStack extends Stack { desiredCount: 2, taskDefinition, prodTargetGroup, + testTargetGroup, }); ecsService.connections.allowFrom(loadBalancer, Port.tcp(80)); @@ -76,10 +77,10 @@ export class BlueGreenContainerDeploymentStack extends Stack { applicationName: 'blue-green-application', deploymentGroupName: 'blue-green-deployment-group', ecsServices: [ecsService], - targetGroupNames: [prodTargetGroup.targetGroupName, testTargetGroup.targetGroupName], + targetGroups: [prodTargetGroup, testTargetGroup], prodTrafficListener: prodListener, testTrafficListener: testListener, - terminationWaitTimeInMinutes: 100, + terminationWaitTime: Duration.minutes(100), }); // @see files: ./blue-green-repository for example content diff --git a/packages/cdk-blue-green-container-deployment/API.md b/packages/cdk-blue-green-container-deployment/API.md index 12aa7a197..054040491 100644 --- a/packages/cdk-blue-green-container-deployment/API.md +++ b/packages/cdk-blue-green-container-deployment/API.md @@ -177,12 +177,12 @@ new EcsDeploymentGroup(scope: Construct, id: string, props: EcsDeploymentGroupPr * **deploymentGroupName** (string) *No description* * **ecsServices** (Array<[IEcsService](#cloudcomponents-cdk-blue-green-container-deployment-iecsservice)>) *No description* * **prodTrafficListener** ([TrafficListener](#cloudcomponents-cdk-blue-green-container-deployment-trafficlistener)) *No description* - * **targetGroupNames** (Array) *No description* + * **targetGroups** (Array<[ApplicationTargetGroup](#aws-cdk-aws-elasticloadbalancingv2-applicationtargetgroup)>) *No description* * **testTrafficListener** ([TrafficListener](#cloudcomponents-cdk-blue-green-container-deployment-trafficlistener)) *No description* * **applicationName** (string) *No description* __*Optional*__ * **autoRollbackOnEvents** (Array<[RollbackEvent](#cloudcomponents-cdk-blue-green-container-deployment-rollbackevent)>) The event type or types that trigger a rollback. __*Optional*__ * **deploymentConfig** ([IEcsDeploymentConfig](#cloudcomponents-cdk-blue-green-container-deployment-iecsdeploymentconfig)) *No description* __*Optional*__ - * **terminationWaitTimeInMinutes** (number) the number of minutes before deleting the original (blue) task set. __*Default*__: 60 + * **terminationWaitTime** ([Duration](#aws-cdk-core-duration)) the number of minutes before deleting the original (blue) task set. __*Default*__: 60 minutes @@ -221,6 +221,7 @@ new EcsService(scope: Construct, id: string, props: EcsServiceProps) * **prodTargetGroup** ([ITargetGroup](#aws-cdk-aws-elasticloadbalancingv2-itargetgroup)) *No description* * **serviceName** (string) *No description* * **taskDefinition** ([DummyTaskDefinition](#cloudcomponents-cdk-blue-green-container-deployment-dummytaskdefinition)) *No description* + * **testTargetGroup** ([ITargetGroup](#aws-cdk-aws-elasticloadbalancingv2-itargetgroup)) *No description* * **circuitBreaker** ([DeploymentCircuitBreaker](#aws-cdk-aws-ecs-deploymentcircuitbreaker)) Whether to enable the deployment circuit breaker. __*Default*__: disabled * **containerPort** (number) *No description* __*Optional*__ * **desiredCount** (number) *No description* __*Optional*__ @@ -317,12 +318,12 @@ Name | Type | Description **deploymentGroupName** | string | **ecsServices** | Array<[IEcsService](#cloudcomponents-cdk-blue-green-container-deployment-iecsservice)> | **prodTrafficListener** | [TrafficListener](#cloudcomponents-cdk-blue-green-container-deployment-trafficlistener) | -**targetGroupNames** | Array | +**targetGroups** | Array<[ApplicationTargetGroup](#aws-cdk-aws-elasticloadbalancingv2-applicationtargetgroup)> | **testTrafficListener** | [TrafficListener](#cloudcomponents-cdk-blue-green-container-deployment-trafficlistener) | **applicationName**? | string | __*Optional*__ **autoRollbackOnEvents**? | Array<[RollbackEvent](#cloudcomponents-cdk-blue-green-container-deployment-rollbackevent)> | The event type or types that trigger a rollback.
__*Optional*__ **deploymentConfig**? | [IEcsDeploymentConfig](#cloudcomponents-cdk-blue-green-container-deployment-iecsdeploymentconfig) | __*Optional*__ -**terminationWaitTimeInMinutes**? | number | the number of minutes before deleting the original (blue) task set.
__*Default*__: 60 +**terminationWaitTime**? | [Duration](#aws-cdk-core-duration) | the number of minutes before deleting the original (blue) task set.
__*Default*__: 60 minutes @@ -339,6 +340,7 @@ Name | Type | Description **prodTargetGroup** | [ITargetGroup](#aws-cdk-aws-elasticloadbalancingv2-itargetgroup) | **serviceName** | string | **taskDefinition** | [DummyTaskDefinition](#cloudcomponents-cdk-blue-green-container-deployment-dummytaskdefinition) | +**testTargetGroup** | [ITargetGroup](#aws-cdk-aws-elasticloadbalancingv2-itargetgroup) | **circuitBreaker**? | [DeploymentCircuitBreaker](#aws-cdk-aws-ecs-deploymentcircuitbreaker) | Whether to enable the deployment circuit breaker.
__*Default*__: disabled **containerPort**? | number | __*Optional*__ **desiredCount**? | number | __*Optional*__ diff --git a/packages/cdk-blue-green-container-deployment/lambda-file-sizes.json b/packages/cdk-blue-green-container-deployment/lambda-file-sizes.json index 6e7d33e9b..5fc34da0a 100644 --- a/packages/cdk-blue-green-container-deployment/lambda-file-sizes.json +++ b/packages/cdk-blue-green-container-deployment/lambda-file-sizes.json @@ -1 +1 @@ -[{"timestamp":1631683651094,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":1728,"diff":0},{"filename":"ecs-deployment-group/index.js","previous":2182,"size":2182,"diff":0},{"filename":"ecs-service/index.js","previous":2152,"size":2138,"diff":-14}]},{"timestamp":1629825530770,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":1728,"diff":0},{"filename":"ecs-deployment-group/index.js","previous":2171,"size":2182,"diff":11},{"filename":"ecs-service/index.js","previous":2152,"size":2152,"diff":0}]},{"timestamp":1629823812165,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":1728,"diff":0},{"filename":"ecs-deployment-group/index.js","previous":2123,"size":2171,"diff":48},{"filename":"ecs-service/index.js","previous":2152,"size":2152,"diff":0}]},{"timestamp":1627652613714,"files":[{"filename":"dummy-task-definition/index.js","previous":1726,"size":1728,"diff":2},{"filename":"ecs-deployment-group/index.js","previous":2123,"size":2123,"diff":0},{"filename":"ecs-service/index.js","previous":2081,"size":2152,"diff":71}]},{"timestamp":1609276390870,"files":[{"filename":"dummy-task-definition/index.js","previous":1712,"size":1726,"diff":14},{"filename":"ecs-deployment-group/index.js","previous":2123,"size":2123,"diff":0},{"filename":"ecs-service/index.js","previous":2081,"size":2081,"diff":0}]},{"timestamp":1606329521054,"files":[{"filename":"dummy-task-definition/index.js","previous":1706,"size":1712,"diff":6},{"filename":"ecs-deployment-group/index.js","previous":2116,"size":2123,"diff":7},{"filename":"ecs-service/index.js","previous":2073,"size":2081,"diff":8}]},{"timestamp":1596457247342,"files":[{"filename":"dummy-task-definition/index.js","previous":1756,"size":1706,"diff":-50},{"filename":"ecs-deployment-group/index.js","previous":2116,"size":2116,"diff":0},{"filename":"ecs-service/index.js","previous":2073,"size":2073,"diff":0}]},{"timestamp":1596454924871,"files":[{"filename":"dummy-task-definition/index.js","previous":4964,"size":1756,"diff":-3208},{"filename":"ecs-deployment-group/index.js","previous":6103,"size":2116,"diff":-3987},{"filename":"ecs-service/index.js","previous":6141,"size":2073,"diff":-4068}]},{"timestamp":1596407637937,"files":[{"filename":"blue-green-service/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"blue-green-service/index.js","previous":3368,"size":0,"diff":-3368},{"filename":"dummy-task-definition/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"dummy-task-definition/index.js","previous":1963,"size":4964,"diff":3001},{"filename":"ecs-deployment-group/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"ecs-deployment-group/index.js","previous":2292,"size":6103,"diff":3811},{"filename":"ecs-service/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"ecs-service/index.js","previous":2312,"size":6141,"diff":3829}]}] +[{"timestamp":1634467601943,"files":[{"filename":"ecs-deployment-group/index.js","previous":144792,"size":144825,"diff":33},{"filename":"ecs-service/index.js","previous":2133,"size":144797,"diff":142664}]},{"timestamp":1634411334117,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":0,"diff":-1728},{"filename":"ecs-deployment-group/index.js","previous":2182,"size":144792,"diff":142610},{"filename":"ecs-service/index.js","previous":2138,"size":2133,"diff":-5}]},{"timestamp":1631683651094,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":1728,"diff":0},{"filename":"ecs-deployment-group/index.js","previous":2182,"size":2182,"diff":0},{"filename":"ecs-service/index.js","previous":2152,"size":2138,"diff":-14}]},{"timestamp":1629825530770,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":1728,"diff":0},{"filename":"ecs-deployment-group/index.js","previous":2171,"size":2182,"diff":11},{"filename":"ecs-service/index.js","previous":2152,"size":2152,"diff":0}]},{"timestamp":1629823812165,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":1728,"diff":0},{"filename":"ecs-deployment-group/index.js","previous":2123,"size":2171,"diff":48},{"filename":"ecs-service/index.js","previous":2152,"size":2152,"diff":0}]},{"timestamp":1627652613714,"files":[{"filename":"dummy-task-definition/index.js","previous":1726,"size":1728,"diff":2},{"filename":"ecs-deployment-group/index.js","previous":2123,"size":2123,"diff":0},{"filename":"ecs-service/index.js","previous":2081,"size":2152,"diff":71}]},{"timestamp":1609276390870,"files":[{"filename":"dummy-task-definition/index.js","previous":1712,"size":1726,"diff":14},{"filename":"ecs-deployment-group/index.js","previous":2123,"size":2123,"diff":0},{"filename":"ecs-service/index.js","previous":2081,"size":2081,"diff":0}]},{"timestamp":1606329521054,"files":[{"filename":"dummy-task-definition/index.js","previous":1706,"size":1712,"diff":6},{"filename":"ecs-deployment-group/index.js","previous":2116,"size":2123,"diff":7},{"filename":"ecs-service/index.js","previous":2073,"size":2081,"diff":8}]},{"timestamp":1596457247342,"files":[{"filename":"dummy-task-definition/index.js","previous":1756,"size":1706,"diff":-50},{"filename":"ecs-deployment-group/index.js","previous":2116,"size":2116,"diff":0},{"filename":"ecs-service/index.js","previous":2073,"size":2073,"diff":0}]},{"timestamp":1596454924871,"files":[{"filename":"dummy-task-definition/index.js","previous":4964,"size":1756,"diff":-3208},{"filename":"ecs-deployment-group/index.js","previous":6103,"size":2116,"diff":-3987},{"filename":"ecs-service/index.js","previous":6141,"size":2073,"diff":-4068}]},{"timestamp":1596407637937,"files":[{"filename":"blue-green-service/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"blue-green-service/index.js","previous":3368,"size":0,"diff":-3368},{"filename":"dummy-task-definition/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"dummy-task-definition/index.js","previous":1963,"size":4964,"diff":3001},{"filename":"ecs-deployment-group/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"ecs-deployment-group/index.js","previous":2292,"size":6103,"diff":3811},{"filename":"ecs-service/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"ecs-service/index.js","previous":2312,"size":6141,"diff":3829}]}] diff --git a/packages/cdk-blue-green-container-deployment/package.json b/packages/cdk-blue-green-container-deployment/package.json index 52702c0cf..ef3a838cf 100644 --- a/packages/cdk-blue-green-container-deployment/package.json +++ b/packages/cdk-blue-green-container-deployment/package.json @@ -63,27 +63,32 @@ "peerDependencies": { "@aws-cdk/aws-codebuild": "^1.127.0", "@aws-cdk/aws-codedeploy": "^1.127.0", + "@aws-cdk/custom-resources": "^1.127.0", "@aws-cdk/aws-ec2": "^1.127.0", "@aws-cdk/aws-ecr": "^1.127.0", "@aws-cdk/aws-ecs": "^1.127.0", "@aws-cdk/aws-elasticloadbalancingv2": "^1.127.0", "@aws-cdk/aws-iam": "^1.127.0", + "@aws-cdk/aws-lambda": "^1.127.0", "@aws-cdk/core": "^1.127.0", "constructs": "^3.2.0" }, "dependencies": { "@aws-cdk/aws-codebuild": "^1.127.0", "@aws-cdk/aws-codedeploy": "^1.127.0", + "@aws-cdk/custom-resources": "^1.127.0", "@aws-cdk/aws-ec2": "^1.127.0", "@aws-cdk/aws-ecr": "^1.127.0", "@aws-cdk/aws-ecs": "^1.127.0", "@aws-cdk/aws-elasticloadbalancingv2": "^1.127.0", "@aws-cdk/aws-iam": "^1.127.0", + "@aws-cdk/aws-lambda": "^1.127.0", "@aws-cdk/core": "^1.127.0" }, "devDependencies": { "@aws-cdk/assert": "^1.127.0", "aws-sdk": "^2.1004.0", + "custom-resource-helper": "^1.0.15", "jest-cdk-snapshot": "^1.4.2" }, "externals": [ diff --git a/packages/cdk-blue-green-container-deployment/src/__tests__/__snapshots__/dummy-task-definition.test.ts.snap b/packages/cdk-blue-green-container-deployment/src/__tests__/__snapshots__/dummy-task-definition.test.ts.snap new file mode 100644 index 000000000..f74c7188f --- /dev/null +++ b/packages/cdk-blue-green-container-deployment/src/__tests__/__snapshots__/dummy-task-definition.test.ts.snap @@ -0,0 +1,156 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`default setup 1`] = ` +Object { + "Parameters": Any, + "Resources": Object { + "AWS679f53fac002430cb0da5b7982bd22872D164C4C": Object { + "DependsOn": Array [ + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2", + ], + "Properties": Object { + "Code": Any, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2", + "Arn", + ], + }, + "Runtime": "nodejs12.x", + "Timeout": 120, + }, + "Type": "AWS::Lambda::Function", + }, + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2": 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", + }, + "DummyTaskDefinitionCustomResourcePolicyB5660701": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "ecs:RegisterTaskDefinition", + "ecs:DeregisterTaskDefinition", + ], + "Effect": "Allow", + "Resource": "*", + }, + Object { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "DummyTaskDefinitionExecutionRole715DBD43", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "DummyTaskDefinitionCustomResourcePolicyB5660701", + "Roles": Array [ + Object { + "Ref": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "DummyTaskDefinitionE3D9D432": Object { + "DeletionPolicy": "Delete", + "DependsOn": Array [ + "DummyTaskDefinitionCustomResourcePolicyB5660701", + ], + "Properties": Object { + "Create": Object { + "Fn::Join": Array [ + "", + Array [ + "{\\"service\\":\\"ECS\\",\\"action\\":\\"registerTaskDefinition\\",\\"parameters\\":{\\"requiresCompatibilities\\":[\\"FARGATE\\"],\\"family\\":\\"c86e1e5d419d3c124c7e40411b7e7805f91621e162\\",\\"executionRoleArn\\":\\"", + Object { + "Fn::GetAtt": Array [ + "DummyTaskDefinitionExecutionRole715DBD43", + "Arn", + ], + }, + "\\",\\"networkMode\\":\\"awsvpc\\",\\"cpu\\":\\"256\\",\\"memory\\":\\"512\\",\\"containerDefinitions\\":[{\\"name\\":\\"sample-website\\",\\"image\\":\\"image\\",\\"portMappings\\":[{\\"hostPort\\":80,\\"protocol\\":\\"tcp\\",\\"containerPort\\":80}]}]},\\"physicalResourceId\\":{\\"responsePath\\":\\"taskDefinition.taskDefinitionArn\\"}}", + ], + ], + }, + "Delete": "{\\"service\\":\\"ECS\\",\\"action\\":\\"deregisterTaskDefinition\\",\\"parameters\\":{\\"taskDefinition\\":\\"PHYSICAL:RESOURCEID:\\"}}", + "InstallLatestAwsSdk": true, + "ServiceToken": Object { + "Fn::GetAtt": Array [ + "AWS679f53fac002430cb0da5b7982bd22872D164C4C", + "Arn", + ], + }, + }, + "Type": "Custom::DummyTaskDefinition", + "UpdateReplacePolicy": "Delete", + }, + "DummyTaskDefinitionExecutionRole715DBD43": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "ecs-tasks.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + }, +} +`; diff --git a/packages/cdk-blue-green-container-deployment/src/__tests__/dummy-task-definition.test.ts b/packages/cdk-blue-green-container-deployment/src/__tests__/dummy-task-definition.test.ts new file mode 100644 index 000000000..29b97c7ae --- /dev/null +++ b/packages/cdk-blue-green-container-deployment/src/__tests__/dummy-task-definition.test.ts @@ -0,0 +1,16 @@ +import { Stack } from '@aws-cdk/core'; +import 'jest-cdk-snapshot'; + +import { DummyTaskDefinition } from '../dummy-task-definition'; + +test('default setup', (): void => { + const stack = new Stack(); + + new DummyTaskDefinition(stack, 'DummyTaskDefinition', { + image: 'image', + }); + + expect(stack).toMatchCdkSnapshot({ + ignoreAssets: true, + }); +}); diff --git a/packages/cdk-blue-green-container-deployment/src/dummy-task-definition.ts b/packages/cdk-blue-green-container-deployment/src/dummy-task-definition.ts index ede7cb0d5..f5397a639 100644 --- a/packages/cdk-blue-green-container-deployment/src/dummy-task-definition.ts +++ b/packages/cdk-blue-green-container-deployment/src/dummy-task-definition.ts @@ -1,8 +1,7 @@ -import * as path from 'path'; - import { NetworkMode } from '@aws-cdk/aws-ecs'; import { Role, ServicePrincipal, ManagedPolicy, PolicyStatement, Effect, IRole } from '@aws-cdk/aws-iam'; -import { Construct, CustomResource, CustomResourceProvider, CustomResourceProviderRuntime } from '@aws-cdk/core'; +import { Construct } from '@aws-cdk/core'; +import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId, PhysicalResourceIdReference } from '@aws-cdk/custom-resources'; export interface IDummyTaskDefinition { readonly executionRole: IRole; @@ -60,41 +59,60 @@ export class DummyTaskDefinition extends Construct implements IDummyTaskDefiniti managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy')], }); - const serviceToken = CustomResourceProvider.getOrCreate(this, 'Custom::DummyTaskDefinition', { - codeDirectory: path.join(__dirname, 'lambdas', 'dummy-task-definition'), - runtime: CustomResourceProviderRuntime.NODEJS_12_X, - policyStatements: [ - { - Effect: Effect.ALLOW, - Action: ['ecs:RegisterTaskDefinition', 'ecs:DeregisterTaskDefinition'], - Resource: '*', - }, - { - Effect: Effect.ALLOW, - Action: ['iam:PassRole'], - Resource: this.executionRole.roleArn, - }, - ], - }); - this.family = props.family ?? this.node.addr; this.containerName = props.containerName ?? 'sample-website'; this.containerPort = props.containerPort ?? 80; - const taskDefinition = new CustomResource(this, 'CustomResource', { - serviceToken, + const taskDefinition = new AwsCustomResource(this, 'DummyTaskDefinition', { resourceType: 'Custom::DummyTaskDefinition', - properties: { - Family: this.family, - Image: props.image, - ExecutionRoleArn: this.executionRole.roleArn, - NetworkMode: NetworkMode.AWS_VPC, - ContainerName: this.containerName, - ContainerPort: this.containerPort, + onCreate: { + service: 'ECS', + action: 'registerTaskDefinition', + parameters: { + requiresCompatibilities: ['FARGATE'], + family: this.family, + executionRoleArn: this.executionRole.roleArn, + networkMode: NetworkMode.AWS_VPC, + cpu: '256', + memory: '512', + containerDefinitions: [ + { + name: this.containerName, + image: props.image, + portMappings: [ + { + hostPort: this.containerPort, + protocol: 'tcp', + containerPort: this.containerPort, + }, + ], + }, + ], + }, + physicalResourceId: PhysicalResourceId.fromResponse('taskDefinition.taskDefinitionArn'), + }, + onDelete: { + service: 'ECS', + action: 'deregisterTaskDefinition', + parameters: { + taskDefinition: new PhysicalResourceIdReference(), + }, }, + policy: AwsCustomResourcePolicy.fromStatements([ + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['ecs:RegisterTaskDefinition', 'ecs:DeregisterTaskDefinition'], + resources: ['*'], + }), + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['iam:PassRole'], + resources: [this.executionRole.roleArn], + }), + ]), }); - this.taskDefinitionArn = taskDefinition.ref; + this.taskDefinitionArn = taskDefinition.getResponseField('taskDefinition.taskDefinitionArn'); } /** diff --git a/packages/cdk-blue-green-container-deployment/src/ecs-deployment-group.ts b/packages/cdk-blue-green-container-deployment/src/ecs-deployment-group.ts index cf1b5af9d..6a4591088 100644 --- a/packages/cdk-blue-green-container-deployment/src/ecs-deployment-group.ts +++ b/packages/cdk-blue-green-container-deployment/src/ecs-deployment-group.ts @@ -1,7 +1,9 @@ import * as path from 'path'; import { EcsApplication, IEcsApplication } from '@aws-cdk/aws-codedeploy'; -import { Role, ServicePrincipal, ManagedPolicy, Effect } from '@aws-cdk/aws-iam'; -import { Aws, Construct, Resource, IResource, CustomResource, CustomResourceProvider, CustomResourceProviderRuntime } from '@aws-cdk/core'; +import { ApplicationTargetGroup } from '@aws-cdk/aws-elasticloadbalancingv2'; +import { Role, ServicePrincipal, ManagedPolicy, Effect, PolicyStatement } from '@aws-cdk/aws-iam'; +import { Function, Runtime, Code } from '@aws-cdk/aws-lambda'; +import { Aws, Construct, Resource, IResource, CustomResource, Duration } from '@aws-cdk/core'; import { EcsDeploymentConfig, IEcsDeploymentConfig } from './ecs-deployment-config'; import { IEcsService } from './ecs-service'; @@ -48,7 +50,7 @@ export interface EcsDeploymentGroupProps { readonly ecsServices: IEcsService[]; - readonly targetGroupNames: string[]; + readonly targetGroups: ApplicationTargetGroup[]; readonly prodTrafficListener: TrafficListener; @@ -61,9 +63,9 @@ export interface EcsDeploymentGroupProps { * * The maximum setting is 2880 minutes (2 days). * - * @default 60 + * @default 60 minutes */ - readonly terminationWaitTimeInMinutes?: number; + readonly terminationWaitTime?: Duration; /** * The event type or types that trigger a rollback. @@ -85,18 +87,18 @@ export class EcsDeploymentGroup extends Resource implements IEcsDeploymentGroup deploymentGroupName, deploymentConfig, ecsServices, - targetGroupNames, + targetGroups, prodTrafficListener, testTrafficListener, - terminationWaitTimeInMinutes = 60, + terminationWaitTime = Duration.minutes(60), autoRollbackOnEvents, } = props; - if (terminationWaitTimeInMinutes > 2880) { + if (terminationWaitTime.toMinutes() > 2880) { throw new Error('Invalid TerminationWaitTimeInMinutes: The maximum setting is 2880 minutes (2 days).'); } - const codeDeployEcsRole = new Role(this, 'EcsCodeDeployRole', { + const codeDeployEcsRole = new Role(this, 'Role', { assumedBy: new ServicePrincipal('codedeploy.amazonaws.com'), managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('AWSCodeDeployRoleForECS')], }); @@ -105,44 +107,51 @@ export class EcsDeploymentGroup extends Resource implements IEcsDeploymentGroup applicationName, }); - const serviceToken = CustomResourceProvider.getOrCreate(this, 'Custom::EcsDeploymentGroup', { - codeDirectory: path.join(__dirname, 'lambdas', 'ecs-deployment-group'), - runtime: CustomResourceProviderRuntime.NODEJS_12_X, - policyStatements: [ - { - Effect: Effect.ALLOW, - Action: ['codeDeploy:CreateDeploymentGroup', 'codeDeploy:UpdateDeploymentGroup', 'codeDeploy:DeleteDeploymentGroup'], - Resource: '*', - }, - { - Effect: Effect.ALLOW, - Action: ['iam:PassRole'], - Resource: codeDeployEcsRole.roleArn, - }, - ], + const serviceToken = new Function(this, 'Function', { + runtime: Runtime.NODEJS_12_X, + code: Code.fromAsset(path.join(__dirname, 'lambdas', 'ecs-deployment-group')), + handler: 'index.handler', + timeout: Duration.minutes(15), }); + serviceToken.addToRolePolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['codeDeploy:CreateDeploymentGroup', 'codeDeploy:UpdateDeploymentGroup', 'codeDeploy:DeleteDeploymentGroup'], + resources: ['*'], + }), + ); + + serviceToken.addToRolePolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['iam:PassRole'], + resources: [codeDeployEcsRole.roleArn], + }), + ); + this.deploymentConfig = deploymentConfig || EcsDeploymentConfig.ALL_AT_ONCE; if (Construct.isConstruct(props.deploymentConfig)) { this.node.addDependency(props.deploymentConfig); } + this.node.addDependency(...ecsServices); const ecsDeploymentGroup = new CustomResource(this, 'CustomResource', { - serviceToken, + serviceToken: serviceToken.functionArn, resourceType: 'Custom::EcsDeploymentGroup', properties: { ApplicationName: this.application.applicationName, DeploymentGroupName: deploymentGroupName, ServiceRoleArn: codeDeployEcsRole.roleArn, - TargetGroupNames: targetGroupNames, + TargetGroupNames: targetGroups.map((tg) => tg.targetGroupName), EcsServices: ecsServices.map((service) => ({ ClusterName: service.clusterName, ServiceName: service.serviceName, })), ProdTrafficListenerArn: prodTrafficListener.listenerArn, TestTrafficListenerArn: testTrafficListener.listenerArn, - TerminationWaitTimeInMinutes: terminationWaitTimeInMinutes, + TerminationWaitTimeInMinutes: terminationWaitTime.toMinutes(), AutoRollbackOnEvents: autoRollbackOnEvents, DeploymentConfigName: this.deploymentConfig.deploymentConfigName, }, diff --git a/packages/cdk-blue-green-container-deployment/src/ecs-service.ts b/packages/cdk-blue-green-container-deployment/src/ecs-service.ts index b8955e437..cf5b4ff18 100644 --- a/packages/cdk-blue-green-container-deployment/src/ecs-service.ts +++ b/packages/cdk-blue-green-container-deployment/src/ecs-service.ts @@ -2,8 +2,9 @@ import * as path from 'path'; import { IConnectable, Connections, SecurityGroup, Port } from '@aws-cdk/aws-ec2'; import { ICluster, LaunchType, DeploymentCircuitBreaker } from '@aws-cdk/aws-ecs'; import { ITargetGroup } from '@aws-cdk/aws-elasticloadbalancingv2'; -import { Effect } from '@aws-cdk/aws-iam'; -import { Duration, Construct, CustomResource, CustomResourceProvider, CustomResourceProviderRuntime } from '@aws-cdk/core'; +import { Effect, PolicyStatement } from '@aws-cdk/aws-iam'; +import { Function, Runtime, Code } from '@aws-cdk/aws-lambda'; +import { Duration, Construct, CustomResource } from '@aws-cdk/core'; import { DummyTaskDefinition } from './dummy-task-definition'; @@ -21,6 +22,7 @@ export interface EcsServiceProps { readonly desiredCount?: number; readonly containerPort?: number; readonly prodTargetGroup: ITargetGroup; + readonly testTargetGroup: ITargetGroup; readonly taskDefinition: DummyTaskDefinition; /** @@ -72,6 +74,7 @@ export class EcsService extends Construct implements IConnectable, IEcsService { platformVersion = '1.4.0', desiredCount = 1, prodTargetGroup, + testTargetGroup, taskDefinition, healthCheckGracePeriod = Duration.seconds(60), } = props; @@ -80,6 +83,8 @@ export class EcsService extends Construct implements IConnectable, IEcsService { const { vpc } = cluster; + this.node.addDependency(prodTargetGroup, testTargetGroup); + const securityGroups = props.securityGroups || [ new SecurityGroup(this, 'SecurityGroup', { description: `Security group for ${this.node.id} service`, @@ -87,25 +92,31 @@ export class EcsService extends Construct implements IConnectable, IEcsService { }), ]; - const serviceToken = CustomResourceProvider.getOrCreate(this, 'Custom::BlueGreenService', { - codeDirectory: path.join(__dirname, 'lambdas', 'ecs-service'), - runtime: CustomResourceProviderRuntime.NODEJS_12_X, - policyStatements: [ - { - Effect: Effect.ALLOW, - Action: ['ecs:CreateService', 'ecs:UpdateService', 'ecs:DeleteService', 'ecs:DescribeServices'], - Resource: '*', - }, - { - Effect: Effect.ALLOW, - Action: ['iam:PassRole'], - Resource: taskDefinition.executionRole.roleArn, - }, - ], + const serviceToken = new Function(this, 'Function', { + runtime: Runtime.NODEJS_12_X, + code: Code.fromAsset(path.join(__dirname, 'lambdas', 'ecs-service')), + handler: 'index.handler', + timeout: Duration.minutes(15), }); + serviceToken.addToRolePolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['ecs:CreateService', 'ecs:UpdateService', 'ecs:DeleteService', 'ecs:DescribeServices'], + resources: ['*'], + }), + ); + + serviceToken.addToRolePolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['iam:PassRole'], + resources: [taskDefinition.executionRole.roleArn], + }), + ); + const service = new CustomResource(this, 'CustomResource', { - serviceToken, + serviceToken: serviceToken.functionArn, resourceType: 'Custom::BlueGreenService', properties: { Cluster: cluster.clusterName, diff --git a/packages/cdk-blue-green-container-deployment/src/lambdas/dummy-task-definition/index.ts b/packages/cdk-blue-green-container-deployment/src/lambdas/dummy-task-definition/index.ts deleted file mode 100644 index b134cbd09..000000000 --- a/packages/cdk-blue-green-container-deployment/src/lambdas/dummy-task-definition/index.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { CloudFormationCustomResourceEvent, CloudFormationCustomResourceCreateEvent, CloudFormationCustomResourceDeleteEvent } from 'aws-lambda'; -import { ECS } from 'aws-sdk'; - -interface HandlerReturn { - PhysicalResourceId: string; -} - -export interface EcsTaskDefinitionProps { - family: string; - image: string; - executionRoleArn: string; - networkMode: string; - containerName: string; - containerPort: number; -} - -const ecs = new ECS(); - -const getProperties = (props: CloudFormationCustomResourceEvent['ResourceProperties']): EcsTaskDefinitionProps => ({ - family: props.Family, - image: props.Image, - executionRoleArn: props.ExecutionRoleArn, - networkMode: props.NetworkMode, - containerName: props.ContainerName, - containerPort: props.ContainerPort, -}); - -const onCreate = async (event: CloudFormationCustomResourceCreateEvent): Promise => { - const { family, image, executionRoleArn, networkMode, containerName, containerPort } = getProperties(event.ResourceProperties); - - const { taskDefinition } = await ecs - .registerTaskDefinition({ - requiresCompatibilities: ['FARGATE'], - family, - executionRoleArn, - networkMode, - cpu: '256', - memory: '512', - containerDefinitions: [ - { - name: containerName, - image, - portMappings: [ - { - hostPort: containerPort, - protocol: 'tcp', - containerPort: containerPort, - }, - ], - }, - ], - }) - .promise(); - - if (!taskDefinition) throw Error('Taskdefinition could not be registerd'); - - return { - PhysicalResourceId: taskDefinition.taskDefinitionArn as string, - }; -}; - -const onDelete = async (event: CloudFormationCustomResourceDeleteEvent): Promise => { - const taskDefinition = event.PhysicalResourceId; - - await ecs - .deregisterTaskDefinition({ - taskDefinition, - }) - .promise(); -}; - -export const handler = async (event: CloudFormationCustomResourceEvent): Promise => { - const requestType = event.RequestType; - - switch (requestType) { - case 'Create': - return onCreate(event); - case 'Update': - // CodeDeploy is responsible for updates on the TaskDefinition - return; - case 'Delete': - return onDelete(event); - default: - throw new Error(`Invalid request type: ${requestType}`); - } -}; diff --git a/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-deployment-group/index.ts b/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-deployment-group/index.ts index 8db3d49a2..fe47f7bf9 100644 --- a/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-deployment-group/index.ts +++ b/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-deployment-group/index.ts @@ -1,10 +1,13 @@ -import type { - CloudFormationCustomResourceEvent, - CloudFormationCustomResourceCreateEvent, - CloudFormationCustomResourceUpdateEvent, - CloudFormationCustomResourceDeleteEvent, -} from 'aws-lambda'; +import type { CloudFormationCustomResourceEvent, CloudFormationCustomResourceUpdateEvent } from 'aws-lambda'; import { CodeDeploy } from 'aws-sdk'; +import { + customResourceHelper, + OnCreateHandler, + OnUpdateHandler, + OnDeleteHandler, + ResourceHandler, + ResourceHandlerReturn, +} from 'custom-resource-helper'; enum RollbackEvent { DEPLOYMENT_FAILURE = 'DEPLOYMENT_FAILURE', @@ -12,10 +15,6 @@ enum RollbackEvent { DEPLOYMENT_STOP_ON_REQUEST = 'DEPLOYMENT_STOP_ON_REQUEST', } -interface HandlerReturn { - PhysicalResourceId: string; -} - export interface EcsDeploymentGroupProps { applicationName: string; deploymentGroupName: string; @@ -49,7 +48,7 @@ const getProperties = ( deploymentConfigName: props.DeploymentConfigName, }); -const onCreate = async (event: CloudFormationCustomResourceCreateEvent): Promise => { +const handleCreate: OnCreateHandler = async (event): Promise => { const { applicationName, deploymentGroupName, @@ -106,11 +105,11 @@ const onCreate = async (event: CloudFormationCustomResourceCreateEvent): Promise .promise(); return { - PhysicalResourceId: deploymentGroupName, + physicalResourceId: deploymentGroupName, }; }; -const onUpdate = async (event: CloudFormationCustomResourceUpdateEvent): Promise => { +const handleUpdate: OnUpdateHandler = async (event): Promise => { const newProps = getProperties(event.ResourceProperties); const oldProps = getProperties(event.OldResourceProperties); @@ -153,11 +152,11 @@ const onUpdate = async (event: CloudFormationCustomResourceUpdateEvent): Promise .promise(); return { - PhysicalResourceId: newProps.deploymentGroupName, + physicalResourceId: newProps.deploymentGroupName, }; }; -const onDelete = async (event: CloudFormationCustomResourceDeleteEvent): Promise => { +const handleDelete: OnDeleteHandler = async (event): Promise => { const { applicationName, deploymentGroupName } = getProperties(event.ResourceProperties); await codeDeploy @@ -168,17 +167,10 @@ const onDelete = async (event: CloudFormationCustomResourceDeleteEvent): Promise .promise(); }; -export const handler = async (event: CloudFormationCustomResourceEvent): Promise => { - const requestType = event.RequestType; - - switch (requestType) { - case 'Create': - return onCreate(event); - case 'Update': - return onUpdate(event); - case 'Delete': - return onDelete(event); - default: - throw new Error(`Invalid request type: ${requestType}`); - } -}; +export const handler = customResourceHelper( + (): ResourceHandler => ({ + onCreate: handleCreate, + onUpdate: handleUpdate, + onDelete: handleDelete, + }), +); diff --git a/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-service/index.ts b/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-service/index.ts index 2d34ed836..3f0e6f8e2 100644 --- a/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-service/index.ts +++ b/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-service/index.ts @@ -1,17 +1,13 @@ -import type { - CloudFormationCustomResourceEvent, - CloudFormationCustomResourceCreateEvent, - CloudFormationCustomResourceUpdateEvent, - CloudFormationCustomResourceDeleteEvent, -} from 'aws-lambda'; +import type { CloudFormationCustomResourceEvent } from 'aws-lambda'; import { ECS } from 'aws-sdk'; - -interface HandlerReturn { - PhysicalResourceId: string; - Data: { - ServiceName: string; - }; -} +import { + customResourceHelper, + OnCreateHandler, + OnUpdateHandler, + OnDeleteHandler, + ResourceHandler, + ResourceHandlerReturn, +} from 'custom-resource-helper'; export interface BlueGreenServiceProps { cluster: string; @@ -49,7 +45,7 @@ const getProperties = (props: CloudFormationCustomResourceEvent['ResourcePropert deploymentConfiguration: props.DeploymentConfiguration, }); -const onCreate = async (event: CloudFormationCustomResourceCreateEvent): Promise => { +const handleCreate: OnCreateHandler = async (event): Promise => { const { cluster, serviceName, @@ -100,8 +96,8 @@ const onCreate = async (event: CloudFormationCustomResourceCreateEvent): Promise if (!service) throw Error('Service could not be created'); return { - PhysicalResourceId: service.serviceArn as string, - Data: { + physicalResourceId: service.serviceArn as string, + responseData: { ServiceName: service.serviceName as string, }, }; @@ -115,7 +111,7 @@ const onCreate = async (event: CloudFormationCustomResourceCreateEvent): Promise * updated, a new AWS CodeDeploy deployment should be created. * For more information, see CreateDeployment in the AWS CodeDeploy API Reference. */ -const onUpdate = async (event: CloudFormationCustomResourceUpdateEvent): Promise => { +const handleUpdate: OnUpdateHandler = async (event): Promise => { const { cluster, serviceName, desiredCount, deploymentConfiguration, healthCheckGracePeriodSeconds } = getProperties(event.ResourceProperties); const { service } = await ecs @@ -131,14 +127,14 @@ const onUpdate = async (event: CloudFormationCustomResourceUpdateEvent): Promise if (!service) throw Error('Service could not be updated'); return { - PhysicalResourceId: service.serviceArn as string, - Data: { + physicalResourceId: service.serviceArn as string, + responseData: { ServiceName: service.serviceName as string, }, }; }; -const onDelete = async (event: CloudFormationCustomResourceDeleteEvent): Promise => { +const handleDelete: OnDeleteHandler = async (event): Promise => { const { cluster, serviceName } = getProperties(event.ResourceProperties); await ecs @@ -157,17 +153,10 @@ const onDelete = async (event: CloudFormationCustomResourceDeleteEvent): Promise .promise(); }; -export const handler = async (event: CloudFormationCustomResourceEvent): Promise => { - const requestType = event.RequestType; - - switch (requestType) { - case 'Create': - return onCreate(event); - case 'Update': - return onUpdate(event); - case 'Delete': - return onDelete(event); - default: - throw new Error(`Invalid request type: ${requestType}`); - } -}; +export const handler = customResourceHelper( + (): ResourceHandler => ({ + onCreate: handleCreate, + onUpdate: handleUpdate, + onDelete: handleDelete, + }), +); From e18441dd14b01b51640ce5292357f66a930fef50 Mon Sep 17 00:00:00 2001 From: hupe1980 Date: Sun, 17 Oct 2021 15:47:38 +0200 Subject: [PATCH 2/2] fix: clamav version --- packages/cdk-s3-antivirus/src/layers/clamav/Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/cdk-s3-antivirus/src/layers/clamav/Dockerfile b/packages/cdk-s3-antivirus/src/layers/clamav/Dockerfile index 8db588e9d..8cdeaa218 100644 --- a/packages/cdk-s3-antivirus/src/layers/clamav/Dockerfile +++ b/packages/cdk-s3-antivirus/src/layers/clamav/Dockerfile @@ -1,7 +1,7 @@ FROM public.ecr.aws/lambda/provided:al2.2021.09.13.11 ARG asset_name=layer -ARG CLAMAV_VERSION=0.103.3-5.el7 +ARG CLAMAV_VERSION=0.103.3-8.el7 USER root RUN mkdir -p /opt/{lib,clamav} @@ -10,8 +10,7 @@ RUN mkdir -p /opt/{lib,clamav} # tools # RUN yum install -y zip yum-utils amazon-linux-extras -RUN amazon-linux-extras install epel -y -RUN yum-config-manager --enable epel +RUN amazon-linux-extras install epel -y && yum-config-manager --enable epel # # layer