diff --git a/.mergify.yml b/.mergify.yml index 49320bf2385ee..3348c3b934909 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -19,7 +19,9 @@ pull_request_rules: queue: name: default method: squash - commit_message: title+body + commit_message_template: |- + {{ title }} (#{{ number }}) + {{ body }} conditions: - base!=release - -title~=(WIP|wip) @@ -40,7 +42,9 @@ pull_request_rules: queue: name: default method: squash - commit_message: title+body + commit_message_template: |- + {{ title }} (#{{ number }}) + {{ body }} conditions: - base!=release - -title~=(WIP|wip) @@ -62,7 +66,9 @@ pull_request_rules: queue: name: default method: merge - commit_message: title+body + commit_message_template: |- + {{ title }} (#{{ number }}) + {{ body }} conditions: - -title~=(WIP|wip) - -label~=(blocked|do-not-merge) @@ -106,7 +112,9 @@ pull_request_rules: queue: name: default method: squash - commit_message: title+body + commit_message_template: |- + {{ title }} (#{{ number }}) + {{ body }} conditions: - -title~=(WIP|wip) - -label~=(blocked|do-not-merge) diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index 6b5d57a000a4e..fa3498335f679 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -86,3 +86,10 @@ removed:@aws-cdk/aws-stepfunctions-tasks.EmrCreateClusterProps.autoTerminationPo # Changed property securityGroupId to optional because either securityGroupId or # securityGroupName is required. Therefore securityGroupId is no longer mandatory. weakened:@aws-cdk/cloud-assembly-schema.SecurityGroupContextQuery + +# refactor autoscaling lifecycle hook target bind() methods to make role optional by +# having bind() methods create the role if it isn't passed to them +incompatible-argument:@aws-cdk/aws-autoscaling-hooktargets.FunctionHook.bind +incompatible-argument:@aws-cdk/aws-autoscaling-hooktargets.QueueHook.bind +incompatible-argument:@aws-cdk/aws-autoscaling-hooktargets.TopicHook.bind +incompatible-argument:@aws-cdk/aws-autoscaling.ILifecycleHookTarget.bind diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/Pipfile.lock b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/Pipfile.lock index 0c01b1a6d6409..37b1c47e3d5e7 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/Pipfile.lock +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/Pipfile.lock @@ -19,58 +19,72 @@ "develop": { "boto3": { "hashes": [ - "sha256:5f3969dd167b787e5bc6742afbfe15e149051d8c6aa1edaa4858133384f64ec7", - "sha256:713da2b28e9e4cd77e922690c97935dd2316ab27635b6bab4745a2d42bd887ec" + "sha256:76b3ee0d1dd860c9218bc864cd29f1ee986f6e1e75e8669725dd3c411039379e", + "sha256:c39cb6ed376ba1d4689ac8f6759a2b2d8a0b0424dbec0cd3af1558079bcf06e8" ], "index": "pypi", - "version": "==1.15.11" + "version": "==1.20.23" }, "botocore": { "hashes": [ - "sha256:1531ee5d7f7d0f0d9a12ea829ef046ac52063a1948409ae19a452a3f47a07937", - "sha256:a0514ba531148af26fe36bf75c73089698a5ed8ae150695b96e7cbdf32dd232b" + "sha256:640b62110aa6d1c25553eceafb5bcd89aedeb84b191598d1f6492ad24374d285", + "sha256:7459766c4594f3b8877e8013f93f0dc6c6486acbeb7d9c9ae488396529cc2e84" ], - "version": "==1.18.11" + "markers": "python_version >= '3.6'", + "version": "==1.23.23" }, "coverage": { "hashes": [ - "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516", - "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259", - "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9", - "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097", - "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0", - "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f", - "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7", - "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c", - "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5", - "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7", - "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729", - "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978", - "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9", - "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f", - "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9", - "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822", - "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418", - "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82", - "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f", - "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d", - "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221", - "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4", - "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21", - "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709", - "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54", - "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d", - "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270", - "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24", - "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751", - "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a", - "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237", - "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7", - "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636", - "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8" + "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0", + "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd", + "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884", + "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48", + "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76", + "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0", + "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64", + "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685", + "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47", + "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d", + "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840", + "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f", + "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971", + "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c", + "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a", + "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de", + "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17", + "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4", + "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521", + "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57", + "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b", + "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282", + "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644", + "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475", + "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d", + "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da", + "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953", + "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2", + "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e", + "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c", + "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc", + "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64", + "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74", + "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617", + "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3", + "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d", + "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa", + "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739", + "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8", + "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8", + "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781", + "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58", + "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9", + "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c", + "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd", + "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e", + "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49" ], "index": "pypi", - "version": "==5.3" + "version": "==6.2" }, "jmespath": { "hashes": [ @@ -82,42 +96,43 @@ }, "python-dateutil": { "hashes": [ - "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", - "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.8.1" + "version": "==2.8.2" }, "s3transfer": { "hashes": [ - "sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13", - "sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db" + "sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c", + "sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803" ], - "version": "==0.3.3" + "markers": "python_version >= '3.6'", + "version": "==0.5.0" }, "six": { "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.15.0" + "version": "==1.16.0" }, "urllib3": { "hashes": [ - "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a", - "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" + "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", + "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" ], - "markers": "python_version != '3.4'", - "version": "==1.25.10" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.7" }, "yapf": { "hashes": [ - "sha256:3000abee4c28daebad55da6c85f3cd07b8062ce48e2e9943c8da1b9667d48427", - "sha256:3abf61ba67cf603069710d30acbc88cfe565d907e16ad81429ae90ce9651e0c9" + "sha256:408fb9a2b254c302f49db83c59f9aa0b4b0fd0ec25be3a5c51181327922ff63d", + "sha256:e3a234ba8455fe201eaa649cdac872d590089a18b661e39bbac7020978dd9c2e" ], "index": "pypi", - "version": "==0.30.0" + "version": "==0.31.0" } } } diff --git a/packages/@aws-cdk/aws-amplify/README.md b/packages/@aws-cdk/aws-amplify/README.md index 059588e493241..c015bd38494e8 100644 --- a/packages/@aws-cdk/aws-amplify/README.md +++ b/packages/@aws-cdk/aws-amplify/README.md @@ -208,3 +208,13 @@ const amplifyApp = new amplify.App(stack, 'App', { ], }); ``` + +## Deploying Assets + +`sourceCodeProvider` is optional; when this is not specified the Amplify app can be deployed to using `.zip` packages. The `asset` property can be used to deploy S3 assets to Amplify as part of the CDK: + +```ts +const asset = new assets.Asset(this, "SampleAsset", {}); +const amplifyApp = new amplify.App(this, 'MyApp', {}); +const branch = amplifyApp.addBranch("dev", { asset: asset }); +``` diff --git a/packages/@aws-cdk/aws-amplify/lib/asset-deployment-handler/common.ts b/packages/@aws-cdk/aws-amplify/lib/asset-deployment-handler/common.ts new file mode 100644 index 0000000000000..92c29e72c6768 --- /dev/null +++ b/packages/@aws-cdk/aws-amplify/lib/asset-deployment-handler/common.ts @@ -0,0 +1,76 @@ +export interface AmplifyJobId { + /** + * If this field is included in an event passed to "IsComplete", it means we + * initiated an Amplify deployment that should be monitored using + * amplify:GetJob + */ + AmplifyJobId?: string; +} + +export type ResourceEvent = AWSLambda.CloudFormationCustomResourceEvent & AmplifyJobId; + +export interface IsCompleteResponse { + /** + * Indicates if the resource operation is complete or should we retry. + */ + readonly IsComplete: boolean; + + /** + * Additional/changes to resource attributes. + */ + readonly Data?: { [name: string]: any }; +}; + +export abstract class ResourceHandler { + protected readonly requestId: string; + protected readonly logicalResourceId: string; + protected readonly requestType: 'Create' | 'Update' | 'Delete'; + protected readonly physicalResourceId?: string; + protected readonly event: ResourceEvent; + + constructor(event: ResourceEvent) { + this.requestType = event.RequestType; + this.requestId = event.RequestId; + this.logicalResourceId = event.LogicalResourceId; + this.physicalResourceId = (event as any).PhysicalResourceId; + this.event = event; + } + + public onEvent() { + switch (this.requestType) { + case 'Create': + return this.onCreate(); + case 'Update': + return this.onUpdate(); + case 'Delete': + return this.onDelete(); + } + + throw new Error(`Invalid request type ${this.requestType}`); + } + + public isComplete() { + switch (this.requestType) { + case 'Create': + return this.isCreateComplete(); + case 'Update': + return this.isUpdateComplete(); + case 'Delete': + return this.isDeleteComplete(); + } + + throw new Error(`Invalid request type ${this.requestType}`); + } + + protected log(x: any) { + // eslint-disable-next-line no-console + console.log(JSON.stringify(x, undefined, 2)); + } + + protected abstract async onCreate(): Promise; + protected abstract async onDelete(): Promise; + protected abstract async onUpdate(): Promise; + protected abstract async isCreateComplete(): Promise; + protected abstract async isDeleteComplete(): Promise; + protected abstract async isUpdateComplete(): Promise; +} diff --git a/packages/@aws-cdk/aws-amplify/lib/asset-deployment-handler/handler.ts b/packages/@aws-cdk/aws-amplify/lib/asset-deployment-handler/handler.ts new file mode 100644 index 0000000000000..9577bb3049986 --- /dev/null +++ b/packages/@aws-cdk/aws-amplify/lib/asset-deployment-handler/handler.ts @@ -0,0 +1,136 @@ +// aws-sdk available at runtime for lambdas +// eslint-disable-next-line import/no-extraneous-dependencies +import { Amplify, S3 } from 'aws-sdk'; +import { AmplifyJobId, IsCompleteResponse, ResourceEvent, ResourceHandler } from './common'; + +export interface AmplifyAssetDeploymentProps { + AppId: string; + BranchName: string; + S3BucketName: string; + S3ObjectKey: string; + TimeoutSeconds: number; +} + +export class AmplifyAssetDeploymentHandler extends ResourceHandler { + private readonly props: AmplifyAssetDeploymentProps; + protected readonly amplify: Amplify; + protected readonly s3: S3; + + constructor(amplify: Amplify, s3: S3, event: ResourceEvent) { + super(event); + + this.props = parseProps(this.event.ResourceProperties); + this.amplify = amplify; + this.s3 = s3; + } + + // ------ + // CREATE + // ------ + + protected async onCreate(): Promise { + // eslint-disable-next-line no-console + console.log('deploying to Amplify with options:', JSON.stringify(this.props, undefined, 2)); + + // Verify no jobs are currently running. + const jobs = await this.amplify + .listJobs({ + appId: this.props.AppId, + branchName: this.props.BranchName, + maxResults: 1, + }) + .promise(); + + if ( + jobs.jobSummaries && + jobs.jobSummaries.find(summary => summary.status === 'PENDING') + ) { + return Promise.reject('Amplify job already running. Aborting deployment.'); + } + + // Create a pre-signed get URL of the asset so Amplify can retrieve it. + const assetUrl = this.s3.getSignedUrl('getObject', { + Bucket: this.props.S3BucketName, + Key: this.props.S3ObjectKey, + }); + + // Deploy the asset to Amplify. + const deployment = await this.amplify + .startDeployment({ + appId: this.props.AppId, + branchName: this.props.BranchName, + sourceUrl: assetUrl, + }) + .promise(); + + return { + AmplifyJobId: deployment.jobSummary.jobId, + }; + } + + protected async isCreateComplete() { + return this.isActive(this.event.AmplifyJobId); + } + + // ------ + // DELETE + // ------ + + protected async onDelete(): Promise { + // We can't delete this resource as it's a deployment. + return; + } + + protected async isDeleteComplete(): Promise { + // We can't delete this resource as it's a deployment. + return { + IsComplete: true, + }; + } + + // ------ + // UPDATE + // ------ + + protected async onUpdate() { + return this.onCreate(); + } + + protected async isUpdateComplete() { + return this.isActive(this.event.AmplifyJobId); + } + + private async isActive(jobId?: string): Promise { + if (!jobId) { + throw new Error('Unable to determine Amplify job status without job id'); + } + + const job = await this.amplify + .getJob({ + appId: this.props.AppId, + branchName: this.props.BranchName, + jobId: jobId, + }) + .promise(); + + if (job.job.summary.status === 'SUCCEED') { + return { + IsComplete: true, + Data: { + JobId: jobId, + Status: job.job.summary.status, + }, + }; + } if (job.job.summary.status === 'FAILED' || job.job.summary.status === 'CANCELLED') { + throw new Error(`Amplify job failed with status: ${job.job.summary.status}`); + } else { + return { + IsComplete: false, + }; + } + } +} + +function parseProps(props: any): AmplifyAssetDeploymentProps { + return props; +} diff --git a/packages/@aws-cdk/aws-amplify/lib/asset-deployment-handler/index.ts b/packages/@aws-cdk/aws-amplify/lib/asset-deployment-handler/index.ts new file mode 100644 index 0000000000000..ebb589bb3f847 --- /dev/null +++ b/packages/@aws-cdk/aws-amplify/lib/asset-deployment-handler/index.ts @@ -0,0 +1,35 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { IsCompleteResponse } from '@aws-cdk/custom-resources/lib/provider-framework/types'; +// aws-sdk available at runtime for lambdas +// eslint-disable-next-line import/no-extraneous-dependencies +import { Amplify, S3, config } from 'aws-sdk'; +import { ResourceEvent } from './common'; +import { AmplifyAssetDeploymentHandler } from './handler'; + +const AMPLIFY_ASSET_DEPLOYMENT_RESOURCE_TYPE = 'Custom::AmplifyAssetDeployment'; + +config.logger = console; + +const amplify = new Amplify(); +const s3 = new S3({ signatureVersion: 'v4' }); + +export async function onEvent(event: ResourceEvent) { + const provider = createResourceHandler(event); + return provider.onEvent(); +} + +export async function isComplete( + event: ResourceEvent, +): Promise { + const provider = createResourceHandler(event); + return provider.isComplete(); +} + +function createResourceHandler(event: ResourceEvent) { + switch (event.ResourceType) { + case AMPLIFY_ASSET_DEPLOYMENT_RESOURCE_TYPE: + return new AmplifyAssetDeploymentHandler(amplify, s3, event); + default: + throw new Error(`Unsupported resource type "${event.ResourceType}"`); + } +} diff --git a/packages/@aws-cdk/aws-amplify/lib/branch.ts b/packages/@aws-cdk/aws-amplify/lib/branch.ts index 210f27d4831e2..05890ec2895a9 100644 --- a/packages/@aws-cdk/aws-amplify/lib/branch.ts +++ b/packages/@aws-cdk/aws-amplify/lib/branch.ts @@ -1,5 +1,19 @@ +import * as path from 'path'; import * as codebuild from '@aws-cdk/aws-codebuild'; -import { IResource, Lazy, Resource } from '@aws-cdk/core'; +import * as iam from '@aws-cdk/aws-iam'; +import * as lambda from '@aws-cdk/aws-lambda'; +import { NodejsFunction } from '@aws-cdk/aws-lambda-nodejs'; +import { Asset } from '@aws-cdk/aws-s3-assets'; +import { + CustomResource, + IResource, + Lazy, + Resource, + Duration, + NestedStack, + Stack, +} from '@aws-cdk/core'; +import { Provider } from '@aws-cdk/custom-resources'; import { Construct } from 'constructs'; import { CfnBranch } from './amplify.generated'; import { IApp } from './app'; @@ -90,6 +104,16 @@ export interface BranchOptions { * @default - no stage */ readonly stage?: string; + + /** + * Asset for deployment. + * + * The Amplify app must not have a sourceCodeProvider configured as this resource uses Amplify's + * startDeployment API to initiate and deploy a S3 asset onto the App. + * + * @default - no asset + */ + readonly asset?: Asset } /** @@ -148,6 +172,19 @@ export class Branch extends Resource implements IBranch { this.arn = branch.attrArn; this.branchName = branch.attrBranchName; + + if (props.asset) { + new CustomResource(this, 'DeploymentResource', { + serviceToken: AmplifyAssetDeploymentProvider.getOrCreate(this), + resourceType: 'Custom::AmplifyAssetDeployment', + properties: { + AppId: props.app.appId, + BranchName: branchName, + S3ObjectKey: props.asset.s3ObjectKey, + S3BucketName: props.asset.s3BucketName, + }, + }); + } } /** @@ -161,3 +198,76 @@ export class Branch extends Resource implements IBranch { return this; } } + +class AmplifyAssetDeploymentProvider extends NestedStack { + /** + * Returns the singleton provider. + */ + public static getOrCreate(scope: Construct) { + const providerId = + 'com.amazonaws.cdk.custom-resources.amplify-asset-deployment-provider'; + const stack = Stack.of(scope); + const group = + (stack.node.tryFindChild(providerId) as AmplifyAssetDeploymentProvider) ?? new AmplifyAssetDeploymentProvider(stack, providerId); + return group.provider.serviceToken; + } + + private readonly provider: Provider; + + constructor(scope: Construct, id: string) { + super(scope, id); + + const onEvent = new NodejsFunction( + this, + 'amplify-asset-deployment-on-event', + { + entry: path.join( + __dirname, + 'asset-deployment-handler/index.ts', + ), + runtime: lambda.Runtime.NODEJS_14_X, + handler: 'onEvent', + initialPolicy: [ + new iam.PolicyStatement({ + resources: ['*'], + actions: [ + 's3:GetObject', + 's3:GetSignedUrl', + 'amplify:ListJobs', + 'amplify:StartDeployment', + ], + }), + ], + }, + ); + + const isComplete = new NodejsFunction( + this, + 'amplify-asset-deployment-is-complete', + { + entry: path.join( + __dirname, + 'asset-deployment-handler/index.ts', + ), + runtime: lambda.Runtime.NODEJS_14_X, + handler: 'isComplete', + initialPolicy: [ + new iam.PolicyStatement({ + resources: ['*'], + actions: ['amplify:GetJob*'], + }), + ], + }, + ); + + this.provider = new Provider( + this, + 'amplify-asset-deployment-handler-provider', + { + onEventHandler: onEvent, + isCompleteHandler: isComplete, + totalTimeout: Duration.minutes(5), + }, + ); + } +} diff --git a/packages/@aws-cdk/aws-amplify/package.json b/packages/@aws-cdk/aws-amplify/package.json index aa5e8cb02ca74..f5903f7a8ca74 100644 --- a/packages/@aws-cdk/aws-amplify/package.json +++ b/packages/@aws-cdk/aws-amplify/package.json @@ -80,15 +80,20 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.0.3", - "@types/yaml": "1.9.6" + "@types/yaml": "1.9.6", + "aws-sdk": "^2.848.0" }, "dependencies": { "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", + "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-lambda-nodejs": "0.0.0", + "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/aws-secretsmanager": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/custom-resources": "0.0.0", "constructs": "^3.3.69", "yaml": "1.10.2" }, @@ -100,8 +105,12 @@ "@aws-cdk/aws-codecommit": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", + "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-lambda-nodejs": "0.0.0", + "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/aws-secretsmanager": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/custom-resources": "0.0.0", "constructs": "^3.3.69" }, "engines": { diff --git a/packages/@aws-cdk/aws-amplify/test/asset-deployment-handler/index.test.ts b/packages/@aws-cdk/aws-amplify/test/asset-deployment-handler/index.test.ts new file mode 100644 index 0000000000000..ad40018672e23 --- /dev/null +++ b/packages/@aws-cdk/aws-amplify/test/asset-deployment-handler/index.test.ts @@ -0,0 +1,741 @@ +const getSignedUrlResponse = jest.fn(); +const mockS3 = { + getSignedUrl: getSignedUrlResponse, +}; +const listJobsResponse = jest.fn(); +const listJobsRequest = jest.fn().mockImplementation(() => { + return { + promise: listJobsResponse, + }; +}); +const startDeploymentResponse = jest.fn(); +const startDeploymentRequest = jest.fn().mockImplementation(() => { + return { + promise: startDeploymentResponse, + }; +}); +const getJobResponse = jest.fn(); +const getJobRequest = jest.fn().mockImplementation(() => { + return { + promise: getJobResponse, + }; +}); +const mockAmplify = { + listJobs: listJobsRequest, + startDeployment: startDeploymentRequest, + getJob: getJobRequest, +}; + +jest.mock('aws-sdk', () => { + return { + S3: jest.fn(() => mockS3), + Amplify: jest.fn(() => mockAmplify), + config: { logger: '' }, + }; +}); + +import { + onEvent, + isComplete, +} from '../../lib/asset-deployment-handler'; + +describe('handler', () => { + + let oldConsoleLog: any; + + beforeAll(() => { + oldConsoleLog = global.console.log; + global.console.log = jest.fn(); + }); + + afterAll(() => { + global.console.log = oldConsoleLog; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('onEvent CREATE success', async () => { + // GIVEN + listJobsResponse.mockImplementation(() => { + return { + jobSummaries: [], + }; + }); + getSignedUrlResponse.mockImplementation(() => { + return 'signedUrlValue'; + }); + startDeploymentResponse.mockImplementation(() => { + return { + jobSummary: { jobId: 'jobIdValue' }, + }; + }); + + // WHEN + const response = await onEvent({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Create', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + }); + + // THEN + expect(response).toEqual({ + AmplifyJobId: 'jobIdValue', + }); + + expect(listJobsRequest).toHaveBeenCalledWith({ + appId: 'appIdValue', + branchName: 'branchNameValue', + maxResults: 1, + }); + expect(listJobsResponse).toBeCalled(); + expect(getSignedUrlResponse).toHaveBeenCalledWith('getObject', { + Bucket: 's3BucketNameValue', + Key: 's3ObjectKeyValue', + }); + expect(startDeploymentRequest).toHaveBeenCalledWith({ + appId: 'appIdValue', + branchName: 'branchNameValue', + sourceUrl: 'signedUrlValue', + }); + expect(startDeploymentResponse).toBeCalled(); + }); + + it('onEvent CREATE pending job', async () => { + // GIVEN + listJobsResponse.mockImplementation(() => { + return { + jobSummaries: [{ status: 'PENDING' }], + }; + }); + + // WHEN + await expect(() => onEvent({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Create', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + })).rejects.toMatch('Amplify job already running. Aborting deployment.'); + + expect(listJobsRequest).toHaveBeenCalledWith({ + appId: 'appIdValue', + branchName: 'branchNameValue', + maxResults: 1, + }); + expect(listJobsResponse).toBeCalled(); + expect(getSignedUrlResponse).not.toHaveBeenCalled(); + expect(startDeploymentRequest).not.toHaveBeenCalled(); + expect(startDeploymentResponse).not.toHaveBeenCalled(); + }); + + it('isComplete CREATE success', async () => { + // GIVEN + getJobResponse.mockImplementation(() => { + return { + job: { summary: { status: 'SUCCEED' } }, + }; + }); + + // WHEN + const response = await isComplete({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Create', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + AmplifyJobId: 'amplifyJobIdValue', + }); + + // THEN + expect(response).toEqual({ + Data: { + JobId: 'amplifyJobIdValue', + Status: 'SUCCEED', + }, + IsComplete: true, + }); + + expect(getJobRequest).toHaveBeenCalledWith({ + appId: 'appIdValue', + branchName: 'branchNameValue', + jobId: 'amplifyJobIdValue', + }); + expect(getJobResponse).toBeCalled(); + }); + + it('isComplete CREATE pending', async () => { + // GIVEN + getJobResponse.mockImplementation(() => { + return { + job: { summary: { status: 'PENDING' } }, + }; + }); + + // WHEN + const response = await isComplete({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Create', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + AmplifyJobId: 'amplifyJobIdValue', + }); + + // THEN + expect(response).toEqual({ + IsComplete: false, + }); + + expect(getJobRequest).toHaveBeenCalledWith({ + appId: 'appIdValue', + branchName: 'branchNameValue', + jobId: 'amplifyJobIdValue', + }); + expect(getJobResponse).toBeCalled(); + }); + + it('isComplete CREATE failed', async () => { + // GIVEN + getJobResponse.mockImplementation(() => { + return { + job: { summary: { status: 'FAILED' } }, + }; + }); + + // WHEN + await expect(() => isComplete({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Create', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + AmplifyJobId: 'amplifyJobIdValue', + })).rejects.toThrow('Amplify job failed with status: FAILED'); + // THEN + expect(getJobRequest).toHaveBeenCalledWith({ + appId: 'appIdValue', + branchName: 'branchNameValue', + jobId: 'amplifyJobIdValue', + }); + expect(getJobResponse).toBeCalled(); + }); + + it('isComplete CREATE cancelled', async () => { + // GIVEN + getJobResponse.mockImplementation(() => { + return { + job: { summary: { status: 'CANCELLED' } }, + }; + }); + + // WHEN + await expect(() => isComplete({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Create', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + AmplifyJobId: 'amplifyJobIdValue', + })).rejects.toThrow('Amplify job failed with status: CANCELLED'); + + // THEN + expect(getJobRequest).toHaveBeenCalledWith({ + appId: 'appIdValue', + branchName: 'branchNameValue', + jobId: 'amplifyJobIdValue', + }); + expect(getJobResponse).toBeCalled(); + }); + + it('isComplete CREATE no JobId', async () => { + // GIVEN + getJobResponse.mockImplementation(() => { + return { + job: { summary: { status: 'PENDING' } }, + }; + }); + + // WHEN + await expect(() => isComplete({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Create', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + })).rejects.toThrow('Unable to determine Amplify job status without job id'); + + // THEN + expect(getJobRequest).not.toHaveBeenCalled(); + expect(getJobResponse).not.toHaveBeenCalled(); + }); + + it('onEvent UPDATE success', async () => { + // GIVEN + listJobsResponse.mockImplementation(() => { + return { + jobSummaries: [], + }; + }); + getSignedUrlResponse.mockImplementation(() => { + return 'signedUrlValue'; + }); + startDeploymentResponse.mockImplementation(() => { + return { + jobSummary: { jobId: 'jobIdValue' }, + }; + }); + + // WHEN + const response = await onEvent({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Update', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + OldResourceProperties: { ServiceToken: 'serviceTokenValue' }, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + PhysicalResourceId: 'physicalResourceIdValue', + }); + + // THEN + expect(response).toEqual({ + AmplifyJobId: 'jobIdValue', + }); + + expect(listJobsRequest).toHaveBeenCalledWith({ + appId: 'appIdValue', + branchName: 'branchNameValue', + maxResults: 1, + }); + expect(listJobsResponse).toBeCalled(); + expect(getSignedUrlResponse).toHaveBeenCalledWith('getObject', { + Bucket: 's3BucketNameValue', + Key: 's3ObjectKeyValue', + }); + expect(startDeploymentRequest).toHaveBeenCalledWith({ + appId: 'appIdValue', + branchName: 'branchNameValue', + sourceUrl: 'signedUrlValue', + }); + expect(startDeploymentResponse).toBeCalled(); + }); + + it('onEvent UPDATE pending job', async () => { + // GIVEN + listJobsResponse.mockImplementation(() => { + return { + jobSummaries: [{ status: 'PENDING' }], + }; + }); + + // WHEN + await expect(() => onEvent({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Update', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + OldResourceProperties: { ServiceToken: 'serviceTokenValue' }, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + PhysicalResourceId: 'physicalResourceIdValue', + })).rejects.toMatch('Amplify job already running. Aborting deployment.'); + + // THEN + expect(listJobsRequest).toHaveBeenCalledWith({ + appId: 'appIdValue', + branchName: 'branchNameValue', + maxResults: 1, + }); + expect(listJobsResponse).toBeCalled(); + expect(getSignedUrlResponse).not.toHaveBeenCalled(); + expect(startDeploymentRequest).not.toHaveBeenCalled(); + expect(startDeploymentResponse).not.toHaveBeenCalled(); + }); + + it('isComplete UPDATE success', async () => { + // GIVEN + getJobResponse.mockImplementation(() => { + return { + job: { summary: { status: 'SUCCEED' } }, + }; + }); + + // WHEN + const response = await isComplete({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Update', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + OldResourceProperties: {}, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + AmplifyJobId: 'amplifyJobIdValue', + PhysicalResourceId: 'physicalResourceIdValue', + }); + + // THEN + expect(response).toEqual({ + Data: { + JobId: 'amplifyJobIdValue', + Status: 'SUCCEED', + }, + IsComplete: true, + }); + + expect(getJobRequest).toHaveBeenCalledWith({ + appId: 'appIdValue', + branchName: 'branchNameValue', + jobId: 'amplifyJobIdValue', + }); + expect(getJobResponse).toBeCalled(); + }); + + it('isComplete UPDATE pending', async () => { + // GIVEN + getJobResponse.mockImplementation(() => { + return { + job: { summary: { status: 'PENDING' } }, + }; + }); + + // WHEN + const response = await isComplete({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Update', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + OldResourceProperties: {}, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + AmplifyJobId: 'amplifyJobIdValue', + PhysicalResourceId: 'physicalResourceIdValue', + }); + + // THEN + expect(response).toEqual({ + IsComplete: false, + }); + + expect(getJobRequest).toHaveBeenCalledWith({ + appId: 'appIdValue', + branchName: 'branchNameValue', + jobId: 'amplifyJobIdValue', + }); + expect(getJobResponse).toBeCalled(); + }); + + it('isComplete UPDATE failed', async () => { + // GIVEN + getJobResponse.mockImplementation(() => { + return { + job: { summary: { status: 'FAILED' } }, + }; + }); + + // WHEN + await expect(() => isComplete({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Update', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + OldResourceProperties: {}, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + AmplifyJobId: 'amplifyJobIdValue', + PhysicalResourceId: 'physicalResourceIdValue', + })).rejects.toThrow('Amplify job failed with status: FAILED'); + // THEN + expect(getJobRequest).toHaveBeenCalledWith({ + appId: 'appIdValue', + branchName: 'branchNameValue', + jobId: 'amplifyJobIdValue', + }); + expect(getJobResponse).toBeCalled(); + }); + + it('isComplete UPDATE cancelled', async () => { + // GIVEN + getJobResponse.mockImplementation(() => { + return { + job: { summary: { status: 'CANCELLED' } }, + }; + }); + + // WHEN + await expect(() => isComplete({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Update', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + OldResourceProperties: {}, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + AmplifyJobId: 'amplifyJobIdValue', + PhysicalResourceId: 'physicalResourceIdValue', + })).rejects.toThrow('Amplify job failed with status: CANCELLED'); + + // THEN + expect(getJobRequest).toHaveBeenCalledWith({ + appId: 'appIdValue', + branchName: 'branchNameValue', + jobId: 'amplifyJobIdValue', + }); + expect(getJobResponse).toBeCalled(); + }); + + it('isComplete UPDATE no JobId', async () => { + // GIVEN + getJobResponse.mockImplementation(() => { + return { + job: { summary: { status: 'PENDING' } }, + }; + }); + + // WHEN + await expect(() => isComplete({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Update', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + OldResourceProperties: {}, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + PhysicalResourceId: 'physicalResourceIdValue', + })).rejects.toThrow('Unable to determine Amplify job status without job id'); + + // THEN + expect(getJobRequest).not.toHaveBeenCalled(); + expect(getJobResponse).not.toHaveBeenCalled(); + }); + + it('onEvent DELETE success', async () => { + // GIVEN + + // WHEN + await expect(() => onEvent({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Delete', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + PhysicalResourceId: 'physicalResourceIdValue', + })).resolves; + }); + + it('isComplete DELETE success', async () => { + // GIVEN + + // WHEN + const response = await isComplete({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Delete', + ResourceType: 'Custom::AmplifyAssetDeployment', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + PhysicalResourceId: 'physicalResourceIdValue', + }); + + // THEN + expect(response).toEqual({ + IsComplete: true, + }); + }); + + it('onEvent unsupported resource type', async () => { + // GIVEN + + // WHEN + await expect(() => onEvent({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Update', + ResourceType: 'Custom::BadResourceType', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + OldResourceProperties: {}, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + PhysicalResourceId: 'physicalResourceIdValue', + })).rejects.toThrow('Unsupported resource type "Custom::BadResourceType"'); + + + // THEN + expect(getJobRequest).not.toHaveBeenCalled(); + expect(getJobResponse).not.toHaveBeenCalled(); + }); + + it('isComplete unsupported resource type', async () => { + // GIVEN + + // WHEN + await expect(() => isComplete({ + ServiceToken: 'serviceTokenValue', + RequestType: 'Update', + ResourceType: 'Custom::BadResourceType', + ResourceProperties: { + ServiceToken: 'serviceTokenValue', + AppId: 'appIdValue', + BranchName: 'branchNameValue', + S3BucketName: 's3BucketNameValue', + S3ObjectKey: 's3ObjectKeyValue', + }, + OldResourceProperties: {}, + ResponseURL: 'responseUrlValue', + StackId: 'stackIdValue', + RequestId: 'requestIdValue', + LogicalResourceId: 'logicalResourceIdValue', + PhysicalResourceId: 'physicalResourceIdValue', + })).rejects.toThrow('Unsupported resource type "Custom::BadResourceType"'); + + // THEN + expect(getJobRequest).not.toHaveBeenCalled(); + expect(getJobResponse).not.toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-amplify/test/branch.test.ts b/packages/@aws-cdk/aws-amplify/test/branch.test.ts index 1638157eb4b14..ba8e517205beb 100644 --- a/packages/@aws-cdk/aws-amplify/test/branch.test.ts +++ b/packages/@aws-cdk/aws-amplify/test/branch.test.ts @@ -1,4 +1,6 @@ +import * as path from 'path'; import { Template } from '@aws-cdk/assertions'; +import { Asset } from '@aws-cdk/aws-s3-assets'; import { SecretValue, Stack } from '@aws-cdk/core'; import * as amplify from '../lib'; @@ -99,3 +101,64 @@ test('with env vars', () => { ], }); }); + +test('with asset deployment', () => { + // WHEN + const asset = new Asset(app, 'SampleAsset', { + path: path.join(__dirname, './test-asset'), + }); + app.addBranch('dev', { asset }); + + // THEN + Template.fromStack(stack).hasResourceProperties('Custom::AmplifyAssetDeployment', { + ServiceToken: { + 'Fn::GetAtt': [ + 'comamazonawscdkcustomresourcesamplifyassetdeploymentproviderNestedStackcomamazonawscdkcustomresourcesamplifyassetdeploymentproviderNestedStackResource89BDFEB2', + 'Outputs.comamazonawscdkcustomresourcesamplifyassetdeploymentprovideramplifyassetdeploymenthandlerproviderframeworkonEventA449D9A9Arn', + ], + }, + AppId: { + 'Fn::GetAtt': [ + 'AppF1B96344', + 'AppId', + ], + }, + BranchName: 'dev', + S3ObjectKey: { + 'Fn::Join': [ + '', + [ + { + 'Fn::Select': [ + 0, + { + 'Fn::Split': [ + '||', + { + Ref: 'AssetParameters8c89eadc6be22019c81ed6b9c7d9929ae10de55679fd8e0e9fd4c00f8edc1cdaS3VersionKey70C0B407', + }, + ], + }, + ], + }, + { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '||', + { + Ref: 'AssetParameters8c89eadc6be22019c81ed6b9c7d9929ae10de55679fd8e0e9fd4c00f8edc1cdaS3VersionKey70C0B407', + }, + ], + }, + ], + }, + ], + ], + }, + S3BucketName: { + Ref: 'AssetParameters8c89eadc6be22019c81ed6b9c7d9929ae10de55679fd8e0e9fd4c00f8edc1cdaS3Bucket83484C89', + }, + }); +}); diff --git a/packages/@aws-cdk/aws-amplify/test/integ.app-asset-deployment.expected.json b/packages/@aws-cdk/aws-amplify/test/integ.app-asset-deployment.expected.json new file mode 100644 index 0000000000000..47b29583b6a21 --- /dev/null +++ b/packages/@aws-cdk/aws-amplify/test/integ.app-asset-deployment.expected.json @@ -0,0 +1,223 @@ +{ + "Parameters": { + "AssetParameters76c74dffba7c3eb9a040dc95633eac403472969bf8a18831ac1cf243971c5bf7S3Bucket3C55BA0F": { + "Type": "String", + "Description": "S3 bucket for asset \"76c74dffba7c3eb9a040dc95633eac403472969bf8a18831ac1cf243971c5bf7\"" + }, + "AssetParameters76c74dffba7c3eb9a040dc95633eac403472969bf8a18831ac1cf243971c5bf7S3VersionKeyE1E2D7D6": { + "Type": "String", + "Description": "S3 key for asset version \"76c74dffba7c3eb9a040dc95633eac403472969bf8a18831ac1cf243971c5bf7\"" + }, + "AssetParameters76c74dffba7c3eb9a040dc95633eac403472969bf8a18831ac1cf243971c5bf7ArtifactHashB1665559": { + "Type": "String", + "Description": "Artifact hash for asset \"76c74dffba7c3eb9a040dc95633eac403472969bf8a18831ac1cf243971c5bf7\"" + }, + "AssetParametersff9527128e3cc60cee11deb3d533504348f62709c853288178d757494fd92c56S3Bucket7A871D89": { + "Type": "String", + "Description": "S3 bucket for asset \"ff9527128e3cc60cee11deb3d533504348f62709c853288178d757494fd92c56\"" + }, + "AssetParametersff9527128e3cc60cee11deb3d533504348f62709c853288178d757494fd92c56S3VersionKeyAACF81DD": { + "Type": "String", + "Description": "S3 key for asset version \"ff9527128e3cc60cee11deb3d533504348f62709c853288178d757494fd92c56\"" + }, + "AssetParametersff9527128e3cc60cee11deb3d533504348f62709c853288178d757494fd92c56ArtifactHash2A4E644A": { + "Type": "String", + "Description": "Artifact hash for asset \"ff9527128e3cc60cee11deb3d533504348f62709c853288178d757494fd92c56\"" + }, + "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1": { + "Type": "String", + "Description": "S3 bucket for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" + }, + "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F": { + "Type": "String", + "Description": "S3 key for asset version \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" + }, + "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1ArtifactHashA521A16F": { + "Type": "String", + "Description": "Artifact hash for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" + }, + "AssetParametersa1ec2b3c34d7ba5b1816474781916bb1c8a8086a266e6d7cf88a0720b114d2ddS3Bucket456FC783": { + "Type": "String", + "Description": "S3 bucket for asset \"a1ec2b3c34d7ba5b1816474781916bb1c8a8086a266e6d7cf88a0720b114d2dd\"" + }, + "AssetParametersa1ec2b3c34d7ba5b1816474781916bb1c8a8086a266e6d7cf88a0720b114d2ddS3VersionKey4A933266": { + "Type": "String", + "Description": "S3 key for asset version \"a1ec2b3c34d7ba5b1816474781916bb1c8a8086a266e6d7cf88a0720b114d2dd\"" + }, + "AssetParametersa1ec2b3c34d7ba5b1816474781916bb1c8a8086a266e6d7cf88a0720b114d2ddArtifactHash7857C55E": { + "Type": "String", + "Description": "Artifact hash for asset \"a1ec2b3c34d7ba5b1816474781916bb1c8a8086a266e6d7cf88a0720b114d2dd\"" + } + }, + "Resources": { + "AppRole1AF9B530": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "amplify.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "AppF1B96344": { + "Type": "AWS::Amplify::App", + "Properties": { + "Name": "App", + "BasicAuthConfig": { + "EnableBasicAuth": false + }, + "IAMServiceRole": { + "Fn::GetAtt": [ + "AppRole1AF9B530", + "Arn" + ] + } + } + }, + "AppmainF505BAED": { + "Type": "AWS::Amplify::Branch", + "Properties": { + "AppId": { + "Fn::GetAtt": [ + "AppF1B96344", + "AppId" + ] + }, + "BranchName": "main", + "EnableAutoBuild": true, + "EnablePullRequestPreview": true + } + }, + "AppmainDeploymentResource442DE93D": { + "Type": "Custom::AmplifyAssetDeployment", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "comamazonawscdkcustomresourcesamplifyassetdeploymentproviderNestedStackcomamazonawscdkcustomresourcesamplifyassetdeploymentproviderNestedStackResource89BDFEB2", + "Outputs.cdkamplifyappassetdeploymentcomamazonawscdkcustomresourcesamplifyassetdeploymentprovideramplifyassetdeploymenthandlerproviderframeworkonEventC3C43E44Arn" + ] + }, + "AppId": { + "Fn::GetAtt": [ + "AppF1B96344", + "AppId" + ] + }, + "BranchName": "main", + "S3ObjectKey": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters76c74dffba7c3eb9a040dc95633eac403472969bf8a18831ac1cf243971c5bf7S3VersionKeyE1E2D7D6" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters76c74dffba7c3eb9a040dc95633eac403472969bf8a18831ac1cf243971c5bf7S3VersionKeyE1E2D7D6" + } + ] + } + ] + } + ] + ] + }, + "S3BucketName": { + "Ref": "AssetParameters76c74dffba7c3eb9a040dc95633eac403472969bf8a18831ac1cf243971c5bf7S3Bucket3C55BA0F" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "comamazonawscdkcustomresourcesamplifyassetdeploymentproviderNestedStackcomamazonawscdkcustomresourcesamplifyassetdeploymentproviderNestedStackResource89BDFEB2": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParametersa1ec2b3c34d7ba5b1816474781916bb1c8a8086a266e6d7cf88a0720b114d2ddS3Bucket456FC783" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersa1ec2b3c34d7ba5b1816474781916bb1c8a8086a266e6d7cf88a0720b114d2ddS3VersionKey4A933266" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersa1ec2b3c34d7ba5b1816474781916bb1c8a8086a266e6d7cf88a0720b114d2ddS3VersionKey4A933266" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetocdkamplifyappassetdeploymentAssetParametersff9527128e3cc60cee11deb3d533504348f62709c853288178d757494fd92c56S3BucketA0EDA7B5Ref": { + "Ref": "AssetParametersff9527128e3cc60cee11deb3d533504348f62709c853288178d757494fd92c56S3Bucket7A871D89" + }, + "referencetocdkamplifyappassetdeploymentAssetParametersff9527128e3cc60cee11deb3d533504348f62709c853288178d757494fd92c56S3VersionKeyD32C918ARef": { + "Ref": "AssetParametersff9527128e3cc60cee11deb3d533504348f62709c853288178d757494fd92c56S3VersionKeyAACF81DD" + }, + "referencetocdkamplifyappassetdeploymentAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketA5B3B03BRef": { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1" + }, + "referencetocdkamplifyappassetdeploymentAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKey61CE3542Ref": { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-amplify/test/integ.app-asset-deployment.ts b/packages/@aws-cdk/aws-amplify/test/integ.app-asset-deployment.ts new file mode 100644 index 0000000000000..976b6c441af47 --- /dev/null +++ b/packages/@aws-cdk/aws-amplify/test/integ.app-asset-deployment.ts @@ -0,0 +1,23 @@ +/// !cdk-integ pragma:ignore-assets +import * as path from 'path'; +import { Asset } from '@aws-cdk/aws-s3-assets'; +import { App, Stack, StackProps } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as amplify from '../lib'; + +class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const asset = new Asset(this, 'SampleAsset', { + path: path.join(__dirname, './test-asset'), + }); + + const amplifyApp = new amplify.App(this, 'App', {}); + amplifyApp.addBranch('main', { asset }); + } +} + +const app = new App(); +new TestStack(app, 'cdk-amplify-app-asset-deployment'); +app.synth(); diff --git a/packages/@aws-cdk/aws-amplify/test/test-asset/index.html b/packages/@aws-cdk/aws-amplify/test/test-asset/index.html new file mode 100644 index 0000000000000..c6b3e17625253 --- /dev/null +++ b/packages/@aws-cdk/aws-amplify/test/test-asset/index.html @@ -0,0 +1 @@ +Hello world! I am deployed on AWS Amplify using the addAssetDeployment method! \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts b/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts index c9f082121bff6..efd4b5584dbfe 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts @@ -48,6 +48,16 @@ export interface DomainNameOptions { * @default - mTLS is not configured. */ readonly mtls?: MTLSConfig; + + /** + * The base path name that callers of the API must provide in the URL after + * the domain name (e.g. `example.com/base-path`). If you specify this + * property, it can't be an empty string. + * + * @default - map requests from the domain root (e.g. `example.com`). If this + * is undefined, no additional mappings will be allowed on this domain name. + */ + readonly basePath?: string; } export interface DomainNameProps extends DomainNameOptions { @@ -104,6 +114,7 @@ export class DomainName extends Resource implements IDomainName { public readonly domainName: string; public readonly domainNameAliasDomainName: string; public readonly domainNameAliasHostedZoneId: string; + private readonly basePaths = new Set(); constructor(scope: Construct, id: string, props: DomainNameProps) { super(scope, id); @@ -136,7 +147,9 @@ export class DomainName extends Resource implements IDomainName { : resource.attrRegionalHostedZoneId; if (props.mapping) { - this.addBasePathMapping(props.mapping); + this.addBasePathMapping(props.mapping, { + basePath: props.basePath, + }); } } @@ -146,6 +159,10 @@ export class DomainName extends Resource implements IDomainName { * @param options Options for mapping to base path with or without a stage */ public addBasePathMapping(targetApi: IRestApi, options: BasePathMappingOptions = { }) { + if (this.basePaths.has(undefined)) { + throw new Error('This domain name already has an empty base path. No additional base paths are allowed.'); + } + this.basePaths.add(options.basePath); const basePath = options.basePath || '/'; const id = `Map:${basePath}=>${Names.nodeUniqueId(targetApi.node)}`; return new BasePathMapping(this, id, { diff --git a/packages/@aws-cdk/aws-apigateway/test/domains.test.ts b/packages/@aws-cdk/aws-apigateway/test/domains.test.ts index 7b8817df48853..31c553ad9c973 100644 --- a/packages/@aws-cdk/aws-apigateway/test/domains.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/domains.test.ts @@ -259,6 +259,79 @@ describe('domains', () => { }); }); + test('a base path can be defined when adding a domain name', () => { + // GIVEN + const domainName = 'my.domain.com'; + const basePath = 'users'; + const stack = new Stack(); + const certificate = new acm.Certificate(stack, 'cert', { domainName: 'my.domain.com' }); + + // WHEN + const api = new apigw.RestApi(stack, 'api', {}); + + api.root.addMethod('GET'); + + api.addDomainName('domainId', { domainName, certificate, basePath }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGateway::BasePathMapping', { + 'BasePath': 'users', + 'RestApiId': { + 'Ref': 'apiC8550315', + }, + }); + }); + + test('additional base paths can added if addDomainName was called with a non-empty base path', () => { + // GIVEN + const domainName = 'my.domain.com'; + const basePath = 'users'; + const stack = new Stack(); + const certificate = new acm.Certificate(stack, 'cert', { domainName: 'my.domain.com' }); + + // WHEN + const api = new apigw.RestApi(stack, 'api', {}); + + api.root.addMethod('GET'); + + const dn = api.addDomainName('domainId', { domainName, certificate, basePath }); + dn.addBasePathMapping(api, { + basePath: 'books', + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGateway::BasePathMapping', { + 'BasePath': 'users', + 'RestApiId': { + 'Ref': 'apiC8550315', + }, + }); + expect(stack).toHaveResource('AWS::ApiGateway::BasePathMapping', { + 'BasePath': 'books', + 'RestApiId': { + 'Ref': 'apiC8550315', + }, + }); + }); + + test('no additional base paths can added if addDomainName was called without a base path', () => { + // GIVEN + const domainName = 'my.domain.com'; + const stack = new Stack(); + const certificate = new acm.Certificate(stack, 'cert', { domainName: 'my.domain.com' }); + + // WHEN + const api = new apigw.RestApi(stack, 'api', {}); + + api.root.addMethod('GET'); + + const dn = api.addDomainName('domainId', { domainName, certificate }); + + expect(() => dn.addBasePathMapping(api, { basePath: 'books' })) + .toThrow(/No additional base paths are allowed/); + }); + + test('domain name cannot contain uppercase letters', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-apigateway/test/integrations/lambda.test.ts b/packages/@aws-cdk/aws-apigateway/test/integrations/lambda.test.ts index 76ef808c4999e..7c5b9e60e85d6 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integrations/lambda.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integrations/lambda.test.ts @@ -9,7 +9,7 @@ describe('lambda', () => { const stack = new cdk.Stack(); const api = new apigateway.RestApi(stack, 'my-api'); const handler = new lambda.Function(stack, 'Handler', { - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, handler: 'boom', code: lambda.Code.fromInline('foo'), }); diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md index d1aebb7477b82..e2d9f13198711 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md @@ -22,9 +22,11 @@ - [HTTP APIs](#http-apis) - [Default Authorization](#default-authorization) - [Route Authorization](#route-authorization) -- [JWT Authorizers](#jwt-authorizers) - - [User Pool Authorizer](#user-pool-authorizer) -- [Lambda Authorizers](#lambda-authorizers) + - [JWT Authorizers](#jwt-authorizers) + - [User Pool Authorizer](#user-pool-authorizer) + - [Lambda Authorizers](#lambda-authorizers) +- [WebSocket APIs](#websocket-apis) + - [Lambda Authorizer](#lambda-authorizer) ## Introduction @@ -37,7 +39,7 @@ API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-acces Access control for Http Apis is managed by restricting which routes can be invoked via. -Authorizers, and scopes can either be applied to the api, or specifically for each route. +Authorizers and scopes can either be applied to the api, or specifically for each route. ### Default Authorization @@ -110,7 +112,7 @@ api.addRoutes({ }); ``` -## JWT Authorizers +### JWT Authorizers JWT authorizers allow the use of JSON Web Tokens (JWTs) as part of [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) and [OAuth 2.0](https://oauth.net/2/) frameworks to allow and restrict clients from accessing HTTP APIs. @@ -144,7 +146,7 @@ api.addRoutes({ }); ``` -### User Pool Authorizer +#### User Pool Authorizer User Pool Authorizer is a type of JWT Authorizer that uses a Cognito user pool and app client to control who can access your Api. After a successful authorization from the app client, the generated access token will be used as the JWT. @@ -170,7 +172,7 @@ api.addRoutes({ }); ``` -## Lambda Authorizers +### Lambda Authorizers Lambda authorizers use a Lambda function to control access to your HTTP API. When a client calls your API, API Gateway invokes your Lambda function and uses the response to determine whether the client can access your API. @@ -196,3 +198,36 @@ api.addRoutes({ authorizer, }); ``` + +## WebSocket APIs + +You can set an authorizer to your WebSocket API's `$connect` route to control access to your API. + +### Lambda Authorizer + +Lambda authorizers use a Lambda function to control access to your WebSocket API. When a client connects to your API, API Gateway invokes your Lambda function and uses the response to determine whether the client can access your API. + +```ts +import { WebSocketLambdaAuthorizer } from '@aws-cdk/aws-apigatewayv2-authorizers'; +import { WebSocketLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +// This function handles your auth logic +declare const authHandler: lambda.Function; + +// This function handles your WebSocket requests +declare const handler: lambda.Function; + +const authorizer = new WebSocketLambdaAuthorizer('Authorizer', authHandler); + +const integration = new WebSocketLambdaIntegration( + 'Integration', + handler, +); + +new apigwv2.WebSocketApi(this, 'WebSocketApi', { + connectRouteOptions: { + integration, + authorizer, + }, +}); +``` diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/index.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/index.ts index c202386ae710e..fd16aff655ff2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/index.ts @@ -1 +1,2 @@ export * from './http'; +export * from './websocket'; diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/index.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/index.ts new file mode 100644 index 0000000000000..04a64da0c7540 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/index.ts @@ -0,0 +1 @@ +export * from './lambda'; diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/lambda.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/lambda.ts new file mode 100644 index 0000000000000..2e60cbdd7b547 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/websocket/lambda.ts @@ -0,0 +1,89 @@ +import { + WebSocketAuthorizer, + WebSocketAuthorizerType, + WebSocketRouteAuthorizerBindOptions, + WebSocketRouteAuthorizerConfig, + IWebSocketRouteAuthorizer, + IWebSocketApi, +} from '@aws-cdk/aws-apigatewayv2'; +import { ServicePrincipal } from '@aws-cdk/aws-iam'; +import { IFunction } from '@aws-cdk/aws-lambda'; +import { Stack, Names } from '@aws-cdk/core'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + +/** + * Properties to initialize WebSocketTokenAuthorizer. + */ +export interface WebSocketLambdaAuthorizerProps { + + /** + * The name of the authorizer + * @default - same value as `id` passed in the constructor. + */ + readonly authorizerName?: string; + + /** + * The identity source for which authorization is requested. + * + * @default ['$request.header.Authorization'] + */ + readonly identitySource?: string[]; +} + +/** + * Authorize WebSocket Api routes via a lambda function + */ +export class WebSocketLambdaAuthorizer implements IWebSocketRouteAuthorizer { + private authorizer?: WebSocketAuthorizer; + private webSocketApi?: IWebSocketApi; + + constructor( + private readonly id: string, + private readonly handler: IFunction, + private readonly props: WebSocketLambdaAuthorizerProps = {}) { + } + + public bind(options: WebSocketRouteAuthorizerBindOptions): WebSocketRouteAuthorizerConfig { + if (this.webSocketApi && (this.webSocketApi.apiId !== options.route.webSocketApi.apiId)) { + throw new Error('Cannot attach the same authorizer to multiple Apis'); + } + + if (!this.authorizer) { + this.webSocketApi = options.route.webSocketApi; + this.authorizer = new WebSocketAuthorizer(options.scope, this.id, { + webSocketApi: options.route.webSocketApi, + identitySource: this.props.identitySource ?? [ + '$request.header.Authorization', + ], + type: WebSocketAuthorizerType.LAMBDA, + authorizerName: this.props.authorizerName ?? this.id, + authorizerUri: lambdaAuthorizerArn(this.handler), + }); + + this.handler.addPermission(`${Names.nodeUniqueId(this.authorizer.node)}-Permission`, { + scope: options.scope as CoreConstruct, + principal: new ServicePrincipal('apigateway.amazonaws.com'), + sourceArn: Stack.of(options.route).formatArn({ + service: 'execute-api', + resource: options.route.webSocketApi.apiId, + resourceName: `authorizers/${this.authorizer.authorizerId}`, + }), + }); + } + + return { + authorizerId: this.authorizer.authorizerId, + authorizationType: 'CUSTOM', + }; + } +} + +/** + * constructs the authorizerURIArn. + */ +function lambdaAuthorizerArn(handler: IFunction) { + return `arn:${Stack.of(handler).partition}:apigateway:${Stack.of(handler).region}:lambda:path/2015-03-31/functions/${handler.functionArn}/invocations`; +} diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/lambda.test.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/lambda.test.ts new file mode 100644 index 0000000000000..c171247801911 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/websocket/lambda.test.ts @@ -0,0 +1,46 @@ +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 { WebSocketLambdaAuthorizer } from '../../lib'; + +describe('WebSocketLambdaAuthorizer', () => { + test('default', () => { + // GIVEN + const stack = new Stack(); + + const handler = new Function(stack, 'auth-function', { + runtime: Runtime.NODEJS_12_X, + code: Code.fromInline('exports.handler = () => {return true}'), + handler: 'index.handler', + }); + const integration = new WebSocketLambdaIntegration( + 'Integration', + handler, + ); + + const authorizer = new WebSocketLambdaAuthorizer('default-authorizer', handler); + + // WHEN + new WebSocketApi(stack, 'WebSocketApi', { + connectRouteOptions: { + integration, + authorizer, + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Authorizer', { + Name: 'default-authorizer', + AuthorizerType: 'REQUEST', + IdentitySource: [ + '$request.header.Authorization', + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Route', { + AuthorizationType: 'CUSTOM', + }); + }); +}); diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/websocket/integ.lambda.expected.json b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/websocket/integ.lambda.expected.json index 6b344f8f1688f..a26b35f5ccfb9 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/websocket/integ.lambda.expected.json +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/websocket/integ.lambda.expected.json @@ -284,6 +284,7 @@ "Ref": "mywsapi32E6CE11" }, "RouteKey": "$connect", + "AuthorizationType": "NONE", "Target": { "Fn::Join": [ "", @@ -373,6 +374,7 @@ "Ref": "mywsapi32E6CE11" }, "RouteKey": "$disconnect", + "AuthorizationType": "NONE", "Target": { "Fn::Join": [ "", @@ -462,6 +464,7 @@ "Ref": "mywsapi32E6CE11" }, "RouteKey": "$default", + "AuthorizationType": "NONE", "Target": { "Fn::Join": [ "", @@ -551,6 +554,7 @@ "Ref": "mywsapi32E6CE11" }, "RouteKey": "sendmessage", + "AuthorizationType": "NONE", "Target": { "Fn::Join": [ "", diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 6c0697287c08d..a09d015dc87a2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -35,12 +35,13 @@ Higher level constructs for Websocket APIs | ![Experimental](https://img.shields - [Publishing HTTP APIs](#publishing-http-apis) - [Custom Domain](#custom-domain) - [Mutual TLS](#mutual-tls-mtls) - - [Managing access](#managing-access) + - [Managing access to HTTP APIs](#managing-access-to-http-apis) - [Metrics](#metrics) - [VPC Link](#vpc-link) - [Private Integration](#private-integration) - [WebSocket API](#websocket-api) - [Manage Connections Permission](#manage-connections-permission) + - [Managing access to WebSocket APIs](#managing-access-to-websocket-apis) ## Introduction @@ -254,7 +255,7 @@ declare const apiDemo: apigwv2.HttpApi; const demoDomainUrl = apiDemo.defaultStage?.domainUrl; // returns "https://example.com/demo" ``` -## Mutual TLS (mTLS) +### Mutual TLS (mTLS) Mutual TLS can be configured to limit access to your API based by using client certificates instead of (or as an extension of) using authorization headers. @@ -277,7 +278,7 @@ new DomainName(stack, 'DomainName', { Instructions for configuring your trust store can be found [here](https://aws.amazon.com/blogs/compute/introducing-mutual-tls-authentication-for-amazon-api-gateway/) -### Managing access +### Managing access to HTTP APIs API Gateway supports multiple mechanisms for [controlling and managing access to your HTTP API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-access-control.html) through authorizers. @@ -419,3 +420,9 @@ stage.grantManageConnections(lambda); // for all the stages permission webSocketApi.grantManageConnections(lambda); ``` + +### Managing access to WebSocket APIs + +API Gateway supports multiple mechanisms for [controlling and managing access to a WebSocket API](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-control-access.html) through authorizers. + +These authorizers can be found in the [APIGatewayV2-Authorizers](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-apigatewayv2-authorizers-readme.html) constructs library. diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/authorizer.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/authorizer.ts index 08936ecf36d8f..d4a7cf21b4ac4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/authorizer.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/authorizer.ts @@ -166,7 +166,7 @@ export class HttpAuthorizer extends Resource implements IHttpAuthorizer { } /** - * This check is required because Cloudformation will fail stack creation is this property + * This check is required because Cloudformation will fail stack creation if this property * is set for the JWT authorizer. AuthorizerPayloadFormatVersion can only be set for REQUEST authorizer */ if (props.type === HttpAuthorizerType.LAMBDA && typeof authorizerPayloadFormatVersion === 'undefined') { diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/authorizer.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/authorizer.ts new file mode 100644 index 0000000000000..5abb420c80bad --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/authorizer.ts @@ -0,0 +1,176 @@ +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnAuthorizer } from '../apigatewayv2.generated'; + +import { IAuthorizer } from '../common'; +import { IWebSocketApi } from './api'; +import { IWebSocketRoute } from './route'; + +/** + * Supported Authorizer types + */ +export enum WebSocketAuthorizerType { + /** Lambda Authorizer */ + LAMBDA = 'REQUEST', +} + +/** + * Properties to initialize an instance of `WebSocketAuthorizer`. + */ +export interface WebSocketAuthorizerProps { + /** + * Name of the authorizer + * @default - id of the WebSocketAuthorizer construct. + */ + readonly authorizerName?: string + + /** + * WebSocket Api to attach the authorizer to + */ + readonly webSocketApi: IWebSocketApi + + /** + * The type of authorizer + */ + readonly type: WebSocketAuthorizerType; + + /** + * The identity source for which authorization is requested. + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-authorizer.html#cfn-apigatewayv2-authorizer-identitysource + */ + readonly identitySource: string[]; + + /** + * The authorizer's Uniform Resource Identifier (URI). + * + * For REQUEST authorizers, this must be a well-formed Lambda function URI. + * + * @default - required for Request authorizer types + */ + readonly authorizerUri?: string; +} + +/** + * An authorizer for WebSocket APIs + */ +export interface IWebSocketAuthorizer extends IAuthorizer { +} + +/** + * Reference to an WebSocket authorizer + */ +export interface WebSocketAuthorizerAttributes { + /** + * Id of the Authorizer + */ + readonly authorizerId: string + + /** + * Type of authorizer + * + * Possible values are: + * - CUSTOM - Lambda Authorizer + * - NONE - No Authorization + */ + readonly authorizerType: string +} + +/** + * An authorizer for WebSocket Apis + * @resource AWS::ApiGatewayV2::Authorizer + */ +export class WebSocketAuthorizer extends Resource implements IWebSocketAuthorizer { + /** + * Import an existing WebSocket Authorizer into this CDK app. + */ + public static fromWebSocketAuthorizerAttributes(scope: Construct, id: string, attrs: WebSocketAuthorizerAttributes): IWebSocketRouteAuthorizer { + class Import extends Resource implements IWebSocketRouteAuthorizer { + public readonly authorizerId = attrs.authorizerId; + public readonly authorizerType = attrs.authorizerType; + + public bind(): WebSocketRouteAuthorizerConfig { + return { + authorizerId: attrs.authorizerId, + authorizationType: attrs.authorizerType, + }; + } + } + return new Import(scope, id); + } + + public readonly authorizerId: string; + + constructor(scope: Construct, id: string, props: WebSocketAuthorizerProps) { + super(scope, id); + + if (props.type === WebSocketAuthorizerType.LAMBDA && !props.authorizerUri) { + throw new Error('authorizerUri is mandatory for Lambda authorizers'); + } + + const resource = new CfnAuthorizer(this, 'Resource', { + name: props.authorizerName ?? id, + apiId: props.webSocketApi.apiId, + authorizerType: props.type, + identitySource: props.identitySource, + authorizerUri: props.authorizerUri, + }); + + this.authorizerId = resource.ref; + } +} + +/** + * Input to the bind() operation, that binds an authorizer to a route. + */ +export interface WebSocketRouteAuthorizerBindOptions { + /** + * The route to which the authorizer is being bound. + */ + readonly route: IWebSocketRoute; + /** + * The scope for any constructs created as part of the bind. + */ + readonly scope: Construct; +} + +/** + * Results of binding an authorizer to an WebSocket route. + */ +export interface WebSocketRouteAuthorizerConfig { + /** + * The authorizer id + * + * @default - No authorizer id (useful for AWS_IAM route authorizer) + */ + readonly authorizerId?: string; + + /** + * The type of authorization + * + * Possible values are: + * - CUSTOM - Lambda Authorizer + * - NONE - No Authorization + */ + readonly authorizationType: string; +} + +/** + * An authorizer that can attach to an WebSocket Route. + */ +export interface IWebSocketRouteAuthorizer { + /** + * Bind this authorizer to a specified WebSocket route. + */ + bind(options: WebSocketRouteAuthorizerBindOptions): WebSocketRouteAuthorizerConfig; +} + +/** + * Explicitly configure no authorizers on specific WebSocket API routes. + */ +export class WebSocketNoneAuthorizer implements IWebSocketRouteAuthorizer { + public bind(_: WebSocketRouteAuthorizerBindOptions): WebSocketRouteAuthorizerConfig { + return { + authorizationType: 'NONE', + }; + } +} diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/index.ts index b0ce6a8a91419..4fe65943cbb8b 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/index.ts @@ -2,3 +2,4 @@ export * from './api'; export * from './route'; export * from './stage'; export * from './integration'; +export * from './authorizer'; diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts index 38316c6449c13..0aaa93587015c 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts @@ -3,6 +3,7 @@ import { Construct } from 'constructs'; import { CfnRoute } from '../apigatewayv2.generated'; import { IRoute } from '../common'; import { IWebSocketApi } from './api'; +import { IWebSocketRouteAuthorizer, WebSocketNoneAuthorizer } from './authorizer'; import { WebSocketRouteIntegration } from './integration'; /** @@ -29,15 +30,21 @@ export interface WebSocketRouteOptions { * The integration to be configured on this route. */ readonly integration: WebSocketRouteIntegration; -} + /** + * The authorize to this route. You can only set authorizer to a $connect route. + * + * @default - No Authorizer + */ + readonly authorizer?: IWebSocketRouteAuthorizer; +} /** * Properties to initialize a new Route */ export interface WebSocketRouteProps extends WebSocketRouteOptions { /** - * the API the route is associated with + * The API the route is associated with. */ readonly webSocketApi: IWebSocketApi; @@ -64,6 +71,10 @@ export class WebSocketRoute extends Resource implements IWebSocketRoute { constructor(scope: Construct, id: string, props: WebSocketRouteProps) { super(scope, id); + if (props.routeKey != '$connect' && props.authorizer) { + throw new Error('You can only set a WebSocket authorizer to a $connect route.'); + } + this.webSocketApi = props.webSocketApi; this.routeKey = props.routeKey; @@ -72,10 +83,18 @@ export class WebSocketRoute extends Resource implements IWebSocketRoute { scope: this, }); + const authorizer = props.authorizer ?? new WebSocketNoneAuthorizer(); // must be explicitly NONE (not undefined) for stack updates to work correctly + const authBindResult = authorizer.bind({ + route: this, + scope: this.webSocketApi instanceof Construct ? this.webSocketApi : this, // scope under the API if it's not imported + }); + const route = new CfnRoute(this, 'Resource', { apiId: props.webSocketApi.apiId, routeKey: props.routeKey, target: `integrations/${config.integrationId}`, + authorizerId: authBindResult.authorizerId, + authorizationType: authBindResult.authorizationType, }); this.routeId = route.ref; } diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/authorizer.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/authorizer.test.ts new file mode 100644 index 0000000000000..105b7f168e74e --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/authorizer.test.ts @@ -0,0 +1,26 @@ +import { Template } from '@aws-cdk/assertions'; +import { Stack } from '@aws-cdk/core'; +import { + WebSocketApi, WebSocketAuthorizer, WebSocketAuthorizerType, +} from '../../lib'; + +describe('WebSocketAuthorizer', () => { + describe('lambda', () => { + it('default', () => { + const stack = new Stack(); + const webSocketApi = new WebSocketApi(stack, 'WebSocketApi'); + + new WebSocketAuthorizer(stack, 'WebSocketAuthorizer', { + webSocketApi, + identitySource: ['identitysource.1', 'identitysource.2'], + type: WebSocketAuthorizerType.LAMBDA, + authorizerUri: 'arn:cool-lambda-arn', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Authorizer', { + AuthorizerType: 'REQUEST', + AuthorizerUri: 'arn:cool-lambda-arn', + }); + }); + }); +}); diff --git a/packages/@aws-cdk/aws-applicationautoscaling/README.md b/packages/@aws-cdk/aws-applicationautoscaling/README.md index 7d96430c90d32..0870212d274a9 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/README.md +++ b/packages/@aws-cdk/aws-applicationautoscaling/README.md @@ -106,6 +106,37 @@ capacity.scaleOnMetric('ScaleToCPU', { The AutoScaling construct library will create the required CloudWatch alarms and AutoScaling policies for you. +### Scaling based on multiple datapoints + +The Step Scaling configuration above will initiate a scaling event when a single +datapoint of the scaling metric is breaching a scaling step breakpoint. In cases +where you might want to initiate scaling actions on a larger number of datapoints +(ie in order to smooth out randomness in the metric data), you can use the +optional `evaluationPeriods` and `datapointsToAlarm` properties: + +```ts +declare const capacity: ScalableAttribute; +declare const cpuUtilization: cloudwatch.Metric; + +capacity.scaleOnMetric('ScaleToCPUWithMultipleDatapoints', { + metric: cpuUtilization, + scalingSteps: [ + { upper: 10, change: -1 }, + { lower: 50, change: +1 }, + { lower: 70, change: +3 }, + ], + + // if the cpuUtilization metric has a period of 1 minute, then data points + // in the last 10 minutes will be evaluated + evaluationPeriods: 10, + + // Only trigger a scaling action when 6 datapoints out of the last 10 are + // breaching. If this is left unspecified, then ALL datapoints in the + // evaluation period must be breaching to trigger a scaling action + datapointsToAlarm: 6 +}); +``` + ## Target Tracking Scaling This type of scaling scales in and out in order to keep a metric (typically diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts index 8b9f7b2644267..6bbc210f4f0c1 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts @@ -58,10 +58,26 @@ export interface BasicStepScalingPolicyProps { * Raising this value can be used to smooth out the metric, at the expense * of slower response times. * + * If `datapointsToAlarm` is not set, then all data points in the evaluation period + * must meet the criteria to trigger a scaling action. + * * @default 1 */ readonly evaluationPeriods?: number; + /** + * The number of data points out of the evaluation periods that must be breaching to + * trigger a scaling action + * + * Creates an "M out of N" alarm, where this property is the M and the value set for + * `evaluationPeriods` is the N value. + * + * Only has meaning if `evaluationPeriods != 1`. + * + * @default `evaluationPeriods` + */ + readonly datapointsToAlarm?: number; + /** * Aggregation to apply to all data points over the evaluation periods * @@ -99,6 +115,10 @@ export class StepScalingPolicy extends CoreConstruct { throw new Error('You must supply at least 2 intervals for autoscaling'); } + if (props.datapointsToAlarm !== undefined && props.datapointsToAlarm < 1) { + throw new RangeError(`datapointsToAlarm cannot be less than 1, got: ${props.datapointsToAlarm}`); + } + const adjustmentType = props.adjustmentType || AdjustmentType.CHANGE_IN_CAPACITY; const changesAreAbsolute = adjustmentType === AdjustmentType.EXACT_CAPACITY; @@ -130,6 +150,7 @@ export class StepScalingPolicy extends CoreConstruct { alarmDescription: 'Lower threshold scaling alarm', comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD, evaluationPeriods: props.evaluationPeriods ?? 1, + datapointsToAlarm: props.datapointsToAlarm, threshold, }); this.lowerAlarm.addAlarmAction(new StepScalingAlarmAction(this.lowerAction)); @@ -160,6 +181,7 @@ export class StepScalingPolicy extends CoreConstruct { alarmDescription: 'Upper threshold scaling alarm', comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, evaluationPeriods: props.evaluationPeriods ?? 1, + datapointsToAlarm: props.datapointsToAlarm, threshold, }); this.upperAlarm.addAlarmAction(new StepScalingAlarmAction(this.upperAction)); diff --git a/packages/@aws-cdk/aws-applicationautoscaling/test/step-scaling-policy.test.ts b/packages/@aws-cdk/aws-applicationautoscaling/test/step-scaling-policy.test.ts index 528a84b997306..ca1011881ec14 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/test/step-scaling-policy.test.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/test/step-scaling-policy.test.ts @@ -227,6 +227,62 @@ describe('step scaling policy', () => { }); + + test('step scaling with evaluation period & data points to alarm configured', () => { + // GIVEN + const stack = new cdk.Stack(); + const target = createScalableTarget(stack); + + // WHEN + target.scaleOnMetric('Tracking', { + metric: new cloudwatch.Metric({ namespace: 'Test', metricName: 'Metric', statistic: 'p99' }), + scalingSteps: [ + { upper: 0, change: -1 }, + { lower: 100, change: +1 }, + { lower: 500, change: +5 }, + ], + evaluationPeriods: 10, + datapointsToAlarm: 6, + metricAggregationType: appscaling.MetricAggregationType.MAXIMUM, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalingPolicy', { + PolicyType: 'StepScaling', + StepScalingPolicyConfiguration: { + AdjustmentType: 'ChangeInCapacity', + MetricAggregationType: 'Maximum', + }, + }); + expect(stack).toHaveResource('AWS::CloudWatch::Alarm', { + ComparisonOperator: 'GreaterThanOrEqualToThreshold', + EvaluationPeriods: 10, + DatapointsToAlarm: 6, + ExtendedStatistic: 'p99', + MetricName: 'Metric', + Namespace: 'Test', + Threshold: 100, + }); + }); + + test('step scaling with invalid datapointsToAlarm throws error', () => { + const stack = new cdk.Stack(); + const target = createScalableTarget(stack); + + expect(() => { + target.scaleOnMetric('Tracking', { + metric: new cloudwatch.Metric({ namespace: 'Test', metricName: 'Metric', statistic: 'p99' }), + scalingSteps: [ + { upper: 0, change: -1 }, + { lower: 100, change: +1 }, + { lower: 500, change: +5 }, + ], + evaluationPeriods: 10, + datapointsToAlarm: 0, + metricAggregationType: appscaling.MetricAggregationType.MAXIMUM, + }); + }).toThrow('datapointsToAlarm cannot be less than 1, got: 0'); + }); }); /** diff --git a/packages/@aws-cdk/aws-appmesh/lib/gateway-route-spec.ts b/packages/@aws-cdk/aws-appmesh/lib/gateway-route-spec.ts index 9e8f6315a2356..d62550a46fc0f 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/gateway-route-spec.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/gateway-route-spec.ts @@ -148,10 +148,24 @@ export interface GrpcGatewayRouteMatch { readonly rewriteRequestHostname?: boolean; } +/** + * Base options for all gateway route specs. + */ +export interface CommonGatewayRouteSpecOptions { + /** + * The priority for the gateway route. When a Virtual Gateway has multiple gateway routes, gateway route match + * is performed in the order of specified value, where 0 is the highest priority, + * and first matched gateway route is selected. + * + * @default - no particular priority + */ + readonly priority?: number; +} + /** * Properties specific for HTTP Based GatewayRoutes */ -export interface HttpGatewayRouteSpecOptions { +export interface HttpGatewayRouteSpecOptions extends CommonGatewayRouteSpecOptions { /** * The criterion for determining a request match for this GatewayRoute. * When path match is defined, this may optionally determine the path rewrite configuration. @@ -169,7 +183,7 @@ export interface HttpGatewayRouteSpecOptions { /** * Properties specific for a gRPC GatewayRoute */ -export interface GrpcGatewayRouteSpecOptions { +export interface GrpcGatewayRouteSpecOptions extends CommonGatewayRouteSpecOptions { /** * The criterion for determining a request match for this GatewayRoute */ @@ -205,6 +219,15 @@ export interface GatewayRouteSpecConfig { * @default - no grpc spec */ readonly grpcSpecConfig?: CfnGatewayRoute.GrpcGatewayRouteProperty; + + /** + * The priority for the gateway route. When a Virtual Gateway has multiple gateway routes, gateway route match + * is performed in the order of specified value, where 0 is the highest priority, + * and first matched gateway route is selected. + * + * @default - no particular priority + */ + readonly priority?: number; } /** @@ -257,12 +280,14 @@ class HttpGatewayRouteSpec extends GatewayRouteSpec { * Type of route you are creating */ readonly routeType: Protocol; + readonly priority?: number; constructor(options: HttpGatewayRouteSpecOptions, protocol: Protocol.HTTP | Protocol.HTTP2) { super(); this.routeTarget = options.routeTarget; this.routeType = protocol; this.match = options.match; + this.priority = options.priority; } public bind(scope: Construct): GatewayRouteSpecConfig { @@ -301,6 +326,7 @@ class HttpGatewayRouteSpec extends GatewayRouteSpec { }, }; return { + priority: this.priority, httpSpecConfig: this.routeType === Protocol.HTTP ? httpConfig : undefined, http2SpecConfig: this.routeType === Protocol.HTTP2 ? httpConfig : undefined, }; @@ -314,11 +340,13 @@ class GrpcGatewayRouteSpec extends GatewayRouteSpec { * The VirtualService this GatewayRoute directs traffic to */ readonly routeTarget: IVirtualService; + readonly priority?: number; constructor(options: GrpcGatewayRouteSpecOptions) { super(); this.match = options.match; this.routeTarget = options.routeTarget; + this.priority = options.priority; } public bind(scope: Construct): GatewayRouteSpecConfig { @@ -349,6 +377,7 @@ class GrpcGatewayRouteSpec extends GatewayRouteSpec { }, }, }, + priority: this.priority, }; } } diff --git a/packages/@aws-cdk/aws-appmesh/lib/gateway-route.ts b/packages/@aws-cdk/aws-appmesh/lib/gateway-route.ts index 088067d06763d..32a52acbe7bda 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/gateway-route.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/gateway-route.ts @@ -119,6 +119,7 @@ export class GatewayRoute extends cdk.Resource implements IGatewayRoute { httpRoute: routeSpecConfig.httpSpecConfig, http2Route: routeSpecConfig.http2SpecConfig, grpcRoute: routeSpecConfig.grpcSpecConfig, + priority: routeSpecConfig.priority, }, virtualGatewayName: this.virtualGateway.virtualGatewayName, }); diff --git a/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts b/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts index a27d589a61ded..1e0e0913296f1 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts @@ -120,8 +120,8 @@ export interface GrpcRouteMatch { */ export interface RouteSpecOptionsBase { /** - * The priority for the route. Routes are matched based on the specified - * value, where 0 is the highest priority. + * The priority for the route. When a Virtual Router has multiple routes, route match is performed in the + * order of specified value, where 0 is the highest priority, and first matched route is selected. * * @default - no particular priority */ @@ -357,8 +357,8 @@ export interface RouteSpecConfig { readonly tcpRouteSpec?: CfnRoute.TcpRouteProperty; /** - * The priority for the route. Routes are matched based on the specified - * value, where 0 is the highest priority. + * The priority for the route. When a Virtual Router has multiple routes, route match is performed in the + * order of specified value, where 0 is the highest priority, and first matched route is selected. * * @default - no particular priority */ diff --git a/packages/@aws-cdk/aws-appmesh/test/gateway-route.test.ts b/packages/@aws-cdk/aws-appmesh/test/gateway-route.test.ts index 85eb5254b3153..6ae344e7ff297 100644 --- a/packages/@aws-cdk/aws-appmesh/test/gateway-route.test.ts +++ b/packages/@aws-cdk/aws-appmesh/test/gateway-route.test.ts @@ -1122,8 +1122,86 @@ describe('gateway route', () => { }, }, }); + }); + }); + + describe('with priority', () => { + test('should set the priority for http gateway route', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + const virtualGateway = new appmesh.VirtualGateway(stack, 'gateway-1', { + listeners: [appmesh.VirtualGatewayListener.http()], + mesh: mesh, + }); + + const virtualService = new appmesh.VirtualService(stack, 'vs-1', { + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), + virtualServiceName: 'target.local', + }); + + // Add an HTTP Route + virtualGateway.addGatewayRoute('gateway-http-route', { + routeSpec: appmesh.GatewayRouteSpec.http({ + routeTarget: virtualService, + match: { + }, + priority: 100, + }), + gatewayRouteName: 'gateway-http-route', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppMesh::GatewayRoute', { + Spec: { + Priority: 100, + }, + }); + }); + test('should set the priority for grpc gateway route', () => { + // GIVEN + const stack = new cdk.Stack(); + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + const virtualGateway = new appmesh.VirtualGateway(stack, 'gateway-1', { + listeners: [appmesh.VirtualGatewayListener.grpc()], + mesh: mesh, + }); + + const virtualService = new appmesh.VirtualService(stack, 'vs-1', { + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), + virtualServiceName: 'target.local', + }); + + // Add an Grpc Route + new appmesh.GatewayRoute(stack, 'test-node', { + routeSpec: appmesh.GatewayRouteSpec.grpc({ + match: { + serviceName: virtualService.virtualServiceName, + }, + routeTarget: virtualService, + priority: 500, + }), + virtualGateway: virtualGateway, + gatewayRouteName: 'routeWithPriority', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppMesh::GatewayRoute', { + Spec: { + Priority: 500, + }, + }); }); }); diff --git a/packages/@aws-cdk/aws-appsync/lib/caching-config.ts b/packages/@aws-cdk/aws-appsync/lib/caching-config.ts index bd189e41ee321..d02a393cc53c0 100644 --- a/packages/@aws-cdk/aws-appsync/lib/caching-config.ts +++ b/packages/@aws-cdk/aws-appsync/lib/caching-config.ts @@ -16,7 +16,6 @@ export interface CachingConfig { * The TTL in seconds for a resolver that has caching enabled. * Valid values are between 1 and 3600 seconds. * - * @default - No TTL */ - readonly ttl?: Duration; + readonly ttl: Duration; } diff --git a/packages/@aws-cdk/aws-appsync/lib/key.ts b/packages/@aws-cdk/aws-appsync/lib/key.ts index d05975ce8ed16..57bfc82e1dc7f 100644 --- a/packages/@aws-cdk/aws-appsync/lib/key.ts +++ b/packages/@aws-cdk/aws-appsync/lib/key.ts @@ -68,14 +68,14 @@ export class KeyCondition { */ public renderTemplate(): string { return `"query" : { - "expression" : "${this.cond.renderCondition()}", - "expressionNames" : { + "expression" : "${this.cond.renderCondition()}", + "expressionNames" : { ${this.cond.renderExpressionNames()} - }, - "expressionValues" : { + }, + "expressionValues" : { ${this.cond.renderExpressionValues()} - } - }`; + } + }`; } } @@ -253,4 +253,4 @@ export class Values { public static attribute(attr: string): AttributeValuesStep { return new AttributeValues('{}').attribute(attr); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json index 6f9fd9c12d899..e14c0af7a5450 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json @@ -437,7 +437,7 @@ "TypeName": "Query", "DataSourceName": "Order", "Kind": "UNIT", - "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#customer = :customer\",\n \"expressionNames\" : {\n \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer)\n }\n }}", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#customer = :customer\",\n \"expressionNames\" : {\n \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer)\n }\n }}", "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ @@ -458,7 +458,7 @@ "TypeName": "Query", "DataSourceName": "Order", "Kind": "UNIT", - "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order = :order\",\n \"expressionNames\" : {\n \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order = :order\",\n \"expressionNames\" : {\n \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ @@ -479,7 +479,7 @@ "TypeName": "Query", "DataSourceName": "Order", "Kind": "UNIT", - "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#customer < :customer\",\n \"expressionNames\" : {\n \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer)\n }\n }}", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#customer < :customer\",\n \"expressionNames\" : {\n \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer)\n }\n }}", "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ @@ -500,7 +500,7 @@ "TypeName": "Query", "DataSourceName": "Order", "Kind": "UNIT", - "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order < :order\",\n \"expressionNames\" : {\n \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order < :order\",\n \"expressionNames\" : {\n \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ @@ -521,7 +521,7 @@ "TypeName": "Query", "DataSourceName": "Order", "Kind": "UNIT", - "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#customer <= :customer\",\n \"expressionNames\" : {\n \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer)\n }\n }}", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#customer <= :customer\",\n \"expressionNames\" : {\n \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer)\n }\n }}", "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ @@ -542,7 +542,7 @@ "TypeName": "Query", "DataSourceName": "Order", "Kind": "UNIT", - "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order <= :order\",\n \"expressionNames\" : {\n \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order <= :order\",\n \"expressionNames\" : {\n \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ @@ -563,7 +563,7 @@ "TypeName": "Query", "DataSourceName": "Order", "Kind": "UNIT", - "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#customer > :customer\",\n \"expressionNames\" : {\n \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer)\n }\n }}", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#customer > :customer\",\n \"expressionNames\" : {\n \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer)\n }\n }}", "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ @@ -584,7 +584,7 @@ "TypeName": "Query", "DataSourceName": "Order", "Kind": "UNIT", - "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order > :order\",\n \"expressionNames\" : {\n \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order > :order\",\n \"expressionNames\" : {\n \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ @@ -605,7 +605,7 @@ "TypeName": "Query", "DataSourceName": "Order", "Kind": "UNIT", - "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#customer >= :customer\",\n \"expressionNames\" : {\n \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer)\n }\n }}", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#customer >= :customer\",\n \"expressionNames\" : {\n \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer)\n }\n }}", "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ @@ -626,7 +626,7 @@ "TypeName": "Query", "DataSourceName": "Order", "Kind": "UNIT", - "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order >= :order\",\n \"expressionNames\" : {\n \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order >= :order\",\n \"expressionNames\" : {\n \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ @@ -647,7 +647,7 @@ "TypeName": "Query", "DataSourceName": "Order", "Kind": "UNIT", - "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#customer = :customer AND begins_with(#order, :order)\",\n \"expressionNames\" : {\n \"#customer\" : \"customer\", \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer), \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#customer = :customer AND begins_with(#order, :order)\",\n \"expressionNames\" : {\n \"#customer\" : \"customer\", \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer), \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ @@ -668,7 +668,7 @@ "TypeName": "Query", "DataSourceName": "Order", "Kind": "UNIT", - "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#customer = :customer AND #order BETWEEN :order1 AND :order2\",\n \"expressionNames\" : {\n \"#customer\" : \"customer\", \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer), \":order1\" : $util.dynamodb.toDynamoDBJson($ctx.args.order1), \":order2\" : $util.dynamodb.toDynamoDBJson($ctx.args.order2)\n }\n }}", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#customer = :customer AND #order BETWEEN :order1 AND :order2\",\n \"expressionNames\" : {\n \"#customer\" : \"customer\", \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer), \":order1\" : $util.dynamodb.toDynamoDBJson($ctx.args.order1), \":order2\" : $util.dynamodb.toDynamoDBJson($ctx.args.order2)\n }\n }}", "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ @@ -689,7 +689,7 @@ "TypeName": "Query", "DataSourceName": "Order", "Kind": "UNIT", - "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#order = :order AND begins_with(#customer, :customer)\",\n \"expressionNames\" : {\n \"#order\" : \"order\", \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order), \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer)\n }\n }}", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#order = :order AND begins_with(#customer, :customer)\",\n \"expressionNames\" : {\n \"#order\" : \"order\", \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order), \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer)\n }\n }}", "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ @@ -710,7 +710,7 @@ "TypeName": "Query", "DataSourceName": "Order", "Kind": "UNIT", - "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order = :order AND #customer BETWEEN :customer1 AND :customer2\",\n \"expressionNames\" : {\n \"#order\" : \"order\", \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order), \":customer1\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer1), \":customer2\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer2)\n }\n }}", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order = :order AND #customer BETWEEN :customer1 AND :customer2\",\n \"expressionNames\" : {\n \"#order\" : \"order\", \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order), \":customer1\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer1), \":customer2\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer2)\n }\n }}", "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/common.ts b/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/common.ts new file mode 100644 index 0000000000000..e16530d6231ff --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/common.ts @@ -0,0 +1,17 @@ +// eslint-disable-next-line import/order +import * as iam from '@aws-cdk/aws-iam'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import * as constructs from 'constructs'; + +export function createRole(scope: constructs.Construct, _role?: iam.IRole) { + let role = _role; + if (!role) { + role = new iam.Role(scope, 'Role', { + assumedBy: new iam.ServicePrincipal('autoscaling.amazonaws.com'), + }); + } + + return role; +} diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/index.ts b/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/index.ts index 4b48b2ab6d1b7..53591e6610bd8 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/index.ts +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/index.ts @@ -1,3 +1,4 @@ +export * from './common'; export * from './queue-hook'; export * from './topic-hook'; export * from './lambda-hook'; diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/lambda-hook.ts b/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/lambda-hook.ts index dbe170438320e..766b4d0149a8a 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/lambda-hook.ts +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/lambda-hook.ts @@ -4,11 +4,12 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; import * as subs from '@aws-cdk/aws-sns-subscriptions'; +import { createRole } from './common'; import { TopicHook } from './topic-hook'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order -import { Construct } from '@aws-cdk/core'; +import { Construct } from 'constructs'; /** * Use a Lambda Function as a hook target @@ -23,16 +24,23 @@ export class FunctionHook implements autoscaling.ILifecycleHookTarget { constructor(private readonly fn: lambda.IFunction, private readonly encryptionKey?: kms.IKey) { } - public bind(scope: Construct, lifecycleHook: autoscaling.ILifecycleHook): autoscaling.LifecycleHookTargetConfig { - const topic = new sns.Topic(scope, 'Topic', { + /** + * If the `IRole` does not exist in `options`, will create an `IRole` and an SNS Topic and attach both to the lifecycle hook. + * If the `IRole` does exist in `options`, will only create an SNS Topic and attach it to the lifecycle hook. + */ + public bind(_scope: Construct, options: autoscaling.BindHookTargetOptions): autoscaling.LifecycleHookTargetConfig { + const topic = new sns.Topic(_scope, 'Topic', { masterKey: this.encryptionKey, }); + + const role = createRole(_scope, options.role); + // Per: https://docs.aws.amazon.com/sns/latest/dg/sns-key-management.html#sns-what-permissions-for-sse // Topic's grantPublish() is in a base class that does not know there is a kms key, and so does not // grant appropriate permissions to the kms key. We do that here to ensure the correct permissions // are in place. - this.encryptionKey?.grant(lifecycleHook.role, 'kms:Decrypt', 'kms:GenerateDataKey'); + this.encryptionKey?.grant(role, 'kms:Decrypt', 'kms:GenerateDataKey'); topic.addSubscription(new subs.LambdaSubscription(this.fn)); - return new TopicHook(topic).bind(scope, lifecycleHook); + return new TopicHook(topic).bind(_scope, { lifecycleHook: options.lifecycleHook, role }); } } diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/queue-hook.ts b/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/queue-hook.ts index 640cd2a8b0ac6..621c5d3be49af 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/queue-hook.ts +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/queue-hook.ts @@ -1,6 +1,10 @@ import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as sqs from '@aws-cdk/aws-sqs'; -import { Construct } from '@aws-cdk/core'; +import { createRole } from './common'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from 'constructs'; /** * Use an SQS queue as a hook target @@ -9,8 +13,19 @@ export class QueueHook implements autoscaling.ILifecycleHookTarget { constructor(private readonly queue: sqs.IQueue) { } - public bind(_scope: Construct, lifecycleHook: autoscaling.ILifecycleHook): autoscaling.LifecycleHookTargetConfig { - this.queue.grantSendMessages(lifecycleHook.role); - return { notificationTargetArn: this.queue.queueArn }; + /** + * If an `IRole` is found in `options`, grant it access to send messages. + * Otherwise, create a new `IRole` and grant it access to send messages. + * + * @returns the `IRole` with access to send messages and the ARN of the queue it has access to send messages to. + */ + public bind(_scope: Construct, options: autoscaling.BindHookTargetOptions): autoscaling.LifecycleHookTargetConfig { + const role = createRole(_scope, options.role); + this.queue.grantSendMessages(role); + + return { + notificationTargetArn: this.queue.queueArn, + createdRole: role, + }; } } diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/topic-hook.ts b/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/topic-hook.ts index 1f6546ebd75ac..168b88bba61c7 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/topic-hook.ts +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/topic-hook.ts @@ -1,6 +1,10 @@ import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as sns from '@aws-cdk/aws-sns'; -import { Construct } from '@aws-cdk/core'; +import { createRole } from './common'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from 'constructs'; /** * Use an SNS topic as a hook target @@ -9,8 +13,19 @@ export class TopicHook implements autoscaling.ILifecycleHookTarget { constructor(private readonly topic: sns.ITopic) { } - public bind(_scope: Construct, lifecycleHook: autoscaling.ILifecycleHook): autoscaling.LifecycleHookTargetConfig { - this.topic.grantPublish(lifecycleHook.role); - return { notificationTargetArn: this.topic.topicArn }; + /** + * If an `IRole` is found in `options`, grant it topic publishing permissions. + * Otherwise, create a new `IRole` and grant it topic publishing permissions. + * + * @returns the `IRole` with topic publishing permissions and the ARN of the topic it has publishing permission to. + */ + public bind(_scope: Construct, options: autoscaling.BindHookTargetOptions): autoscaling.LifecycleHookTargetConfig { + const role = createRole(_scope, options.role); + this.topic.grantPublish(role); + + return { + notificationTargetArn: this.topic.topicArn, + createdRole: role, + }; } } diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/test/hooks.test.ts b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/hooks.test.ts index d984693280351..4615c45913b44 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/test/hooks.test.ts +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/hooks.test.ts @@ -2,6 +2,7 @@ import '@aws-cdk/assert-internal/jest'; import { arrayWith } from '@aws-cdk/assert-internal'; import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; @@ -10,7 +11,7 @@ import { Stack } from '@aws-cdk/core'; import * as hooks from '../lib'; -describe('given an AutoScalingGroup', () => { +describe('given an AutoScalingGroup and no role', () => { let stack: Stack; let asg: autoscaling.AutoScalingGroup; @@ -25,7 +26,24 @@ describe('given an AutoScalingGroup', () => { }); }); - test('can use queue as hook target', () => { + afterEach(() => { + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'autoscaling.amazonaws.com', + }, + }, + ], + }, + }); + }); + + test('can use queue as hook target without providing a role', () => { // GIVEN const queue = new sqs.Queue(stack, 'Queue'); @@ -37,9 +55,36 @@ describe('given an AutoScalingGroup', () => { // THEN expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { 'Fn::GetAtt': ['Queue4A7E3555', 'Arn'] } }); + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'sqs:SendMessage', + 'sqs:GetQueueAttributes', + 'sqs:GetQueueUrl', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'Queue4A7E3555', + 'Arn', + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'ASGLifecycleHookTransRoleDefaultPolicy43D7C82A', + Roles: [ + { + Ref: 'ASGLifecycleHookTransRole71E0A219', + }, + ], + }); }); - test('can use topic as hook target', () => { + test('can use topic as hook target without providing a role', () => { // GIVEN const topic = new sns.Topic(stack, 'Topic'); @@ -50,12 +95,30 @@ describe('given an AutoScalingGroup', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { - NotificationTargetARN: { Ref: 'TopicBFC7AF6E' }, + expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'TopicBFC7AF6E' } }); + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'sns:Publish', + Effect: 'Allow', + Resource: { + Ref: 'TopicBFC7AF6E', + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'ASGLifecycleHookTransRoleDefaultPolicy43D7C82A', + Roles: [ + { + Ref: 'ASGLifecycleHookTransRole71E0A219', + }, + ], }); }); - test('can use Lambda function as hook target', () => { + test('can use Lambda function as hook target without providing a role', () => { // GIVEN const fn = new lambda.Function(stack, 'Fn', { code: lambda.Code.fromInline('foo'), @@ -70,14 +133,32 @@ describe('given an AutoScalingGroup', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { - NotificationTargetARN: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' }, - }); + expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' } }); expect(stack).toHaveResource('AWS::SNS::Subscription', { Protocol: 'lambda', TopicArn: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' }, Endpoint: { 'Fn::GetAtt': ['Fn9270CBC0', 'Arn'] }, }); + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'sns:Publish', + Effect: 'Allow', + Resource: { + Ref: 'ASGLifecycleHookTransTopic9B0D4842', + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'ASGLifecycleHookTransRoleDefaultPolicy43D7C82A', + Roles: [ + { + Ref: 'ASGLifecycleHookTransRole71E0A219', + }, + ], + }); }); test('can use Lambda function as hook target with encrypted SNS', () => { @@ -124,5 +205,139 @@ describe('given an AutoScalingGroup', () => { }, }); }); +}); + +describe('given an AutoScalingGroup and a role', () => { + let stack: Stack; + let asg: autoscaling.AutoScalingGroup; + + beforeEach(() => { + stack = new Stack(); + + const vpc = new ec2.Vpc(stack, 'VPC'); + asg = new autoscaling.AutoScalingGroup(stack, 'ASG', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: new ec2.AmazonLinuxImage(), + }); + }); + + afterEach(() => { + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'custom.role.domain.com', + }, + }, + ], + }, + }); + }); + test('can use queue as hook target with a role', () => { + // GIVEN + const queue = new sqs.Queue(stack, 'Queue'); + const myrole = new iam.Role(stack, 'MyRole', { + assumedBy: new iam.ServicePrincipal('custom.role.domain.com'), + }); + // WHEN + asg.addLifecycleHook('Trans', { + lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_LAUNCHING, + notificationTarget: new hooks.QueueHook(queue), + role: myrole, + }); + + // THEN + expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { 'Fn::GetAtt': ['Queue4A7E3555', 'Arn'] } }); + }); + + test('can use topic as hook target with a role', () => { + // GIVEN + const topic = new sns.Topic(stack, 'Topic'); + const myrole = new iam.Role(stack, 'MyRole', { + assumedBy: new iam.ServicePrincipal('custom.role.domain.com'), + }); + + // WHEN + asg.addLifecycleHook('Trans', { + lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_LAUNCHING, + notificationTarget: new hooks.TopicHook(topic), + role: myrole, + }); + + // THEN + expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'TopicBFC7AF6E' } }); + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'sns:Publish', + Effect: 'Allow', + Resource: { + Ref: 'TopicBFC7AF6E', + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyRoleDefaultPolicyA36BE1DD', + Roles: [ + { + Ref: 'MyRoleF48FFE04', + }, + ], + }); + }); + + test('can use Lambda function as hook target with a role', () => { + // GIVEN + const fn = new lambda.Function(stack, 'Fn', { + code: lambda.Code.fromInline('foo'), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.index', + }); + const myrole = new iam.Role(stack, 'MyRole', { + assumedBy: new iam.ServicePrincipal('custom.role.domain.com'), + }); + + // WHEN + asg.addLifecycleHook('Trans', { + lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_LAUNCHING, + notificationTarget: new hooks.FunctionHook(fn), + role: myrole, + }); + + // THEN + expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' } }); + expect(stack).toHaveResource('AWS::SNS::Subscription', { + Protocol: 'lambda', + TopicArn: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' }, + Endpoint: { 'Fn::GetAtt': ['Fn9270CBC0', 'Arn'] }, + }); + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'sns:Publish', + Effect: 'Allow', + Resource: { + Ref: 'ASGLifecycleHookTransTopic9B0D4842', + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyRoleDefaultPolicyA36BE1DD', + Roles: [ + { + Ref: 'MyRoleF48FFE04', + }, + ], + }); + }); }); diff --git a/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook-target.ts b/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook-target.ts index e15ae3ef081b5..4004b5de39f67 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook-target.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook-target.ts @@ -1,26 +1,50 @@ - -import { ILifecycleHook } from './lifecycle-hook'; +// eslint-disable-next-line import/order +import { LifecycleHook } from './lifecycle-hook'; +import * as iam from '@aws-cdk/aws-iam'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order -import { Construct } from '@aws-cdk/core'; +import * as constructs from 'constructs'; /** - * Interface for autoscaling lifecycle hook targets + * Options needed to bind a target to a lifecycle hook. + * [disable-awslint:ref-via-interface] The lifecycle hook to attach to and an IRole to use */ -export interface ILifecycleHookTarget { +export interface BindHookTargetOptions { /** - * Called when this object is used as the target of a lifecycle hook + * The lifecycle hook to attach to. + * [disable-awslint:ref-via-interface] */ - bind(scope: Construct, lifecycleHook: ILifecycleHook): LifecycleHookTargetConfig; + readonly lifecycleHook: LifecycleHook; + /** + * The role to use when attaching to the lifecycle hook. + * [disable-awslint:ref-via-interface] + * @default: a role is not created unless the target arn is specified + */ + readonly role?: iam.IRole; } /** - * Properties to add the target to a lifecycle hook + * Result of binding a lifecycle hook to a target. */ export interface LifecycleHookTargetConfig { /** - * The ARN to use as the notification target + * The IRole that was used to bind the lifecycle hook to the target + */ + readonly createdRole: iam.IRole; + /** + * The targetArn that the lifecycle hook was bound to */ readonly notificationTargetArn: string; } + +/** + * Interface for autoscaling lifecycle hook targets + */ +export interface ILifecycleHookTarget { + /** + * Called when this object is used as the target of a lifecycle hook + * @param options [disable-awslint:ref-via-interface] The lifecycle hook to attach to and a role to use + */ + bind(scope: constructs.Construct, options: BindHookTargetOptions): LifecycleHookTargetConfig; +} diff --git a/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts b/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts index 4e4e8408ad326..cdcd9870ab37d 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts @@ -46,13 +46,15 @@ export interface BasicLifecycleHookProps { /** * The target of the lifecycle hook + * + * @default - No target. */ - readonly notificationTarget: ILifecycleHookTarget; + readonly notificationTarget?: ILifecycleHookTarget; /** * The role that allows publishing to the notification target * - * @default - A role is automatically created. + * @default - A role will be created if a target is provided. Otherwise, no role is created. */ readonly role?: iam.IRole; } @@ -73,6 +75,9 @@ export interface LifecycleHookProps extends BasicLifecycleHookProps { export interface ILifecycleHook extends IResource { /** * The role for the lifecycle hook to execute + * + * @default - A default role is created if 'notificationTarget' is specified. + * Otherwise, no role is created. */ readonly role: iam.IRole; } @@ -81,10 +86,21 @@ export interface ILifecycleHook extends IResource { * Define a life cycle hook */ export class LifecycleHook extends Resource implements ILifecycleHook { + private _role?: iam.IRole; + /** * The role that allows the ASG to publish to the notification target + * + * @default - A default role is created if 'notificationTarget' is specified. + * Otherwise, no role is created. */ - public readonly role: iam.IRole; + public get role() { + if (!this._role) { + throw new Error('\'role\' is undefined. Please specify a \'role\' or specify a \'notificationTarget\' to have a role provided for you.'); + } + + return this._role; + } /** * The name of this lifecycle hook @@ -97,11 +113,20 @@ export class LifecycleHook extends Resource implements ILifecycleHook { physicalName: props.lifecycleHookName, }); - this.role = props.role || new iam.Role(this, 'Role', { - assumedBy: new iam.ServicePrincipal('autoscaling.amazonaws.com'), - }); + const targetProps = props.notificationTarget ? props.notificationTarget.bind(this, { lifecycleHook: this, role: props.role }) : undefined; + + if (props.role) { + this._role = props.role; + + if (!props.notificationTarget) { + throw new Error("'notificationTarget' parameter required when 'role' parameter is specified"); + } + } else { + this._role = targetProps ? targetProps.createdRole : undefined; + } - const targetProps = props.notificationTarget.bind(this, this); + const l1NotificationTargetArn = targetProps ? targetProps.notificationTargetArn : undefined; + const l1RoleArn = this._role ? this.role.roleArn : undefined; const resource = new CfnLifecycleHook(this, 'Resource', { autoScalingGroupName: props.autoScalingGroup.autoScalingGroupName, @@ -110,14 +135,16 @@ export class LifecycleHook extends Resource implements ILifecycleHook { lifecycleHookName: this.physicalName, lifecycleTransition: props.lifecycleTransition, notificationMetadata: props.notificationMetadata, - notificationTargetArn: targetProps.notificationTargetArn, - roleArn: this.role.roleArn, + notificationTargetArn: l1NotificationTargetArn, + roleArn: l1RoleArn, }); // A LifecycleHook resource is going to do a permissions test upon creation, // so we have to make sure the role has full permissions before creating the // lifecycle hook. - resource.node.addDependency(this.role); + if (this._role) { + resource.node.addDependency(this.role); + } this.lifecycleHookName = resource.ref; } diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.expected.json index af9c16803e320..f8514c807ed91 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.expected.json @@ -95,15 +95,15 @@ "VPCPublicSubnet1NATGatewayE0556630": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "VPCPublicSubnet2NATGateway3C070193": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet2EIP4947BC00", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" - }, "Tags": [ { "Key": "Name", diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json index 5882016a8f8b2..185201fe1f69e 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json @@ -95,15 +95,15 @@ "VPCPublicSubnet1NATGatewayE0556630": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "VPCPublicSubnet2NATGateway3C070193": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet2EIP4947BC00", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" - }, "Tags": [ { "Key": "Name", @@ -289,15 +289,15 @@ "VPCPublicSubnet3NATGatewayD3048F5C": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet3EIPAD4BC883", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet3Subnet631C5E25" - }, "Tags": [ { "Key": "Name", diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.custom-scaling.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.custom-scaling.expected.json index 21457d1ea78e6..304e554bf120d 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.custom-scaling.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.custom-scaling.expected.json @@ -95,15 +95,15 @@ "VPCPublicSubnet1NATGatewayE0556630": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "VPCPublicSubnet2NATGateway3C070193": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet2EIP4947BC00", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" - }, "Tags": [ { "Key": "Name", diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.external-role.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.external-role.expected.json index 49196dcc8ba93..9a01de34d1d55 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.external-role.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.external-role.expected.json @@ -95,15 +95,15 @@ "VPCPublicSubnet1NATGatewayE0556630": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "VPCPublicSubnet2NATGateway3C070193": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet2EIP4947BC00", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" - }, "Tags": [ { "Key": "Name", @@ -289,15 +289,15 @@ "VPCPublicSubnet3NATGatewayD3048F5C": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet3EIPAD4BC883", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet3Subnet631C5E25" - }, "Tags": [ { "Key": "Name", diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.role-target-hook.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.role-target-hook.expected.json new file mode 100644 index 0000000000000..54ccaf7b2e51a --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.role-target-hook.expected.json @@ -0,0 +1,788 @@ +{ + "Resources": { + "myVpcAuto1A4B61E2": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto" + } + ] + } + }, + "myVpcAutoPublicSubnet1Subnet3516098F": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "myVpcAuto1A4B61E2" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PublicSubnet1" + } + ] + } + }, + "myVpcAutoPublicSubnet1RouteTable3D618310": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "myVpcAuto1A4B61E2" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PublicSubnet1" + } + ] + } + }, + "myVpcAutoPublicSubnet1RouteTableAssociationB3A6EFAC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "myVpcAutoPublicSubnet1RouteTable3D618310" + }, + "SubnetId": { + "Ref": "myVpcAutoPublicSubnet1Subnet3516098F" + } + } + }, + "myVpcAutoPublicSubnet1DefaultRoute2791173D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "myVpcAutoPublicSubnet1RouteTable3D618310" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "myVpcAutoIGW08055396" + } + }, + "DependsOn": [ + "myVpcAutoVPCGWEC42CD12" + ] + }, + "myVpcAutoPublicSubnet1EIP15D99CAF": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PublicSubnet1" + } + ] + } + }, + "myVpcAutoPublicSubnet1NATGatewayF3EA78A2": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "myVpcAutoPublicSubnet1Subnet3516098F" + }, + "AllocationId": { + "Fn::GetAtt": [ + "myVpcAutoPublicSubnet1EIP15D99CAF", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PublicSubnet1" + } + ] + } + }, + "myVpcAutoPublicSubnet2Subnet297C7839": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "myVpcAuto1A4B61E2" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PublicSubnet2" + } + ] + } + }, + "myVpcAutoPublicSubnet2RouteTable17ECF2AC": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "myVpcAuto1A4B61E2" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PublicSubnet2" + } + ] + } + }, + "myVpcAutoPublicSubnet2RouteTableAssociationE21B7B6C": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "myVpcAutoPublicSubnet2RouteTable17ECF2AC" + }, + "SubnetId": { + "Ref": "myVpcAutoPublicSubnet2Subnet297C7839" + } + } + }, + "myVpcAutoPublicSubnet2DefaultRouteE9454F16": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "myVpcAutoPublicSubnet2RouteTable17ECF2AC" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "myVpcAutoIGW08055396" + } + }, + "DependsOn": [ + "myVpcAutoVPCGWEC42CD12" + ] + }, + "myVpcAutoPublicSubnet2EIPA484FACE": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PublicSubnet2" + } + ] + } + }, + "myVpcAutoPublicSubnet2NATGatewayF670624F": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "myVpcAutoPublicSubnet2Subnet297C7839" + }, + "AllocationId": { + "Fn::GetAtt": [ + "myVpcAutoPublicSubnet2EIPA484FACE", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PublicSubnet2" + } + ] + } + }, + "myVpcAutoPublicSubnet3SubnetF68815E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "myVpcAuto1A4B61E2" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PublicSubnet3" + } + ] + } + }, + "myVpcAutoPublicSubnet3RouteTableD3E0A17D": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "myVpcAuto1A4B61E2" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PublicSubnet3" + } + ] + } + }, + "myVpcAutoPublicSubnet3RouteTableAssociationD4E51D66": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "myVpcAutoPublicSubnet3RouteTableD3E0A17D" + }, + "SubnetId": { + "Ref": "myVpcAutoPublicSubnet3SubnetF68815E0" + } + } + }, + "myVpcAutoPublicSubnet3DefaultRouteE6596C40": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "myVpcAutoPublicSubnet3RouteTableD3E0A17D" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "myVpcAutoIGW08055396" + } + }, + "DependsOn": [ + "myVpcAutoVPCGWEC42CD12" + ] + }, + "myVpcAutoPublicSubnet3EIP8D506EFA": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PublicSubnet3" + } + ] + } + }, + "myVpcAutoPublicSubnet3NATGatewayC06521CD": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "myVpcAutoPublicSubnet3SubnetF68815E0" + }, + "AllocationId": { + "Fn::GetAtt": [ + "myVpcAutoPublicSubnet3EIP8D506EFA", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PublicSubnet3" + } + ] + } + }, + "myVpcAutoPrivateSubnet1SubnetCF0D49B2": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "myVpcAuto1A4B61E2" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PrivateSubnet1" + } + ] + } + }, + "myVpcAutoPrivateSubnet1RouteTableDC61148B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "myVpcAuto1A4B61E2" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PrivateSubnet1" + } + ] + } + }, + "myVpcAutoPrivateSubnet1RouteTableAssociation9848EFFB": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "myVpcAutoPrivateSubnet1RouteTableDC61148B" + }, + "SubnetId": { + "Ref": "myVpcAutoPrivateSubnet1SubnetCF0D49B2" + } + } + }, + "myVpcAutoPrivateSubnet1DefaultRouteF007F5E7": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "myVpcAutoPrivateSubnet1RouteTableDC61148B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "myVpcAutoPublicSubnet1NATGatewayF3EA78A2" + } + } + }, + "myVpcAutoPrivateSubnet2Subnet592674AC": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "myVpcAuto1A4B61E2" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PrivateSubnet2" + } + ] + } + }, + "myVpcAutoPrivateSubnet2RouteTableE10F6006": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "myVpcAuto1A4B61E2" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PrivateSubnet2" + } + ] + } + }, + "myVpcAutoPrivateSubnet2RouteTableAssociation05CC4CEB": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "myVpcAutoPrivateSubnet2RouteTableE10F6006" + }, + "SubnetId": { + "Ref": "myVpcAutoPrivateSubnet2Subnet592674AC" + } + } + }, + "myVpcAutoPrivateSubnet2DefaultRouteDA295DF0": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "myVpcAutoPrivateSubnet2RouteTableE10F6006" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "myVpcAutoPublicSubnet2NATGatewayF670624F" + } + } + }, + "myVpcAutoPrivateSubnet3Subnet4C2A5EDE": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "myVpcAuto1A4B61E2" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PrivateSubnet3" + } + ] + } + }, + "myVpcAutoPrivateSubnet3RouteTable22C6C602": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "myVpcAuto1A4B61E2" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto/PrivateSubnet3" + } + ] + } + }, + "myVpcAutoPrivateSubnet3RouteTableAssociation3A3751AE": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "myVpcAutoPrivateSubnet3RouteTable22C6C602" + }, + "SubnetId": { + "Ref": "myVpcAutoPrivateSubnet3Subnet4C2A5EDE" + } + } + }, + "myVpcAutoPrivateSubnet3DefaultRouteAE484D6A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "myVpcAutoPrivateSubnet3RouteTable22C6C602" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "myVpcAutoPublicSubnet3NATGatewayC06521CD" + } + } + }, + "myVpcAutoIGW08055396": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/myVpcAuto" + } + ] + } + }, + "myVpcAutoVPCGWEC42CD12": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "myVpcAuto1A4B61E2" + }, + "InternetGatewayId": { + "Ref": "myVpcAutoIGW08055396" + } + } + }, + "MyRoleF48FFE04": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "autoscaling.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyRoleDefaultPolicyA36BE1DD": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Effect": "Allow", + "Resource": { + "Ref": "topic2A4FB547F" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyRoleDefaultPolicyA36BE1DD", + "Roles": [ + { + "Ref": "MyRoleF48FFE04" + } + ] + } + }, + "topic69831491": { + "Type": "AWS::SNS::Topic" + }, + "topic2A4FB547F": { + "Type": "AWS::SNS::Topic" + }, + "ASGInstanceSecurityGroup0525485D": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "integ-role-target-hook/ASG/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/ASG" + } + ], + "VpcId": { + "Ref": "myVpcAuto1A4B61E2" + } + } + }, + "ASGInstanceRoleE263A41B": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-role-target-hook/ASG" + } + ] + } + }, + "ASGInstanceProfile0A2834D7": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "ASGInstanceRoleE263A41B" + } + ] + } + }, + "ASGLaunchConfigC00AF12B": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": { + "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "t2.micro", + "IamInstanceProfile": { + "Ref": "ASGInstanceProfile0A2834D7" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ASGInstanceSecurityGroup0525485D", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": "#!/bin/bash" + } + }, + "DependsOn": [ + "ASGInstanceRoleE263A41B" + ] + }, + "ASG46ED3070": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "1", + "MinSize": "1", + "HealthCheckType": "EC2", + "LaunchConfigurationName": { + "Ref": "ASGLaunchConfigC00AF12B" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "integ-role-target-hook/ASG" + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "myVpcAutoPrivateSubnet1SubnetCF0D49B2" + }, + { + "Ref": "myVpcAutoPrivateSubnet2Subnet592674AC" + }, + { + "Ref": "myVpcAutoPrivateSubnet3Subnet4C2A5EDE" + } + ] + }, + "UpdatePolicy": { + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "LCHookNoRoleNoTarget1144AD75": { + "Type": "AWS::AutoScaling::LifecycleHook", + "Properties": { + "AutoScalingGroupName": { + "Ref": "ASG46ED3070" + }, + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" + } + }, + "LCHookNoRoleTargetRole35B4344D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "autoscaling.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "LCHookNoRoleTargetRoleDefaultPolicyFE681941": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Effect": "Allow", + "Resource": { + "Ref": "topic69831491" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LCHookNoRoleTargetRoleDefaultPolicyFE681941", + "Roles": [ + { + "Ref": "LCHookNoRoleTargetRole35B4344D" + } + ] + } + }, + "LCHookNoRoleTarget4EF682CF": { + "Type": "AWS::AutoScaling::LifecycleHook", + "Properties": { + "AutoScalingGroupName": { + "Ref": "ASG46ED3070" + }, + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING", + "NotificationTargetARN": { + "Ref": "topic69831491" + }, + "RoleARN": { + "Fn::GetAtt": [ + "LCHookNoRoleTargetRole35B4344D", + "Arn" + ] + } + }, + "DependsOn": [ + "LCHookNoRoleTargetRoleDefaultPolicyFE681941", + "LCHookNoRoleTargetRole35B4344D" + ] + }, + "LCHookRoleTarget0ADB20B8": { + "Type": "AWS::AutoScaling::LifecycleHook", + "Properties": { + "AutoScalingGroupName": { + "Ref": "ASG46ED3070" + }, + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING", + "NotificationTargetARN": { + "Ref": "topic2A4FB547F" + }, + "RoleARN": { + "Fn::GetAtt": [ + "MyRoleF48FFE04", + "Arn" + ] + } + }, + "DependsOn": [ + "MyRoleDefaultPolicyA36BE1DD", + "MyRoleF48FFE04" + ] + } + }, + "Parameters": { + "SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.role-target-hook.ts b/packages/@aws-cdk/aws-autoscaling/test/integ.role-target-hook.ts new file mode 100644 index 0000000000000..42fe82d88fb6a --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.role-target-hook.ts @@ -0,0 +1,79 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; +import * as sns from '@aws-cdk/aws-sns'; +import * as cdk from '@aws-cdk/core'; +import * as constructs from 'constructs'; +import * as autoscaling from '../lib'; + +export class FakeNotificationTarget implements autoscaling.ILifecycleHookTarget { + constructor(private readonly topic: sns.ITopic) { + } + + private createRole(scope: constructs.Construct, _role?: iam.IRole) { + let role = _role; + if (!role) { + role = new iam.Role(scope, 'Role', { + assumedBy: new iam.ServicePrincipal('autoscaling.amazonaws.com'), + }); + } + + return role; + } + + public bind(_scope: constructs.Construct, options: autoscaling.BindHookTargetOptions): autoscaling.LifecycleHookTargetConfig { + const role = this.createRole(options.lifecycleHook, options.role); + this.topic.grantPublish(role); + + return { + notificationTargetArn: this.topic.topicArn, + createdRole: role, + }; + } +} + +export class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + let vpc = new ec2.Vpc(this, 'myVpcAuto', {}); + const myrole = new iam.Role(this, 'MyRole', { + assumedBy: new iam.ServicePrincipal('autoscaling.amazonaws.com'), + }); + const topic = new sns.Topic(this, 'topic', {}); + const topic2 = new sns.Topic(this, 'topic2', {}); + + const asg = new autoscaling.AutoScalingGroup(this, 'ASG', { + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage(), // get the latest Amazon Linux image + healthCheck: autoscaling.HealthCheck.ec2(), + }); + + // no role or notificationTarget + new autoscaling.LifecycleHook(this, 'LCHookNoRoleNoTarget', { + autoScalingGroup: asg, + lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_TERMINATING, + }); + + // no role with notificationTarget + new autoscaling.LifecycleHook(this, 'LCHookNoRoleTarget', { + notificationTarget: new FakeNotificationTarget(topic), + autoScalingGroup: asg, + lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_TERMINATING, + }); + + // role with target + new autoscaling.LifecycleHook(this, 'LCHookRoleTarget', { + notificationTarget: new FakeNotificationTarget(topic2), + role: myrole, + autoScalingGroup: asg, + lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_TERMINATING, + }); + } +} + +const app = new cdk.App(); + +new TestStack(app, 'integ-role-target-hook'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-autoscaling/test/lifecyclehooks.test.ts b/packages/@aws-cdk/aws-autoscaling/test/lifecyclehooks.test.ts index 7ebf6d18cbe6e..a1f3717f91042 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/lifecyclehooks.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/lifecyclehooks.test.ts @@ -1,5 +1,5 @@ import '@aws-cdk/assert-internal/jest'; -import { expect, haveResource, ResourcePart } from '@aws-cdk/assert-internal'; +import { ResourcePart } from '@aws-cdk/assert-internal'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; @@ -7,15 +7,10 @@ import * as constructs from 'constructs'; import * as autoscaling from '../lib'; describe('lifecycle hooks', () => { - test('we can add a lifecycle hook to an ASG', () => { + test('we can add a lifecycle hook with no role and with a notifcationTarget to an ASG', () => { // GIVEN const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const asg = new autoscaling.AutoScalingGroup(stack, 'ASG', { - vpc, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), - machineImage: new ec2.AmazonLinuxImage(), - }); + const asg = newASG(stack); // WHEN asg.addLifecycleHook('Transition', { @@ -25,21 +20,22 @@ describe('lifecycle hooks', () => { }); // THEN - expect(stack).to(haveResource('AWS::AutoScaling::LifecycleHook', { + expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { LifecycleTransition: 'autoscaling:EC2_INSTANCE_LAUNCHING', DefaultResult: 'ABANDON', NotificationTargetARN: 'target:arn', - })); + }); // Lifecycle Hook has a dependency on the policy object - expect(stack).to(haveResource('AWS::AutoScaling::LifecycleHook', { + expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { DependsOn: [ 'ASGLifecycleHookTransitionRoleDefaultPolicy2E50C7DB', 'ASGLifecycleHookTransitionRole3AAA6BB7', ], - }, ResourcePart.CompleteDefinition)); + }, ResourcePart.CompleteDefinition); - expect(stack).to(haveResource('AWS::IAM::Role', { + // A default role is provided + expect(stack).toHaveResource('AWS::IAM::Role', { AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [ @@ -52,9 +48,10 @@ describe('lifecycle hooks', () => { }, ], }, - })); + }); - expect(stack).to(haveResource('AWS::IAM::Policy', { + // FakeNotificationTarget.bind() was executed + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Version: '2012-10-17', Statement: [ @@ -65,18 +62,146 @@ describe('lifecycle hooks', () => { }, ], }, - })); + }); + }); +}); + +test('we can add a lifecycle hook to an ASG with no role and with no notificationTargetArn', ()=> { + // GIVEN + const stack = new cdk.Stack(); + const asg = newASG(stack); + + // WHEN + asg.addLifecycleHook('Transition', { + lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_LAUNCHING, + defaultResult: autoscaling.DefaultResult.ABANDON, + }); + + // THEN + expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { + LifecycleTransition: 'autoscaling:EC2_INSTANCE_LAUNCHING', + DefaultResult: 'ABANDON', + }); + + // A default role is NOT provided + expect(stack).not.toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'autoscaling.amazonaws.com', + }, + }, + ], + }, + }); + + // FakeNotificationTarget.bind() was NOT executed + expect(stack).not.toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: 'action:Work', + Effect: 'Allow', + Resource: '*', + }, + ], + }, + }); +}); + +test('we can add a lifecycle hook to an ASG with a role and with a notificationTargetArn', () => { + // GIVEN + const stack = new cdk.Stack(); + const asg = newASG(stack); + const myrole = new iam.Role(stack, 'MyRole', { + assumedBy: new iam.ServicePrincipal('custom.role.domain.com'), + }); + + // WHEN + asg.addLifecycleHook('Transition', { + lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_LAUNCHING, + defaultResult: autoscaling.DefaultResult.ABANDON, + notificationTarget: new FakeNotificationTarget(), + role: myrole, + }); + // THEN + expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { + NotificationTargetARN: 'target:arn', + LifecycleTransition: 'autoscaling:EC2_INSTANCE_LAUNCHING', + DefaultResult: 'ABANDON', + }); + // the provided role (myrole), not the default role, is used + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'custom.role.domain.com', + }, + }, + ], + }, }); }); +test('adding a lifecycle hook with a role and with no notificationTarget to an ASG throws an error', () => { + // GIVEN + const stack = new cdk.Stack(); + const asg = newASG(stack); + const myrole = new iam.Role(stack, 'MyRole', { + assumedBy: new iam.ServicePrincipal('custom.role.domain.com'), + }); + + // WHEN + expect(() => { + asg.addLifecycleHook('Transition', { + lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_LAUNCHING, + defaultResult: autoscaling.DefaultResult.ABANDON, + role: myrole, + }); + }).toThrow(/'notificationTarget' parameter required when 'role' parameter is specified/); +}); + class FakeNotificationTarget implements autoscaling.ILifecycleHookTarget { - public bind(_scope: constructs.Construct, lifecycleHook: autoscaling.ILifecycleHook): autoscaling.LifecycleHookTargetConfig { - lifecycleHook.role.addToPrincipalPolicy(new iam.PolicyStatement({ + private createRole(scope: constructs.Construct, _role?: iam.IRole) { + let role = _role; + if (!role) { + role = new iam.Role(scope, 'Role', { + assumedBy: new iam.ServicePrincipal('autoscaling.amazonaws.com'), + }); + } + + return role; + } + + public bind(_scope: constructs.Construct, options: autoscaling.BindHookTargetOptions): autoscaling.LifecycleHookTargetConfig { + const role = this.createRole(options.lifecycleHook, options.role); + + role.addToPrincipalPolicy(new iam.PolicyStatement({ actions: ['action:Work'], resources: ['*'], })); - return { notificationTargetArn: 'target:arn' }; + + return { notificationTargetArn: 'target:arn', createdRole: role }; } } + +function newASG(stack: cdk.Stack) { + const vpc = new ec2.Vpc(stack, 'VPC'); + + return new autoscaling.AutoScalingGroup(stack, 'ASG', { + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage(), + }); +} diff --git a/packages/@aws-cdk/aws-cloudformation/test/resource.test.ts b/packages/@aws-cdk/aws-cloudformation/test/resource.test.ts index e241171e43df5..c6cefdc714675 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/resource.test.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/resource.test.ts @@ -105,7 +105,7 @@ testDeprecated('custom resource is added twice, lambda is added once', () => { 'Arn', ], }, - 'Runtime': 'python2.7', + 'Runtime': 'python3.9', 'Timeout': 300, }, 'DependsOn': [ @@ -208,7 +208,7 @@ class TestCustomResource extends Construct { const singletonLambda = new lambda.SingletonFunction(this, 'Lambda', { uuid: 'TestCustomResourceProvider', code: new lambda.InlineCode('def hello(): pass'), - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, handler: 'index.hello', timeout: cdk.Duration.minutes(5), }); diff --git a/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts b/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts index 1fe9e745e8079..e095984ed2081 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts @@ -46,6 +46,7 @@ export class EdgeFunction extends Resource implements lambda.IVersion { public readonly permissionsNode: ConstructNode; public readonly role?: iam.IRole; public readonly version: string; + public readonly architecture: lambda.Architecture; private readonly _edgeFunction: lambda.Function; @@ -66,6 +67,7 @@ export class EdgeFunction extends Resource implements lambda.IVersion { this.grantPrincipal = this._edgeFunction.role!; this.permissionsNode = this._edgeFunction.permissionsNode; this.version = lambda.extractQualifierFromArn(this.functionArn); + this.architecture = this._edgeFunction.architecture; this.node.defaultChild = this._edgeFunction; } diff --git a/packages/@aws-cdk/aws-codecommit/README.md b/packages/@aws-cdk/aws-codecommit/README.md index 7dd08eef3bb82..ea263c6d3ab3c 100644 --- a/packages/@aws-cdk/aws-codecommit/README.md +++ b/packages/@aws-cdk/aws-codecommit/README.md @@ -21,7 +21,7 @@ To add a CodeCommit Repository to your stack: ```ts import * as codecommit from '@aws-cdk/aws-codecommit'; -const repo = new codecommit.Repository(this, 'Repository' ,{ +const repo = new codecommit.Repository(this, 'Repository', { repositoryName: 'MyRepositoryName', description: 'Some description.', // optional property }); @@ -37,6 +37,23 @@ To add an Amazon SNS trigger to your repository: repo.notify('arn:aws:sns:*:123456789012:my_topic'); ``` +## Add initial commit + +It is possible to initialize the Repository via the `Code` class. +It provides methods for loading code from a directory, `.zip` file and from a pre-created CDK Asset. + +Example: + +```ts +import * as codecommit from '@aws-cdk/aws-codecommit'; +import * as path from 'path'; + +const repo = new codecommit.Repository(this, 'Repository', { + repositoryName: 'MyRepositoryName', + code: codecommit.Code.fromDirectory(path.join(__dirname, 'directory/'), 'develop'), // optional property, branch parameter can be omitted +}); +``` + ## Events CodeCommit repositories emit Amazon CloudWatch events for certain activities. diff --git a/packages/@aws-cdk/aws-codecommit/lib/code.ts b/packages/@aws-cdk/aws-codecommit/lib/code.ts new file mode 100644 index 0000000000000..fde5d85cb25f4 --- /dev/null +++ b/packages/@aws-cdk/aws-codecommit/lib/code.ts @@ -0,0 +1,104 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as assets from '@aws-cdk/aws-s3-assets'; +import { Construct } from 'constructs'; +import { CfnRepository } from './codecommit.generated'; + +/** + * Represents the structure to pass into the underlying CfnRepository class. + */ +export interface CodeConfig { + /** + * represents the underlying code structure + */ + readonly code: CfnRepository.CodeProperty; +} + +/** + * Represents the contents to initialize the repository with. + */ +export abstract class Code { + /** + * Code from directory. + * @param directoryPath the path to the local directory containing the contents to initialize the repository with + * @param branch the name of the branch to create in the repository. Default is "main" + */ + public static fromDirectory(directoryPath: string, branch?: string): Code { + const resolvedPath = path.resolve(directoryPath); + + const statResult = fs.statSync(resolvedPath); + if (!statResult || !statResult.isDirectory()) { + throw new Error(`'${directoryPath}' needs to be a path to a directory (resolved to: '${resolvedPath }')`); + } + + return new PathResolvedCode(resolvedPath, branch); + } + + /** + * Code from preexisting ZIP file. + * @param filePath the path to the local ZIP file containing the contents to initialize the repository with + * @param branch the name of the branch to create in the repository. Default is "main" + */ + public static fromZipFile(filePath: string, branch?: string): Code { + const resolvedPath = path.resolve(filePath); + + const statResult = fs.statSync(resolvedPath); + if (!statResult || !statResult.isFile()) { + throw new Error(`'${filePath}' needs to be a path to a ZIP file (resolved to: '${resolvedPath }')`); + } + + return new PathResolvedCode(resolvedPath, branch); + } + + /** + * Code from user-supplied asset. + * @param asset pre-existing asset + * @param branch the name of the branch to create in the repository. Default is "main" + */ + public static fromAsset(asset: assets.Asset, branch?: string): Code { + return new AssetCode(asset, branch); + } + + /** + * This method is called after a repository is passed this instance of Code in its 'code' property. + * + * @param scope the binding scope + */ + public abstract bind(scope: Construct): CodeConfig; +} + +class PathResolvedCode extends Code { + constructor(private readonly resolvedPath: string, private readonly branch?: string) { + super(); + } + + public bind(scope: Construct): CodeConfig { + const asset = new assets.Asset(scope, 'PathResolvedCodeAsset', { + path: this.resolvedPath, + }); + + return (new AssetCode(asset, this.branch)).bind(scope); + } +} + +class AssetCode extends Code { + constructor(private readonly asset: assets.Asset, private readonly branch?: string) { + super(); + } + + public bind(_scope: Construct): CodeConfig { + if (!this.asset.isZipArchive) { + throw new Error('Asset must be a .zip file or a directory (resolved to: ' + this.asset.assetPath + ' )'); + } + + return { + code: { + branchName: this.branch, + s3: { + bucket: this.asset.s3BucketName, + key: this.asset.s3ObjectKey, + }, + }, + }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codecommit/lib/index.ts b/packages/@aws-cdk/aws-codecommit/lib/index.ts index 05aa730eb214d..8ef697865609e 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/index.ts +++ b/packages/@aws-cdk/aws-codecommit/lib/index.ts @@ -1,5 +1,6 @@ export * from './events'; export * from './repository'; +export * from './code'; // AWS::CodeCommit CloudFormation Resources: export * from './codecommit.generated'; diff --git a/packages/@aws-cdk/aws-codecommit/lib/repository.ts b/packages/@aws-cdk/aws-codecommit/lib/repository.ts index 69781b04f793c..f32dfa0bacefb 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/repository.ts +++ b/packages/@aws-cdk/aws-codecommit/lib/repository.ts @@ -3,6 +3,7 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import { ArnFormat, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; +import { Code } from './code'; import { CfnRepository } from './codecommit.generated'; /** @@ -488,6 +489,13 @@ export interface RepositoryProps { * @default - No description. */ readonly description?: string; + + /** + * The contents with which to initialize the repository after it has been created. + * + * @default - No initialization (create empty repo) + */ + readonly code?: Code; } /** @@ -552,6 +560,7 @@ export class Repository extends RepositoryBase { repositoryName: props.repositoryName, repositoryDescription: props.description, triggers: Lazy.any({ produce: () => this.triggers }, { omitEmptyArray: true }), + code: (props.code?.bind(this))?.code, }); this.repositoryName = this.getResourceNameAttribute(repository.attrName); diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index 7d69fb323dc4e..92b3f017d5226 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -82,6 +82,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.0.3", "aws-sdk": "^2.848.0", @@ -91,6 +92,7 @@ "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.3.69" }, @@ -99,6 +101,7 @@ "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.3.69" }, diff --git a/packages/@aws-cdk/aws-codecommit/test/asset-test.zip b/packages/@aws-cdk/aws-codecommit/test/asset-test.zip new file mode 100644 index 0000000000000..f00b919ebb64b Binary files /dev/null and b/packages/@aws-cdk/aws-codecommit/test/asset-test.zip differ diff --git a/packages/@aws-cdk/aws-codecommit/test/asset-test/test.md b/packages/@aws-cdk/aws-codecommit/test/asset-test/test.md new file mode 100644 index 0000000000000..21e60f8358c61 --- /dev/null +++ b/packages/@aws-cdk/aws-codecommit/test/asset-test/test.md @@ -0,0 +1 @@ +# Test \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codecommit/test/codecommit.test.ts b/packages/@aws-cdk/aws-codecommit/test/codecommit.test.ts index 927c245b3bdd9..3dca30532e1ad 100644 --- a/packages/@aws-cdk/aws-codecommit/test/codecommit.test.ts +++ b/packages/@aws-cdk/aws-codecommit/test/codecommit.test.ts @@ -1,7 +1,10 @@ import '@aws-cdk/assert-internal/jest'; +import { join, resolve } from 'path'; import { Role, ServicePrincipal } from '@aws-cdk/aws-iam'; -import { Stack } from '@aws-cdk/core'; -import { Repository, RepositoryProps } from '../lib'; +import { Asset } from '@aws-cdk/aws-s3-assets'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import { App, Stack } from '@aws-cdk/core'; +import { Code, Repository, RepositoryProps } from '../lib'; describe('codecommit', () => { describe('CodeCommit Repositories', () => { @@ -66,6 +69,89 @@ describe('codecommit', () => { }); + test('Repository can be initialized with contents from a ZIP file', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'MyStack'); + + // WHEN + new Repository(stack, 'Repository', { + repositoryName: 'MyRepositoryName', + code: Code.fromZipFile(join(__dirname, 'asset-test.zip')), + }); + + // THEN + const assetMetadata = app.synth().tryGetArtifact(stack.stackName)!.findMetadataByType(cxschema.ArtifactMetadataEntryType.ASSET); + expect(assetMetadata).toHaveLength(1); + }); + + test('Repository can be initialized with contents from a directory', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'MyStack'); + + // WHEN + new Repository(stack, 'Repository', { + repositoryName: 'MyRepositoryName', + code: Code.fromDirectory(join(__dirname, 'asset-test')), + }); + + // THEN + const assetMetadata = app.synth().tryGetArtifact(stack.stackName)!.findMetadataByType(cxschema.ArtifactMetadataEntryType.ASSET); + expect(assetMetadata).toHaveLength(1); + }); + + test('Repository can be initialized with contents from an asset', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'MyStack'); + + const readmeAsset = new Asset(stack, 'ReadmeAsset', { + path: join(__dirname, 'asset-test'), + }); + + // WHEN + new Repository(stack, 'Repository', { + repositoryName: 'MyRepositoryName', + code: Code.fromAsset(readmeAsset), + }); + + // THEN + const assetMetadata = app.synth().tryGetArtifact(stack.stackName)!.findMetadataByType(cxschema.ArtifactMetadataEntryType.ASSET); + expect(assetMetadata).toHaveLength(1); + }); + + test('Repository throws Error when initialized with file while expecting directory', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'MyStack'); + const filePath = join(__dirname, 'asset-test/test.md'); + + // THEN + expect(() => { + new Repository(stack, 'Repository', { + repositoryName: 'MyRepositoryName', + code: Code.fromDirectory(filePath), + }); + }).toThrow(`'${filePath}' needs to be a path to a directory (resolved to: '${resolve(filePath)}')`); + }); + + test('Repository throws Error when initialized with directory while expecting file', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'MyStack'); + + const dirPath = join(__dirname, 'asset-test/'); + + // THEN + expect(() => { + new Repository(stack, 'Repository', { + repositoryName: 'MyRepositoryName', + code: Code.fromZipFile(dirPath), + }); + }).toThrow(`'${dirPath}' needs to be a path to a ZIP file (resolved to: '${resolve(dirPath)}')`); + }); + /** * Fix for https://github.com/aws/aws-cdk/issues/10630 */ diff --git a/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-code-asset-zip.expected.json b/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-code-asset-zip.expected.json new file mode 100644 index 0000000000000..168622d89ca47 --- /dev/null +++ b/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-code-asset-zip.expected.json @@ -0,0 +1,64 @@ +{ + "Resources": { + "Repo02AC86CF": { + "Type": "AWS::CodeCommit::Repository", + "Properties": { + "RepositoryName": "aws-cdk-codecommit-repo-contents-zip-file", + "Code": { + "S3": { + "Bucket": { + "Ref": "AssetParametersea7c70c09e0d23ef6105931ee931effc8b607184343aebf5e45e972807b3fc18S3Bucket361A4B4D" + }, + "Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersea7c70c09e0d23ef6105931ee931effc8b607184343aebf5e45e972807b3fc18S3VersionKeyFDE2007C" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersea7c70c09e0d23ef6105931ee931effc8b607184343aebf5e45e972807b3fc18S3VersionKeyFDE2007C" + } + ] + } + ] + } + ] + ] + } + } + } + } + } + }, + "Parameters": { + "AssetParametersea7c70c09e0d23ef6105931ee931effc8b607184343aebf5e45e972807b3fc18S3Bucket361A4B4D": { + "Type": "String", + "Description": "S3 bucket for asset \"ea7c70c09e0d23ef6105931ee931effc8b607184343aebf5e45e972807b3fc18\"" + }, + "AssetParametersea7c70c09e0d23ef6105931ee931effc8b607184343aebf5e45e972807b3fc18S3VersionKeyFDE2007C": { + "Type": "String", + "Description": "S3 key for asset version \"ea7c70c09e0d23ef6105931ee931effc8b607184343aebf5e45e972807b3fc18\"" + }, + "AssetParametersea7c70c09e0d23ef6105931ee931effc8b607184343aebf5e45e972807b3fc18ArtifactHash21ADA702": { + "Type": "String", + "Description": "Artifact hash for asset \"ea7c70c09e0d23ef6105931ee931effc8b607184343aebf5e45e972807b3fc18\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-code-asset-zip.ts b/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-code-asset-zip.ts new file mode 100644 index 0000000000000..c2adb12b71485 --- /dev/null +++ b/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-code-asset-zip.ts @@ -0,0 +1,13 @@ +import * as cdk from '@aws-cdk/core'; +import * as codecommit from '../lib'; +import { Code } from '../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-cdk-codecommit-repo-contents-zip-file'); + +new codecommit.Repository(stack, 'Repo', { + repositoryName: 'aws-cdk-codecommit-repo-contents-zip-file', + code: Code.fromZipFile('./asset-test.zip'), +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-code-asset.expected.json b/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-code-asset.expected.json new file mode 100644 index 0000000000000..715f117d295ed --- /dev/null +++ b/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-code-asset.expected.json @@ -0,0 +1,64 @@ +{ + "Resources": { + "Repo02AC86CF": { + "Type": "AWS::CodeCommit::Repository", + "Properties": { + "RepositoryName": "aws-cdk-codecommit-repo-contents-assets", + "Code": { + "S3": { + "Bucket": { + "Ref": "AssetParameters32b8e8a8b79a84deb31e4d456dbcf3e40937f201633ae38c9e90e15b82084ae3S3BucketD4E005C8" + }, + "Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters32b8e8a8b79a84deb31e4d456dbcf3e40937f201633ae38c9e90e15b82084ae3S3VersionKey52BCEABD" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters32b8e8a8b79a84deb31e4d456dbcf3e40937f201633ae38c9e90e15b82084ae3S3VersionKey52BCEABD" + } + ] + } + ] + } + ] + ] + } + } + } + } + } + }, + "Parameters": { + "AssetParameters32b8e8a8b79a84deb31e4d456dbcf3e40937f201633ae38c9e90e15b82084ae3S3BucketD4E005C8": { + "Type": "String", + "Description": "S3 bucket for asset \"32b8e8a8b79a84deb31e4d456dbcf3e40937f201633ae38c9e90e15b82084ae3\"" + }, + "AssetParameters32b8e8a8b79a84deb31e4d456dbcf3e40937f201633ae38c9e90e15b82084ae3S3VersionKey52BCEABD": { + "Type": "String", + "Description": "S3 key for asset version \"32b8e8a8b79a84deb31e4d456dbcf3e40937f201633ae38c9e90e15b82084ae3\"" + }, + "AssetParameters32b8e8a8b79a84deb31e4d456dbcf3e40937f201633ae38c9e90e15b82084ae3ArtifactHash1A78403B": { + "Type": "String", + "Description": "Artifact hash for asset \"32b8e8a8b79a84deb31e4d456dbcf3e40937f201633ae38c9e90e15b82084ae3\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-code-asset.ts b/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-code-asset.ts new file mode 100644 index 0000000000000..ac94a84ade264 --- /dev/null +++ b/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-code-asset.ts @@ -0,0 +1,13 @@ +import * as cdk from '@aws-cdk/core'; +import * as codecommit from '../lib'; +import { Code } from '../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-cdk-codecommit-repo-contents-assets'); + +new codecommit.Repository(stack, 'Repo', { + repositoryName: 'aws-cdk-codecommit-repo-contents-assets', + code: Code.fromDirectory('./asset-test'), +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts index e377b0f1edb9b..5f30f1493987a 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts @@ -183,6 +183,56 @@ export enum InstanceClass { */ MEMORY5_AMD_NVME_DRIVE = 'r5ad', + /** + * High memory instances (6TB) based on Intel Xeon Platinum 8176M (Skylake) processors, 1st generation + */ + HIGH_MEMORY_6TB_1 = 'u-6tb1', + + /** + * High memory instances (6TB) based on Intel Xeon Platinum 8176M (Skylake) processors, 1st generation + */ + U_6TB1 = 'u-6tb1', + + /** + * High memory instances (9TB) based on Intel Xeon Platinum 8176M (Skylake) processors, 1st generation + */ + HIGH_MEMORY_9TB_1 = 'u-9tb1', + + /** + * High memory instances (9TB) based on Intel Xeon Platinum 8176M (Skylake) processors, 1st generation + */ + U_9TB1 = 'u-9tb1', + + /** + * High memory instances (12TB) based on Intel Xeon Platinum 8176M (Skylake) processors, 1st generation + */ + HIGH_MEMORY_12TB_1 = 'u-12tb1', + + /** + * High memory instances (12TB) based on Intel Xeon Platinum 8176M (Skylake) processors, 1st generation + */ + U_12TB1 = 'u-12tb1', + + /** + * High memory instances (18TB) based on Intel Xeon Scalable (Cascade Lake) processors, 1st generation + */ + HIGH_MEMORY_18TB_1 = 'u-18tb1', + + /** + * High memory instances (18TB) based on Intel Xeon Scalable (Cascade Lake) processors, 1st generation + */ + U_18TB1 = 'u-18tb1', + + /** + * High memory instances (24TB) based on Intel Xeon Scalable (Cascade Lake) processors, 1st generation + */ + HIGH_MEMORY_24TB_1 = 'u-24tb1', + + /** + * High memory instances (24TB) based on Intel Xeon Scalable (Cascade Lake) processors, 1st generation + */ + U_24TB1 = 'u-24tb1', + /** * Memory optimized instances based on AMD EPYC with local NVME drive, 5th generation */ @@ -342,6 +392,26 @@ export enum InstanceClass { */ D2 = 'd2', + /** + * Storage-optimized instances, 3rd generation + */ + STORAGE3 = 'd3', + + /** + * Storage-optimized instances, 3rd generation + */ + D3 = 'd3', + + /** + * Storage-optimized instances, 3rd generation + */ + STORAGE3_ENHANCED_NETWORK = 'd3en', + + /** + * Storage-optimized instances, 3rd generation + */ + D3EN = 'd3en', + /** * Storage/compute balanced instances, 1st generation */ @@ -372,6 +442,26 @@ export enum InstanceClass { */ I3EN = 'i3en', + /** + * Storage optimized instances powered by Graviton2 processor, 4th generation + */ + STORAGE4_GRAVITON_NETWORK_OPTIMIZED = 'im4gn', + + /** + * Storage optimized instances powered by Graviton2 processor, 4th generation + */ + IM4GN = 'im4gn', + + /** + * Storage optimized instances powered by Graviton2 processor, 4th generation + */ + STORAGE4_GRAVITON_NETWORK_STORAGE_OPTIMIZED = 'is4gen', + + /** + * Storage optimized instances powered by Graviton2 processor, 4th generation + */ + IS4GEN = 'is4gen', + /** * Burstable instances, 2nd generation */ @@ -741,6 +831,16 @@ export enum InstanceSize { */ XLARGE48 = '48xlarge', + /** + * Instance size XLARGE56 (56xlarge) + */ + XLARGE56 = '56xlarge', + + /** + * Instance size XLARGE56 (112xlarge) + */ + XLARGE112 = '112xlarge', + /** * Instance size METAL (metal) */ diff --git a/packages/@aws-cdk/aws-ec2/test/instance.test.ts b/packages/@aws-cdk/aws-ec2/test/instance.test.ts index 86326ef7242df..afd3570466eee 100644 --- a/packages/@aws-cdk/aws-ec2/test/instance.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/instance.test.ts @@ -122,7 +122,7 @@ describe('instance', () => { test('instance architecture is correctly discerned for arm instances', () => { // GIVEN const sampleInstanceClasses = [ - 'a1', 't4g', 'c6g', 'c6gd', 'c6gn', 'm6g', 'm6gd', 'r6g', 'r6gd', 'g5g', // current Graviton-based instance classes + 'a1', 't4g', 'c6g', 'c6gd', 'c6gn', 'm6g', 'm6gd', 'r6g', 'r6gd', 'g5g', 'im4gn', 'is4gen', // current Graviton-based instance classes 'a13', 't11g', 'y10ng', 'z11ngd', // theoretical future Graviton-based instance classes ]; diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 2c4f04e64b257..602e046ba2d61 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -405,7 +405,7 @@ declare const parameter: ssm.StringParameter; declare const taskDefinition: ecs.TaskDefinition; declare const s3Bucket: s3.Bucket; -taskDefinition.addContainer('container', { +const newContainer = taskDefinition.addContainer('container', { image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 1024, environment: { // clear text, not for sensitive data @@ -421,6 +421,7 @@ taskDefinition.addContainer('container', { PARAMETER: ecs.Secret.fromSsmParameter(parameter), }, }); +newContainer.addEnvironment('QUEUE_NAME', 'MyQueue'); ``` The task execution role is automatically granted read permissions on the secrets/parameters. Support for environment diff --git a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts index 625041468a0a4..6dd3fd0dbbe40 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts @@ -422,6 +422,8 @@ export class ContainerDefinition extends CoreConstruct { private readonly secrets?: CfnTaskDefinition.SecretProperty[]; + private readonly environment: { [key: string]: string }; + /** * Constructs a new instance of the ContainerDefinition class. */ @@ -457,6 +459,12 @@ export class ContainerDefinition extends CoreConstruct { } } + if (props.environment) { + this.environment = { ...props.environment }; + } else { + this.environment = {}; + } + if (props.environmentFiles) { this.environmentFiles = []; @@ -547,6 +555,13 @@ export class ContainerDefinition extends CoreConstruct { })); } + /** + * This method adds an environment variable to the container. + */ + public addEnvironment(name: string, value: string) { + this.environment[name] = value; + } + /** * This method adds one or more resources to the container. */ @@ -669,7 +684,7 @@ export class ContainerDefinition extends CoreConstruct { volumesFrom: cdk.Lazy.any({ produce: () => this.volumesFrom.map(renderVolumeFrom) }, { omitEmptyArray: true }), workingDirectory: this.props.workingDirectory, logConfiguration: this.logDriverConfig, - environment: this.props.environment && renderKV(this.props.environment, 'name', 'value'), + environment: this.environment && Object.keys(this.environment).length ? renderKV(this.environment, 'name', 'value') : undefined, environmentFiles: this.environmentFiles && renderEnvironmentFiles(this.environmentFiles), secrets: this.secrets, extraHosts: this.props.extraHosts && renderKV(this.props.extraHosts, 'hostname', 'ipAddress'), diff --git a/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts index a384294900342..ac7a7a7824450 100644 --- a/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts @@ -690,13 +690,14 @@ describe('container definition', () => { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); // WHEN - taskDefinition.addContainer('cont', { + const container = taskDefinition.addContainer('cont', { image: ecs.ContainerImage.fromRegistry('test'), memoryLimitMiB: 1024, environment: { TEST_ENVIRONMENT_VARIABLE: 'test environment variable value', }, }); + container.addEnvironment('SECOND_ENVIRONEMENT_VARIABLE', 'second test value'); // THEN expect(stack).toHaveResourceLike('AWS::ECS::TaskDefinition', { @@ -705,6 +706,37 @@ describe('container definition', () => { Environment: [{ Name: 'TEST_ENVIRONMENT_VARIABLE', Value: 'test environment variable value', + }, + { + Name: 'SECOND_ENVIRONEMENT_VARIABLE', + Value: 'second test value', + }], + }, + ], + }); + + + }); + + test('can add environment variables to container definition with no environment', () => { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + + // WHEN + const container = taskDefinition.addContainer('cont', { + image: ecs.ContainerImage.fromRegistry('test'), + memoryLimitMiB: 1024, + }); + container.addEnvironment('SECOND_ENVIRONEMENT_VARIABLE', 'second test value'); + + // THEN + expect(stack).toHaveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Environment: [{ + Name: 'SECOND_ENVIRONEMENT_VARIABLE', + Value: 'second test value', }], }, ], diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.add-environment-variable.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.add-environment-variable.expected.json new file mode 100644 index 0000000000000..8a18b3554d752 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.add-environment-variable.expected.json @@ -0,0 +1,479 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet2EIP3C605A87": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "FargateCluster7CCD5F93": { + "Type": "AWS::ECS::Cluster" + }, + "TaskDefTaskRole1EDB4A67": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDef54694570": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Environment": [ + { + "Name": "nameOne", + "Value": "valueOne" + } + ], + "Essential": true, + "Image": "nginx", + "Name": "nginx", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ] + } + ], + "Cpu": "512", + "Family": "awsecsintegTaskDef6FDFB69A", + "Memory": "1024", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + } + } + }, + "websvcsgA808F313": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ/websvc-sg", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "from 0.0.0.0/0:80", + "FromPort": 80, + "IpProtocol": "tcp", + "ToPort": 80 + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "ServiceD69D759B": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "FargateCluster7CCD5F93" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "EnableECSManagedTags": false, + "LaunchType": "FARGATE", + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "ENABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "websvcsgA808F313", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + ] + } + }, + "TaskDefinition": { + "Ref": "TaskDef54694570" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.add-environment-variable.ts b/packages/@aws-cdk/aws-ecs/test/fargate/integ.add-environment-variable.ts new file mode 100644 index 0000000000000..d42dc6bc98a58 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.add-environment-variable.ts @@ -0,0 +1,37 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as cdk from '@aws-cdk/core'; +import * as ecs from '../../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-integ'); +const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 2 }); +const cluster = new ecs.Cluster(stack, 'FargateCluster', { vpc }); + +const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef', { + memoryLimitMiB: 1024, + cpu: 512, +}); + +// new container with firelens log driver, firelens log router will be created automatically. +const container = taskDefinition.addContainer('nginx', { + image: ecs.ContainerImage.fromRegistry('nginx'), +}); + +container.addPortMappings({ + containerPort: 80, + protocol: ecs.Protocol.TCP, +}); + +// Create a security group that allows tcp @ port 80 +const securityGroup = new ec2.SecurityGroup(stack, 'websvc-sg', { vpc }); +securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80)); +new ecs.FargateService(stack, 'Service', { + cluster, + taskDefinition, + securityGroup, + assignPublicIp: true, +}); + +container.addEnvironment('nameOne', 'valueOne'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts b/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts index d23d7eb3feae0..5aa19af311b35 100644 --- a/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts @@ -106,7 +106,7 @@ test('adding same singleton lambda function as target mutiple times creates perm const fn = new lambda.SingletonFunction(stack, 'MyLambda', { code: new lambda.InlineCode('foo'), handler: 'bar', - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, uuid: 'uuid', }); const rule = new events.Rule(stack, 'Rule', { @@ -133,7 +133,7 @@ test('lambda handler and cloudwatch event across stacks', () => { const fn = new lambda.Function(lambdaStack, 'MyLambda', { code: new lambda.InlineCode('foo'), handler: 'bar', - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, }); const eventStack = new cdk.Stack(app, 'EventStack'); @@ -156,7 +156,7 @@ test('use a Dead Letter Queue for the rule target', () => { const fn = new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('foo'), handler: 'bar', - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, }); const queue = new sqs.Queue(stack, 'Queue'); @@ -248,7 +248,7 @@ test('throw an error when using a Dead Letter Queue for the rule target in a dif const fn = new lambda.Function(stack1, 'MyLambda', { code: new lambda.InlineCode('foo'), handler: 'bar', - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, }); const queue = new sqs.Queue(stack2, 'Queue'); @@ -285,7 +285,7 @@ test('must display a warning when using a Dead Letter Queue from another account const fn = new lambda.Function(stack1, 'MyLambda', { code: new lambda.InlineCode('foo'), handler: 'bar', - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, }); const queue = sqs.Queue.fromQueueArn(stack2, 'Queue', 'arn:aws:sqs:eu-west-1:444455556666:queue1'); @@ -334,7 +334,7 @@ test('specifying retry policy', () => { const fn = new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('foo'), handler: 'bar', - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, }); // WHEN @@ -375,6 +375,6 @@ function newTestLambda(scope: constructs.Construct, suffix = '') { return new lambda.Function(scope, `MyLambda${suffix}`, { code: new lambda.InlineCode('foo'), handler: 'bar', - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, }); } diff --git a/packages/@aws-cdk/aws-iotevents/README.md b/packages/@aws-cdk/aws-iotevents/README.md index e9a17af332776..6dc6a681636cc 100644 --- a/packages/@aws-cdk/aws-iotevents/README.md +++ b/packages/@aws-cdk/aws-iotevents/README.md @@ -1,4 +1,5 @@ # AWS::IoTEvents Construct Library + --- @@ -9,23 +10,45 @@ > > [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. +> They are subject to non-backward compatible changes or removal in any future version. These are +> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be +> announced in the release notes. This means that while you may use them, you may need to update +> your source code when upgrading to a newer version of this package. + --- -This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. +AWS IoT Events enables you to monitor your equipment or device fleets for +failures or changes in operation, and to trigger actions when such events +occur. + +## Installation + +Install the module: + +```console +$ npm i @aws-cdk/aws-iotevents +``` + +Import it into your code: ```ts nofixture import * as iotevents from '@aws-cdk/aws-iotevents'; ``` - - -There are no hand-written ([L2](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) constructs for this service yet. -However, you can still use the automatically generated [L1](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_l1_using) constructs, and use this service exactly as you would using CloudFormation directly. +## `Input` -For more information on the resources and properties available for this service, see the [CloudFormation documentation for AWS::IoTEvents](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_IoTEvents.html). +Add an AWS IoT Events input to your stack: -(Read the [CDK Contributing Guide](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) if you are interested in contributing to this construct library.) +```ts +import * as iotevents from '@aws-cdk/aws-iotevents'; - +new iotevents.Input(this, 'MyInput', { + inputName: 'my_input', + attributeJsonPaths: ['payload.temperature'], +}); +``` diff --git a/packages/@aws-cdk/aws-iotevents/lib/index.ts b/packages/@aws-cdk/aws-iotevents/lib/index.ts index 6390f9ed0b0ee..3851e30984391 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/index.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/index.ts @@ -1,2 +1,4 @@ +export * from './input'; + // AWS::IoTEvents CloudFormation Resources: export * from './iotevents.generated'; diff --git a/packages/@aws-cdk/aws-iotevents/lib/input.ts b/packages/@aws-cdk/aws-iotevents/lib/input.ts new file mode 100644 index 0000000000000..e4bba5684b7a4 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents/lib/input.ts @@ -0,0 +1,71 @@ +import { Resource, IResource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnInput } from './iotevents.generated'; + +/** + * Represents an AWS IoT Events input + */ +export interface IInput extends IResource { + /** + * The name of the input + * @attribute + */ + readonly inputName: string; +} + +/** + * Properties for defining an AWS IoT Events input + */ +export interface InputProps { + /** + * The name of the input + * + * @default - CloudFormation will generate a unique name of the input + */ + readonly inputName?: string, + + /** + * An expression that specifies an attribute-value pair in a JSON structure. + * Use this to specify an attribute from the JSON payload that is made available + * by the input. Inputs are derived from messages sent to AWS IoT Events (BatchPutMessage). + * Each such message contains a JSON payload. The attribute (and its paired value) + * specified here are available for use in the condition expressions used by detectors. + */ + readonly attributeJsonPaths: string[]; +} + +/** + * Defines an AWS IoT Events input in this stack. + */ +export class Input extends Resource implements IInput { + /** + * Import an existing input + */ + public static fromInputName(scope: Construct, id: string, inputName: string): IInput { + class Import extends Resource implements IInput { + public readonly inputName = inputName; + } + return new Import(scope, id); + } + + public readonly inputName: string; + + constructor(scope: Construct, id: string, props: InputProps) { + super(scope, id, { + physicalName: props.inputName, + }); + + if (props.attributeJsonPaths.length === 0) { + throw new Error('attributeJsonPaths property cannot be empty'); + } + + const resource = new CfnInput(this, 'Resource', { + inputName: this.physicalName, + inputDefinition: { + attributes: props.attributeJsonPaths.map(path => ({ jsonPath: path })), + }, + }); + + this.inputName = this.getResourceNameAttribute(resource.ref); + } +} diff --git a/packages/@aws-cdk/aws-iotevents/package.json b/packages/@aws-cdk/aws-iotevents/package.json index 7c69ce3fe3da5..50ed464cf76d7 100644 --- a/packages/@aws-cdk/aws-iotevents/package.json +++ b/packages/@aws-cdk/aws-iotevents/package.json @@ -76,9 +76,11 @@ "devDependencies": { "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", + "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^27.0.3" + "@types/jest": "^27.0.3", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/core": "0.0.0", @@ -92,7 +94,7 @@ "node": ">= 10.13.0 <13 || >=13.7.0" }, "stability": "experimental", - "maturity": "cfn-only", + "maturity": "experimental", "awscdkio": { "announce": false }, diff --git a/packages/@aws-cdk/aws-iotevents/test/input.test.ts b/packages/@aws-cdk/aws-iotevents/test/input.test.ts new file mode 100644 index 0000000000000..11b457bb0cf1b --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents/test/input.test.ts @@ -0,0 +1,78 @@ +import { Template } from '@aws-cdk/assertions'; +import * as cdk from '@aws-cdk/core'; +import * as iotevents from '../lib'; + +test('Default property', () => { + const stack = new cdk.Stack(); + + // WHEN + new iotevents.Input(stack, 'MyInput', { + attributeJsonPaths: ['payload.temperature'], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoTEvents::Input', { + InputDefinition: { + Attributes: [{ JsonPath: 'payload.temperature' }], + }, + }); +}); + +test('can get input name', () => { + const stack = new cdk.Stack(); + // GIVEN + const input = new iotevents.Input(stack, 'MyInput', { + attributeJsonPaths: ['payload.temperature'], + }); + + // WHEN + new cdk.CfnResource(stack, 'Res', { + type: 'Test::Resource', + properties: { + InputName: input.inputName, + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('Test::Resource', { + InputName: { Ref: 'MyInput08947B23' }, + }); +}); + +test('can set physical name', () => { + const stack = new cdk.Stack(); + + // WHEN + new iotevents.Input(stack, 'MyInput', { + inputName: 'test_input', + attributeJsonPaths: ['payload.temperature'], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoTEvents::Input', { + InputName: 'test_input', + }); +}); + +test('can import a Input by inputName', () => { + const stack = new cdk.Stack(); + + // WHEN + const inputName = 'test-input-name'; + const topicRule = iotevents.Input.fromInputName(stack, 'InputFromInputName', inputName); + + // THEN + expect(topicRule).toMatchObject({ + inputName, + }); +}); + +test('cannot be created with an empty array of attributeJsonPaths', () => { + const stack = new cdk.Stack(); + + expect(() => { + new iotevents.Input(stack, 'MyInput', { + attributeJsonPaths: [], + }); + }).toThrow('attributeJsonPaths property cannot be empty'); +}); diff --git a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json new file mode 100644 index 0000000000000..1f5d452b5475d --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json @@ -0,0 +1,17 @@ +{ + "Resources": { + "MyInput08947B23": { + "Type": "AWS::IoTEvents::Input", + "Properties": { + "InputDefinition": { + "Attributes": [ + { + "JsonPath": "payload.temperature" + } + ] + }, + "InputName": "test_input" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts new file mode 100644 index 0000000000000..cb900c83a3f44 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts @@ -0,0 +1,18 @@ +import * as cdk from '@aws-cdk/core'; +import * as iotevents from '../lib'; + +const app = new cdk.App(); + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + new iotevents.Input(this, 'MyInput', { + inputName: 'test_input', + attributeJsonPaths: ['payload.temperature'], + }); + } +} + +new TestStack(app, 'test-stack'); +app.synth(); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts index 0bb00a2c35e5c..171df8ccbf385 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts @@ -5,7 +5,7 @@ import { Architecture } from '@aws-cdk/aws-lambda'; import { Bundling } from './bundling'; import { PackageManager } from './package-manager'; import { BundlingOptions } from './types'; -import { callsites, findUp } from './util'; +import { callsites, findUpMultiple } from './util'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -137,15 +137,20 @@ function findLockFile(depsLockFilePath?: string): string { return path.resolve(depsLockFilePath); } - const lockFile = findUp(PackageManager.PNPM.lockFile) - ?? findUp(PackageManager.YARN.lockFile) - ?? findUp(PackageManager.NPM.lockFile); + const lockFiles = findUpMultiple([ + PackageManager.PNPM.lockFile, + PackageManager.YARN.lockFile, + PackageManager.NPM.lockFile, + ]); - if (!lockFile) { + if (lockFiles.length === 0) { throw new Error('Cannot find a package lock file (`pnpm-lock.yaml`, `yarn.lock` or `package-lock.json`). Please specify it with `depsFileLockPath`.'); } + if (lockFiles.length > 1) { + throw new Error(`Multiple package lock files found: ${lockFiles.join(', ')}. Please specify the desired one with \`depsFileLockPath\`.`); + } - return lockFile; + return lockFiles[0]; } /** diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts index bc754185cf7a6..0ececb74ab95f 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts @@ -35,19 +35,34 @@ export function callsites(): CallSite[] { * Find a file by walking up parent directories */ export function findUp(name: string, directory: string = process.cwd()): string | undefined { + return findUpMultiple([name], directory)[0]; +} + +/** + * Find the lowest of multiple files by walking up parent directories. If + * multiple files exist at the same level, they will all be returned. + */ +export function findUpMultiple(names: string[], directory: string = process.cwd()): string[] { const absoluteDirectory = path.resolve(directory); - const file = path.join(directory, name); - if (fs.existsSync(file)) { - return file; + const files = []; + for (const name of names) { + const file = path.join(directory, name); + if (fs.existsSync(file)) { + files.push(file); + } + } + + if (files.length > 0) { + return files; } const { root } = path.parse(absoluteDirectory); if (absoluteDirectory === root) { - return undefined; + return []; } - return findUp(name, path.dirname(absoluteDirectory)); + return findUpMultiple(names, path.dirname(absoluteDirectory)); } /** 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 ea68bbffa156b..d2249f4f59118 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts @@ -1,7 +1,7 @@ import * as child_process from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; -import { callsites, exec, extractDependencies, findUp } from '../lib/util'; +import { callsites, exec, extractDependencies, findUp, findUpMultiple } from '../lib/util'; beforeEach(() => { jest.clearAllMocks(); @@ -33,6 +33,49 @@ describe('findUp', () => { }); }); +describe('findUpMultiple', () => { + test('Starting at process.cwd()', () => { + const files = findUpMultiple(['README.md', 'package.json']); + expect(files).toHaveLength(2); + expect(files[0]).toMatch(/aws-lambda-nodejs\/README\.md$/); + expect(files[1]).toMatch(/aws-lambda-nodejs\/package\.json$/); + }); + + test('Non existing files', () => { + expect(findUpMultiple(['non-existing-file.unknown', 'non-existing-file.unknown2'])).toEqual([]); + }); + + test('Existing and non existing files', () => { + const files = findUpMultiple(['non-existing-file.unknown', 'README.md']); + expect(files).toHaveLength(1); + expect(files[0]).toMatch(/aws-lambda-nodejs\/README\.md$/); + }); + + test('Starting at a specific path', () => { + const files = findUpMultiple(['util.test.ts', 'function.test.ts'], path.join(__dirname, 'integ-handlers')); + expect(files).toHaveLength(2); + expect(files[0]).toMatch(/aws-lambda-nodejs\/test\/util\.test\.ts$/); + expect(files[1]).toMatch(/aws-lambda-nodejs\/test\/function\.test\.ts$/); + }); + + test('Non existing files starting at a non existing relative path', () => { + expect(findUpMultiple(['not-to-be-found.txt', 'not-to-be-found2.txt'], 'non-existing/relative/path')).toEqual([]); + }); + + test('Starting at a relative path', () => { + const files = findUpMultiple(['util.test.ts', 'function.test.ts'], 'test/integ-handlers'); + expect(files).toHaveLength(2); + expect(files[0]).toMatch(/aws-lambda-nodejs\/test\/util\.test\.ts$/); + expect(files[1]).toMatch(/aws-lambda-nodejs\/test\/function\.test\.ts$/); + }); + + test('Files on multiple levels', () => { + const files = findUpMultiple(['README.md', 'util.test.ts'], path.join(__dirname, 'integ-handlers')); + expect(files).toHaveLength(1); + expect(files[0]).toMatch(/aws-lambda-nodejs\/test\/util\.test\.ts$/); + }); +}); + describe('exec', () => { test('normal execution', () => { const spawnSyncMock = jest.spyOn(child_process, 'spawnSync').mockReturnValue({ diff --git a/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts index 01449e0cadeaa..5043a1501853e 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts @@ -96,7 +96,7 @@ test('Bundling a layer with dependencies', () => { bundle({ entry: entry, - runtime: Runtime.PYTHON_2_7, + runtime: Runtime.PYTHON_3_9, architecture: Architecture.X86_64, outputPathSuffix: 'python', }); @@ -116,7 +116,7 @@ test('Bundling a python code layer', () => { bundle({ entry: path.join(entry, '.'), - runtime: Runtime.PYTHON_2_7, + runtime: Runtime.PYTHON_3_9, architecture: Architecture.X86_64, outputPathSuffix: 'python', }); diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json index 2b64c7f650529..dc2c5477be700 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters2f7cfa4e9b9d7c70c9af6f1820a340c116a1e139fcac4dde381f541331ba5e46S3Bucket84213B6F" + "Ref": "AssetParameters227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7S3BucketBE0FA4A0" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters2f7cfa4e9b9d7c70c9af6f1820a340c116a1e139fcac4dde381f541331ba5e46S3VersionKeyC495D2FD" + "Ref": "AssetParameters227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7S3VersionKeyD39BB444" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters2f7cfa4e9b9d7c70c9af6f1820a340c116a1e139fcac4dde381f541331ba5e46S3VersionKeyC495D2FD" + "Ref": "AssetParameters227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7S3VersionKeyD39BB444" } ] } @@ -85,91 +85,6 @@ "myhandlerinlineServiceRole10C681F6" ] }, - "myhandlerpython27ServiceRole2ED49C06": { - "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" - ] - ] - } - ] - } - }, - "myhandlerpython274D7465EA": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParameters7157876e6cb8bea4bc3b6e272edab8a4d202a0be6bdcecf33b95008fbacc2335S3Bucket1E236877" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters7157876e6cb8bea4bc3b6e272edab8a4d202a0be6bdcecf33b95008fbacc2335S3VersionKeyD0D6DBBF" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters7157876e6cb8bea4bc3b6e272edab8a4d202a0be6bdcecf33b95008fbacc2335S3VersionKeyD0D6DBBF" - } - ] - } - ] - } - ] - ] - } - }, - "Role": { - "Fn::GetAtt": [ - "myhandlerpython27ServiceRole2ED49C06", - "Arn" - ] - }, - "Handler": "index.handler", - "Runtime": "python2.7" - }, - "DependsOn": [ - "myhandlerpython27ServiceRole2ED49C06" - ] - }, "myhandlerpython38ServiceRole2049AFF7": { "Type": "AWS::IAM::Role", "Properties": { @@ -206,7 +121,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters1d8b9cdbaaf7c36dc94604966b3b1f6fb2a972fbddc5cfd62fef7a7a8efd5888S3BucketC4EB0B32" + "Ref": "AssetParameters02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100S3Bucket6E69F943" }, "S3Key": { "Fn::Join": [ @@ -219,7 +134,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters1d8b9cdbaaf7c36dc94604966b3b1f6fb2a972fbddc5cfd62fef7a7a8efd5888S3VersionKeyB8755D2B" + "Ref": "AssetParameters02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100S3VersionKey8350D955" } ] } @@ -232,7 +147,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters1d8b9cdbaaf7c36dc94604966b3b1f6fb2a972fbddc5cfd62fef7a7a8efd5888S3VersionKeyB8755D2B" + "Ref": "AssetParameters02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100S3VersionKey8350D955" } ] } @@ -257,41 +172,29 @@ } }, "Parameters": { - "AssetParameters2f7cfa4e9b9d7c70c9af6f1820a340c116a1e139fcac4dde381f541331ba5e46S3Bucket84213B6F": { - "Type": "String", - "Description": "S3 bucket for asset \"2f7cfa4e9b9d7c70c9af6f1820a340c116a1e139fcac4dde381f541331ba5e46\"" - }, - "AssetParameters2f7cfa4e9b9d7c70c9af6f1820a340c116a1e139fcac4dde381f541331ba5e46S3VersionKeyC495D2FD": { - "Type": "String", - "Description": "S3 key for asset version \"2f7cfa4e9b9d7c70c9af6f1820a340c116a1e139fcac4dde381f541331ba5e46\"" - }, - "AssetParameters2f7cfa4e9b9d7c70c9af6f1820a340c116a1e139fcac4dde381f541331ba5e46ArtifactHashBBA8D438": { + "AssetParameters227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7S3BucketBE0FA4A0": { "Type": "String", - "Description": "Artifact hash for asset \"2f7cfa4e9b9d7c70c9af6f1820a340c116a1e139fcac4dde381f541331ba5e46\"" + "Description": "S3 bucket for asset \"227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7\"" }, - "AssetParameters7157876e6cb8bea4bc3b6e272edab8a4d202a0be6bdcecf33b95008fbacc2335S3Bucket1E236877": { + "AssetParameters227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7S3VersionKeyD39BB444": { "Type": "String", - "Description": "S3 bucket for asset \"7157876e6cb8bea4bc3b6e272edab8a4d202a0be6bdcecf33b95008fbacc2335\"" + "Description": "S3 key for asset version \"227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7\"" }, - "AssetParameters7157876e6cb8bea4bc3b6e272edab8a4d202a0be6bdcecf33b95008fbacc2335S3VersionKeyD0D6DBBF": { + "AssetParameters227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7ArtifactHash7E65B893": { "Type": "String", - "Description": "S3 key for asset version \"7157876e6cb8bea4bc3b6e272edab8a4d202a0be6bdcecf33b95008fbacc2335\"" + "Description": "Artifact hash for asset \"227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7\"" }, - "AssetParameters7157876e6cb8bea4bc3b6e272edab8a4d202a0be6bdcecf33b95008fbacc2335ArtifactHash3C95A517": { + "AssetParameters02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100S3Bucket6E69F943": { "Type": "String", - "Description": "Artifact hash for asset \"7157876e6cb8bea4bc3b6e272edab8a4d202a0be6bdcecf33b95008fbacc2335\"" + "Description": "S3 bucket for asset \"02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100\"" }, - "AssetParameters1d8b9cdbaaf7c36dc94604966b3b1f6fb2a972fbddc5cfd62fef7a7a8efd5888S3BucketC4EB0B32": { + "AssetParameters02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100S3VersionKey8350D955": { "Type": "String", - "Description": "S3 bucket for asset \"1d8b9cdbaaf7c36dc94604966b3b1f6fb2a972fbddc5cfd62fef7a7a8efd5888\"" + "Description": "S3 key for asset version \"02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100\"" }, - "AssetParameters1d8b9cdbaaf7c36dc94604966b3b1f6fb2a972fbddc5cfd62fef7a7a8efd5888S3VersionKeyB8755D2B": { + "AssetParameters02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100ArtifactHashFBCF65DE": { "Type": "String", - "Description": "S3 key for asset version \"1d8b9cdbaaf7c36dc94604966b3b1f6fb2a972fbddc5cfd62fef7a7a8efd5888\"" - }, - "AssetParameters1d8b9cdbaaf7c36dc94604966b3b1f6fb2a972fbddc5cfd62fef7a7a8efd5888ArtifactHash46327689": { - "Type": "String", - "Description": "Artifact hash for asset \"1d8b9cdbaaf7c36dc94604966b3b1f6fb2a972fbddc5cfd62fef7a7a8efd5888\"" + "Description": "Artifact hash for asset \"02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100\"" } }, "Outputs": { @@ -300,11 +203,6 @@ "Ref": "myhandlerinline53D120C7" } }, - "Python27FunctionName": { - "Value": { - "Ref": "myhandlerpython274D7465EA" - } - }, "Python38FunctionName": { "Value": { "Ref": "myhandlerpython384D62BBB5" diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.ts index 2b51c5d1efc67..f41495f0f9419 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.ts @@ -22,14 +22,6 @@ class TestStack extends Stack { value: pythonFunctionInline.functionName, }); - const pythonFunction27 = new lambda.PythonFunction(this, 'my_handler_python_27', { - entry: path.join(__dirname, 'lambda-handler-pipenv'), - runtime: Runtime.PYTHON_2_7, - }); - new CfnOutput(this, 'Python27FunctionName', { - value: pythonFunction27.functionName, - }); - const pythonFunction38 = new lambda.PythonFunction(this, 'my_handler_python_38', { entry: path.join(__dirname, 'lambda-handler-pipenv'), runtime: Runtime.PYTHON_3_8, diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json index 6e305994bd801..0ed5358e2c8c3 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5e3cce416e15bd5ddb77e3ffc9fa3d8f5eac73b0db8c1db7ae3d7f6197c0ecb3S3Bucket666BD1AB" + "Ref": "AssetParameters8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302S3Bucket19CB0678" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e3cce416e15bd5ddb77e3ffc9fa3d8f5eac73b0db8c1db7ae3d7f6197c0ecb3S3VersionKeyA31B2B4A" + "Ref": "AssetParameters8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302S3VersionKeyA2FCDE76" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e3cce416e15bd5ddb77e3ffc9fa3d8f5eac73b0db8c1db7ae3d7f6197c0ecb3S3VersionKeyA31B2B4A" + "Ref": "AssetParameters8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302S3VersionKeyA2FCDE76" } ] } @@ -85,91 +85,6 @@ "myhandlerinlineServiceRole10C681F6" ] }, - "myhandlerpython27ServiceRole2ED49C06": { - "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" - ] - ] - } - ] - } - }, - "myhandlerpython274D7465EA": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParameters12a01ed3f74f4bee61a4c67c4ca842472390f70c4b8bdd2cceb033abe16d7764S3BucketFA6FBCEA" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters12a01ed3f74f4bee61a4c67c4ca842472390f70c4b8bdd2cceb033abe16d7764S3VersionKey55B7E38F" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters12a01ed3f74f4bee61a4c67c4ca842472390f70c4b8bdd2cceb033abe16d7764S3VersionKey55B7E38F" - } - ] - } - ] - } - ] - ] - } - }, - "Role": { - "Fn::GetAtt": [ - "myhandlerpython27ServiceRole2ED49C06", - "Arn" - ] - }, - "Handler": "index.handler", - "Runtime": "python2.7" - }, - "DependsOn": [ - "myhandlerpython27ServiceRole2ED49C06" - ] - }, "myhandlerpython38ServiceRole2049AFF7": { "Type": "AWS::IAM::Role", "Properties": { @@ -206,7 +121,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters22d2bf270f7c8f776322a3bad39e8c690cbaa95a442ae2fec419b259df5632f0S3BucketC2F9D441" + "Ref": "AssetParameters40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9ebS3Bucket07693CB5" }, "S3Key": { "Fn::Join": [ @@ -219,7 +134,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters22d2bf270f7c8f776322a3bad39e8c690cbaa95a442ae2fec419b259df5632f0S3VersionKeyB53188B8" + "Ref": "AssetParameters40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9ebS3VersionKey453B45DF" } ] } @@ -232,7 +147,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters22d2bf270f7c8f776322a3bad39e8c690cbaa95a442ae2fec419b259df5632f0S3VersionKeyB53188B8" + "Ref": "AssetParameters40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9ebS3VersionKey453B45DF" } ] } @@ -257,41 +172,29 @@ } }, "Parameters": { - "AssetParameters5e3cce416e15bd5ddb77e3ffc9fa3d8f5eac73b0db8c1db7ae3d7f6197c0ecb3S3Bucket666BD1AB": { - "Type": "String", - "Description": "S3 bucket for asset \"5e3cce416e15bd5ddb77e3ffc9fa3d8f5eac73b0db8c1db7ae3d7f6197c0ecb3\"" - }, - "AssetParameters5e3cce416e15bd5ddb77e3ffc9fa3d8f5eac73b0db8c1db7ae3d7f6197c0ecb3S3VersionKeyA31B2B4A": { - "Type": "String", - "Description": "S3 key for asset version \"5e3cce416e15bd5ddb77e3ffc9fa3d8f5eac73b0db8c1db7ae3d7f6197c0ecb3\"" - }, - "AssetParameters5e3cce416e15bd5ddb77e3ffc9fa3d8f5eac73b0db8c1db7ae3d7f6197c0ecb3ArtifactHash8BEEBB0C": { + "AssetParameters8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302S3Bucket19CB0678": { "Type": "String", - "Description": "Artifact hash for asset \"5e3cce416e15bd5ddb77e3ffc9fa3d8f5eac73b0db8c1db7ae3d7f6197c0ecb3\"" + "Description": "S3 bucket for asset \"8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302\"" }, - "AssetParameters12a01ed3f74f4bee61a4c67c4ca842472390f70c4b8bdd2cceb033abe16d7764S3BucketFA6FBCEA": { + "AssetParameters8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302S3VersionKeyA2FCDE76": { "Type": "String", - "Description": "S3 bucket for asset \"12a01ed3f74f4bee61a4c67c4ca842472390f70c4b8bdd2cceb033abe16d7764\"" + "Description": "S3 key for asset version \"8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302\"" }, - "AssetParameters12a01ed3f74f4bee61a4c67c4ca842472390f70c4b8bdd2cceb033abe16d7764S3VersionKey55B7E38F": { + "AssetParameters8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302ArtifactHash7948D306": { "Type": "String", - "Description": "S3 key for asset version \"12a01ed3f74f4bee61a4c67c4ca842472390f70c4b8bdd2cceb033abe16d7764\"" + "Description": "Artifact hash for asset \"8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302\"" }, - "AssetParameters12a01ed3f74f4bee61a4c67c4ca842472390f70c4b8bdd2cceb033abe16d7764ArtifactHashF654A092": { + "AssetParameters40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9ebS3Bucket07693CB5": { "Type": "String", - "Description": "Artifact hash for asset \"12a01ed3f74f4bee61a4c67c4ca842472390f70c4b8bdd2cceb033abe16d7764\"" + "Description": "S3 bucket for asset \"40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9eb\"" }, - "AssetParameters22d2bf270f7c8f776322a3bad39e8c690cbaa95a442ae2fec419b259df5632f0S3BucketC2F9D441": { + "AssetParameters40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9ebS3VersionKey453B45DF": { "Type": "String", - "Description": "S3 bucket for asset \"22d2bf270f7c8f776322a3bad39e8c690cbaa95a442ae2fec419b259df5632f0\"" + "Description": "S3 key for asset version \"40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9eb\"" }, - "AssetParameters22d2bf270f7c8f776322a3bad39e8c690cbaa95a442ae2fec419b259df5632f0S3VersionKeyB53188B8": { + "AssetParameters40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9ebArtifactHashFAE08C9B": { "Type": "String", - "Description": "S3 key for asset version \"22d2bf270f7c8f776322a3bad39e8c690cbaa95a442ae2fec419b259df5632f0\"" - }, - "AssetParameters22d2bf270f7c8f776322a3bad39e8c690cbaa95a442ae2fec419b259df5632f0ArtifactHash63D0E537": { - "Type": "String", - "Description": "Artifact hash for asset \"22d2bf270f7c8f776322a3bad39e8c690cbaa95a442ae2fec419b259df5632f0\"" + "Description": "Artifact hash for asset \"40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9eb\"" } }, "Outputs": { @@ -300,11 +203,6 @@ "Ref": "myhandlerinline53D120C7" } }, - "Python27FunctionName": { - "Value": { - "Ref": "myhandlerpython274D7465EA" - } - }, "Python38FunctionName": { "Value": { "Ref": "myhandlerpython384D62BBB5" diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.ts index d3eaafe75a120..4f5060a870486 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.ts @@ -22,14 +22,6 @@ class TestStack extends Stack { value: pythonFunctionInline.functionName, }); - const pythonFunction27 = new lambda.PythonFunction(this, 'my_handler_python_27', { - entry: path.join(__dirname, 'lambda-handler-poetry'), - runtime: Runtime.PYTHON_2_7, - }); - new CfnOutput(this, 'Python27FunctionName', { - value: pythonFunction27.functionName, - }); - const pythonFunction38 = new lambda.PythonFunction(this, 'my_handler_python_38', { entry: path.join(__dirname, 'lambda-handler-poetry'), runtime: Runtime.PYTHON_3_8, diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json index eb7fc23f146c9..7cfe2de877c4a 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersdf0fb94d329926d232a09d16076d3eee0200e6a945f32ff69a97ba787087d563S3Bucket1ACC1E9E" + "Ref": "AssetParameters50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfaS3Bucket954AFCD2" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersdf0fb94d329926d232a09d16076d3eee0200e6a945f32ff69a97ba787087d563S3VersionKeyEA6BC868" + "Ref": "AssetParameters50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfaS3VersionKeyDC672869" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersdf0fb94d329926d232a09d16076d3eee0200e6a945f32ff69a97ba787087d563S3VersionKeyEA6BC868" + "Ref": "AssetParameters50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfaS3VersionKeyDC672869" } ] } @@ -79,7 +79,7 @@ ] }, "Handler": "index.handler", - "Runtime": "python2.7" + "Runtime": "python3.9" }, "DependsOn": [ "functionServiceRoleEF216095" @@ -87,17 +87,17 @@ } }, "Parameters": { - "AssetParametersdf0fb94d329926d232a09d16076d3eee0200e6a945f32ff69a97ba787087d563S3Bucket1ACC1E9E": { + "AssetParameters50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfaS3Bucket954AFCD2": { "Type": "String", - "Description": "S3 bucket for asset \"df0fb94d329926d232a09d16076d3eee0200e6a945f32ff69a97ba787087d563\"" + "Description": "S3 bucket for asset \"50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfa\"" }, - "AssetParametersdf0fb94d329926d232a09d16076d3eee0200e6a945f32ff69a97ba787087d563S3VersionKeyEA6BC868": { + "AssetParameters50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfaS3VersionKeyDC672869": { "Type": "String", - "Description": "S3 key for asset version \"df0fb94d329926d232a09d16076d3eee0200e6a945f32ff69a97ba787087d563\"" + "Description": "S3 key for asset version \"50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfa\"" }, - "AssetParametersdf0fb94d329926d232a09d16076d3eee0200e6a945f32ff69a97ba787087d563ArtifactHash887C8025": { + "AssetParameters50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfaArtifactHash06BB4065": { "Type": "String", - "Description": "Artifact hash for asset \"df0fb94d329926d232a09d16076d3eee0200e6a945f32ff69a97ba787087d563\"" + "Description": "Artifact hash for asset \"50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfa\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.ts index b53754a003778..ca0b62aceb950 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.ts @@ -20,7 +20,7 @@ class TestStack extends Stack { const fn = new lambda.PythonFunction(this, 'function', { entry: workDir, - runtime: Runtime.PYTHON_2_7, + runtime: Runtime.PYTHON_3_9, }); new CfnOutput(this, 'Function', { @@ -43,7 +43,7 @@ fs.copyFileSync(path.join(__dirname, 'lambda-handler', 'index.py'), path.join(wo const requirementsTxtPath = path.join(workDir, 'requirements.txt'); // Write a requirements.txt with an extraneous dependency (colorama) -const beforeDeps = 'certifi==2020.6.20\nchardet==3.0.4\nidna==2.10\nurllib3==1.25.11\nrequests==2.23.0\npillow==6.2.2\ncolorama==0.4.3\n'; +const beforeDeps = 'certifi==2020.6.20\nchardet==3.0.4\nidna==2.10\nurllib3==1.26.7\nrequests==2.26.0\nPillow==8.4.0\ncolorama==0.4.3\n'; fs.writeFileSync(requirementsTxtPath, beforeDeps); // Synth the first time @@ -52,7 +52,7 @@ const stack1 = new TestStack(app, 'cdk-integ-lambda-python-requirements-removed1 app.synth(); // Then, write a requirements.txt without the extraneous dependency and synth again -const afterDeps = 'certifi==2020.6.20\nchardet==3.0.4\nidna==2.10\nurllib3==1.25.11\nrequests==2.23.0\npillow==6.2.2\n'; +const afterDeps = 'certifi==2020.6.20\nchardet==3.0.4\nidna==2.10\nurllib3==1.26.7\nrequests==2.26.0\nPillow==8.4.0\n'; fs.writeFileSync(requirementsTxtPath, afterDeps); // Synth the same stack a second time with different requirements.txt contents diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/Pipfile b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/Pipfile index 2d72b30bc8be8..a309b821c5801 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/Pipfile +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/Pipfile @@ -4,5 +4,5 @@ url = "https://pypi.org/simple" verify_ssl = true [packages] -requests = "==2.23.0" -pillow = "==6.2.2" +requests = "==2.26.0" +Pillow = "==8.4.0" diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/Pipfile.lock b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/Pipfile.lock index 0113c74bf7363..f92befb9e3dd6 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/Pipfile.lock +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "0f782e44e69391c98999b575a7f93228f06d22716a144d955386b9c6abec2040" + "sha256": "fe29bbb3f12db421fd27678820291d33cf6b3dce6bb189274449dee89cf434e8" }, "pipfile-spec": 6, "requires": {}, @@ -16,78 +16,89 @@ "default": { "certifi": { "hashes": [ - "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", - "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" + "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", + "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" ], - "version": "==2020.6.20" + "version": "==2021.10.8" }, - "chardet": { + "charset-normalizer": { "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721", + "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c" ], - "version": "==3.0.4" + "markers": "python_version >= '3'", + "version": "==2.0.9" }, "idna": { "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.10" + "markers": "python_version >= '3'", + "version": "==3.3" }, "pillow": { "hashes": [ - "sha256:00e0bbe9923adc5cc38a8da7d87d4ce16cde53b8d3bba8886cb928e84522d963", - "sha256:03457e439d073770d88afdd90318382084732a5b98b0eb6f49454746dbaae701", - "sha256:0d5c99f80068f13231ac206bd9b2e80ea357f5cf9ae0fa97fab21e32d5b61065", - "sha256:1a3bc8e1db5af40a81535a62a591fafdb30a8a1b319798ea8052aa65ef8f06d2", - "sha256:2b4a94be53dff02af90760c10a2e3634c3c7703410f38c98154d5ce71fe63d20", - "sha256:3ba7d8f1d962780f86aa747fef0baf3211b80cb13310fff0c375da879c0656d4", - "sha256:3e81485cec47c24f5fb27acb485a4fc97376b2b332ed633867dc68ac3077998c", - "sha256:43ef1cff7ee57f9c8c8e6fa02a62eae9fa23a7e34418c7ce88c0e3fe09d1fb38", - "sha256:4adc3302df4faf77c63ab3a83e1a3e34b94a6a992084f4aa1cb236d1deaf4b39", - "sha256:535e8e0e02c9f1fc2e307256149d6ee8ad3aa9a6e24144b7b6e6fb6126cb0e99", - "sha256:5ccfcb0a34ad9b77ad247c231edb781763198f405a5c8dc1b642449af821fb7f", - "sha256:5dcbbaa3a24d091a64560d3c439a8962866a79a033d40eb1a75f1b3413bfc2bc", - "sha256:6e2a7e74d1a626b817ecb7a28c433b471a395c010b2a1f511f976e9ea4363e64", - "sha256:82859575005408af81b3e9171ae326ff56a69af5439d3fc20e8cb76cd51c8246", - "sha256:834dd023b7f987d6b700ad93dc818098d7eb046bd445e9992b3093c6f9d7a95f", - "sha256:87ef0eca169f7f0bc050b22f05c7e174a65c36d584428431e802c0165c5856ea", - "sha256:900de1fdc93764be13f6b39dc0dd0207d9ff441d87ad7c6e97e49b81987dc0f3", - "sha256:92b83b380f9181cacc994f4c983d95a9c8b00b50bf786c66d235716b526a3332", - "sha256:aa1b0297e352007ec781a33f026afbb062a9a9895bb103c8f49af434b1666880", - "sha256:aa4792ab056f51b49e7d59ce5733155e10a918baf8ce50f64405db23d5627fa2", - "sha256:b72c39585f1837d946bd1a829a4820ccf86e361f28cbf60f5d646f06318b61e2", - "sha256:bb7861e4618a0c06c40a2e509c1bea207eea5fd4320d486e314e00745a402ca5", - "sha256:bc149dab804291a18e1186536519e5e122a2ac1316cb80f506e855a500b1cdd4", - "sha256:c424d35a5259be559b64490d0fd9e03fba81f1ce8e5b66e0a59de97547351d80", - "sha256:cbd5647097dc55e501f459dbac7f1d0402225636deeb9e0a98a8d2df649fc19d", - "sha256:ccf16fe444cc43800eeacd4f4769971200982200a71b1368f49410d0eb769543", - "sha256:d3a98444a00b4643b22b0685dbf9e0ddcaf4ebfd4ea23f84f228adf5a0765bb2", - "sha256:d6b4dc325170bee04ca8292bbd556c6f5398d52c6149ca881e67daf62215426f", - "sha256:db9ff0c251ed066d367f53b64827cc9e18ccea001b986d08c265e53625dab950", - "sha256:e3a797a079ce289e59dbd7eac9ca3bf682d52687f718686857281475b7ca8e6a" + "sha256:066f3999cb3b070a95c3652712cffa1a748cd02d60ad7b4e485c3748a04d9d76", + "sha256:0a0956fdc5defc34462bb1c765ee88d933239f9a94bc37d132004775241a7585", + "sha256:0b052a619a8bfcf26bd8b3f48f45283f9e977890263e4571f2393ed8898d331b", + "sha256:1394a6ad5abc838c5cd8a92c5a07535648cdf6d09e8e2d6df916dfa9ea86ead8", + "sha256:1bc723b434fbc4ab50bb68e11e93ce5fb69866ad621e3c2c9bdb0cd70e345f55", + "sha256:244cf3b97802c34c41905d22810846802a3329ddcb93ccc432870243211c79fc", + "sha256:25a49dc2e2f74e65efaa32b153527fc5ac98508d502fa46e74fa4fd678ed6645", + "sha256:2e4440b8f00f504ee4b53fe30f4e381aae30b0568193be305256b1462216feff", + "sha256:3862b7256046fcd950618ed22d1d60b842e3a40a48236a5498746f21189afbbc", + "sha256:3eb1ce5f65908556c2d8685a8f0a6e989d887ec4057326f6c22b24e8a172c66b", + "sha256:3f97cfb1e5a392d75dd8b9fd274d205404729923840ca94ca45a0af57e13dbe6", + "sha256:493cb4e415f44cd601fcec11c99836f707bb714ab03f5ed46ac25713baf0ff20", + "sha256:4acc0985ddf39d1bc969a9220b51d94ed51695d455c228d8ac29fcdb25810e6e", + "sha256:5503c86916d27c2e101b7f71c2ae2cddba01a2cf55b8395b0255fd33fa4d1f1a", + "sha256:5b7bb9de00197fb4261825c15551adf7605cf14a80badf1761d61e59da347779", + "sha256:5e9ac5f66616b87d4da618a20ab0a38324dbe88d8a39b55be8964eb520021e02", + "sha256:620582db2a85b2df5f8a82ddeb52116560d7e5e6b055095f04ad828d1b0baa39", + "sha256:62cc1afda735a8d109007164714e73771b499768b9bb5afcbbee9d0ff374b43f", + "sha256:70ad9e5c6cb9b8487280a02c0ad8a51581dcbbe8484ce058477692a27c151c0a", + "sha256:72b9e656e340447f827885b8d7a15fc8c4e68d410dc2297ef6787eec0f0ea409", + "sha256:72cbcfd54df6caf85cc35264c77ede902452d6df41166010262374155947460c", + "sha256:792e5c12376594bfcb986ebf3855aa4b7c225754e9a9521298e460e92fb4a488", + "sha256:7b7017b61bbcdd7f6363aeceb881e23c46583739cb69a3ab39cb384f6ec82e5b", + "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d", + "sha256:82aafa8d5eb68c8463b6e9baeb4f19043bb31fefc03eb7b216b51e6a9981ae09", + "sha256:84c471a734240653a0ec91dec0996696eea227eafe72a33bd06c92697728046b", + "sha256:8c803ac3c28bbc53763e6825746f05cc407b20e4a69d0122e526a582e3b5e153", + "sha256:93ce9e955cc95959df98505e4608ad98281fff037350d8c2671c9aa86bcf10a9", + "sha256:9a3e5ddc44c14042f0844b8cf7d2cd455f6cc80fd7f5eefbe657292cf601d9ad", + "sha256:a4901622493f88b1a29bd30ec1a2f683782e57c3c16a2dbc7f2595ba01f639df", + "sha256:a5a4532a12314149d8b4e4ad8ff09dde7427731fcfa5917ff16d0291f13609df", + "sha256:b8831cb7332eda5dc89b21a7bce7ef6ad305548820595033a4b03cf3091235ed", + "sha256:b8e2f83c56e141920c39464b852de3719dfbfb6e3c99a2d8da0edf4fb33176ed", + "sha256:c70e94281588ef053ae8998039610dbd71bc509e4acbc77ab59d7d2937b10698", + "sha256:c8a17b5d948f4ceeceb66384727dde11b240736fddeda54ca740b9b8b1556b29", + "sha256:d82cdb63100ef5eedb8391732375e6d05993b765f72cb34311fab92103314649", + "sha256:d89363f02658e253dbd171f7c3716a5d340a24ee82d38aab9183f7fdf0cdca49", + "sha256:d99ec152570e4196772e7a8e4ba5320d2d27bf22fdf11743dd882936ed64305b", + "sha256:ddc4d832a0f0b4c52fff973a0d44b6c99839a9d016fe4e6a1cb8f3eea96479c2", + "sha256:e3dacecfbeec9a33e932f00c6cd7996e62f53ad46fbe677577394aaa90ee419a", + "sha256:eb9fc393f3c61f9054e1ed26e6fe912c7321af2f41ff49d3f83d05bacf22cc78" ], "index": "pypi", - "version": "==6.2.2" + "version": "==8.4.0" }, "requests": { "hashes": [ - "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", - "sha256:5d2d0ffbb515f39417009a46c14256291061ac01ba8f875b90cad137de83beb4", - "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" + "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", + "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" ], "index": "pypi", - "version": "==2.23.0" + "version": "==2.26.0" }, "urllib3": { "hashes": [ - "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a", - "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" + "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", + "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.25.10" + "version": "==1.26.7" } }, "develop": {} diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-poetry/poetry.lock b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-poetry/poetry.lock index 3d343fcf061a2..d07a92e9ef100 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-poetry/poetry.lock +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-poetry/poetry.lock @@ -1,56 +1,59 @@ [[package]] name = "certifi" -version = "2020.11.8" +version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = "*" [[package]] -name = "chardet" -version = "3.0.4" -description = "Universal encoding detector for Python 2 and 3" +name = "charset-normalizer" +version = "2.0.9" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] [[package]] name = "idna" -version = "2.10" +version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [[package]] name = "pillow" -version = "6.2.2" +version = "8.4.0" description = "Python Imaging Library (Fork)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [[package]] name = "requests" -version = "2.23.0" +version = "2.26.0" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<4" -idna = ">=2.5,<3" -urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "urllib3" -version = "1.25.11" +version = "1.26.7" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -63,60 +66,70 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [metadata] lock-version = "1.1" -python-versions = "^2.7" -content-hash = "033798f820d979d69232ab05eb36fb9d517a49e5a6887c6d0e709aea9671464d" +python-versions = "^3.6" +content-hash = "cf158be6b11f3c989228cb0acf2bbb120599853ed83b562d267bfc135eed126a" [metadata.files] certifi = [ - {file = "certifi-2020.11.8-py2.py3-none-any.whl", hash = "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd"}, - {file = "certifi-2020.11.8.tar.gz", hash = "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"}, + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] -chardet = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, +charset-normalizer = [ + {file = "charset-normalizer-2.0.9.tar.gz", hash = "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"}, + {file = "charset_normalizer-2.0.9-py3-none-any.whl", hash = "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] pillow = [ - {file = "Pillow-6.2.2-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:834dd023b7f987d6b700ad93dc818098d7eb046bd445e9992b3093c6f9d7a95f"}, - {file = "Pillow-6.2.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:d3a98444a00b4643b22b0685dbf9e0ddcaf4ebfd4ea23f84f228adf5a0765bb2"}, - {file = "Pillow-6.2.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2b4a94be53dff02af90760c10a2e3634c3c7703410f38c98154d5ce71fe63d20"}, - {file = "Pillow-6.2.2-cp27-cp27m-win32.whl", hash = "sha256:87ef0eca169f7f0bc050b22f05c7e174a65c36d584428431e802c0165c5856ea"}, - {file = "Pillow-6.2.2-cp27-cp27m-win_amd64.whl", hash = "sha256:cbd5647097dc55e501f459dbac7f1d0402225636deeb9e0a98a8d2df649fc19d"}, - {file = "Pillow-6.2.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:4adc3302df4faf77c63ab3a83e1a3e34b94a6a992084f4aa1cb236d1deaf4b39"}, - {file = "Pillow-6.2.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e3a797a079ce289e59dbd7eac9ca3bf682d52687f718686857281475b7ca8e6a"}, - {file = "Pillow-6.2.2-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:bb7861e4618a0c06c40a2e509c1bea207eea5fd4320d486e314e00745a402ca5"}, - {file = "Pillow-6.2.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:535e8e0e02c9f1fc2e307256149d6ee8ad3aa9a6e24144b7b6e6fb6126cb0e99"}, - {file = "Pillow-6.2.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:bc149dab804291a18e1186536519e5e122a2ac1316cb80f506e855a500b1cdd4"}, - {file = "Pillow-6.2.2-cp35-cp35m-win32.whl", hash = "sha256:1a3bc8e1db5af40a81535a62a591fafdb30a8a1b319798ea8052aa65ef8f06d2"}, - {file = "Pillow-6.2.2-cp35-cp35m-win_amd64.whl", hash = "sha256:d6b4dc325170bee04ca8292bbd556c6f5398d52c6149ca881e67daf62215426f"}, - {file = "Pillow-6.2.2-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:43ef1cff7ee57f9c8c8e6fa02a62eae9fa23a7e34418c7ce88c0e3fe09d1fb38"}, - {file = "Pillow-6.2.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:900de1fdc93764be13f6b39dc0dd0207d9ff441d87ad7c6e97e49b81987dc0f3"}, - {file = "Pillow-6.2.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b83b380f9181cacc994f4c983d95a9c8b00b50bf786c66d235716b526a3332"}, - {file = "Pillow-6.2.2-cp36-cp36m-win32.whl", hash = "sha256:00e0bbe9923adc5cc38a8da7d87d4ce16cde53b8d3bba8886cb928e84522d963"}, - {file = "Pillow-6.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:5ccfcb0a34ad9b77ad247c231edb781763198f405a5c8dc1b642449af821fb7f"}, - {file = "Pillow-6.2.2-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:5dcbbaa3a24d091a64560d3c439a8962866a79a033d40eb1a75f1b3413bfc2bc"}, - {file = "Pillow-6.2.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6e2a7e74d1a626b817ecb7a28c433b471a395c010b2a1f511f976e9ea4363e64"}, - {file = "Pillow-6.2.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c424d35a5259be559b64490d0fd9e03fba81f1ce8e5b66e0a59de97547351d80"}, - {file = "Pillow-6.2.2-cp37-cp37m-win32.whl", hash = "sha256:aa4792ab056f51b49e7d59ce5733155e10a918baf8ce50f64405db23d5627fa2"}, - {file = "Pillow-6.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:0d5c99f80068f13231ac206bd9b2e80ea357f5cf9ae0fa97fab21e32d5b61065"}, - {file = "Pillow-6.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03457e439d073770d88afdd90318382084732a5b98b0eb6f49454746dbaae701"}, - {file = "Pillow-6.2.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ccf16fe444cc43800eeacd4f4769971200982200a71b1368f49410d0eb769543"}, - {file = "Pillow-6.2.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b72c39585f1837d946bd1a829a4820ccf86e361f28cbf60f5d646f06318b61e2"}, - {file = "Pillow-6.2.2-cp38-cp38-win32.whl", hash = "sha256:3ba7d8f1d962780f86aa747fef0baf3211b80cb13310fff0c375da879c0656d4"}, - {file = "Pillow-6.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:3e81485cec47c24f5fb27acb485a4fc97376b2b332ed633867dc68ac3077998c"}, - {file = "Pillow-6.2.2-pp273-pypy_73-win32.whl", hash = "sha256:aa1b0297e352007ec781a33f026afbb062a9a9895bb103c8f49af434b1666880"}, - {file = "Pillow-6.2.2-pp373-pypy36_pp73-win32.whl", hash = "sha256:82859575005408af81b3e9171ae326ff56a69af5439d3fc20e8cb76cd51c8246"}, - {file = "Pillow-6.2.2.tar.gz", hash = "sha256:db9ff0c251ed066d367f53b64827cc9e18ccea001b986d08c265e53625dab950"}, + {file = "Pillow-8.4.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d"}, + {file = "Pillow-8.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3f97cfb1e5a392d75dd8b9fd274d205404729923840ca94ca45a0af57e13dbe6"}, + {file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb9fc393f3c61f9054e1ed26e6fe912c7321af2f41ff49d3f83d05bacf22cc78"}, + {file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d82cdb63100ef5eedb8391732375e6d05993b765f72cb34311fab92103314649"}, + {file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc1afda735a8d109007164714e73771b499768b9bb5afcbbee9d0ff374b43f"}, + {file = "Pillow-8.4.0-cp310-cp310-win32.whl", hash = "sha256:e3dacecfbeec9a33e932f00c6cd7996e62f53ad46fbe677577394aaa90ee419a"}, + {file = "Pillow-8.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:620582db2a85b2df5f8a82ddeb52116560d7e5e6b055095f04ad828d1b0baa39"}, + {file = "Pillow-8.4.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:1bc723b434fbc4ab50bb68e11e93ce5fb69866ad621e3c2c9bdb0cd70e345f55"}, + {file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72cbcfd54df6caf85cc35264c77ede902452d6df41166010262374155947460c"}, + {file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70ad9e5c6cb9b8487280a02c0ad8a51581dcbbe8484ce058477692a27c151c0a"}, + {file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25a49dc2e2f74e65efaa32b153527fc5ac98508d502fa46e74fa4fd678ed6645"}, + {file = "Pillow-8.4.0-cp36-cp36m-win32.whl", hash = "sha256:93ce9e955cc95959df98505e4608ad98281fff037350d8c2671c9aa86bcf10a9"}, + {file = "Pillow-8.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2e4440b8f00f504ee4b53fe30f4e381aae30b0568193be305256b1462216feff"}, + {file = "Pillow-8.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8c803ac3c28bbc53763e6825746f05cc407b20e4a69d0122e526a582e3b5e153"}, + {file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8a17b5d948f4ceeceb66384727dde11b240736fddeda54ca740b9b8b1556b29"}, + {file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1394a6ad5abc838c5cd8a92c5a07535648cdf6d09e8e2d6df916dfa9ea86ead8"}, + {file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:792e5c12376594bfcb986ebf3855aa4b7c225754e9a9521298e460e92fb4a488"}, + {file = "Pillow-8.4.0-cp37-cp37m-win32.whl", hash = "sha256:d99ec152570e4196772e7a8e4ba5320d2d27bf22fdf11743dd882936ed64305b"}, + {file = "Pillow-8.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:7b7017b61bbcdd7f6363aeceb881e23c46583739cb69a3ab39cb384f6ec82e5b"}, + {file = "Pillow-8.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:d89363f02658e253dbd171f7c3716a5d340a24ee82d38aab9183f7fdf0cdca49"}, + {file = "Pillow-8.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a0956fdc5defc34462bb1c765ee88d933239f9a94bc37d132004775241a7585"}, + {file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b7bb9de00197fb4261825c15551adf7605cf14a80badf1761d61e59da347779"}, + {file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72b9e656e340447f827885b8d7a15fc8c4e68d410dc2297ef6787eec0f0ea409"}, + {file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5a4532a12314149d8b4e4ad8ff09dde7427731fcfa5917ff16d0291f13609df"}, + {file = "Pillow-8.4.0-cp38-cp38-win32.whl", hash = "sha256:82aafa8d5eb68c8463b6e9baeb4f19043bb31fefc03eb7b216b51e6a9981ae09"}, + {file = "Pillow-8.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:066f3999cb3b070a95c3652712cffa1a748cd02d60ad7b4e485c3748a04d9d76"}, + {file = "Pillow-8.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:5503c86916d27c2e101b7f71c2ae2cddba01a2cf55b8395b0255fd33fa4d1f1a"}, + {file = "Pillow-8.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4acc0985ddf39d1bc969a9220b51d94ed51695d455c228d8ac29fcdb25810e6e"}, + {file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b052a619a8bfcf26bd8b3f48f45283f9e977890263e4571f2393ed8898d331b"}, + {file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:493cb4e415f44cd601fcec11c99836f707bb714ab03f5ed46ac25713baf0ff20"}, + {file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8831cb7332eda5dc89b21a7bce7ef6ad305548820595033a4b03cf3091235ed"}, + {file = "Pillow-8.4.0-cp39-cp39-win32.whl", hash = "sha256:5e9ac5f66616b87d4da618a20ab0a38324dbe88d8a39b55be8964eb520021e02"}, + {file = "Pillow-8.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:3eb1ce5f65908556c2d8685a8f0a6e989d887ec4057326f6c22b24e8a172c66b"}, + {file = "Pillow-8.4.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ddc4d832a0f0b4c52fff973a0d44b6c99839a9d016fe4e6a1cb8f3eea96479c2"}, + {file = "Pillow-8.4.0-pp36-pypy36_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3e5ddc44c14042f0844b8cf7d2cd455f6cc80fd7f5eefbe657292cf601d9ad"}, + {file = "Pillow-8.4.0-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70e94281588ef053ae8998039610dbd71bc509e4acbc77ab59d7d2937b10698"}, + {file = "Pillow-8.4.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:3862b7256046fcd950618ed22d1d60b842e3a40a48236a5498746f21189afbbc"}, + {file = "Pillow-8.4.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4901622493f88b1a29bd30ec1a2f683782e57c3c16a2dbc7f2595ba01f639df"}, + {file = "Pillow-8.4.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c471a734240653a0ec91dec0996696eea227eafe72a33bd06c92697728046b"}, + {file = "Pillow-8.4.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:244cf3b97802c34c41905d22810846802a3329ddcb93ccc432870243211c79fc"}, + {file = "Pillow-8.4.0.tar.gz", hash = "sha256:b8e2f83c56e141920c39464b852de3719dfbfb6e3c99a2d8da0edf4fb33176ed"}, ] requests = [ - {file = "requests-2.23.0-py2.7.egg", hash = "sha256:5d2d0ffbb515f39417009a46c14256291061ac01ba8f875b90cad137de83beb4"}, - {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, - {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, + {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, + {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, ] urllib3 = [ - {file = "urllib3-1.25.11-py2.py3-none-any.whl", hash = "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"}, - {file = "urllib3-1.25.11.tar.gz", hash = "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2"}, + {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, + {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, ] diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-poetry/pyproject.toml b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-poetry/pyproject.toml index 15fec8cc54bec..c4dd461c007a7 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-poetry/pyproject.toml +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-poetry/pyproject.toml @@ -5,9 +5,9 @@ description = "" authors = ["Your Name "] [tool.poetry.dependencies] -python = "^2.7" -requests = "2.23.0" -pillow = "6.2.2" +python = "^3.6" +requests = "2.26.0" +Pillow = "8.4.0" [tool.poetry.dev-dependencies] diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/shared/requirements.txt b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/shared/requirements.txt index 149a1792d9cdb..d87aff1f66a75 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/shared/requirements.txt +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/shared/requirements.txt @@ -2,8 +2,7 @@ certifi==2020.6.20 chardet==3.0.4 idna==2.10 -urllib3==1.25.11 +urllib3==1.26.7 # Requests used by this lambda -requests==2.23.0 -# Pillow 6.x so that python 2.7 and 3.x can both use this fixture -pillow==8.3.2 +requests==2.26.0 +Pillow==8.4.0 diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler/requirements.txt b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler/requirements.txt index 149a1792d9cdb..c636db83b8c9e 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler/requirements.txt +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler/requirements.txt @@ -2,8 +2,8 @@ certifi==2020.6.20 chardet==3.0.4 idna==2.10 -urllib3==1.25.11 +urllib3==1.26.7 # Requests used by this lambda -requests==2.23.0 +requests==2.26.0 # Pillow 6.x so that python 2.7 and 3.x can both use this fixture -pillow==8.3.2 +Pillow==8.4.0 diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index 13a5b81dd35e0..f6c95dfe0d68f 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -411,6 +411,19 @@ new lambda.Function(this, 'MyFunction', { }); ``` +If you are deploying an ARM_64 Lambda Function, you must specify a +Lambda Insights Version >= `1_0_119_0`. + +```ts +new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + architecture: lambda.Architecture.ARM_64, + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), + insightsVersion: lambda.LambdaInsightsVersion.VERSION_1_0_119_0, +}); +``` + ## Event Rule Target You can use an AWS Lambda function as a target for an Amazon CloudWatch event diff --git a/packages/@aws-cdk/aws-lambda/lib/alias.ts b/packages/@aws-cdk/aws-lambda/lib/alias.ts index e497ad2e29071..36fdbdfcc2eaf 100644 --- a/packages/@aws-cdk/aws-lambda/lib/alias.ts +++ b/packages/@aws-cdk/aws-lambda/lib/alias.ts @@ -3,6 +3,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import { ArnFormat } from '@aws-cdk/core'; import { Construct } from 'constructs'; +import { Architecture } from './architecture'; import { EventInvokeConfigOptions } from './event-invoke-config'; import { IFunction, QualifiedFunctionBase } from './function-base'; import { extractQualifierFromArn, IVersion } from './lambda-version'; @@ -97,6 +98,7 @@ export class Alias extends QualifiedFunctionBase implements IAlias { public readonly functionName = `${attrs.aliasVersion.lambda.functionName}:${attrs.aliasName}`; public readonly grantPrincipal = attrs.aliasVersion.grantPrincipal; public readonly role = attrs.aliasVersion.role; + public readonly architecture = attrs.aliasVersion.lambda.architecture; protected readonly canCreatePermissions = this._isStackAccount(); protected readonly qualifier = attrs.aliasName; @@ -120,6 +122,8 @@ export class Alias extends QualifiedFunctionBase implements IAlias { public readonly lambda: IFunction; + public readonly architecture: Architecture; + public readonly version: IVersion; /** @@ -145,6 +149,7 @@ export class Alias extends QualifiedFunctionBase implements IAlias { this.lambda = props.version.lambda; this.aliasName = this.physicalName; this.version = props.version; + this.architecture = this.lambda.architecture; const alias = new CfnAlias(this, 'Resource', { name: this.aliasName, diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index 9b231b4c802c5..a0953b93d1a85 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -3,6 +3,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import { ArnFormat, ConstructNode, IResource, Resource, Token } from '@aws-cdk/core'; import { AliasOptions } from './alias'; +import { Architecture } from './architecture'; import { EventInvokeConfig, EventInvokeConfigOptions } from './event-invoke-config'; import { IEventSource } from './event-source'; import { EventSourceMapping, EventSourceMappingOptions } from './event-source-mapping'; @@ -56,6 +57,11 @@ export interface IFunction extends IResource, ec2.IConnectable, iam.IGrantable { */ readonly permissionsNode: ConstructNode; + /** + * The system architectures compatible with this lambda function. + */ + readonly architecture: Architecture; + /** * Adds an event source that maps to this AWS Lambda function. * @param id construct ID @@ -173,6 +179,12 @@ export interface FunctionAttributes { * For environment-agnostic stacks this will default to `false`. */ readonly sameEnvironment?: boolean; + + /** + * The architecture of this Lambda Function (this is an optional attribute and defaults to X86_64). + * @default - Architecture.X86_64 + */ + readonly architecture?: Architecture; } export abstract class FunctionBase extends Resource implements IFunction, ec2.IClientVpnConnectionHandler { @@ -203,6 +215,11 @@ export abstract class FunctionBase extends Resource implements IFunction, ec2.IC */ public abstract readonly permissionsNode: ConstructNode; + /** + * The architecture of this Lambda Function. + */ + public abstract readonly architecture: Architecture; + /** * Whether the addPermission() call adds any permissions * @@ -521,6 +538,10 @@ class LatestVersion extends FunctionBase implements IVersion { return `${this.lambda.functionName}:${this.version}`; } + public get architecture() { + return this.lambda.architecture; + } + public get grantPrincipal() { return this.lambda.grantPrincipal; } diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 564e45d5a9460..3b5df3c5c5c41 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -450,6 +450,7 @@ export class Function extends FunctionBase { public readonly grantPrincipal: iam.IPrincipal; public readonly role = role; public readonly permissionsNode = this.node; + public readonly architecture = attrs.architecture ?? Architecture.X86_64; protected readonly canCreatePermissions = attrs.sameEnvironment ?? this._isStackAccount(); @@ -576,7 +577,7 @@ export class Function extends FunctionBase { /** * The architecture of this Lambda Function (this is an optional attribute and defaults to X86_64). */ - public readonly architecture?: Architecture; + public readonly architecture: Architecture; /** * The timeout configured for this lambda. @@ -600,6 +601,8 @@ export class Function extends FunctionBase { private readonly currentVersionOptions?: VersionOptions; private _currentVersion?: Version; + private _architecture?: Architecture; + constructor(scope: Construct, id: string, props: FunctionProps) { super(scope, id, { physicalName: props.functionName, @@ -683,7 +686,7 @@ export class Function extends FunctionBase { if (props.architectures && props.architectures.length > 1) { throw new Error('Only one architecture must be specified.'); } - const architecture = props.architecture ?? (props.architectures && props.architectures[0]); + this._architecture = props.architecture ?? (props.architectures && props.architectures[0]); const resource: CfnFunction = new CfnFunction(this, 'Resource', { functionName: this.physicalName, @@ -717,7 +720,7 @@ export class Function extends FunctionBase { kmsKeyArn: props.environmentEncryption?.keyArn, fileSystemConfigs, codeSigningConfigArn: props.codeSigningConfig?.codeSigningConfigArn, - architectures: architecture ? [architecture.name] : undefined, + architectures: this._architecture ? [this._architecture.name] : undefined, }); resource.node.addDependency(this.role); @@ -733,7 +736,7 @@ export class Function extends FunctionBase { this.runtime = props.runtime; this.timeout = props.timeout; - this.architecture = props.architecture; + this.architecture = props.architecture ?? Architecture.X86_64; if (props.layers) { if (props.runtime === Runtime.FROM_IMAGE) { @@ -935,7 +938,7 @@ Environment variables can be marked for removal when used in Lambda@Edge by sett if (props.runtime !== Runtime.FROM_IMAGE) { // Layers cannot be added to Lambda container images. The image should have the insights agent installed. // See https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-Getting-Started-docker.html - this.addLayers(LayerVersion.fromLayerVersionArn(this, 'LambdaInsightsLayer', props.insightsVersion.layerVersionArn)); + this.addLayers(LayerVersion.fromLayerVersionArn(this, 'LambdaInsightsLayer', props.insightsVersion._bind(this, this).arn)); } this.role?.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchLambdaInsightsExecutionRolePolicy')); } diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-insights.ts b/packages/@aws-cdk/aws-lambda/lib/lambda-insights.ts index 2d2b88511786e..c4dacfbde0149 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-insights.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda-insights.ts @@ -1,9 +1,23 @@ import { Aws, CfnMapping, Fn, IResolveContext, Lazy, Stack, Token } from '@aws-cdk/core'; import { FactName, RegionInfo } from '@aws-cdk/region-info'; +import { Construct } from 'constructs'; +import { Architecture } from './architecture'; +import { IFunction } from './function-base'; + // This is the name of the mapping that will be added to the CloudFormation template, if a stack is region agnostic const DEFAULT_MAPPING_PREFIX = 'LambdaInsightsVersions'; +/** + * Config returned from {@link LambdaInsightsVersion._bind} + */ +interface InsightsBindConfig { + /** + * ARN of the Lambda Insights Layer Version + */ + readonly arn: string; +} + // To add new versions, update fact-tables.ts `CLOUDWATCH_LAMBDA_INSIGHTS_ARNS` and create a new `public static readonly VERSION_A_B_C_D` /** @@ -31,6 +45,11 @@ export abstract class LambdaInsightsVersion { */ public static readonly VERSION_1_0_98_0 = LambdaInsightsVersion.fromInsightsVersion('1.0.98.0'); + /** + * Version 1.0.119.0 + */ + public static readonly VERSION_1_0_119_0 = LambdaInsightsVersion.fromInsightsVersion('1.0.119.0'); + /** * Use the insights extension associated with the provided ARN. Make sure the ARN is associated * with same region as your function @@ -40,6 +59,9 @@ export abstract class LambdaInsightsVersion { public static fromInsightVersionArn(arn: string): LambdaInsightsVersion { class InsightsArn extends LambdaInsightsVersion { public readonly layerVersionArn = arn; + public _bind(_scope: Construct, _function: IFunction): InsightsBindConfig { + return { arn }; + } } return new InsightsArn(); } @@ -47,16 +69,25 @@ export abstract class LambdaInsightsVersion { // Use the verison to build the object. Not meant to be called by the user -- user should use e.g. VERSION_1_0_54_0 private static fromInsightsVersion(insightsVersion: string): LambdaInsightsVersion { - // Check if insights version is valid. This should only happen if one of the public static readonly versions are set incorrectly - const versionExists = RegionInfo.regions.some(regionInfo => regionInfo.cloudwatchLambdaInsightsArn(insightsVersion)); - if (!versionExists) { - throw new Error(`Insights version ${insightsVersion} does not exist.`); - } - class InsightsVersion extends LambdaInsightsVersion { public readonly layerVersionArn = Lazy.uncachedString({ produce: (context) => getVersionArn(context, insightsVersion), }); + + public _bind(_scope: Construct, _function: IFunction): InsightsBindConfig { + const arch = _function.architecture?.name ?? Architecture.X86_64.name; + // Check if insights version is valid. This should only happen if one of the public static readonly versions are set incorrectly + // or if the version is not available for the Lambda Architecture + const versionExists = RegionInfo.regions.some(regionInfo => regionInfo.cloudwatchLambdaInsightsArn(insightsVersion, arch)); + if (!versionExists) { + throw new Error(`Insights version ${insightsVersion} does not exist.`); + } + return { + arn: Lazy.uncachedString({ + produce: (context) => getVersionArn(context, insightsVersion, arch), + }), + }; + } } return new InsightsVersion(); } @@ -65,6 +96,13 @@ export abstract class LambdaInsightsVersion { * The arn of the Lambda Insights extension */ public readonly layerVersionArn: string = ''; + + /** + * Returns the arn of the Lambda Insights extension based on the + * Lambda architecture + * @internal + */ + public abstract _bind(_scope: Construct, _function: IFunction): InsightsBindConfig; } /** @@ -73,14 +111,15 @@ export abstract class LambdaInsightsVersion { * * This function is run on CDK synthesis. */ -function getVersionArn(context: IResolveContext, insightsVersion: string): string { +function getVersionArn(context: IResolveContext, insightsVersion: string, architecture?: string): string { const scopeStack = Stack.of(context.scope); const region = scopeStack.region; + const arch = architecture ?? Architecture.X86_64.name; // Region is defined, look up the arn, or throw an error if the version isn't supported by a region if (region !== undefined && !Token.isUnresolved(region)) { - const arn = RegionInfo.get(region).cloudwatchLambdaInsightsArn(insightsVersion); + const arn = RegionInfo.get(region).cloudwatchLambdaInsightsArn(insightsVersion, arch); if (arn === undefined) { throw new Error(`Insights version ${insightsVersion} is not supported in region ${region}`); } @@ -116,19 +155,33 @@ function getVersionArn(context: IResolveContext, insightsVersion: string): strin * -- {'arn': 'arn3'}, * - us-east-2 * -- {'arn': 'arn4'} + * LambdaInsightsVersions101190arm64 // a separate mapping version 1.0.119.0 arm64 + * - us-east-1 + * -- {'arn': 'arn3'}, + * - us-east-2 + * -- {'arn': 'arn4'} */ - const mapName = DEFAULT_MAPPING_PREFIX + insightsVersion.split('.').join(''); + let mapName = DEFAULT_MAPPING_PREFIX + insightsVersion.split('.').join(''); + // if the architecture is arm64 then append that to the end of the name + // this is so that we can have a separate mapping for x86 vs arm in scenarios + // where we have Lambda functions with both architectures in the same stack + if (arch === Architecture.ARM_64.name) { + mapName += arch; + } const mapping: { [k1: string]: { [k2: string]: any } } = {}; - const region2arns = RegionInfo.regionMap(FactName.cloudwatchLambdaInsightsVersion(insightsVersion)); + const region2arns = RegionInfo.regionMap(FactName.cloudwatchLambdaInsightsVersion(insightsVersion, arch)); for (const [reg, arn] of Object.entries(region2arns)) { mapping[reg] = { arn }; } // Only create a given mapping once. If another version of insights is used elsewhere, that mapping will also exist if (!scopeStack.node.tryFindChild(mapName)) { - new CfnMapping(scopeStack, mapName, { mapping }); + // need to call findInMap here if we are going to set lazy=true, otherwise + // we get the informLazyUse info message + const map = new CfnMapping(scopeStack, mapName, { mapping, lazy: true }); + return map.findInMap(Aws.REGION, 'arn'); } // The ARN will be looked up at deployment time from the mapping we created return Fn.findInMap(mapName, Aws.REGION, 'arn'); -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts index 94f3e0b326b16..dbbd1496d8d8c 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts @@ -2,6 +2,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import { Fn, Lazy, RemovalPolicy } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { Alias, AliasOptions } from './alias'; +import { Architecture } from './architecture'; import { EventInvokeConfigOptions } from './event-invoke-config'; import { Function } from './function'; import { IFunction, QualifiedFunctionBase } from './function-base'; @@ -127,11 +128,12 @@ export class Version extends QualifiedFunctionBase implements IVersion { public readonly functionArn = versionArn; public readonly grantPrincipal = lambda.grantPrincipal; public readonly role = lambda.role; + public readonly architecture = lambda.architecture; protected readonly qualifier = version; protected readonly canCreatePermissions = this._isStackAccount(); - public addAlias(name: string, opts: AliasOptions = { }): Alias { + public addAlias(name: string, opts: AliasOptions = {}): Alias { return addAlias(this, this, name, opts); } @@ -153,11 +155,12 @@ export class Version extends QualifiedFunctionBase implements IVersion { public readonly functionArn = `${attrs.lambda.functionArn}:${attrs.version}`; public readonly grantPrincipal = attrs.lambda.grantPrincipal; public readonly role = attrs.lambda.role; + public readonly architecture = attrs.lambda.architecture; protected readonly qualifier = attrs.version; protected readonly canCreatePermissions = this._isStackAccount(); - public addAlias(name: string, opts: AliasOptions = { }): Alias { + public addAlias(name: string, opts: AliasOptions = {}): Alias { return addAlias(this, this, name, opts); } @@ -175,6 +178,7 @@ export class Version extends QualifiedFunctionBase implements IVersion { public readonly lambda: IFunction; public readonly functionArn: string; public readonly functionName: string; + public readonly architecture: Architecture; protected readonly qualifier: string; protected readonly canCreatePermissions = true; @@ -183,6 +187,7 @@ export class Version extends QualifiedFunctionBase implements IVersion { super(scope, id); this.lambda = props.lambda; + this.architecture = props.lambda.architecture; const version = new CfnVersion(this, 'Resource', { codeSha256: props.codeSha256, @@ -239,7 +244,7 @@ export class Version extends QualifiedFunctionBase implements IVersion { * @param aliasName The name of the alias (e.g. "live") * @param options Alias options */ - public addAlias(aliasName: string, options: AliasOptions = { }): Alias { + public addAlias(aliasName: string, options: AliasOptions = {}): Alias { return addAlias(this, this, aliasName, options); } diff --git a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts index c096730b1e8eb..7ee0cf016e52d 100644 --- a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts @@ -3,6 +3,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; +import { Architecture } from './architecture'; import { Function as LambdaFunction, FunctionProps, EnvironmentOptions } from './function'; import { FunctionBase } from './function-base'; import { Version } from './lambda-version'; @@ -50,6 +51,7 @@ export class SingletonFunction extends FunctionBase { public readonly functionArn: string; public readonly role?: iam.IRole; public readonly permissionsNode: cdk.ConstructNode; + public readonly architecture: Architecture; /** * The runtime environment for the Lambda function. @@ -64,6 +66,7 @@ export class SingletonFunction extends FunctionBase { this.lambdaFunction = this.ensureLambda(props); this.permissionsNode = this.lambdaFunction.node; + this.architecture = this.lambdaFunction.architecture; this.functionArn = this.lambdaFunction.functionArn; this.functionName = this.lambdaFunction.functionName; diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index bd25c33b7a109..7b8902698de9b 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -157,7 +157,7 @@ describe('function', () => { 'Arn', ], }, - Runtime: 'python2.7', + Runtime: 'python3.9', }, DependsOn: [ 'MyLambdaServiceRole4539ECB6', @@ -2276,6 +2276,6 @@ function newTestLambda(scope: constructs.Construct) { return new lambda.Function(scope, 'MyLambda', { code: new lambda.InlineCode('foo'), handler: 'bar', - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, }); } diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda-insights-mapping.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.lambda-insights-mapping.expected.json index 7c6fabf1b1fa2..e0975af7723cd 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda-insights-mapping.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda-insights-mapping.expected.json @@ -67,7 +67,7 @@ ] } ], - "Runtime": "nodejs10.x" + "Runtime": "nodejs14.x" }, "DependsOn": [ "MyFunc1ServiceRoleF96C5B5C" @@ -140,7 +140,7 @@ ] } ], - "Runtime": "nodejs10.x" + "Runtime": "nodejs14.x" }, "DependsOn": [ "MyFunc2ServiceRole68E50443" @@ -213,7 +213,7 @@ ] } ], - "Runtime": "nodejs10.x" + "Runtime": "nodejs14.x" }, "DependsOn": [ "MyFunc3ServiceRoleA69795ED" @@ -286,11 +286,160 @@ ] } ], - "Runtime": "nodejs10.x" + "Runtime": "nodejs14.x" }, "DependsOn": [ "MyFunc4ServiceRole93C4DEFF" ] + }, + "MyFunc5ServiceRoleFE4CE92B": { + "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" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy" + ] + ] + } + ] + } + }, + "MyFunc586573B53": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function handler(event, _context, callback) {\n console.log(JSON.stringify(event, undefined, 2));\n return callback();\n}" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunc5ServiceRoleFE4CE92B", + "Arn" + ] + }, + "Handler": "index.handler", + "Layers": [ + { + "Fn::FindInMap": [ + "LambdaInsightsVersions101190", + { + "Ref": "AWS::Region" + }, + "arn" + ] + } + ], + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "MyFunc5ServiceRoleFE4CE92B" + ] + }, + "MyFunc6ServiceRoleCDDBC2C6": { + "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" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy" + ] + ] + } + ] + } + }, + "MyFunc60D944984": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function handler(event, _context, callback) {\n console.log(JSON.stringify(event, undefined, 2));\n return callback();\n}" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunc6ServiceRoleCDDBC2C6", + "Arn" + ] + }, + "Architectures": [ + "arm64" + ], + "Handler": "index.handler", + "Layers": [ + { + "Fn::FindInMap": [ + "LambdaInsightsVersions101190arm64", + { + "Ref": "AWS::Region" + }, + "arn" + ] + } + ], + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "MyFunc6ServiceRoleCDDBC2C6" + ] } }, "Mappings": { @@ -511,6 +660,106 @@ "us-west-2": { "arn": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:14" } + }, + "LambdaInsightsVersions101190": { + "af-south-1": { + "arn": "arn:aws:lambda:af-south-1:012438385374:layer:LambdaInsightsExtension:9" + }, + "ap-east-1": { + "arn": "arn:aws:lambda:ap-east-1:519774774795:layer:LambdaInsightsExtension:9" + }, + "ap-northeast-1": { + "arn": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:23" + }, + "ap-northeast-2": { + "arn": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:16" + }, + "ap-south-1": { + "arn": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:16" + }, + "ap-southeast-1": { + "arn": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:16" + }, + "ap-southeast-2": { + "arn": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:16" + }, + "ca-central-1": { + "arn": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:16" + }, + "cn-north-1": { + "arn": "arn:aws-cn:lambda:cn-north-1:488211338238:layer:LambdaInsightsExtension:9" + }, + "cn-northwest-1": { + "arn": "arn:aws-cn:lambda:cn-northwest-1:488211338238:layer:LambdaInsightsExtension:9" + }, + "eu-central-1": { + "arn": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:16" + }, + "eu-north-1": { + "arn": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:16" + }, + "eu-south-1": { + "arn": "arn:aws:lambda:eu-south-1:339249233099:layer:LambdaInsightsExtension:9" + }, + "eu-west-1": { + "arn": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:16" + }, + "eu-west-2": { + "arn": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:16" + }, + "eu-west-3": { + "arn": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:16" + }, + "me-south-1": { + "arn": "arn:aws:lambda:me-south-1:285320876703:layer:LambdaInsightsExtension:9" + }, + "sa-east-1": { + "arn": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:16" + }, + "us-east-1": { + "arn": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:16" + }, + "us-east-2": { + "arn": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:16" + }, + "us-west-1": { + "arn": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:16" + }, + "us-west-2": { + "arn": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:16" + } + }, + "LambdaInsightsVersions101190arm64": { + "ap-northeast-1": { + "arn": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension-Arm64:1" + }, + "ap-south-1": { + "arn": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension-Arm64:1" + }, + "ap-southeast-1": { + "arn": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension-Arm64:1" + }, + "ap-southeast-2": { + "arn": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension-Arm64:1" + }, + "eu-central-1": { + "arn": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension-Arm64:1" + }, + "eu-west-1": { + "arn": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension-Arm64:1" + }, + "eu-west-2": { + "arn": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:1" + }, + "us-east-1": { + "arn": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension-Arm64:1" + }, + "us-east-2": { + "arn": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension-Arm64:1" + }, + "us-west-2": { + "arn": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:1" + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda-insights-mapping.ts b/packages/@aws-cdk/aws-lambda/test/integ.lambda-insights-mapping.ts index 78cf8d74106ed..8fce7e06fdd97 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda-insights-mapping.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda-insights-mapping.ts @@ -6,33 +6,48 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'stack'); new lambda.Function(stack, 'MyFunc1', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.Code.fromInline(`exports.handler = ${handler.toString()}`), insightsVersion: lambda.LambdaInsightsVersion.VERSION_1_0_54_0, }); new lambda.Function(stack, 'MyFunc2', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.Code.fromInline(`exports.handler = ${handler.toString()}`), insightsVersion: lambda.LambdaInsightsVersion.VERSION_1_0_86_0, }); new lambda.Function(stack, 'MyFunc3', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.Code.fromInline(`exports.handler = ${handler.toString()}`), insightsVersion: lambda.LambdaInsightsVersion.VERSION_1_0_89_0, }); new lambda.Function(stack, 'MyFunc4', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.Code.fromInline(`exports.handler = ${handler.toString()}`), insightsVersion: lambda.LambdaInsightsVersion.VERSION_1_0_98_0, }); +new lambda.Function(stack, 'MyFunc5', { + runtime: lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + code: lambda.Code.fromInline(`exports.handler = ${handler.toString()}`), + insightsVersion: lambda.LambdaInsightsVersion.VERSION_1_0_119_0, +}); + +new lambda.Function(stack, 'MyFunc6', { + runtime: lambda.Runtime.NODEJS_14_X, + architecture: lambda.Architecture.ARM_64, + handler: 'index.handler', + code: lambda.Code.fromInline(`exports.handler = ${handler.toString()}`), + insightsVersion: lambda.LambdaInsightsVersion.VERSION_1_0_119_0, +}); + app.synth(); /* eslint-disable no-console */ diff --git a/packages/@aws-cdk/aws-lambda/test/lambda-insights.test.ts b/packages/@aws-cdk/aws-lambda/test/lambda-insights.test.ts index 762df158da6f4..29bfee2f02615 100644 --- a/packages/@aws-cdk/aws-lambda/test/lambda-insights.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/lambda-insights.test.ts @@ -7,11 +7,17 @@ import * as lambda from '../lib'; /** * Boilerplate code to create a Function with a given insights version */ -function functionWithInsightsVersion(stack: cdk.Stack, id: string, insightsVersion: lambda.LambdaInsightsVersion): lambda.IFunction { +function functionWithInsightsVersion( + stack: cdk.Stack, + id: string, + insightsVersion: lambda.LambdaInsightsVersion, + architecture?: lambda.Architecture, +): lambda.IFunction { return new lambda.Function(stack, id, { code: new lambda.InlineCode('foo'), handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, + architecture, insightsVersion, }); } @@ -337,7 +343,7 @@ describe('lambda-insights', () => { ], }, 'ManagedPolicyArns': [ - { }, + {}, { 'Fn::Join': [ '', @@ -353,4 +359,304 @@ describe('lambda-insights', () => { ], }); }); + + test('can use with arm architecture', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack', { + env: { account: '123456789012', region: 'us-east-1' }, + }); + functionWithInsightsVersion(stack, 'MyLambda', lambda.LambdaInsightsVersion.VERSION_1_0_119_0, lambda.Architecture.ARM_64); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Layers: ['arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension-Arm64:1'], + }); + + // On synthesis it should not throw an error + expect(() => app.synth()).not.toThrow(); + }); + + test('throws if arm is not available in this version', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack', { + env: { account: '123456789012', region: 'us-east-1' }, + }); + expect(() => functionWithInsightsVersion(stack, 'MyLambda', lambda.LambdaInsightsVersion.VERSION_1_0_98_0, lambda.Architecture.ARM_64)).toThrow('Insights version 1.0.98.0 does not exist.'); + }); + test('throws if arm is available in this version, but not in this region', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack', { + env: { account: '123456789012', region: 'us-west-1' }, + }); + functionWithInsightsVersion(stack, 'MyLambda', lambda.LambdaInsightsVersion.VERSION_1_0_119_0, lambda.Architecture.ARM_64); + + // On synthesis it should not throw an error + expect(() => app.synth()).toThrow('Insights version 1.0.119.0 is not supported in region us-west-1'); + }); + + test('can create two functions, with different architectures in a region agnostic stack with the same version', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack', {}); + + functionWithInsightsVersion(stack, 'MyLambda1', lambda.LambdaInsightsVersion.VERSION_1_0_119_0); + functionWithInsightsVersion(stack, 'MyLambda2', lambda.LambdaInsightsVersion.VERSION_1_0_119_0, lambda.Architecture.ARM_64); + + /* eslint-disable quote-props */ + expect(stack).toMatchTemplate({ + Resources: { + MyLambda1ServiceRole69A7E1EA: { + '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', + ], + ], + }, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy', + ], + ], + }, + ], + }, + }, + MyLambda1AAFB4554: { + 'Type': 'AWS::Lambda::Function', + 'Properties': { + 'Code': { + 'ZipFile': 'foo', + }, + 'Role': { + 'Fn::GetAtt': [ + 'MyLambda1ServiceRole69A7E1EA', + 'Arn', + ], + }, + 'Handler': 'index.handler', + 'Layers': [ + { + 'Fn::FindInMap': [ + 'LambdaInsightsVersions101190', + { + 'Ref': 'AWS::Region', + }, + 'arn', + ], + }, + ], + 'Runtime': 'nodejs10.x', + }, + 'DependsOn': [ + 'MyLambda1ServiceRole69A7E1EA', + ], + }, + MyLambda2ServiceRoleD09B370C: { + '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', + ], + ], + }, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy', + ], + ], + }, + ], + }, + }, + MyLambda2254B54D5: { + 'Type': 'AWS::Lambda::Function', + 'Properties': { + 'Code': { + 'ZipFile': 'foo', + }, + 'Role': { + 'Fn::GetAtt': [ + 'MyLambda2ServiceRoleD09B370C', + 'Arn', + ], + }, + 'Architectures': [ + 'arm64', + ], + 'Handler': 'index.handler', + 'Layers': [ + { + 'Fn::FindInMap': [ + 'LambdaInsightsVersions101190arm64', + { + 'Ref': 'AWS::Region', + }, + 'arn', + ], + }, + ], + 'Runtime': 'nodejs10.x', + }, + 'DependsOn': [ + 'MyLambda2ServiceRoleD09B370C', + ], + }, + }, + Mappings: { + LambdaInsightsVersions101190: { + 'af-south-1': { + 'arn': 'arn:aws:lambda:af-south-1:012438385374:layer:LambdaInsightsExtension:9', + }, + 'ap-east-1': { + 'arn': 'arn:aws:lambda:ap-east-1:519774774795:layer:LambdaInsightsExtension:9', + }, + 'ap-northeast-1': { + 'arn': 'arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:23', + }, + 'ap-northeast-2': { + 'arn': 'arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:16', + }, + 'ap-south-1': { + 'arn': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:16', + }, + 'ap-southeast-1': { + 'arn': 'arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:16', + }, + 'ap-southeast-2': { + 'arn': 'arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:16', + }, + 'ca-central-1': { + 'arn': 'arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:16', + }, + 'cn-north-1': { + 'arn': 'arn:aws-cn:lambda:cn-north-1:488211338238:layer:LambdaInsightsExtension:9', + }, + 'cn-northwest-1': { + 'arn': 'arn:aws-cn:lambda:cn-northwest-1:488211338238:layer:LambdaInsightsExtension:9', + }, + 'eu-central-1': { + 'arn': 'arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:16', + }, + 'eu-north-1': { + 'arn': 'arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:16', + }, + 'eu-south-1': { + 'arn': 'arn:aws:lambda:eu-south-1:339249233099:layer:LambdaInsightsExtension:9', + }, + 'eu-west-1': { + 'arn': 'arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:16', + }, + 'eu-west-2': { + 'arn': 'arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:16', + }, + 'eu-west-3': { + 'arn': 'arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:16', + }, + 'me-south-1': { + 'arn': 'arn:aws:lambda:me-south-1:285320876703:layer:LambdaInsightsExtension:9', + }, + 'sa-east-1': { + 'arn': 'arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:16', + }, + 'us-east-1': { + 'arn': 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:16', + }, + 'us-east-2': { + 'arn': 'arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:16', + }, + 'us-west-1': { + 'arn': 'arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:16', + }, + 'us-west-2': { + 'arn': 'arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:16', + }, + }, + 'LambdaInsightsVersions101190arm64': { + 'ap-northeast-1': { + 'arn': 'arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension-Arm64:1', + }, + 'ap-south-1': { + 'arn': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension-Arm64:1', + }, + 'ap-southeast-1': { + 'arn': 'arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension-Arm64:1', + }, + 'ap-southeast-2': { + 'arn': 'arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension-Arm64:1', + }, + 'eu-central-1': { + 'arn': 'arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension-Arm64:1', + }, + 'eu-west-1': { + 'arn': 'arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension-Arm64:1', + }, + 'eu-west-2': { + 'arn': 'arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:1', + }, + 'us-east-1': { + 'arn': 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension-Arm64:1', + }, + 'us-east-2': { + 'arn': 'arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension-Arm64:1', + }, + 'us-west-2': { + 'arn': 'arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:1', + }, + }, + }, + }, MatchStyle.EXACT); + // On synthesis it should not throw an error + expect(() => app.synth()).not.toThrow(); + }); }); diff --git a/packages/@aws-cdk/aws-lambda/test/python-lambda-handler/requirements.txt b/packages/@aws-cdk/aws-lambda/test/python-lambda-handler/requirements.txt index b4500579db515..a8ed785e41af0 100644 --- a/packages/@aws-cdk/aws-lambda/test/python-lambda-handler/requirements.txt +++ b/packages/@aws-cdk/aws-lambda/test/python-lambda-handler/requirements.txt @@ -1 +1 @@ -requests==2.23.0 +requests==2.26.0 diff --git a/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts b/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts index 4200b7a18a6e5..1e9f984b4aee5 100644 --- a/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts @@ -16,7 +16,7 @@ describe('singleton lambda', () => { new lambda.SingletonFunction(stack, `Singleton${i}`, { uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', code: new lambda.InlineCode('def hello(): pass'), - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, handler: 'index.hello', timeout: cdk.Duration.minutes(5), }); @@ -53,7 +53,7 @@ describe('singleton lambda', () => { }, Handler: 'index.hello', Role: { 'Fn::GetAtt': ['SingletonLambda84c0de93353f42179b0b45b6c993251aServiceRole26D59235', 'Arn'] }, - Runtime: 'python2.7', + Runtime: 'python3.9', Timeout: 300, }, DependsOn: ['SingletonLambda84c0de93353f42179b0b45b6c993251aServiceRole26D59235'], @@ -68,7 +68,7 @@ describe('singleton lambda', () => { const singleton = new lambda.SingletonFunction(stack, 'Singleton', { uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', code: new lambda.InlineCode('def hello(): pass'), - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, handler: 'index.hello', timeout: cdk.Duration.minutes(5), }); @@ -92,7 +92,7 @@ describe('singleton lambda', () => { const singleton = new lambda.SingletonFunction(stack, 'Singleton', { uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', code: new lambda.InlineCode('def hello(): pass'), - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, handler: 'index.hello', timeout: cdk.Duration.minutes(5), }); @@ -116,7 +116,7 @@ describe('singleton lambda', () => { const singleton = new lambda.SingletonFunction(stack, 'Singleton', { uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', code: new lambda.InlineCode('def hello(): pass'), - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, handler: 'index.hello', timeout: cdk.Duration.minutes(5), }); @@ -140,14 +140,14 @@ describe('singleton lambda', () => { const singleton = new lambda.SingletonFunction(stack, 'Singleton', { uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', code: new lambda.InlineCode('def hello(): pass'), - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, handler: 'index.hello', timeout: cdk.Duration.minutes(5), }); const bucket = new s3.Bucket(stack, 'Bucket'); const layer = new lambda.LayerVersion(stack, 'myLayer', { code: new lambda.S3Code(bucket, 'ObjectKey'), - compatibleRuntimes: [lambda.Runtime.PYTHON_2_7], + compatibleRuntimes: [lambda.Runtime.PYTHON_3_9], }); // WHEN @@ -167,7 +167,7 @@ describe('singleton lambda', () => { const singleton = new lambda.SingletonFunction(stack, 'Singleton', { uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', code: new lambda.InlineCode('def hello(): pass'), - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, handler: 'index.hello', }); @@ -194,7 +194,7 @@ describe('singleton lambda', () => { const singleton = new lambda.SingletonFunction(stack, 'Singleton', { uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', code: new lambda.InlineCode('def hello(): pass'), - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, handler: 'index.hello', environment: { KEY: 'value', @@ -214,7 +214,7 @@ describe('singleton lambda', () => { const singleton = new lambda.SingletonFunction(stack, 'Singleton', { uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', code: new lambda.InlineCode('def hello(): pass'), - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, handler: 'index.hello', timeout: cdk.Duration.minutes(5), }); @@ -232,13 +232,13 @@ describe('singleton lambda', () => { const singleton = new lambda.SingletonFunction(stack, 'Singleton', { uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', code: new lambda.InlineCode('def hello(): pass'), - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, handler: 'index.hello', timeout: cdk.Duration.minutes(5), }); // THEN - expect(singleton.runtime).toStrictEqual(lambda.Runtime.PYTHON_2_7); + expect(singleton.runtime).toStrictEqual(lambda.Runtime.PYTHON_3_9); }); test('current version of a singleton function', () => { diff --git a/packages/@aws-cdk/aws-lex/.eslintrc.js b/packages/@aws-cdk/aws-lex/.eslintrc.js new file mode 100644 index 0000000000000..2658ee8727166 --- /dev/null +++ b/packages/@aws-cdk/aws-lex/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-lex/.gitignore b/packages/@aws-cdk/aws-lex/.gitignore new file mode 100644 index 0000000000000..62ebc95d75ce6 --- /dev/null +++ b/packages/@aws-cdk/aws-lex/.gitignore @@ -0,0 +1,19 @@ +*.js +*.js.map +*.d.ts +tsconfig.json +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk +nyc.config.js +!.eslintrc.js +!jest.config.js +junit.xml diff --git a/packages/@aws-cdk/aws-lex/.npmignore b/packages/@aws-cdk/aws-lex/.npmignore new file mode 100644 index 0000000000000..f931fede67c44 --- /dev/null +++ b/packages/@aws-cdk/aws-lex/.npmignore @@ -0,0 +1,29 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js + +# Include .jsii +!.jsii + +*.snk + +*.tsbuildinfo + +tsconfig.json + +.eslintrc.js +jest.config.js + +# exclude cdk artifacts +**/cdk.out +junit.xml +test/ +!*.lit.ts diff --git a/packages/@aws-cdk/aws-lex/LICENSE b/packages/@aws-cdk/aws-lex/LICENSE new file mode 100644 index 0000000000000..28e4bdcec77ec --- /dev/null +++ b/packages/@aws-cdk/aws-lex/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/aws-lex/NOTICE b/packages/@aws-cdk/aws-lex/NOTICE new file mode 100644 index 0000000000000..5fc3826926b5b --- /dev/null +++ b/packages/@aws-cdk/aws-lex/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-lex/README.md b/packages/@aws-cdk/aws-lex/README.md new file mode 100644 index 0000000000000..340e3cc3f113a --- /dev/null +++ b/packages/@aws-cdk/aws-lex/README.md @@ -0,0 +1,31 @@ +# AWS::Lex Construct Library + + +--- + +![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) + +> All classes with the `Cfn` prefix in this module ([CFN Resources]) are always stable and safe to use. +> +> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib + +--- + + + +This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. + +```ts nofixture +import * as lex from '@aws-cdk/aws-lex'; +``` + + + +There are no hand-written ([L2](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) constructs for this service yet. +However, you can still use the automatically generated [L1](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_l1_using) constructs, and use this service exactly as you would using CloudFormation directly. + +For more information on the resources and properties available for this service, see the [CloudFormation documentation for AWS::Lex](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_Lex.html). + +(Read the [CDK Contributing Guide](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) if you are interested in contributing to this construct library.) + + diff --git a/packages/@aws-cdk/aws-lex/jest.config.js b/packages/@aws-cdk/aws-lex/jest.config.js new file mode 100644 index 0000000000000..3a2fd93a1228a --- /dev/null +++ b/packages/@aws-cdk/aws-lex/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-lex/lib/index.ts b/packages/@aws-cdk/aws-lex/lib/index.ts new file mode 100644 index 0000000000000..b8085185a8f7f --- /dev/null +++ b/packages/@aws-cdk/aws-lex/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::Lex CloudFormation Resources: +export * from './lex.generated'; diff --git a/packages/@aws-cdk/aws-lex/package.json b/packages/@aws-cdk/aws-lex/package.json new file mode 100644 index 0000000000000..cac15623eb8c5 --- /dev/null +++ b/packages/@aws-cdk/aws-lex/package.json @@ -0,0 +1,110 @@ +{ + "name": "@aws-cdk/aws-lex", + "version": "0.0.0", + "description": "AWS::Lex Construct Library", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "projectReferences": true, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.Lex", + "packageId": "Amazon.CDK.AWS.Lex", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "java": { + "package": "software.amazon.awscdk.services.lex", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "lex" + } + }, + "python": { + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ], + "distName": "aws-cdk.aws-lex", + "module": "aws_cdk.aws_lex" + } + }, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-lex" + }, + "homepage": "https://github.com/aws/aws-cdk", + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts", + "build+test": "yarn build && yarn test", + "build+test+package": "yarn build+test && yarn package", + "compat": "cdk-compat", + "gen": "cfn2ts", + "rosetta:extract": "yarn --silent jsii-rosetta extract", + "build+extract": "yarn build && yarn rosetta:extract", + "build+test+extract": "yarn build+test && yarn rosetta:extract" + }, + "cdk-build": { + "cloudformation": "AWS::Lex", + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "AWS::Lex", + "aws-lex" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assertions": "0.0.0", + "@aws-cdk/cdk-build-tools": "0.0.0", + "@aws-cdk/cfn2ts": "0.0.0", + "@aws-cdk/pkglint": "0.0.0", + "@types/jest": "^26.0.22" + }, + "dependencies": { + "@aws-cdk/core": "0.0.0" + }, + "peerDependencies": { + "@aws-cdk/core": "0.0.0" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "experimental", + "maturity": "cfn-only", + "awscdkio": { + "announce": false + }, + "publishConfig": { + "tag": "latest" + } +} diff --git a/packages/@aws-cdk/aws-lex/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-lex/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e208762bca03c --- /dev/null +++ b/packages/@aws-cdk/aws-lex/rosetta/default.ts-fixture @@ -0,0 +1,8 @@ +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class MyStack extends Stack { + constructor(scope: Construct, id: string) { + /// here + } +} diff --git a/packages/@aws-cdk/aws-iotevents/test/iotevents.test.ts b/packages/@aws-cdk/aws-lex/test/lex.test.ts similarity index 100% rename from packages/@aws-cdk/aws-iotevents/test/iotevents.test.ts rename to packages/@aws-cdk/aws-lex/test/lex.test.ts diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts index 26a4743b7e598..fa1068025c657 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts @@ -338,6 +338,8 @@ export class AuroraMysqlEngineVersion { public static readonly VER_2_10_0 = AuroraMysqlEngineVersion.builtIn_5_7('2.10.0'); /** Version "5.7.mysql_aurora.2.10.1". */ public static readonly VER_2_10_1 = AuroraMysqlEngineVersion.builtIn_5_7('2.10.1'); + /** Version "8.0.mysql_aurora.3.01.0". */ + public static readonly VER_3_01_0 = AuroraMysqlEngineVersion.builtIn_8_0('3.01.0'); /** * Create a new AuroraMysqlEngineVersion with an arbitrary version. @@ -355,6 +357,10 @@ export class AuroraMysqlEngineVersion { return new AuroraMysqlEngineVersion(`5.7.${addStandardPrefix ? 'mysql_aurora.' : ''}${minorVersion}`); } + private static builtIn_8_0(minorVersion: string): AuroraMysqlEngineVersion { + return new AuroraMysqlEngineVersion(`8.0.mysql_aurora.${minorVersion}`, '8.0'); + } + /** The full version string, for example, "5.7.mysql_aurora.1.78.3.6". */ public readonly auroraMysqlFullVersion: string; /** The major version of the engine. Currently, it's always "5.7". */ diff --git a/packages/@aws-cdk/aws-rds/lib/instance-engine.ts b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts index 21edeeaaed63d..5b2388eb35b45 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance-engine.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts @@ -259,6 +259,8 @@ export class MariaDbEngineVersion { public static readonly VER_10_2_39 = MariaDbEngineVersion.of('10.2.39', '10.2'); /** Version "10.2.40". */ public static readonly VER_10_2_40 = MariaDbEngineVersion.of('10.2.40', '10.2'); + /** Version "10.2.41". */ + public static readonly VER_10_2_41 = MariaDbEngineVersion.of('10.2.41', '10.2'); /** Version "10.3" (only a major version, without a specific minor version). */ public static readonly VER_10_3 = MariaDbEngineVersion.of('10.3', '10.3'); @@ -274,6 +276,8 @@ export class MariaDbEngineVersion { public static readonly VER_10_3_28 = MariaDbEngineVersion.of('10.3.28', '10.3'); /** Version "10.3.31". */ public static readonly VER_10_3_31 = MariaDbEngineVersion.of('10.3.31', '10.3'); + /** Version "10.3.32". */ + public static readonly VER_10_3_32 = MariaDbEngineVersion.of('10.3.32', '10.3'); /** Version "10.4" (only a major version, without a specific minor version). */ public static readonly VER_10_4 = MariaDbEngineVersion.of('10.4', '10.4'); @@ -285,6 +289,8 @@ export class MariaDbEngineVersion { public static readonly VER_10_4_18 = MariaDbEngineVersion.of('10.4.18', '10.4'); /** Version "10.4.21". */ public static readonly VER_10_4_21 = MariaDbEngineVersion.of('10.4.21', '10.4'); + /** Version "10.4.22". */ + public static readonly VER_10_4_22 = MariaDbEngineVersion.of('10.4.22', '10.4'); /** Version "10.5" (only a major version, without a specific minor version). */ public static readonly VER_10_5 = MariaDbEngineVersion.of('10.5', '10.5'); @@ -294,6 +300,8 @@ export class MariaDbEngineVersion { public static readonly VER_10_5_9 = MariaDbEngineVersion.of('10.5.9', '10.5'); /** Version "10.5.12". */ public static readonly VER_10_5_12 = MariaDbEngineVersion.of('10.5.12', '10.5'); + /** Version "10.5.13". */ + public static readonly VER_10_5_13 = MariaDbEngineVersion.of('10.5.13', '10.5'); /** * Create a new MariaDbEngineVersion with an arbitrary version. diff --git a/packages/@aws-cdk/aws-rds/test/cluster-engine.test.ts b/packages/@aws-cdk/aws-rds/test/cluster-engine.test.ts index 9784dfe949473..826c988688c24 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster-engine.test.ts +++ b/packages/@aws-cdk/aws-rds/test/cluster-engine.test.ts @@ -71,6 +71,19 @@ describe('cluster engine', () => { }); + test('cluster parameter group correctly determined for AURORA_MYSQL and given version 3', () => { + // GIVEN + const engine = DatabaseClusterEngine.auroraMysql({ + version: AuroraMysqlEngineVersion.VER_3_01_0, + }); + + // WHEN + const family = engine.parameterGroupFamily; + + // THEN + expect(family).toEqual('aurora-mysql8.0'); + }); + test('cluster parameter group correctly determined for AURORA_POSTGRESQL and given version', () => { // GIVEN const engine = DatabaseClusterEngine.auroraPostgres({ diff --git a/packages/@aws-cdk/aws-s3/README.md b/packages/@aws-cdk/aws-s3/README.md index 57d9369a274a7..6a45b1dec50ea 100644 --- a/packages/@aws-cdk/aws-s3/README.md +++ b/packages/@aws-cdk/aws-s3/README.md @@ -417,7 +417,7 @@ bucket.virtualHostedUrlForObject('objectname', { regional: false }); // Virtual ## Object Ownership -You can use the two following properties to specify the bucket [object Ownership]. +You can use one of following properties to specify the bucket [object Ownership]. [object Ownership]: https://docs.aws.amazon.com/AmazonS3/latest/dev/about-object-ownership.html @@ -441,6 +441,16 @@ new s3.Bucket(this, 'MyBucket', { }); ``` +### Bucket owner enforced (recommended) + +ACLs are disabled, and the bucket owner automatically owns and has full control over every object in the bucket. ACLs no longer affect permissions to data in the S3 bucket. The bucket uses policies to define access control. + +```ts +new s3.Bucket(this, 'MyBucket', { + objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_ENFORCED, +}); +``` + ## Bucket deletion When a bucket is removed from a stack (or the stack is deleted), the S3 @@ -466,7 +476,7 @@ by deploying with CDK version `1.126.0` or later **before** switching this value ## Transfer Acceleration -[Transfer Acceleration](https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html) can be configured to enable fast, easy, and secure transfers of files over long distances: +[Transfer Acceleration](https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html) can be configured to enable fast, easy, and secure transfers of files over long distances: ```ts const bucket = new s3.Bucket(this, 'MyBucket', { diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 673a1b4f5b561..404bcfbc6dfbe 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -1206,6 +1206,13 @@ export interface Inventory { * */ export enum ObjectOwnership { + /** + * ACLs are disabled, and the bucket owner automatically owns + * and has full control over every object in the bucket. + * ACLs no longer affect permissions to data in the S3 bucket. + * The bucket uses policies to define access control. + */ + BUCKET_OWNER_ENFORCED = 'BucketOwnerEnforced', /** * Objects uploaded to the bucket change ownership to the bucket owner . */ diff --git a/packages/@aws-cdk/aws-s3/test/bucket.test.ts b/packages/@aws-cdk/aws-s3/test/bucket.test.ts index 04f15cf302bda..ba61c03bf9eae 100644 --- a/packages/@aws-cdk/aws-s3/test/bucket.test.ts +++ b/packages/@aws-cdk/aws-s3/test/bucket.test.ts @@ -2303,6 +2303,32 @@ describe('bucket', () => { }); + test('Bucket with objectOwnership set to BUCKET_OWNER_ENFORCED', () => { + const stack = new cdk.Stack(); + new s3.Bucket(stack, 'MyBucket', { + objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_ENFORCED, + }); + expect(stack).toMatchTemplate({ + 'Resources': { + 'MyBucketF68F3FF0': { + 'Type': 'AWS::S3::Bucket', + 'Properties': { + 'OwnershipControls': { + 'Rules': [ + { + 'ObjectOwnership': 'BucketOwnerEnforced', + }, + ], + }, + }, + 'UpdateReplacePolicy': 'Retain', + 'DeletionPolicy': 'Retain', + }, + }, + }); + + }); + test('Bucket with objectOwnership set to BUCKET_OWNER_PREFERRED', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts index 940b45bd7486b..57c36af15b234 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts @@ -12,7 +12,7 @@ beforeEach(() => { fn = new lambda.Function(stack, 'Fn', { code: lambda.Code.fromInline('hello'), handler: 'index.hello', - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, }); }); @@ -57,4 +57,4 @@ describeDeprecated('InvokeFunction', () => { }, }); }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts index 7aa2ec16dccac..5929a684485b0 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts @@ -11,7 +11,7 @@ beforeEach(() => { fn = new lambda.Function(stack, 'Fn', { code: lambda.Code.fromInline('hello'), handler: 'index.hello', - runtime: lambda.Runtime.PYTHON_2_7, + runtime: lambda.Runtime.PYTHON_3_9, }); }); @@ -184,4 +184,4 @@ describeDeprecated('run lambda task', () => { }); }).toThrow(/Invalid Service Integration Pattern: SYNC is not supported to call Lambda./i); }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/cfnspec/CHANGELOG.md b/packages/@aws-cdk/cfnspec/CHANGELOG.md index b34cd32757ca8..00bc8552e8367 100644 --- a/packages/@aws-cdk/cfnspec/CHANGELOG.md +++ b/packages/@aws-cdk/cfnspec/CHANGELOG.md @@ -1,3 +1,91 @@ +# CloudFormation Resource Specification v51.0.0 + +## New Resource Types + +* AWS::AppSync::DomainName +* AWS::AppSync::DomainNameApiAssociation +* AWS::Lex::Bot +* AWS::Lex::BotAlias +* AWS::Lex::BotVersion +* AWS::Lex::ResourcePolicy + +## Attribute Changes + +* AWS::ApiGateway::Deployment DeploymentId (__added__) +* AWS::EC2::VPCEndpoint Id (__deleted__) +* AWS::EC2::VPCEndpoint DnsEntries.DuplicatesAllowed (__deleted__) +* AWS::EC2::VPCEndpoint NetworkInterfaceIds.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Pipeline Id (__added__) + +## Property Changes + +* AWS::EC2::VPCEndpointService PayerResponsibility (__added__) +* AWS::Evidently::Project DataDelivery.PrimitiveType (__deleted__) +* AWS::IoTAnalytics::Pipeline PipelineActivities.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Pipeline Tags.DuplicatesAllowed (__added__) +* AWS::Kinesis::Stream StreamModeDetails (__added__) +* AWS::Kinesis::Stream ShardCount.Required (__changed__) + * Old: true + * New: false +* AWS::WAFv2::WebACL CaptchaConfig (__added__) + +## Property Type Changes + +* AWS::Kinesis::Stream.StreamModeDetails (__added__) +* AWS::WAFv2::RuleGroup.CaptchaConfig (__added__) +* AWS::WAFv2::RuleGroup.ImmunityTimeProperty (__added__) +* AWS::WAFv2::RuleGroup.RegexMatchStatement (__added__) +* AWS::WAFv2::WebACL.CaptchaAction (__added__) +* AWS::WAFv2::WebACL.CaptchaConfig (__added__) +* AWS::WAFv2::WebACL.ImmunityTimeProperty (__added__) +* AWS::WAFv2::WebACL.RegexMatchStatement (__added__) +* AWS::ApiGateway::Deployment.CanarySetting StageVariableOverrides.DuplicatesAllowed (__deleted__) +* AWS::ApiGateway::Deployment.DeploymentCanarySettings StageVariableOverrides.DuplicatesAllowed (__deleted__) +* AWS::ApiGateway::Deployment.MethodSetting CacheDataEncrypted.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-cachedataencrypted + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-cachedataencrypted +* AWS::ApiGateway::Deployment.MethodSetting CacheTtlInSeconds.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-cachettlinseconds + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-cachettlinseconds +* AWS::ApiGateway::Deployment.MethodSetting CachingEnabled.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-cachingenabled + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-cachingenabled +* AWS::ApiGateway::Deployment.MethodSetting DataTraceEnabled.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-datatraceenabled + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-datatraceenabled +* AWS::ApiGateway::Deployment.MethodSetting HttpMethod.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-httpmethod + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-httpmethod +* AWS::ApiGateway::Deployment.MethodSetting LoggingLevel.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-logginglevel + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-logginglevel +* AWS::ApiGateway::Deployment.MethodSetting MetricsEnabled.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-metricsenabled + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-metricsenabled +* AWS::ApiGateway::Deployment.MethodSetting ResourcePath.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-resourcepath + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-resourcepath +* AWS::ApiGateway::Deployment.MethodSetting ThrottlingBurstLimit.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-throttlingburstlimit + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-throttlingburstlimit +* AWS::ApiGateway::Deployment.MethodSetting ThrottlingRateLimit.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-throttlingratelimit + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-throttlingratelimit +* AWS::ApiGateway::Deployment.StageDescription Tags.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription.html#cfn-apigateway-deployment-tags + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription.html#cfn-apigateway-deployment-stagedescription-tags +* AWS::ApiGateway::Deployment.StageDescription Variables.DuplicatesAllowed (__deleted__) +* AWS::Evidently::Project.DataDeliveryObject S3.PrimitiveType (__deleted__) +* AWS::IoTAnalytics::Pipeline.RemoveAttributes Attributes.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Pipeline.SelectAttributes Attributes.DuplicatesAllowed (__added__) +* AWS::WAFv2::RuleGroup.Rule CaptchaConfig (__added__) +* AWS::WAFv2::RuleGroup.RuleAction Captcha (__added__) +* AWS::WAFv2::RuleGroup.Statement RegexMatchStatement (__added__) +* AWS::WAFv2::WebACL.Rule CaptchaConfig (__added__) +* AWS::WAFv2::WebACL.RuleAction Captcha (__added__) +* AWS::WAFv2::WebACL.Statement RegexMatchStatement (__added__) + + # CloudFormation Resource Specification v50.0.0 ## New Resource Types diff --git a/packages/@aws-cdk/cfnspec/cfn.version b/packages/@aws-cdk/cfnspec/cfn.version index 819ea47b74030..92837e4d8f0cc 100644 --- a/packages/@aws-cdk/cfnspec/cfn.version +++ b/packages/@aws-cdk/cfnspec/cfn.version @@ -1 +1 @@ -50.0.0 +51.0.0 diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json index 25732b1b28605..87ffb748056f0 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json @@ -1638,7 +1638,6 @@ }, "StageVariableOverrides": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-canarysetting.html#cfn-apigateway-deployment-canarysetting-stagevariableoverrides", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "Map", @@ -1663,7 +1662,6 @@ }, "StageVariableOverrides": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-deploymentcanarysettings.html#cfn-apigateway-deployment-deploymentcanarysettings-stagevariableoverrides", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "Map", @@ -1678,64 +1676,64 @@ } }, "AWS::ApiGateway::Deployment.MethodSetting": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html", "Properties": { "CacheDataEncrypted": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-cachedataencrypted", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-cachedataencrypted", "PrimitiveType": "Boolean", "Required": false, "UpdateType": "Mutable" }, "CacheTtlInSeconds": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-cachettlinseconds", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-cachettlinseconds", "PrimitiveType": "Integer", "Required": false, "UpdateType": "Mutable" }, "CachingEnabled": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-cachingenabled", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-cachingenabled", "PrimitiveType": "Boolean", "Required": false, "UpdateType": "Mutable" }, "DataTraceEnabled": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-datatraceenabled", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-datatraceenabled", "PrimitiveType": "Boolean", "Required": false, "UpdateType": "Mutable" }, "HttpMethod": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-httpmethod", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-httpmethod", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "LoggingLevel": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-logginglevel", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-logginglevel", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "MetricsEnabled": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-metricsenabled", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-metricsenabled", "PrimitiveType": "Boolean", "Required": false, "UpdateType": "Mutable" }, "ResourcePath": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-resourcepath", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-resourcepath", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "ThrottlingBurstLimit": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-throttlingburstlimit", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-throttlingburstlimit", "PrimitiveType": "Integer", "Required": false, "UpdateType": "Mutable" }, "ThrottlingRateLimit": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html#cfn-apigateway-deployment-stagedescription-methodsetting-throttlingratelimit", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-methodsetting.html#cfn-apigateway-deployment-methodsetting-throttlingratelimit", "PrimitiveType": "Double", "Required": false, "UpdateType": "Mutable" @@ -1832,7 +1830,7 @@ "UpdateType": "Mutable" }, "Tags": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription.html#cfn-apigateway-deployment-tags", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription.html#cfn-apigateway-deployment-stagedescription-tags", "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, @@ -1859,7 +1857,6 @@ }, "Variables": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription.html#cfn-apigateway-deployment-stagedescription-variables", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "Map", @@ -30797,7 +30794,6 @@ }, "S3": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-evidently-project-datadeliveryobject.html#cfn-evidently-project-datadeliveryobject-s3", - "PrimitiveType": "Json", "Required": false, "Type": "S3Destination", "UpdateType": "Mutable" @@ -38890,6 +38886,7 @@ "Properties": { "Attributes": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-removeattributes.html#cfn-iotanalytics-pipeline-removeattributes-attributes", + "DuplicatesAllowed": true, "PrimitiveItemType": "String", "Required": false, "Type": "List", @@ -38914,6 +38911,7 @@ "Properties": { "Attributes": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-selectattributes.html#cfn-iotanalytics-pipeline-selectattributes-attributes", + "DuplicatesAllowed": true, "PrimitiveItemType": "String", "Required": false, "Type": "List", @@ -42136,6 +42134,17 @@ } } }, + "AWS::Kinesis::Stream.StreamModeDetails": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesis-stream-streammodedetails.html", + "Properties": { + "StreamMode": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesis-stream-streammodedetails.html#cfn-kinesis-stream-streammodedetails-streammode", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::KinesisAnalytics::Application.CSVMappingParameters": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalytics-application-csvmappingparameters.html", "Properties": { @@ -45099,6 +45108,1134 @@ } } }, + "AWS::Lex::Bot.BotLocale": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-botlocale.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-botlocale.html#cfn-lex-bot-botlocale-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Intents": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-botlocale.html#cfn-lex-bot-botlocale-intents", + "DuplicatesAllowed": false, + "ItemType": "Intent", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "LocaleId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-botlocale.html#cfn-lex-bot-botlocale-localeid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "NluConfidenceThreshold": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-botlocale.html#cfn-lex-bot-botlocale-nluconfidencethreshold", + "PrimitiveType": "Double", + "Required": true, + "UpdateType": "Mutable" + }, + "SlotTypes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-botlocale.html#cfn-lex-bot-botlocale-slottypes", + "DuplicatesAllowed": false, + "ItemType": "SlotType", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "VoiceSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-botlocale.html#cfn-lex-bot-botlocale-voicesettings", + "Required": false, + "Type": "VoiceSettings", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.Button": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-button.html", + "Properties": { + "Text": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-button.html#cfn-lex-bot-button-text", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-button.html#cfn-lex-bot-button-value", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.CustomPayload": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-custompayload.html", + "Properties": { + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-custompayload.html#cfn-lex-bot-custompayload-value", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.DialogCodeHookSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-dialogcodehooksetting.html", + "Properties": { + "Enabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-dialogcodehooksetting.html#cfn-lex-bot-dialogcodehooksetting-enabled", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.ExternalSourceSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-externalsourcesetting.html", + "Properties": { + "GrammarSlotTypeSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-externalsourcesetting.html#cfn-lex-bot-externalsourcesetting-grammarslottypesetting", + "Required": false, + "Type": "GrammarSlotTypeSetting", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.FulfillmentCodeHookSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentcodehooksetting.html", + "Properties": { + "Enabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentcodehooksetting.html#cfn-lex-bot-fulfillmentcodehooksetting-enabled", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + }, + "FulfillmentUpdatesSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentcodehooksetting.html#cfn-lex-bot-fulfillmentcodehooksetting-fulfillmentupdatesspecification", + "Required": false, + "Type": "FulfillmentUpdatesSpecification", + "UpdateType": "Mutable" + }, + "PostFulfillmentStatusSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentcodehooksetting.html#cfn-lex-bot-fulfillmentcodehooksetting-postfulfillmentstatusspecification", + "Required": false, + "Type": "PostFulfillmentStatusSpecification", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.FulfillmentStartResponseSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentstartresponsespecification.html", + "Properties": { + "AllowInterrupt": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentstartresponsespecification.html#cfn-lex-bot-fulfillmentstartresponsespecification-allowinterrupt", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "DelayInSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentstartresponsespecification.html#cfn-lex-bot-fulfillmentstartresponsespecification-delayinseconds", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "MessageGroups": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentstartresponsespecification.html#cfn-lex-bot-fulfillmentstartresponsespecification-messagegroups", + "ItemType": "MessageGroup", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.FulfillmentUpdateResponseSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentupdateresponsespecification.html", + "Properties": { + "AllowInterrupt": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentupdateresponsespecification.html#cfn-lex-bot-fulfillmentupdateresponsespecification-allowinterrupt", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "FrequencyInSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentupdateresponsespecification.html#cfn-lex-bot-fulfillmentupdateresponsespecification-frequencyinseconds", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "MessageGroups": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentupdateresponsespecification.html#cfn-lex-bot-fulfillmentupdateresponsespecification-messagegroups", + "ItemType": "MessageGroup", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.FulfillmentUpdatesSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentupdatesspecification.html", + "Properties": { + "Active": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentupdatesspecification.html#cfn-lex-bot-fulfillmentupdatesspecification-active", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + }, + "StartResponse": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentupdatesspecification.html#cfn-lex-bot-fulfillmentupdatesspecification-startresponse", + "Required": false, + "Type": "FulfillmentStartResponseSpecification", + "UpdateType": "Mutable" + }, + "TimeoutInSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentupdatesspecification.html#cfn-lex-bot-fulfillmentupdatesspecification-timeoutinseconds", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "UpdateResponse": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-fulfillmentupdatesspecification.html#cfn-lex-bot-fulfillmentupdatesspecification-updateresponse", + "Required": false, + "Type": "FulfillmentUpdateResponseSpecification", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.GrammarSlotTypeSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-grammarslottypesetting.html", + "Properties": { + "Source": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-grammarslottypesetting.html#cfn-lex-bot-grammarslottypesetting-source", + "Required": false, + "Type": "GrammarSlotTypeSource", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.GrammarSlotTypeSource": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-grammarslottypesource.html", + "Properties": { + "KmsKeyArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-grammarslottypesource.html#cfn-lex-bot-grammarslottypesource-kmskeyarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "S3BucketName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-grammarslottypesource.html#cfn-lex-bot-grammarslottypesource-s3bucketname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "S3ObjectKey": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-grammarslottypesource.html#cfn-lex-bot-grammarslottypesource-s3objectkey", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.ImageResponseCard": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-imageresponsecard.html", + "Properties": { + "Buttons": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-imageresponsecard.html#cfn-lex-bot-imageresponsecard-buttons", + "ItemType": "Button", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "ImageUrl": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-imageresponsecard.html#cfn-lex-bot-imageresponsecard-imageurl", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Subtitle": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-imageresponsecard.html#cfn-lex-bot-imageresponsecard-subtitle", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Title": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-imageresponsecard.html#cfn-lex-bot-imageresponsecard-title", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.InputContext": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-inputcontext.html", + "Properties": { + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-inputcontext.html#cfn-lex-bot-inputcontext-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.Intent": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intent.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intent.html#cfn-lex-bot-intent-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "DialogCodeHook": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intent.html#cfn-lex-bot-intent-dialogcodehook", + "Required": false, + "Type": "DialogCodeHookSetting", + "UpdateType": "Mutable" + }, + "FulfillmentCodeHook": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intent.html#cfn-lex-bot-intent-fulfillmentcodehook", + "Required": false, + "Type": "FulfillmentCodeHookSetting", + "UpdateType": "Mutable" + }, + "InputContexts": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intent.html#cfn-lex-bot-intent-inputcontexts", + "ItemType": "InputContext", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "IntentClosingSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intent.html#cfn-lex-bot-intent-intentclosingsetting", + "Required": false, + "Type": "IntentClosingSetting", + "UpdateType": "Mutable" + }, + "IntentConfirmationSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intent.html#cfn-lex-bot-intent-intentconfirmationsetting", + "Required": false, + "Type": "IntentConfirmationSetting", + "UpdateType": "Mutable" + }, + "KendraConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intent.html#cfn-lex-bot-intent-kendraconfiguration", + "Required": false, + "Type": "KendraConfiguration", + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intent.html#cfn-lex-bot-intent-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "OutputContexts": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intent.html#cfn-lex-bot-intent-outputcontexts", + "ItemType": "OutputContext", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "ParentIntentSignature": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intent.html#cfn-lex-bot-intent-parentintentsignature", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SampleUtterances": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intent.html#cfn-lex-bot-intent-sampleutterances", + "ItemType": "SampleUtterance", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "SlotPriorities": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intent.html#cfn-lex-bot-intent-slotpriorities", + "ItemType": "SlotPriority", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Slots": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intent.html#cfn-lex-bot-intent-slots", + "DuplicatesAllowed": false, + "ItemType": "Slot", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.IntentClosingSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intentclosingsetting.html", + "Properties": { + "ClosingResponse": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intentclosingsetting.html#cfn-lex-bot-intentclosingsetting-closingresponse", + "Required": true, + "Type": "ResponseSpecification", + "UpdateType": "Mutable" + }, + "IsActive": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intentclosingsetting.html#cfn-lex-bot-intentclosingsetting-isactive", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.IntentConfirmationSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intentconfirmationsetting.html", + "Properties": { + "DeclinationResponse": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intentconfirmationsetting.html#cfn-lex-bot-intentconfirmationsetting-declinationresponse", + "Required": true, + "Type": "ResponseSpecification", + "UpdateType": "Mutable" + }, + "IsActive": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intentconfirmationsetting.html#cfn-lex-bot-intentconfirmationsetting-isactive", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "PromptSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-intentconfirmationsetting.html#cfn-lex-bot-intentconfirmationsetting-promptspecification", + "Required": true, + "Type": "PromptSpecification", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.KendraConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-kendraconfiguration.html", + "Properties": { + "KendraIndex": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-kendraconfiguration.html#cfn-lex-bot-kendraconfiguration-kendraindex", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "QueryFilterString": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-kendraconfiguration.html#cfn-lex-bot-kendraconfiguration-queryfilterstring", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "QueryFilterStringEnabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-kendraconfiguration.html#cfn-lex-bot-kendraconfiguration-queryfilterstringenabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.Message": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-message.html", + "Properties": { + "CustomPayload": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-message.html#cfn-lex-bot-message-custompayload", + "Required": false, + "Type": "CustomPayload", + "UpdateType": "Mutable" + }, + "ImageResponseCard": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-message.html#cfn-lex-bot-message-imageresponsecard", + "Required": false, + "Type": "ImageResponseCard", + "UpdateType": "Mutable" + }, + "PlainTextMessage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-message.html#cfn-lex-bot-message-plaintextmessage", + "Required": false, + "Type": "PlainTextMessage", + "UpdateType": "Mutable" + }, + "SSMLMessage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-message.html#cfn-lex-bot-message-ssmlmessage", + "Required": false, + "Type": "SSMLMessage", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.MessageGroup": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-messagegroup.html", + "Properties": { + "Message": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-messagegroup.html#cfn-lex-bot-messagegroup-message", + "Required": true, + "Type": "Message", + "UpdateType": "Mutable" + }, + "Variations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-messagegroup.html#cfn-lex-bot-messagegroup-variations", + "ItemType": "Message", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.MultipleValuesSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-multiplevaluessetting.html", + "Properties": { + "AllowMultipleValues": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-multiplevaluessetting.html#cfn-lex-bot-multiplevaluessetting-allowmultiplevalues", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.ObfuscationSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-obfuscationsetting.html", + "Properties": { + "ObfuscationSettingType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-obfuscationsetting.html#cfn-lex-bot-obfuscationsetting-obfuscationsettingtype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.OutputContext": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-outputcontext.html", + "Properties": { + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-outputcontext.html#cfn-lex-bot-outputcontext-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "TimeToLiveInSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-outputcontext.html#cfn-lex-bot-outputcontext-timetoliveinseconds", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "TurnsToLive": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-outputcontext.html#cfn-lex-bot-outputcontext-turnstolive", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.PlainTextMessage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-plaintextmessage.html", + "Properties": { + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-plaintextmessage.html#cfn-lex-bot-plaintextmessage-value", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.PostFulfillmentStatusSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-postfulfillmentstatusspecification.html", + "Properties": { + "FailureResponse": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-postfulfillmentstatusspecification.html#cfn-lex-bot-postfulfillmentstatusspecification-failureresponse", + "Required": false, + "Type": "ResponseSpecification", + "UpdateType": "Mutable" + }, + "SuccessResponse": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-postfulfillmentstatusspecification.html#cfn-lex-bot-postfulfillmentstatusspecification-successresponse", + "Required": false, + "Type": "ResponseSpecification", + "UpdateType": "Mutable" + }, + "TimeoutResponse": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-postfulfillmentstatusspecification.html#cfn-lex-bot-postfulfillmentstatusspecification-timeoutresponse", + "Required": false, + "Type": "ResponseSpecification", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.PromptSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-promptspecification.html", + "Properties": { + "AllowInterrupt": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-promptspecification.html#cfn-lex-bot-promptspecification-allowinterrupt", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "MaxRetries": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-promptspecification.html#cfn-lex-bot-promptspecification-maxretries", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "MessageGroupsList": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-promptspecification.html#cfn-lex-bot-promptspecification-messagegroupslist", + "ItemType": "MessageGroup", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.ResponseSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-responsespecification.html", + "Properties": { + "AllowInterrupt": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-responsespecification.html#cfn-lex-bot-responsespecification-allowinterrupt", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "MessageGroupsList": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-responsespecification.html#cfn-lex-bot-responsespecification-messagegroupslist", + "ItemType": "MessageGroup", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.S3Location": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-s3location.html", + "Properties": { + "S3Bucket": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-s3location.html#cfn-lex-bot-s3location-s3bucket", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "S3ObjectKey": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-s3location.html#cfn-lex-bot-s3location-s3objectkey", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "S3ObjectVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-s3location.html#cfn-lex-bot-s3location-s3objectversion", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.SSMLMessage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-ssmlmessage.html", + "Properties": { + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-ssmlmessage.html#cfn-lex-bot-ssmlmessage-value", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.SampleUtterance": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-sampleutterance.html", + "Properties": { + "Utterance": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-sampleutterance.html#cfn-lex-bot-sampleutterance-utterance", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.SampleValue": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-samplevalue.html", + "Properties": { + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-samplevalue.html#cfn-lex-bot-samplevalue-value", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.Slot": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slot.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slot.html#cfn-lex-bot-slot-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "MultipleValuesSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slot.html#cfn-lex-bot-slot-multiplevaluessetting", + "Required": false, + "Type": "MultipleValuesSetting", + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slot.html#cfn-lex-bot-slot-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ObfuscationSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slot.html#cfn-lex-bot-slot-obfuscationsetting", + "Required": false, + "Type": "ObfuscationSetting", + "UpdateType": "Mutable" + }, + "SlotTypeName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slot.html#cfn-lex-bot-slot-slottypename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ValueElicitationSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slot.html#cfn-lex-bot-slot-valueelicitationsetting", + "Required": true, + "Type": "SlotValueElicitationSetting", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.SlotDefaultValue": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotdefaultvalue.html", + "Properties": { + "DefaultValue": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotdefaultvalue.html#cfn-lex-bot-slotdefaultvalue-defaultvalue", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.SlotDefaultValueSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotdefaultvaluespecification.html", + "Properties": { + "DefaultValueList": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotdefaultvaluespecification.html#cfn-lex-bot-slotdefaultvaluespecification-defaultvaluelist", + "ItemType": "SlotDefaultValue", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.SlotPriority": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotpriority.html", + "Properties": { + "Priority": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotpriority.html#cfn-lex-bot-slotpriority-priority", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "SlotName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotpriority.html#cfn-lex-bot-slotpriority-slotname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.SlotType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slottype.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slottype.html#cfn-lex-bot-slottype-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ExternalSourceSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slottype.html#cfn-lex-bot-slottype-externalsourcesetting", + "Required": false, + "Type": "ExternalSourceSetting", + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slottype.html#cfn-lex-bot-slottype-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ParentSlotTypeSignature": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slottype.html#cfn-lex-bot-slottype-parentslottypesignature", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SlotTypeValues": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slottype.html#cfn-lex-bot-slottype-slottypevalues", + "ItemType": "SlotTypeValue", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "ValueSelectionSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slottype.html#cfn-lex-bot-slottype-valueselectionsetting", + "Required": false, + "Type": "SlotValueSelectionSetting", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.SlotTypeValue": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slottypevalue.html", + "Properties": { + "SampleValue": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slottypevalue.html#cfn-lex-bot-slottypevalue-samplevalue", + "Required": true, + "Type": "SampleValue", + "UpdateType": "Mutable" + }, + "Synonyms": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slottypevalue.html#cfn-lex-bot-slottypevalue-synonyms", + "ItemType": "SampleValue", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.SlotValueElicitationSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotvalueelicitationsetting.html", + "Properties": { + "DefaultValueSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotvalueelicitationsetting.html#cfn-lex-bot-slotvalueelicitationsetting-defaultvaluespecification", + "Required": false, + "Type": "SlotDefaultValueSpecification", + "UpdateType": "Mutable" + }, + "PromptSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotvalueelicitationsetting.html#cfn-lex-bot-slotvalueelicitationsetting-promptspecification", + "Required": false, + "Type": "PromptSpecification", + "UpdateType": "Mutable" + }, + "SampleUtterances": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotvalueelicitationsetting.html#cfn-lex-bot-slotvalueelicitationsetting-sampleutterances", + "ItemType": "SampleUtterance", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "SlotConstraint": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotvalueelicitationsetting.html#cfn-lex-bot-slotvalueelicitationsetting-slotconstraint", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "WaitAndContinueSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotvalueelicitationsetting.html#cfn-lex-bot-slotvalueelicitationsetting-waitandcontinuespecification", + "Required": false, + "Type": "WaitAndContinueSpecification", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.SlotValueRegexFilter": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotvalueregexfilter.html", + "Properties": { + "Pattern": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotvalueregexfilter.html#cfn-lex-bot-slotvalueregexfilter-pattern", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.SlotValueSelectionSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotvalueselectionsetting.html", + "Properties": { + "RegexFilter": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotvalueselectionsetting.html#cfn-lex-bot-slotvalueselectionsetting-regexfilter", + "Required": false, + "Type": "SlotValueRegexFilter", + "UpdateType": "Mutable" + }, + "ResolutionStrategy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-slotvalueselectionsetting.html#cfn-lex-bot-slotvalueselectionsetting-resolutionstrategy", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.StillWaitingResponseSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-stillwaitingresponsespecification.html", + "Properties": { + "AllowInterrupt": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-stillwaitingresponsespecification.html#cfn-lex-bot-stillwaitingresponsespecification-allowinterrupt", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "FrequencyInSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-stillwaitingresponsespecification.html#cfn-lex-bot-stillwaitingresponsespecification-frequencyinseconds", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "MessageGroupsList": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-stillwaitingresponsespecification.html#cfn-lex-bot-stillwaitingresponsespecification-messagegroupslist", + "ItemType": "MessageGroup", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + }, + "TimeoutInSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-stillwaitingresponsespecification.html#cfn-lex-bot-stillwaitingresponsespecification-timeoutinseconds", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.VoiceSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-voicesettings.html", + "Properties": { + "VoiceId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-voicesettings.html#cfn-lex-bot-voicesettings-voiceid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::Bot.WaitAndContinueSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-waitandcontinuespecification.html", + "Properties": { + "ContinueResponse": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-waitandcontinuespecification.html#cfn-lex-bot-waitandcontinuespecification-continueresponse", + "Required": true, + "Type": "ResponseSpecification", + "UpdateType": "Mutable" + }, + "IsActive": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-waitandcontinuespecification.html#cfn-lex-bot-waitandcontinuespecification-isactive", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "StillWaitingResponse": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-waitandcontinuespecification.html#cfn-lex-bot-waitandcontinuespecification-stillwaitingresponse", + "Required": false, + "Type": "StillWaitingResponseSpecification", + "UpdateType": "Mutable" + }, + "WaitingResponse": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-bot-waitandcontinuespecification.html#cfn-lex-bot-waitandcontinuespecification-waitingresponse", + "Required": true, + "Type": "ResponseSpecification", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::BotAlias.AudioLogDestination": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-audiologdestination.html", + "Properties": { + "S3Bucket": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-audiologdestination.html#cfn-lex-botalias-audiologdestination-s3bucket", + "Required": false, + "Type": "S3BucketLogDestination", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::BotAlias.AudioLogSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-audiologsetting.html", + "Properties": { + "Destination": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-audiologsetting.html#cfn-lex-botalias-audiologsetting-destination", + "Required": true, + "Type": "AudioLogDestination", + "UpdateType": "Mutable" + }, + "Enabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-audiologsetting.html#cfn-lex-botalias-audiologsetting-enabled", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::BotAlias.BotAliasLocaleSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-botaliaslocalesettings.html", + "Properties": { + "CodeHookSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-botaliaslocalesettings.html#cfn-lex-botalias-botaliaslocalesettings-codehookspecification", + "Required": false, + "Type": "CodeHookSpecification", + "UpdateType": "Mutable" + }, + "Enabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-botaliaslocalesettings.html#cfn-lex-botalias-botaliaslocalesettings-enabled", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::BotAlias.BotAliasLocaleSettingsItem": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-botaliaslocalesettingsitem.html", + "Properties": { + "BotAliasLocaleSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-botaliaslocalesettingsitem.html#cfn-lex-botalias-botaliaslocalesettingsitem-botaliaslocalesetting", + "Required": false, + "Type": "BotAliasLocaleSettings", + "UpdateType": "Mutable" + }, + "LocaleId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-botaliaslocalesettingsitem.html#cfn-lex-botalias-botaliaslocalesettingsitem-localeid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::BotAlias.CloudWatchLogGroupLogDestination": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-cloudwatchloggrouplogdestination.html", + "Properties": { + "CloudWatchLogGroupArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-cloudwatchloggrouplogdestination.html#cfn-lex-botalias-cloudwatchloggrouplogdestination-cloudwatchloggrouparn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "LogPrefix": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-cloudwatchloggrouplogdestination.html#cfn-lex-botalias-cloudwatchloggrouplogdestination-logprefix", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::BotAlias.CodeHookSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-codehookspecification.html", + "Properties": { + "LambdaCodeHook": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-codehookspecification.html#cfn-lex-botalias-codehookspecification-lambdacodehook", + "Required": true, + "Type": "LambdaCodeHook", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::BotAlias.ConversationLogSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-conversationlogsettings.html", + "Properties": { + "AudioLogSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-conversationlogsettings.html#cfn-lex-botalias-conversationlogsettings-audiologsettings", + "ItemType": "AudioLogSetting", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "TextLogSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-conversationlogsettings.html#cfn-lex-botalias-conversationlogsettings-textlogsettings", + "ItemType": "TextLogSetting", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::BotAlias.LambdaCodeHook": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-lambdacodehook.html", + "Properties": { + "CodeHookInterfaceVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-lambdacodehook.html#cfn-lex-botalias-lambdacodehook-codehookinterfaceversion", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "LambdaArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-lambdacodehook.html#cfn-lex-botalias-lambdacodehook-lambdaarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::BotAlias.S3BucketLogDestination": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-s3bucketlogdestination.html", + "Properties": { + "KmsKeyArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-s3bucketlogdestination.html#cfn-lex-botalias-s3bucketlogdestination-kmskeyarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "LogPrefix": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-s3bucketlogdestination.html#cfn-lex-botalias-s3bucketlogdestination-logprefix", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "S3BucketArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-s3bucketlogdestination.html#cfn-lex-botalias-s3bucketlogdestination-s3bucketarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::BotAlias.TextLogDestination": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-textlogdestination.html", + "Properties": { + "CloudWatch": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-textlogdestination.html#cfn-lex-botalias-textlogdestination-cloudwatch", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::BotAlias.TextLogSetting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-textlogsetting.html", + "Properties": { + "Destination": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-textlogsetting.html#cfn-lex-botalias-textlogsetting-destination", + "Required": false, + "Type": "TextLogDestination", + "UpdateType": "Mutable" + }, + "Enabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botalias-textlogsetting.html#cfn-lex-botalias-textlogsetting-enabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::BotVersion.BotVersionLocaleDetails": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botversion-botversionlocaledetails.html", + "Properties": { + "SourceBotVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botversion-botversionlocaledetails.html#cfn-lex-botversion-botversionlocaledetails-sourcebotversion", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::BotVersion.BotVersionLocaleSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botversion-botversionlocalespecification.html", + "Properties": { + "BotVersionLocaleDetails": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botversion-botversionlocalespecification.html#cfn-lex-botversion-botversionlocalespecification-botversionlocaledetails", + "Required": true, + "Type": "BotVersionLocaleDetails", + "UpdateType": "Mutable" + }, + "LocaleId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-botversion-botversionlocalespecification.html#cfn-lex-botversion-botversionlocalespecification-localeid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::ResourcePolicy.Policy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lex-resourcepolicy-policy.html" + }, "AWS::LicenseManager::License.BorrowConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-licensemanager-license-borrowconfiguration.html", "Properties": { @@ -66558,6 +67695,17 @@ } } }, + "AWS::WAFv2::RuleGroup.CaptchaConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-captchaconfig.html", + "Properties": { + "ImmunityTimeProperty": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-captchaconfig.html#cfn-wafv2-rulegroup-captchaconfig-immunitytimeproperty", + "Required": false, + "Type": "ImmunityTimeProperty", + "UpdateType": "Mutable" + } + } + }, "AWS::WAFv2::RuleGroup.CustomResponseBody": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-customresponsebody.html", "Properties": { @@ -66703,6 +67851,17 @@ } } }, + "AWS::WAFv2::RuleGroup.ImmunityTimeProperty": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-immunitytimeproperty.html", + "Properties": { + "ImmunityTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-immunitytimeproperty.html#cfn-wafv2-rulegroup-immunitytimeproperty-immunitytime", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::WAFv2::RuleGroup.JsonBody": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-jsonbody.html", "Properties": { @@ -66835,6 +67994,30 @@ } } }, + "AWS::WAFv2::RuleGroup.RegexMatchStatement": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-regexmatchstatement.html", + "Properties": { + "FieldToMatch": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-regexmatchstatement.html#cfn-wafv2-rulegroup-regexmatchstatement-fieldtomatch", + "Required": true, + "Type": "FieldToMatch", + "UpdateType": "Mutable" + }, + "RegexString": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-regexmatchstatement.html#cfn-wafv2-rulegroup-regexmatchstatement-regexstring", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "TextTransformations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-regexmatchstatement.html#cfn-wafv2-rulegroup-regexmatchstatement-texttransformations", + "ItemType": "TextTransformation", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::WAFv2::RuleGroup.RegexPatternSetReferenceStatement": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-regexpatternsetreferencestatement.html", "Properties": { @@ -66868,6 +68051,12 @@ "Type": "RuleAction", "UpdateType": "Mutable" }, + "CaptchaConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-rule.html#cfn-wafv2-rulegroup-rule-captchaconfig", + "Required": false, + "Type": "CaptchaConfig", + "UpdateType": "Mutable" + }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-rule.html#cfn-wafv2-rulegroup-rule-name", "PrimitiveType": "String", @@ -66916,6 +68105,12 @@ "Required": false, "UpdateType": "Mutable" }, + "Captcha": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-ruleaction.html#cfn-wafv2-rulegroup-ruleaction-captcha", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, "Count": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-ruleaction.html#cfn-wafv2-rulegroup-ruleaction-count", "PrimitiveType": "Json", @@ -67023,6 +68218,12 @@ "Type": "RateBasedStatement", "UpdateType": "Mutable" }, + "RegexMatchStatement": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-statement.html#cfn-wafv2-rulegroup-statement-regexmatchstatement", + "Required": false, + "Type": "RegexMatchStatement", + "UpdateType": "Mutable" + }, "RegexPatternSetReferenceStatement": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-rulegroup-statement.html#cfn-wafv2-rulegroup-statement-regexpatternsetreferencestatement", "Required": false, @@ -67177,6 +68378,28 @@ } } }, + "AWS::WAFv2::WebACL.CaptchaAction": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-captchaaction.html", + "Properties": { + "CustomRequestHandling": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-captchaaction.html#cfn-wafv2-webacl-captchaaction-customrequesthandling", + "Required": false, + "Type": "CustomRequestHandling", + "UpdateType": "Mutable" + } + } + }, + "AWS::WAFv2::WebACL.CaptchaConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-captchaconfig.html", + "Properties": { + "ImmunityTimeProperty": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-captchaconfig.html#cfn-wafv2-webacl-captchaconfig-immunitytimeproperty", + "Required": false, + "Type": "ImmunityTimeProperty", + "UpdateType": "Mutable" + } + } + }, "AWS::WAFv2::WebACL.CountAction": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-countaction.html", "Properties": { @@ -67414,6 +68637,17 @@ } } }, + "AWS::WAFv2::WebACL.ImmunityTimeProperty": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-immunitytimeproperty.html", + "Properties": { + "ImmunityTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-immunitytimeproperty.html#cfn-wafv2-webacl-immunitytimeproperty-immunitytime", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::WAFv2::WebACL.JsonBody": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-jsonbody.html", "Properties": { @@ -67588,6 +68822,30 @@ } } }, + "AWS::WAFv2::WebACL.RegexMatchStatement": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-regexmatchstatement.html", + "Properties": { + "FieldToMatch": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-regexmatchstatement.html#cfn-wafv2-webacl-regexmatchstatement-fieldtomatch", + "Required": true, + "Type": "FieldToMatch", + "UpdateType": "Mutable" + }, + "RegexString": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-regexmatchstatement.html#cfn-wafv2-webacl-regexmatchstatement-regexstring", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "TextTransformations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-regexmatchstatement.html#cfn-wafv2-webacl-regexmatchstatement-texttransformations", + "ItemType": "TextTransformation", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::WAFv2::WebACL.RegexPatternSetReferenceStatement": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-regexpatternsetreferencestatement.html", "Properties": { @@ -67621,6 +68879,12 @@ "Type": "RuleAction", "UpdateType": "Mutable" }, + "CaptchaConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-rule.html#cfn-wafv2-webacl-rule-captchaconfig", + "Required": false, + "Type": "CaptchaConfig", + "UpdateType": "Mutable" + }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-rule.html#cfn-wafv2-webacl-rule-name", "PrimitiveType": "String", @@ -67675,6 +68939,12 @@ "Type": "BlockAction", "UpdateType": "Mutable" }, + "Captcha": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-ruleaction.html#cfn-wafv2-webacl-ruleaction-captcha", + "Required": false, + "Type": "CaptchaAction", + "UpdateType": "Mutable" + }, "Count": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-ruleaction.html#cfn-wafv2-webacl-ruleaction-count", "Required": false, @@ -67806,6 +69076,12 @@ "Type": "RateBasedStatement", "UpdateType": "Mutable" }, + "RegexMatchStatement": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-statement.html#cfn-wafv2-webacl-statement-regexmatchstatement", + "Required": false, + "Type": "RegexMatchStatement", + "UpdateType": "Mutable" + }, "RegexPatternSetReferenceStatement": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-statement.html#cfn-wafv2-webacl-statement-regexpatternsetreferencestatement", "Required": false, @@ -68322,7 +69598,7 @@ } } }, - "ResourceSpecificationVersion": "50.0.0", + "ResourceSpecificationVersion": "51.0.0", "ResourceTypes": { "AWS::ACMPCA::Certificate": { "Attributes": { @@ -69455,6 +70731,11 @@ } }, "AWS::ApiGateway::Deployment": { + "Attributes": { + "DeploymentId": { + "PrimitiveType": "String" + } + }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-deployment.html", "Properties": { "DeploymentCanarySettings": { @@ -72461,6 +73742,62 @@ } } }, + "AWS::AppSync::DomainName": { + "Attributes": { + "AppSyncDomainName": { + "PrimitiveType": "String" + }, + "DomainName": { + "PrimitiveType": "String" + }, + "HostedZoneId": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-domainname.html", + "Properties": { + "CertificateArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-domainname.html#cfn-appsync-domainname-certificatearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-domainname.html#cfn-appsync-domainname-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "DomainName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-domainname.html#cfn-appsync-domainname-domainname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::AppSync::DomainNameApiAssociation": { + "Attributes": { + "ApiAssociationIdentifier": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-domainnameapiassociation.html", + "Properties": { + "ApiId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-domainnameapiassociation.html#cfn-appsync-domainnameapiassociation-apiid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "DomainName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-domainnameapiassociation.html#cfn-appsync-domainnameapiassociation-domainname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::AppSync::FunctionConfiguration": { "Attributes": { "DataSourceName": { @@ -83837,15 +85174,10 @@ "PrimitiveType": "String" }, "DnsEntries": { - "DuplicatesAllowed": true, "PrimitiveItemType": "String", "Type": "List" }, - "Id": { - "PrimitiveType": "String" - }, "NetworkInterfaceIds": { - "DuplicatesAllowed": true, "PrimitiveItemType": "String", "Type": "List" } @@ -83960,6 +85292,12 @@ "Required": false, "Type": "List", "UpdateType": "Mutable" + }, + "PayerResponsibility": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcendpointservice.html#cfn-ec2-vpcendpointservice-payerresponsibility", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" } } }, @@ -87879,7 +89217,6 @@ "Properties": { "DataDelivery": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-evidently-project.html#cfn-evidently-project-datadelivery", - "PrimitiveType": "Json", "Required": false, "Type": "DataDeliveryObject", "UpdateType": "Mutable" @@ -93147,10 +94484,16 @@ } }, "AWS::IoTAnalytics::Pipeline": { + "Attributes": { + "Id": { + "PrimitiveType": "String" + } + }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-pipeline.html", "Properties": { "PipelineActivities": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-pipeline.html#cfn-iotanalytics-pipeline-pipelineactivities", + "DuplicatesAllowed": true, "ItemType": "Activity", "Required": true, "Type": "List", @@ -93164,6 +94507,7 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-pipeline.html#cfn-iotanalytics-pipeline-tags", + "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, "Type": "List", @@ -94573,7 +95917,7 @@ "ShardCount": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kinesis-stream.html#cfn-kinesis-stream-shardcount", "PrimitiveType": "Integer", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "StreamEncryption": { @@ -94582,6 +95926,12 @@ "Type": "StreamEncryption", "UpdateType": "Mutable" }, + "StreamModeDetails": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kinesis-stream.html#cfn-kinesis-stream-streammodedetails", + "Required": false, + "Type": "StreamModeDetails", + "UpdateType": "Mutable" + }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kinesis-stream.html#cfn-kinesis-stream-tags", "DuplicatesAllowed": true, @@ -95473,6 +96823,207 @@ } } }, + "AWS::Lex::Bot": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "Id": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-bot.html", + "Properties": { + "AutoBuildBotLocales": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-bot.html#cfn-lex-bot-autobuildbotlocales", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "BotFileS3Location": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-bot.html#cfn-lex-bot-botfiles3location", + "Required": false, + "Type": "S3Location", + "UpdateType": "Mutable" + }, + "BotLocales": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-bot.html#cfn-lex-bot-botlocales", + "DuplicatesAllowed": false, + "ItemType": "BotLocale", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "BotTags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-bot.html#cfn-lex-bot-bottags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "DataPrivacy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-bot.html#cfn-lex-bot-dataprivacy", + "PrimitiveType": "Json", + "Required": true, + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-bot.html#cfn-lex-bot-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "IdleSessionTTLInSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-bot.html#cfn-lex-bot-idlesessionttlinseconds", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-bot.html#cfn-lex-bot-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "RoleArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-bot.html#cfn-lex-bot-rolearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "TestBotAliasTags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-bot.html#cfn-lex-bot-testbotaliastags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::BotAlias": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "BotAliasId": { + "PrimitiveType": "String" + }, + "BotAliasStatus": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-botalias.html", + "Properties": { + "BotAliasLocaleSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-botalias.html#cfn-lex-botalias-botaliaslocalesettings", + "DuplicatesAllowed": false, + "ItemType": "BotAliasLocaleSettingsItem", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "BotAliasName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-botalias.html#cfn-lex-botalias-botaliasname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "BotAliasTags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-botalias.html#cfn-lex-botalias-botaliastags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "BotId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-botalias.html#cfn-lex-botalias-botid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "BotVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-botalias.html#cfn-lex-botalias-botversion", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ConversationLogSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-botalias.html#cfn-lex-botalias-conversationlogsettings", + "Required": false, + "Type": "ConversationLogSettings", + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-botalias.html#cfn-lex-botalias-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SentimentAnalysisSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-botalias.html#cfn-lex-botalias-sentimentanalysissettings", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::BotVersion": { + "Attributes": { + "BotVersion": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-botversion.html", + "Properties": { + "BotId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-botversion.html#cfn-lex-botversion-botid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "BotVersionLocaleSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-botversion.html#cfn-lex-botversion-botversionlocalespecification", + "ItemType": "BotVersionLocaleSpecification", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-botversion.html#cfn-lex-botversion-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lex::ResourcePolicy": { + "Attributes": { + "Id": { + "PrimitiveType": "String" + }, + "RevisionId": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-resourcepolicy.html", + "Properties": { + "Policy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-resourcepolicy.html#cfn-lex-resourcepolicy-policy", + "Required": true, + "Type": "Policy", + "UpdateType": "Mutable" + }, + "ResourceArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-resourcepolicy.html#cfn-lex-resourcepolicy-resourcearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::LicenseManager::Grant": { "Attributes": { "GrantArn": { @@ -110677,6 +112228,12 @@ }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wafv2-webacl.html", "Properties": { + "CaptchaConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wafv2-webacl.html#cfn-wafv2-webacl-captchaconfig", + "Required": false, + "Type": "CaptchaConfig", + "UpdateType": "Mutable" + }, "CustomResponseBodies": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wafv2-webacl.html#cfn-wafv2-webacl-customresponsebodies", "ItemType": "CustomResponseBody", diff --git a/packages/@aws-cdk/cfnspec/spec-source/1000_Evidently_Project_DataDeliveryObject_S3_patch.json b/packages/@aws-cdk/cfnspec/spec-source/1000_Evidently_Project_DataDeliveryObject_S3_patch.json deleted file mode 100644 index 04ce5e9abc0ba..0000000000000 --- a/packages/@aws-cdk/cfnspec/spec-source/1000_Evidently_Project_DataDeliveryObject_S3_patch.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "PropertyTypes": { - "AWS::Evidently::Project.DataDeliveryObject": { - "patch": { - "description": "Primitive type should not exist because it already has a complex type of S3Destination", - "operations": [ - { - "path": "/Properties/S3/PrimitiveType", - "op": "remove" - } - ] - } - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/cfnspec/spec-source/1001_Evidently_Project_DataDelivery_patch.json b/packages/@aws-cdk/cfnspec/spec-source/1001_Evidently_Project_DataDelivery_patch.json deleted file mode 100644 index d68d81ab1b26c..0000000000000 --- a/packages/@aws-cdk/cfnspec/spec-source/1001_Evidently_Project_DataDelivery_patch.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "ResourceTypes": { - "AWS::Evidently::Project": { - "patch": { - "description": "Primitive type should not exist because it already has a complex type of DataDeliveryObject", - "operations": [ - { - "path": "/Properties/DataDelivery/PrimitiveType", - "op": "remove" - } - ] - } - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/cfnspec/spec-source/1002_Lex_BotAlias_TextLogDestination_patch.json b/packages/@aws-cdk/cfnspec/spec-source/1002_Lex_BotAlias_TextLogDestination_patch.json new file mode 100644 index 0000000000000..a34166be675b3 --- /dev/null +++ b/packages/@aws-cdk/cfnspec/spec-source/1002_Lex_BotAlias_TextLogDestination_patch.json @@ -0,0 +1,15 @@ +{ + "PropertyTypes": { + "AWS::Lex::BotAlias.TextLogDestination": { + "patch": { + "description": "Temporarily remove AWS::Lex::BotAlias.TextLogDestination.CloudWatch until cfn specs for it are stable.", + "operations": [ + { + "op": "remove", + "path": "/Properties/CloudWatch" + } + ] + } + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/package.json b/packages/@aws-cdk/cloudformation-include/package.json index fe576932f7642..6bb764b0fe253 100644 --- a/packages/@aws-cdk/cloudformation-include/package.json +++ b/packages/@aws-cdk/cloudformation-include/package.json @@ -180,6 +180,7 @@ "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-lakeformation": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-lex": "0.0.0", "@aws-cdk/aws-licensemanager": "0.0.0", "@aws-cdk/aws-lightsail": "0.0.0", "@aws-cdk/aws-location": "0.0.0", @@ -363,6 +364,7 @@ "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-lakeformation": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-lex": "0.0.0", "@aws-cdk/aws-licensemanager": "0.0.0", "@aws-cdk/aws-lightsail": "0.0.0", "@aws-cdk/aws-location": "0.0.0", diff --git a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts index e88b441637cfc..d56558ef1be31 100644 --- a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts +++ b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts @@ -161,8 +161,8 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent if (call) { + let credentials; if (call.assumedRoleArn) { - const timestamp = (new Date()).getTime(); const params = { @@ -170,10 +170,9 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent RoleSessionName: `${timestamp}-${physicalResourceId}`.substring(0, 64), }; - AWS.config.credentials = new AWS.ChainableTemporaryCredentials({ + credentials = new AWS.ChainableTemporaryCredentials({ params: params, }); - } if (!Object.prototype.hasOwnProperty.call(AWS, call.service)) { @@ -181,6 +180,7 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent } const awsService = new (AWS as any)[call.service]({ apiVersion: call.apiVersion, + credentials: credentials, region: call.region, }); diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/runtime/index.test.ts b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/runtime/index.test.ts new file mode 100644 index 0000000000000..f16812a7d0c30 --- /dev/null +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/runtime/index.test.ts @@ -0,0 +1,158 @@ +import * as AWS from 'aws-sdk'; +import { PhysicalResourceId } from '../../../lib'; +import { handler } from '../../../lib/aws-custom-resource/runtime/index'; + +/* eslint-disable no-console */ +console.log = jest.fn(); + +jest.mock('aws-sdk', () => { + return { + ...jest.requireActual('aws-sdk'), + SSM: jest.fn(() => { + return { + config: { + apiVersion: 'apiVersion', + region: 'eu-west-1', + }, + getParameter: () => { + return { + promise: async () => {}, + }; + }, + }; + }), + }; +}); + +jest.mock('https', () => { + return { + request: (_: any, callback: () => void) => { + return { + on: () => undefined, + write: () => true, + end: callback, + }; + }, + }; +}); + +afterEach(() => { + jest.clearAllMocks(); +}); + +test('SDK global credentials are never set', async () => { + // WHEN + await handler({ + LogicalResourceId: 'logicalResourceId', + RequestId: 'requestId', + RequestType: 'Create', + ResponseURL: 'responseUrl', + ResourceProperties: { + Create: JSON.stringify({ + action: 'getParameter', + assumedRoleArn: 'arn:aws:iam::123456789012:role/CoolRole', + parameters: { + Name: 'foo', + }, + physicalResourceId: PhysicalResourceId.of('id'), + service: 'SSM', + }), + ServiceToken: 'serviceToken', + }, + ResourceType: 'resourceType', + ServiceToken: 'serviceToken', + StackId: 'stackId', + }, {} as AWSLambda.Context); + + // THEN + expect(AWS.config).toBeInstanceOf(AWS.Config); + expect(AWS.config.credentials).toBeNull(); +}); + +test('SDK credentials are not persisted across subsequent invocations', async () => { + // GIVEN + const mockCreds = new AWS.ChainableTemporaryCredentials(); + jest.spyOn(AWS, 'ChainableTemporaryCredentials').mockReturnValue(mockCreds); + + // WHEN + await handler({ + LogicalResourceId: 'logicalResourceId', + RequestId: 'requestId', + RequestType: 'Create', + ResponseURL: 'responseUrl', + ResourceProperties: { + Create: JSON.stringify({ + action: 'getParameter', + parameters: { + Name: 'foo', + }, + physicalResourceId: PhysicalResourceId.of('id'), + service: 'SSM', + }), + ServiceToken: 'serviceToken', + }, + ResourceType: 'resourceType', + ServiceToken: 'serviceToken', + StackId: 'stackId', + }, {} as AWSLambda.Context); + + await handler({ + LogicalResourceId: 'logicalResourceId', + RequestId: 'requestId', + RequestType: 'Create', + ResponseURL: 'responseUrl', + ResourceProperties: { + Create: JSON.stringify({ + action: 'getParameter', + assumedRoleArn: 'arn:aws:iam::123456789012:role/CoolRole', + parameters: { + Name: 'foo', + }, + physicalResourceId: PhysicalResourceId.of('id'), + service: 'SSM', + }), + ServiceToken: 'serviceToken', + }, + ResourceType: 'resourceType', + ServiceToken: 'serviceToken', + StackId: 'stackId', + }, {} as AWSLambda.Context); + + await handler({ + LogicalResourceId: 'logicalResourceId', + RequestId: 'requestId', + RequestType: 'Create', + ResponseURL: 'responseUrl', + ResourceProperties: { + Create: JSON.stringify({ + action: 'getParameter', + parameters: { + Name: 'foo', + }, + physicalResourceId: PhysicalResourceId.of('id'), + service: 'SSM', + }), + ServiceToken: 'serviceToken', + }, + ResourceType: 'resourceType', + ServiceToken: 'serviceToken', + StackId: 'stackId', + }, {} as AWSLambda.Context); + + // THEN + expect(AWS.SSM).toHaveBeenNthCalledWith(1, { + apiVersion: undefined, + credentials: undefined, + region: undefined, + }); + expect(AWS.SSM).toHaveBeenNthCalledWith(2, { + apiVersion: undefined, + credentials: mockCreds, + region: undefined, + }); + expect(AWS.SSM).toHaveBeenNthCalledWith(3, { + apiVersion: undefined, + credentials: undefined, + region: undefined, + }); +}); diff --git a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts index 6d95f24e5a7e8..6f3b0abd737f1 100644 --- a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts +++ b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts @@ -196,83 +196,161 @@ export const APPMESH_ECR_ACCOUNTS: { [region: string]: string } = { // https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-extension-versions.html export const CLOUDWATCH_LAMBDA_INSIGHTS_ARNS: { [key: string]: any } = { + '1.0.119.0': { + arm64: { + // US East (N. Virginia) + 'us-east-1': 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension-Arm64:1', + // US East (Ohio) + 'us-east-2': 'arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension-Arm64:1', + // US West (Oregon) + 'us-west-2': 'arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:1', + // Asia Pacific (Mumbai) + 'ap-south-1': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension-Arm64:1', + // Asia Pacific (Singapore) + 'ap-southeast-1': 'arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension-Arm64:1', + // Asia Pacific (Sydney) + 'ap-southeast-2': 'arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension-Arm64:1', + // Asia Pacific (Tokyo) + 'ap-northeast-1': 'arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension-Arm64:1', + // Europe (Frankfurt) + 'eu-central-1': 'arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension-Arm64:1', + // Europe (Ireland) + 'eu-west-1': 'arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension-Arm64:1', + // Europe (London) + 'eu-west-2': 'arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:1', + }, + x86_64: { + // US East (N. Virginia) + 'us-east-1': 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:16', + // US East (Ohio) + 'us-east-2': 'arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:16', + // US West (N. California) + 'us-west-1': 'arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:16', + // US West (Oregon) + 'us-west-2': 'arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:16', + // Africa (Cape Town) + 'af-south-1': 'arn:aws:lambda:af-south-1:012438385374:layer:LambdaInsightsExtension:9', + // Asia Pacific (Hong Kong) + 'ap-east-1': 'arn:aws:lambda:ap-east-1:519774774795:layer:LambdaInsightsExtension:9', + // Asia Pacific (Mumbai) + 'ap-south-1': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:16', + // Asia Pacific (Seoul) + 'ap-northeast-2': 'arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:16', + // Asia Pacific (Singapore) + 'ap-southeast-1': 'arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:16', + // Asia Pacific (Sydney) + 'ap-southeast-2': 'arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:16', + // Asia Pacific (Tokyo) + 'ap-northeast-1': 'arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:23', + // Canada (Central) + 'ca-central-1': 'arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:16', + // China (Beijing) + 'cn-north-1': 'arn:aws-cn:lambda:cn-north-1:488211338238:layer:LambdaInsightsExtension:9', + // China (Ningxia) + 'cn-northwest-1': 'arn:aws-cn:lambda:cn-northwest-1:488211338238:layer:LambdaInsightsExtension:9', + // Europe (Frankfurt) + 'eu-central-1': 'arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:16', + // Europe (Ireland) + 'eu-west-1': 'arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:16', + // Europe (London) + 'eu-west-2': 'arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:16', + // Europe (Milan) + 'eu-south-1': 'arn:aws:lambda:eu-south-1:339249233099:layer:LambdaInsightsExtension:9', + // Europe (Paris) + 'eu-west-3': 'arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:16', + // Europe (Stockholm) + 'eu-north-1': 'arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:16', + // Middle East (Bahrain) + 'me-south-1': 'arn:aws:lambda:me-south-1:285320876703:layer:LambdaInsightsExtension:9', + // South America (Sao Paulo) + 'sa-east-1': 'arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:16', + }, + }, '1.0.98.0': { - 'us-east-1': 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:14', - 'us-east-2': 'arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:14', - 'us-west-1': 'arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:14', - 'us-west-2': 'arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:14', - 'af-south-1': 'arn:aws:lambda:af-south-1:012438385374:layer:LambdaInsightsExtension:8', - 'ap-east-1': 'arn:aws:lambda:ap-east-1:519774774795:layer:LambdaInsightsExtension:8', - 'ap-south-1': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:14', - 'ap-northeast-2': 'arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:14', - 'ap-southeast-1': 'arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:14', - 'ap-southeast-2': 'arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:14', - 'ap-northeast-1': 'arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:14', - 'ca-central-1': 'arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:14', - 'cn-north-1': 'arn:aws-cn:lambda:cn-north-1:488211338238:layer:LambdaInsightsExtension:8', - 'cn-northwest-1': 'arn:aws-cn:lambda:cn-northwest-1:488211338238:layer:LambdaInsightsExtension:8', - 'eu-central-1': 'arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:14', - 'eu-west-1': 'arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:14', - 'eu-west-2': 'arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:14', - 'eu-south-1': 'arn:aws:lambda:eu-south-1:339249233099:layer:LambdaInsightsExtension:8', - 'eu-west-3': 'arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:14', - 'eu-north-1': 'arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:14', - 'me-south-1': 'arn:aws:lambda:me-south-1:285320876703:layer:LambdaInsightsExtension:8', - 'sa-east-1': 'arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:14', + x86_64: { + 'us-east-1': 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:14', + 'us-east-2': 'arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:14', + 'us-west-1': 'arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:14', + 'us-west-2': 'arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:14', + 'af-south-1': 'arn:aws:lambda:af-south-1:012438385374:layer:LambdaInsightsExtension:8', + 'ap-east-1': 'arn:aws:lambda:ap-east-1:519774774795:layer:LambdaInsightsExtension:8', + 'ap-south-1': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:14', + 'ap-northeast-2': 'arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:14', + 'ap-southeast-1': 'arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:14', + 'ap-southeast-2': 'arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:14', + 'ap-northeast-1': 'arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:14', + 'ca-central-1': 'arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:14', + 'cn-north-1': 'arn:aws-cn:lambda:cn-north-1:488211338238:layer:LambdaInsightsExtension:8', + 'cn-northwest-1': 'arn:aws-cn:lambda:cn-northwest-1:488211338238:layer:LambdaInsightsExtension:8', + 'eu-central-1': 'arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:14', + 'eu-west-1': 'arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:14', + 'eu-west-2': 'arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:14', + 'eu-south-1': 'arn:aws:lambda:eu-south-1:339249233099:layer:LambdaInsightsExtension:8', + 'eu-west-3': 'arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:14', + 'eu-north-1': 'arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:14', + 'me-south-1': 'arn:aws:lambda:me-south-1:285320876703:layer:LambdaInsightsExtension:8', + 'sa-east-1': 'arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:14', + }, }, '1.0.89.0': { - 'us-east-1': 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:12', - 'us-east-2': 'arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:12', - 'us-west-1': 'arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:12', - 'us-west-2': 'arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:12', - 'ap-south-1': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:12', - 'ap-northeast-2': 'arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:12', - 'ap-southeast-1': 'arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:12', - 'ap-southeast-2': 'arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:12', - 'ap-northeast-1': 'arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:12', - 'ca-central-1': 'arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:12', - 'eu-central-1': 'arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:12', - 'eu-west-1': 'arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:12', - 'eu-west-2': 'arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:12', - 'eu-west-3': 'arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:12', - 'eu-north-1': 'arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:12', - 'sa-east-1': 'arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:12', + x86_64: { + 'us-east-1': 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:12', + 'us-east-2': 'arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:12', + 'us-west-1': 'arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:12', + 'us-west-2': 'arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:12', + 'ap-south-1': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:12', + 'ap-northeast-2': 'arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:12', + 'ap-southeast-1': 'arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:12', + 'ap-southeast-2': 'arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:12', + 'ap-northeast-1': 'arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:12', + 'ca-central-1': 'arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:12', + 'eu-central-1': 'arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:12', + 'eu-west-1': 'arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:12', + 'eu-west-2': 'arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:12', + 'eu-west-3': 'arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:12', + 'eu-north-1': 'arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:12', + 'sa-east-1': 'arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:12', + }, }, '1.0.86.0': { - 'us-east-1': 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:11', - 'us-east-2': 'arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:11', - 'us-west-1': 'arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:11', - 'us-west-2': 'arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:11', - 'ap-south-1': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:11', - 'ap-northeast-2': 'arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:11', - 'ap-southeast-1': 'arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:11', - 'ap-southeast-2': 'arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:11', - 'ap-northeast-1': 'arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:11', - 'ca-central-1': 'arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:11', - 'eu-central-1': 'arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:11', - 'eu-west-1': 'arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:11', - 'eu-west-2': 'arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:11', - 'eu-west-3': 'arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:11', - 'eu-north-1': 'arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:11', - 'sa-east-1': 'arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:11', + x86_64: { + 'us-east-1': 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:11', + 'us-east-2': 'arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:11', + 'us-west-1': 'arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:11', + 'us-west-2': 'arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:11', + 'ap-south-1': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:11', + 'ap-northeast-2': 'arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:11', + 'ap-southeast-1': 'arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:11', + 'ap-southeast-2': 'arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:11', + 'ap-northeast-1': 'arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:11', + 'ca-central-1': 'arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:11', + 'eu-central-1': 'arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:11', + 'eu-west-1': 'arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:11', + 'eu-west-2': 'arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:11', + 'eu-west-3': 'arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:11', + 'eu-north-1': 'arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:11', + 'sa-east-1': 'arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:11', + }, }, '1.0.54.0': { - 'us-east-1': 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:2', - 'us-east-2': 'arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:2', - 'us-west-1': 'arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:2', - 'us-west-2': 'arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:2', - 'ap-south-1': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:2', - 'ap-northeast-2': 'arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:2', - 'ap-southeast-1': 'arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:2', - 'ap-southeast-2': 'arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:2', - 'ap-northeast-1': 'arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:2', - 'ca-central-1': 'arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:2', - 'eu-central-1': 'arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:2', - 'eu-west-1': 'arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:2', - 'eu-west-2': 'arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:2', - 'eu-west-3': 'arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:2', - 'eu-north-1': 'arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:2', - 'sa-east-1': 'arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:2', + x86_64: { + 'us-east-1': 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:2', + 'us-east-2': 'arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:2', + 'us-west-1': 'arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:2', + 'us-west-2': 'arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:2', + 'ap-south-1': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:2', + 'ap-northeast-2': 'arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:2', + 'ap-southeast-1': 'arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:2', + 'ap-southeast-2': 'arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:2', + 'ap-northeast-1': 'arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:2', + 'ca-central-1': 'arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:2', + 'eu-central-1': 'arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:2', + 'eu-west-1': 'arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:2', + 'eu-west-2': 'arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:2', + 'eu-west-3': 'arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:2', + 'eu-north-1': 'arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:2', + 'sa-east-1': 'arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:2', + }, }, }; diff --git a/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts b/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts index 006de89d3994d..23cdbc85071bb 100644 --- a/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts +++ b/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts @@ -79,7 +79,10 @@ async function main(): Promise { } for (const version in CLOUDWATCH_LAMBDA_INSIGHTS_ARNS) { - registerFact(region, ['cloudwatchLambdaInsightsVersion', version], CLOUDWATCH_LAMBDA_INSIGHTS_ARNS[version][region]); + for (const arch in CLOUDWATCH_LAMBDA_INSIGHTS_ARNS[version]) { + registerFact(region, ['cloudwatchLambdaInsightsVersion', version, arch], CLOUDWATCH_LAMBDA_INSIGHTS_ARNS[version][arch][region]); + + } } } lines.push(' }'); @@ -112,13 +115,16 @@ function checkRegions(map: Record) { * Verifies that the provided map of to region to fact does not contain an entry * for a region that was not registered in `AWS_REGIONS`. */ -function checkRegionsSubMap(map: Record>) { +function checkRegionsSubMap(map: Record>>) { const allRegions = new Set(AWS_REGIONS); for (const key of Object.keys(map)) { - for (const region of Object.keys(map[key])) { - if (!allRegions.has(region)) { - throw new Error(`Un-registered region fact found: ${region}. Add to AWS_REGIONS list!`); + for (const subKey of Object.keys(map[key])) { + for (const region of Object.keys(map[key][subKey])) { + if (!allRegions.has(region)) { + throw new Error(`Un-registered region fact found: ${region}. Add to AWS_REGIONS list!`); + } } + } } } diff --git a/packages/@aws-cdk/region-info/lib/fact.ts b/packages/@aws-cdk/region-info/lib/fact.ts index 8d4e33802be43..498e33d693abf 100644 --- a/packages/@aws-cdk/region-info/lib/fact.ts +++ b/packages/@aws-cdk/region-info/lib/fact.ts @@ -165,8 +165,11 @@ export class FactName { /** * The ARN of CloudWatch Lambda Insights for a version (e.g. 1.0.98.0) */ - public static cloudwatchLambdaInsightsVersion(version: string) { - return `cloudwatch-lambda-insights-version:${version.split('.').join('_')}`; + public static cloudwatchLambdaInsightsVersion(version: string, arch?: string) { + // if we are provided an architecture use that, otherwise + // default to x86_64 for backwards compatibility + const suffix = version.split('.').join('_') + `_${arch ?? 'x86_64'}`; + return `cloudwatch-lambda-insights-version:${suffix}`; } /** diff --git a/packages/@aws-cdk/region-info/lib/region-info.ts b/packages/@aws-cdk/region-info/lib/region-info.ts index 3482acf66b9a1..35dce3d6ca980 100644 --- a/packages/@aws-cdk/region-info/lib/region-info.ts +++ b/packages/@aws-cdk/region-info/lib/region-info.ts @@ -120,9 +120,10 @@ export class RegionInfo { /** * The ARN of the CloudWatch Lambda Insights extension, for the given version. * @param insightsVersion the version (e.g. 1.0.98.0) + * @param architecture the Lambda Function architecture (e.g. 'x86_64' or 'arm64') */ - public cloudwatchLambdaInsightsArn(insightsVersion: string): string | undefined { - return Fact.find(this.name, FactName.cloudwatchLambdaInsightsVersion(insightsVersion)); + public cloudwatchLambdaInsightsArn(insightsVersion: string, architecture?: string): string | undefined { + return Fact.find(this.name, FactName.cloudwatchLambdaInsightsVersion(insightsVersion, architecture)); } /** diff --git a/packages/@aws-cdk/region-info/test/__snapshots__/region-info.test.js.snap b/packages/@aws-cdk/region-info/test/__snapshots__/region-info.test.js.snap index f1797f8083af5..39b8766351e37 100644 --- a/packages/@aws-cdk/region-info/test/__snapshots__/region-info.test.js.snap +++ b/packages/@aws-cdk/region-info/test/__snapshots__/region-info.test.js.snap @@ -5,7 +5,11 @@ Object { "af-south-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:af-south-1:012438385374:layer:LambdaInsightsExtension:9", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -31,7 +35,11 @@ Object { "ap-east-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:ap-east-1:519774774795:layer:LambdaInsightsExtension:9", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -57,7 +65,11 @@ Object { "ap-northeast-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension-Arm64:1", + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:23", "1.0.54.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:12", @@ -83,7 +95,11 @@ Object { "ap-northeast-2": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:16", "1.0.54.0": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:12", @@ -109,7 +125,11 @@ Object { "ap-northeast-3": Object { "cdkMetadataResourceAvailable": false, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -135,7 +155,11 @@ Object { "ap-south-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension-Arm64:1", + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.54.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:12", @@ -161,7 +185,11 @@ Object { "ap-southeast-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension-Arm64:1", + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.54.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:12", @@ -187,7 +215,11 @@ Object { "ap-southeast-2": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension-Arm64:1", + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:16", "1.0.54.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:12", @@ -213,7 +245,11 @@ Object { "ca-central-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.54.0": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:12", @@ -239,7 +275,11 @@ Object { "cn-north-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com.cn", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws-cn:lambda:cn-north-1:488211338238:layer:LambdaInsightsExtension:9", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -265,7 +305,11 @@ Object { "cn-northwest-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com.cn", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws-cn:lambda:cn-northwest-1:488211338238:layer:LambdaInsightsExtension:9", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -291,7 +335,11 @@ Object { "eu-central-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension-Arm64:1", + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.54.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:12", @@ -317,7 +365,11 @@ Object { "eu-north-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.54.0": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:12", @@ -343,7 +395,11 @@ Object { "eu-south-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:eu-south-1:339249233099:layer:LambdaInsightsExtension:9", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -369,7 +425,11 @@ Object { "eu-west-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension-Arm64:1", + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.54.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:12", @@ -395,7 +455,11 @@ Object { "eu-west-2": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:1", + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:16", "1.0.54.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:12", @@ -421,7 +485,11 @@ Object { "eu-west-3": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:16", "1.0.54.0": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:12", @@ -447,7 +515,11 @@ Object { "me-south-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:me-south-1:285320876703:layer:LambdaInsightsExtension:9", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -473,7 +545,11 @@ Object { "sa-east-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.54.0": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:12", @@ -499,7 +575,11 @@ Object { "us-east-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension-Arm64:1", + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.54.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:12", @@ -525,7 +605,11 @@ Object { "us-east-2": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension-Arm64:1", + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:16", "1.0.54.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:12", @@ -551,7 +635,11 @@ Object { "us-gov-east-1": Object { "cdkMetadataResourceAvailable": false, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -577,7 +665,11 @@ Object { "us-gov-west-1": Object { "cdkMetadataResourceAvailable": false, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -603,7 +695,11 @@ Object { "us-iso-east-1": Object { "cdkMetadataResourceAvailable": false, "domainSuffix": "c2s.ic.gov", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -629,7 +725,11 @@ Object { "us-isob-east-1": Object { "cdkMetadataResourceAvailable": false, "domainSuffix": "sc2s.sgov.gov", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -655,7 +755,11 @@ Object { "us-west-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": undefined, + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.54.0": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:12", @@ -681,7 +785,11 @@ Object { "us-west-2": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", + "lambdaInsightsArmVersions": Object { + "1.0.119.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:1", + }, "lambdaInsightsVersions": Object { + "1.0.119.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:16", "1.0.54.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:12", diff --git a/packages/@aws-cdk/region-info/test/region-info.test.ts b/packages/@aws-cdk/region-info/test/region-info.test.ts index 8c06907052b23..984d5e31cf45d 100644 --- a/packages/@aws-cdk/region-info/test/region-info.test.ts +++ b/packages/@aws-cdk/region-info/test/region-info.test.ts @@ -9,13 +9,19 @@ test('built-in data is correct', () => { const servicePrincipals: { [service: string]: string | undefined } = {}; const lambdaInsightsVersions: { [service: string]: string | undefined } = {}; + const lambdaInsightsArmVersions: { [service: string]: string | undefined } = {}; AWS_SERVICES.forEach(service => servicePrincipals[service] = region.servicePrincipal(service)); for (const version in CLOUDWATCH_LAMBDA_INSIGHTS_ARNS) { lambdaInsightsVersions[version] = region.cloudwatchLambdaInsightsArn(version); + + if ('arm64' in CLOUDWATCH_LAMBDA_INSIGHTS_ARNS[version]) { + lambdaInsightsArmVersions[version] = region.cloudwatchLambdaInsightsArn(version, 'arm64'); + } }; + snapshot[name] = { cdkMetadataResourceAvailable: region.cdkMetadataResourceAvailable, domainSuffix: region.domainSuffix, @@ -24,6 +30,7 @@ test('built-in data is correct', () => { vpcEndPointServiceNamePrefix: region.vpcEndpointServiceNamePrefix, servicePrincipals, lambdaInsightsVersions, + lambdaInsightsArmVersions, }; } expect(snapshot).toMatchSnapshot(); diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index 75d5a983d7e6a..132429ed8bc35 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -248,6 +248,7 @@ "@aws-cdk/aws-lambda-go": "0.0.0", "@aws-cdk/aws-lambda-nodejs": "0.0.0", "@aws-cdk/aws-lambda-python": "0.0.0", + "@aws-cdk/aws-lex": "0.0.0", "@aws-cdk/aws-licensemanager": "0.0.0", "@aws-cdk/aws-lightsail": "0.0.0", "@aws-cdk/aws-location": "0.0.0", diff --git a/packages/aws-cdk/README.md b/packages/aws-cdk/README.md index 1bd9e491e34e4..ac399df3008df 100644 --- a/packages/aws-cdk/README.md +++ b/packages/aws-cdk/README.md @@ -364,6 +364,7 @@ Hotswapping is currently supported for the following changes - Code asset changes of AWS Lambda functions. - Definition changes of AWS Step Functions State Machines. - Container asset changes of AWS ECS Services. +- Website asset changes of AWS S3 Bucket Deployments. **⚠ Note #1**: This command deliberately introduces drift in CloudFormation stacks in order to speed up deployments. For this reason, only use it for development purposes. diff --git a/packages/aws-cdk/lib/api/hotswap-deployments.ts b/packages/aws-cdk/lib/api/hotswap-deployments.ts index 0d8a4165f9401..b40ba030588ac 100644 --- a/packages/aws-cdk/lib/api/hotswap-deployments.ts +++ b/packages/aws-cdk/lib/api/hotswap-deployments.ts @@ -7,6 +7,7 @@ import { ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, Hotswappabl import { isHotswappableEcsServiceChange } from './hotswap/ecs-services'; import { EvaluateCloudFormationTemplate } from './hotswap/evaluate-cloudformation-template'; import { isHotswappableLambdaFunctionChange } from './hotswap/lambda-functions'; +import { isHotswappableS3BucketDeploymentChange } from './hotswap/s3-bucket-deployments'; import { isHotswappableStateMachineChange } from './hotswap/stepfunctions-state-machines'; import { CloudFormationStack } from './util/cloudformation'; @@ -73,6 +74,7 @@ async function findAllHotswappableChanges( isHotswappableLambdaFunctionChange(logicalId, resourceHotswapEvaluation, evaluateCfnTemplate), isHotswappableStateMachineChange(logicalId, resourceHotswapEvaluation, evaluateCfnTemplate), isHotswappableEcsServiceChange(logicalId, resourceHotswapEvaluation, evaluateCfnTemplate), + isHotswappableS3BucketDeploymentChange(logicalId, resourceHotswapEvaluation, evaluateCfnTemplate), ]); } }); diff --git a/packages/aws-cdk/lib/api/hotswap/evaluate-cloudformation-template.ts b/packages/aws-cdk/lib/api/hotswap/evaluate-cloudformation-template.ts index 5dc9f948a4250..011a9b28fc394 100644 --- a/packages/aws-cdk/lib/api/hotswap/evaluate-cloudformation-template.ts +++ b/packages/aws-cdk/lib/api/hotswap/evaluate-cloudformation-template.ts @@ -50,6 +50,11 @@ export class EvaluateCloudFormationTemplate { return stackResources.find(sr => sr.LogicalResourceId === logicalId)?.PhysicalResourceId; } + public async findLogicalIdForPhysicalName(physicalName: string): Promise { + const stackResources = await this.stackResources.listStackResources(); + return stackResources.find(sr => sr.PhysicalResourceId === physicalName)?.LogicalResourceId; + } + public findReferencesTo(logicalId: string): Array { const ret = new Array(); for (const [resourceLogicalId, resourceDef] of Object.entries(this.template?.Resources ?? {})) { diff --git a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts index c45bb432ac65d..9a02fc2ba2d2f 100644 --- a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts +++ b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts @@ -3,8 +3,8 @@ import { assetMetadataChanged, ChangeHotswapImpact, ChangeHotswapResult, Hotswap import { EvaluateCloudFormationTemplate } from './evaluate-cloudformation-template'; /** - * Returns `false` if the change cannot be short-circuited, - * `true` if the change is irrelevant from a short-circuit perspective + * Returns `ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT` if the change cannot be short-circuited, + * `ChangeHotswapImpact.IRRELEVANT` if the change is irrelevant from a short-circuit perspective * (like a change to CDKMetadata), * or a LambdaFunctionResource if the change can be short-circuited. */ diff --git a/packages/aws-cdk/lib/api/hotswap/s3-bucket-deployments.ts b/packages/aws-cdk/lib/api/hotswap/s3-bucket-deployments.ts new file mode 100644 index 0000000000000..49ba4ce129759 --- /dev/null +++ b/packages/aws-cdk/lib/api/hotswap/s3-bucket-deployments.ts @@ -0,0 +1,122 @@ +import { ISDK } from '../aws-auth'; +import { ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, HotswappableChangeCandidate/*, establishResourcePhysicalName*/ } from './common'; +import { EvaluateCloudFormationTemplate } from './evaluate-cloudformation-template'; + +/** + * This means that the value is required to exist by CloudFormation's API (or our S3 Bucket Deployment Lambda) + * but the actual value specified is irrelevant + */ +export const REQUIRED_BY_CFN = 'required-to-be-present-by-cfn'; + +export async function isHotswappableS3BucketDeploymentChange( + logicalId: string, change: HotswappableChangeCandidate, evaluateCfnTemplate: EvaluateCloudFormationTemplate, +): Promise { + // In old-style synthesis, the policy used by the lambda to copy assets Ref's the assets directly, + // meaning that the changes made to the Policy are artifacts that can be safely ignored + if (change.newValue.Type === 'AWS::IAM::Policy') { + return changeIsForS3DeployCustomResourcePolicy(logicalId, change, evaluateCfnTemplate); + } + + if (change.newValue.Type !== 'Custom::CDKBucketDeployment') { + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + + // note that this gives the ARN of the lambda, not the name. This is fine though, the invoke() sdk call will take either + const functionName = await evaluateCfnTemplate.evaluateCfnExpression(change.newValue.Properties?.ServiceToken); + if (!functionName) { + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + + const customResourceProperties = await evaluateCfnTemplate.evaluateCfnExpression({ + ...change.newValue.Properties, + ServiceToken: undefined, + }); + + return new S3BucketDeploymentHotswapOperation(functionName, customResourceProperties); +} + +class S3BucketDeploymentHotswapOperation implements HotswapOperation { + public readonly service = 'custom-s3-deployment'; + + constructor(private readonly functionName: string, private readonly customResourceProperties: any) { + } + + public async apply(sdk: ISDK): Promise { + return sdk.lambda().invoke({ + FunctionName: this.functionName, + // Lambda refuses to take a direct JSON object and requires it to be stringify()'d + Payload: JSON.stringify({ + RequestType: 'Update', + ResponseURL: REQUIRED_BY_CFN, + PhysicalResourceId: REQUIRED_BY_CFN, + StackId: REQUIRED_BY_CFN, + RequestId: REQUIRED_BY_CFN, + LogicalResourceId: REQUIRED_BY_CFN, + ResourceProperties: stringifyObject(this.customResourceProperties), // JSON.stringify() doesn't turn the actual objects to strings, but the lambda expects strings + }), + }).promise(); + } +} + +async function changeIsForS3DeployCustomResourcePolicy( + iamPolicyLogicalId: string, change: HotswappableChangeCandidate, evaluateCfnTemplate: EvaluateCloudFormationTemplate, +): Promise { + const roles = change.newValue.Properties?.Roles; + if (!roles) { + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + + for (const role of roles) { + const roleLogicalId = await evaluateCfnTemplate.findLogicalIdForPhysicalName(await evaluateCfnTemplate.evaluateCfnExpression(role)); + if (!roleLogicalId) { + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + + const roleRefs = evaluateCfnTemplate.findReferencesTo(roleLogicalId); + for (const roleRef of roleRefs) { + if (roleRef.Type === 'AWS::Lambda::Function') { + const lambdaRefs = evaluateCfnTemplate.findReferencesTo(roleRef.LogicalId); + for (const lambdaRef of lambdaRefs) { + // If S3Deployment -> Lambda -> Role and IAM::Policy -> Role, then this IAM::Policy change is an + // artifact of old-style synthesis + if (lambdaRef.Type !== 'Custom::CDKBucketDeployment') { + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + } + } else if (roleRef.Type === 'AWS::IAM::Policy') { + if (roleRef.LogicalId !== iamPolicyLogicalId) { + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + } else { + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + } + } + + return new EmptyHotswapOperation(); +} + +function stringifyObject(obj: any): any { + if (obj == null) { + return obj; + } + if (Array.isArray(obj)) { + return obj.map(stringifyObject); + } + if (typeof obj !== 'object') { + return obj.toString(); + } + + const ret: { [k: string]: any } = {}; + for (const [k, v] of Object.entries(obj)) { + ret[k] = stringifyObject(v); + } + return ret; +} + +class EmptyHotswapOperation implements HotswapOperation { + readonly service = 'empty'; + public async apply(sdk: ISDK): Promise { + return Promise.resolve(sdk); + } +} diff --git a/packages/aws-cdk/lib/api/hotswap/stepfunctions-state-machines.ts b/packages/aws-cdk/lib/api/hotswap/stepfunctions-state-machines.ts index dbaff58ab608b..3af97aef07455 100644 --- a/packages/aws-cdk/lib/api/hotswap/stepfunctions-state-machines.ts +++ b/packages/aws-cdk/lib/api/hotswap/stepfunctions-state-machines.ts @@ -1,5 +1,5 @@ import { ISDK } from '../aws-auth'; -import { ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, HotswappableChangeCandidate, establishResourcePhysicalName } from './common'; +import { ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, HotswappableChangeCandidate } from './common'; import { EvaluateCloudFormationTemplate } from './evaluate-cloudformation-template'; export async function isHotswappableStateMachineChange( @@ -11,15 +11,20 @@ export async function isHotswappableStateMachineChange( return stateMachineDefinitionChange; } - const machineNameInCfnTemplate = change.newValue?.Properties?.StateMachineName; - const machineName = await establishResourcePhysicalName(logicalId, machineNameInCfnTemplate, evaluateCfnTemplate); - if (!machineName) { + const stateMachineNameInCfnTemplate = change.newValue?.Properties?.StateMachineName; + const stateMachineArn = stateMachineNameInCfnTemplate + ? await evaluateCfnTemplate.evaluateCfnExpression({ + 'Fn::Sub': 'arn:${AWS::Partition}:states:${AWS::Region}:${AWS::AccountId}:stateMachine:' + stateMachineNameInCfnTemplate, + }) + : await evaluateCfnTemplate.findPhysicalNameFor(logicalId); + + if (!stateMachineArn) { return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; } return new StateMachineHotswapOperation({ definition: stateMachineDefinitionChange, - stateMachineName: machineName, + stateMachineArn: stateMachineArn, }); } @@ -43,7 +48,7 @@ async function isStateMachineDefinitionOnlyChange( } interface StateMachineResource { - readonly stateMachineName: string; + readonly stateMachineArn: string; readonly definition: string; } @@ -56,8 +61,7 @@ class StateMachineHotswapOperation implements HotswapOperation { public async apply(sdk: ISDK): Promise { // not passing the optional properties leaves them unchanged return sdk.stepFunctions().updateStateMachine({ - // even though the name of the property is stateMachineArn, passing the name of the state machine is allowed here - stateMachineArn: this.stepFunctionResource.stateMachineName, + stateMachineArn: this.stepFunctionResource.stateMachineArn, definition: this.stepFunctionResource.definition, }).promise(); } diff --git a/packages/aws-cdk/lib/util/asset-publishing.ts b/packages/aws-cdk/lib/util/asset-publishing.ts index 670231627ec92..2f85350f28f5e 100644 --- a/packages/aws-cdk/lib/util/asset-publishing.ts +++ b/packages/aws-cdk/lib/util/asset-publishing.ts @@ -50,7 +50,15 @@ class PublishingAws implements cdk_assets.IAws { } public async discoverCurrentAccount(): Promise { - return (await this.sdk({})).currentAccount(); + const account = await this.aws.defaultAccount(); + return account ?? { + accountId: '', + partition: 'aws', + }; + } + + public async discoverTargetAccount(options: cdk_assets.ClientOptions): Promise { + return (await this.sdk(options)).currentAccount(); } public async s3Client(options: cdk_assets.ClientOptions): Promise { diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts index f96b419b94b20..056e4728ff354 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts @@ -42,7 +42,8 @@ export function pushStackResourceSummaries(...items: CloudFormation.StackResourc } export function setCurrentCfnStackTemplate(template: Template) { - currentCfnStack.setTemplate(template); + const templateDeepCopy = JSON.parse(JSON.stringify(template)); // deep copy the template, so our tests can mutate one template instead of creating two + currentCfnStack.setTemplate(templateDeepCopy); } export function stackSummaryOf(logicalId: string, resourceType: string, physicalResourceId: string): CloudFormation.StackResourceSummary { @@ -87,6 +88,12 @@ export class HotswapMockSdkProvider { }); } + public setInvokeLambdaMock(mockInvokeLambda: (input: lambda.InvocationRequest) => lambda.InvocationResponse) { + this.mockSdkProvider.stubLambda({ + invoke: mockInvokeLambda, + }); + } + public stubEcs(stubs: SyncHandlerSubsetOf, additionalProperties: { [key: string]: any } = {}): void { this.mockSdkProvider.stubEcs(stubs, additionalProperties); } diff --git a/packages/aws-cdk/test/api/hotswap/s3-bucket-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/s3-bucket-hotswap-deployments.test.ts new file mode 100644 index 0000000000000..3a015308fcadc --- /dev/null +++ b/packages/aws-cdk/test/api/hotswap/s3-bucket-hotswap-deployments.test.ts @@ -0,0 +1,738 @@ +import { Lambda } from 'aws-sdk'; +import { REQUIRED_BY_CFN } from '../../../lib/api/hotswap/s3-bucket-deployments'; +import * as setup from './hotswap-test-setup'; + +let mockLambdaInvoke: (params: Lambda.Types.InvocationRequest) => Lambda.Types.InvocationResponse; +let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; + +const payloadWithoutCustomResProps = { + RequestType: 'Update', + ResponseURL: REQUIRED_BY_CFN, + PhysicalResourceId: REQUIRED_BY_CFN, + StackId: REQUIRED_BY_CFN, + RequestId: REQUIRED_BY_CFN, + LogicalResourceId: REQUIRED_BY_CFN, +}; + +beforeEach(() => { + hotswapMockSdkProvider = setup.setupHotswapTests(); + mockLambdaInvoke = jest.fn(); + hotswapMockSdkProvider.setInvokeLambdaMock(mockLambdaInvoke); +}); + +test('calls the lambdaInvoke() API when it receives only an asset difference in an S3 bucket deployment and evaluates CFN expressions in S3 Deployment Properties', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + S3Deployment: { + Type: 'Custom::CDKBucketDeployment', + Properties: { + ServiceToken: 'a-lambda-arn', + SourceBucketNames: ['src-bucket'], + SourceObjectKeys: ['src-key-old'], + DestinationBucketName: 'dest-bucket', + DestinationBucketKeyPrefix: 'my-key/some-old-prefix', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + S3Deployment: { + Type: 'Custom::CDKBucketDeployment', + Properties: { + ServiceToken: 'a-lambda-arn', + SourceBucketNames: ['src-bucket'], + SourceObjectKeys: { + 'Fn::Split': [ + '-', + 'key1-key2-key3', + ], + }, + DestinationBucketName: 'dest-bucket', + DestinationBucketKeyPrefix: 'my-key/some-new-prefix', + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + + expect(mockLambdaInvoke).toHaveBeenCalledWith({ + FunctionName: 'a-lambda-arn', + Payload: JSON.stringify({ + ...payloadWithoutCustomResProps, + ResourceProperties: { + SourceBucketNames: ['src-bucket'], + SourceObjectKeys: ['key1', 'key2', 'key3'], + DestinationBucketName: 'dest-bucket', + DestinationBucketKeyPrefix: 'my-key/some-new-prefix', + }, + }), + }); +}); + +test('does not call the invoke() API when a resource with type that is not Custom::CDKBucketDeployment but has the same properties is changed', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + S3Deployment: { + Type: 'Custom::NotCDKBucketDeployment', + Properties: { + SourceObjectKeys: ['src-key-old'], + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + S3Deployment: { + Type: 'Custom::NotCDKBucketDeployment', + Properties: { + SourceObjectKeys: ['src-key-new'], + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockLambdaInvoke).not.toHaveBeenCalled(); +}); + +test('does not call the invokeLambda() api if the updated Policy has no Roles', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Parameters: { + WebsiteBucketParamOld: { Type: 'String' }, + WebsiteBucketParamNew: { Type: 'String' }, + }, + Resources: { + S3Deployment: { + Type: 'Custom::CDKBucketDeployment', + Properties: { + ServiceToken: 'a-lambda-arn', + SourceObjectKeys: ['src-key-old'], + }, + }, + Policy: { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'my-policy', + PolicyDocument: { + Statement: [ + { + Action: ['s3:GetObject*'], + Effect: 'Allow', + Resource: { + Ref: 'WebsiteBucketParamOld', + }, + }, + ], + }, + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Parameters: { + WebsiteBucketParamOld: { Type: 'String' }, + WebsiteBucketParamNew: { Type: 'String' }, + }, + Resources: { + S3Deployment: { + Type: 'Custom::CDKBucketDeployment', + Properties: { + ServiceToken: 'a-lambda-arn', + SourceObjectKeys: ['src-key-new'], + }, + }, + Policy: { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'my-policy', + PolicyDocument: { + Statement: [ + { + Action: ['s3:GetObject*'], + Effect: 'Allow', + Resource: { + Ref: 'WebsiteBucketParamNew', + }, + }, + ], + }, + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockLambdaInvoke).not.toHaveBeenCalled(); +}); + +test('throws an error when the serviceToken fails evaluation in the template', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + S3Deployment: { + Type: 'Custom::CDKBucketDeployment', + Properties: { + ServiceToken: { + Ref: 'BadLamba', + }, + SourceBucketNames: ['src-bucket'], + SourceObjectKeys: ['src-key-old'], + DestinationBucketName: 'dest-bucket', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + S3Deployment: { + Type: 'Custom::CDKBucketDeployment', + Properties: { + ServiceToken: { + Ref: 'BadLamba', + }, + SourceBucketNames: ['src-bucket'], + SourceObjectKeys: ['src-key-new'], + DestinationBucketName: 'dest-bucket', + }, + }, + }, + }, + }); + + // WHEN + await expect(() => + hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact), + ).rejects.toThrow(/Parameter or resource 'BadLamba' could not be found for evaluation/); + + expect(mockLambdaInvoke).not.toHaveBeenCalled(); +}); + +describe('old-style synthesis', () => { + const parameters = { + WebsiteBucketParamOld: { Type: 'String' }, + WebsiteBucketParamNew: { Type: 'String' }, + DifferentBucketParamNew: { Type: 'String' }, + }; + + const serviceRole = { + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'lambda.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }, + }; + + const policyOld = { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'my-policy-old', + Roles: [ + { Ref: 'ServiceRole' }, + ], + PolicyDocument: { + Statement: [ + { + Action: ['s3:GetObject*'], + Effect: 'Allow', + Resource: { + Ref: 'WebsiteBucketParamOld', + }, + }, + ], + }, + }, + }; + + const policyNew = { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'my-policy-new', + Roles: [ + { Ref: 'ServiceRole' }, + ], + PolicyDocument: { + Statement: [ + { + Action: ['s3:GetObject*'], + Effect: 'Allow', + Resource: { + Ref: 'WebsiteBucketParamNew', + }, + }, + ], + }, + }, + }; + + const policy2Old = { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'my-policy-old-2', + Roles: [ + { Ref: 'ServiceRole' }, + ], + PolicyDocument: { + Statement: [ + { + Action: ['s3:GetObject*'], + Effect: 'Allow', + Resource: { + Ref: 'WebsiteBucketParamOld', + }, + }, + ], + }, + }, + }; + + const policy2New = { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'my-policy-new-2', + Roles: [ + { Ref: 'ServiceRole2' }, + ], + PolicyDocument: { + Statement: [ + { + Action: ['s3:GetObject*'], + Effect: 'Allow', + Resource: { + Ref: 'DifferentBucketParamOld', + }, + }, + ], + }, + }, + }; + + const deploymentLambda = { + Type: 'AWS::Lambda::Function', + Role: { + 'Fn::GetAtt': [ + 'ServiceRole', + 'Arn', + ], + }, + }; + + const s3DeploymentOld = { + Type: 'Custom::CDKBucketDeployment', + Properties: { + ServiceToken: { + 'Fn::GetAtt': [ + 'S3DeploymentLambda', + 'Arn', + ], + }, + SourceBucketNames: ['src-bucket-old'], + SourceObjectKeys: ['src-key-old'], + DestinationBucketName: 'WebsiteBucketOld', + }, + }; + + const s3DeploymentNew = { + Type: 'Custom::CDKBucketDeployment', + Properties: { + ServiceToken: { + 'Fn::GetAtt': [ + 'S3DeploymentLambda', + 'Arn', + ], + }, + SourceBucketNames: ['src-bucket-new'], + SourceObjectKeys: ['src-key-new'], + DestinationBucketName: 'WebsiteBucketNew', + }, + }; + + beforeEach(() => { + setup.pushStackResourceSummaries( + setup.stackSummaryOf('S3DeploymentLambda', 'AWS::Lambda::Function', 'my-deployment-lambda'), + setup.stackSummaryOf('ServiceRole', 'AWS::IAM::Role', 'my-service-role'), + ); + }); + + test('calls the lambdaInvoke() API when it receives an asset difference in an S3 bucket deployment and an IAM Policy difference using old-style synthesis', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Parameters: parameters, + ServiceRole: serviceRole, + Policy: policyOld, + S3DeploymentLambda: deploymentLambda, + S3Deployment: s3DeploymentOld, + }, + }); + + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Parameters: parameters, + ServiceRole: serviceRole, + Policy: policyNew, + S3DeploymentLambda: deploymentLambda, + S3Deployment: s3DeploymentNew, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact, { WebsiteBucketParamOld: 'WebsiteBucketOld', WebsiteBucketParamNew: 'WebsiteBucketNew' }); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(mockLambdaInvoke).toHaveBeenCalledWith({ + FunctionName: 'arn:aws:lambda:here:123456789012:function:my-deployment-lambda', + Payload: JSON.stringify({ + ...payloadWithoutCustomResProps, + ResourceProperties: { + SourceBucketNames: ['src-bucket-new'], + SourceObjectKeys: ['src-key-new'], + DestinationBucketName: 'WebsiteBucketNew', + }, + }), + }); + }); + + test('does not call the lambdaInvoke() API when the difference in the S3 deployment is referred to in one IAM policy change but not another', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + ServiceRole: serviceRole, + Policy1: policyOld, + Policy2: policy2Old, + S3DeploymentLambda: deploymentLambda, + S3Deployment: s3DeploymentOld, + }, + }); + + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + ServiceRole: serviceRole, + Policy1: policyNew, + Policy2: { + Properties: { + Roles: [ + { Ref: 'ServiceRole' }, + 'different-role', + ], + PolicyDocument: { + Statement: [ + { + Action: ['s3:GetObject*'], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'DifferentBucketNew', + 'Arn', + ], + }, + }, + ], + }, + }, + }, + S3DeploymentLambda: deploymentLambda, + S3Deployment: s3DeploymentNew, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockLambdaInvoke).not.toHaveBeenCalled(); + }); + + test('does not call the lambdaInvoke() API when the lambda that references the role is referred to by something other than an S3 deployment', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + ServiceRole: serviceRole, + Policy: policyOld, + S3DeploymentLambda: deploymentLambda, + S3Deployment: s3DeploymentOld, + Endpoint: { + Type: 'AWS::Lambda::Permission', + Properties: { + Action: 'lambda:InvokeFunction', + FunctionName: { + 'Fn::GetAtt': [ + 'S3DeploymentLambda', + 'Arn', + ], + }, + Principal: 'apigateway.amazonaws.com', + }, + }, + }, + }); + + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + ServiceRole: serviceRole, + Policy: policyNew, + S3DeploymentLambda: deploymentLambda, + S3Deployment: s3DeploymentNew, + Endpoint: { + Type: 'AWS::Lambda::Permission', + Properties: { + Action: 'lambda:InvokeFunction', + FunctionName: { + 'Fn::GetAtt': [ + 'S3DeploymentLambda', + 'Arn', + ], + }, + Principal: 'apigateway.amazonaws.com', + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockLambdaInvoke).not.toHaveBeenCalled(); + }); + + test('calls the lambdaInvoke() API when it receives an asset difference in two S3 bucket deployments and IAM Policy differences using old-style synthesis', async () => { + // GIVEN + const s3Deployment2Old = { + Type: 'Custom::CDKBucketDeployment', + Properties: { + ServiceToken: { + 'Fn::GetAtt': [ + 'S3DeploymentLambda2', + 'Arn', + ], + }, + SourceBucketNames: ['src-bucket-old'], + SourceObjectKeys: ['src-key-old'], + DestinationBucketName: 'DifferentBucketOld', + }, + }; + + const s3Deployment2New = { + Type: 'Custom::CDKBucketDeployment', + Properties: { + ServiceToken: { + 'Fn::GetAtt': [ + 'S3DeploymentLambda2', + 'Arn', + ], + }, + SourceBucketNames: ['src-bucket-new'], + SourceObjectKeys: ['src-key-new'], + DestinationBucketName: 'DifferentBucketNew', + }, + }; + + setup.setCurrentCfnStackTemplate({ + Resources: { + ServiceRole: serviceRole, + ServiceRole2: serviceRole, + Policy1: policyOld, + Policy2: policy2Old, + S3DeploymentLambda: deploymentLambda, + S3DeploymentLambda2: deploymentLambda, + S3Deployment: s3DeploymentOld, + S3Deployment2: s3Deployment2Old, + }, + }); + + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Parameters: parameters, + ServiceRole: serviceRole, + ServiceRole2: serviceRole, + Policy1: policyNew, + Policy2: policy2New, + S3DeploymentLambda: deploymentLambda, + S3DeploymentLambda2: deploymentLambda, + S3Deployment: s3DeploymentNew, + S3Deployment2: s3Deployment2New, + }, + }, + }); + + // WHEN + setup.pushStackResourceSummaries( + setup.stackSummaryOf('S3DeploymentLambda2', 'AWS::Lambda::Function', 'my-deployment-lambda-2'), + setup.stackSummaryOf('ServiceRole2', 'AWS::IAM::Role', 'my-service-role-2'), + ); + + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact, { + WebsiteBucketParamOld: 'WebsiteBucketOld', + WebsiteBucketParamNew: 'WebsiteBucketNew', + DifferentBucketParamNew: 'WebsiteBucketNew', + }); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(mockLambdaInvoke).toHaveBeenCalledWith({ + FunctionName: 'arn:aws:lambda:here:123456789012:function:my-deployment-lambda', + Payload: JSON.stringify({ + ...payloadWithoutCustomResProps, + ResourceProperties: { + SourceBucketNames: ['src-bucket-new'], + SourceObjectKeys: ['src-key-new'], + DestinationBucketName: 'WebsiteBucketNew', + }, + }), + }); + + expect(mockLambdaInvoke).toHaveBeenCalledWith({ + FunctionName: 'arn:aws:lambda:here:123456789012:function:my-deployment-lambda-2', + Payload: JSON.stringify({ + ...payloadWithoutCustomResProps, + ResourceProperties: { + SourceBucketNames: ['src-bucket-new'], + SourceObjectKeys: ['src-key-new'], + DestinationBucketName: 'DifferentBucketNew', + }, + }), + }); + }); + + test('does not call the lambdaInvoke() API when it receives an asset difference in an S3 bucket deployment that references two different policies', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + ServiceRole: serviceRole, + Policy1: policyOld, + Policy2: policy2Old, + S3DeploymentLambda: deploymentLambda, + S3Deployment: s3DeploymentOld, + }, + }); + + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + ServiceRole: serviceRole, + Policy1: policyNew, + Policy2: { + Properties: { + Roles: [ + { Ref: 'ServiceRole' }, + ], + PolicyDocument: { + Statement: [ + { + Action: ['s3:GetObject*'], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'DifferentBucketNew', + 'Arn', + ], + }, + }, + ], + }, + }, + }, + S3DeploymentLambda: deploymentLambda, + S3Deployment: s3DeploymentNew, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockLambdaInvoke).not.toHaveBeenCalled(); + }); + + test('does not call the lambdaInvoke() API when a policy is referenced by a resource that is not an S3 deployment', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + ServiceRole: serviceRole, + Policy1: policyOld, + S3DeploymentLambda: deploymentLambda, + S3Deployment: s3DeploymentOld, + NotADeployment: { + Type: 'AWS::Not::S3Deployment', + Properties: { + Prop: { + Ref: 'ServiceRole', + }, + }, + }, + }, + }); + + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + ServiceRole: serviceRole, + Policy1: policyNew, + S3DeploymentLambda: deploymentLambda, + S3Deployment: s3DeploymentNew, + NotADeployment: { + Type: 'AWS::Not::S3Deployment', + Properties: { + Prop: { + Ref: 'ServiceRole', + }, + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockLambdaInvoke).not.toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/packages/aws-cdk/test/api/hotswap/state-machine-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/state-machine-hotswap-deployments.test.ts index 289921fb2bd76..94467c5ac123d 100644 --- a/packages/aws-cdk/test/api/hotswap/state-machine-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/state-machine-hotswap-deployments.test.ts @@ -63,7 +63,7 @@ test('calls the updateStateMachine() API when it receives only a definitionStrin expect(deployStackResult).not.toBeUndefined(); expect(mockUpdateMachineDefinition).toHaveBeenCalledWith({ definition: '{ Prop: "new-value" }', - stateMachineArn: 'my-machine', + stateMachineArn: 'arn:aws:states:here:123456789012:stateMachine:my-machine', }); }); @@ -138,7 +138,7 @@ test('calls the updateStateMachine() API when it receives only a definitionStrin }, }, }, null, 2), - stateMachineArn: 'my-machine', + stateMachineArn: 'arn:aws:states:here:123456789012:stateMachine:my-machine', }); }); @@ -168,14 +168,14 @@ test('calls the updateStateMachine() API when it receives a change to the defini }); // WHEN - setup.pushStackResourceSummaries(setup.stackSummaryOf('Machine', 'AWS::StepFunctions::StateMachine', 'mock-machine-resource-id')); + setup.pushStackResourceSummaries(setup.stackSummaryOf('Machine', 'AWS::StepFunctions::StateMachine', 'arn:aws:states:here:123456789012:stateMachine:my-machine')); const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mockUpdateMachineDefinition).toHaveBeenCalledWith({ definition: '{ "Prop" : "new-value" }', - stateMachineArn: 'mock-machine-resource-id', // the sdk will convert the ID to the arn in a production environment + stateMachineArn: 'arn:aws:states:here:123456789012:stateMachine:my-machine', }); }); @@ -256,7 +256,7 @@ test('can correctly hotswap old style synth changes', async () => { setup.setCurrentCfnStackTemplate({ Parameters: { AssetParam1: { Type: 'String' } }, Resources: { - SM: { + Machine: { Type: 'AWS::StepFunctions::StateMachine', Properties: { DefinitionString: { Ref: 'AssetParam1' }, @@ -269,7 +269,7 @@ test('can correctly hotswap old style synth changes', async () => { template: { Parameters: { AssetParam2: { Type: String } }, Resources: { - SM: { + Machine: { Type: 'AWS::StepFunctions::StateMachine', Properties: { DefinitionString: { Ref: 'AssetParam2' }, @@ -281,13 +281,14 @@ test('can correctly hotswap old style synth changes', async () => { }); // WHEN + setup.pushStackResourceSummaries(setup.stackSummaryOf('Machine', 'AWS::StepFunctions::StateMachine', 'arn:aws:states:here:123456789012:stateMachine:my-machine')); const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact, { AssetParam2: 'asset-param-2' }); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mockUpdateMachineDefinition).toHaveBeenCalledWith({ definition: 'asset-param-2', - stateMachineArn: 'machine-name', + stateMachineArn: 'arn:aws:states:here:123456789012:stateMachine:machine-name', }); }); @@ -348,7 +349,7 @@ test('calls the updateStateMachine() API when it receives a change to the defini // WHEN setup.pushStackResourceSummaries( - setup.stackSummaryOf('Machine', 'AWS::StepFunctions::StateMachine', 'mock-machine-resource-id'), + setup.stackSummaryOf('Machine', 'AWS::StepFunctions::StateMachine', 'arn:aws:states:here:123456789012:stateMachine:my-machine'), setup.stackSummaryOf('Func', 'AWS::Lambda::Function', 'my-func'), ); const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); @@ -357,7 +358,7 @@ test('calls the updateStateMachine() API when it receives a change to the defini expect(deployStackResult).not.toBeUndefined(); expect(mockUpdateMachineDefinition).toHaveBeenCalledWith({ definition: '"Resource": arn:aws:lambda:here:123456789012:function:my-func', - stateMachineArn: 'my-machine', + stateMachineArn: 'arn:aws:states:here:123456789012:stateMachine:my-machine', }); }); @@ -446,7 +447,7 @@ test("will not perform a hotswap deployment if it doesn't know how to handle a s }, }); setup.pushStackResourceSummaries( - setup.stackSummaryOf('Machine', 'AWS::StepFunctions::StateMachine', 'my-machine'), + setup.stackSummaryOf('Machine', 'AWS::StepFunctions::StateMachine', 'arn:aws:states:here:123456789012:stateMachine:my-machine'), setup.stackSummaryOf('Bucket', 'AWS::S3::Bucket', 'my-bucket'), ); const cdkStackArtifact = setup.cdkStackArtifactOf({ diff --git a/packages/cdk-assets/lib/aws.ts b/packages/cdk-assets/lib/aws.ts index 936f0d94954e6..c35dedb38bbe2 100644 --- a/packages/cdk-assets/lib/aws.ts +++ b/packages/cdk-assets/lib/aws.ts @@ -8,6 +8,7 @@ export interface IAws { discoverDefaultRegion(): Promise; discoverCurrentAccount(): Promise; + discoverTargetAccount(options: ClientOptions): Promise; s3Client(options: ClientOptions): Promise; ecrClient(options: ClientOptions): Promise; secretsManagerClient(options: ClientOptions): Promise; @@ -94,6 +95,18 @@ export class DefaultAwsClient implements IAws { return this.account; } + public async discoverTargetAccount(options: ClientOptions): Promise { + const sts = new this.AWS.STS(await this.awsOptions(options)); + const response = await sts.getCallerIdentity().promise(); + if (!response.Account || !response.Arn) { + throw new Error(`Unrecognized reponse from STS: '${JSON.stringify(response)}'`); + } + return { + accountId: response.Account!, + partition: response.Arn!.split(':')[1], + }; + } + private async awsOptions(options: ClientOptions) { let credentials; diff --git a/packages/cdk-assets/lib/private/handlers/files.ts b/packages/cdk-assets/lib/private/handlers/files.ts index 0350edb27c39d..f9e2807753c79 100644 --- a/packages/cdk-assets/lib/private/handlers/files.ts +++ b/packages/cdk-assets/lib/private/handlers/files.ts @@ -30,7 +30,7 @@ export class FileAssetHandler implements IAssetHandler { // A thunk for describing the current account. Used when we need to format an error // message, not in the success case. - const account = async () => (await this.host.aws.discoverCurrentAccount())?.accountId; + const account = async () => (await this.host.aws.discoverTargetAccount(destination))?.accountId; switch (await bucketInfo.bucketOwnership(s3, destination.bucketName)) { case BucketOwnership.MINE: break; diff --git a/packages/cdk-assets/test/fake-listener.ts b/packages/cdk-assets/test/fake-listener.ts new file mode 100644 index 0000000000000..46b7f31a3ecc7 --- /dev/null +++ b/packages/cdk-assets/test/fake-listener.ts @@ -0,0 +1,16 @@ +import { IPublishProgressListener, EventType, IPublishProgress } from '../lib/progress'; + +export class FakeListener implements IPublishProgressListener { + public readonly messages = new Array(); + + constructor(private readonly doAbort = false) { + } + + public onPublishEvent(_type: EventType, event: IPublishProgress): void { + this.messages.push(event.message); + + if (this.doAbort) { + event.abort(); + } + } +} \ No newline at end of file diff --git a/packages/cdk-assets/test/files.test.ts b/packages/cdk-assets/test/files.test.ts index a2cbb592263dc..074d08308027f 100644 --- a/packages/cdk-assets/test/files.test.ts +++ b/packages/cdk-assets/test/files.test.ts @@ -3,6 +3,7 @@ jest.mock('child_process'); import { Manifest } from '@aws-cdk/cloud-assembly-schema'; import * as mockfs from 'mock-fs'; import { AssetManifest, AssetPublishing } from '../lib'; +import { FakeListener } from './fake-listener'; import { mockAws, mockedApiFailure, mockedApiResult, mockUpload } from './mock-aws'; import { mockSpawn } from './mock-child_process'; @@ -226,7 +227,8 @@ test('will only read bucketEncryption once even for multiple assets', async () = }); test('no server side encryption header if access denied for bucket encryption', async () => { - const pub = new AssetPublishing(AssetManifest.fromPath('/simple/cdk.out'), { aws }); + const progressListener = new FakeListener(); + const pub = new AssetPublishing(AssetManifest.fromPath('/simple/cdk.out'), { aws, progressListener }); aws.mockS3.getBucketEncryption = mockedApiFailure('AccessDenied', 'Access Denied'); @@ -243,7 +245,8 @@ test('no server side encryption header if access denied for bucket encryption', ServerSideEncryption: 'AES256', })); - // We'll just have to assume the contents are correct + // Error message references target_account, not current_account + expect(progressListener.messages).toContainEqual(expect.stringMatching(/ACCES_DENIED.*target_account/)); }); test('correctly looks up content type', async () => { @@ -294,6 +297,7 @@ test('successful run does not need to query account ID', async () => { await pub.publish(); expect(aws.discoverCurrentAccount).not.toHaveBeenCalled(); + expect(aws.discoverTargetAccount).not.toHaveBeenCalled(); }); test('correctly identify asset path if path is absolute', async () => { diff --git a/packages/cdk-assets/test/mock-aws.ts b/packages/cdk-assets/test/mock-aws.ts index fcf2fdffa6447..26b4f5d80d914 100644 --- a/packages/cdk-assets/test/mock-aws.ts +++ b/packages/cdk-assets/test/mock-aws.ts @@ -24,6 +24,7 @@ export function mockAws() { discoverPartition: jest.fn(() => Promise.resolve('swa')), discoverCurrentAccount: jest.fn(() => Promise.resolve({ accountId: 'current_account', partition: 'swa' })), discoverDefaultRegion: jest.fn(() => Promise.resolve('current_region')), + discoverTargetAccount: jest.fn(() => Promise.resolve({ accountId: 'target_account', partition: 'swa' })), ecrClient: jest.fn(() => Promise.resolve(mockEcr)), s3Client: jest.fn(() => Promise.resolve(mockS3)), secretsManagerClient: jest.fn(() => Promise.resolve(mockSecretsManager)), @@ -69,4 +70,4 @@ export function mockUpload(expectContent?: string) { }); }), })); -} +} \ No newline at end of file diff --git a/packages/cdk-assets/test/progress.test.ts b/packages/cdk-assets/test/progress.test.ts index d18af611465ba..4003ec7df5482 100644 --- a/packages/cdk-assets/test/progress.test.ts +++ b/packages/cdk-assets/test/progress.test.ts @@ -1,6 +1,7 @@ import { Manifest } from '@aws-cdk/cloud-assembly-schema'; import * as mockfs from 'mock-fs'; -import { AssetManifest, AssetPublishing, EventType, IPublishProgress, IPublishProgressListener } from '../lib'; +import { AssetManifest, AssetPublishing } from '../lib'; +import { FakeListener } from './fake-listener'; import { mockAws, mockedApiResult, mockUpload } from './mock-aws'; let aws: ReturnType; @@ -68,19 +69,4 @@ test('test abort', async () => { // We never get to asset 2 expect(allMessages).not.toContain('theAsset:theDestination2'); -}); - -class FakeListener implements IPublishProgressListener { - public readonly messages = new Array(); - - constructor(private readonly doAbort = false) { - } - - public onPublishEvent(_type: EventType, event: IPublishProgress): void { - this.messages.push(event.message); - - if (this.doAbort) { - event.abort(); - } - } -} +}); \ No newline at end of file diff --git a/packages/cdk/package.json b/packages/cdk/package.json index 496bc37da628e..c1f9a6c167236 100644 --- a/packages/cdk/package.json +++ b/packages/cdk/package.json @@ -38,5 +38,8 @@ }, "engines": { "node": ">= 8.10.0" + }, + "publishConfig": { + "tag": "latest-1" } } diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 02d01f0efd406..3a0bada3ac73b 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -160,6 +160,7 @@ "@aws-cdk/aws-lambda-go": "0.0.0", "@aws-cdk/aws-lambda-nodejs": "0.0.0", "@aws-cdk/aws-lambda-python": "0.0.0", + "@aws-cdk/aws-lex": "0.0.0", "@aws-cdk/aws-licensemanager": "0.0.0", "@aws-cdk/aws-lightsail": "0.0.0", "@aws-cdk/aws-location": "0.0.0", diff --git a/packages/monocdk/package.json b/packages/monocdk/package.json index 10e191e5842b4..e0dc84b63f393 100644 --- a/packages/monocdk/package.json +++ b/packages/monocdk/package.json @@ -246,6 +246,7 @@ "@aws-cdk/aws-lambda-go": "0.0.0", "@aws-cdk/aws-lambda-nodejs": "0.0.0", "@aws-cdk/aws-lambda-python": "0.0.0", + "@aws-cdk/aws-lex": "0.0.0", "@aws-cdk/aws-licensemanager": "0.0.0", "@aws-cdk/aws-lightsail": "0.0.0", "@aws-cdk/aws-location": "0.0.0", diff --git a/tools/@aws-cdk/pkglint/lib/library-creation.ts b/tools/@aws-cdk/pkglint/lib/library-creation.ts index 04b13ffbaead3..9dfec23a34a19 100644 --- a/tools/@aws-cdk/pkglint/lib/library-creation.ts +++ b/tools/@aws-cdk/pkglint/lib/library-creation.ts @@ -56,11 +56,12 @@ export function createModuleDefinitionFromCfnNamespace(namespace: string): Modul } -export async function createLibraryReadme(namespace: string, readmePath: string) { +export async function createLibraryReadme(namespace: string, readmePath: string, alphaPackageName?: string) { const module = createModuleDefinitionFromCfnNamespace(namespace); await fs.writeFile(readmePath, cfnOnlyReadmeContents({ cfnNamespace: namespace, packageName: module.packageName, + alphaPackageName, })); } diff --git a/tools/@aws-cdk/pkglint/lib/readme-contents.ts b/tools/@aws-cdk/pkglint/lib/readme-contents.ts index 6e8492566c359..ef80ca6262d0d 100644 --- a/tools/@aws-cdk/pkglint/lib/readme-contents.ts +++ b/tools/@aws-cdk/pkglint/lib/readme-contents.ts @@ -11,6 +11,14 @@ export interface LibraryReadmeOptions { * The name under which we publish this NPM package */ readonly packageName: string; + + /** + * Alpha package name + * + * If the "actual" contents of this library are available in another package, + * give the name here. + */ + readonly alphaPackageName?: string; } /** @@ -55,6 +63,13 @@ export function cfnOnlyReadmeContents(options: LibraryReadmeOptions) { '```ts nofixture', `import * as ${importName} from '${options.packageName}';`, '```', + ...(options.alphaPackageName ? [ + '', + '> The construct library for this service is in preview. Since it is not stable yet, it is distributed', + '> as a separate package so that you can pin its version independently of the rest of the CDK. See the package named:', + '>', + `> ${options.alphaPackageName}`, + ] : []), '', '', '', diff --git a/tools/@aws-cdk/ubergen/bin/ubergen.ts b/tools/@aws-cdk/ubergen/bin/ubergen.ts index f12c86e353d9e..7041235a03ffc 100644 --- a/tools/@aws-cdk/ubergen/bin/ubergen.ts +++ b/tools/@aws-cdk/ubergen/bin/ubergen.ts @@ -364,13 +364,16 @@ async function transformPackage( await fs.mkdirp(destinationLib); await cfn2ts(cfnScopes, destinationLib); + // We know what this is going to be, so predict it + const alphaPackageName = `${library.packageJson.name}-alpha`; + // create a lib/index.ts which only exports the generated files fs.writeFileSync(path.join(destinationLib, 'index.ts'), /// logic copied from `create-missing-libraries.ts` cfnScopes.map(s => (s === 'AWS::Serverless' ? 'AWS::SAM' : s).split('::')[1].toLocaleLowerCase()) .map(s => `export * from './${s}.generated';`) .join('\n')); - await pkglint.createLibraryReadme(cfnScopes[0], path.join(destination, 'README.md')); + await pkglint.createLibraryReadme(cfnScopes[0], path.join(destination, 'README.md'), alphaPackageName); await copyOrTransformFiles(destination, destination, allLibraries, uberPackageJson); } else {