diff --git a/.github/workflows/yarn-upgrade.yml b/.github/workflows/yarn-upgrade.yml index 13d551cf740cf..c0df170ff30ef 100644 --- a/.github/workflows/yarn-upgrade.yml +++ b/.github/workflows/yarn-upgrade.yml @@ -1,9 +1,10 @@ name: Yarn Upgrade on: - schedule: + # Disable this workflow + #schedule: # Every wednesday at 13:37 UTC - - cron: 37 13 * * 3 + #- cron: 37 13 * * 3 workflow_dispatch: {} jobs: diff --git a/CHANGELOG.md b/CHANGELOG.md index 63aa4c3bf3be9..30f83c5d5d399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.104.0](https://github.com/aws/aws-cdk/compare/v1.103.0...v1.104.0) (2021-05-14) + + +### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES + +* **apigatewayv2:** setting the authorizer of an API route to HttpNoneAuthorizer will now remove any existing authorizer on the route + +### Features + +* **appsync:** elasticsearch data source for graphql api ([#14651](https://github.com/aws/aws-cdk/issues/14651)) ([2337b5d](https://github.com/aws/aws-cdk/commit/2337b5d965028ba06d6ff72f991c0b8e46433a8f)), closes [#6063](https://github.com/aws/aws-cdk/issues/6063) +* **cfnspec:** cloudformation spec v35.2.0 ([#14610](https://github.com/aws/aws-cdk/issues/14610)) ([799ce1a](https://github.com/aws/aws-cdk/commit/799ce1a7d5fb261cae92d514b4f7e315d8f0e589)) +* **cloudwatch:** GraphWidget supports period and statistic ([#14679](https://github.com/aws/aws-cdk/issues/14679)) ([b240f6e](https://github.com/aws/aws-cdk/commit/b240f6ece74d129e5f43b210e8ad12f95c4a2971)) +* **cloudwatch:** time range support for GraphWidget ([#14659](https://github.com/aws/aws-cdk/issues/14659)) ([010a6b1](https://github.com/aws/aws-cdk/commit/010a6b1a14f14be5001779644df3d3a2e27d4e71)), closes [#4649](https://github.com/aws/aws-cdk/issues/4649) +* **ecs:** add support for EC2 Capacity Providers ([#14386](https://github.com/aws/aws-cdk/issues/14386)) ([114f7cc](https://github.com/aws/aws-cdk/commit/114f7ccdaf736988834fe2be487363a992a31369)) +* **secretsmanager:** Automatically grant permissions to rotation Lambda ([#14471](https://github.com/aws/aws-cdk/issues/14471)) ([85e00fa](https://github.com/aws/aws-cdk/commit/85e00faf1e3bcc32c2f7aa881d42c6d1f6c17f63)) + + +### Bug Fixes + +* **apigatewayv2:** authorizer is not removed when HttpNoneAuthorizer is used ([#14424](https://github.com/aws/aws-cdk/issues/14424)) ([3698a91](https://github.com/aws/aws-cdk/commit/3698a91ac81a31f763c55487f200458d5b5eaf0f)) +* **ecs:** Classes FargateService and Ec2Service have no defaultChild ([#14691](https://github.com/aws/aws-cdk/issues/14691)) ([348e11e](https://github.com/aws/aws-cdk/commit/348e11e26edc0ff90b623b7cec778f4935e61e6d)), closes [#14665](https://github.com/aws/aws-cdk/issues/14665) +* **events-targets:** circular dependency when adding a KMS-encrypted SQS queue ([#14638](https://github.com/aws/aws-cdk/issues/14638)) ([3063818](https://github.com/aws/aws-cdk/commit/3063818aa7c3c3ff56cf55254b0f6561db190a3e)), closes [#11158](https://github.com/aws/aws-cdk/issues/11158) +* **lambda:** custom resource fails to connect to efs filesystem ([#14431](https://github.com/aws/aws-cdk/issues/14431)) ([10a633c](https://github.com/aws/aws-cdk/commit/10a633c8cda9f21b85c82f911d88641f3a362c4d)) +* **lambda-event-sources:** incorrect documented defaults for stream types ([#14562](https://github.com/aws/aws-cdk/issues/14562)) ([0ea24e9](https://github.com/aws/aws-cdk/commit/0ea24e95939412765c0e09133a7793557f779c76)), closes [#13908](https://github.com/aws/aws-cdk/issues/13908) +* **lambda-nodejs:** handler filename missing from error message ([#14564](https://github.com/aws/aws-cdk/issues/14564)) ([256fd4c](https://github.com/aws/aws-cdk/commit/256fd4c6fcdbe6519bc70f62415557dbeae950a1)) + ## [1.103.0](https://github.com/aws/aws-cdk/compare/v1.102.0...v1.103.0) (2021-05-10) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/CONTRIBUTING.md b/packages/@aws-cdk-containers/ecs-service-extensions/CONTRIBUTING.md new file mode 100644 index 0000000000000..58d8e97ba78e7 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/CONTRIBUTING.md @@ -0,0 +1 @@ +See: [Contributing Guide](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-ecs/README.md) diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md index ae44e5e5d97fc..3e888e57c9a9c 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md @@ -24,6 +24,7 @@ - [Route Authorization](#route-authorization) - [JWT Authorizers](#jwt-authorizers) - [User Pool Authorizer](#user-pool-authorizer) +- [Lambda Authorizers](#lambda-authorizers) ## Introduction @@ -162,3 +163,32 @@ api.addRoutes({ authorizer, }); ``` + +## Lambda Authorizers + +Lambda authorizers use a Lambda function to control access to your HTTP API. When a client calls your API, API Gateway invokes your Lambda function and uses the response to determine whether the client can access your API. + +Lambda authorizers depending on their response, fall into either two types - Simple or IAM. You can learn about differences [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html#http-api-lambda-authorizer.payload-format-response). + + +```ts +// This function handles your auth logic +const authHandler = new Function(this, 'auth-function', { + //... +}); + +const authorizer = new HttpLambdaAuthorizer({ + responseTypes: [HttpLambdaAuthorizerType.SIMPLE] // Define if returns simple and/or iam response + handler: authHandler, +}); + +const api = new HttpApi(stack, 'HttpApi'); + +api.addRoutes({ + integration: new HttpProxyIntegration({ + url: 'https://get-books-proxy.myproxy.internal', + }), + path: '/books', + authorizer, +}); +``` diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/index.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/index.ts index 9f9ad94c6a4b7..410cc8aa09f2e 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/index.ts @@ -1,2 +1,3 @@ export * from './user-pool'; -export * from './jwt'; \ No newline at end of file +export * from './jwt'; +export * from './lambda'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/jwt.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/jwt.ts index afb5f10ac07f8..184d02f3382b6 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/jwt.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/jwt.ts @@ -64,7 +64,7 @@ export class HttpJwtAuthorizer implements IHttpRouteAuthorizer { return { authorizerId: this.authorizer.authorizerId, - authorizationType: HttpAuthorizerType.JWT, + authorizationType: 'JWT', }; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/lambda.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/lambda.ts new file mode 100644 index 0000000000000..fcb4f327c08a2 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/lambda.ts @@ -0,0 +1,130 @@ +import { + HttpAuthorizer, + HttpAuthorizerType, + HttpRouteAuthorizerBindOptions, + HttpRouteAuthorizerConfig, + IHttpRouteAuthorizer, + AuthorizerPayloadVersion, + IHttpApi, +} from '@aws-cdk/aws-apigatewayv2'; +import { ServicePrincipal } from '@aws-cdk/aws-iam'; +import { IFunction } from '@aws-cdk/aws-lambda'; +import { Stack, Duration, Names } from '@aws-cdk/core'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + +/** + * Specifies the type responses the lambda returns + */ +export enum HttpLambdaResponseType { + /** Returns simple boolean response */ + SIMPLE, + + /** Returns an IAM Policy */ + IAM, +} + +/** + * Properties to initialize HttpTokenAuthorizer. + */ +export interface HttpLambdaAuthorizerProps { + + /** + * The name of the authorizer + */ + readonly authorizerName: string; + + /** + * The identity source for which authorization is requested. + * + * @default ['$request.header.Authorization'] + */ + readonly identitySource?: string[]; + + /** + * The lambda function used for authorization + */ + readonly handler: IFunction; + + /** + * How long APIGateway should cache the results. Max 1 hour. + * Disable caching by setting this to `Duration.seconds(0)`. + * + * @default Duration.minutes(5) + */ + readonly resultsCacheTtl?: Duration; + + /** + * The types of responses the lambda can return + * + * If HttpLambdaResponseType.SIMPLE is included then + * response format 2.0 will be used. + * + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html#http-api-lambda-authorizer.payload-format-response + * + * @default [HttpLambdaResponseType.IAM] + */ + readonly responseTypes?: HttpLambdaResponseType[]; +} + +/** + * Authorize Http Api routes via a lambda function + */ +export class HttpLambdaAuthorizer implements IHttpRouteAuthorizer { + private authorizer?: HttpAuthorizer; + private httpApi?: IHttpApi; + + constructor(private readonly props: HttpLambdaAuthorizerProps) { + } + + public bind(options: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig { + if (this.httpApi && (this.httpApi.apiId !== options.route.httpApi.apiId)) { + throw new Error('Cannot attach the same authorizer to multiple Apis'); + } + + if (!this.authorizer) { + const id = this.props.authorizerName; + + const responseTypes = this.props.responseTypes ?? [HttpLambdaResponseType.IAM]; + const enableSimpleResponses = responseTypes.includes(HttpLambdaResponseType.SIMPLE) || undefined; + + this.httpApi = options.route.httpApi; + this.authorizer = new HttpAuthorizer(options.scope, id, { + httpApi: options.route.httpApi, + identitySource: this.props.identitySource ?? [ + '$request.header.Authorization', + ], + type: HttpAuthorizerType.LAMBDA, + authorizerName: this.props.authorizerName, + enableSimpleResponses, + payloadFormatVersion: enableSimpleResponses ? AuthorizerPayloadVersion.VERSION_2_0 : AuthorizerPayloadVersion.VERSION_1_0, + authorizerUri: lambdaAuthorizerArn(this.props.handler), + resultsCacheTtl: this.props.resultsCacheTtl ?? Duration.minutes(5), + }); + + this.props.handler.addPermission(`${Names.nodeUniqueId(this.authorizer.node)}-Permission`, { + scope: options.scope as CoreConstruct, + principal: new ServicePrincipal('apigateway.amazonaws.com'), + sourceArn: Stack.of(options.route).formatArn({ + service: 'execute-api', + resource: options.route.httpApi.apiId, + resourceName: `authorizers/${this.authorizer.authorizerId}`, + }), + }); + } + + return { + authorizerId: this.authorizer.authorizerId, + authorizationType: 'CUSTOM', + }; + } +} + +/** + * constructs the authorizerURIArn. + */ +function lambdaAuthorizerArn(handler: IFunction) { + return `arn:${Stack.of(handler).partition}:apigateway:${Stack.of(handler).region}:lambda:path/2015-03-31/functions/${handler.functionArn}/invocations`; +} diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/user-pool.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/user-pool.ts index 4a251b8eb7406..702a3a05576ec 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/user-pool.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/user-pool.ts @@ -63,7 +63,7 @@ export class HttpUserPoolAuthorizer implements IHttpRouteAuthorizer { return { authorizerId: this.authorizer.authorizerId, - authorizationType: HttpAuthorizerType.JWT, + authorizationType: 'JWT', }; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json index c70e7a5dd8115..58de08da038e4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json @@ -82,12 +82,16 @@ "dependencies": { "@aws-cdk/aws-apigatewayv2": "0.0.0", "@aws-cdk/aws-cognito": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.3.69" }, "peerDependencies": { "@aws-cdk/aws-apigatewayv2": "0.0.0", "@aws-cdk/aws-cognito": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.3.69" }, diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/auth-handler/index.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/auth-handler/index.ts new file mode 100644 index 0000000000000..f08c1bdb1b42a --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/auth-handler/index.ts @@ -0,0 +1,9 @@ +/* eslint-disable no-console */ + +export const handler = async (event: AWSLambda.APIGatewayProxyEventV2) => { + const key = event.headers['x-api-key']; + + return { + isAuthorized: key === '123', + }; +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.lambda.expected.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.lambda.expected.json new file mode 100644 index 0000000000000..69c407f274908 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.lambda.expected.json @@ -0,0 +1,397 @@ +{ + "Resources": { + "MyHttpApi8AEAAC21": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "MyHttpApi", + "ProtocolType": "HTTP" + } + }, + "MyHttpApiDefaultStageDCB9BC49": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyHttpApi8AEAAC21" + }, + "StageName": "$default", + "AutoDeploy": true + } + }, + "MyHttpApiGETAuthorizerIntegMyHttpApiGET16D02385PermissionBB02EBFE": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "lambda8B5974B5", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "MyHttpApi8AEAAC21" + }, + "/*/*/" + ] + ] + } + } + }, + "MyHttpApiGETHttpIntegration6f095b8469365f72e33fa33d9711b140516EBE31": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "MyHttpApi8AEAAC21" + }, + "IntegrationType": "AWS_PROXY", + "IntegrationUri": { + "Fn::GetAtt": [ + "lambda8B5974B5", + "Arn" + ] + }, + "PayloadFormatVersion": "2.0" + } + }, + "MyHttpApiGETE0EFC6F8": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "MyHttpApi8AEAAC21" + }, + "RouteKey": "GET /", + "AuthorizationType": "CUSTOM", + "AuthorizerId": { + "Ref": "MyHttpApimysimpleauthorizer98398C16" + }, + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "MyHttpApiGETHttpIntegration6f095b8469365f72e33fa33d9711b140516EBE31" + } + ] + ] + } + } + }, + "MyHttpApimysimpleauthorizer98398C16": { + "Type": "AWS::ApiGatewayV2::Authorizer", + "Properties": { + "ApiId": { + "Ref": "MyHttpApi8AEAAC21" + }, + "AuthorizerType": "REQUEST", + "IdentitySource": [ + "$request.header.X-API-Key" + ], + "Name": "my-simple-authorizer", + "AuthorizerPayloadFormatVersion": "2.0", + "AuthorizerResultTtlInSeconds": 300, + "AuthorizerUri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "authfunction96361832", + "Arn" + ] + }, + "/invocations" + ] + ] + }, + "EnableSimpleResponses": true + } + }, + "MyHttpApiAuthorizerIntegMyHttpApimysimpleauthorizer0F14A472PermissionF37EF5C8": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "authfunction96361832", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "MyHttpApi8AEAAC21" + }, + "/authorizers/", + { + "Ref": "MyHttpApimysimpleauthorizer98398C16" + } + ] + ] + } + } + }, + "authfunctionServiceRoleFCB72198": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "authfunction96361832": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters7f2fe4e4fa40a84f0f773203f5c5fdaac31c80ce42c5185ed2659a049db03043S3BucketC7E46972" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters7f2fe4e4fa40a84f0f773203f5c5fdaac31c80ce42c5185ed2659a049db03043S3VersionKeyA8ECA032" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters7f2fe4e4fa40a84f0f773203f5c5fdaac31c80ce42c5185ed2659a049db03043S3VersionKeyA8ECA032" + } + ] + } + ] + } + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "authfunctionServiceRoleFCB72198", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "authfunctionServiceRoleFCB72198" + ] + }, + "lambdaServiceRole494E4CA6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "lambda8B5974B5": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters1fd1c15cb7d5e2e36a11745fd10b4b7c3ca8eb30642b41954630413d2b913cdaS3Bucket2E6D85D3" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters1fd1c15cb7d5e2e36a11745fd10b4b7c3ca8eb30642b41954630413d2b913cdaS3VersionKey22B8E7C6" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters1fd1c15cb7d5e2e36a11745fd10b4b7c3ca8eb30642b41954630413d2b913cdaS3VersionKey22B8E7C6" + } + ] + } + ] + } + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "lambdaServiceRole494E4CA6", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs12.x" + }, + "DependsOn": [ + "lambdaServiceRole494E4CA6" + ] + } + }, + "Parameters": { + "AssetParameters7f2fe4e4fa40a84f0f773203f5c5fdaac31c80ce42c5185ed2659a049db03043S3BucketC7E46972": { + "Type": "String", + "Description": "S3 bucket for asset \"7f2fe4e4fa40a84f0f773203f5c5fdaac31c80ce42c5185ed2659a049db03043\"" + }, + "AssetParameters7f2fe4e4fa40a84f0f773203f5c5fdaac31c80ce42c5185ed2659a049db03043S3VersionKeyA8ECA032": { + "Type": "String", + "Description": "S3 key for asset version \"7f2fe4e4fa40a84f0f773203f5c5fdaac31c80ce42c5185ed2659a049db03043\"" + }, + "AssetParameters7f2fe4e4fa40a84f0f773203f5c5fdaac31c80ce42c5185ed2659a049db03043ArtifactHashE679D99A": { + "Type": "String", + "Description": "Artifact hash for asset \"7f2fe4e4fa40a84f0f773203f5c5fdaac31c80ce42c5185ed2659a049db03043\"" + }, + "AssetParameters1fd1c15cb7d5e2e36a11745fd10b4b7c3ca8eb30642b41954630413d2b913cdaS3Bucket2E6D85D3": { + "Type": "String", + "Description": "S3 bucket for asset \"1fd1c15cb7d5e2e36a11745fd10b4b7c3ca8eb30642b41954630413d2b913cda\"" + }, + "AssetParameters1fd1c15cb7d5e2e36a11745fd10b4b7c3ca8eb30642b41954630413d2b913cdaS3VersionKey22B8E7C6": { + "Type": "String", + "Description": "S3 key for asset version \"1fd1c15cb7d5e2e36a11745fd10b4b7c3ca8eb30642b41954630413d2b913cda\"" + }, + "AssetParameters1fd1c15cb7d5e2e36a11745fd10b4b7c3ca8eb30642b41954630413d2b913cdaArtifactHash82A279EA": { + "Type": "String", + "Description": "Artifact hash for asset \"1fd1c15cb7d5e2e36a11745fd10b4b7c3ca8eb30642b41954630413d2b913cda\"" + } + }, + "Outputs": { + "URL": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "MyHttpApi8AEAAC21" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.lambda.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.lambda.ts new file mode 100644 index 0000000000000..264da5f4bf510 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.lambda.ts @@ -0,0 +1,49 @@ +import * as path from 'path'; +import { HttpApi, HttpMethod } from '@aws-cdk/aws-apigatewayv2'; +import { LambdaProxyIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; +import * as lambda from '@aws-cdk/aws-lambda'; +import { App, Stack, CfnOutput } from '@aws-cdk/core'; +import { HttpLambdaAuthorizer, HttpLambdaResponseType } from '../../lib'; + +/* + * Stack verification steps: + * * `curl -H 'X-API-Key: 123' ` should return 200 + * * `curl ` should return 401 + * * `curl -H 'X-API-Key: 1234' ` should return 403 + */ + +const app = new App(); +const stack = new Stack(app, 'AuthorizerInteg'); + +const httpApi = new HttpApi(stack, 'MyHttpApi'); + +const authHandler = new lambda.Function(stack, 'auth-function', { + runtime: lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + code: lambda.Code.fromAsset(path.join(__dirname, '../auth-handler')), +}); + + +const authorizer = new HttpLambdaAuthorizer({ + authorizerName: 'my-simple-authorizer', + identitySource: ['$request.header.X-API-Key'], + handler: authHandler, + responseTypes: [HttpLambdaResponseType.SIMPLE], +}); + +const handler = new lambda.Function(stack, 'lambda', { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.AssetCode.fromAsset(path.join(__dirname, '../integ.lambda.handler')), +}); + +httpApi.addRoutes({ + path: '/', + methods: [HttpMethod.GET], + integration: new LambdaProxyIntegration({ handler }), + authorizer, +}); + +new CfnOutput(stack, 'URL', { + value: httpApi.url!, +}); diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/lambda.test.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/lambda.test.ts new file mode 100644 index 0000000000000..a9efd500e6bf1 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/lambda.test.ts @@ -0,0 +1,182 @@ +import '@aws-cdk/assert-internal/jest'; +import { ABSENT } from '@aws-cdk/assert-internal'; +import { HttpApi, HttpIntegrationType, HttpRouteIntegrationBindOptions, IHttpRouteIntegration, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2'; +import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; +import { Duration, Stack } from '@aws-cdk/core'; +import { HttpLambdaAuthorizer, HttpLambdaResponseType } from '../../lib'; + +describe('HttpLambdaAuthorizer', () => { + + test('default', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + + const handler = new Function(stack, 'auth-function', { + runtime: Runtime.NODEJS_12_X, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', + }); + + const authorizer = new HttpLambdaAuthorizer({ + authorizerName: 'default-authorizer', + handler, + }); + + // WHEN + api.addRoutes({ + integration: new DummyRouteIntegration(), + path: '/books', + authorizer, + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::Authorizer', { + Name: 'default-authorizer', + AuthorizerType: 'REQUEST', + AuthorizerResultTtlInSeconds: 300, + AuthorizerPayloadFormatVersion: '1.0', + IdentitySource: [ + '$request.header.Authorization', + ], + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Route', { + AuthorizationType: 'CUSTOM', + }); + }); + + test('should use format 2.0 and simple responses when simple response type is requested', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + + const handler = new Function(stack, 'auth-function', { + runtime: Runtime.NODEJS_12_X, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', + }); + + const authorizer = new HttpLambdaAuthorizer({ + authorizerName: 'my-simple-authorizer', + responseTypes: [HttpLambdaResponseType.SIMPLE], + handler, + }); + + // WHEN + api.addRoutes({ + integration: new DummyRouteIntegration(), + path: '/books', + authorizer, + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::Authorizer', { + AuthorizerPayloadFormatVersion: '2.0', + EnableSimpleResponses: true, + }); + }); + + test('should use format 1.0 when only IAM response type is requested', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + + const handler = new Function(stack, 'auth-function', { + runtime: Runtime.NODEJS_12_X, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', + }); + + const authorizer = new HttpLambdaAuthorizer({ + authorizerName: 'my-iam-authorizer', + responseTypes: [HttpLambdaResponseType.IAM], + handler, + }); + + // WHEN + api.addRoutes({ + integration: new DummyRouteIntegration(), + path: '/books', + authorizer, + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::Authorizer', { + AuthorizerPayloadFormatVersion: '1.0', + EnableSimpleResponses: ABSENT, + }); + }); + + test('should use format 2.0 and simple responses when both response types are requested', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + + const handler = new Function(stack, 'auth-function', { + runtime: Runtime.NODEJS_12_X, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', + }); + + const authorizer = new HttpLambdaAuthorizer({ + authorizerName: 'my-simple-iam-authorizer', + responseTypes: [HttpLambdaResponseType.IAM, HttpLambdaResponseType.SIMPLE], + handler, + }); + + // WHEN + api.addRoutes({ + integration: new DummyRouteIntegration(), + path: '/books', + authorizer, + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::Authorizer', { + AuthorizerPayloadFormatVersion: '2.0', + EnableSimpleResponses: true, + }); + }); + + test('can override cache ttl', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + + const handler = new Function(stack, 'auth-functon', { + runtime: Runtime.NODEJS_12_X, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', + }); + + const authorizer = new HttpLambdaAuthorizer({ + authorizerName: 'my-simple-authorizer', + responseTypes: [HttpLambdaResponseType.SIMPLE], + handler, + resultsCacheTtl: Duration.minutes(10), + }); + + // WHEN + api.addRoutes({ + integration: new DummyRouteIntegration(), + path: '/books', + authorizer, + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::Authorizer', { + AuthorizerResultTtlInSeconds: 600, + }); + }); +}); + +class DummyRouteIntegration implements IHttpRouteIntegration { + public bind(_: HttpRouteIntegrationBindOptions) { + return { + payloadFormatVersion: PayloadFormatVersion.VERSION_2_0, + type: HttpIntegrationType.HTTP_PROXY, + uri: 'some-uri', + }; + } +} diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/integ.lambda.handler/index.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/integ.lambda.handler/index.ts new file mode 100644 index 0000000000000..def194e303e1e --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/integ.lambda.handler/index.ts @@ -0,0 +1,9 @@ +export const handler = async () => { + return { + statusCode: 200, + body: JSON.stringify({ message: 'Hello from authenticated lambda' }), + headers: { + 'Content-Type': 'application/json', + }, + }; +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/authorizer.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/authorizer.ts index 297abf12e78e2..08936ecf36d8f 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/authorizer.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/authorizer.ts @@ -1,4 +1,4 @@ -import { Resource } from '@aws-cdk/core'; +import { Duration, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnAuthorizer } from '../apigatewayv2.generated'; @@ -15,9 +15,18 @@ export enum HttpAuthorizerType { /** Lambda Authorizer */ LAMBDA = 'REQUEST', +} + +/** + * Payload format version for lambda authorizers + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html + */ +export enum AuthorizerPayloadVersion { + /** Version 1.0 */ + VERSION_1_0 = '1.0', - /** No authorizer */ - NONE = 'NONE' + /** Version 2.0 */ + VERSION_2_0 = '2.0' } /** @@ -58,6 +67,38 @@ export interface HttpAuthorizerProps { * @default - required for JWT authorizer types. */ readonly jwtIssuer?: string; + + /** + * Specifies whether a Lambda authorizer returns a response in a simple format. + * + * If enabled, the Lambda authorizer can return a boolean value instead of an IAM policy. + * + * @default - The lambda authorizer must return an IAM policy as its response + */ + readonly enableSimpleResponses?: boolean; + + /** + * Specifies the format of the payload sent to an HTTP API Lambda authorizer. + * + * @default AuthorizerPayloadVersion.VERSION_2_0 if the authorizer type is HttpAuthorizerType.LAMBDA + */ + readonly payloadFormatVersion?: AuthorizerPayloadVersion; + + /** + * The authorizer's Uniform Resource Identifier (URI). + * + * For REQUEST authorizers, this must be a well-formed Lambda function URI. + * + * @default - required for Request authorizer types + */ + readonly authorizerUri?: string; + + /** + * How long APIGateway should cache the results. Max 1 hour. + * + * @default - API Gateway will not cache authorizer responses + */ + readonly resultsCacheTtl?: Duration; } /** @@ -77,8 +118,13 @@ export interface HttpAuthorizerAttributes { /** * Type of authorizer + * + * Possible values are: + * - JWT - JSON Web Token Authorizer + * - CUSTOM - Lambda Authorizer + * - NONE - No Authorization */ - readonly authorizerType: HttpAuthorizerType + readonly authorizerType: string } /** @@ -109,10 +155,24 @@ export class HttpAuthorizer extends Resource implements IHttpAuthorizer { constructor(scope: Construct, id: string, props: HttpAuthorizerProps) { super(scope, id); + let authorizerPayloadFormatVersion = props.payloadFormatVersion; + if (props.type === HttpAuthorizerType.JWT && (!props.jwtAudience || props.jwtAudience.length === 0 || !props.jwtIssuer)) { throw new Error('jwtAudience and jwtIssuer are mandatory for JWT authorizers'); } + if (props.type === HttpAuthorizerType.LAMBDA && !props.authorizerUri) { + throw new Error('authorizerUri is mandatory for Lambda authorizers'); + } + + /** + * This check is required because Cloudformation will fail stack creation is this property + * is set for the JWT authorizer. AuthorizerPayloadFormatVersion can only be set for REQUEST authorizer + */ + if (props.type === HttpAuthorizerType.LAMBDA && typeof authorizerPayloadFormatVersion === 'undefined') { + authorizerPayloadFormatVersion = AuthorizerPayloadVersion.VERSION_2_0; + } + const resource = new CfnAuthorizer(this, 'Resource', { name: props.authorizerName ?? id, apiId: props.httpApi.apiId, @@ -122,6 +182,10 @@ export class HttpAuthorizer extends Resource implements IHttpAuthorizer { audience: props.jwtAudience, issuer: props.jwtIssuer, }), + enableSimpleResponses: props.enableSimpleResponses, + authorizerPayloadFormatVersion, + authorizerUri: props.authorizerUri, + authorizerResultTtlInSeconds: props.resultsCacheTtl?.toSeconds(), }); this.authorizerId = resource.ref; @@ -152,10 +216,17 @@ export interface HttpRouteAuthorizerConfig { * @default - No authorizer id (useful for AWS_IAM route authorizer) */ readonly authorizerId?: string; + /** * The type of authorization + * + * Possible values are: + * - JWT - JSON Web Token Authorizer + * - CUSTOM - Lambda Authorizer + * - NONE - No Authorization */ - readonly authorizationType: HttpAuthorizerType; + readonly authorizationType: string; + /** * The list of OIDC scopes to include in the authorization. * @default - no authorization scopes @@ -184,7 +255,7 @@ function undefinedIfNoKeys(obj: A): A | undefined { export class HttpNoneAuthorizer implements IHttpRouteAuthorizer { public bind(_: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig { return { - authorizationType: HttpAuthorizerType.NONE, + authorizationType: 'NONE', }; } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts index 5178281d08953..a88aaae0b3416 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts @@ -3,7 +3,7 @@ import { Construct } from 'constructs'; import { CfnRoute, CfnRouteProps } from '../apigatewayv2.generated'; import { IRoute } from '../common'; import { IHttpApi } from './api'; -import { HttpAuthorizerType, IHttpRouteAuthorizer } from './authorizer'; +import { IHttpRouteAuthorizer } from './authorizer'; import { IHttpRouteIntegration } from './integration'; /** @@ -120,6 +120,20 @@ export interface HttpRouteProps extends BatchHttpRouteOptions { readonly authorizationScopes?: string[]; } +/** + * Supported Route Authorizer types + */ +enum HttpRouteAuthorizationType { + /** JSON Web Tokens */ + JWT = 'JWT', + + /** Lambda Authorizer */ + CUSTOM = 'CUSTOM', + + /** No authorizer */ + NONE = 'NONE' +} + /** * Route class that creates the Route for API Gateway HTTP API * @resource AWS::ApiGatewayV2::Route @@ -147,6 +161,10 @@ export class HttpRoute extends Resource implements IHttpRoute { scope: this.httpApi instanceof Construct ? this.httpApi : this, // scope under the API if it's not imported }) : undefined; + if (authBindResult && !(authBindResult.authorizationType in HttpRouteAuthorizationType)) { + throw new Error('authorizationType should either be JWT, CUSTOM, or NONE'); + } + let authorizationScopes = authBindResult?.authorizationScopes; if (authBindResult && props.authorizationScopes) { @@ -165,7 +183,7 @@ export class HttpRoute extends Resource implements IHttpRoute { routeKey: props.routeKey.key, target: `integrations/${integration.integrationId}`, authorizerId: authBindResult?.authorizerId, - authorizationType: authBindResult?.authorizationType ?? HttpAuthorizerType.NONE, // must be explicitly NONE (not undefined) for stack updates to work correctly + authorizationType: authBindResult?.authorizationType ?? 'NONE', // must be explicitly NONE (not undefined) for stack updates to work correctly authorizationScopes, }; diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts index 12d2c68aa0ecb..3b07593676c11 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts @@ -5,7 +5,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import { Duration, Stack } from '@aws-cdk/core'; import { CorsHttpMethod, - HttpApi, HttpAuthorizer, HttpAuthorizerType, HttpIntegrationType, HttpMethod, HttpRouteAuthorizerBindOptions, HttpRouteAuthorizerConfig, + HttpApi, HttpAuthorizer, HttpIntegrationType, HttpMethod, HttpRouteAuthorizerBindOptions, HttpRouteAuthorizerConfig, HttpRouteIntegrationBindOptions, HttpRouteIntegrationConfig, IHttpRouteAuthorizer, IHttpRouteIntegration, HttpNoneAuthorizer, PayloadFormatVersion, } from '../../lib'; @@ -310,7 +310,7 @@ describe('HttpApi', () => { const authorizer = HttpAuthorizer.fromHttpAuthorizerAttributes(stack, 'auth', { authorizerId: '12345', - authorizerType: HttpAuthorizerType.JWT, + authorizerType: 'JWT', }); // WHEN @@ -506,7 +506,7 @@ class DummyAuthorizer implements IHttpRouteAuthorizer { public bind(_: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig { return { authorizerId: 'auth-1234', - authorizationType: HttpAuthorizerType.JWT, + authorizationType: 'JWT', }; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/authorizer.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/authorizer.test.ts index 9f99ccb9f7691..92c0ee0422c17 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/authorizer.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/authorizer.test.ts @@ -64,4 +64,24 @@ describe('HttpAuthorizer', () => { }); }); }); + + describe('lambda', () => { + it('default', () => { + const stack = new Stack(); + const httpApi = new HttpApi(stack, 'HttpApi'); + + new HttpAuthorizer(stack, 'HttpAuthorizer', { + httpApi, + identitySource: ['identitysource.1', 'identitysource.2'], + type: HttpAuthorizerType.LAMBDA, + authorizerUri: 'arn:cool-lambda-arn', + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Authorizer', { + AuthorizerType: 'REQUEST', + AuthorizerPayloadFormatVersion: '2.0', + AuthorizerUri: 'arn:cool-lambda-arn', + }); + }); + }); }); diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts index 8de7d2ae7f1d6..f30bdaba9205e 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts @@ -242,6 +242,20 @@ describe('HttpRoute', () => { AuthorizationScopes: ['read:books'], }); }); + + test('should fail when unsupported authorization type is used', () => { + const stack = new Stack(); + const httpApi = new HttpApi(stack, 'HttpApi'); + + const authorizer = new InvalidTypeAuthorizer(); + + expect(() => new HttpRoute(stack, 'HttpRoute', { + httpApi, + integration: new DummyIntegration(), + routeKey: HttpRouteKey.with('/books', HttpMethod.GET), + authorizer, + })).toThrowError('authorizationType should either be JWT, CUSTOM, or NONE'); + }); }); class DummyIntegration implements IHttpRouteIntegration { @@ -272,7 +286,29 @@ class DummyAuthorizer implements IHttpRouteAuthorizer { return { authorizerId: this.authorizer.authorizerId, - authorizationType: HttpAuthorizerType.JWT, + authorizationType: 'JWT', }; } } + +class InvalidTypeAuthorizer implements IHttpRouteAuthorizer { + private authorizer?: HttpAuthorizer; + + public bind(options: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig { + if (!this.authorizer) { + + this.authorizer = new HttpAuthorizer(options.scope, 'auth-1234', { + httpApi: options.route.httpApi, + identitySource: ['identitysource.1', 'identitysource.2'], + type: HttpAuthorizerType.JWT, + jwtAudience: ['audience.1', 'audience.2'], + jwtIssuer: 'issuer', + }); + } + + return { + authorizerId: this.authorizer.authorizerId, + authorizationType: 'Random', + }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/data-source.ts b/packages/@aws-cdk/aws-appsync/lib/data-source.ts index 4c1280c2196d9..b7570be255fac 100644 --- a/packages/@aws-cdk/aws-appsync/lib/data-source.ts +++ b/packages/@aws-cdk/aws-appsync/lib/data-source.ts @@ -350,12 +350,14 @@ export class RdsDataSource extends BackedDataSource { props.secretStore.grantRead(this); // Change to grant with RDS grant becomes implemented + + props.serverlessCluster.grantDataApiAccess(this); + Grant.addToPrincipal({ grantee: this, actions: [ 'rds-data:DeleteItems', 'rds-data:ExecuteSql', - 'rds-data:ExecuteStatement', 'rds-data:GetItems', 'rds-data:InsertItems', 'rds-data:UpdateItems', diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts index 1f7c942811791..9a328b0fe65a0 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts @@ -58,11 +58,29 @@ describe('Rds Data Source configuration', () => { Effect: 'Allow', Resource: { Ref: 'AuroraSecret41E6E877' }, }, + { + Action: [ + 'rds-data:BatchExecuteStatement', + 'rds-data:BeginTransaction', + 'rds-data:CommitTransaction', + 'rds-data:ExecuteStatement', + 'rds-data:RollbackTransaction', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'secretsmanager:GetSecretValue', + 'secretsmanager:DescribeSecret', + ], + Effect: 'Allow', + Resource: { Ref: 'AuroraClusterSecretAttachmentDB8032DA' }, + }, { Action: [ 'rds-data:DeleteItems', 'rds-data:ExecuteSql', - 'rds-data:ExecuteStatement', 'rds-data:GetItems', 'rds-data:InsertItems', 'rds-data:UpdateItems', diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts b/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts index bc6407a52f8e6..709baba719109 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts @@ -206,7 +206,6 @@ export interface GraphWidgetProps extends MetricWidgetProps { */ readonly liveData?: boolean; - /** * Display this metric * @@ -223,6 +222,23 @@ export interface GraphWidgetProps extends MetricWidgetProps { * @default false */ readonly setPeriodToTimeRange?: boolean; + + /** + * The default period for all metrics in this widget. + * The period is the length of time represented by one data point on the graph. + * This default can be overridden within each metric definition. + * + * @default cdk.Duration.seconds(300) + */ + readonly period?: cdk.Duration; + + /** + * The default statistic to be displayed for each metric. + * This default can be overridden within the definition of each individual metric + * + * @default - The statistic for each metric is used + */ + readonly statistic?: string; } /** @@ -287,6 +303,8 @@ export class GraphWidget extends ConcreteWidget { legend: this.props.legendPosition !== undefined ? { position: this.props.legendPosition } : undefined, liveData: this.props.liveData, setPeriodToTimeRange: this.props.setPeriodToTimeRange, + period: this.props.period?.toSeconds(), + stat: this.props.statistic, }, }]; } diff --git a/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts b/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts index e6420bbec1955..e5cc11781393d 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts @@ -1,4 +1,4 @@ -import { Stack } from '@aws-cdk/core'; +import { Duration, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import { Alarm, AlarmWidget, Color, GraphWidget, GraphWidgetView, LegendPosition, LogQueryWidget, Metric, Shading, SingleValueWidget, LogQueryVisualizationType } from '../lib'; @@ -688,4 +688,33 @@ export = { test.done(); }, + + 'GraphWidget supports stat and period'(test: Test) { + // GIVEN + const stack = new Stack(); + const widget = new GraphWidget({ + left: [new Metric({ namespace: 'CDK', metricName: 'Test' })], + statistic: 'Average', + period: Duration.days(2), + }); + + // THEN + test.deepEqual(stack.resolve(widget.toJson()), [{ + type: 'metric', + width: 6, + height: 6, + properties: { + view: 'timeSeries', + region: { Ref: 'AWS::Region' }, + metrics: [ + ['CDK', 'Test'], + ], + yAxis: {}, + stat: 'Average', + period: 172800, + }, + }]); + + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index 3734db8176d36..16331864e1e23 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -1,3 +1,4 @@ +import { EOL } from 'os'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import { IResource, Lazy, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; @@ -440,6 +441,31 @@ export class Repository extends RepositoryBase { }); } + + private static validateRepositoryName(physicalName: string) { + const repositoryName = physicalName; + if (!repositoryName || Token.isUnresolved(repositoryName)) { + // the name is a late-bound value, not a defined string, + // so skip validation + return; + } + + const errors: string[] = []; + + // Rules codified from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecr-repository.html + if (repositoryName.length < 2 || repositoryName.length > 256) { + errors.push('Repository name must be at least 2 and no more than 256 characters'); + } + const isPatternMatch = /^(?:[a-z0-9]+(?:[._-][a-z0-9]+)*\/)*[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(repositoryName); + if (!isPatternMatch) { + errors.push('Repository name must follow the specified pattern: (?:[a-z0-9]+(?:[._-][a-z0-9]+)*/)*[a-z0-9]+(?:[._-][a-z0-9]+)*'); + } + + if (errors.length > 0) { + throw new Error(`Invalid ECR repository name (value: ${repositoryName})${EOL}${errors.join(EOL)}`); + } + } + public readonly repositoryName: string; public readonly repositoryArn: string; private readonly lifecycleRules = new Array(); @@ -451,6 +477,8 @@ export class Repository extends RepositoryBase { physicalName: props.repositoryName, }); + Repository.validateRepositoryName(this.physicalName); + const resource = new CfnRepository(this, 'Resource', { repositoryName: this.physicalName, // It says "Text", but they actually mean "Object". diff --git a/packages/@aws-cdk/aws-ecr/test/repository.test.ts b/packages/@aws-cdk/aws-ecr/test/repository.test.ts index 5c7efecca1380..9b8c1f79796c5 100644 --- a/packages/@aws-cdk/aws-ecr/test/repository.test.ts +++ b/packages/@aws-cdk/aws-ecr/test/repository.test.ts @@ -1,3 +1,4 @@ +import { EOL } from 'os'; import { expect as expectCDK, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; @@ -519,4 +520,72 @@ describe('repository', () => { })); }); }); + + describe('repository name validation', () => { + test('repository name validations', () => { + const stack = new cdk.Stack(); + + expect(() => new ecr.Repository(stack, 'Repo1', { + repositoryName: 'abc-xyz-34ab', + })).not.toThrow(); + + expect(() => new ecr.Repository(stack, 'Repo2', { + repositoryName: '124/pp-33', + })).not.toThrow(); + }); + + test('repository name validation skips tokenized values', () => { + const stack = new cdk.Stack(); + + expect(() => new ecr.Repository(stack, 'Repo', { + repositoryName: cdk.Lazy.string({ produce: () => '_REPO' }), + })).not.toThrow(); + }); + + test('fails with message on invalid repository names', () => { + const stack = new cdk.Stack(); + const repositoryName = `-repositoRy.--${new Array(256).join('$')}`; + const expectedErrors = [ + `Invalid ECR repository name (value: ${repositoryName})`, + 'Repository name must be at least 2 and no more than 256 characters', + 'Repository name must follow the specified pattern: (?:[a-z0-9]+(?:[._-][a-z0-9]+)*/)*[a-z0-9]+(?:[._-][a-z0-9]+)*', + ].join(EOL); + + expect(() => new ecr.Repository(stack, 'Repo', { + repositoryName, + })).toThrow(expectedErrors); + }); + + test('fails if repository name has less than 2 or more than 256 characters', () => { + const stack = new cdk.Stack(); + + expect(() => new ecr.Repository(stack, 'Repo1', { + repositoryName: 'a', + })).toThrow(/at least 2/); + + expect(() => new ecr.Repository(stack, 'Repo2', { + repositoryName: new Array(258).join('x'), + })).toThrow(/no more than 256/); + }); + + test('fails if repository name does not follow the specified pattern', () => { + const stack = new cdk.Stack(); + + expect(() => new ecr.Repository(stack, 'Repo1', { + repositoryName: 'aAa', + })).toThrow(/must follow the specified pattern/); + + expect(() => new ecr.Repository(stack, 'Repo2', { + repositoryName: 'a--a', + })).toThrow(/must follow the specified pattern/); + + expect(() => new ecr.Repository(stack, 'Repo3', { + repositoryName: 'a./a-a', + })).toThrow(/must follow the specified pattern/); + + expect(() => new ecr.Repository(stack, 'Repo4', { + repositoryName: 'a//a-a', + })).toThrow(/must follow the specified pattern/); + }); + }); }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/CONTRIBUTING.md b/packages/@aws-cdk/aws-ecs-patterns/CONTRIBUTING.md new file mode 100644 index 0000000000000..58d8e97ba78e7 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/CONTRIBUTING.md @@ -0,0 +1 @@ +See: [Contributing Guide](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-ecs/README.md) diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-https.expected.json similarity index 92% rename from packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.expected.json rename to packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-https.expected.json index 1d670f79f58a6..5a67a969707c8 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-https.expected.json @@ -10,7 +10,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc" + "Value": "aws-ecs-integ-alb-fg-https/Vpc" } ] } @@ -35,7 +35,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-alb-fg-https/Vpc/PublicSubnet1" } ] } @@ -49,7 +49,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-alb-fg-https/Vpc/PublicSubnet1" } ] } @@ -87,7 +87,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-alb-fg-https/Vpc/PublicSubnet1" } ] } @@ -107,7 +107,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-alb-fg-https/Vpc/PublicSubnet1" } ] } @@ -132,7 +132,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-alb-fg-https/Vpc/PublicSubnet2" } ] } @@ -146,7 +146,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-alb-fg-https/Vpc/PublicSubnet2" } ] } @@ -184,7 +184,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-alb-fg-https/Vpc/PublicSubnet2" } ] } @@ -204,7 +204,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-alb-fg-https/Vpc/PublicSubnet2" } ] } @@ -229,7 +229,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PrivateSubnet1" + "Value": "aws-ecs-integ-alb-fg-https/Vpc/PrivateSubnet1" } ] } @@ -243,7 +243,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PrivateSubnet1" + "Value": "aws-ecs-integ-alb-fg-https/Vpc/PrivateSubnet1" } ] } @@ -291,7 +291,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PrivateSubnet2" + "Value": "aws-ecs-integ-alb-fg-https/Vpc/PrivateSubnet2" } ] } @@ -305,7 +305,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PrivateSubnet2" + "Value": "aws-ecs-integ-alb-fg-https/Vpc/PrivateSubnet2" } ] } @@ -339,7 +339,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc" + "Value": "aws-ecs-integ-alb-fg-https/Vpc" } ] } @@ -394,7 +394,7 @@ "myServiceLBSecurityGroupFE0ED608": { "Type": "AWS::EC2::SecurityGroup", "Properties": { - "GroupDescription": "Automatically created Security Group for ELB awsecsintegmyServiceLB1F7A535D", + "GroupDescription": "Automatically created Security Group for ELB awsecsintegalbfghttpsmyServiceLB8BEE3C49", "SecurityGroupIngress": [ { "CidrIp": "0.0.0.0/0", @@ -416,7 +416,7 @@ } } }, - "myServiceLBSecurityGrouptoawsecsintegmyServiceSecurityGroup8DAB521180B6703B07": { + "myServiceLBSecurityGrouptoawsecsintegalbfghttpsmyServiceSecurityGroup49C558AD803FB613FF": { "Type": "AWS::EC2::SecurityGroupEgress", "Properties": { "GroupId": { @@ -451,15 +451,15 @@ "LoadBalancerArn": { "Ref": "myServiceLB168895E1" }, - "Port": 443, - "Protocol": "HTTPS", "Certificates": [ { "CertificateArn": { "Ref": "myServiceCertificate152F9DDA" } } - ] + ], + "Port": 443, + "Protocol": "HTTPS" } }, "myServiceLBPublicListenerECSGroup17E9BBC1": { @@ -513,8 +513,7 @@ "Type": "A", "AliasTarget": { "DNSName": { - "Fn::Join": - [ + "Fn::Join": [ "", [ "dualstack.", @@ -589,7 +588,7 @@ "Arn" ] }, - "Family": "awsecsintegmyServiceTaskDefA3A33D18", + "Family": "awsecsintegalbfghttpsmyServiceTaskDefD8ABFBF2", "Memory": "512", "NetworkMode": "awsvpc", "RequiresCompatibilities": [ @@ -709,7 +708,7 @@ "myServiceSecurityGroupC3B9D4E0": { "Type": "AWS::EC2::SecurityGroup", "Properties": { - "GroupDescription": "aws-ecs-integ/myService/Service/SecurityGroup", + "GroupDescription": "aws-ecs-integ-alb-fg-https/myService/Service/SecurityGroup", "SecurityGroupEgress": [ { "CidrIp": "0.0.0.0/0", @@ -722,7 +721,7 @@ } } }, - "myServiceSecurityGroupfromawsecsintegmyServiceLBSecurityGroupFA544FE5800A81885C": { + "myServiceSecurityGroupfromawsecsintegalbfghttpsmyServiceLBSecurityGroupA934AF89808E9FB7A3": { "Type": "AWS::EC2::SecurityGroupIngress", "Properties": { "IpProtocol": "tcp", @@ -767,4 +766,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-https.ts similarity index 94% rename from packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.ts rename to packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-https.ts index b78ff8da2304f..fe6940d272cc9 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-https.ts @@ -7,7 +7,7 @@ import { App, Stack } from '@aws-cdk/core'; import { ApplicationLoadBalancedFargateService } from '../../lib'; const app = new App(); -const stack = new Stack(app, 'aws-ecs-integ'); +const stack = new Stack(app, 'aws-ecs-integ-alb-fg-https'); const vpc = new Vpc(stack, 'Vpc', { maxAzs: 2 }); const cluster = new Cluster(stack, 'Cluster', { vpc }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-autocreate.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-autocreate.expected.json index 5813cd78e41f3..778523d6bc6df 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-autocreate.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-autocreate.expected.json @@ -1,6 +1,6 @@ { "Resources": { - "L3LB212FC0E0": { + "ALBFargateServiceLB64A0074E": { "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "Properties": { "LoadBalancerAttributes": [ @@ -13,7 +13,7 @@ "SecurityGroups": [ { "Fn::GetAtt": [ - "L3LBSecurityGroupEDE61198", + "ALBFargateServiceLBSecurityGroup5DC3060E", "GroupId" ] } @@ -33,10 +33,10 @@ "EcsDefaultClusterMnL3mNNYNVpcPublicSubnet2DefaultRouteB1375520" ] }, - "L3LBSecurityGroupEDE61198": { + "ALBFargateServiceLBSecurityGroup5DC3060E": { "Type": "AWS::EC2::SecurityGroup", "Properties": { - "GroupDescription": "Automatically created Security Group for ELB awsecsintegL3LB6453BA0A", + "GroupDescription": "Automatically created Security Group for ELB awsecsintegl3autocreateALBFargateServiceLB31EA4AB6", "SecurityGroupIngress": [ { "CidrIp": "0.0.0.0/0", @@ -51,12 +51,12 @@ } } }, - "L3LBSecurityGrouptoawsecsintegL3ServiceSecurityGroup7B96C87F8094933E0A": { + "ALBFargateServiceLBSecurityGrouptoawsecsintegl3autocreateALBFargateServiceSecurityGroup6F9400B580770A6C60": { "Type": "AWS::EC2::SecurityGroupEgress", "Properties": { "GroupId": { "Fn::GetAtt": [ - "L3LBSecurityGroupEDE61198", + "ALBFargateServiceLBSecurityGroup5DC3060E", "GroupId" ] }, @@ -64,7 +64,7 @@ "Description": "Load balancer to target", "DestinationSecurityGroupId": { "Fn::GetAtt": [ - "L3ServiceSecurityGroup677B0897", + "ALBFargateServiceSecurityGroup82F7A67E", "GroupId" ] }, @@ -72,25 +72,25 @@ "ToPort": 80 } }, - "L3LBPublicListener156FFC0F": { + "ALBFargateServiceLBPublicListener3489002A": { "Type": "AWS::ElasticLoadBalancingV2::Listener", "Properties": { "DefaultActions": [ { "TargetGroupArn": { - "Ref": "L3LBPublicListenerECSGroup648EEA11" + "Ref": "ALBFargateServiceLBPublicListenerECSGroup6871FB8C" }, "Type": "forward" } ], "LoadBalancerArn": { - "Ref": "L3LB212FC0E0" + "Ref": "ALBFargateServiceLB64A0074E" }, "Port": 80, "Protocol": "HTTP" } }, - "L3LBPublicListenerECSGroup648EEA11": { + "ALBFargateServiceLBPublicListenerECSGroup6871FB8C": { "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", "Properties": { "Port": 80, @@ -101,7 +101,7 @@ } } }, - "L3TaskDefTaskRole21C75D10": { + "ALBFargateServiceTaskDefTaskRole11408723": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -118,7 +118,7 @@ } } }, - "L3TaskDef48D8ACB8": { + "ALBFargateServiceTaskDefF69F17D6": { "Type": "AWS::ECS::TaskDefinition", "Properties": { "ContainerDefinitions": [ @@ -129,9 +129,9 @@ "LogDriver": "awslogs", "Options": { "awslogs-group": { - "Ref": "L3TaskDefwebLogGroupC6E4A38A" + "Ref": "ALBFargateServiceTaskDefwebLogGroup7073A41D" }, - "awslogs-stream-prefix": "L3", + "awslogs-stream-prefix": "ALBFargateService", "awslogs-region": { "Ref": "AWS::Region" } @@ -149,11 +149,11 @@ "Cpu": "512", "ExecutionRoleArn": { "Fn::GetAtt": [ - "L3TaskDefExecutionRole49AF0996", + "ALBFargateServiceTaskDefExecutionRole9E885E7B", "Arn" ] }, - "Family": "awsecsintegL3TaskDefAA25240E", + "Family": "awsecsintegl3autocreateALBFargateServiceTaskDefDA905826", "Memory": "1024", "NetworkMode": "awsvpc", "RequiresCompatibilities": [ @@ -161,18 +161,18 @@ ], "TaskRoleArn": { "Fn::GetAtt": [ - "L3TaskDefTaskRole21C75D10", + "ALBFargateServiceTaskDefTaskRole11408723", "Arn" ] } } }, - "L3TaskDefwebLogGroupC6E4A38A": { + "ALBFargateServiceTaskDefwebLogGroup7073A41D": { "Type": "AWS::Logs::LogGroup", "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, - "L3TaskDefExecutionRole49AF0996": { + "ALBFargateServiceTaskDefExecutionRole9E885E7B": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -189,7 +189,7 @@ } } }, - "L3TaskDefExecutionRoleDefaultPolicy4656E642": { + "ALBFargateServiceTaskDefExecutionRoleDefaultPolicy574B9EAD": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -202,7 +202,7 @@ "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "L3TaskDefwebLogGroupC6E4A38A", + "ALBFargateServiceTaskDefwebLogGroup7073A41D", "Arn" ] } @@ -210,15 +210,15 @@ ], "Version": "2012-10-17" }, - "PolicyName": "L3TaskDefExecutionRoleDefaultPolicy4656E642", + "PolicyName": "ALBFargateServiceTaskDefExecutionRoleDefaultPolicy574B9EAD", "Roles": [ { - "Ref": "L3TaskDefExecutionRole49AF0996" + "Ref": "ALBFargateServiceTaskDefExecutionRole9E885E7B" } ] } }, - "L3Service616D5A93": { + "ALBFargateService90FDCE10": { "Type": "AWS::ECS::Service", "Properties": { "Cluster": { @@ -236,7 +236,7 @@ "ContainerName": "web", "ContainerPort": 80, "TargetGroupArn": { - "Ref": "L3LBPublicListenerECSGroup648EEA11" + "Ref": "ALBFargateServiceLBPublicListenerECSGroup6871FB8C" } } ], @@ -246,7 +246,7 @@ "SecurityGroups": [ { "Fn::GetAtt": [ - "L3ServiceSecurityGroup677B0897", + "ALBFargateServiceSecurityGroup82F7A67E", "GroupId" ] } @@ -262,18 +262,18 @@ } }, "TaskDefinition": { - "Ref": "L3TaskDef48D8ACB8" + "Ref": "ALBFargateServiceTaskDefF69F17D6" } }, "DependsOn": [ - "L3LBPublicListenerECSGroup648EEA11", - "L3LBPublicListener156FFC0F" + "ALBFargateServiceLBPublicListenerECSGroup6871FB8C", + "ALBFargateServiceLBPublicListener3489002A" ] }, - "L3ServiceSecurityGroup677B0897": { + "ALBFargateServiceSecurityGroup82F7A67E": { "Type": "AWS::EC2::SecurityGroup", "Properties": { - "GroupDescription": "aws-ecs-integ/L3/Service/SecurityGroup", + "GroupDescription": "aws-ecs-integ-l3-autocreate/ALBFargateService/Service/SecurityGroup", "SecurityGroupEgress": [ { "CidrIp": "0.0.0.0/0", @@ -286,7 +286,7 @@ } } }, - "L3ServiceSecurityGroupfromawsecsintegL3LBSecurityGroupA70DA46C80DBDFBCD6": { + "ALBFargateServiceSecurityGroupfromawsecsintegl3autocreateALBFargateServiceLBSecurityGroupD565E0BF802E7B8344": { "Type": "AWS::EC2::SecurityGroupIngress", "Properties": { "IpProtocol": "tcp", @@ -294,13 +294,13 @@ "FromPort": 80, "GroupId": { "Fn::GetAtt": [ - "L3ServiceSecurityGroup677B0897", + "ALBFargateServiceSecurityGroup82F7A67E", "GroupId" ] }, "SourceSecurityGroupId": { "Fn::GetAtt": [ - "L3LBSecurityGroupEDE61198", + "ALBFargateServiceLBSecurityGroup5DC3060E", "GroupId" ] }, @@ -320,7 +320,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/EcsDefaultClusterMnL3mNNYN/Vpc" + "Value": "aws-ecs-integ-l3-autocreate/EcsDefaultClusterMnL3mNNYN/Vpc" } ] } @@ -345,7 +345,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-l3-autocreate/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet1" } ] } @@ -359,7 +359,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-l3-autocreate/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet1" } ] } @@ -397,7 +397,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-l3-autocreate/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet1" } ] } @@ -417,7 +417,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-l3-autocreate/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet1" } ] } @@ -442,7 +442,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-l3-autocreate/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet2" } ] } @@ -456,7 +456,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-l3-autocreate/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet2" } ] } @@ -494,7 +494,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-l3-autocreate/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet2" } ] } @@ -514,7 +514,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-l3-autocreate/EcsDefaultClusterMnL3mNNYN/Vpc/PublicSubnet2" } ] } @@ -539,7 +539,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/EcsDefaultClusterMnL3mNNYN/Vpc/PrivateSubnet1" + "Value": "aws-ecs-integ-l3-autocreate/EcsDefaultClusterMnL3mNNYN/Vpc/PrivateSubnet1" } ] } @@ -553,7 +553,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/EcsDefaultClusterMnL3mNNYN/Vpc/PrivateSubnet1" + "Value": "aws-ecs-integ-l3-autocreate/EcsDefaultClusterMnL3mNNYN/Vpc/PrivateSubnet1" } ] } @@ -601,7 +601,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/EcsDefaultClusterMnL3mNNYN/Vpc/PrivateSubnet2" + "Value": "aws-ecs-integ-l3-autocreate/EcsDefaultClusterMnL3mNNYN/Vpc/PrivateSubnet2" } ] } @@ -615,7 +615,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/EcsDefaultClusterMnL3mNNYN/Vpc/PrivateSubnet2" + "Value": "aws-ecs-integ-l3-autocreate/EcsDefaultClusterMnL3mNNYN/Vpc/PrivateSubnet2" } ] } @@ -649,7 +649,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/EcsDefaultClusterMnL3mNNYN/Vpc" + "Value": "aws-ecs-integ-l3-autocreate/EcsDefaultClusterMnL3mNNYN/Vpc" } ] } @@ -665,7 +665,7 @@ } } }, - "L3bLBB8FADA4E": { + "NLBFargateServiceLB659EC17C": { "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "Properties": { "LoadBalancerAttributes": [ @@ -675,14 +675,6 @@ } ], "Scheme": "internet-facing", - "SecurityGroups": [ - { - "Fn::GetAtt": [ - "L3bLBSecurityGroup7A2B0AA0", - "GroupId" - ] - } - ], "Subnets": [ { "Ref": "EcsDefaultClusterMnL3mNNYNVpcPublicSubnet1Subnet3C273B99" @@ -691,82 +683,43 @@ "Ref": "EcsDefaultClusterMnL3mNNYNVpcPublicSubnet2Subnet95FF715A" } ], - "Type": "application" + "Type": "network" }, "DependsOn": [ "EcsDefaultClusterMnL3mNNYNVpcPublicSubnet1DefaultRouteFF4E2178", "EcsDefaultClusterMnL3mNNYNVpcPublicSubnet2DefaultRouteB1375520" ] }, - "L3bLBSecurityGroup7A2B0AA0": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "Automatically created Security Group for ELB awsecsintegL3bLB9C1497A7", - "SecurityGroupIngress": [ - { - "CidrIp": "0.0.0.0/0", - "Description": "Allow from anyone on port 80", - "FromPort": 80, - "IpProtocol": "tcp", - "ToPort": 80 - } - ], - "VpcId": { - "Ref": "EcsDefaultClusterMnL3mNNYNVpc7788A521" - } - } - }, - "L3bLBSecurityGrouptoawsecsintegL3bServiceSecurityGroupC2BD1A598019C4C37D": { - "Type": "AWS::EC2::SecurityGroupEgress", - "Properties": { - "GroupId": { - "Fn::GetAtt": [ - "L3bLBSecurityGroup7A2B0AA0", - "GroupId" - ] - }, - "IpProtocol": "tcp", - "Description": "Load balancer to target", - "DestinationSecurityGroupId": { - "Fn::GetAtt": [ - "L3bServiceSecurityGroupA8DA736E", - "GroupId" - ] - }, - "FromPort": 80, - "ToPort": 80 - } - }, - "L3bLBPublicListenerA825925B": { + "NLBFargateServiceLBPublicListenerB0DCA73C": { "Type": "AWS::ElasticLoadBalancingV2::Listener", "Properties": { "DefaultActions": [ { "TargetGroupArn": { - "Ref": "L3bLBPublicListenerECSGroup0070C5CA" + "Ref": "NLBFargateServiceLBPublicListenerECSGroupC469CAA2" }, "Type": "forward" } ], "LoadBalancerArn": { - "Ref": "L3bLBB8FADA4E" + "Ref": "NLBFargateServiceLB659EC17C" }, "Port": 80, - "Protocol": "HTTP" + "Protocol": "TCP" } }, - "L3bLBPublicListenerECSGroup0070C5CA": { + "NLBFargateServiceLBPublicListenerECSGroupC469CAA2": { "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", "Properties": { "Port": 80, - "Protocol": "HTTP", + "Protocol": "TCP", "TargetType": "ip", "VpcId": { "Ref": "EcsDefaultClusterMnL3mNNYNVpc7788A521" } } }, - "L3bTaskDefTaskRoleADAB80C8": { + "NLBFargateServiceTaskDefTaskRole6C88F40B": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -783,7 +736,7 @@ } } }, - "L3bTaskDef5506864D": { + "NLBFargateServiceTaskDefB836FA89": { "Type": "AWS::ECS::TaskDefinition", "Properties": { "ContainerDefinitions": [ @@ -794,9 +747,9 @@ "LogDriver": "awslogs", "Options": { "awslogs-group": { - "Ref": "L3bTaskDefwebLogGroup8E5F1183" + "Ref": "NLBFargateServiceTaskDefwebLogGroupC4A42FE2" }, - "awslogs-stream-prefix": "L3b", + "awslogs-stream-prefix": "NLBFargateService", "awslogs-region": { "Ref": "AWS::Region" } @@ -814,11 +767,11 @@ "Cpu": "512", "ExecutionRoleArn": { "Fn::GetAtt": [ - "L3bTaskDefExecutionRole9A3E2688", + "NLBFargateServiceTaskDefExecutionRoleF6D642D5", "Arn" ] }, - "Family": "awsecsintegL3bTaskDef24D7E4F1", + "Family": "awsecsintegl3autocreateNLBFargateServiceTaskDef7AC6C114", "Memory": "1024", "NetworkMode": "awsvpc", "RequiresCompatibilities": [ @@ -826,18 +779,18 @@ ], "TaskRoleArn": { "Fn::GetAtt": [ - "L3bTaskDefTaskRoleADAB80C8", + "NLBFargateServiceTaskDefTaskRole6C88F40B", "Arn" ] } } }, - "L3bTaskDefwebLogGroup8E5F1183": { + "NLBFargateServiceTaskDefwebLogGroupC4A42FE2": { "Type": "AWS::Logs::LogGroup", "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, - "L3bTaskDefExecutionRole9A3E2688": { + "NLBFargateServiceTaskDefExecutionRoleF6D642D5": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -854,7 +807,7 @@ } } }, - "L3bTaskDefExecutionRoleDefaultPolicy0CEA0ED2": { + "NLBFargateServiceTaskDefExecutionRoleDefaultPolicy90080805": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -867,7 +820,7 @@ "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "L3bTaskDefwebLogGroup8E5F1183", + "NLBFargateServiceTaskDefwebLogGroupC4A42FE2", "Arn" ] } @@ -875,15 +828,15 @@ ], "Version": "2012-10-17" }, - "PolicyName": "L3bTaskDefExecutionRoleDefaultPolicy0CEA0ED2", + "PolicyName": "NLBFargateServiceTaskDefExecutionRoleDefaultPolicy90080805", "Roles": [ { - "Ref": "L3bTaskDefExecutionRole9A3E2688" + "Ref": "NLBFargateServiceTaskDefExecutionRoleF6D642D5" } ] } }, - "L3bServiceF9D33D5A": { + "NLBFargateServiceB92AC095": { "Type": "AWS::ECS::Service", "Properties": { "Cluster": { @@ -901,7 +854,7 @@ "ContainerName": "web", "ContainerPort": 80, "TargetGroupArn": { - "Ref": "L3bLBPublicListenerECSGroup0070C5CA" + "Ref": "NLBFargateServiceLBPublicListenerECSGroupC469CAA2" } } ], @@ -911,7 +864,7 @@ "SecurityGroups": [ { "Fn::GetAtt": [ - "L3bServiceSecurityGroupA8DA736E", + "NLBFargateServiceSecurityGroup9D81388B", "GroupId" ] } @@ -927,18 +880,18 @@ } }, "TaskDefinition": { - "Ref": "L3bTaskDef5506864D" + "Ref": "NLBFargateServiceTaskDefB836FA89" } }, "DependsOn": [ - "L3bLBPublicListenerECSGroup0070C5CA", - "L3bLBPublicListenerA825925B" + "NLBFargateServiceLBPublicListenerECSGroupC469CAA2", + "NLBFargateServiceLBPublicListenerB0DCA73C" ] }, - "L3bServiceSecurityGroupA8DA736E": { + "NLBFargateServiceSecurityGroup9D81388B": { "Type": "AWS::EC2::SecurityGroup", "Properties": { - "GroupDescription": "aws-ecs-integ/L3b/Service/SecurityGroup", + "GroupDescription": "aws-ecs-integ-l3-autocreate/NLBFargateService/Service/SecurityGroup", "SecurityGroupEgress": [ { "CidrIp": "0.0.0.0/0", @@ -950,39 +903,18 @@ "Ref": "EcsDefaultClusterMnL3mNNYNVpc7788A521" } } - }, - "L3bServiceSecurityGroupfromawsecsintegL3bLBSecurityGroupA7B79A628034042CE5": { - "Type": "AWS::EC2::SecurityGroupIngress", - "Properties": { - "IpProtocol": "tcp", - "Description": "Load balancer to target", - "FromPort": 80, - "GroupId": { - "Fn::GetAtt": [ - "L3bServiceSecurityGroupA8DA736E", - "GroupId" - ] - }, - "SourceSecurityGroupId": { - "Fn::GetAtt": [ - "L3bLBSecurityGroup7A2B0AA0", - "GroupId" - ] - }, - "ToPort": 80 - } } }, "Outputs": { - "L3LoadBalancerDNSC6CB4A70": { + "ALBFargateServiceLoadBalancerDNSAFB2EDDB": { "Value": { "Fn::GetAtt": [ - "L3LB212FC0E0", + "ALBFargateServiceLB64A0074E", "DNSName" ] } }, - "L3ServiceURL0F065F2D": { + "ALBFargateServiceServiceURL4A19CF25": { "Value": { "Fn::Join": [ "", @@ -990,7 +922,7 @@ "http://", { "Fn::GetAtt": [ - "L3LB212FC0E0", + "ALBFargateServiceLB64A0074E", "DNSName" ] } @@ -998,29 +930,13 @@ ] } }, - "L3bLoadBalancerDNSED096132": { + "NLBFargateServiceLoadBalancerDNSC2B2922F": { "Value": { "Fn::GetAtt": [ - "L3bLBB8FADA4E", + "NLBFargateServiceLB659EC17C", "DNSName" ] } - }, - "L3bServiceURL0EDED888": { - "Value": { - "Fn::Join": [ - "", - [ - "http://", - { - "Fn::GetAtt": [ - "L3bLBB8FADA4E", - "DNSName" - ] - } - ] - ] - } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-autocreate.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-autocreate.ts index aae9efc969bac..3644cbbe8ec9f 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-autocreate.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-autocreate.ts @@ -3,9 +3,12 @@ import * as cdk from '@aws-cdk/core'; import * as ecsPatterns from '../../lib'; const app = new cdk.App(); -const stack = new cdk.Stack(app, 'aws-ecs-integ'); +const stack = new cdk.Stack(app, 'aws-ecs-integ-l3-autocreate'); -new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'L3', { +// No VPC or Cluster specified + +// Create ALB service +new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'ALBFargateService', { memoryLimitMiB: 1024, cpu: 512, taskImageOptions: { @@ -13,7 +16,8 @@ new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'L3', { }, }); -new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'L3b', { +// Create NLB service +new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'NLBFargateService', { memoryLimitMiB: 1024, cpu: 512, taskImageOptions: { diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-vpconly.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-vpconly.expected.json index 5556df70a59b7..f221c99ccf4fc 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-vpconly.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-vpconly.expected.json @@ -10,7 +10,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc" + "Value": "aws-ecs-integ-l3-vpconly/Vpc" } ] } @@ -35,7 +35,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-l3-vpconly/Vpc/PublicSubnet1" } ] } @@ -49,7 +49,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-l3-vpconly/Vpc/PublicSubnet1" } ] } @@ -87,7 +87,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-l3-vpconly/Vpc/PublicSubnet1" } ] } @@ -107,7 +107,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-l3-vpconly/Vpc/PublicSubnet1" } ] } @@ -132,7 +132,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-l3-vpconly/Vpc/PublicSubnet2" } ] } @@ -146,7 +146,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-l3-vpconly/Vpc/PublicSubnet2" } ] } @@ -184,7 +184,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-l3-vpconly/Vpc/PublicSubnet2" } ] } @@ -204,7 +204,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-l3-vpconly/Vpc/PublicSubnet2" } ] } @@ -229,7 +229,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PrivateSubnet1" + "Value": "aws-ecs-integ-l3-vpconly/Vpc/PrivateSubnet1" } ] } @@ -243,7 +243,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PrivateSubnet1" + "Value": "aws-ecs-integ-l3-vpconly/Vpc/PrivateSubnet1" } ] } @@ -291,7 +291,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PrivateSubnet2" + "Value": "aws-ecs-integ-l3-vpconly/Vpc/PrivateSubnet2" } ] } @@ -305,7 +305,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PrivateSubnet2" + "Value": "aws-ecs-integ-l3-vpconly/Vpc/PrivateSubnet2" } ] } @@ -339,7 +339,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc" + "Value": "aws-ecs-integ-l3-vpconly/Vpc" } ] } @@ -355,7 +355,7 @@ } } }, - "L3LB212FC0E0": { + "ALBFargateServiceLB64A0074E": { "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "Properties": { "LoadBalancerAttributes": [ @@ -368,7 +368,7 @@ "SecurityGroups": [ { "Fn::GetAtt": [ - "L3LBSecurityGroupEDE61198", + "ALBFargateServiceLBSecurityGroup5DC3060E", "GroupId" ] } @@ -388,10 +388,10 @@ "VpcPublicSubnet2DefaultRoute97F91067" ] }, - "L3LBSecurityGroupEDE61198": { + "ALBFargateServiceLBSecurityGroup5DC3060E": { "Type": "AWS::EC2::SecurityGroup", "Properties": { - "GroupDescription": "Automatically created Security Group for ELB awsecsintegL3LB6453BA0A", + "GroupDescription": "Automatically created Security Group for ELB awsecsintegl3vpconlyALBFargateServiceLBE08492C1", "SecurityGroupIngress": [ { "CidrIp": "0.0.0.0/0", @@ -406,12 +406,12 @@ } } }, - "L3LBSecurityGrouptoawsecsintegL3ServiceSecurityGroup7B96C87F8094933E0A": { + "ALBFargateServiceLBSecurityGrouptoawsecsintegl3vpconlyALBFargateServiceSecurityGroup3700A42180D1AB9DBC": { "Type": "AWS::EC2::SecurityGroupEgress", "Properties": { "GroupId": { "Fn::GetAtt": [ - "L3LBSecurityGroupEDE61198", + "ALBFargateServiceLBSecurityGroup5DC3060E", "GroupId" ] }, @@ -419,7 +419,7 @@ "Description": "Load balancer to target", "DestinationSecurityGroupId": { "Fn::GetAtt": [ - "L3ServiceSecurityGroup677B0897", + "ALBFargateServiceSecurityGroup82F7A67E", "GroupId" ] }, @@ -427,25 +427,25 @@ "ToPort": 80 } }, - "L3LBPublicListener156FFC0F": { + "ALBFargateServiceLBPublicListener3489002A": { "Type": "AWS::ElasticLoadBalancingV2::Listener", "Properties": { "DefaultActions": [ { "TargetGroupArn": { - "Ref": "L3LBPublicListenerECSGroup648EEA11" + "Ref": "ALBFargateServiceLBPublicListenerECSGroup6871FB8C" }, "Type": "forward" } ], "LoadBalancerArn": { - "Ref": "L3LB212FC0E0" + "Ref": "ALBFargateServiceLB64A0074E" }, "Port": 80, "Protocol": "HTTP" } }, - "L3LBPublicListenerECSGroup648EEA11": { + "ALBFargateServiceLBPublicListenerECSGroup6871FB8C": { "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", "Properties": { "Port": 80, @@ -456,7 +456,7 @@ } } }, - "L3TaskDefTaskRole21C75D10": { + "ALBFargateServiceTaskDefTaskRole11408723": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -473,7 +473,7 @@ } } }, - "L3TaskDef48D8ACB8": { + "ALBFargateServiceTaskDefF69F17D6": { "Type": "AWS::ECS::TaskDefinition", "Properties": { "ContainerDefinitions": [ @@ -484,9 +484,9 @@ "LogDriver": "awslogs", "Options": { "awslogs-group": { - "Ref": "L3TaskDefwebLogGroupC6E4A38A" + "Ref": "ALBFargateServiceTaskDefwebLogGroup7073A41D" }, - "awslogs-stream-prefix": "L3", + "awslogs-stream-prefix": "ALBFargateService", "awslogs-region": { "Ref": "AWS::Region" } @@ -504,11 +504,11 @@ "Cpu": "512", "ExecutionRoleArn": { "Fn::GetAtt": [ - "L3TaskDefExecutionRole49AF0996", + "ALBFargateServiceTaskDefExecutionRole9E885E7B", "Arn" ] }, - "Family": "awsecsintegL3TaskDefAA25240E", + "Family": "awsecsintegl3vpconlyALBFargateServiceTaskDef846555AE", "Memory": "1024", "NetworkMode": "awsvpc", "RequiresCompatibilities": [ @@ -516,18 +516,18 @@ ], "TaskRoleArn": { "Fn::GetAtt": [ - "L3TaskDefTaskRole21C75D10", + "ALBFargateServiceTaskDefTaskRole11408723", "Arn" ] } } }, - "L3TaskDefwebLogGroupC6E4A38A": { + "ALBFargateServiceTaskDefwebLogGroup7073A41D": { "Type": "AWS::Logs::LogGroup", "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, - "L3TaskDefExecutionRole49AF0996": { + "ALBFargateServiceTaskDefExecutionRole9E885E7B": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -544,7 +544,7 @@ } } }, - "L3TaskDefExecutionRoleDefaultPolicy4656E642": { + "ALBFargateServiceTaskDefExecutionRoleDefaultPolicy574B9EAD": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -557,7 +557,7 @@ "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "L3TaskDefwebLogGroupC6E4A38A", + "ALBFargateServiceTaskDefwebLogGroup7073A41D", "Arn" ] } @@ -565,15 +565,15 @@ ], "Version": "2012-10-17" }, - "PolicyName": "L3TaskDefExecutionRoleDefaultPolicy4656E642", + "PolicyName": "ALBFargateServiceTaskDefExecutionRoleDefaultPolicy574B9EAD", "Roles": [ { - "Ref": "L3TaskDefExecutionRole49AF0996" + "Ref": "ALBFargateServiceTaskDefExecutionRole9E885E7B" } ] } }, - "L3Service616D5A93": { + "ALBFargateService90FDCE10": { "Type": "AWS::ECS::Service", "Properties": { "Cluster": { @@ -591,7 +591,7 @@ "ContainerName": "web", "ContainerPort": 80, "TargetGroupArn": { - "Ref": "L3LBPublicListenerECSGroup648EEA11" + "Ref": "ALBFargateServiceLBPublicListenerECSGroup6871FB8C" } } ], @@ -601,7 +601,7 @@ "SecurityGroups": [ { "Fn::GetAtt": [ - "L3ServiceSecurityGroup677B0897", + "ALBFargateServiceSecurityGroup82F7A67E", "GroupId" ] } @@ -617,18 +617,18 @@ } }, "TaskDefinition": { - "Ref": "L3TaskDef48D8ACB8" + "Ref": "ALBFargateServiceTaskDefF69F17D6" } }, "DependsOn": [ - "L3LBPublicListenerECSGroup648EEA11", - "L3LBPublicListener156FFC0F" + "ALBFargateServiceLBPublicListenerECSGroup6871FB8C", + "ALBFargateServiceLBPublicListener3489002A" ] }, - "L3ServiceSecurityGroup677B0897": { + "ALBFargateServiceSecurityGroup82F7A67E": { "Type": "AWS::EC2::SecurityGroup", "Properties": { - "GroupDescription": "aws-ecs-integ/L3/Service/SecurityGroup", + "GroupDescription": "aws-ecs-integ-l3-vpconly/ALBFargateService/Service/SecurityGroup", "SecurityGroupEgress": [ { "CidrIp": "0.0.0.0/0", @@ -641,7 +641,7 @@ } } }, - "L3ServiceSecurityGroupfromawsecsintegL3LBSecurityGroupA70DA46C80DBDFBCD6": { + "ALBFargateServiceSecurityGroupfromawsecsintegl3vpconlyALBFargateServiceLBSecurityGroup96E9BBBD8073FB670D": { "Type": "AWS::EC2::SecurityGroupIngress", "Properties": { "IpProtocol": "tcp", @@ -649,13 +649,13 @@ "FromPort": 80, "GroupId": { "Fn::GetAtt": [ - "L3ServiceSecurityGroup677B0897", + "ALBFargateServiceSecurityGroup82F7A67E", "GroupId" ] }, "SourceSecurityGroupId": { "Fn::GetAtt": [ - "L3LBSecurityGroupEDE61198", + "ALBFargateServiceLBSecurityGroup5DC3060E", "GroupId" ] }, @@ -665,672 +665,7 @@ "EcsDefaultClusterMnL3mNNYNVpc18E0451A": { "Type": "AWS::ECS::Cluster" }, - "Vpc299FDBC5F": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16", - "EnableDnsHostnames": true, - "EnableDnsSupport": true, - "InstanceTenancy": "default", - "Tags": [ - { - "Key": "Name", - "Value": "aws-ecs-integ/Vpc2" - } - ] - } - }, - "Vpc2PublicSubnet1Subnet758D49A9": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.0.0/18", - "VpcId": { - "Ref": "Vpc299FDBC5F" - }, - "AvailabilityZone": "test-region-1a", - "MapPublicIpOnLaunch": true, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Public" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Public" - }, - { - "Key": "Name", - "Value": "aws-ecs-integ/Vpc2/PublicSubnet1" - } - ] - } - }, - "Vpc2PublicSubnet1RouteTable424A19D4": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc299FDBC5F" - }, - "Tags": [ - { - "Key": "Name", - "Value": "aws-ecs-integ/Vpc2/PublicSubnet1" - } - ] - } - }, - "Vpc2PublicSubnet1RouteTableAssociationA1651F3A": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "Vpc2PublicSubnet1RouteTable424A19D4" - }, - "SubnetId": { - "Ref": "Vpc2PublicSubnet1Subnet758D49A9" - } - } - }, - "Vpc2PublicSubnet1DefaultRoute64172CA2": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "Vpc2PublicSubnet1RouteTable424A19D4" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "Vpc2IGWB10A76EB" - } - }, - "DependsOn": [ - "Vpc2VPCGW62C338EF" - ] - }, - "Vpc2PublicSubnet1EIP42DB8E45": { - "Type": "AWS::EC2::EIP", - "Properties": { - "Domain": "vpc", - "Tags": [ - { - "Key": "Name", - "Value": "aws-ecs-integ/Vpc2/PublicSubnet1" - } - ] - } - }, - "Vpc2PublicSubnet1NATGateway26016506": { - "Type": "AWS::EC2::NatGateway", - "Properties": { - "AllocationId": { - "Fn::GetAtt": [ - "Vpc2PublicSubnet1EIP42DB8E45", - "AllocationId" - ] - }, - "SubnetId": { - "Ref": "Vpc2PublicSubnet1Subnet758D49A9" - }, - "Tags": [ - { - "Key": "Name", - "Value": "aws-ecs-integ/Vpc2/PublicSubnet1" - } - ] - } - }, - "Vpc2PublicSubnet2Subnet0BF8C291": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.64.0/18", - "VpcId": { - "Ref": "Vpc299FDBC5F" - }, - "AvailabilityZone": "test-region-1b", - "MapPublicIpOnLaunch": true, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Public" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Public" - }, - { - "Key": "Name", - "Value": "aws-ecs-integ/Vpc2/PublicSubnet2" - } - ] - } - }, - "Vpc2PublicSubnet2RouteTableF9AE47B1": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc299FDBC5F" - }, - "Tags": [ - { - "Key": "Name", - "Value": "aws-ecs-integ/Vpc2/PublicSubnet2" - } - ] - } - }, - "Vpc2PublicSubnet2RouteTableAssociation361E1341": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "Vpc2PublicSubnet2RouteTableF9AE47B1" - }, - "SubnetId": { - "Ref": "Vpc2PublicSubnet2Subnet0BF8C291" - } - } - }, - "Vpc2PublicSubnet2DefaultRouteBAB514C1": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "Vpc2PublicSubnet2RouteTableF9AE47B1" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "Vpc2IGWB10A76EB" - } - }, - "DependsOn": [ - "Vpc2VPCGW62C338EF" - ] - }, - "Vpc2PublicSubnet2EIP66DD26A4": { - "Type": "AWS::EC2::EIP", - "Properties": { - "Domain": "vpc", - "Tags": [ - { - "Key": "Name", - "Value": "aws-ecs-integ/Vpc2/PublicSubnet2" - } - ] - } - }, - "Vpc2PublicSubnet2NATGateway6CBF7FA6": { - "Type": "AWS::EC2::NatGateway", - "Properties": { - "AllocationId": { - "Fn::GetAtt": [ - "Vpc2PublicSubnet2EIP66DD26A4", - "AllocationId" - ] - }, - "SubnetId": { - "Ref": "Vpc2PublicSubnet2Subnet0BF8C291" - }, - "Tags": [ - { - "Key": "Name", - "Value": "aws-ecs-integ/Vpc2/PublicSubnet2" - } - ] - } - }, - "Vpc2PrivateSubnet1Subnet34902000": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.128.0/18", - "VpcId": { - "Ref": "Vpc299FDBC5F" - }, - "AvailabilityZone": "test-region-1a", - "MapPublicIpOnLaunch": false, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Private" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Private" - }, - { - "Key": "Name", - "Value": "aws-ecs-integ/Vpc2/PrivateSubnet1" - } - ] - } - }, - "Vpc2PrivateSubnet1RouteTableF8A2430B": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc299FDBC5F" - }, - "Tags": [ - { - "Key": "Name", - "Value": "aws-ecs-integ/Vpc2/PrivateSubnet1" - } - ] - } - }, - "Vpc2PrivateSubnet1RouteTableAssociation74320528": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "Vpc2PrivateSubnet1RouteTableF8A2430B" - }, - "SubnetId": { - "Ref": "Vpc2PrivateSubnet1Subnet34902000" - } - } - }, - "Vpc2PrivateSubnet1DefaultRoute24717F54": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "Vpc2PrivateSubnet1RouteTableF8A2430B" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "Vpc2PublicSubnet1NATGateway26016506" - } - } - }, - "Vpc2PrivateSubnet2Subnet3BA0F39B": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.192.0/18", - "VpcId": { - "Ref": "Vpc299FDBC5F" - }, - "AvailabilityZone": "test-region-1b", - "MapPublicIpOnLaunch": false, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Private" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Private" - }, - { - "Key": "Name", - "Value": "aws-ecs-integ/Vpc2/PrivateSubnet2" - } - ] - } - }, - "Vpc2PrivateSubnet2RouteTableB4F37E84": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc299FDBC5F" - }, - "Tags": [ - { - "Key": "Name", - "Value": "aws-ecs-integ/Vpc2/PrivateSubnet2" - } - ] - } - }, - "Vpc2PrivateSubnet2RouteTableAssociation19A1B68F": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "Vpc2PrivateSubnet2RouteTableB4F37E84" - }, - "SubnetId": { - "Ref": "Vpc2PrivateSubnet2Subnet3BA0F39B" - } - } - }, - "Vpc2PrivateSubnet2DefaultRouteA55B1734": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "Vpc2PrivateSubnet2RouteTableB4F37E84" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "Vpc2PublicSubnet2NATGateway6CBF7FA6" - } - } - }, - "Vpc2IGWB10A76EB": { - "Type": "AWS::EC2::InternetGateway", - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "aws-ecs-integ/Vpc2" - } - ] - } - }, - "Vpc2VPCGW62C338EF": { - "Type": "AWS::EC2::VPCGatewayAttachment", - "Properties": { - "VpcId": { - "Ref": "Vpc299FDBC5F" - }, - "InternetGatewayId": { - "Ref": "Vpc2IGWB10A76EB" - } - } - }, - "L3bLBB8FADA4E": { - "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", - "Properties": { - "LoadBalancerAttributes": [ - { - "Key": "deletion_protection.enabled", - "Value": "false" - } - ], - "Scheme": "internet-facing", - "SecurityGroups": [ - { - "Fn::GetAtt": [ - "L3bLBSecurityGroup7A2B0AA0", - "GroupId" - ] - } - ], - "Subnets": [ - { - "Ref": "Vpc2PublicSubnet1Subnet758D49A9" - }, - { - "Ref": "Vpc2PublicSubnet2Subnet0BF8C291" - } - ], - "Type": "application" - }, - "DependsOn": [ - "Vpc2PublicSubnet1DefaultRoute64172CA2", - "Vpc2PublicSubnet2DefaultRouteBAB514C1" - ] - }, - "L3bLBSecurityGroup7A2B0AA0": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "Automatically created Security Group for ELB awsecsintegL3bLB9C1497A7", - "SecurityGroupIngress": [ - { - "CidrIp": "0.0.0.0/0", - "Description": "Allow from anyone on port 80", - "FromPort": 80, - "IpProtocol": "tcp", - "ToPort": 80 - } - ], - "VpcId": { - "Ref": "Vpc299FDBC5F" - } - } - }, - "L3bLBSecurityGrouptoawsecsintegL3bServiceSecurityGroupC2BD1A598019C4C37D": { - "Type": "AWS::EC2::SecurityGroupEgress", - "Properties": { - "GroupId": { - "Fn::GetAtt": [ - "L3bLBSecurityGroup7A2B0AA0", - "GroupId" - ] - }, - "IpProtocol": "tcp", - "Description": "Load balancer to target", - "DestinationSecurityGroupId": { - "Fn::GetAtt": [ - "L3bServiceSecurityGroupA8DA736E", - "GroupId" - ] - }, - "FromPort": 80, - "ToPort": 80 - } - }, - "L3bLBPublicListenerA825925B": { - "Type": "AWS::ElasticLoadBalancingV2::Listener", - "Properties": { - "DefaultActions": [ - { - "TargetGroupArn": { - "Ref": "L3bLBPublicListenerECSGroup0070C5CA" - }, - "Type": "forward" - } - ], - "LoadBalancerArn": { - "Ref": "L3bLBB8FADA4E" - }, - "Port": 80, - "Protocol": "HTTP" - } - }, - "L3bLBPublicListenerECSGroup0070C5CA": { - "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", - "Properties": { - "Port": 80, - "Protocol": "HTTP", - "TargetType": "ip", - "VpcId": { - "Ref": "Vpc299FDBC5F" - } - } - }, - "L3bTaskDefTaskRoleADAB80C8": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ecs-tasks.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - } - } - }, - "L3bTaskDef5506864D": { - "Type": "AWS::ECS::TaskDefinition", - "Properties": { - "ContainerDefinitions": [ - { - "Essential": true, - "Image": "amazon/amazon-ecs-sample", - "LogConfiguration": { - "LogDriver": "awslogs", - "Options": { - "awslogs-group": { - "Ref": "L3bTaskDefwebLogGroup8E5F1183" - }, - "awslogs-stream-prefix": "L3b", - "awslogs-region": { - "Ref": "AWS::Region" - } - } - }, - "Name": "web", - "PortMappings": [ - { - "ContainerPort": 80, - "Protocol": "tcp" - } - ] - } - ], - "Cpu": "512", - "ExecutionRoleArn": { - "Fn::GetAtt": [ - "L3bTaskDefExecutionRole9A3E2688", - "Arn" - ] - }, - "Family": "awsecsintegL3bTaskDef24D7E4F1", - "Memory": "1024", - "NetworkMode": "awsvpc", - "RequiresCompatibilities": [ - "FARGATE" - ], - "TaskRoleArn": { - "Fn::GetAtt": [ - "L3bTaskDefTaskRoleADAB80C8", - "Arn" - ] - } - } - }, - "L3bTaskDefwebLogGroup8E5F1183": { - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - "DeletionPolicy": "Retain" - }, - "L3bTaskDefExecutionRole9A3E2688": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ecs-tasks.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - } - } - }, - "L3bTaskDefExecutionRoleDefaultPolicy0CEA0ED2": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "L3bTaskDefwebLogGroup8E5F1183", - "Arn" - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "L3bTaskDefExecutionRoleDefaultPolicy0CEA0ED2", - "Roles": [ - { - "Ref": "L3bTaskDefExecutionRole9A3E2688" - } - ] - } - }, - "L3bServiceF9D33D5A": { - "Type": "AWS::ECS::Service", - "Properties": { - "Cluster": { - "Ref": "EcsDefaultClusterMnL3mNNYNVpc2B5DB011D" - }, - "DeploymentConfiguration": { - "MaximumPercent": 200, - "MinimumHealthyPercent": 50 - }, - "EnableECSManagedTags": false, - "HealthCheckGracePeriodSeconds": 60, - "LaunchType": "FARGATE", - "LoadBalancers": [ - { - "ContainerName": "web", - "ContainerPort": 80, - "TargetGroupArn": { - "Ref": "L3bLBPublicListenerECSGroup0070C5CA" - } - } - ], - "NetworkConfiguration": { - "AwsvpcConfiguration": { - "AssignPublicIp": "DISABLED", - "SecurityGroups": [ - { - "Fn::GetAtt": [ - "L3bServiceSecurityGroupA8DA736E", - "GroupId" - ] - } - ], - "Subnets": [ - { - "Ref": "Vpc2PrivateSubnet1Subnet34902000" - }, - { - "Ref": "Vpc2PrivateSubnet2Subnet3BA0F39B" - } - ] - } - }, - "TaskDefinition": { - "Ref": "L3bTaskDef5506864D" - } - }, - "DependsOn": [ - "L3bLBPublicListenerECSGroup0070C5CA", - "L3bLBPublicListenerA825925B" - ] - }, - "L3bServiceSecurityGroupA8DA736E": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "aws-ecs-integ/L3b/Service/SecurityGroup", - "SecurityGroupEgress": [ - { - "CidrIp": "0.0.0.0/0", - "Description": "Allow all outbound traffic by default", - "IpProtocol": "-1" - } - ], - "VpcId": { - "Ref": "Vpc299FDBC5F" - } - } - }, - "L3bServiceSecurityGroupfromawsecsintegL3bLBSecurityGroupA7B79A628034042CE5": { - "Type": "AWS::EC2::SecurityGroupIngress", - "Properties": { - "IpProtocol": "tcp", - "Description": "Load balancer to target", - "FromPort": 80, - "GroupId": { - "Fn::GetAtt": [ - "L3bServiceSecurityGroupA8DA736E", - "GroupId" - ] - }, - "SourceSecurityGroupId": { - "Fn::GetAtt": [ - "L3bLBSecurityGroup7A2B0AA0", - "GroupId" - ] - }, - "ToPort": 80 - } - }, - "EcsDefaultClusterMnL3mNNYNVpc2B5DB011D": { - "Type": "AWS::ECS::Cluster" - }, - "L3cLB041B1E8C": { + "NLBFargateServiceLB659EC17C": { "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "Properties": { "LoadBalancerAttributes": [ @@ -1340,98 +675,51 @@ } ], "Scheme": "internet-facing", - "SecurityGroups": [ - { - "Fn::GetAtt": [ - "L3cLBSecurityGroup818CBDE1", - "GroupId" - ] - } - ], "Subnets": [ { - "Ref": "Vpc2PublicSubnet1Subnet758D49A9" + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" }, { - "Ref": "Vpc2PublicSubnet2Subnet0BF8C291" + "Ref": "VpcPublicSubnet2Subnet691E08A3" } ], - "Type": "application" + "Type": "network" }, "DependsOn": [ - "Vpc2PublicSubnet1DefaultRoute64172CA2", - "Vpc2PublicSubnet2DefaultRouteBAB514C1" + "VpcPublicSubnet1DefaultRoute3DA9E72A", + "VpcPublicSubnet2DefaultRoute97F91067" ] }, - "L3cLBSecurityGroup818CBDE1": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "Automatically created Security Group for ELB awsecsintegL3cLB16505710", - "SecurityGroupIngress": [ - { - "CidrIp": "0.0.0.0/0", - "Description": "Allow from anyone on port 80", - "FromPort": 80, - "IpProtocol": "tcp", - "ToPort": 80 - } - ], - "VpcId": { - "Ref": "Vpc299FDBC5F" - } - } - }, - "L3cLBSecurityGrouptoawsecsintegL3cServiceSecurityGroupA4254E838029E3B246": { - "Type": "AWS::EC2::SecurityGroupEgress", - "Properties": { - "GroupId": { - "Fn::GetAtt": [ - "L3cLBSecurityGroup818CBDE1", - "GroupId" - ] - }, - "IpProtocol": "tcp", - "Description": "Load balancer to target", - "DestinationSecurityGroupId": { - "Fn::GetAtt": [ - "L3cServiceSecurityGroup94AFACED", - "GroupId" - ] - }, - "FromPort": 80, - "ToPort": 80 - } - }, - "L3cLBPublicListener1D4B3F11": { + "NLBFargateServiceLBPublicListenerB0DCA73C": { "Type": "AWS::ElasticLoadBalancingV2::Listener", "Properties": { "DefaultActions": [ { "TargetGroupArn": { - "Ref": "L3cLBPublicListenerECSGroup62D7B705" + "Ref": "NLBFargateServiceLBPublicListenerECSGroupC469CAA2" }, "Type": "forward" } ], "LoadBalancerArn": { - "Ref": "L3cLB041B1E8C" + "Ref": "NLBFargateServiceLB659EC17C" }, "Port": 80, - "Protocol": "HTTP" + "Protocol": "TCP" } }, - "L3cLBPublicListenerECSGroup62D7B705": { + "NLBFargateServiceLBPublicListenerECSGroupC469CAA2": { "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", "Properties": { "Port": 80, - "Protocol": "HTTP", + "Protocol": "TCP", "TargetType": "ip", "VpcId": { - "Ref": "Vpc299FDBC5F" + "Ref": "Vpc8378EB38" } } }, - "L3cTaskDefTaskRole3C3C6124": { + "NLBFargateServiceTaskDefTaskRole6C88F40B": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -1448,7 +736,7 @@ } } }, - "L3cTaskDefA575AF8A": { + "NLBFargateServiceTaskDefB836FA89": { "Type": "AWS::ECS::TaskDefinition", "Properties": { "ContainerDefinitions": [ @@ -1459,9 +747,9 @@ "LogDriver": "awslogs", "Options": { "awslogs-group": { - "Ref": "L3cTaskDefwebLogGroupE4BDEC1B" + "Ref": "NLBFargateServiceTaskDefwebLogGroupC4A42FE2" }, - "awslogs-stream-prefix": "L3c", + "awslogs-stream-prefix": "NLBFargateService", "awslogs-region": { "Ref": "AWS::Region" } @@ -1479,11 +767,11 @@ "Cpu": "512", "ExecutionRoleArn": { "Fn::GetAtt": [ - "L3cTaskDefExecutionRoleF366B4B2", + "NLBFargateServiceTaskDefExecutionRoleF6D642D5", "Arn" ] }, - "Family": "awsecsintegL3cTaskDefF83D4A1D", + "Family": "awsecsintegl3vpconlyNLBFargateServiceTaskDef1E6E41A6", "Memory": "1024", "NetworkMode": "awsvpc", "RequiresCompatibilities": [ @@ -1491,18 +779,18 @@ ], "TaskRoleArn": { "Fn::GetAtt": [ - "L3cTaskDefTaskRole3C3C6124", + "NLBFargateServiceTaskDefTaskRole6C88F40B", "Arn" ] } } }, - "L3cTaskDefwebLogGroupE4BDEC1B": { + "NLBFargateServiceTaskDefwebLogGroupC4A42FE2": { "Type": "AWS::Logs::LogGroup", "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, - "L3cTaskDefExecutionRoleF366B4B2": { + "NLBFargateServiceTaskDefExecutionRoleF6D642D5": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -1519,7 +807,7 @@ } } }, - "L3cTaskDefExecutionRoleDefaultPolicy364B8E8C": { + "NLBFargateServiceTaskDefExecutionRoleDefaultPolicy90080805": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -1532,7 +820,7 @@ "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "L3cTaskDefwebLogGroupE4BDEC1B", + "NLBFargateServiceTaskDefwebLogGroupC4A42FE2", "Arn" ] } @@ -1540,19 +828,19 @@ ], "Version": "2012-10-17" }, - "PolicyName": "L3cTaskDefExecutionRoleDefaultPolicy364B8E8C", + "PolicyName": "NLBFargateServiceTaskDefExecutionRoleDefaultPolicy90080805", "Roles": [ { - "Ref": "L3cTaskDefExecutionRoleF366B4B2" + "Ref": "NLBFargateServiceTaskDefExecutionRoleF6D642D5" } ] } }, - "L3cServiceADA1E573": { + "NLBFargateServiceB92AC095": { "Type": "AWS::ECS::Service", "Properties": { "Cluster": { - "Ref": "EcsDefaultClusterMnL3mNNYNVpc2B5DB011D" + "Ref": "EcsDefaultClusterMnL3mNNYNVpc18E0451A" }, "DeploymentConfiguration": { "MaximumPercent": 200, @@ -1566,7 +854,7 @@ "ContainerName": "web", "ContainerPort": 80, "TargetGroupArn": { - "Ref": "L3cLBPublicListenerECSGroup62D7B705" + "Ref": "NLBFargateServiceLBPublicListenerECSGroupC469CAA2" } } ], @@ -1576,34 +864,34 @@ "SecurityGroups": [ { "Fn::GetAtt": [ - "L3cServiceSecurityGroup94AFACED", + "NLBFargateServiceSecurityGroup9D81388B", "GroupId" ] } ], "Subnets": [ { - "Ref": "Vpc2PrivateSubnet1Subnet34902000" + "Ref": "VpcPrivateSubnet1Subnet536B997A" }, { - "Ref": "Vpc2PrivateSubnet2Subnet3BA0F39B" + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" } ] } }, "TaskDefinition": { - "Ref": "L3cTaskDefA575AF8A" + "Ref": "NLBFargateServiceTaskDefB836FA89" } }, "DependsOn": [ - "L3cLBPublicListenerECSGroup62D7B705", - "L3cLBPublicListener1D4B3F11" + "NLBFargateServiceLBPublicListenerECSGroupC469CAA2", + "NLBFargateServiceLBPublicListenerB0DCA73C" ] }, - "L3cServiceSecurityGroup94AFACED": { + "NLBFargateServiceSecurityGroup9D81388B": { "Type": "AWS::EC2::SecurityGroup", "Properties": { - "GroupDescription": "aws-ecs-integ/L3c/Service/SecurityGroup", + "GroupDescription": "aws-ecs-integ-l3-vpconly/NLBFargateService/Service/SecurityGroup", "SecurityGroupEgress": [ { "CidrIp": "0.0.0.0/0", @@ -1612,66 +900,21 @@ } ], "VpcId": { - "Ref": "Vpc299FDBC5F" + "Ref": "Vpc8378EB38" } } - }, - "L3cServiceSecurityGroupfromawsecsintegL3cLBSecurityGroup7820B0B28070DB6447": { - "Type": "AWS::EC2::SecurityGroupIngress", - "Properties": { - "IpProtocol": "tcp", - "Description": "Load balancer to target", - "FromPort": 80, - "GroupId": { - "Fn::GetAtt": [ - "L3cServiceSecurityGroup94AFACED", - "GroupId" - ] - }, - "SourceSecurityGroupId": { - "Fn::GetAtt": [ - "L3cLBSecurityGroup818CBDE1", - "GroupId" - ] - }, - "ToPort": 80 - } } }, "Outputs": { - "L3LoadBalancerDNSC6CB4A70": { - "Value": { - "Fn::GetAtt": [ - "L3LB212FC0E0", - "DNSName" - ] - } - }, - "L3ServiceURL0F065F2D": { - "Value": { - "Fn::Join": [ - "", - [ - "http://", - { - "Fn::GetAtt": [ - "L3LB212FC0E0", - "DNSName" - ] - } - ] - ] - } - }, - "L3bLoadBalancerDNSED096132": { + "ALBFargateServiceLoadBalancerDNSAFB2EDDB": { "Value": { "Fn::GetAtt": [ - "L3bLBB8FADA4E", + "ALBFargateServiceLB64A0074E", "DNSName" ] } }, - "L3bServiceURL0EDED888": { + "ALBFargateServiceServiceURL4A19CF25": { "Value": { "Fn::Join": [ "", @@ -1679,7 +922,7 @@ "http://", { "Fn::GetAtt": [ - "L3bLBB8FADA4E", + "ALBFargateServiceLB64A0074E", "DNSName" ] } @@ -1687,29 +930,13 @@ ] } }, - "L3cLoadBalancerDNS9409202E": { + "NLBFargateServiceLoadBalancerDNSC2B2922F": { "Value": { "Fn::GetAtt": [ - "L3cLB041B1E8C", + "NLBFargateServiceLB659EC17C", "DNSName" ] } - }, - "L3cServiceURL2E1758C7": { - "Value": { - "Fn::Join": [ - "", - [ - "http://", - { - "Fn::GetAtt": [ - "L3cLB041B1E8C", - "DNSName" - ] - } - ] - ] - } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-vpconly.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-vpconly.ts index e3e7fedceda97..f0f76f754639e 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-vpconly.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-vpconly.ts @@ -4,21 +4,14 @@ import * as cdk from '@aws-cdk/core'; import * as ecsPatterns from '../../lib'; const app = new cdk.App(); -const stack = new cdk.Stack(app, 'aws-ecs-integ'); +const stack = new cdk.Stack(app, 'aws-ecs-integ-l3-vpconly'); +// Create VPC only const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 2 }); -new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'L3', { - vpc, - memoryLimitMiB: 1024, - cpu: 512, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, -}); -const vpc2 = new ec2.Vpc(stack, 'Vpc2', { maxAzs: 2 }); -new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'L3b', { - vpc: vpc2, +// Create ALB service +new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'ALBFargateService', { + vpc, memoryLimitMiB: 1024, cpu: 512, taskImageOptions: { @@ -26,8 +19,9 @@ new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'L3b', { }, }); -new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'L3c', { - vpc: vpc2, +// Create NLB service +new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'NLBFargateService', { + vpc, memoryLimitMiB: 1024, cpu: 512, taskImageOptions: { diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.expected.json index 40d86be5d331b..efd340e79b5cf 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.expected.json @@ -10,7 +10,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc" + "Value": "aws-ecs-integ-lb-fargate/Vpc" } ] } @@ -35,7 +35,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-lb-fargate/Vpc/PublicSubnet1" } ] } @@ -49,7 +49,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-lb-fargate/Vpc/PublicSubnet1" } ] } @@ -87,7 +87,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-lb-fargate/Vpc/PublicSubnet1" } ] } @@ -107,7 +107,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + "Value": "aws-ecs-integ-lb-fargate/Vpc/PublicSubnet1" } ] } @@ -132,7 +132,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-lb-fargate/Vpc/PublicSubnet2" } ] } @@ -146,7 +146,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-lb-fargate/Vpc/PublicSubnet2" } ] } @@ -184,7 +184,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-lb-fargate/Vpc/PublicSubnet2" } ] } @@ -204,7 +204,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + "Value": "aws-ecs-integ-lb-fargate/Vpc/PublicSubnet2" } ] } @@ -229,7 +229,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PrivateSubnet1" + "Value": "aws-ecs-integ-lb-fargate/Vpc/PrivateSubnet1" } ] } @@ -243,7 +243,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PrivateSubnet1" + "Value": "aws-ecs-integ-lb-fargate/Vpc/PrivateSubnet1" } ] } @@ -291,7 +291,7 @@ }, { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PrivateSubnet2" + "Value": "aws-ecs-integ-lb-fargate/Vpc/PrivateSubnet2" } ] } @@ -305,7 +305,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc/PrivateSubnet2" + "Value": "aws-ecs-integ-lb-fargate/Vpc/PrivateSubnet2" } ] } @@ -339,7 +339,7 @@ "Tags": [ { "Key": "Name", - "Value": "aws-ecs-integ/Vpc" + "Value": "aws-ecs-integ-lb-fargate/Vpc" } ] } @@ -358,7 +358,7 @@ "FargateCluster7CCD5F93": { "Type": "AWS::ECS::Cluster" }, - "L3LB212FC0E0": { + "ALBFargateServiceLB64A0074E": { "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "Properties": { "LoadBalancerAttributes": [ @@ -371,7 +371,7 @@ "SecurityGroups": [ { "Fn::GetAtt": [ - "L3LBSecurityGroupEDE61198", + "ALBFargateServiceLBSecurityGroup5DC3060E", "GroupId" ] } @@ -391,10 +391,10 @@ "VpcPublicSubnet2DefaultRoute97F91067" ] }, - "L3LBSecurityGroupEDE61198": { + "ALBFargateServiceLBSecurityGroup5DC3060E": { "Type": "AWS::EC2::SecurityGroup", "Properties": { - "GroupDescription": "Automatically created Security Group for ELB awsecsintegL3LB6453BA0A", + "GroupDescription": "Automatically created Security Group for ELB awsecsinteglbfargateALBFargateServiceLBF93E98F2", "SecurityGroupIngress": [ { "CidrIp": "0.0.0.0/0", @@ -409,12 +409,12 @@ } } }, - "L3LBSecurityGrouptoawsecsintegL3ServiceSecurityGroup7B96C87F8094933E0A": { + "ALBFargateServiceLBSecurityGrouptoawsecsinteglbfargateALBFargateServiceSecurityGroup0D9B5AEB80C5CFCE6C": { "Type": "AWS::EC2::SecurityGroupEgress", "Properties": { "GroupId": { "Fn::GetAtt": [ - "L3LBSecurityGroupEDE61198", + "ALBFargateServiceLBSecurityGroup5DC3060E", "GroupId" ] }, @@ -422,7 +422,7 @@ "Description": "Load balancer to target", "DestinationSecurityGroupId": { "Fn::GetAtt": [ - "L3ServiceSecurityGroup677B0897", + "ALBFargateServiceSecurityGroup82F7A67E", "GroupId" ] }, @@ -430,25 +430,25 @@ "ToPort": 80 } }, - "L3LBPublicListener156FFC0F": { + "ALBFargateServiceLBPublicListener3489002A": { "Type": "AWS::ElasticLoadBalancingV2::Listener", "Properties": { "DefaultActions": [ { "TargetGroupArn": { - "Ref": "L3LBPublicListenerECSGroup648EEA11" + "Ref": "ALBFargateServiceLBPublicListenerECSGroup6871FB8C" }, "Type": "forward" } ], "LoadBalancerArn": { - "Ref": "L3LB212FC0E0" + "Ref": "ALBFargateServiceLB64A0074E" }, "Port": 80, "Protocol": "HTTP" } }, - "L3LBPublicListenerECSGroup648EEA11": { + "ALBFargateServiceLBPublicListenerECSGroup6871FB8C": { "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", "Properties": { "Port": 80, @@ -459,7 +459,7 @@ } } }, - "L3TaskDefTaskRole21C75D10": { + "ALBFargateServiceTaskDefTaskRole11408723": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -476,7 +476,7 @@ } } }, - "L3TaskDef48D8ACB8": { + "ALBFargateServiceTaskDefF69F17D6": { "Type": "AWS::ECS::TaskDefinition", "Properties": { "ContainerDefinitions": [ @@ -487,9 +487,9 @@ "LogDriver": "awslogs", "Options": { "awslogs-group": { - "Ref": "L3TaskDefwebLogGroupC6E4A38A" + "Ref": "ALBFargateServiceTaskDefwebLogGroup7073A41D" }, - "awslogs-stream-prefix": "L3", + "awslogs-stream-prefix": "ALBFargateService", "awslogs-region": { "Ref": "AWS::Region" } @@ -507,11 +507,11 @@ "Cpu": "512", "ExecutionRoleArn": { "Fn::GetAtt": [ - "L3TaskDefExecutionRole49AF0996", + "ALBFargateServiceTaskDefExecutionRole9E885E7B", "Arn" ] }, - "Family": "awsecsintegL3TaskDefAA25240E", + "Family": "awsecsinteglbfargateALBFargateServiceTaskDef26FE75C0", "Memory": "1024", "NetworkMode": "awsvpc", "RequiresCompatibilities": [ @@ -519,18 +519,18 @@ ], "TaskRoleArn": { "Fn::GetAtt": [ - "L3TaskDefTaskRole21C75D10", + "ALBFargateServiceTaskDefTaskRole11408723", "Arn" ] } } }, - "L3TaskDefwebLogGroupC6E4A38A": { + "ALBFargateServiceTaskDefwebLogGroup7073A41D": { "Type": "AWS::Logs::LogGroup", "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, - "L3TaskDefExecutionRole49AF0996": { + "ALBFargateServiceTaskDefExecutionRole9E885E7B": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -547,7 +547,7 @@ } } }, - "L3TaskDefExecutionRoleDefaultPolicy4656E642": { + "ALBFargateServiceTaskDefExecutionRoleDefaultPolicy574B9EAD": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -560,7 +560,7 @@ "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "L3TaskDefwebLogGroupC6E4A38A", + "ALBFargateServiceTaskDefwebLogGroup7073A41D", "Arn" ] } @@ -568,15 +568,15 @@ ], "Version": "2012-10-17" }, - "PolicyName": "L3TaskDefExecutionRoleDefaultPolicy4656E642", + "PolicyName": "ALBFargateServiceTaskDefExecutionRoleDefaultPolicy574B9EAD", "Roles": [ { - "Ref": "L3TaskDefExecutionRole49AF0996" + "Ref": "ALBFargateServiceTaskDefExecutionRole9E885E7B" } ] } }, - "L3Service616D5A93": { + "ALBFargateService90FDCE10": { "Type": "AWS::ECS::Service", "Properties": { "Cluster": { @@ -594,7 +594,7 @@ "ContainerName": "web", "ContainerPort": 80, "TargetGroupArn": { - "Ref": "L3LBPublicListenerECSGroup648EEA11" + "Ref": "ALBFargateServiceLBPublicListenerECSGroup6871FB8C" } } ], @@ -604,7 +604,7 @@ "SecurityGroups": [ { "Fn::GetAtt": [ - "L3ServiceSecurityGroup677B0897", + "ALBFargateServiceSecurityGroup82F7A67E", "GroupId" ] } @@ -620,18 +620,18 @@ } }, "TaskDefinition": { - "Ref": "L3TaskDef48D8ACB8" + "Ref": "ALBFargateServiceTaskDefF69F17D6" } }, "DependsOn": [ - "L3LBPublicListenerECSGroup648EEA11", - "L3LBPublicListener156FFC0F" + "ALBFargateServiceLBPublicListenerECSGroup6871FB8C", + "ALBFargateServiceLBPublicListener3489002A" ] }, - "L3ServiceSecurityGroup677B0897": { + "ALBFargateServiceSecurityGroup82F7A67E": { "Type": "AWS::EC2::SecurityGroup", "Properties": { - "GroupDescription": "aws-ecs-integ/L3/Service/SecurityGroup", + "GroupDescription": "aws-ecs-integ-lb-fargate/ALBFargateService/Service/SecurityGroup", "SecurityGroupEgress": [ { "CidrIp": "0.0.0.0/0", @@ -644,7 +644,7 @@ } } }, - "L3ServiceSecurityGroupfromawsecsintegL3LBSecurityGroupA70DA46C80DBDFBCD6": { + "ALBFargateServiceSecurityGroupfromawsecsinteglbfargateALBFargateServiceLBSecurityGroupCD911D2880462ECC11": { "Type": "AWS::EC2::SecurityGroupIngress", "Properties": { "IpProtocol": "tcp", @@ -652,30 +652,269 @@ "FromPort": 80, "GroupId": { "Fn::GetAtt": [ - "L3ServiceSecurityGroup677B0897", + "ALBFargateServiceSecurityGroup82F7A67E", "GroupId" ] }, "SourceSecurityGroupId": { "Fn::GetAtt": [ - "L3LBSecurityGroupEDE61198", + "ALBFargateServiceLBSecurityGroup5DC3060E", "GroupId" ] }, "ToPort": 80 } + }, + "NLBFargateServiceLB659EC17C": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "LoadBalancerAttributes": [ + { + "Key": "deletion_protection.enabled", + "Value": "false" + } + ], + "Scheme": "internet-facing", + "Subnets": [ + { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + ], + "Type": "network" + }, + "DependsOn": [ + "VpcPublicSubnet1DefaultRoute3DA9E72A", + "VpcPublicSubnet2DefaultRoute97F91067" + ] + }, + "NLBFargateServiceLBPublicListenerB0DCA73C": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "TargetGroupArn": { + "Ref": "NLBFargateServiceLBPublicListenerECSGroupC469CAA2" + }, + "Type": "forward" + } + ], + "LoadBalancerArn": { + "Ref": "NLBFargateServiceLB659EC17C" + }, + "Port": 80, + "Protocol": "TCP" + } + }, + "NLBFargateServiceLBPublicListenerECSGroupC469CAA2": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "Port": 80, + "Protocol": "TCP", + "TargetType": "ip", + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "NLBFargateServiceTaskDefTaskRole6C88F40B": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "NLBFargateServiceTaskDefB836FA89": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": "amazon/amazon-ecs-sample", + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "NLBFargateServiceTaskDefwebLogGroupC4A42FE2" + }, + "awslogs-stream-prefix": "NLBFargateService", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "Name": "web", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ] + } + ], + "Cpu": "512", + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "NLBFargateServiceTaskDefExecutionRoleF6D642D5", + "Arn" + ] + }, + "Family": "awsecsinteglbfargateNLBFargateServiceTaskDef1265FF34", + "Memory": "1024", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "NLBFargateServiceTaskDefTaskRole6C88F40B", + "Arn" + ] + } + } + }, + "NLBFargateServiceTaskDefwebLogGroupC4A42FE2": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "NLBFargateServiceTaskDefExecutionRoleF6D642D5": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "NLBFargateServiceTaskDefExecutionRoleDefaultPolicy90080805": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "NLBFargateServiceTaskDefwebLogGroupC4A42FE2", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "NLBFargateServiceTaskDefExecutionRoleDefaultPolicy90080805", + "Roles": [ + { + "Ref": "NLBFargateServiceTaskDefExecutionRoleF6D642D5" + } + ] + } + }, + "NLBFargateServiceB92AC095": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "FargateCluster7CCD5F93" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "EnableECSManagedTags": false, + "HealthCheckGracePeriodSeconds": 60, + "LaunchType": "FARGATE", + "LoadBalancers": [ + { + "ContainerName": "web", + "ContainerPort": 80, + "TargetGroupArn": { + "Ref": "NLBFargateServiceLBPublicListenerECSGroupC469CAA2" + } + } + ], + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "NLBFargateServiceSecurityGroup9D81388B", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + ] + } + }, + "TaskDefinition": { + "Ref": "NLBFargateServiceTaskDefB836FA89" + } + }, + "DependsOn": [ + "NLBFargateServiceLBPublicListenerECSGroupC469CAA2", + "NLBFargateServiceLBPublicListenerB0DCA73C" + ] + }, + "NLBFargateServiceSecurityGroup9D81388B": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ-lb-fargate/NLBFargateService/Service/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } } }, "Outputs": { - "L3LoadBalancerDNSC6CB4A70": { + "ALBFargateServiceLoadBalancerDNSAFB2EDDB": { "Value": { "Fn::GetAtt": [ - "L3LB212FC0E0", + "ALBFargateServiceLB64A0074E", "DNSName" ] } }, - "L3ServiceURL0F065F2D": { + "ALBFargateServiceServiceURL4A19CF25": { "Value": { "Fn::Join": [ "", @@ -683,13 +922,21 @@ "http://", { "Fn::GetAtt": [ - "L3LB212FC0E0", + "ALBFargateServiceLB64A0074E", "DNSName" ] } ] ] } + }, + "NLBFargateServiceLoadBalancerDNSC2B2922F": { + "Value": { + "Fn::GetAtt": [ + "NLBFargateServiceLB659EC17C", + "DNSName" + ] + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.ts index 5819bb3955190..b7cca5925e67c 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.ts @@ -4,13 +4,24 @@ import * as cdk from '@aws-cdk/core'; import * as ecsPatterns from '../../lib'; const app = new cdk.App(); -const stack = new cdk.Stack(app, 'aws-ecs-integ'); +const stack = new cdk.Stack(app, 'aws-ecs-integ-lb-fargate'); +// Create VPC and cluster const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 2 }); - const cluster = new ecs.Cluster(stack, 'FargateCluster', { vpc }); -new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'L3', { +// Create ALB service +new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'ALBFargateService', { + cluster, + memoryLimitMiB: 1024, + cpu: 512, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, +}); + +// Create NLB service +new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'NLBFargateService', { cluster, memoryLimitMiB: 1024, cpu: 512, diff --git a/packages/@aws-cdk/aws-ecs/CONTRIBUTING.md b/packages/@aws-cdk/aws-ecs/CONTRIBUTING.md new file mode 100644 index 0000000000000..da767696a0b39 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/CONTRIBUTING.md @@ -0,0 +1,56 @@ +# Contributing to the AWS ECS and AWS ECS Patterns modules + +Hiya! Thanks for your interest in contributing to the ECS modules! The [ECS +Developer Experience](https://github.com/orgs/aws/teams/aws-ecs-devx) team +currently owns the following construct libraries: + +- [@aws-cdk/aws-ecs](https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/aws-ecs): + the main construct library for AWS ECS +- [@aws-cdk/aws-ecs-patterns](https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/aws-ecs-patterns): + a set of simplified, higher-level constructs based on common container-based +application architectures. Great for first-time container developers! +- [@aws-cdk-containers/ecs-service-extensions](https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk-containers/ecs-service-extensions): + a set of ECS constructs that promote best practices for container +infrastructure by using composable add-ons, such as load balancers and sidecar +containers used for tracing and metric logging. More suitable for advanced +container configuration. + +## Find something to work on + +Issues related to ECS, ECS Patterns, and ECS Service extensions are tracked on +our public [project board](https://github.com/aws/aws-cdk/projects/2). + +If you want to contribute a specific feature or fix you have in mind, check our +[in-flight work](https://github.com/aws/aws-cdk/projects/2#column-8268897) or +[open pull requests](https://github.com/aws/aws-cdk/projects/2#column-11918985) +to see if someone else is already working on it. If an issue has someone +assigned to it in our "In Progress" column, that means they are actively +working on it. Otherwise, any unassigned issue is up for grabs! + +If an issue doesn't exist for your feature/fix, please create one using the +appropriate [issue +template](https://github.com/aws/aws-cdk/tree/master/.github/ISSUE_TEMPLATE). + +If you're simply looking for any issue to work on, explore our [Backlog of +issues](https://github.com/aws/aws-cdk/projects/2#column-8114389) on the public +project board and find something that piques your interest. If you are looking +for your first contribution, the ['good first issue' +label](https://github.com/aws/aws-cdk/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) +will be of help. + +### Let us know what you're working on! +Once you've chosen to work on an existing issue, please **add a comment to +indicate that you are starting work on it!** This will help us assign the +issue to you and keep track of issues being actively worked on, to avoid +duplicate effort. + +### Include a design if you can! +For larger features, your contribution is far more likely to be accepted if you: +1. let us know you are working on it! +2. include a design document. + +Examples of past designs for the ECS module can be found in under the +[design](https://github.com/aws/aws-cdk/tree/master/design/aws-ecs) directory. + +## Breaking Changes +See guidance on breaking changes in the [Contributing Guide](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md#breaking-changes). diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index e51756cf7fa91..8d46952bf1baa 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -415,6 +415,8 @@ export abstract class BaseService extends Resource if (props.cloudMapOptions) { this.enableCloudMap(props.cloudMapOptions); } + + this.node.defaultChild = this.resource; } /** diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts index c63c86cce9f65..036604d079c71 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts @@ -25,7 +25,7 @@ nodeunitShim({ memoryLimitMiB: 512, }); - new ecs.Ec2Service(stack, 'Ec2Service', { + const service = new ecs.Ec2Service(stack, 'Ec2Service', { cluster, taskDefinition, }); @@ -47,6 +47,8 @@ nodeunitShim({ EnableECSManagedTags: false, })); + test.notEqual(service.node.defaultChild, undefined); + test.done(); }, diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index a75ff256cc457..af4b92370726d 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts @@ -23,7 +23,7 @@ nodeunitShim({ image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), }); - new ecs.FargateService(stack, 'FargateService', { + const service = new ecs.FargateService(stack, 'FargateService', { cluster, taskDefinition, }); @@ -79,6 +79,8 @@ nodeunitShim({ }, })); + test.notEqual(service.node.defaultChild, undefined); + test.done(); }, diff --git a/packages/@aws-cdk/aws-events-targets/lib/sqs.ts b/packages/@aws-cdk/aws-events-targets/lib/sqs.ts index 8d711b4b9f5be..501414ecee348 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/sqs.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/sqs.ts @@ -52,14 +52,15 @@ export class SqsQueue implements events.IRuleTarget { * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/resource-based-policies-eventbridge.html#sqs-permissions */ public bind(rule: events.IRule, _id?: string): events.RuleTargetConfig { + // Only add the rule as a condition if the queue is not encrypted, to avoid circular dependency. See issue #11158. + const principalOpts = this.queue.encryptionMasterKey ? {} : { + conditions: { + ArnEquals: { 'aws:SourceArn': rule.ruleArn }, + }, + }; + // deduplicated automatically - this.queue.grantSendMessages(new iam.ServicePrincipal('events.amazonaws.com', - { - conditions: { - ArnEquals: { 'aws:SourceArn': rule.ruleArn }, - }, - }), - ); + this.queue.grantSendMessages(new iam.ServicePrincipal('events.amazonaws.com', principalOpts)); return { arn: this.queue.queueArn, diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index e36df0baf5c71..f262d5897aa5a 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -93,6 +93,7 @@ "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kinesis": "0.0.0", "@aws-cdk/aws-kinesisfirehose": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", @@ -114,6 +115,7 @@ "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kinesis": "0.0.0", "@aws-cdk/aws-kinesisfirehose": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", diff --git a/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.expected.json b/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.expected.json index eebbc3a996344..eb2a7dd26ef5f 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.expected.json @@ -1,5 +1,53 @@ { "Resources": { + "MyKey6AB29FA6": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, "MyRuleA44AB831": { "Type": "AWS::Events::Rule", "Properties": { @@ -20,6 +68,14 @@ }, "MyQueueE6CA6235": { "Type": "AWS::SQS::Queue", + "Properties": { + "KmsMasterKeyId": { + "Fn::GetAtt": [ + "MyKey6AB29FA6", + "Arn" + ] + } + }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, @@ -34,16 +90,6 @@ "sqs:GetQueueAttributes", "sqs:GetQueueUrl" ], - "Condition": { - "ArnEquals": { - "aws:SourceArn": { - "Fn::GetAtt": [ - "MyRuleA44AB831", - "Arn" - ] - } - } - }, "Effect": "Allow", "Principal": { "Service": "events.amazonaws.com" diff --git a/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.ts b/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.ts index b58641f727d03..b2b8fb334bff6 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.ts +++ b/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.ts @@ -1,4 +1,5 @@ import * as events from '@aws-cdk/aws-events'; +import * as kms from '@aws-cdk/aws-kms'; import * as sqs from '@aws-cdk/aws-sqs'; import * as cdk from '@aws-cdk/core'; import * as targets from '../../lib'; @@ -12,11 +13,17 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-sqs-event-target'); +const key = new kms.Key(stack, 'MyKey'); + const event = new events.Rule(stack, 'MyRule', { schedule: events.Schedule.rate(cdk.Duration.minutes(1)), }); -const queue = new sqs.Queue(stack, 'MyQueue'); +const queue = new sqs.Queue(stack, 'MyQueue', { + encryption: sqs.QueueEncryption.KMS, + encryptionMasterKey: key, +}); + event.addTarget(new targets.SqsQueue(queue)); app.synth(); diff --git a/packages/@aws-cdk/aws-kinesis/lib/stream.ts b/packages/@aws-cdk/aws-kinesis/lib/stream.ts index 0a73c6b9062f8..b2fed1eb10329 100644 --- a/packages/@aws-cdk/aws-kinesis/lib/stream.ts +++ b/packages/@aws-cdk/aws-kinesis/lib/stream.ts @@ -326,7 +326,7 @@ abstract class StreamBase extends Resource implements IStream { public abstract readonly encryptionKey?: kms.IKey; /** - * Grant write permissions for this stream and its contents to an IAM + * Grant read permissions for this stream and its contents to an IAM * principal (Role/Group/User). * * If an encryption key is used, permission to ues the key to decrypt the @@ -343,10 +343,10 @@ abstract class StreamBase extends Resource implements IStream { } /** - * Grant read permissions for this stream and its contents to an IAM + * Grant write permissions for this stream and its contents to an IAM * principal (Role/Group/User). * - * If an encryption key is used, permission to ues the key to decrypt the + * If an encryption key is used, permission to ues the key to encrypt the * contents of the stream will also be granted. */ public grantWrite(grantee: iam.IGrantable) { diff --git a/packages/@aws-cdk/aws-lambda-go/lib/Dockerfile b/packages/@aws-cdk/aws-lambda-go/lib/Dockerfile index 149d117cffdb9..ffa102c84803c 100644 --- a/packages/@aws-cdk/aws-lambda-go/lib/Dockerfile +++ b/packages/@aws-cdk/aws-lambda-go/lib/Dockerfile @@ -5,6 +5,7 @@ FROM $IMAGE # set the GOCACHE ENV GOCACHE=$GOPATH/.cache/go-build +ENV GOPROXY=direct # Ensure all users can write to GOPATH RUN chmod -R 777 $GOPATH diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index c52b0fe2570c9..981bf6755148e 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -144,8 +144,8 @@ new lambda.NodejsFunction(this, 'my-handler', { keepNames: true, // defaults to false tsconfig: 'custom-tsconfig.json', // use custom-tsconfig.json instead of default, metafile: true, // include meta file, defaults to false - banner : '/* comments */', // by default no comments are passed - footer : '/* comments */', // by default no comments are passed + banner : '/* comments */', // requires esbuild >= 0.9.0, defaults to none + footer : '/* comments */', // requires esbuild >= 0.9.0, defaults to none }, }); ``` @@ -166,7 +166,6 @@ new lambda.NodejsFunction(this, 'my-handler-with-commands', { } // ... } - }); ``` diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 8624dce8c1359..d28f43ca874c1 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -153,8 +153,8 @@ export class Bundling implements cdk.BundlingOptions { ...this.props.keepNames ? ['--keep-names'] : [], ...this.relativeTsconfigPath ? [`--tsconfig=${pathJoin(inputDir, this.relativeTsconfigPath)}`] : [], ...this.props.metafile ? [`--metafile=${pathJoin(outputDir, 'index.meta.json')}`] : [], - ...this.props.banner ? [`--banner='${this.props.banner}'`] : [], - ...this.props.footer ? [`--footer='${this.props.footer}'`] : [], + ...this.props.banner ? [`--banner:js=${JSON.stringify(this.props.banner)}`] : [], + ...this.props.footer ? [`--footer:js=${JSON.stringify(this.props.footer)}`] : [], ].join(' '); let depsCommand = ''; diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index 064c8458dbf97..e5441cb5aeadb 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -189,7 +189,7 @@ test('esbuild bundling with esbuild options', () => { '--minify --sourcemap --external:aws-sdk --loader:.png=dataurl', defineInstructions, '--log-level=silent --keep-names --tsconfig=/asset-input/lib/custom-tsconfig.ts', - '--metafile=/asset-output/index.meta.json --banner=\'/* comments */\' --footer=\'/* comments */\'', + '--metafile=/asset-output/index.meta.json --banner:js="/* comments */" --footer:js="/* comments */"', ].join(' '), ], }), diff --git a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts index 170f1f3e37573..431b9bf6a71d9 100644 --- a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts @@ -1,3 +1,4 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; @@ -63,6 +64,20 @@ export class SingletonFunction extends FunctionBase { this.canCreatePermissions = true; // Doesn't matter, addPermission is overriden anyway } + /** + * @inheritdoc + */ + public get isBoundToVpc(): boolean { + return this.lambdaFunction.isBoundToVpc; + } + + /** + * @inheritdoc + */ + public get connections(): ec2.Connections { + return this.lambdaFunction.connections; + } + /** * Returns a `lambda.Version` which represents the current version of this * singleton Lambda function. A new version will be created every time the diff --git a/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts b/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts index ebe9a5c5253cd..3bf78253d5ed6 100644 --- a/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import { ResourcePart } from '@aws-cdk/assert-internal'; +import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as lambda from '../lib'; @@ -174,4 +175,27 @@ describe('singleton lambda', () => { }, }); }); + + test('bind to vpc and access connections', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC', { maxAzs: 2 }); + const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { + vpc: vpc, + }); + + // WHEN + const singleton = new lambda.SingletonFunction(stack, 'Singleton', { + uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', + code: new lambda.InlineCode('foo'), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + securityGroups: [securityGroup], + vpc: vpc, + }); + + // THEN + expect(singleton.isBoundToVpc).toBeTruthy(); + expect(singleton.connections).toEqual(new ec2.Connections({ securityGroups: [securityGroup] })); + }); }); diff --git a/packages/@aws-cdk/aws-msk/test/integ.cluster.ts b/packages/@aws-cdk/aws-msk/test/integ.cluster.ts index c422a26b5cc32..c05fa496d7210 100644 --- a/packages/@aws-cdk/aws-msk/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-msk/test/integ.cluster.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import * as msk from '../lib'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md index 0888563679d47..74134d35ba3e3 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md @@ -133,6 +133,32 @@ const submitJob = new tasks.LambdaInvoke(this, 'Invoke Handler', { }); ``` +### ResultSelector + +You can use [`ResultSelector`](https://docs.aws.amazon.com/step-functions/latest/dg/input-output-inputpath-params.html#input-output-resultselector) +to manipulate the raw result of a Task, Map or Parallel state before it is +passed to [`ResultPath`](###ResultPath). For service integrations, the raw +result contains metadata in addition to the response payload. You can use +ResultSelector to construct a JSON payload that becomes the effective result +using static values or references to the raw result or context object. + +The following example extracts the output payload of a Lambda function Task and combines +it with some static values and the state name from the context object. + +```ts +new tasks.LambdaInvoke(this, 'Invoke Handler', { + lambdaFunction: fn, + resultSelector: { + lambdaOutput: sfn.JsonPath.stringAt('$.Payload'), + invokeRequestId: sfn.JsonPath.stringAt('$.SdkResponseMetadata.RequestId'), + staticValue: { + foo: 'bar', + }, + stateName: sfn.JsonPath.stringAt('$$.State.Name'), + }, +}) +``` + ### ResultPath The output of a state can be a copy of its input, the result it produces (for @@ -226,8 +252,8 @@ of the Node.js family are supported. Step Functions supports [API Gateway](https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html) through the service integration pattern. -HTTP APIs are designed for low-latency, cost-effective integrations with AWS services, including AWS Lambda, and HTTP endpoints. -HTTP APIs support OIDC and OAuth 2.0 authorization, and come with built-in support for CORS and automatic deployments. +HTTP APIs are designed for low-latency, cost-effective integrations with AWS services, including AWS Lambda, and HTTP endpoints. +HTTP APIs support OIDC and OAuth 2.0 authorization, and come with built-in support for CORS and automatic deployments. Previous-generation REST APIs currently offer more features. More details can be found [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html). ### Call REST API Endpoint @@ -507,8 +533,8 @@ isolation by design. Learn more about [Fargate](https://aws.amazon.com/fargate/) The Fargate launch type allows you to run your containerized applications without the need to provision and manage the backend infrastructure. Just register your task definition and -Fargate launches the container for you. The latest ACTIVE revision of the passed -task definition is used for running the task. Learn more about +Fargate launches the container for you. The latest ACTIVE revision of the passed +task definition is used for running the task. Learn more about [Fargate Versioning](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DescribeTaskDefinition.html) The following example runs a job from a task definition on Fargate @@ -718,7 +744,7 @@ You can call the [`StartJobRun`](https://docs.aws.amazon.com/glue/latest/dg/aws- new tasks.GlueStartJobRun(this, 'Task', { glueJobName: 'my-glue-job', arguments: sfn.TaskInput.fromObject({ - key: 'value', + key: 'value', }), timeout: cdk.Duration.minutes(30), notifyDelayAfter: cdk.Duration.minutes(5), @@ -1020,7 +1046,7 @@ a specific task in a state machine. When Step Functions reaches an activity task state, the workflow waits for an activity worker to poll for a task. An activity worker polls Step Functions by -using GetActivityTask, and sending the ARN for the related activity. +using GetActivityTask, and sending the ARN for the related activity. After the activity worker completes its work, it can provide a report of its success or failure by using `SendTaskSuccess` or `SendTaskFailure`. These two diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts index 16e64fcc9cd54..bb8cf601f3bf0 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts @@ -35,7 +35,7 @@ export interface StepFunctionsStartExecutionProps extends sfn.TaskStateBaseProps /** * A Step Functions Task to call StartExecution on another state machine. * - * It supports three service integration patterns: FIRE_AND_FORGET, SYNC and WAIT_FOR_TASK_TOKEN. + * It supports three service integration patterns: REQUEST_RESPONSE, RUN_JOB, and WAIT_FOR_TASK_TOKEN. */ export class StepFunctionsStartExecution extends sfn.TaskStateBase { private static readonly SUPPORTED_INTEGRATION_PATTERNS = [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.expected.json index 1d0e7fcdc6f51..fe1262610ffd2 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.expected.json @@ -188,7 +188,7 @@ "Arn" ] }, - "\",\"Payload.$\":\"$\"}},\"Check the job state\":{\"Next\":\"Job Complete?\",\"Retry\":[{\"ErrorEquals\":[\"Lambda.ServiceException\",\"Lambda.AWSLambdaException\",\"Lambda.SdkClientException\"],\"IntervalSeconds\":2,\"MaxAttempts\":6,\"BackoffRate\":2}],\"Type\":\"Task\",\"OutputPath\":\"$.Payload\",\"Resource\":\"arn:", + "\",\"Payload.$\":\"$\"}},\"Check the job state\":{\"Next\":\"Job Complete?\",\"Retry\":[{\"ErrorEquals\":[\"Lambda.ServiceException\",\"Lambda.AWSLambdaException\",\"Lambda.SdkClientException\"],\"IntervalSeconds\":2,\"MaxAttempts\":6,\"BackoffRate\":2}],\"Type\":\"Task\",\"ResultSelector\":{\"status.$\":\"$.Payload.status\"},\"Resource\":\"arn:", { "Ref": "AWS::Partition" }, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.ts index 870589ff72b3e..b7006b2ad33c4 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.ts @@ -46,7 +46,9 @@ const checkJobStateLambda = new Function(stack, 'checkJobStateLambda', { const checkJobState = new LambdaInvoke(stack, 'Check the job state', { lambdaFunction: checkJobStateLambda, - outputPath: '$.Payload', + resultSelector: { + status: sfn.JsonPath.stringAt('$.Payload.status'), + }, }); const isComplete = new sfn.Choice(stack, 'Job Complete?'); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts index e0166427b502c..05bc1245d903e 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts @@ -112,6 +112,58 @@ describe('LambdaInvoke', () => { })); }); + test('resultSelector', () => { + // WHEN + const task = new LambdaInvoke(stack, 'Task', { + lambdaFunction, + resultSelector: { + Result: sfn.JsonPath.stringAt('$.output.Payload'), + }, + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual(expect.objectContaining({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::lambda:invoke', + ], + ], + }, + End: true, + Parameters: { + FunctionName: { + 'Fn::GetAtt': [ + 'Fn9270CBC0', + 'Arn', + ], + }, + 'Payload.$': '$', + }, + ResultSelector: { + 'Result.$': '$.output.Payload', + }, + Retry: [ + { + ErrorEquals: [ + 'Lambda.ServiceException', + 'Lambda.AWSLambdaException', + 'Lambda.SdkClientException', + ], + IntervalSeconds: 2, + MaxAttempts: 6, + BackoffRate: 2, + }, + ], + })); + }); + test('invoke Lambda function and wait for task token', () => { // GIVEN const task = new LambdaInvoke(stack, 'Task', { diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts index e3f6e4800991f..431886e59e0b2 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts @@ -61,6 +61,20 @@ export interface MapProps { */ readonly parameters?: { [key: string]: any }; + /** + * The JSON that will replace the state's raw result and become the effective + * result before ResultPath is applied. + * + * You can use ResultSelector to create a payload with values that are static + * or selected from the state's raw result. + * + * @see + * https://docs.aws.amazon.com/step-functions/latest/dg/input-output-inputpath-params.html#input-output-resultselector + * + * @default - None + */ + readonly resultSelector?: { [key: string]: any }; + /** * MaxConcurrency * @@ -158,6 +172,7 @@ export class Map extends State implements INextable { ...this.renderNextEnd(), ...this.renderInputOutput(), ...this.renderParameters(), + ...this.renderResultSelector(), ...this.renderRetryCatch(), ...this.renderIterator(), ...this.renderItemsPath(), diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts index 70ac5819a6888..9167c7d0d0355 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts @@ -45,6 +45,20 @@ export interface ParallelProps { * @default $ */ readonly resultPath?: string; + + /** + * The JSON that will replace the state's raw result and become the effective + * result before ResultPath is applied. + * + * You can use ResultSelector to create a payload with values that are static + * or selected from the state's raw result. + * + * @see + * https://docs.aws.amazon.com/step-functions/latest/dg/input-output-inputpath-params.html#input-output-resultselector + * + * @default - None + */ + readonly resultSelector?: { [key: string]: any }; } /** @@ -117,6 +131,7 @@ export class Parallel extends State implements INextable { ...this.renderInputOutput(), ...this.renderRetryCatch(), ...this.renderBranches(), + ...this.renderResultSelector(), }; } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts index 42a4c8e9bf1b5..5dee29cf978ac 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts @@ -1,6 +1,6 @@ import { IConstruct, Construct, Node } from 'constructs'; import { Condition } from '../condition'; -import { JsonPath } from '../fields'; +import { FieldUtils, JsonPath } from '../fields'; import { StateGraph } from '../state-graph'; import { CatchProps, Errors, IChainable, INextable, RetryProps } from '../types'; @@ -58,6 +58,20 @@ export interface StateProps { * @default $ */ readonly resultPath?: string; + + /** + * The JSON that will replace the state's raw result and become the effective + * result before ResultPath is applied. + * + * You can use ResultSelector to create a payload with values that are static + * or selected from the state's raw result. + * + * @see + * https://docs.aws.amazon.com/step-functions/latest/dg/input-output-inputpath-params.html#input-output-resultselector + * + * @default - None + */ + readonly resultSelector?: { [key: string]: any }; } /** @@ -149,6 +163,7 @@ export abstract class State extends CoreConstruct implements IChainable { protected readonly parameters?: object; protected readonly outputPath?: string; protected readonly resultPath?: string; + protected readonly resultSelector?: object; protected readonly branches: StateGraph[] = []; protected iteration?: StateGraph; protected defaultChoice?: State; @@ -187,6 +202,7 @@ export abstract class State extends CoreConstruct implements IChainable { this.parameters = props.parameters; this.outputPath = props.outputPath; this.resultPath = props.resultPath; + this.resultSelector = props.resultSelector; } public get id() { @@ -398,6 +414,15 @@ export abstract class State extends CoreConstruct implements IChainable { }; } + /** + * Render ResultSelector in ASL JSON format + */ + protected renderResultSelector(): any { + return FieldUtils.renderObject({ + ResultSelector: this.resultSelector, + }); + } + /** * Called whenever this state is bound to a graph * diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts index f530113de2202..5743c0171d188 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts @@ -51,6 +51,20 @@ export interface TaskStateBaseProps { */ readonly resultPath?: string; + /** + * The JSON that will replace the state's raw result and become the effective + * result before ResultPath is applied. + * + * You can use ResultSelector to create a payload with values that are static + * or selected from the state's raw result. + * + * @see + * https://docs.aws.amazon.com/step-functions/latest/dg/input-output-inputpath-params.html#input-output-resultselector + * + * @default - None + */ + readonly resultSelector?: { [key: string]: any }; + /** * Timeout for the state machine * @@ -269,6 +283,7 @@ export abstract class TaskStateBase extends State implements INextable { InputPath: renderJsonPath(this.inputPath), OutputPath: renderJsonPath(this.outputPath), ResultPath: renderJsonPath(this.resultPath), + ...this.renderResultSelector(), }; } } diff --git a/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts index 1f9ea12651d4a..e5e379f578800 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts @@ -44,6 +44,47 @@ describe('Map State', () => { }, }); }), + test('State Machine With Map State and ResultSelector', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const map = new stepfunctions.Map(stack, 'Map State', { + maxConcurrency: 1, + itemsPath: stepfunctions.JsonPath.stringAt('$.inputForMap'), + resultSelector: { + buz: 'buz', + baz: stepfunctions.JsonPath.stringAt('$.baz'), + }, + }); + map.iterator(new stepfunctions.Pass(stack, 'Pass State')); + + // THEN + expect(render(map)).toStrictEqual({ + StartAt: 'Map State', + States: { + 'Map State': { + Type: 'Map', + End: true, + Iterator: { + StartAt: 'Pass State', + States: { + 'Pass State': { + Type: 'Pass', + End: true, + }, + }, + }, + ItemsPath: '$.inputForMap', + MaxConcurrency: 1, + ResultSelector: { + 'buz': 'buz', + 'baz.$': '$.baz', + }, + }, + }, + }); + }), test('synth is successful', () => { const app = createAppWithMap((stack) => { const map = new stepfunctions.Map(stack, 'Map State', { diff --git a/packages/@aws-cdk/aws-stepfunctions/test/parallel.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/parallel.test.ts index cae6bdf543e99..b89a567b22826 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/parallel.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/parallel.test.ts @@ -27,6 +27,38 @@ describe('Parallel State', () => { }, }); }); + + test('State Machine With Parallel State and ResultSelector', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const parallel = new stepfunctions.Parallel(stack, 'Parallel State', { + resultSelector: { + buz: 'buz', + baz: stepfunctions.JsonPath.stringAt('$.baz'), + }, + }); + parallel.branch(new stepfunctions.Pass(stack, 'Branch 1')); + + // THEN + expect(render(parallel)).toStrictEqual({ + StartAt: 'Parallel State', + States: { + 'Parallel State': { + Type: 'Parallel', + End: true, + Branches: [ + { StartAt: 'Branch 1', States: { 'Branch 1': { Type: 'Pass', End: true } } }, + ], + ResultSelector: { + 'buz': 'buz', + 'baz.$': '$.baz', + }, + }, + }, + }); + }); }); function render(sm: stepfunctions.IChainable) { diff --git a/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts index 3a8132a1f5cb8..a57c489134efd 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts @@ -44,6 +44,33 @@ describe('Task base', () => { }); }); + test('instantiate a concrete implementation with resultSelector', () => { + // WHEN + task = new FakeTask(stack, 'my-exciting-task', { + resultSelector: { + buz: 'buz', + baz: sfn.JsonPath.stringAt('$.baz'), + }, + }); + + // THEN + expect(render(task)).toEqual({ + StartAt: 'my-exciting-task', + States: { + 'my-exciting-task': { + End: true, + Type: 'Task', + Resource: 'my-resource', + Parameters: { MyParameter: 'myParameter' }, + ResultSelector: { + 'buz': 'buz', + 'baz.$': '$.baz', + }, + }, + }, + }); + }); + test('add catch configuration', () => { // GIVEN const failure = new sfn.Fail(stack, 'failed', { diff --git a/packages/@aws-cdk/cloudformation-diff/package.json b/packages/@aws-cdk/cloudformation-diff/package.json index 4f0e79827e7a0..9d51aa3274650 100644 --- a/packages/@aws-cdk/cloudformation-diff/package.json +++ b/packages/@aws-cdk/cloudformation-diff/package.json @@ -22,6 +22,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-cdk/cfnspec": "0.0.0", + "@types/node": "^10.17.56", "colors": "^1.4.0", "diff": "^5.0.0", "fast-deep-equal": "^3.1.3", diff --git a/packages/@aws-cdk/core/lib/asset-staging.ts b/packages/@aws-cdk/core/lib/asset-staging.ts index 09820293b63ec..44ab0de0bdd5d 100644 --- a/packages/@aws-cdk/core/lib/asset-staging.ts +++ b/packages/@aws-cdk/core/lib/asset-staging.ts @@ -336,6 +336,15 @@ export class AssetStaging extends CoreConstruct { const stagedPath = path.resolve(this.assetOutdir, renderAssetFilename(assetHash, bundledAsset.extension)); this.stageAsset(bundledAsset.path, stagedPath, 'move'); + + // If bundling produced a single archive file we "touch" this file in the bundling + // directory after it has been moved to the staging directory. This way if bundling + // is skipped because the bundling directory already exists we can still determine + // the correct packaging type. + if (bundledAsset.packaging === FileAssetPackaging.FILE) { + fs.closeSync(fs.openSync(bundledAsset.path, 'w')); + } + return { assetHash, stagedPath, diff --git a/packages/@aws-cdk/core/test/staging.test.ts b/packages/@aws-cdk/core/test/staging.test.ts index ee87780a0957e..e492f0a2dce88 100644 --- a/packages/@aws-cdk/core/test/staging.test.ts +++ b/packages/@aws-cdk/core/test/staging.test.ts @@ -887,20 +887,70 @@ nodeunitShim({ // THEN const assembly = app.synth(); test.deepEqual(fs.readdirSync(assembly.directory), [ - 'asset.f43148c61174f444925231b5849b468f21e93b5d1469cd07c53625ffd039ef48', // this is the bundle dir but it's empty + 'asset.f43148c61174f444925231b5849b468f21e93b5d1469cd07c53625ffd039ef48', // this is the bundle dir 'asset.f43148c61174f444925231b5849b468f21e93b5d1469cd07c53625ffd039ef48.zip', 'cdk.out', 'manifest.json', 'stack.template.json', 'tree.json', ]); - test.equal(fs.readdirSync(path.join(assembly.directory, 'asset.f43148c61174f444925231b5849b468f21e93b5d1469cd07c53625ffd039ef48')).length, 0); // empty bundle dir + test.deepEqual(fs.readdirSync(path.join(assembly.directory, 'asset.f43148c61174f444925231b5849b468f21e93b5d1469cd07c53625ffd039ef48')), [ + 'test.zip', // bundle dir with "touched" bundled output file + ]); test.deepEqual(staging.packaging, FileAssetPackaging.FILE); test.deepEqual(staging.isArchive, true); test.done(); }, + 'bundling that produces a single archive file with disk cache'(test: Test) { + // GIVEN + const TEST_OUTDIR = path.join(__dirname, 'cdk.out'); + if (fs.existsSync(TEST_OUTDIR)) { + fs.removeSync(TEST_OUTDIR); + } + + const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + const app1 = new App({ outdir: TEST_OUTDIR }); + const stack1 = new Stack(app1, 'Stack'); + + const app2 = new App({ outdir: TEST_OUTDIR }); // same OUTDIR + const stack2 = new Stack(app2, 'stack'); + + // WHEN + const staging1 = new AssetStaging(stack1, 'Asset', { + sourcePath: directory, + bundling: { + image: BundlingDockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.SINGLE_ARCHIVE], + outputType: BundlingOutput.ARCHIVED, + }, + }); + + // Now clear asset hash cache to show that during the second staging + // even though bundling is skipped it will correctly be considered + // as a FileAssetPackaging.FILE. + AssetStaging.clearAssetHashCache(); + + const staging2 = new AssetStaging(stack2, 'Asset', { + sourcePath: directory, + bundling: { + image: BundlingDockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.SINGLE_ARCHIVE], + outputType: BundlingOutput.ARCHIVED, + }, + }); + + // THEN + test.deepEqual(staging1.packaging, FileAssetPackaging.FILE); + test.deepEqual(staging1.isArchive, true); + test.deepEqual(staging2.packaging, staging1.packaging); + test.deepEqual(staging2.isArchive, staging1.isArchive); + + test.done(); + }, + 'bundling that produces a single archive file with NOT_ARCHIVED'(test: Test) { // GIVEN const app = new App(); diff --git a/packages/@aws-cdk/pipelines/lib/actions/update-pipeline-action.ts b/packages/@aws-cdk/pipelines/lib/actions/update-pipeline-action.ts index 0d77f616e7cf9..a5922ada39926 100644 --- a/packages/@aws-cdk/pipelines/lib/actions/update-pipeline-action.ts +++ b/packages/@aws-cdk/pipelines/lib/actions/update-pipeline-action.ts @@ -37,6 +37,13 @@ export interface UpdatePipelineActionProps { * @default - Automatically generated */ readonly projectName?: string; + + /** + * Whether the build step should run in privileged mode. + * + * @default - false + */ + readonly privileged?: boolean } /** @@ -58,7 +65,10 @@ export class UpdatePipelineAction extends CoreConstruct implements codepipeline. const selfMutationProject = new codebuild.PipelineProject(this, 'SelfMutation', { projectName: props.projectName, - environment: { buildImage: codebuild.LinuxBuildImage.STANDARD_5_0 }, + environment: { + buildImage: codebuild.LinuxBuildImage.STANDARD_5_0, + privileged: props.privileged ?? false, + }, buildSpec: codebuild.BuildSpec.fromObject({ version: '0.2', phases: { diff --git a/packages/@aws-cdk/pipelines/lib/pipeline.ts b/packages/@aws-cdk/pipelines/lib/pipeline.ts index 20f751078646e..5684a4eb0d604 100644 --- a/packages/@aws-cdk/pipelines/lib/pipeline.ts +++ b/packages/@aws-cdk/pipelines/lib/pipeline.ts @@ -12,6 +12,7 @@ import { AddStageOptions, AssetPublishingCommand, CdkStage, StackOutput } from ' // eslint-disable-next-line import { Construct as CoreConstruct } from '@aws-cdk/core'; +const CODE_BUILD_LENGTH_LIMIT = 100; /** * Properties for a CdkPipeline */ @@ -119,6 +120,22 @@ export interface CdkPipelineProps { * @default true */ readonly selfMutating?: boolean; + + /** + * Whether the pipeline needs to build Docker images in the UpdatePipeline stage. + * + * If the UpdatePipeline stage tries to build a Docker image and this flag is not + * set to `true`, the build step will run in non-privileged mode and consequently + * will fail with a message like: + * + * > Cannot connect to the Docker daemon at unix:///var/run/docker.sock. + * > Is the docker daemon running? + * + * This flag has an effect only if `selfMutating` is also `true`. + * + * @default - false + */ + readonly supportDockerAssets?: boolean; } /** @@ -200,6 +217,7 @@ export class CdkPipeline extends CoreConstruct { pipelineStackName: pipelineStack.stackName, cdkCliVersion: props.cdkCliVersion, projectName: maybeSuffix(props.pipelineName, '-selfupdate'), + privileged: props.supportDockerAssets, })], }); } @@ -286,7 +304,9 @@ export class CdkPipeline extends CoreConstruct { if (!this._outputArtifacts[stack.artifactId]) { // We should have stored the ArtifactPath in the map, but its Artifact // property isn't publicly readable... - this._outputArtifacts[stack.artifactId] = new codepipeline.Artifact(`Artifact_${stack.artifactId}_Outputs`); + const artifactName = `${stack.artifactId}_Outputs`; + const compactName = artifactName.slice(artifactName.length - Math.min(artifactName.length, CODE_BUILD_LENGTH_LIMIT)); + this._outputArtifacts[stack.artifactId] = new codepipeline.Artifact(compactName); } return new StackOutput(this._outputArtifacts[stack.artifactId].atPath('outputs.json'), cfnOutput.logicalId); diff --git a/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts b/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts index eec90185b9744..b43355789feb0 100644 --- a/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts +++ b/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts @@ -211,6 +211,16 @@ export class SimpleSynthAction implements codepipeline.IAction, iam.IGrantable { synthCommand: options.synthCommand ?? 'npx cdk synth', vpc: options.vpc, subnetSelection: options.subnetSelection, + environment: { + ...options.environment, + environmentVariables: { + // Need this in case the CDK CLI is not in the 'package.json' of the project, + // and 'npx' is going to download it; without this setting, 'npx' will not properly + // install the package into the root user's home directory + NPM_CONFIG_UNSAFE_PERM: { value: 'true' }, + ...options.environment?.environmentVariables, + }, + }, }); } @@ -228,6 +238,16 @@ export class SimpleSynthAction implements codepipeline.IAction, iam.IGrantable { synthCommand: options.synthCommand ?? 'npx cdk synth', vpc: options.vpc, subnetSelection: options.subnetSelection, + environment: { + ...options.environment, + environmentVariables: { + // Need this in case the CDK CLI is not in the 'package.json' of the project, + // and 'npx' is going to download it; without this setting, 'npx' will not properly + // install the package into the root user's home directory + NPM_CONFIG_UNSAFE_PERM: { value: 'true' }, + ...options.environment?.environmentVariables, + }, + }, }); } diff --git a/packages/@aws-cdk/pipelines/test/builds.test.ts b/packages/@aws-cdk/pipelines/test/builds.test.ts index 5f91472b458f1..1a8f9b5c41208 100644 --- a/packages/@aws-cdk/pipelines/test/builds.test.ts +++ b/packages/@aws-cdk/pipelines/test/builds.test.ts @@ -124,6 +124,31 @@ test.each([['npm'], ['yarn']])('%s build respects subdirectory', (npmYarn) => { }); }); +test.each([['npm'], ['yarn']])('%s build sets UNSAFE_PERM=true', (npmYarn) => { + // WHEN + new TestGitHubNpmPipeline(pipelineStack, 'Cdk', { + sourceArtifact, + cloudAssemblyArtifact, + synthAction: npmYarnBuild(npmYarn)({ + sourceArtifact, + cloudAssemblyArtifact, + }), + }); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Environment: { + EnvironmentVariables: [ + { + Name: 'NPM_CONFIG_UNSAFE_PERM', + Type: 'PLAINTEXT', + Value: 'true', + }, + ], + }, + }); +}); + test.each([['npm'], ['yarn']])('%s assumes no build step by default', (npmYarn) => { // WHEN new TestGitHubNpmPipeline(pipelineStack, 'Cdk', { diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json index 871d741989ca3..9d869296f2148 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json @@ -1,36 +1,4 @@ { - "Parameters": { - "BootstrapVersion": { - "Type": "AWS::SSM::Parameter::Value", - "Default": "/cdk-bootstrap/hnb659fds/version", - "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store." - } - }, - "Rules": { - "CheckBootstrapVersion": { - "Assertions": [ - { - "Assert": { - "Fn::Not": [ - { - "Fn::Contains": [ - [ - "1", - "2", - "3" - ], - { - "Ref": "BootstrapVersion" - } - ] - } - ] - }, - "AssertDescription": "CDK bootstrap stack version 4 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." - } - ] - } - }, "Resources": { "PipelineArtifactsBucketEncryptionKeyF5BF0670": { "Type": "AWS::KMS::Key", @@ -63,6 +31,20 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "PipelineArtifactsBucketEncryptionKeyAlias94A07392": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/codepipeline-pipelinestackpipelinee95eedaa", + "TargetKeyId": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "PipelineArtifactsBucketAEA9A052": { "Type": "AWS::S3::Bucket", "Properties": { @@ -91,20 +73,6 @@ "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, - "PipelineArtifactsBucketEncryptionKeyAlias94A07392": { - "Type": "AWS::KMS::Alias", - "Properties": { - "AliasName": "alias/codepipeline-pipelinestackpipelinee95eedaa", - "TargetKeyId": { - "Fn::GetAtt": [ - "PipelineArtifactsBucketEncryptionKeyF5BF0670", - "Arn" - ] - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "PipelineRoleB27FAA37": { "Type": "AWS::IAM::Role", "Properties": { @@ -293,7 +261,7 @@ "ProjectName": { "Ref": "PipelineBuildSynthCdkBuildProject6BEFA8E6" }, - "EnvironmentVariables": "[{\"name\":\"_PROJECT_CONFIG_HASH\",\"type\":\"PLAINTEXT\",\"value\":\"a0d828d64c88aa603631eb7a08bc4748769f45d4b84d1b550cc85f41e49dc87e\"}]" + "EnvironmentVariables": "[{\"name\":\"_PROJECT_CONFIG_HASH\",\"type\":\"PLAINTEXT\",\"value\":\"da8c1ee6d645a5802d25355fec94a3f22a961102b74ac3846898d6b14e3a2c30\"}]" }, "InputArtifacts": [ { @@ -765,6 +733,13 @@ }, "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", + "EnvironmentVariables": [ + { + "Name": "NPM_CONFIG_UNSAFE_PERM", + "Type": "PLAINTEXT", + "Value": "true" + } + ], "Image": "aws/codebuild/standard:5.0", "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, @@ -1507,5 +1482,37 @@ } } } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store." + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 4 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json index 68e86fd208660..304126cf148e6 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json @@ -1,36 +1,4 @@ { - "Parameters": { - "BootstrapVersion": { - "Type": "AWS::SSM::Parameter::Value", - "Default": "/cdk-bootstrap/hnb659fds/version", - "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store." - } - }, - "Rules": { - "CheckBootstrapVersion": { - "Assertions": [ - { - "Assert": { - "Fn::Not": [ - { - "Fn::Contains": [ - [ - "1", - "2", - "3" - ], - { - "Ref": "BootstrapVersion" - } - ] - } - ] - }, - "AssertDescription": "CDK bootstrap stack version 4 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." - } - ] - } - }, "Resources": { "PipelineArtifactsBucketEncryptionKeyF5BF0670": { "Type": "AWS::KMS::Key", @@ -63,6 +31,20 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "PipelineArtifactsBucketEncryptionKeyAlias94A07392": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/codepipeline-pipelinestackpipelinee95eedaa", + "TargetKeyId": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "PipelineArtifactsBucketAEA9A052": { "Type": "AWS::S3::Bucket", "Properties": { @@ -91,20 +73,6 @@ "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, - "PipelineArtifactsBucketEncryptionKeyAlias94A07392": { - "Type": "AWS::KMS::Alias", - "Properties": { - "AliasName": "alias/codepipeline-pipelinestackpipelinee95eedaa", - "TargetKeyId": { - "Fn::GetAtt": [ - "PipelineArtifactsBucketEncryptionKeyF5BF0670", - "Arn" - ] - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "PipelineRoleB27FAA37": { "Type": "AWS::IAM::Role", "Properties": { @@ -283,7 +251,7 @@ "ProjectName": { "Ref": "PipelineBuildSynthCdkBuildProject6BEFA8E6" }, - "EnvironmentVariables": "[{\"name\":\"_PROJECT_CONFIG_HASH\",\"type\":\"PLAINTEXT\",\"value\":\"a0d828d64c88aa603631eb7a08bc4748769f45d4b84d1b550cc85f41e49dc87e\"}]" + "EnvironmentVariables": "[{\"name\":\"_PROJECT_CONFIG_HASH\",\"type\":\"PLAINTEXT\",\"value\":\"da8c1ee6d645a5802d25355fec94a3f22a961102b74ac3846898d6b14e3a2c30\"}]" }, "InputArtifacts": [ { @@ -698,6 +666,13 @@ }, "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", + "EnvironmentVariables": [ + { + "Name": "NPM_CONFIG_UNSAFE_PERM", + "Type": "PLAINTEXT", + "Value": "true" + } + ], "Image": "aws/codebuild/standard:5.0", "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, @@ -1234,5 +1209,37 @@ } } } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store." + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 4 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline.test.ts b/packages/@aws-cdk/pipelines/test/pipeline.test.ts index 5069e9c6d542e..b04f9d7ff3a6e 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline.test.ts +++ b/packages/@aws-cdk/pipelines/test/pipeline.test.ts @@ -302,6 +302,7 @@ test('pipeline has self-mutation stage', () => { expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', + PrivilegedMode: false, }, Source: { BuildSpec: encodedJson(deepObjectLike({ @@ -358,6 +359,21 @@ test('selfmutation feature can be turned off', () => { }); }); +test('generates CodeBuild project in privileged mode', () => { + // WHEN + const stack = new Stack(app, 'PrivilegedPipelineStack', { env: PIPELINE_ENV }); + new TestGitHubNpmPipeline(stack, 'PrivilegedPipeline', { + supportDockerAssets: true, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Environment: { + PrivilegedMode: true, + }, + }); +}); + test('overridden stack names are respected', () => { // WHEN pipeline.addApplicationStage(new OneStackAppWithCustomName(app, 'App1')); diff --git a/packages/@aws-cdk/pipelines/test/testutil.ts b/packages/@aws-cdk/pipelines/test/testutil.ts index f3513eee6c5ce..26d7f686399af 100644 --- a/packages/@aws-cdk/pipelines/test/testutil.ts +++ b/packages/@aws-cdk/pipelines/test/testutil.ts @@ -1,5 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; +import { annotateMatcher, InspectionFailure, PropertyMatcher } from '@aws-cdk/assert-internal'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions'; import * as s3 from '@aws-cdk/aws-s3'; @@ -110,4 +111,20 @@ export function stackTemplate(stack: Stack) { const stage = Stage.of(stack); if (!stage) { throw new Error('stack not in a Stage'); } return stage.synth().getStackArtifact(stack.artifactId); +} + +export function stringNoLongerThan(length: number): PropertyMatcher { + return annotateMatcher({ $stringIsNoLongerThan: length }, (value: any, failure: InspectionFailure) => { + if (typeof value !== 'string') { + failure.failureReason = `Expected a string, but got '${typeof value}'`; + return false; + } + + if (value.length > length) { + failure.failureReason = `String is ${value.length} characters long. Expected at most ${length} characters`; + return false; + } + + return true; + }); } \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/validation.test.ts b/packages/@aws-cdk/pipelines/test/validation.test.ts index c78903612714e..71fa95a16f107 100644 --- a/packages/@aws-cdk/pipelines/test/validation.test.ts +++ b/packages/@aws-cdk/pipelines/test/validation.test.ts @@ -9,7 +9,7 @@ import { CfnOutput, Stack, Stage, StageProps } from '@aws-cdk/core'; import { Construct } from 'constructs'; import * as cdkp from '../lib'; import { } from './testmatchers'; -import { BucketStack, PIPELINE_ENV, TestApp, TestGitHubNpmPipeline } from './testutil'; +import { BucketStack, PIPELINE_ENV, stringNoLongerThan, TestApp, TestGitHubNpmPipeline } from './testutil'; let app: TestApp; let pipelineStack: Stack; @@ -39,6 +39,44 @@ afterEach(() => { app.cleanup(); }); +test('stackOutput generates names limited to 100 characters', () => { + const stage = new AppWithStackOutput(app, 'APreposterouslyLongAndComplicatedNameMadeUpJustToMakeItExceedTheLimitDefinedByCodeBuild'); + const pipeStage = pipeline.addApplicationStage(stage); + pipeStage.addActions(new cdkp.ShellScriptAction({ + actionName: 'TestOutput', + useOutputs: { + BUCKET_NAME: pipeline.stackOutput(stage.output), + }, + commands: ['echo $BUCKET_NAME'], + })); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'APreposterouslyLongAndComplicatedNameMadeUpJustToMakeItExceedTheLimitDefinedByCodeBuild', + Actions: arrayWith( + deepObjectLike({ + Name: 'Stack.Deploy', + OutputArtifacts: [{ Name: stringNoLongerThan(100) }], + Configuration: { + OutputFileName: 'outputs.json', + }, + }), + deepObjectLike({ + ActionTypeId: { + Provider: 'CodeBuild', + }, + Configuration: { + ProjectName: anything(), + }, + InputArtifacts: [{ Name: stringNoLongerThan(100) }], + Name: 'TestOutput', + }), + ), + }), + }); +}); + test('can use stack outputs as validation inputs', () => { // GIVEN const stage = new AppWithStackOutput(app, 'MyApp'); diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index a9d5c8e0d9f0b..7c69bcb43a3f3 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -58,7 +58,7 @@ async function parseCommandLineArguments() { .option('ec2creds', { type: 'boolean', alias: 'i', default: undefined, desc: 'Force trying to fetch EC2 instance credentials. Default: guess EC2 instance status' }) .option('version-reporting', { type: 'boolean', desc: 'Include the "AWS::CDK::Metadata" resource in synthesized templates (enabled by default)', default: undefined }) .option('path-metadata', { type: 'boolean', desc: 'Include "aws:cdk:path" CloudFormation metadata for each resource (enabled by default)', default: true }) - .option('asset-metadata', { type: 'boolean', desc: 'Include "aws:asset:*" CloudFormation metadata for resources that user assets (enabled by default)', default: true }) + .option('asset-metadata', { type: 'boolean', desc: 'Include "aws:asset:*" CloudFormation metadata for resources that uses assets (enabled by default)', default: true }) .option('role-arn', { type: 'string', alias: 'r', desc: 'ARN of Role to use when invoking CloudFormation', default: undefined, requiresArg: true }) .option('toolkit-stack-name', { type: 'string', desc: 'The name of the CDK toolkit stack', requiresArg: true }) .option('staging', { type: 'boolean', desc: 'Copy assets to the output directory (use --no-staging to disable, needed for local debugging the source files with SAM CLI)', default: true }) diff --git a/packages/aws-cdk/lib/init-templates/v1/app/javascript/jest.config.js b/packages/aws-cdk/lib/init-templates/v1/app/javascript/jest.config.js new file mode 100644 index 0000000000000..668e089fb02b3 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/app/javascript/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + testEnvironment: "node" +} diff --git a/packages/aws-cdk/lib/init-templates/v1/app/typescript/jest.config.js b/packages/aws-cdk/lib/init-templates/v1/app/typescript/jest.config.js index 772f974903b79..08263b8954a42 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/typescript/jest.config.js +++ b/packages/aws-cdk/lib/init-templates/v1/app/typescript/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + testEnvironment: 'node', roots: ['/test'], testMatch: ['**/*.test.ts'], transform: { diff --git a/packages/aws-cdk/lib/init-templates/v1/lib/typescript/jest.config.js b/packages/aws-cdk/lib/init-templates/v1/lib/typescript/jest.config.js index 772f974903b79..08263b8954a42 100644 --- a/packages/aws-cdk/lib/init-templates/v1/lib/typescript/jest.config.js +++ b/packages/aws-cdk/lib/init-templates/v1/lib/typescript/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + testEnvironment: 'node', roots: ['/test'], testMatch: ['**/*.test.ts'], transform: { diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/jest.config.js b/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/jest.config.js new file mode 100644 index 0000000000000..95495de92eb8c --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + testEnvironment: 'node' +} diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/jest.config.js b/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/jest.config.js index 772f974903b79..08263b8954a42 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/jest.config.js +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + testEnvironment: 'node', roots: ['/test'], testMatch: ['**/*.test.ts'], transform: { diff --git a/packages/aws-cdk/lib/init-templates/v2/app/javascript/jest.config.js b/packages/aws-cdk/lib/init-templates/v2/app/javascript/jest.config.js new file mode 100644 index 0000000000000..95495de92eb8c --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v2/app/javascript/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + testEnvironment: 'node' +} diff --git a/packages/aws-cdk/lib/init-templates/v2/app/typescript/jest.config.js b/packages/aws-cdk/lib/init-templates/v2/app/typescript/jest.config.js index 772f974903b79..08263b8954a42 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/typescript/jest.config.js +++ b/packages/aws-cdk/lib/init-templates/v2/app/typescript/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + testEnvironment: 'node', roots: ['/test'], testMatch: ['**/*.test.ts'], transform: { diff --git a/packages/aws-cdk/lib/init-templates/v2/lib/typescript/jest.config.js b/packages/aws-cdk/lib/init-templates/v2/lib/typescript/jest.config.js index 772f974903b79..08263b8954a42 100644 --- a/packages/aws-cdk/lib/init-templates/v2/lib/typescript/jest.config.js +++ b/packages/aws-cdk/lib/init-templates/v2/lib/typescript/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + testEnvironment: 'node', roots: ['/test'], testMatch: ['**/*.test.ts'], transform: { diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/javascript/jest.config.js b/packages/aws-cdk/lib/init-templates/v2/sample-app/javascript/jest.config.js new file mode 100644 index 0000000000000..95495de92eb8c --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/javascript/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + testEnvironment: 'node' +} diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/jest.config.js b/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/jest.config.js index 772f974903b79..08263b8954a42 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/jest.config.js +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + testEnvironment: 'node', roots: ['/test'], testMatch: ['**/*.test.ts'], transform: { diff --git a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts index e8510d3e49aee..37b0a803fb81e 100644 --- a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts @@ -54,7 +54,8 @@ integTest('upgrade legacy bootstrap stack to new bootstrap stack while in use', '--force', ], }); -})); +}), 3_600_000, // Observed in eu-west-2 that CF update takes over 10 minutes for this test. +); integTest('can and deploy if omitting execution policies', withDefaultFixture(async (fixture) => { const bootstrapStackName = fixture.bootstrapStackName; diff --git a/packages/aws-cdk/test/integ/helpers/test-helpers.ts b/packages/aws-cdk/test/integ/helpers/test-helpers.ts index 549c7a3b747d7..cfe2e57b59750 100644 --- a/packages/aws-cdk/test/integ/helpers/test-helpers.ts +++ b/packages/aws-cdk/test/integ/helpers/test-helpers.ts @@ -9,8 +9,11 @@ export type TestContext = { readonly output: NodeJS.WritableStream; }; /** * A wrapper for jest's 'test' which takes regression-disabled tests into account and prints a banner */ -export function integTest(name: string, - callback: (context: TestContext) => Promise) { +export function integTest( + name: string, + callback: (context: TestContext) => Promise, + timeoutMillis?: number, +) { // Integ tests can run concurrently, and are responsible for blocking themselves if they cannot. const runner = shouldSkip(name) ? test.skip : test.concurrent; @@ -36,7 +39,7 @@ export function integTest(name: string, process.stderr.write('✅'); } } - }); + }, timeoutMillis); } function shouldSkip(testName: string) { diff --git a/tools/eslint-plugin-cdk/package.json b/tools/eslint-plugin-cdk/package.json index 562f31110ad8d..9a28d67e390b2 100644 --- a/tools/eslint-plugin-cdk/package.json +++ b/tools/eslint-plugin-cdk/package.json @@ -16,6 +16,7 @@ "@types/fs-extra": "^8.1.1", "@types/jest": "^26.0.23", "@types/node": "^10.17.59", + "@types/estree": "*", "eslint-plugin-rulesdir": "^0.2.0", "jest": "^26.6.3", "typescript": "~3.9.9" @@ -32,5 +33,5 @@ }, "keywords": [], "author": "", - "license": "ISC" + "license": "Apache-2.0" } diff --git a/tools/pkglint/lib/rules.ts b/tools/pkglint/lib/rules.ts index 3555b4e4d0ae0..d7494a372c1c7 100644 --- a/tools/pkglint/lib/rules.ts +++ b/tools/pkglint/lib/rules.ts @@ -818,27 +818,6 @@ export class NodeCompatibility extends ValidationRule { } } -/** - * Verifies that the ``@types/`` dependencies are correctly recorded in ``devDependencies`` and not ``dependencies``. - */ -export class NoAtTypesInDependencies extends ValidationRule { - public readonly name = 'dependencies/at-types'; - - public validate(pkg: PackageJson): void { - const predicate = (s: string) => s.startsWith('@types/'); - for (const dependency of pkg.getDependencies(predicate)) { - pkg.report({ - ruleName: this.name, - message: `dependency on ${dependency.name}@${dependency.version} must be in devDependencies`, - fix: () => { - pkg.addDevDependency(dependency.name, dependency.version); - pkg.removeDependency(predicate); - }, - }); - } - } -} - function isCdkModuleName(name: string) { return !!name.match(/^@aws-cdk\//); } diff --git a/version.v1.json b/version.v1.json index 60bdf592f8796..9076251f5032f 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.103.0" + "version": "1.104.0" }