From a2bb263ec40c842dc332f2a55d494849665d38ba Mon Sep 17 00:00:00 2001 From: Niko Virtala Date: Thu, 4 Aug 2022 17:49:52 +0300 Subject: [PATCH 1/6] feat(appsync): allow user to configure log retention time (#21418) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This pull request implements a feature that allows users to configure the log retention period for App Sync logs. AWS AppSync doesn't allow users to use user-defined log groups, leaving the option to create the log group for the user and set the retention time accordingly. Fortunately, the service always creates its log groups according to a pre-defined naming convention. > The log group is named following the /aws/appsync/apis/{graphql_api_id} format ref. https://docs.aws.amazon.com/appsync/latest/devguide/monitoring.html At the same time, AWS CDK provides a stable [construct](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_logs.LogRetention.html) to set log retention times for any log group. Thus, it is completely unnecessary to force every user to learn this detail when it can be abstracted to the construct creating the resource for them – that is what this pull request aims to do. This is the continuation of work done in #20536 – this time with documentation and integration test 😄 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-appsync/README.md | 25 +- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 29 +- packages/@aws-cdk/aws-appsync/package.json | 3 + .../@aws-cdk/aws-appsync/test/appsync.test.ts | 47 ++ .../aws-appsync/test/integ.log-retention.ts | 40 ++ .../AppSyncIntegLogRetention.template.json | 240 +++++++ ...aultTestDeployAssert4E6713E1.template.json | 183 ++++++ .../index.js | 612 ++++++++++++++++++ .../index.d.ts | 1 + .../index.js | 174 +++++ .../index.ts | 186 ++++++ .../test/log-retention.integ.snapshot/cdk.out | 1 + .../log-retention.integ.snapshot/integ.json | 11 + .../manifest.json | 184 ++++++ .../log-retention.integ.snapshot/tree.json | 592 +++++++++++++++++ 15 files changed, 2326 insertions(+), 2 deletions(-) create mode 100644 packages/@aws-cdk/aws-appsync/test/integ.log-retention.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/AppSyncIntegLogRetention.template.json create mode 100644 packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/IntegDefaultTestDeployAssert4E6713E1.template.json create mode 100644 packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd.bundle/index.js create mode 100644 packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/index.d.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/index.js create mode 100644 packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/index.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/tree.json diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 2422fbe40a5fe..32d44938c8eed 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -334,6 +334,29 @@ new route53.CnameRecord(this, `CnameApiRecord`, { }); ``` +## Log Group + +AppSync automatically create a log group with the name `/aws/appsync/apis/` upon deployment with +log data set to never expire. If you want to set a different expiration period, use the `logConfig.retention` property. + +To obtain the GraphQL API's log group as a `logs.ILogGroup` use the `logGroup` property of the +`GraphqlApi` construct. + +```ts +import * as logs from '@aws-cdk/aws-logs'; + +const logConfig: appsync.LogConfig = { + retention: logs.RetentionDays.ONE_WEEK, +}; + +new appsync.GraphqlApi(this, 'api', { + authorizationConfig: {}, + name: 'myApi', + schema: appsync.Schema.fromAsset(path.join(__dirname, 'myApi.graphql')), + logConfig, +}); +``` + ## Schema Every GraphQL Api needs a schema to define the Api. CDK offers `appsync.Schema` @@ -427,7 +450,7 @@ new appsync.GraphqlApi(this, 'api', { defaultAuthorization: { authorizationType: appsync.AuthorizationType.LAMBDA, lambdaAuthorizerConfig: { - handler: authFunction, + handler: authFunction, // can also specify `resultsCacheTtl` and `validationRegex`. }, }, diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index ab740aac4cfb2..d384713310581 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -2,6 +2,7 @@ import { ICertificate } from '@aws-cdk/aws-certificatemanager'; import { IUserPool } from '@aws-cdk/aws-cognito'; import { ManagedPolicy, Role, IRole, ServicePrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam'; import { IFunction } from '@aws-cdk/aws-lambda'; +import { ILogGroup, LogGroup, LogRetention, RetentionDays } from '@aws-cdk/aws-logs'; import { ArnFormat, CfnResource, Duration, Expiration, IResolvable, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema, CfnDomainName, CfnDomainNameApiAssociation } from './appsync.generated'; @@ -248,6 +249,16 @@ export interface LogConfig { * @default - None */ readonly role?: IRole; + + /** + * The number of days log events are kept in CloudWatch Logs. + * By default AppSync keeps the logs infinitely. When updating this property, + * unsetting it doesn't remove the log retention policy. + * To remove the retention policy, set the value to `INFINITE` + * + * @default RetentionDays.INFINITE + */ + readonly retention?: RetentionDays } /** @@ -459,6 +470,11 @@ export class GraphqlApi extends GraphqlApiBase { */ public readonly apiKey?: string; + /** + * the CloudWatch Log Group for this API + */ + public readonly logGroup: ILogGroup; + private schemaResource: CfnGraphQLSchema; private api: CfnGraphQLApi; private apiKeyResource?: CfnApiKey; @@ -527,6 +543,16 @@ export class GraphqlApi extends GraphqlApiBase { }); } + const logGroupName = `/aws/appsync/apis/${this.apiId}`; + + this.logGroup = LogGroup.fromLogGroupName(this, 'LogGroup', logGroupName); + + if (props.logConfig?.retention) { + new LogRetention(this, 'LogRetention', { + logGroupName: this.logGroup.logGroupName, + retention: props.logConfig.retention, + }); + }; } /** @@ -620,10 +646,11 @@ export class GraphqlApi extends GraphqlApiBase { ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSAppSyncPushToCloudWatchLogs'), ], }).roleArn; + const fieldLogLevel: FieldLogLevel = config.fieldLogLevel ?? FieldLogLevel.NONE; return { cloudWatchLogsRoleArn: logsRoleArn, excludeVerboseContent: config.excludeVerboseContent, - fieldLogLevel: config.fieldLogLevel, + fieldLogLevel: fieldLogLevel, }; } diff --git a/packages/@aws-cdk/aws-appsync/package.json b/packages/@aws-cdk/aws-appsync/package.json index 480e781af2bbc..b0e9b8c1320d8 100644 --- a/packages/@aws-cdk/aws-appsync/package.json +++ b/packages/@aws-cdk/aws-appsync/package.json @@ -84,6 +84,7 @@ "@aws-cdk/aws-stepfunctions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/integ-runner": "0.0.0", + "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", @@ -97,6 +98,7 @@ "@aws-cdk/aws-elasticsearch": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-opensearchservice": "0.0.0", "@aws-cdk/aws-rds": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", @@ -113,6 +115,7 @@ "@aws-cdk/aws-elasticsearch": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-opensearchservice": "0.0.0", "@aws-cdk/aws-rds": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", diff --git a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts index 350fbff6229db..1ed2a95b6aeae 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts @@ -2,6 +2,7 @@ import * as path from 'path'; import { Template } from '@aws-cdk/assertions'; import { Certificate } from '@aws-cdk/aws-certificatemanager'; import * as iam from '@aws-cdk/aws-iam'; +import * as logs from '@aws-cdk/aws-logs'; import * as cdk from '@aws-cdk/core'; import * as appsync from '../lib'; @@ -191,3 +192,49 @@ test('appsync GraphqlApi should be configured with custom domain when specified' DomainName: domainName, }); }); + +test('log retention should be configured with given retention time when specified', () => { + // GIVEN + const retentionTime = logs.RetentionDays.ONE_WEEK; + + // WHEN + new appsync.GraphqlApi(stack, 'log-retention', { + authorizationConfig: {}, + name: 'log-retention', + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), + logConfig: { + retention: retentionTime, + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('Custom::LogRetention', { + LogGroupName: { + 'Fn::Join': [ + '', + [ + '/aws/appsync/apis/', + { + 'Fn::GetAtt': [ + 'logretentionB69DFB48', + 'ApiId', + ], + }, + ], + ], + }, + RetentionInDays: 7, + }); +}); + +test('log retention should not appear when no retention time is specified', () => { + // WHEN + new appsync.GraphqlApi(stack, 'no-log-retention', { + authorizationConfig: {}, + name: 'no-log-retention', + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), + }); + + // THEN + Template.fromStack(stack).resourceCountIs('Custom::LogRetention', 0); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.log-retention.ts b/packages/@aws-cdk/aws-appsync/test/integ.log-retention.ts new file mode 100644 index 0000000000000..664c28ceee237 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/integ.log-retention.ts @@ -0,0 +1,40 @@ +import { join } from 'path'; +import { RetentionDays } from '@aws-cdk/aws-logs'; +import { App, Stack } from '@aws-cdk/core'; +import { ExpectedResult, IntegTest } from '@aws-cdk/integ-tests'; +import { GraphqlApi, LogConfig, Schema } from '../lib'; + +const app = new App(); +const stack = new Stack(app, 'AppSyncIntegLogRetention'); + + +const retentionTime = RetentionDays.ONE_WEEK; +const logConfig: LogConfig = { + retention: retentionTime, +}; + +const api = new GraphqlApi(stack, 'GraphqlApi', { + authorizationConfig: {}, + name: 'IntegLogRetention', + schema: Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), + logConfig, +}); + +const integ = new IntegTest(app, 'Integ', { testCases: [stack] }); + +const describe = integ.assertions.awsApiCall('CloudWatchLogs', + 'describeLogGroups', + { + logGroupNamePrefix: api.logGroup.logGroupName, + }); + +describe.expect(ExpectedResult.objectLike({ + logGroups: [ + { + logGroupName: api.logGroup.logGroupName, + retentionInDays: retentionTime, + }, + ], +})); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/AppSyncIntegLogRetention.template.json b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/AppSyncIntegLogRetention.template.json new file mode 100644 index 0000000000000..1dc3cc6f7eaa2 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/AppSyncIntegLogRetention.template.json @@ -0,0 +1,240 @@ +{ + "Resources": { + "GraphqlApiApiLogsRoleBB9E6BAD": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "appsync.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs" + ] + ] + } + ] + } + }, + "GraphqlApi1B6CF24C": { + "Type": "AWS::AppSync::GraphQLApi", + "Properties": { + "AuthenticationType": "API_KEY", + "Name": "IntegLogRetention", + "LogConfig": { + "CloudWatchLogsRoleArn": { + "Fn::GetAtt": [ + "GraphqlApiApiLogsRoleBB9E6BAD", + "Arn" + ] + }, + "FieldLogLevel": "NONE" + } + } + }, + "GraphqlApiSchema1B71CE4F": { + "Type": "AWS::AppSync::GraphQLSchema", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "GraphqlApi1B6CF24C", + "ApiId" + ] + }, + "Definition": "type test {\n version: String!\n}\ntype Query {\n getTests: [test]!\n}\ntype Mutation {\n addTest(version: String!): test\n}\n" + } + }, + "GraphqlApiDefaultApiKey47EE7128": { + "Type": "AWS::AppSync::ApiKey", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "GraphqlApi1B6CF24C", + "ApiId" + ] + } + }, + "DependsOn": [ + "GraphqlApiSchema1B71CE4F" + ] + }, + "GraphqlApiLogRetention082A7017": { + "Type": "Custom::LogRetention", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A", + "Arn" + ] + }, + "LogGroupName": { + "Fn::Join": [ + "", + [ + "/aws/appsync/apis/", + { + "Fn::GetAtt": [ + "GraphqlApi1B6CF24C", + "ApiId" + ] + } + ] + ] + }, + "RetentionInDays": 7 + } + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB": { + "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" + ] + ] + } + ] + } + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:DeleteRetentionPolicy", + "logs:PutRetentionPolicy" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "Roles": [ + { + "Ref": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB" + } + ] + } + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Ref": "AssetParametersc70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13aS3BucketB4787383" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersc70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13aS3VersionKey8CF8E820" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersc70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13aS3VersionKey8CF8E820" + } + ] + } + ] + } + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB", + "Arn" + ] + } + }, + "DependsOn": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB" + ] + } + }, + "Parameters": { + "AssetParametersc70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13aS3BucketB4787383": { + "Type": "String", + "Description": "S3 bucket for asset \"c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a\"" + }, + "AssetParametersc70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13aS3VersionKey8CF8E820": { + "Type": "String", + "Description": "S3 key for asset version \"c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a\"" + }, + "AssetParametersc70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13aArtifactHashC9560B34": { + "Type": "String", + "Description": "Artifact hash for asset \"c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a\"" + } + }, + "Outputs": { + "ExportsOutputFnGetAttGraphqlApi1B6CF24CApiIdE34D50AD": { + "Value": { + "Fn::GetAtt": [ + "GraphqlApi1B6CF24C", + "ApiId" + ] + }, + "Export": { + "Name": "AppSyncIntegLogRetention:ExportsOutputFnGetAttGraphqlApi1B6CF24CApiIdE34D50AD" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/IntegDefaultTestDeployAssert4E6713E1.template.json b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/IntegDefaultTestDeployAssert4E6713E1.template.json new file mode 100644 index 0000000000000..ddbbd027583b6 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/IntegDefaultTestDeployAssert4E6713E1.template.json @@ -0,0 +1,183 @@ +{ + "Resources": { + "AwsApiCallCloudWatchLogsdescribeLogGroups": { + "Type": "Custom::DeployAssert@SdkCallCloudWatchLogsdescribeLogGroups", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "CloudWatchLogs", + "api": "describeLogGroups", + "parameters": { + "logGroupNamePrefix": { + "Fn::Join": [ + "", + [ + "/aws/appsync/apis/", + { + "Fn::ImportValue": "AppSyncIntegLogRetention:ExportsOutputFnGetAttGraphqlApi1B6CF24CApiIdE34D50AD" + } + ] + ] + } + }, + "flattenResponse": "false", + "salt": "1659609543121" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AwsApiCallCloudWatchLogsdescribeLogGroupsAssertEqualsCloudWatchLogsdescribeLogGroupsD2E2CD37": { + "Type": "Custom::DeployAssert@AssertEquals", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "actual": { + "Fn::GetAtt": [ + "AwsApiCallCloudWatchLogsdescribeLogGroups", + "apiCallResponse" + ] + }, + "expected": { + "Fn::Join": [ + "", + [ + "{\"$ObjectLike\":{\"logGroups\":[{\"logGroupName\":\"/aws/appsync/apis/", + { + "Fn::ImportValue": "AppSyncIntegLogRetention:ExportsOutputFnGetAttGraphqlApi1B6CF24CApiIdE34D50AD" + }, + "\",\"retentionInDays\":7}]}}" + ] + ] + }, + "salt": "1659609543122" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "logs:DescribeLogGroups" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Ref": "AssetParameters0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbdS3BucketF94385B7" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbdS3VersionKey66DB0F9E" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbdS3VersionKey66DB0F9E" + } + ] + } + ] + } + ] + ] + } + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + } + }, + "Outputs": { + "AssertionResultsAssertEqualsCloudWatchLogsdescribeLogGroups": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallCloudWatchLogsdescribeLogGroupsAssertEqualsCloudWatchLogsdescribeLogGroupsD2E2CD37", + "data" + ] + } + } + }, + "Parameters": { + "AssetParameters0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbdS3BucketF94385B7": { + "Type": "String", + "Description": "S3 bucket for asset \"0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd\"" + }, + "AssetParameters0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbdS3VersionKey66DB0F9E": { + "Type": "String", + "Description": "S3 key for asset version \"0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd\"" + }, + "AssetParameters0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbdArtifactHash2AC894D9": { + "Type": "String", + "Description": "Artifact hash for asset \"0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd.bundle/index.js b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd.bundle/index.js new file mode 100644 index 0000000000000..b3ec1b8c53d30 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd.bundle/index.js @@ -0,0 +1,612 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// lib/assertions/providers/lambda-handler/index.ts +var lambda_handler_exports = {}; +__export(lambda_handler_exports, { + handler: () => handler +}); +module.exports = __toCommonJS(lambda_handler_exports); + +// ../assertions/lib/matcher.ts +var Matcher = class { + static isMatcher(x) { + return x && x instanceof Matcher; + } +}; +var MatchResult = class { + constructor(target) { + this.failures = []; + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.target = target; + } + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + recordFailure(failure) { + this.failures.push(failure); + return this; + } + hasFailed() { + return this.failures.length !== 0; + } + get failCount() { + return this.failures.length; + } + compose(id, inner) { + const innerF = inner.failures; + this.failures.push(...innerF.map((f) => { + return { path: [id, ...f.path], message: f.message, matcher: f.matcher }; + })); + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + toHumanStrings() { + return this.failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at ${r.path.join("")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + } + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } +}; + +// ../assertions/lib/private/matchers/absent.ts +var AbsentMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual !== void 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Received ${actual}, but key should be absent` + }); + } + return result; + } +}; + +// ../assertions/lib/private/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} + +// ../assertions/lib/match.ts +var Match = class { + static absent() { + return new AbsentMatch("absent"); + } + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + static not(pattern) { + return new NotMatch("not", pattern); + } + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + static anyValue() { + return new AnyMatch("anyValue"); + } + static stringLikeRegexp(pattern) { + return new StringLikeRegexpMatch("stringLikeRegexp", pattern); + } +}; +var LiteralMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partialObjects = options.partialObjects ?? false; + if (Matcher.isMatcher(this.pattern)) { + throw new Error("LiteralMatch cannot directly contain another matcher. Remove the top-level matcher or nest it more deeply."); + } + } + test(actual) { + if (Array.isArray(this.pattern)) { + return new ArrayMatch(this.name, this.pattern, { subsequence: false, partialObjects: this.partialObjects }).test(actual); + } + if (typeof this.pattern === "object") { + return new ObjectMatch(this.name, this.pattern, { partial: this.partialObjects }).test(actual); + } + const result = new MatchResult(actual); + if (typeof this.pattern !== typeof actual) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected type ${typeof this.pattern} but received ${getType(actual)}` + }); + return result; + } + if (actual !== this.pattern) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected ${this.pattern} but received ${actual}` + }); + } + return result; + } +}; +var ArrayMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.subsequence = options.subsequence ?? true; + this.partialObjects = options.partialObjects ?? false; + } + test(actual) { + if (!Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type array but received ${getType(actual)}` + }); + } + if (!this.subsequence && this.pattern.length !== actual.length) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected array of length ${this.pattern.length} but received ${actual.length}` + }); + } + let patternIdx = 0; + let actualIdx = 0; + const result = new MatchResult(actual); + while (patternIdx < this.pattern.length && actualIdx < actual.length) { + const patternElement = this.pattern[patternIdx]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const matcherName = matcher.name; + if (this.subsequence && (matcherName == "absent" || matcherName == "anyValue")) { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + if (!this.subsequence || !innerResult.hasFailed()) { + result.compose(`[${actualIdx}]`, innerResult); + patternIdx++; + actualIdx++; + } else { + actualIdx++; + } + } + for (; patternIdx < this.pattern.length; patternIdx++) { + const pattern = this.pattern[patternIdx]; + const element = Matcher.isMatcher(pattern) || typeof pattern === "object" ? " " : ` [${pattern}] `; + result.recordFailure({ + matcher: this, + path: [], + message: `Missing element${element}at pattern index ${patternIdx}` + }); + } + return result; + } +}; +var ObjectMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partial = options.partial ?? true; + } + test(actual) { + if (typeof actual !== "object" || Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type object but received ${getType(actual)}` + }); + } + const result = new MatchResult(actual); + if (!this.partial) { + for (const a of Object.keys(actual)) { + if (!(a in this.pattern)) { + result.recordFailure({ + matcher: this, + path: [`/${a}`], + message: "Unexpected key" + }); + } + } + } + for (const [patternKey, patternVal] of Object.entries(this.pattern)) { + if (!(patternKey in actual) && !(patternVal instanceof AbsentMatch)) { + result.recordFailure({ + matcher: this, + path: [`/${patternKey}`], + message: `Missing key '${patternKey}' among {${Object.keys(actual).join(",")}}` + }); + continue; + } + const matcher = Matcher.isMatcher(patternVal) ? patternVal : new LiteralMatch(this.name, patternVal, { partialObjects: this.partial }); + const inner = matcher.test(actual[patternKey]); + result.compose(`/${patternKey}`, inner); + } + return result; + } +}; +var SerializedJson = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + if (getType(actual) !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + return result; + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + result.recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + return result; + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + result.compose(`(${this.name})`, innerResult); + return result; + } +}; +var NotMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(actual); + const result = new MatchResult(actual); + if (innerResult.failCount === 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Found unexpected match: ${JSON.stringify(actual, void 0, 2)}` + }); + } + return result; + } +}; +var AnyMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual == null) { + result.recordFailure({ + matcher: this, + path: [], + message: "Expected a value but found none" + }); + } + return result; + } +}; +var StringLikeRegexpMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + const regex = new RegExp(this.pattern, "gm"); + if (typeof actual !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected a string, but got '${typeof actual}'` + }); + } + if (!regex.test(actual)) { + result.recordFailure({ + matcher: this, + path: [], + message: `String '${actual}' did not match pattern '${this.pattern}'` + }); + } + return result; + } +}; + +// lib/assertions/providers/lambda-handler/base.ts +var https = __toESM(require("https")); +var url = __toESM(require("url")); +var CustomResourceHandler = class { + constructor(event, context) { + this.event = event; + this.context = context; + this.timedOut = false; + this.timeout = setTimeout(async () => { + await this.respond({ + status: "FAILED", + reason: "Lambda Function Timeout", + data: this.context.logStreamName + }); + this.timedOut = true; + }, context.getRemainingTimeInMillis() - 1200); + this.event = event; + this.physicalResourceId = extractPhysicalResourceId(event); + } + async handle() { + try { + console.log(`Event: ${JSON.stringify({ ...this.event, ResponseURL: "..." })}`); + const response = await this.processEvent(this.event.ResourceProperties); + console.log(`Event output : ${JSON.stringify(response)}`); + await this.respond({ + status: "SUCCESS", + reason: "OK", + data: response + }); + } catch (e) { + console.log(e); + await this.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + } finally { + clearTimeout(this.timeout); + } + } + respond(response) { + if (this.timedOut) { + return; + } + const cfResponse = { + Status: response.status, + Reason: response.reason, + PhysicalResourceId: this.physicalResourceId, + StackId: this.event.StackId, + RequestId: this.event.RequestId, + LogicalResourceId: this.event.LogicalResourceId, + NoEcho: false, + Data: response.data + }; + const responseBody = JSON.stringify(cfResponse); + console.log("Responding to CloudFormation", responseBody); + const parsedUrl = url.parse(this.event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: "PUT", + headers: { "content-type": "", "content-length": responseBody.length } + }; + return new Promise((resolve, reject) => { + try { + const request2 = https.request(requestOptions, resolve); + request2.on("error", reject); + request2.write(responseBody); + request2.end(); + } catch (e) { + reject(e); + } + }); + } +}; +function extractPhysicalResourceId(event) { + switch (event.RequestType) { + case "Create": + return event.LogicalResourceId; + case "Update": + case "Delete": + return event.PhysicalResourceId; + } +} + +// lib/assertions/providers/lambda-handler/assertion.ts +var AssertionHandler = class extends CustomResourceHandler { + async processEvent(request2) { + let actual = decodeCall(request2.actual); + const expected = decodeCall(request2.expected); + let result; + const matcher = new MatchCreator(expected).getMatcher(); + console.log(`Testing equality between ${JSON.stringify(request2.actual)} and ${JSON.stringify(request2.expected)}`); + const matchResult = matcher.test(actual); + matchResult.finished(); + if (matchResult.hasFailed()) { + result = { + data: JSON.stringify({ + status: "fail", + message: [ + ...matchResult.toHumanStrings(), + JSON.stringify(matchResult.target, void 0, 2) + ].join("\n") + }) + }; + if (request2.failDeployment) { + throw new Error(result.data); + } + } else { + result = { + data: JSON.stringify({ + status: "pass" + }) + }; + } + return result; + } +}; +var MatchCreator = class { + constructor(obj) { + this.parsedObj = { + matcher: obj + }; + } + getMatcher() { + try { + const final = JSON.parse(JSON.stringify(this.parsedObj), function(_k, v) { + const nested = Object.keys(v)[0]; + switch (nested) { + case "$ArrayWith": + return Match.arrayWith(v[nested]); + case "$ObjectLike": + return Match.objectLike(v[nested]); + case "$StringLike": + return Match.stringLikeRegexp(v[nested]); + default: + return v; + } + }); + if (Matcher.isMatcher(final.matcher)) { + return final.matcher; + } + return Match.exact(final.matcher); + } catch { + return Match.exact(this.parsedObj.matcher); + } + } +}; +function decodeCall(call) { + if (!call) { + return void 0; + } + try { + const parsed = JSON.parse(call); + return parsed; + } catch (e) { + return call; + } +} + +// lib/assertions/providers/lambda-handler/utils.ts +function decode(object) { + return JSON.parse(JSON.stringify(object), (_k, v) => { + switch (v) { + case "TRUE:BOOLEAN": + return true; + case "FALSE:BOOLEAN": + return false; + default: + return v; + } + }); +} + +// lib/assertions/providers/lambda-handler/sdk.ts +function flatten(object) { + return Object.assign( + {}, + ...function _flatten(child, path = []) { + return [].concat(...Object.keys(child).map((key) => { + const childKey = Buffer.isBuffer(child[key]) ? child[key].toString("utf8") : child[key]; + return typeof childKey === "object" && childKey !== null ? _flatten(childKey, path.concat([key])) : { [path.concat([key]).join(".")]: childKey }; + })); + }(object) + ); +} +var AwsApiCallHandler = class extends CustomResourceHandler { + async processEvent(request2) { + const AWS = require("aws-sdk"); + console.log(`AWS SDK VERSION: ${AWS.VERSION}`); + const service = new AWS[request2.service](); + const response = await service[request2.api](request2.parameters && decode(request2.parameters)).promise(); + console.log(`SDK response received ${JSON.stringify(response)}`); + delete response.ResponseMetadata; + const respond = { + apiCallResponse: response + }; + const flatData = { + ...flatten(respond) + }; + return request2.flattenResponse === "true" ? flatData : respond; + } +}; + +// lib/assertions/providers/lambda-handler/types.ts +var ASSERT_RESOURCE_TYPE = "Custom::DeployAssert@AssertEquals"; +var SDK_RESOURCE_TYPE_PREFIX = "Custom::DeployAssert@SdkCall"; + +// lib/assertions/providers/lambda-handler/index.ts +async function handler(event, context) { + const provider = createResourceHandler(event, context); + await provider.handle(); +} +function createResourceHandler(event, context) { + if (event.ResourceType.startsWith(SDK_RESOURCE_TYPE_PREFIX)) { + return new AwsApiCallHandler(event, context); + } + switch (event.ResourceType) { + case ASSERT_RESOURCE_TYPE: + return new AssertionHandler(event, context); + default: + throw new Error(`Unsupported resource type "${event.ResourceType}`); + } +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + handler +}); diff --git a/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/index.d.ts b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/index.d.ts new file mode 100644 index 0000000000000..9bbf5854684b6 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/index.d.ts @@ -0,0 +1 @@ +export declare function handler(event: AWSLambda.CloudFormationCustomResourceEvent, context: AWSLambda.Context): Promise; diff --git a/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/index.js b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/index.js new file mode 100644 index 0000000000000..a844de880012a --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/index.js @@ -0,0 +1,174 @@ +"use strict"; +/* eslint-disable no-console */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handler = void 0; +// eslint-disable-next-line import/no-extraneous-dependencies +const AWS = require("aws-sdk"); +/** + * Creates a log group and doesn't throw if it exists. + * + * @param logGroupName the name of the log group to create. + * @param region to create the log group in + * @param options CloudWatch API SDK options. + */ +async function createLogGroupSafe(logGroupName, region, options) { + // If we set the log retention for a lambda, then due to the async nature of + // Lambda logging there could be a race condition when the same log group is + // already being created by the lambda execution. This can sometime result in + // an error "OperationAbortedException: A conflicting operation is currently + // in progress...Please try again." + // To avoid an error, we do as requested and try again. + let retryCount = options?.maxRetries == undefined ? 10 : options.maxRetries; + const delay = options?.retryOptions?.base == undefined ? 10 : options.retryOptions.base; + do { + try { + const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28', region, ...options }); + await cloudwatchlogs.createLogGroup({ logGroupName }).promise(); + return; + } + catch (error) { + if (error.code === 'ResourceAlreadyExistsException') { + // The log group is already created by the lambda execution + return; + } + if (error.code === 'OperationAbortedException') { + if (retryCount > 0) { + retryCount--; + await new Promise(resolve => setTimeout(resolve, delay)); + continue; + } + else { + // The log group is still being created by another execution but we are out of retries + throw new Error('Out of attempts to create a logGroup'); + } + } + throw error; + } + } while (true); // exit happens on retry count check +} +/** + * Puts or deletes a retention policy on a log group. + * + * @param logGroupName the name of the log group to create + * @param region the region of the log group + * @param options CloudWatch API SDK options. + * @param retentionInDays the number of days to retain the log events in the specified log group. + */ +async function setRetentionPolicy(logGroupName, region, options, retentionInDays) { + // The same as in createLogGroupSafe(), here we could end up with the race + // condition where a log group is either already being created or its retention + // policy is being updated. This would result in an OperationAbortedException, + // which we will try to catch and retry the command a number of times before failing + let retryCount = options?.maxRetries == undefined ? 10 : options.maxRetries; + const delay = options?.retryOptions?.base == undefined ? 10 : options.retryOptions.base; + do { + try { + const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28', region, ...options }); + if (!retentionInDays) { + await cloudwatchlogs.deleteRetentionPolicy({ logGroupName }).promise(); + } + else { + await cloudwatchlogs.putRetentionPolicy({ logGroupName, retentionInDays }).promise(); + } + return; + } + catch (error) { + if (error.code === 'OperationAbortedException') { + if (retryCount > 0) { + retryCount--; + await new Promise(resolve => setTimeout(resolve, delay)); + continue; + } + else { + // The log group is still being created by another execution but we are out of retries + throw new Error('Out of attempts to create a logGroup'); + } + } + throw error; + } + } while (true); // exit happens on retry count check +} +async function handler(event, context) { + try { + console.log(JSON.stringify({ ...event, ResponseURL: '...' })); + // The target log group + const logGroupName = event.ResourceProperties.LogGroupName; + // The region of the target log group + const logGroupRegion = event.ResourceProperties.LogGroupRegion; + // Parse to AWS SDK retry options + const retryOptions = parseRetryOptions(event.ResourceProperties.SdkRetry); + if (event.RequestType === 'Create' || event.RequestType === 'Update') { + // Act on the target log group + await createLogGroupSafe(logGroupName, logGroupRegion, retryOptions); + await setRetentionPolicy(logGroupName, logGroupRegion, retryOptions, parseInt(event.ResourceProperties.RetentionInDays, 10)); + if (event.RequestType === 'Create') { + // Set a retention policy of 1 day on the logs of this very function. + // Due to the async nature of the log group creation, the log group for this function might + // still be not created yet at this point. Therefore we attempt to create it. + // In case it is being created, createLogGroupSafe will handle the conflict. + const region = process.env.AWS_REGION; + await createLogGroupSafe(`/aws/lambda/${context.functionName}`, region, retryOptions); + // If createLogGroupSafe fails, the log group is not created even after multiple attempts. + // In this case we have nothing to set the retention policy on but an exception will skip + // the next line. + await setRetentionPolicy(`/aws/lambda/${context.functionName}`, region, retryOptions, 1); + } + } + await respond('SUCCESS', 'OK', logGroupName); + } + catch (e) { + console.log(e); + await respond('FAILED', e.message, event.ResourceProperties.LogGroupName); + } + function respond(responseStatus, reason, physicalResourceId) { + const responseBody = JSON.stringify({ + Status: responseStatus, + Reason: reason, + PhysicalResourceId: physicalResourceId, + StackId: event.StackId, + RequestId: event.RequestId, + LogicalResourceId: event.LogicalResourceId, + Data: { + // Add log group name as part of the response so that it's available via Fn::GetAtt + LogGroupName: event.ResourceProperties.LogGroupName, + }, + }); + console.log('Responding', responseBody); + // eslint-disable-next-line @typescript-eslint/no-require-imports + const parsedUrl = require('url').parse(event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { 'content-type': '', 'content-length': responseBody.length }, + }; + return new Promise((resolve, reject) => { + try { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const request = require('https').request(requestOptions, resolve); + request.on('error', reject); + request.write(responseBody); + request.end(); + } + catch (e) { + reject(e); + } + }); + } + function parseRetryOptions(rawOptions) { + const retryOptions = {}; + if (rawOptions) { + if (rawOptions.maxRetries) { + retryOptions.maxRetries = parseInt(rawOptions.maxRetries, 10); + } + if (rawOptions.base) { + retryOptions.retryOptions = { + base: parseInt(rawOptions.base, 10), + }; + } + } + return retryOptions; + } +} +exports.handler = handler; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsK0JBQStCOzs7QUFFL0IsNkRBQTZEO0FBQzdELCtCQUErQjtBQVMvQjs7Ozs7O0dBTUc7QUFDSCxLQUFLLFVBQVUsa0JBQWtCLENBQUMsWUFBb0IsRUFBRSxNQUFlLEVBQUUsT0FBeUI7SUFDaEcsNEVBQTRFO0lBQzVFLDRFQUE0RTtJQUM1RSw2RUFBNkU7SUFDN0UsNEVBQTRFO0lBQzVFLG1DQUFtQztJQUNuQyx1REFBdUQ7SUFDdkQsSUFBSSxVQUFVLEdBQUcsT0FBTyxFQUFFLFVBQVUsSUFBSSxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQztJQUM1RSxNQUFNLEtBQUssR0FBRyxPQUFPLEVBQUUsWUFBWSxFQUFFLElBQUksSUFBSSxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUM7SUFDeEYsR0FBRztRQUNELElBQUk7WUFDRixNQUFNLGNBQWMsR0FBRyxJQUFJLEdBQUcsQ0FBQyxjQUFjLENBQUMsRUFBRSxVQUFVLEVBQUUsWUFBWSxFQUFFLE1BQU0sRUFBRSxHQUFHLE9BQU8sRUFBRSxDQUFDLENBQUM7WUFDaEcsTUFBTSxjQUFjLENBQUMsY0FBYyxDQUFDLEVBQUUsWUFBWSxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUNoRSxPQUFPO1NBQ1I7UUFBQyxPQUFPLEtBQUssRUFBRTtZQUNkLElBQUksS0FBSyxDQUFDLElBQUksS0FBSyxnQ0FBZ0MsRUFBRTtnQkFDbkQsMkRBQTJEO2dCQUMzRCxPQUFPO2FBQ1I7WUFDRCxJQUFJLEtBQUssQ0FBQyxJQUFJLEtBQUssMkJBQTJCLEVBQUU7Z0JBQzlDLElBQUksVUFBVSxHQUFHLENBQUMsRUFBRTtvQkFDbEIsVUFBVSxFQUFFLENBQUM7b0JBQ2IsTUFBTSxJQUFJLE9BQU8sQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxPQUFPLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQztvQkFDekQsU0FBUztpQkFDVjtxQkFBTTtvQkFDTCxzRkFBc0Y7b0JBQ3RGLE1BQU0sSUFBSSxLQUFLLENBQUMsc0NBQXNDLENBQUMsQ0FBQztpQkFDekQ7YUFDRjtZQUNELE1BQU0sS0FBSyxDQUFDO1NBQ2I7S0FDRixRQUFRLElBQUksRUFBRSxDQUFDLG9DQUFvQztBQUN0RCxDQUFDO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILEtBQUssVUFBVSxrQkFBa0IsQ0FBQyxZQUFvQixFQUFFLE1BQWUsRUFBRSxPQUF5QixFQUFFLGVBQXdCO0lBQzFILDBFQUEwRTtJQUMxRSwrRUFBK0U7SUFDL0UsOEVBQThFO0lBQzlFLG9GQUFvRjtJQUNwRixJQUFJLFVBQVUsR0FBRyxPQUFPLEVBQUUsVUFBVSxJQUFJLFNBQVMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDO0lBQzVFLE1BQU0sS0FBSyxHQUFHLE9BQU8sRUFBRSxZQUFZLEVBQUUsSUFBSSxJQUFJLFNBQVMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQztJQUN4RixHQUFHO1FBQ0QsSUFBSTtZQUNGLE1BQU0sY0FBYyxHQUFHLElBQUksR0FBRyxDQUFDLGNBQWMsQ0FBQyxFQUFFLFVBQVUsRUFBRSxZQUFZLEVBQUUsTUFBTSxFQUFFLEdBQUcsT0FBTyxFQUFFLENBQUMsQ0FBQztZQUNoRyxJQUFJLENBQUMsZUFBZSxFQUFFO2dCQUNwQixNQUFNLGNBQWMsQ0FBQyxxQkFBcUIsQ0FBQyxFQUFFLFlBQVksRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7YUFDeEU7aUJBQU07Z0JBQ0wsTUFBTSxjQUFjLENBQUMsa0JBQWtCLENBQUMsRUFBRSxZQUFZLEVBQUUsZUFBZSxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQzthQUN0RjtZQUNELE9BQU87U0FFUjtRQUFDLE9BQU8sS0FBSyxFQUFFO1lBQ2QsSUFBSSxLQUFLLENBQUMsSUFBSSxLQUFLLDJCQUEyQixFQUFFO2dCQUM5QyxJQUFJLFVBQVUsR0FBRyxDQUFDLEVBQUU7b0JBQ2xCLFVBQVUsRUFBRSxDQUFDO29CQUNiLE1BQU0sSUFBSSxPQUFPLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxDQUFDLENBQUM7b0JBQ3pELFNBQVM7aUJBQ1Y7cUJBQU07b0JBQ0wsc0ZBQXNGO29CQUN0RixNQUFNLElBQUksS0FBSyxDQUFDLHNDQUFzQyxDQUFDLENBQUM7aUJBQ3pEO2FBQ0Y7WUFDRCxNQUFNLEtBQUssQ0FBQztTQUNiO0tBQ0YsUUFBUSxJQUFJLEVBQUUsQ0FBQyxvQ0FBb0M7QUFDdEQsQ0FBQztBQUVNLEtBQUssVUFBVSxPQUFPLENBQUMsS0FBa0QsRUFBRSxPQUEwQjtJQUMxRyxJQUFJO1FBQ0YsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsR0FBRyxLQUFLLEVBQUUsV0FBVyxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUMsQ0FBQztRQUU5RCx1QkFBdUI7UUFDdkIsTUFBTSxZQUFZLEdBQUcsS0FBSyxDQUFDLGtCQUFrQixDQUFDLFlBQVksQ0FBQztRQUUzRCxxQ0FBcUM7UUFDckMsTUFBTSxjQUFjLEdBQUcsS0FBSyxDQUFDLGtCQUFrQixDQUFDLGNBQWMsQ0FBQztRQUUvRCxpQ0FBaUM7UUFDakMsTUFBTSxZQUFZLEdBQUcsaUJBQWlCLENBQUMsS0FBSyxDQUFDLGtCQUFrQixDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBRTFFLElBQUksS0FBSyxDQUFDLFdBQVcsS0FBSyxRQUFRLElBQUksS0FBSyxDQUFDLFdBQVcsS0FBSyxRQUFRLEVBQUU7WUFDcEUsOEJBQThCO1lBQzlCLE1BQU0sa0JBQWtCLENBQUMsWUFBWSxFQUFFLGNBQWMsRUFBRSxZQUFZLENBQUMsQ0FBQztZQUNyRSxNQUFNLGtCQUFrQixDQUFDLFlBQVksRUFBRSxjQUFjLEVBQUUsWUFBWSxFQUFFLFFBQVEsQ0FBQyxLQUFLLENBQUMsa0JBQWtCLENBQUMsZUFBZSxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUM7WUFFN0gsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsRUFBRTtnQkFDbEMscUVBQXFFO2dCQUNyRSwyRkFBMkY7Z0JBQzNGLDZFQUE2RTtnQkFDN0UsNEVBQTRFO2dCQUM1RSxNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQztnQkFDdEMsTUFBTSxrQkFBa0IsQ0FBQyxlQUFlLE9BQU8sQ0FBQyxZQUFZLEVBQUUsRUFBRSxNQUFNLEVBQUUsWUFBWSxDQUFDLENBQUM7Z0JBQ3RGLDBGQUEwRjtnQkFDMUYseUZBQXlGO2dCQUN6RixpQkFBaUI7Z0JBQ2pCLE1BQU0sa0JBQWtCLENBQUMsZUFBZSxPQUFPLENBQUMsWUFBWSxFQUFFLEVBQUUsTUFBTSxFQUFFLFlBQVksRUFBRSxDQUFDLENBQUMsQ0FBQzthQUMxRjtTQUNGO1FBRUQsTUFBTSxPQUFPLENBQUMsU0FBUyxFQUFFLElBQUksRUFBRSxZQUFZLENBQUMsQ0FBQztLQUM5QztJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUVmLE1BQU0sT0FBTyxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxrQkFBa0IsQ0FBQyxZQUFZLENBQUMsQ0FBQztLQUMzRTtJQUVELFNBQVMsT0FBTyxDQUFDLGNBQXNCLEVBQUUsTUFBYyxFQUFFLGtCQUEwQjtRQUNqRixNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDO1lBQ2xDLE1BQU0sRUFBRSxjQUFjO1lBQ3RCLE1BQU0sRUFBRSxNQUFNO1lBQ2Qsa0JBQWtCLEVBQUUsa0JBQWtCO1lBQ3RDLE9BQU8sRUFBRSxLQUFLLENBQUMsT0FBTztZQUN0QixTQUFTLEVBQUUsS0FBSyxDQUFDLFNBQVM7WUFDMUIsaUJBQWlCLEVBQUUsS0FBSyxDQUFDLGlCQUFpQjtZQUMxQyxJQUFJLEVBQUU7Z0JBQ0osbUZBQW1GO2dCQUNuRixZQUFZLEVBQUUsS0FBSyxDQUFDLGtCQUFrQixDQUFDLFlBQVk7YUFDcEQ7U0FDRixDQUFDLENBQUM7UUFFSCxPQUFPLENBQUMsR0FBRyxDQUFDLFlBQVksRUFBRSxZQUFZLENBQUMsQ0FBQztRQUV4QyxpRUFBaUU7UUFDakUsTUFBTSxTQUFTLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDLENBQUM7UUFDMUQsTUFBTSxjQUFjLEdBQUc7WUFDckIsUUFBUSxFQUFFLFNBQVMsQ0FBQyxRQUFRO1lBQzVCLElBQUksRUFBRSxTQUFTLENBQUMsSUFBSTtZQUNwQixNQUFNLEVBQUUsS0FBSztZQUNiLE9BQU8sRUFBRSxFQUFFLGNBQWMsRUFBRSxFQUFFLEVBQUUsZ0JBQWdCLEVBQUUsWUFBWSxDQUFDLE1BQU0sRUFBRTtTQUN2RSxDQUFDO1FBRUYsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRTtZQUNyQyxJQUFJO2dCQUNGLGlFQUFpRTtnQkFDakUsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxjQUFjLEVBQUUsT0FBTyxDQUFDLENBQUM7Z0JBQ2xFLE9BQU8sQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxDQUFDO2dCQUM1QixPQUFPLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxDQUFDO2dCQUM1QixPQUFPLENBQUMsR0FBRyxFQUFFLENBQUM7YUFDZjtZQUFDLE9BQU8sQ0FBQyxFQUFFO2dCQUNWLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQzthQUNYO1FBQ0gsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQsU0FBUyxpQkFBaUIsQ0FBQyxVQUFlO1FBQ3hDLE1BQU0sWUFBWSxHQUFvQixFQUFFLENBQUM7UUFDekMsSUFBSSxVQUFVLEVBQUU7WUFDZCxJQUFJLFVBQVUsQ0FBQyxVQUFVLEVBQUU7Z0JBQ3pCLFlBQVksQ0FBQyxVQUFVLEdBQUcsUUFBUSxDQUFDLFVBQVUsQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDLENBQUM7YUFDL0Q7WUFDRCxJQUFJLFVBQVUsQ0FBQyxJQUFJLEVBQUU7Z0JBQ25CLFlBQVksQ0FBQyxZQUFZLEdBQUc7b0JBQzFCLElBQUksRUFBRSxRQUFRLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRSxFQUFFLENBQUM7aUJBQ3BDLENBQUM7YUFDSDtTQUNGO1FBQ0QsT0FBTyxZQUFZLENBQUM7SUFDdEIsQ0FBQztBQUNILENBQUM7QUEzRkQsMEJBMkZDIiwic291cmNlc0NvbnRlbnQiOlsiLyogZXNsaW50LWRpc2FibGUgbm8tY29uc29sZSAqL1xuXG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgaW1wb3J0L25vLWV4dHJhbmVvdXMtZGVwZW5kZW5jaWVzXG5pbXBvcnQgKiBhcyBBV1MgZnJvbSAnYXdzLXNkayc7XG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgaW1wb3J0L25vLWV4dHJhbmVvdXMtZGVwZW5kZW5jaWVzXG5pbXBvcnQgdHlwZSB7IFJldHJ5RGVsYXlPcHRpb25zIH0gZnJvbSAnYXdzLXNkay9saWIvY29uZmlnLWJhc2UnO1xuXG5pbnRlcmZhY2UgU2RrUmV0cnlPcHRpb25zIHtcbiAgbWF4UmV0cmllcz86IG51bWJlcjtcbiAgcmV0cnlPcHRpb25zPzogUmV0cnlEZWxheU9wdGlvbnM7XG59XG5cbi8qKlxuICogQ3JlYXRlcyBhIGxvZyBncm91cCBhbmQgZG9lc24ndCB0aHJvdyBpZiBpdCBleGlzdHMuXG4gKlxuICogQHBhcmFtIGxvZ0dyb3VwTmFtZSB0aGUgbmFtZSBvZiB0aGUgbG9nIGdyb3VwIHRvIGNyZWF0ZS5cbiAqIEBwYXJhbSByZWdpb24gdG8gY3JlYXRlIHRoZSBsb2cgZ3JvdXAgaW5cbiAqIEBwYXJhbSBvcHRpb25zIENsb3VkV2F0Y2ggQVBJIFNESyBvcHRpb25zLlxuICovXG5hc3luYyBmdW5jdGlvbiBjcmVhdGVMb2dHcm91cFNhZmUobG9nR3JvdXBOYW1lOiBzdHJpbmcsIHJlZ2lvbj86IHN0cmluZywgb3B0aW9ucz86IFNka1JldHJ5T3B0aW9ucykge1xuICAvLyBJZiB3ZSBzZXQgdGhlIGxvZyByZXRlbnRpb24gZm9yIGEgbGFtYmRhLCB0aGVuIGR1ZSB0byB0aGUgYXN5bmMgbmF0dXJlIG9mXG4gIC8vIExhbWJkYSBsb2dnaW5nIHRoZXJlIGNvdWxkIGJlIGEgcmFjZSBjb25kaXRpb24gd2hlbiB0aGUgc2FtZSBsb2cgZ3JvdXAgaXNcbiAgLy8gYWxyZWFkeSBiZWluZyBjcmVhdGVkIGJ5IHRoZSBsYW1iZGEgZXhlY3V0aW9uLiBUaGlzIGNhbiBzb21ldGltZSByZXN1bHQgaW5cbiAgLy8gYW4gZXJyb3IgXCJPcGVyYXRpb25BYm9ydGVkRXhjZXB0aW9uOiBBIGNvbmZsaWN0aW5nIG9wZXJhdGlvbiBpcyBjdXJyZW50bHlcbiAgLy8gaW4gcHJvZ3Jlc3MuLi5QbGVhc2UgdHJ5IGFnYWluLlwiXG4gIC8vIFRvIGF2b2lkIGFuIGVycm9yLCB3ZSBkbyBhcyByZXF1ZXN0ZWQgYW5kIHRyeSBhZ2Fpbi5cbiAgbGV0IHJldHJ5Q291bnQgPSBvcHRpb25zPy5tYXhSZXRyaWVzID09IHVuZGVmaW5lZCA/IDEwIDogb3B0aW9ucy5tYXhSZXRyaWVzO1xuICBjb25zdCBkZWxheSA9IG9wdGlvbnM/LnJldHJ5T3B0aW9ucz8uYmFzZSA9PSB1bmRlZmluZWQgPyAxMCA6IG9wdGlvbnMucmV0cnlPcHRpb25zLmJhc2U7XG4gIGRvIHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgY2xvdWR3YXRjaGxvZ3MgPSBuZXcgQVdTLkNsb3VkV2F0Y2hMb2dzKHsgYXBpVmVyc2lvbjogJzIwMTQtMDMtMjgnLCByZWdpb24sIC4uLm9wdGlvbnMgfSk7XG4gICAgICBhd2FpdCBjbG91ZHdhdGNobG9ncy5jcmVhdGVMb2dHcm91cCh7IGxvZ0dyb3VwTmFtZSB9KS5wcm9taXNlKCk7XG4gICAgICByZXR1cm47XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGlmIChlcnJvci5jb2RlID09PSAnUmVzb3VyY2VBbHJlYWR5RXhpc3RzRXhjZXB0aW9uJykge1xuICAgICAgICAvLyBUaGUgbG9nIGdyb3VwIGlzIGFscmVhZHkgY3JlYXRlZCBieSB0aGUgbGFtYmRhIGV4ZWN1dGlvblxuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBpZiAoZXJyb3IuY29kZSA9PT0gJ09wZXJhdGlvbkFib3J0ZWRFeGNlcHRpb24nKSB7XG4gICAgICAgIGlmIChyZXRyeUNvdW50ID4gMCkge1xuICAgICAgICAgIHJldHJ5Q291bnQtLTtcbiAgICAgICAgICBhd2FpdCBuZXcgUHJvbWlzZShyZXNvbHZlID0+IHNldFRpbWVvdXQocmVzb2x2ZSwgZGVsYXkpKTtcbiAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAvLyBUaGUgbG9nIGdyb3VwIGlzIHN0aWxsIGJlaW5nIGNyZWF0ZWQgYnkgYW5vdGhlciBleGVjdXRpb24gYnV0IHdlIGFyZSBvdXQgb2YgcmV0cmllc1xuICAgICAgICAgIHRocm93IG5ldyBFcnJvcignT3V0IG9mIGF0dGVtcHRzIHRvIGNyZWF0ZSBhIGxvZ0dyb3VwJyk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHRocm93IGVycm9yO1xuICAgIH1cbiAgfSB3aGlsZSAodHJ1ZSk7IC8vIGV4aXQgaGFwcGVucyBvbiByZXRyeSBjb3VudCBjaGVja1xufVxuXG4vKipcbiAqIFB1dHMgb3IgZGVsZXRlcyBhIHJldGVudGlvbiBwb2xpY3kgb24gYSBsb2cgZ3JvdXAuXG4gKlxuICogQHBhcmFtIGxvZ0dyb3VwTmFtZSB0aGUgbmFtZSBvZiB0aGUgbG9nIGdyb3VwIHRvIGNyZWF0ZVxuICogQHBhcmFtIHJlZ2lvbiB0aGUgcmVnaW9uIG9mIHRoZSBsb2cgZ3JvdXBcbiAqIEBwYXJhbSBvcHRpb25zIENsb3VkV2F0Y2ggQVBJIFNESyBvcHRpb25zLlxuICogQHBhcmFtIHJldGVudGlvbkluRGF5cyB0aGUgbnVtYmVyIG9mIGRheXMgdG8gcmV0YWluIHRoZSBsb2cgZXZlbnRzIGluIHRoZSBzcGVjaWZpZWQgbG9nIGdyb3VwLlxuICovXG5hc3luYyBmdW5jdGlvbiBzZXRSZXRlbnRpb25Qb2xpY3kobG9nR3JvdXBOYW1lOiBzdHJpbmcsIHJlZ2lvbj86IHN0cmluZywgb3B0aW9ucz86IFNka1JldHJ5T3B0aW9ucywgcmV0ZW50aW9uSW5EYXlzPzogbnVtYmVyKSB7XG4gIC8vIFRoZSBzYW1lIGFzIGluIGNyZWF0ZUxvZ0dyb3VwU2FmZSgpLCBoZXJlIHdlIGNvdWxkIGVuZCB1cCB3aXRoIHRoZSByYWNlXG4gIC8vIGNvbmRpdGlvbiB3aGVyZSBhIGxvZyBncm91cCBpcyBlaXRoZXIgYWxyZWFkeSBiZWluZyBjcmVhdGVkIG9yIGl0cyByZXRlbnRpb25cbiAgLy8gcG9saWN5IGlzIGJlaW5nIHVwZGF0ZWQuIFRoaXMgd291bGQgcmVzdWx0IGluIGFuIE9wZXJhdGlvbkFib3J0ZWRFeGNlcHRpb24sXG4gIC8vIHdoaWNoIHdlIHdpbGwgdHJ5IHRvIGNhdGNoIGFuZCByZXRyeSB0aGUgY29tbWFuZCBhIG51bWJlciBvZiB0aW1lcyBiZWZvcmUgZmFpbGluZ1xuICBsZXQgcmV0cnlDb3VudCA9IG9wdGlvbnM/Lm1heFJldHJpZXMgPT0gdW5kZWZpbmVkID8gMTAgOiBvcHRpb25zLm1heFJldHJpZXM7XG4gIGNvbnN0IGRlbGF5ID0gb3B0aW9ucz8ucmV0cnlPcHRpb25zPy5iYXNlID09IHVuZGVmaW5lZCA/IDEwIDogb3B0aW9ucy5yZXRyeU9wdGlvbnMuYmFzZTtcbiAgZG8ge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBjbG91ZHdhdGNobG9ncyA9IG5ldyBBV1MuQ2xvdWRXYXRjaExvZ3MoeyBhcGlWZXJzaW9uOiAnMjAxNC0wMy0yOCcsIHJlZ2lvbiwgLi4ub3B0aW9ucyB9KTtcbiAgICAgIGlmICghcmV0ZW50aW9uSW5EYXlzKSB7XG4gICAgICAgIGF3YWl0IGNsb3Vkd2F0Y2hsb2dzLmRlbGV0ZVJldGVudGlvblBvbGljeSh7IGxvZ0dyb3VwTmFtZSB9KS5wcm9taXNlKCk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBhd2FpdCBjbG91ZHdhdGNobG9ncy5wdXRSZXRlbnRpb25Qb2xpY3koeyBsb2dHcm91cE5hbWUsIHJldGVudGlvbkluRGF5cyB9KS5wcm9taXNlKCk7XG4gICAgICB9XG4gICAgICByZXR1cm47XG5cbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgaWYgKGVycm9yLmNvZGUgPT09ICdPcGVyYXRpb25BYm9ydGVkRXhjZXB0aW9uJykge1xuICAgICAgICBpZiAocmV0cnlDb3VudCA+IDApIHtcbiAgICAgICAgICByZXRyeUNvdW50LS07XG4gICAgICAgICAgYXdhaXQgbmV3IFByb21pc2UocmVzb2x2ZSA9PiBzZXRUaW1lb3V0KHJlc29sdmUsIGRlbGF5KSk7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgLy8gVGhlIGxvZyBncm91cCBpcyBzdGlsbCBiZWluZyBjcmVhdGVkIGJ5IGFub3RoZXIgZXhlY3V0aW9uIGJ1dCB3ZSBhcmUgb3V0IG9mIHJldHJpZXNcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ091dCBvZiBhdHRlbXB0cyB0byBjcmVhdGUgYSBsb2dHcm91cCcpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICB0aHJvdyBlcnJvcjtcbiAgICB9XG4gIH0gd2hpbGUgKHRydWUpOyAvLyBleGl0IGhhcHBlbnMgb24gcmV0cnkgY291bnQgY2hlY2tcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGhhbmRsZXIoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQsIGNvbnRleHQ6IEFXU0xhbWJkYS5Db250ZXh0KSB7XG4gIHRyeSB7XG4gICAgY29uc29sZS5sb2coSlNPTi5zdHJpbmdpZnkoeyAuLi5ldmVudCwgUmVzcG9uc2VVUkw6ICcuLi4nIH0pKTtcblxuICAgIC8vIFRoZSB0YXJnZXQgbG9nIGdyb3VwXG4gICAgY29uc3QgbG9nR3JvdXBOYW1lID0gZXZlbnQuUmVzb3VyY2VQcm9wZXJ0aWVzLkxvZ0dyb3VwTmFtZTtcblxuICAgIC8vIFRoZSByZWdpb24gb2YgdGhlIHRhcmdldCBsb2cgZ3JvdXBcbiAgICBjb25zdCBsb2dHcm91cFJlZ2lvbiA9IGV2ZW50LlJlc291cmNlUHJvcGVydGllcy5Mb2dHcm91cFJlZ2lvbjtcblxuICAgIC8vIFBhcnNlIHRvIEFXUyBTREsgcmV0cnkgb3B0aW9uc1xuICAgIGNvbnN0IHJldHJ5T3B0aW9ucyA9IHBhcnNlUmV0cnlPcHRpb25zKGV2ZW50LlJlc291cmNlUHJvcGVydGllcy5TZGtSZXRyeSk7XG5cbiAgICBpZiAoZXZlbnQuUmVxdWVzdFR5cGUgPT09ICdDcmVhdGUnIHx8IGV2ZW50LlJlcXVlc3RUeXBlID09PSAnVXBkYXRlJykge1xuICAgICAgLy8gQWN0IG9uIHRoZSB0YXJnZXQgbG9nIGdyb3VwXG4gICAgICBhd2FpdCBjcmVhdGVMb2dHcm91cFNhZmUobG9nR3JvdXBOYW1lLCBsb2dHcm91cFJlZ2lvbiwgcmV0cnlPcHRpb25zKTtcbiAgICAgIGF3YWl0IHNldFJldGVudGlvblBvbGljeShsb2dHcm91cE5hbWUsIGxvZ0dyb3VwUmVnaW9uLCByZXRyeU9wdGlvbnMsIHBhcnNlSW50KGV2ZW50LlJlc291cmNlUHJvcGVydGllcy5SZXRlbnRpb25JbkRheXMsIDEwKSk7XG5cbiAgICAgIGlmIChldmVudC5SZXF1ZXN0VHlwZSA9PT0gJ0NyZWF0ZScpIHtcbiAgICAgICAgLy8gU2V0IGEgcmV0ZW50aW9uIHBvbGljeSBvZiAxIGRheSBvbiB0aGUgbG9ncyBvZiB0aGlzIHZlcnkgZnVuY3Rpb24uXG4gICAgICAgIC8vIER1ZSB0byB0aGUgYXN5bmMgbmF0dXJlIG9mIHRoZSBsb2cgZ3JvdXAgY3JlYXRpb24sIHRoZSBsb2cgZ3JvdXAgZm9yIHRoaXMgZnVuY3Rpb24gbWlnaHRcbiAgICAgICAgLy8gc3RpbGwgYmUgbm90IGNyZWF0ZWQgeWV0IGF0IHRoaXMgcG9pbnQuIFRoZXJlZm9yZSB3ZSBhdHRlbXB0IHRvIGNyZWF0ZSBpdC5cbiAgICAgICAgLy8gSW4gY2FzZSBpdCBpcyBiZWluZyBjcmVhdGVkLCBjcmVhdGVMb2dHcm91cFNhZmUgd2lsbCBoYW5kbGUgdGhlIGNvbmZsaWN0LlxuICAgICAgICBjb25zdCByZWdpb24gPSBwcm9jZXNzLmVudi5BV1NfUkVHSU9OO1xuICAgICAgICBhd2FpdCBjcmVhdGVMb2dHcm91cFNhZmUoYC9hd3MvbGFtYmRhLyR7Y29udGV4dC5mdW5jdGlvbk5hbWV9YCwgcmVnaW9uLCByZXRyeU9wdGlvbnMpO1xuICAgICAgICAvLyBJZiBjcmVhdGVMb2dHcm91cFNhZmUgZmFpbHMsIHRoZSBsb2cgZ3JvdXAgaXMgbm90IGNyZWF0ZWQgZXZlbiBhZnRlciBtdWx0aXBsZSBhdHRlbXB0cy5cbiAgICAgICAgLy8gSW4gdGhpcyBjYXNlIHdlIGhhdmUgbm90aGluZyB0byBzZXQgdGhlIHJldGVudGlvbiBwb2xpY3kgb24gYnV0IGFuIGV4Y2VwdGlvbiB3aWxsIHNraXBcbiAgICAgICAgLy8gdGhlIG5leHQgbGluZS5cbiAgICAgICAgYXdhaXQgc2V0UmV0ZW50aW9uUG9saWN5KGAvYXdzL2xhbWJkYS8ke2NvbnRleHQuZnVuY3Rpb25OYW1lfWAsIHJlZ2lvbiwgcmV0cnlPcHRpb25zLCAxKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBhd2FpdCByZXNwb25kKCdTVUNDRVNTJywgJ09LJywgbG9nR3JvdXBOYW1lKTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGNvbnNvbGUubG9nKGUpO1xuXG4gICAgYXdhaXQgcmVzcG9uZCgnRkFJTEVEJywgZS5tZXNzYWdlLCBldmVudC5SZXNvdXJjZVByb3BlcnRpZXMuTG9nR3JvdXBOYW1lKTtcbiAgfVxuXG4gIGZ1bmN0aW9uIHJlc3BvbmQocmVzcG9uc2VTdGF0dXM6IHN0cmluZywgcmVhc29uOiBzdHJpbmcsIHBoeXNpY2FsUmVzb3VyY2VJZDogc3RyaW5nKSB7XG4gICAgY29uc3QgcmVzcG9uc2VCb2R5ID0gSlNPTi5zdHJpbmdpZnkoe1xuICAgICAgU3RhdHVzOiByZXNwb25zZVN0YXR1cyxcbiAgICAgIFJlYXNvbjogcmVhc29uLFxuICAgICAgUGh5c2ljYWxSZXNvdXJjZUlkOiBwaHlzaWNhbFJlc291cmNlSWQsXG4gICAgICBTdGFja0lkOiBldmVudC5TdGFja0lkLFxuICAgICAgUmVxdWVzdElkOiBldmVudC5SZXF1ZXN0SWQsXG4gICAgICBMb2dpY2FsUmVzb3VyY2VJZDogZXZlbnQuTG9naWNhbFJlc291cmNlSWQsXG4gICAgICBEYXRhOiB7XG4gICAgICAgIC8vIEFkZCBsb2cgZ3JvdXAgbmFtZSBhcyBwYXJ0IG9mIHRoZSByZXNwb25zZSBzbyB0aGF0IGl0J3MgYXZhaWxhYmxlIHZpYSBGbjo6R2V0QXR0XG4gICAgICAgIExvZ0dyb3VwTmFtZTogZXZlbnQuUmVzb3VyY2VQcm9wZXJ0aWVzLkxvZ0dyb3VwTmFtZSxcbiAgICAgIH0sXG4gICAgfSk7XG5cbiAgICBjb25zb2xlLmxvZygnUmVzcG9uZGluZycsIHJlc3BvbnNlQm9keSk7XG5cbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLXJlcXVpcmUtaW1wb3J0c1xuICAgIGNvbnN0IHBhcnNlZFVybCA9IHJlcXVpcmUoJ3VybCcpLnBhcnNlKGV2ZW50LlJlc3BvbnNlVVJMKTtcbiAgICBjb25zdCByZXF1ZXN0T3B0aW9ucyA9IHtcbiAgICAgIGhvc3RuYW1lOiBwYXJzZWRVcmwuaG9zdG5hbWUsXG4gICAgICBwYXRoOiBwYXJzZWRVcmwucGF0aCxcbiAgICAgIG1ldGhvZDogJ1BVVCcsXG4gICAgICBoZWFkZXJzOiB7ICdjb250ZW50LXR5cGUnOiAnJywgJ2NvbnRlbnQtbGVuZ3RoJzogcmVzcG9uc2VCb2R5Lmxlbmd0aCB9LFxuICAgIH07XG5cbiAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgdHJ5IHtcbiAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1yZXF1aXJlLWltcG9ydHNcbiAgICAgICAgY29uc3QgcmVxdWVzdCA9IHJlcXVpcmUoJ2h0dHBzJykucmVxdWVzdChyZXF1ZXN0T3B0aW9ucywgcmVzb2x2ZSk7XG4gICAgICAgIHJlcXVlc3Qub24oJ2Vycm9yJywgcmVqZWN0KTtcbiAgICAgICAgcmVxdWVzdC53cml0ZShyZXNwb25zZUJvZHkpO1xuICAgICAgICByZXF1ZXN0LmVuZCgpO1xuICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICByZWplY3QoZSk7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICBmdW5jdGlvbiBwYXJzZVJldHJ5T3B0aW9ucyhyYXdPcHRpb25zOiBhbnkpOiBTZGtSZXRyeU9wdGlvbnMge1xuICAgIGNvbnN0IHJldHJ5T3B0aW9uczogU2RrUmV0cnlPcHRpb25zID0ge307XG4gICAgaWYgKHJhd09wdGlvbnMpIHtcbiAgICAgIGlmIChyYXdPcHRpb25zLm1heFJldHJpZXMpIHtcbiAgICAgICAgcmV0cnlPcHRpb25zLm1heFJldHJpZXMgPSBwYXJzZUludChyYXdPcHRpb25zLm1heFJldHJpZXMsIDEwKTtcbiAgICAgIH1cbiAgICAgIGlmIChyYXdPcHRpb25zLmJhc2UpIHtcbiAgICAgICAgcmV0cnlPcHRpb25zLnJldHJ5T3B0aW9ucyA9IHtcbiAgICAgICAgICBiYXNlOiBwYXJzZUludChyYXdPcHRpb25zLmJhc2UsIDEwKSxcbiAgICAgICAgfTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHJldHJ5T3B0aW9ucztcbiAgfVxufVxuIl19 \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/index.ts b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/index.ts new file mode 100644 index 0000000000000..b78be3cb5c1ec --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/asset.c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/index.ts @@ -0,0 +1,186 @@ +/* eslint-disable no-console */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import * as AWS from 'aws-sdk'; +// eslint-disable-next-line import/no-extraneous-dependencies +import type { RetryDelayOptions } from 'aws-sdk/lib/config-base'; + +interface SdkRetryOptions { + maxRetries?: number; + retryOptions?: RetryDelayOptions; +} + +/** + * Creates a log group and doesn't throw if it exists. + * + * @param logGroupName the name of the log group to create. + * @param region to create the log group in + * @param options CloudWatch API SDK options. + */ +async function createLogGroupSafe(logGroupName: string, region?: string, options?: SdkRetryOptions) { + // If we set the log retention for a lambda, then due to the async nature of + // Lambda logging there could be a race condition when the same log group is + // already being created by the lambda execution. This can sometime result in + // an error "OperationAbortedException: A conflicting operation is currently + // in progress...Please try again." + // To avoid an error, we do as requested and try again. + let retryCount = options?.maxRetries == undefined ? 10 : options.maxRetries; + const delay = options?.retryOptions?.base == undefined ? 10 : options.retryOptions.base; + do { + try { + const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28', region, ...options }); + await cloudwatchlogs.createLogGroup({ logGroupName }).promise(); + return; + } catch (error) { + if (error.code === 'ResourceAlreadyExistsException') { + // The log group is already created by the lambda execution + return; + } + if (error.code === 'OperationAbortedException') { + if (retryCount > 0) { + retryCount--; + await new Promise(resolve => setTimeout(resolve, delay)); + continue; + } else { + // The log group is still being created by another execution but we are out of retries + throw new Error('Out of attempts to create a logGroup'); + } + } + throw error; + } + } while (true); // exit happens on retry count check +} + +/** + * Puts or deletes a retention policy on a log group. + * + * @param logGroupName the name of the log group to create + * @param region the region of the log group + * @param options CloudWatch API SDK options. + * @param retentionInDays the number of days to retain the log events in the specified log group. + */ +async function setRetentionPolicy(logGroupName: string, region?: string, options?: SdkRetryOptions, retentionInDays?: number) { + // The same as in createLogGroupSafe(), here we could end up with the race + // condition where a log group is either already being created or its retention + // policy is being updated. This would result in an OperationAbortedException, + // which we will try to catch and retry the command a number of times before failing + let retryCount = options?.maxRetries == undefined ? 10 : options.maxRetries; + const delay = options?.retryOptions?.base == undefined ? 10 : options.retryOptions.base; + do { + try { + const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28', region, ...options }); + if (!retentionInDays) { + await cloudwatchlogs.deleteRetentionPolicy({ logGroupName }).promise(); + } else { + await cloudwatchlogs.putRetentionPolicy({ logGroupName, retentionInDays }).promise(); + } + return; + + } catch (error) { + if (error.code === 'OperationAbortedException') { + if (retryCount > 0) { + retryCount--; + await new Promise(resolve => setTimeout(resolve, delay)); + continue; + } else { + // The log group is still being created by another execution but we are out of retries + throw new Error('Out of attempts to create a logGroup'); + } + } + throw error; + } + } while (true); // exit happens on retry count check +} + +export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent, context: AWSLambda.Context) { + try { + console.log(JSON.stringify({ ...event, ResponseURL: '...' })); + + // The target log group + const logGroupName = event.ResourceProperties.LogGroupName; + + // The region of the target log group + const logGroupRegion = event.ResourceProperties.LogGroupRegion; + + // Parse to AWS SDK retry options + const retryOptions = parseRetryOptions(event.ResourceProperties.SdkRetry); + + if (event.RequestType === 'Create' || event.RequestType === 'Update') { + // Act on the target log group + await createLogGroupSafe(logGroupName, logGroupRegion, retryOptions); + await setRetentionPolicy(logGroupName, logGroupRegion, retryOptions, parseInt(event.ResourceProperties.RetentionInDays, 10)); + + if (event.RequestType === 'Create') { + // Set a retention policy of 1 day on the logs of this very function. + // Due to the async nature of the log group creation, the log group for this function might + // still be not created yet at this point. Therefore we attempt to create it. + // In case it is being created, createLogGroupSafe will handle the conflict. + const region = process.env.AWS_REGION; + await createLogGroupSafe(`/aws/lambda/${context.functionName}`, region, retryOptions); + // If createLogGroupSafe fails, the log group is not created even after multiple attempts. + // In this case we have nothing to set the retention policy on but an exception will skip + // the next line. + await setRetentionPolicy(`/aws/lambda/${context.functionName}`, region, retryOptions, 1); + } + } + + await respond('SUCCESS', 'OK', logGroupName); + } catch (e) { + console.log(e); + + await respond('FAILED', e.message, event.ResourceProperties.LogGroupName); + } + + function respond(responseStatus: string, reason: string, physicalResourceId: string) { + const responseBody = JSON.stringify({ + Status: responseStatus, + Reason: reason, + PhysicalResourceId: physicalResourceId, + StackId: event.StackId, + RequestId: event.RequestId, + LogicalResourceId: event.LogicalResourceId, + Data: { + // Add log group name as part of the response so that it's available via Fn::GetAtt + LogGroupName: event.ResourceProperties.LogGroupName, + }, + }); + + console.log('Responding', responseBody); + + // eslint-disable-next-line @typescript-eslint/no-require-imports + const parsedUrl = require('url').parse(event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { 'content-type': '', 'content-length': responseBody.length }, + }; + + return new Promise((resolve, reject) => { + try { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const request = require('https').request(requestOptions, resolve); + request.on('error', reject); + request.write(responseBody); + request.end(); + } catch (e) { + reject(e); + } + }); + } + + function parseRetryOptions(rawOptions: any): SdkRetryOptions { + const retryOptions: SdkRetryOptions = {}; + if (rawOptions) { + if (rawOptions.maxRetries) { + retryOptions.maxRetries = parseInt(rawOptions.maxRetries, 10); + } + if (rawOptions.base) { + retryOptions.retryOptions = { + base: parseInt(rawOptions.base, 10), + }; + } + } + return retryOptions; + } +} diff --git a/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..588d7b269d34f --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/integ.json b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/integ.json new file mode 100644 index 0000000000000..2e53939a47b06 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/integ.json @@ -0,0 +1,11 @@ +{ + "version": "20.0.0", + "testCases": { + "Integ/DefaultTest": { + "stacks": [ + "AppSyncIntegLogRetention" + ], + "assertionStack": "IntegDefaultTestDeployAssert4E6713E1" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..f5d5110397c91 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/manifest.json @@ -0,0 +1,184 @@ +{ + "version": "20.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "AppSyncIntegLogRetention": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "AppSyncIntegLogRetention.template.json", + "validateOnSynth": false + }, + "metadata": { + "/AppSyncIntegLogRetention": [ + { + "type": "aws:cdk:asset", + "data": { + "path": "asset.c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a", + "id": "c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a", + "packaging": "zip", + "sourceHash": "c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a", + "s3BucketParameter": "AssetParametersc70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13aS3BucketB4787383", + "s3KeyParameter": "AssetParametersc70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13aS3VersionKey8CF8E820", + "artifactHashParameter": "AssetParametersc70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13aArtifactHashC9560B34" + } + } + ], + "/AppSyncIntegLogRetention/GraphqlApi/ApiLogsRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GraphqlApiApiLogsRoleBB9E6BAD" + } + ], + "/AppSyncIntegLogRetention/GraphqlApi/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GraphqlApi1B6CF24C" + } + ], + "/AppSyncIntegLogRetention/GraphqlApi/Schema": [ + { + "type": "aws:cdk:logicalId", + "data": "GraphqlApiSchema1B71CE4F" + } + ], + "/AppSyncIntegLogRetention/GraphqlApi/DefaultApiKey": [ + { + "type": "aws:cdk:logicalId", + "data": "GraphqlApiDefaultApiKey47EE7128" + } + ], + "/AppSyncIntegLogRetention/GraphqlApi/LogRetention/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GraphqlApiLogRetention082A7017" + } + ], + "/AppSyncIntegLogRetention/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB" + } + ], + "/AppSyncIntegLogRetention/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB" + } + ], + "/AppSyncIntegLogRetention/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A" + } + ], + "/AppSyncIntegLogRetention/AssetParameters/c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/S3Bucket": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetParametersc70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13aS3BucketB4787383" + } + ], + "/AppSyncIntegLogRetention/AssetParameters/c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/S3VersionKey": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetParametersc70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13aS3VersionKey8CF8E820" + } + ], + "/AppSyncIntegLogRetention/AssetParameters/c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/ArtifactHash": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetParametersc70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13aArtifactHashC9560B34" + } + ], + "/AppSyncIntegLogRetention/Exports/Output{\"Fn::GetAtt\":[\"GraphqlApi1B6CF24C\",\"ApiId\"]}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputFnGetAttGraphqlApi1B6CF24CApiIdE34D50AD" + } + ] + }, + "displayName": "AppSyncIntegLogRetention" + }, + "IntegDefaultTestDeployAssert4E6713E1": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "IntegDefaultTestDeployAssert4E6713E1.template.json", + "validateOnSynth": false + }, + "dependencies": [ + "AppSyncIntegLogRetention" + ], + "metadata": { + "/Integ/DefaultTest/DeployAssert": [ + { + "type": "aws:cdk:asset", + "data": { + "path": "asset.0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd.bundle", + "id": "0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd", + "packaging": "zip", + "sourceHash": "0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd", + "s3BucketParameter": "AssetParameters0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbdS3BucketF94385B7", + "s3KeyParameter": "AssetParameters0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbdS3VersionKey66DB0F9E", + "artifactHashParameter": "AssetParameters0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbdArtifactHash2AC894D9" + } + } + ], + "/Integ/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsdescribeLogGroups/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCloudWatchLogsdescribeLogGroups" + } + ], + "/Integ/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsdescribeLogGroups/AssertEqualsCloudWatchLogsdescribeLogGroups/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCloudWatchLogsdescribeLogGroupsAssertEqualsCloudWatchLogsdescribeLogGroupsD2E2CD37" + } + ], + "/Integ/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsdescribeLogGroups/AssertEqualsCloudWatchLogsdescribeLogGroups/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAssertEqualsCloudWatchLogsdescribeLogGroups" + } + ], + "/Integ/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/Integ/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], + "/Integ/DefaultTest/DeployAssert/AssetParameters/0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd/S3Bucket": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetParameters0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbdS3BucketF94385B7" + } + ], + "/Integ/DefaultTest/DeployAssert/AssetParameters/0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd/S3VersionKey": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetParameters0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbdS3VersionKey66DB0F9E" + } + ], + "/Integ/DefaultTest/DeployAssert/AssetParameters/0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd/ArtifactHash": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetParameters0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbdArtifactHash2AC894D9" + } + ] + }, + "displayName": "Integ/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/tree.json b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/tree.json new file mode 100644 index 0000000000000..cd73d74dee554 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/log-retention.integ.snapshot/tree.json @@ -0,0 +1,592 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + }, + "AppSyncIntegLogRetention": { + "id": "AppSyncIntegLogRetention", + "path": "AppSyncIntegLogRetention", + "children": { + "GraphqlApi": { + "id": "GraphqlApi", + "path": "AppSyncIntegLogRetention/GraphqlApi", + "children": { + "ApiLogsRole": { + "id": "ApiLogsRole", + "path": "AppSyncIntegLogRetention/GraphqlApi/ApiLogsRole", + "children": { + "Resource": { + "id": "Resource", + "path": "AppSyncIntegLogRetention/GraphqlApi/ApiLogsRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "appsync.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "AppSyncIntegLogRetention/GraphqlApi/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::GraphQLApi", + "aws:cdk:cloudformation:props": { + "authenticationType": "API_KEY", + "name": "IntegLogRetention", + "logConfig": { + "cloudWatchLogsRoleArn": { + "Fn::GetAtt": [ + "GraphqlApiApiLogsRoleBB9E6BAD", + "Arn" + ] + }, + "fieldLogLevel": "NONE" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-appsync.CfnGraphQLApi", + "version": "0.0.0" + } + }, + "Schema": { + "id": "Schema", + "path": "AppSyncIntegLogRetention/GraphqlApi/Schema", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::GraphQLSchema", + "aws:cdk:cloudformation:props": { + "apiId": { + "Fn::GetAtt": [ + "GraphqlApi1B6CF24C", + "ApiId" + ] + }, + "definition": "type test {\n version: String!\n}\ntype Query {\n getTests: [test]!\n}\ntype Mutation {\n addTest(version: String!): test\n}\n" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-appsync.CfnGraphQLSchema", + "version": "0.0.0" + } + }, + "DefaultApiKey": { + "id": "DefaultApiKey", + "path": "AppSyncIntegLogRetention/GraphqlApi/DefaultApiKey", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppSync::ApiKey", + "aws:cdk:cloudformation:props": { + "apiId": { + "Fn::GetAtt": [ + "GraphqlApi1B6CF24C", + "ApiId" + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-appsync.CfnApiKey", + "version": "0.0.0" + } + }, + "LogGroup": { + "id": "LogGroup", + "path": "AppSyncIntegLogRetention/GraphqlApi/LogGroup", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "LogRetention": { + "id": "LogRetention", + "path": "AppSyncIntegLogRetention/GraphqlApi/LogRetention", + "children": { + "Resource": { + "id": "Resource", + "path": "AppSyncIntegLogRetention/GraphqlApi/LogRetention/Resource", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-logs.LogRetention", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-appsync.GraphqlApi", + "version": "0.0.0" + } + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a": { + "id": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a", + "path": "AppSyncIntegLogRetention/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a", + "children": { + "Code": { + "id": "Code", + "path": "AppSyncIntegLogRetention/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "AppSyncIntegLogRetention/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/Code/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "AppSyncIntegLogRetention/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/Code/AssetBucket", + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3-assets.Asset", + "version": "0.0.0" + } + }, + "ServiceRole": { + "id": "ServiceRole", + "path": "AppSyncIntegLogRetention/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole", + "children": { + "Resource": { + "id": "Resource", + "path": "AppSyncIntegLogRetention/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "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" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "AppSyncIntegLogRetention/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "AppSyncIntegLogRetention/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "logs:DeleteRetentionPolicy", + "logs:PutRetentionPolicy" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "policyName": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "roles": [ + { + "Ref": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "AppSyncIntegLogRetention/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/Resource", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + }, + "AssetParameters": { + "id": "AssetParameters", + "path": "AppSyncIntegLogRetention/AssetParameters", + "children": { + "c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a": { + "id": "c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a", + "path": "AppSyncIntegLogRetention/AssetParameters/c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a", + "children": { + "S3Bucket": { + "id": "S3Bucket", + "path": "AppSyncIntegLogRetention/AssetParameters/c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/S3Bucket", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "S3VersionKey": { + "id": "S3VersionKey", + "path": "AppSyncIntegLogRetention/AssetParameters/c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/S3VersionKey", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "ArtifactHash": { + "id": "ArtifactHash", + "path": "AppSyncIntegLogRetention/AssetParameters/c70c1d1695771af4771fd98971e16bb3e6443c62c2994b002b2c3d76e707b13a/ArtifactHash", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + }, + "Exports": { + "id": "Exports", + "path": "AppSyncIntegLogRetention/Exports", + "children": { + "Output{\"Fn::GetAtt\":[\"GraphqlApi1B6CF24C\",\"ApiId\"]}": { + "id": "Output{\"Fn::GetAtt\":[\"GraphqlApi1B6CF24C\",\"ApiId\"]}", + "path": "AppSyncIntegLogRetention/Exports/Output{\"Fn::GetAtt\":[\"GraphqlApi1B6CF24C\",\"ApiId\"]}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "Integ": { + "id": "Integ", + "path": "Integ", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "Integ/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "Integ/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "Integ/DefaultTest/DeployAssert", + "children": { + "AwsApiCallCloudWatchLogsdescribeLogGroups": { + "id": "AwsApiCallCloudWatchLogsdescribeLogGroups", + "path": "Integ/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsdescribeLogGroups", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "Integ/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsdescribeLogGroups/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "Integ/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsdescribeLogGroups/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "Integ/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsdescribeLogGroups/Default", + "children": { + "Default": { + "id": "Default", + "path": "Integ/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsdescribeLogGroups/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "AssertEqualsCloudWatchLogsdescribeLogGroups": { + "id": "AssertEqualsCloudWatchLogsdescribeLogGroups", + "path": "Integ/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsdescribeLogGroups/AssertEqualsCloudWatchLogsdescribeLogGroups", + "children": { + "AssertionProvider": { + "id": "AssertionProvider", + "path": "Integ/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsdescribeLogGroups/AssertEqualsCloudWatchLogsdescribeLogGroups/AssertionProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "Integ/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsdescribeLogGroups/AssertEqualsCloudWatchLogsdescribeLogGroups/AssertionProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "Integ/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsdescribeLogGroups/AssertEqualsCloudWatchLogsdescribeLogGroups/Default", + "children": { + "Default": { + "id": "Default", + "path": "Integ/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsdescribeLogGroups/AssertEqualsCloudWatchLogsdescribeLogGroups/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "AssertionResults": { + "id": "AssertionResults", + "path": "Integ/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsdescribeLogGroups/AssertEqualsCloudWatchLogsdescribeLogGroups/AssertionResults", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.EqualsAssertion", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AwsApiCall", + "version": "0.0.0" + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81": { + "id": "SingletonFunction1488541a7b23466481b69b4408076b81", + "path": "Integ/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81", + "children": { + "Staging": { + "id": "Staging", + "path": "Integ/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "Integ/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "Integ/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + }, + "AssetParameters": { + "id": "AssetParameters", + "path": "Integ/DefaultTest/DeployAssert/AssetParameters", + "children": { + "0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd": { + "id": "0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd", + "path": "Integ/DefaultTest/DeployAssert/AssetParameters/0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd", + "children": { + "S3Bucket": { + "id": "S3Bucket", + "path": "Integ/DefaultTest/DeployAssert/AssetParameters/0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd/S3Bucket", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "S3VersionKey": { + "id": "S3VersionKey", + "path": "Integ/DefaultTest/DeployAssert/AssetParameters/0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd/S3VersionKey", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "ArtifactHash": { + "id": "ArtifactHash", + "path": "Integ/DefaultTest/DeployAssert/AssetParameters/0d8d96305807ac805d23c6d7b279eef238715605efad63c839ad1e7e8236bdbd/ArtifactHash", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file From e9233fae025ff5ee13cd47d35d22b4ec12fffa9e Mon Sep 17 00:00:00 2001 From: Kyle Laker Date: Thu, 4 Aug 2022 11:30:38 -0400 Subject: [PATCH 2/6] fix(lambda): Function allows specifying vpcSubnets without vpc (#21369) This is almost certainly never something that a user would intend. As-is, `vpcSubnets` does nothing if `vpc` is unspecified, so the Lambda Function would have no interfaces placed in a VPC. This is unlikely to be what a user would have expected if they passed a set of private subnets. This should be an error to prevent users from accidentally thinking they've put a Lambda Function into a VPC. If throwing an Error is a breaking change and not acceptable, then perhaps at least a Warning Annotation should be added in this case. This is motivated by the feedback received during review of #21357. ---- ### All Submissions: * [X] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-lambda/lib/function.ts | 7 ++++++- .../@aws-cdk/aws-lambda/test/vpc-lambda.test.ts | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 9c142eb4da0c8..2f14b31b3b9d2 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -1094,7 +1094,12 @@ Environment variables can be marked for removal when used in Lambda@Edge by sett throw new Error('Cannot configure \'securityGroup\' or \'allowAllOutbound\' without configuring a VPC'); } - if (!props.vpc) { return undefined; } + if (!props.vpc) { + if (props.vpcSubnets) { + throw new Error('Cannot configure \'vpcSubnets\' without configuring a VPC'); + } + return undefined; + } if (props.securityGroup && props.allowAllOutbound !== undefined) { throw new Error('Configure \'allowAllOutbound\' directly on the supplied SecurityGroup.'); diff --git a/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts b/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts index f1811b59e8f03..bdb742462194c 100644 --- a/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts @@ -323,6 +323,21 @@ describe('lambda + vpc', () => { }); }).toThrow(/Lambda Functions in a public subnet/); }); + + test('specifying vpcSubnets without a vpc throws an Error', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + expect(() => { + new lambda.Function(stack, 'Function', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_14_X, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE }, + }); + }).toThrow('Cannot configure \'vpcSubnets\' without configuring a VPC'); + }); }); class SomethingConnectable implements ec2.IConnectable { From 7ccc644008d974c91bb789628d23f4f0b510075b Mon Sep 17 00:00:00 2001 From: Masashi Tomooka Date: Fri, 5 Aug 2022 02:16:33 +0900 Subject: [PATCH 3/6] fix(cli): `--hotswap` does not handle `CfnOutput` change correctly (#21461) closes #19998 Although there are other types of diffs (other than `outputs`) that are currently ignored by hotswap, I leave them as-is since no one is complaining about the behavior and it may break someone's hotswap experience. https://github.com/aws/aws-cdk/blob/3853728c699bd9c47b60fcc24ac6a8b7d65306fe/packages/%40aws-cdk/cloudformation-diff/lib/diff/types.ts#L10-L21 ---- ### All Submissions: * [X] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-cdk/lib/api/hotswap-deployments.ts | 5 ++ .../api/hotswap/hotswap-deployments.test.ts | 58 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/packages/aws-cdk/lib/api/hotswap-deployments.ts b/packages/aws-cdk/lib/api/hotswap-deployments.ts index f64e5c16eddfa..487d8a1dd2281 100644 --- a/packages/aws-cdk/lib/api/hotswap-deployments.ts +++ b/packages/aws-cdk/lib/api/hotswap-deployments.ts @@ -68,6 +68,11 @@ async function findAllHotswappableChanges( sdk: ISDK, nestedStackNames: { [nestedStackName: string]: NestedStackNames }, ): Promise { + // Skip hotswap if there is any change on stack outputs + if (stackChanges.outputs.differenceCount > 0) { + return undefined; + } + const resourceDifferences = getStackResourceDifferences(stackChanges); let foundNonHotswappableChange = false; diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts index e7436e8d127ec..4c3bd67ee764c 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts @@ -356,3 +356,61 @@ test('changing the type of a deployed resource always results in a full deployme expect(mockUpdateMachineDefinition).not.toHaveBeenCalled(); expect(mockUpdateLambdaCode).not.toHaveBeenCalled(); }); + +test('A change to both a hotswappable resource and a stack output results in a full deployment', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + Outputs: { + SomeOutput: { + Value: 'old-value', + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + Outputs: { + SomeOutput: { + Value: 'new-value', + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockUpdateMachineDefinition).not.toHaveBeenCalled(); + expect(mockUpdateLambdaCode).not.toHaveBeenCalled(); +}); From a1a6e6cf2e03110322ea39e806d3d8206b609843 Mon Sep 17 00:00:00 2001 From: Schubert Date: Thu, 4 Aug 2022 19:31:22 +0100 Subject: [PATCH 4/6] feat(apigatewayv2): WebSocket API - IAM authorizer support (#21393) This adds support for `AWS_IAM` as Authorizer for Websocket $connect route. The CDK supports adding IAM Authorizer as `authorizationType` for `HttpApi`, but does not support it for `WebSocketApi` L2 construct IAM Authorization is covered in the docs [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-control-access-iam.html). It works the same way as REST or HTTP API's where you can make an endpoint (connect route for websocket) publicly inaccessible, and setup an IAM user, and allow access using signed URL's The above doc links back to [this](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html) section which explains the `AWS_IAM` This is also available from the AWS console for Websocket connect Route: Screenshot 2022-07-30 at 17 47 51 --- * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-apigatewayv2-authorizers/README.md | 40 ++ .../lib/websocket/iam.ts | 19 + .../lib/websocket/index.ts | 1 + .../aws-apigatewayv2-authorizers/package.json | 1 + ...aultTestDeployAssert2B412D7B.template.json | 1 + .../IntegApiGatewayV2Iam.template.json | 226 +++++++++ .../test/websocket/iam.integ.snapshot/cdk.out | 1 + .../websocket/iam.integ.snapshot/integ.json | 11 + .../iam.integ.snapshot/manifest.json | 103 ++++ .../websocket/iam.integ.snapshot/tree.json | 443 ++++++++++++++++++ .../test/websocket/iam.test.ts | 36 ++ .../test/websocket/integ.iam.ts | 61 +++ .../lib/websocket/authorizer.ts | 14 +- 13 files changed, 951 insertions(+), 6 deletions(-) create mode 100644 packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/iam.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/ApiGatewayV2WebSocketIamTestDefaultTestDeployAssert2B412D7B.template.json create mode 100644 packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/IntegApiGatewayV2Iam.template.json create mode 100644 packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/tree.json create mode 100644 packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.test.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/integ.iam.ts diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md index 16a7b3c591b27..2320be5505ba4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md @@ -28,6 +28,7 @@ - [IAM Authorizers](#iam-authorizers) - [WebSocket APIs](#websocket-apis) - [Lambda Authorizer](#lambda-authorizer) + - [IAM Authorizers](#iam-authorizer) ## Introduction @@ -256,3 +257,42 @@ new apigwv2.WebSocketApi(this, 'WebSocketApi', { }, }); ``` + +### IAM Authorizer + +IAM authorizers can be used to allow identity-based access to your WebSocket API. + +```ts +import { WebSocketIamAuthorizer } from '@aws-cdk/aws-apigatewayv2-authorizers'; +import { WebSocketLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +// This function handles your connect route +declare const connectHandler: lambda.Function; + +const webSocketApi = new apigwv2.WebSocketApi(this, 'WebSocketApi'); + +webSocketApi.addRoute('$connect', { + integration: new WebSocketLambdaIntegration('Integration', connectHandler), + authorizer: new WebSocketIamAuthorizer() +}); + +// Create an IAM user (identity) +const user = new iam.User(this, 'User'); + +const webSocketArn = Stack.of(this).formatArn({ + service: 'execute-api', + resource: webSocketApi.apiId, +}); + +// Grant access to the IAM user +user.attachInlinePolicy(new iam.Policy(this, 'AllowInvoke', { + statements: [ + new iam.PolicyStatement({ + actions: ['execute-api:Invoke'], + effect: iam.Effect.ALLOW, + resources: [webSocketArn], + }), + ], +})); + +``` diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/iam.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/iam.ts new file mode 100644 index 0000000000000..1adac6766e3b7 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/iam.ts @@ -0,0 +1,19 @@ +import { + WebSocketAuthorizerType, + WebSocketRouteAuthorizerBindOptions, + WebSocketRouteAuthorizerConfig, + IWebSocketRouteAuthorizer, +} from '@aws-cdk/aws-apigatewayv2'; + +/** + * Authorize WebSocket API Routes with IAM + */ +export class WebSocketIamAuthorizer implements IWebSocketRouteAuthorizer { + public bind( + _options: WebSocketRouteAuthorizerBindOptions, + ): WebSocketRouteAuthorizerConfig { + return { + authorizationType: WebSocketAuthorizerType.IAM, + }; + } +} diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/index.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/index.ts index 04a64da0c7540..645c9d0758583 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/index.ts @@ -1 +1,2 @@ export * from './lambda'; +export * from './iam'; diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json index 9327e2d103707..54aea78d30e1f 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json @@ -86,6 +86,7 @@ "@aws-cdk/integ-runner": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/aws-lambda": "^8.10.101", + "@aws-cdk/integ-tests": "0.0.0", "@types/jest": "^27.5.2" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/ApiGatewayV2WebSocketIamTestDefaultTestDeployAssert2B412D7B.template.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/ApiGatewayV2WebSocketIamTestDefaultTestDeployAssert2B412D7B.template.json new file mode 100644 index 0000000000000..9e26dfeeb6e64 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/ApiGatewayV2WebSocketIamTestDefaultTestDeployAssert2B412D7B.template.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/IntegApiGatewayV2Iam.template.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/IntegApiGatewayV2Iam.template.json new file mode 100644 index 0000000000000..8083d38ced5f5 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/IntegApiGatewayV2Iam.template.json @@ -0,0 +1,226 @@ +{ + "Resources": { + "User00B015A1": { + "Type": "AWS::IAM::User" + }, + "UserAccessEC42ADF7": { + "Type": "AWS::IAM::AccessKey", + "Properties": { + "UserName": { + "Ref": "User00B015A1" + } + } + }, + "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": { + "ZipFile": "exports.handler = () => {return true}" + }, + "Role": { + "Fn::GetAtt": [ + "authfunctionServiceRoleFCB72198", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "authfunctionServiceRoleFCB72198" + ] + }, + "WebSocketApi34BCF99B": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "WebSocketApi", + "ProtocolType": "WEBSOCKET", + "RouteSelectionExpression": "$request.body.action" + } + }, + "WebSocketApiconnectRouteWebSocketLambdaIntegrationPermission76CD86C6": { + "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": "WebSocketApi34BCF99B" + }, + "/*/*$connect" + ] + ] + } + } + }, + "WebSocketApiconnectRouteWebSocketLambdaIntegration3D2B13DD": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "WebSocketApi34BCF99B" + }, + "IntegrationType": "AWS_PROXY", + "IntegrationUri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "authfunction96361832", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + }, + "WebSocketApiconnectRoute846149DD": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "WebSocketApi34BCF99B" + }, + "RouteKey": "$connect", + "AuthorizationType": "AWS_IAM", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "WebSocketApiconnectRouteWebSocketLambdaIntegration3D2B13DD" + } + ] + ] + } + } + }, + "AllowInvoke767865EA": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "WebSocketApi34BCF99B" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AllowInvoke767865EA", + "Users": [ + { + "Ref": "User00B015A1" + } + ] + } + } + }, + "Outputs": { + "TESTACCESSKEYID": { + "Value": { + "Ref": "UserAccessEC42ADF7" + } + }, + "TESTSECRETACCESSKEY": { + "Value": { + "Fn::GetAtt": [ + "UserAccessEC42ADF7", + "SecretAccessKey" + ] + } + }, + "TESTREGION": { + "Value": { + "Ref": "AWS::Region" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..588d7b269d34f --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/integ.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/integ.json new file mode 100644 index 0000000000000..c1ef6336fee4f --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/integ.json @@ -0,0 +1,11 @@ +{ + "version": "20.0.0", + "testCases": { + "ApiGatewayV2WebSocketIamTest/DefaultTest": { + "stacks": [ + "IntegApiGatewayV2Iam" + ], + "assertionStack": "ApiGatewayV2WebSocketIamTestDefaultTestDeployAssert2B412D7B" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..0731b24a3b278 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/manifest.json @@ -0,0 +1,103 @@ +{ + "version": "20.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "IntegApiGatewayV2Iam": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "IntegApiGatewayV2Iam.template.json", + "validateOnSynth": false + }, + "metadata": { + "/IntegApiGatewayV2Iam/User/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "User00B015A1" + } + ], + "/IntegApiGatewayV2Iam/UserAccess/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "UserAccessEC42ADF7" + } + ], + "/IntegApiGatewayV2Iam/auth-function/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "authfunctionServiceRoleFCB72198" + } + ], + "/IntegApiGatewayV2Iam/auth-function/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "authfunction96361832" + } + ], + "/IntegApiGatewayV2Iam/WebSocketApi/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "WebSocketApi34BCF99B" + } + ], + "/IntegApiGatewayV2Iam/WebSocketApi/$connect-Route/WebSocketLambdaIntegration-Permission": [ + { + "type": "aws:cdk:logicalId", + "data": "WebSocketApiconnectRouteWebSocketLambdaIntegrationPermission76CD86C6" + } + ], + "/IntegApiGatewayV2Iam/WebSocketApi/$connect-Route/WebSocketLambdaIntegration/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "WebSocketApiconnectRouteWebSocketLambdaIntegration3D2B13DD" + } + ], + "/IntegApiGatewayV2Iam/WebSocketApi/$connect-Route/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "WebSocketApiconnectRoute846149DD" + } + ], + "/IntegApiGatewayV2Iam/AllowInvoke/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AllowInvoke767865EA" + } + ], + "/IntegApiGatewayV2Iam/TESTACCESSKEYID": [ + { + "type": "aws:cdk:logicalId", + "data": "TESTACCESSKEYID" + } + ], + "/IntegApiGatewayV2Iam/TESTSECRETACCESSKEY": [ + { + "type": "aws:cdk:logicalId", + "data": "TESTSECRETACCESSKEY" + } + ], + "/IntegApiGatewayV2Iam/TESTREGION": [ + { + "type": "aws:cdk:logicalId", + "data": "TESTREGION" + } + ] + }, + "displayName": "IntegApiGatewayV2Iam" + }, + "ApiGatewayV2WebSocketIamTestDefaultTestDeployAssert2B412D7B": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "ApiGatewayV2WebSocketIamTestDefaultTestDeployAssert2B412D7B.template.json", + "validateOnSynth": false + }, + "displayName": "ApiGatewayV2WebSocketIamTest/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/tree.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/tree.json new file mode 100644 index 0000000000000..f0b23496709c0 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.integ.snapshot/tree.json @@ -0,0 +1,443 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.58" + } + }, + "IntegApiGatewayV2Iam": { + "id": "IntegApiGatewayV2Iam", + "path": "IntegApiGatewayV2Iam", + "children": { + "User": { + "id": "User", + "path": "IntegApiGatewayV2Iam/User", + "children": { + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/User/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::User", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnUser", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.User", + "version": "0.0.0" + } + }, + "UserAccess": { + "id": "UserAccess", + "path": "IntegApiGatewayV2Iam/UserAccess", + "children": { + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/UserAccess/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::AccessKey", + "aws:cdk:cloudformation:props": { + "userName": { + "Ref": "User00B015A1" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnAccessKey", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.AccessKey", + "version": "0.0.0" + } + }, + "auth-function": { + "id": "auth-function", + "path": "IntegApiGatewayV2Iam/auth-function", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "IntegApiGatewayV2Iam/auth-function/ServiceRole", + "children": { + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/auth-function/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "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" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/auth-function/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "zipFile": "exports.handler = () => {return true}" + }, + "role": { + "Fn::GetAtt": [ + "authfunctionServiceRoleFCB72198", + "Arn" + ] + }, + "handler": "index.handler", + "runtime": "nodejs14.x" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.CfnFunction", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.Function", + "version": "0.0.0" + } + }, + "WebSocketApi": { + "id": "WebSocketApi", + "path": "IntegApiGatewayV2Iam/WebSocketApi", + "children": { + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/WebSocketApi/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::Api", + "aws:cdk:cloudformation:props": { + "name": "WebSocketApi", + "protocolType": "WEBSOCKET", + "routeSelectionExpression": "$request.body.action" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigatewayv2.CfnApi", + "version": "0.0.0" + } + }, + "$connect-Route": { + "id": "$connect-Route", + "path": "IntegApiGatewayV2Iam/WebSocketApi/$connect-Route", + "children": { + "WebSocketLambdaIntegration-Permission": { + "id": "WebSocketLambdaIntegration-Permission", + "path": "IntegApiGatewayV2Iam/WebSocketApi/$connect-Route/WebSocketLambdaIntegration-Permission", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Permission", + "aws:cdk:cloudformation:props": { + "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": "WebSocketApi34BCF99B" + }, + "/*/*$connect" + ] + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.CfnPermission", + "version": "0.0.0" + } + }, + "WebSocketLambdaIntegration": { + "id": "WebSocketLambdaIntegration", + "path": "IntegApiGatewayV2Iam/WebSocketApi/$connect-Route/WebSocketLambdaIntegration", + "children": { + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/WebSocketApi/$connect-Route/WebSocketLambdaIntegration/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::Integration", + "aws:cdk:cloudformation:props": { + "apiId": { + "Ref": "WebSocketApi34BCF99B" + }, + "integrationType": "AWS_PROXY", + "integrationUri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "authfunction96361832", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigatewayv2.CfnIntegration", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketIntegration", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/WebSocketApi/$connect-Route/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::Route", + "aws:cdk:cloudformation:props": { + "apiId": { + "Ref": "WebSocketApi34BCF99B" + }, + "routeKey": "$connect", + "authorizationType": "AWS_IAM", + "target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "WebSocketApiconnectRouteWebSocketLambdaIntegration3D2B13DD" + } + ] + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigatewayv2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketApi", + "version": "0.0.0" + } + }, + "AllowInvoke": { + "id": "AllowInvoke", + "path": "IntegApiGatewayV2Iam/AllowInvoke", + "children": { + "Resource": { + "id": "Resource", + "path": "IntegApiGatewayV2Iam/AllowInvoke/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "WebSocketApi34BCF99B" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "AllowInvoke767865EA", + "users": [ + { + "Ref": "User00B015A1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + }, + "TESTACCESSKEYID": { + "id": "TESTACCESSKEYID", + "path": "IntegApiGatewayV2Iam/TESTACCESSKEYID", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "TESTSECRETACCESSKEY": { + "id": "TESTSECRETACCESSKEY", + "path": "IntegApiGatewayV2Iam/TESTSECRETACCESSKEY", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "TESTREGION": { + "id": "TESTREGION", + "path": "IntegApiGatewayV2Iam/TESTREGION", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "ApiGatewayV2WebSocketIamTest": { + "id": "ApiGatewayV2WebSocketIamTest", + "path": "ApiGatewayV2WebSocketIamTest", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "ApiGatewayV2WebSocketIamTest/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "ApiGatewayV2WebSocketIamTest/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.58" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "ApiGatewayV2WebSocketIamTest/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.test.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.test.ts new file mode 100644 index 0000000000000..f0f34a3c11214 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/iam.test.ts @@ -0,0 +1,36 @@ +import { Template } from '@aws-cdk/assertions'; +import { WebSocketApi } from '@aws-cdk/aws-apigatewayv2'; +import { WebSocketLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; +import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; +import { Stack } from '@aws-cdk/core'; +import { WebSocketIamAuthorizer } from '../../lib'; + +describe('WebSocketLambdaAuthorizer', () => { + test('default', () => { + const stack = new Stack(); + + const handler = new Function(stack, 'auth-function', { + runtime: Runtime.NODEJS_14_X, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', + }); + const integration = new WebSocketLambdaIntegration('Integration', handler); + + const authorizer = new WebSocketIamAuthorizer(); + + new WebSocketApi(stack, 'WebSocketApi', { + connectRouteOptions: { + integration, + authorizer, + }, + }); + + Template.fromStack(stack).hasResourceProperties( + 'AWS::ApiGatewayV2::Route', + { + RouteKey: '$connect', + AuthorizationType: 'AWS_IAM', + }, + ); + }); +}); diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/integ.iam.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/integ.iam.ts new file mode 100644 index 0000000000000..281268ab6bee8 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/integ.iam.ts @@ -0,0 +1,61 @@ +import * as apigatewayv2 from '@aws-cdk/aws-apigatewayv2'; +import { WebSocketLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; +import * as iam from '@aws-cdk/aws-iam'; +import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import { Stack } from '@aws-cdk/core'; +import * as integ from '@aws-cdk/integ-tests'; +import { WebSocketIamAuthorizer } from '../../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'IntegApiGatewayV2Iam'); +const user = new iam.User(stack, 'User'); +const userAccessKey = new iam.AccessKey(stack, 'UserAccess', { + user, +}); + +const handler = new Function(stack, 'auth-function', { + runtime: Runtime.NODEJS_14_X, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', +}); + +const webSocketApi = new apigatewayv2.WebSocketApi(stack, 'WebSocketApi', { + connectRouteOptions: { + integration: new WebSocketLambdaIntegration('WebSocketLambdaIntegration', handler), + authorizer: new WebSocketIamAuthorizer(), + }, +}); + +const arn = Stack.of(stack).formatArn({ + service: 'execute-api', + resource: webSocketApi.apiId, +}); + +user.attachInlinePolicy(new iam.Policy(stack, 'AllowInvoke', { + statements: [ + new iam.PolicyStatement({ + actions: ['execute-api:Invoke'], + effect: iam.Effect.ALLOW, + resources: [arn], + }), + ], +})); + +new integ.IntegTest(app, 'ApiGatewayV2WebSocketIamTest', { + testCases: [stack], +}); + +new cdk.CfnOutput(stack, 'TESTACCESSKEYID', { + value: userAccessKey.accessKeyId, +}); + +new cdk.CfnOutput(stack, 'TESTSECRETACCESSKEY', { + value: userAccessKey.secretAccessKey.unsafeUnwrap(), +}); + +new cdk.CfnOutput(stack, 'TESTREGION', { + value: stack.region, +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/authorizer.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/authorizer.ts index 5abb420c80bad..28d002d3a1044 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/authorizer.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/authorizer.ts @@ -12,6 +12,9 @@ import { IWebSocketRoute } from './route'; export enum WebSocketAuthorizerType { /** Lambda Authorizer */ LAMBDA = 'REQUEST', + + /** IAM Authorizer */ + IAM = 'AWS_IAM', } /** @@ -22,12 +25,12 @@ export interface WebSocketAuthorizerProps { * Name of the authorizer * @default - id of the WebSocketAuthorizer construct. */ - readonly authorizerName?: string + readonly authorizerName?: string; /** * WebSocket Api to attach the authorizer to */ - readonly webSocketApi: IWebSocketApi + readonly webSocketApi: IWebSocketApi; /** * The type of authorizer @@ -53,8 +56,7 @@ export interface WebSocketAuthorizerProps { /** * An authorizer for WebSocket APIs */ -export interface IWebSocketAuthorizer extends IAuthorizer { -} +export interface IWebSocketAuthorizer extends IAuthorizer {} /** * Reference to an WebSocket authorizer @@ -63,7 +65,7 @@ export interface WebSocketAuthorizerAttributes { /** * Id of the Authorizer */ - readonly authorizerId: string + readonly authorizerId: string; /** * Type of authorizer @@ -72,7 +74,7 @@ export interface WebSocketAuthorizerAttributes { * - CUSTOM - Lambda Authorizer * - NONE - No Authorization */ - readonly authorizerType: string + readonly authorizerType: string; } /** From 1000abe43b111ea933ed9b717c3ebe18f96a4d7b Mon Sep 17 00:00:00 2001 From: guessi Date: Fri, 5 Aug 2022 03:15:39 +0800 Subject: [PATCH 5/6] fix(eks): missing question marks cause update cluster setting failure (#21463) introduced by #21185 Fixes: #21436 ---- ### All Submissions: * [X] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [X] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [X] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [X] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts index d7e66c7c79961..dfafd02d4c291 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts @@ -286,11 +286,11 @@ function parseProps(props: any): aws.EKS.CreateClusterRequest { parsed.resourcesVpcConfig.endpointPublicAccess = parsed.resourcesVpcConfig.endpointPublicAccess === 'true'; } - if (typeof (parsed.logging?.clusterLogging[0].enabled) === 'string') { + if (typeof (parsed.logging?.clusterLogging[0]?.enabled) === 'string') { parsed.logging.clusterLogging[0].enabled = parsed.logging.clusterLogging[0].enabled === 'true'; } - if (typeof (parsed.logging?.clusterLogging[1].enabled) === 'string') { + if (typeof (parsed.logging?.clusterLogging[1]?.enabled) === 'string') { parsed.logging.clusterLogging[1].enabled = parsed.logging.clusterLogging[1].enabled === 'false'; } From cef98cf20357bb0f3748e315e4bea093a97e2302 Mon Sep 17 00:00:00 2001 From: Hassan Azhar Date: Fri, 5 Aug 2022 01:22:59 +0500 Subject: [PATCH 6/6] fix(lambda-nodejs):`false` compiler options are not passed tsc (#21377) Any option that is set to false is omitted from the `tsc` command line options. This results in failed compilations during cdk synthesis whereas building directly with `tsc` does not fail. closes #20670 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts | 2 ++ packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts | 3 +++ 2 files changed, 5 insertions(+) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts index ddd9a7b01ab97..eb2f59db03e09 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts @@ -175,6 +175,8 @@ export function getTsconfigCompilerOptions(tsconfigPath: string): string { if (type === 'boolean') { if (value) { compilerOptionsString += option + ' '; + } else { + compilerOptionsString += option + ' false '; } } else if (type === 'string') { compilerOptionsString += option + ' ' + value + ' '; diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts index c38334e7e4176..189769bc90f34 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts @@ -188,7 +188,9 @@ describe('getTsconfigCompilerOptions', () => { '--alwaysStrict', '--charset utf8', '--declaration', + '--declarationMap false', '--experimentalDecorators', + '--incremental false', '--inlineSourceMap', '--inlineSources', '--lib es2020', @@ -207,6 +209,7 @@ describe('getTsconfigCompilerOptions', () => { '--strict', '--strictNullChecks', '--strictPropertyInitialization', + '--stripInternal false', '--target ES2020', ].join(' ')); });