From 7711981ea30bfdffd21dd840d676be4a2b45c9ba Mon Sep 17 00:00:00 2001 From: AWS CDK Automation <43080478+aws-cdk-automation@users.noreply.github.com> Date: Wed, 10 Mar 2021 10:38:11 +0100 Subject: [PATCH 01/41] feat(cfnspec): cloudformation spec v30.1.0 (#13519) Co-authored-by: AWS CDK Team --- packages/@aws-cdk/aws-s3outposts/.eslintrc.js | 3 + packages/@aws-cdk/aws-s3outposts/.gitignore | 19 + packages/@aws-cdk/aws-s3outposts/.npmignore | 28 + packages/@aws-cdk/aws-s3outposts/LICENSE | 201 +++ packages/@aws-cdk/aws-s3outposts/NOTICE | 2 + packages/@aws-cdk/aws-s3outposts/README.md | 20 + .../@aws-cdk/aws-s3outposts/jest.config.js | 2 + packages/@aws-cdk/aws-s3outposts/lib/index.ts | 2 + packages/@aws-cdk/aws-s3outposts/package.json | 100 ++ .../aws-s3outposts/test/s3outposts.test.ts | 6 + packages/@aws-cdk/cfnspec/CHANGELOG.md | 59 + packages/@aws-cdk/cfnspec/cfn.version | 2 +- ...0_CloudFormationResourceSpecification.json | 1112 ++++++++++++++++- .../cloudformation-include/package.json | 2 + packages/aws-cdk-lib/package.json | 1 + packages/decdk/package.json | 3 +- packages/monocdk/package.json | 1 + 17 files changed, 1547 insertions(+), 16 deletions(-) create mode 100644 packages/@aws-cdk/aws-s3outposts/.eslintrc.js create mode 100644 packages/@aws-cdk/aws-s3outposts/.gitignore create mode 100644 packages/@aws-cdk/aws-s3outposts/.npmignore create mode 100644 packages/@aws-cdk/aws-s3outposts/LICENSE create mode 100644 packages/@aws-cdk/aws-s3outposts/NOTICE create mode 100644 packages/@aws-cdk/aws-s3outposts/README.md create mode 100644 packages/@aws-cdk/aws-s3outposts/jest.config.js create mode 100644 packages/@aws-cdk/aws-s3outposts/lib/index.ts create mode 100644 packages/@aws-cdk/aws-s3outposts/package.json create mode 100644 packages/@aws-cdk/aws-s3outposts/test/s3outposts.test.ts diff --git a/packages/@aws-cdk/aws-s3outposts/.eslintrc.js b/packages/@aws-cdk/aws-s3outposts/.eslintrc.js new file mode 100644 index 0000000000000..61dd8dd001f63 --- /dev/null +++ b/packages/@aws-cdk/aws-s3outposts/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-s3outposts/.gitignore b/packages/@aws-cdk/aws-s3outposts/.gitignore new file mode 100644 index 0000000000000..62ebc95d75ce6 --- /dev/null +++ b/packages/@aws-cdk/aws-s3outposts/.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-s3outposts/.npmignore b/packages/@aws-cdk/aws-s3outposts/.npmignore new file mode 100644 index 0000000000000..e4486030fcb17 --- /dev/null +++ b/packages/@aws-cdk/aws-s3outposts/.npmignore @@ -0,0 +1,28 @@ +# 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/ diff --git a/packages/@aws-cdk/aws-s3outposts/LICENSE b/packages/@aws-cdk/aws-s3outposts/LICENSE new file mode 100644 index 0000000000000..28e4bdcec77ec --- /dev/null +++ b/packages/@aws-cdk/aws-s3outposts/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-s3outposts/NOTICE b/packages/@aws-cdk/aws-s3outposts/NOTICE new file mode 100644 index 0000000000000..5fc3826926b5b --- /dev/null +++ b/packages/@aws-cdk/aws-s3outposts/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-s3outposts/README.md b/packages/@aws-cdk/aws-s3outposts/README.md new file mode 100644 index 0000000000000..08fc4b75a732a --- /dev/null +++ b/packages/@aws-cdk/aws-s3outposts/README.md @@ -0,0 +1,20 @@ +# AWS::S3Outposts 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 +import s3outposts = require('@aws-cdk/aws-s3outposts'); +``` diff --git a/packages/@aws-cdk/aws-s3outposts/jest.config.js b/packages/@aws-cdk/aws-s3outposts/jest.config.js new file mode 100644 index 0000000000000..54e28beb9798b --- /dev/null +++ b/packages/@aws-cdk/aws-s3outposts/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-s3outposts/lib/index.ts b/packages/@aws-cdk/aws-s3outposts/lib/index.ts new file mode 100644 index 0000000000000..06c96e7c920bc --- /dev/null +++ b/packages/@aws-cdk/aws-s3outposts/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::S3Outposts CloudFormation Resources: +export * from './s3outposts.generated'; diff --git a/packages/@aws-cdk/aws-s3outposts/package.json b/packages/@aws-cdk/aws-s3outposts/package.json new file mode 100644 index 0000000000000..7965518ea1d24 --- /dev/null +++ b/packages/@aws-cdk/aws-s3outposts/package.json @@ -0,0 +1,100 @@ +{ + "name": "@aws-cdk/aws-s3outposts", + "version": "0.0.0", + "description": "The CDK Construct Library for AWS::S3Outposts", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "projectReferences": true, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.S3Outposts", + "packageId": "Amazon.CDK.AWS.S3Outposts", + "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.s3outposts", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "s3outposts" + } + }, + "python": { + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ], + "distName": "aws-cdk.aws-s3outposts", + "module": "aws_cdk.aws_s3outposts" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-s3outposts" + }, + "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" + }, + "cdk-build": { + "cloudformation": "AWS::S3Outposts", + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "AWS::S3Outposts", + "aws-s3outposts" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "0.0.0", + "cdk-build-tools": "0.0.0", + "cfn2ts": "0.0.0", + "pkglint": "0.0.0" + }, + "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-s3outposts/test/s3outposts.test.ts b/packages/@aws-cdk/aws-s3outposts/test/s3outposts.test.ts new file mode 100644 index 0000000000000..e394ef336bfb4 --- /dev/null +++ b/packages/@aws-cdk/aws-s3outposts/test/s3outposts.test.ts @@ -0,0 +1,6 @@ +import '@aws-cdk/assert/jest'; +import {} from '../lib'; + +test('No tests are specified for this package', () => { + expect(true).toBe(true); +}); diff --git a/packages/@aws-cdk/cfnspec/CHANGELOG.md b/packages/@aws-cdk/cfnspec/CHANGELOG.md index 9485ec700294e..1affea5b1d444 100644 --- a/packages/@aws-cdk/cfnspec/CHANGELOG.md +++ b/packages/@aws-cdk/cfnspec/CHANGELOG.md @@ -1,3 +1,62 @@ +# CloudFormation Resource Specification v30.1.0 + +## New Resource Types + +* AWS::Events::ApiDestination +* AWS::Events::Connection +* AWS::IoT::AccountAuditConfiguration +* AWS::IoT::CustomMetric +* AWS::IoT::Dimension +* AWS::IoT::MitigationAction +* AWS::IoT::ScheduledAudit +* AWS::IoT::SecurityProfile +* AWS::S3Outposts::AccessPoint +* AWS::S3Outposts::Bucket +* AWS::S3Outposts::BucketPolicy +* AWS::S3Outposts::Endpoint + +## Attribute Changes + +* AWS::Athena::WorkGroup EffectiveEngineVersion (__added__) + +## Property Changes + +* AWS::Backup::BackupVault BackupVaultTags.PrimitiveType (__deleted__) +* AWS::Backup::BackupVault BackupVaultTags.PrimitiveItemType (__added__) +* AWS::Backup::BackupVault BackupVaultTags.Type (__added__) +* AWS::Cloud9::EnvironmentEC2 ImageId (__added__) +* AWS::CloudFormation::ModuleVersion ModuleName.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::CloudFormation::ModuleVersion ModulePackage.Required (__changed__) + * Old: false + * New: true +* AWS::DataBrew::Dataset Format (__added__) +* AWS::Detective::MemberInvitation DisableEmailNotification (__added__) +* AWS::IVS::Channel RecordingConfigurationArn (__deleted__) +* AWS::SecretsManager::Secret ReplicaRegions (__added__) +* AWS::ServiceDiscovery::HttpNamespace Tags.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::ServiceDiscovery::PrivateDnsNamespace Tags.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::ServiceDiscovery::PublicDnsNamespace Tags.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::ServiceDiscovery::Service Tags.UpdateType (__changed__) + * Old: Immutable + * New: Mutable + +## Property Type Changes + +* AWS::Athena::WorkGroup.EngineVersion (__added__) +* AWS::SecretsManager::Secret.ReplicaRegion (__added__) +* AWS::Athena::WorkGroup.WorkGroupConfiguration EngineVersion (__added__) +* AWS::Athena::WorkGroup.WorkGroupConfigurationUpdates EngineVersion (__added__) +* AWS::Backup::BackupVault.NotificationObjectType BackupVaultEvents.DuplicatesAllowed (__added__) + + # CloudFormation Resource Specification v30.0.0 ## New Resource Types diff --git a/packages/@aws-cdk/cfnspec/cfn.version b/packages/@aws-cdk/cfnspec/cfn.version index 8dd5c17a1b5cd..a75ef34cbaa5c 100644 --- a/packages/@aws-cdk/cfnspec/cfn.version +++ b/packages/@aws-cdk/cfnspec/cfn.version @@ -1 +1 @@ -30.0.0 +30.1.0 diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json index 75da81b0c270b..05d46dca703b8 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json @@ -7164,6 +7164,23 @@ } } }, + "AWS::Athena::WorkGroup.EngineVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-athena-workgroup-engineversion.html", + "Properties": { + "EffectiveEngineVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-athena-workgroup-engineversion.html#cfn-athena-workgroup-engineversion-effectiveengineversion", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SelectedEngineVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-athena-workgroup-engineversion.html#cfn-athena-workgroup-engineversion-selectedengineversion", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Athena::WorkGroup.ResultConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-athena-workgroup-resultconfiguration.html", "Properties": { @@ -7225,6 +7242,12 @@ "Required": false, "UpdateType": "Mutable" }, + "EngineVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-athena-workgroup-workgroupconfiguration.html#cfn-athena-workgroup-workgroupconfiguration-engineversion", + "Required": false, + "Type": "EngineVersion", + "UpdateType": "Mutable" + }, "PublishCloudWatchMetricsEnabled": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-athena-workgroup-workgroupconfiguration.html#cfn-athena-workgroup-workgroupconfiguration-publishcloudwatchmetricsenabled", "PrimitiveType": "Boolean", @@ -7260,6 +7283,12 @@ "Required": false, "UpdateType": "Mutable" }, + "EngineVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-athena-workgroup-workgroupconfigurationupdates.html#cfn-athena-workgroup-workgroupconfigurationupdates-engineversion", + "Required": false, + "Type": "EngineVersion", + "UpdateType": "Mutable" + }, "PublishCloudWatchMetricsEnabled": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-athena-workgroup-workgroupconfigurationupdates.html#cfn-athena-workgroup-workgroupconfigurationupdates-publishcloudwatchmetricsenabled", "PrimitiveType": "Boolean", @@ -8376,6 +8405,7 @@ "Properties": { "BackupVaultEvents": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-backup-backupvault-notificationobjecttype.html#cfn-backup-backupvault-notificationobjecttype-backupvaultevents", + "DuplicatesAllowed": true, "PrimitiveItemType": "String", "Required": true, "Type": "List", @@ -27787,6 +27817,140 @@ } } }, + "AWS::IoT::AccountAuditConfiguration.AuditCheckConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfiguration.html", + "Properties": { + "Enabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfiguration.html#cfn-iot-accountauditconfiguration-auditcheckconfiguration-enabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::AccountAuditConfiguration.AuditCheckConfigurations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfigurations.html", + "Properties": { + "AuthenticatedCognitoRoleOverlyPermissiveCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfigurations.html#cfn-iot-accountauditconfiguration-auditcheckconfigurations-authenticatedcognitoroleoverlypermissivecheck", + "Required": false, + "Type": "AuditCheckConfiguration", + "UpdateType": "Mutable" + }, + "CaCertificateExpiringCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfigurations.html#cfn-iot-accountauditconfiguration-auditcheckconfigurations-cacertificateexpiringcheck", + "Required": false, + "Type": "AuditCheckConfiguration", + "UpdateType": "Mutable" + }, + "CaCertificateKeyQualityCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfigurations.html#cfn-iot-accountauditconfiguration-auditcheckconfigurations-cacertificatekeyqualitycheck", + "Required": false, + "Type": "AuditCheckConfiguration", + "UpdateType": "Mutable" + }, + "ConflictingClientIdsCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfigurations.html#cfn-iot-accountauditconfiguration-auditcheckconfigurations-conflictingclientidscheck", + "Required": false, + "Type": "AuditCheckConfiguration", + "UpdateType": "Mutable" + }, + "DeviceCertificateExpiringCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfigurations.html#cfn-iot-accountauditconfiguration-auditcheckconfigurations-devicecertificateexpiringcheck", + "Required": false, + "Type": "AuditCheckConfiguration", + "UpdateType": "Mutable" + }, + "DeviceCertificateKeyQualityCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfigurations.html#cfn-iot-accountauditconfiguration-auditcheckconfigurations-devicecertificatekeyqualitycheck", + "Required": false, + "Type": "AuditCheckConfiguration", + "UpdateType": "Mutable" + }, + "DeviceCertificateSharedCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfigurations.html#cfn-iot-accountauditconfiguration-auditcheckconfigurations-devicecertificatesharedcheck", + "Required": false, + "Type": "AuditCheckConfiguration", + "UpdateType": "Mutable" + }, + "IotPolicyOverlyPermissiveCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfigurations.html#cfn-iot-accountauditconfiguration-auditcheckconfigurations-iotpolicyoverlypermissivecheck", + "Required": false, + "Type": "AuditCheckConfiguration", + "UpdateType": "Mutable" + }, + "IotRoleAliasAllowsAccessToUnusedServicesCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfigurations.html#cfn-iot-accountauditconfiguration-auditcheckconfigurations-iotrolealiasallowsaccesstounusedservicescheck", + "Required": false, + "Type": "AuditCheckConfiguration", + "UpdateType": "Mutable" + }, + "IotRoleAliasOverlyPermissiveCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfigurations.html#cfn-iot-accountauditconfiguration-auditcheckconfigurations-iotrolealiasoverlypermissivecheck", + "Required": false, + "Type": "AuditCheckConfiguration", + "UpdateType": "Mutable" + }, + "LoggingDisabledCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfigurations.html#cfn-iot-accountauditconfiguration-auditcheckconfigurations-loggingdisabledcheck", + "Required": false, + "Type": "AuditCheckConfiguration", + "UpdateType": "Mutable" + }, + "RevokedCaCertificateStillActiveCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfigurations.html#cfn-iot-accountauditconfiguration-auditcheckconfigurations-revokedcacertificatestillactivecheck", + "Required": false, + "Type": "AuditCheckConfiguration", + "UpdateType": "Mutable" + }, + "RevokedDeviceCertificateStillActiveCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfigurations.html#cfn-iot-accountauditconfiguration-auditcheckconfigurations-revokeddevicecertificatestillactivecheck", + "Required": false, + "Type": "AuditCheckConfiguration", + "UpdateType": "Mutable" + }, + "UnauthenticatedCognitoRoleOverlyPermissiveCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditcheckconfigurations.html#cfn-iot-accountauditconfiguration-auditcheckconfigurations-unauthenticatedcognitoroleoverlypermissivecheck", + "Required": false, + "Type": "AuditCheckConfiguration", + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::AccountAuditConfiguration.AuditNotificationTarget": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditnotificationtarget.html", + "Properties": { + "Enabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditnotificationtarget.html#cfn-iot-accountauditconfiguration-auditnotificationtarget-enabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "RoleArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditnotificationtarget.html#cfn-iot-accountauditconfiguration-auditnotificationtarget-rolearn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TargetArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditnotificationtarget.html#cfn-iot-accountauditconfiguration-auditnotificationtarget-targetarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::AccountAuditConfiguration.AuditNotificationTargetConfigurations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditnotificationtargetconfigurations.html", + "Properties": { + "Sns": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-accountauditconfiguration-auditnotificationtargetconfigurations.html#cfn-iot-accountauditconfiguration-auditnotificationtargetconfigurations-sns", + "Required": false, + "Type": "AuditNotificationTarget", + "UpdateType": "Mutable" + } + } + }, "AWS::IoT::DomainConfiguration.AuthorizerConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-domainconfiguration-authorizerconfig.html", "Properties": { @@ -27827,6 +27991,127 @@ } } }, + "AWS::IoT::MitigationAction.ActionParams": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-actionparams.html", + "Properties": { + "AddThingsToThingGroupParams": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-actionparams.html#cfn-iot-mitigationaction-actionparams-addthingstothinggroupparams", + "Required": false, + "Type": "AddThingsToThingGroupParams", + "UpdateType": "Mutable" + }, + "EnableIoTLoggingParams": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-actionparams.html#cfn-iot-mitigationaction-actionparams-enableiotloggingparams", + "Required": false, + "Type": "EnableIoTLoggingParams", + "UpdateType": "Mutable" + }, + "PublishFindingToSnsParams": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-actionparams.html#cfn-iot-mitigationaction-actionparams-publishfindingtosnsparams", + "Required": false, + "Type": "PublishFindingToSnsParams", + "UpdateType": "Mutable" + }, + "ReplaceDefaultPolicyVersionParams": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-actionparams.html#cfn-iot-mitigationaction-actionparams-replacedefaultpolicyversionparams", + "Required": false, + "Type": "ReplaceDefaultPolicyVersionParams", + "UpdateType": "Mutable" + }, + "UpdateCACertificateParams": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-actionparams.html#cfn-iot-mitigationaction-actionparams-updatecacertificateparams", + "Required": false, + "Type": "UpdateCACertificateParams", + "UpdateType": "Mutable" + }, + "UpdateDeviceCertificateParams": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-actionparams.html#cfn-iot-mitigationaction-actionparams-updatedevicecertificateparams", + "Required": false, + "Type": "UpdateDeviceCertificateParams", + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::MitigationAction.AddThingsToThingGroupParams": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-addthingstothinggroupparams.html", + "Properties": { + "OverrideDynamicGroups": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-addthingstothinggroupparams.html#cfn-iot-mitigationaction-addthingstothinggroupparams-overridedynamicgroups", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "ThingGroupNames": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-addthingstothinggroupparams.html#cfn-iot-mitigationaction-addthingstothinggroupparams-thinggroupnames", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::MitigationAction.EnableIoTLoggingParams": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-enableiotloggingparams.html", + "Properties": { + "LogLevel": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-enableiotloggingparams.html#cfn-iot-mitigationaction-enableiotloggingparams-loglevel", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "RoleArnForLogging": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-enableiotloggingparams.html#cfn-iot-mitigationaction-enableiotloggingparams-rolearnforlogging", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::MitigationAction.PublishFindingToSnsParams": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-publishfindingtosnsparams.html", + "Properties": { + "TopicArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-publishfindingtosnsparams.html#cfn-iot-mitigationaction-publishfindingtosnsparams-topicarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::MitigationAction.ReplaceDefaultPolicyVersionParams": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-replacedefaultpolicyversionparams.html", + "Properties": { + "TemplateName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-replacedefaultpolicyversionparams.html#cfn-iot-mitigationaction-replacedefaultpolicyversionparams-templatename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::MitigationAction.UpdateCACertificateParams": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-updatecacertificateparams.html", + "Properties": { + "Action": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-updatecacertificateparams.html#cfn-iot-mitigationaction-updatecacertificateparams-action", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::MitigationAction.UpdateDeviceCertificateParams": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-updatedevicecertificateparams.html", + "Properties": { + "Action": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-mitigationaction-updatedevicecertificateparams.html#cfn-iot-mitigationaction-updatedevicecertificateparams-action", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::IoT::ProvisioningTemplate.ProvisioningHook": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-provisioningtemplate-provisioninghook.html", "Properties": { @@ -27844,6 +28129,210 @@ } } }, + "AWS::IoT::SecurityProfile.AlertTarget": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-alerttarget.html", + "Properties": { + "AlertTargetArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-alerttarget.html#cfn-iot-securityprofile-alerttarget-alerttargetarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "RoleArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-alerttarget.html#cfn-iot-securityprofile-alerttarget-rolearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::SecurityProfile.Behavior": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-behavior.html", + "Properties": { + "Criteria": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-behavior.html#cfn-iot-securityprofile-behavior-criteria", + "Required": false, + "Type": "BehaviorCriteria", + "UpdateType": "Mutable" + }, + "Metric": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-behavior.html#cfn-iot-securityprofile-behavior-metric", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "MetricDimension": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-behavior.html#cfn-iot-securityprofile-behavior-metricdimension", + "Required": false, + "Type": "MetricDimension", + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-behavior.html#cfn-iot-securityprofile-behavior-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "SuppressAlerts": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-behavior.html#cfn-iot-securityprofile-behavior-suppressalerts", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::SecurityProfile.BehaviorCriteria": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-behaviorcriteria.html", + "Properties": { + "ComparisonOperator": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-behaviorcriteria.html#cfn-iot-securityprofile-behaviorcriteria-comparisonoperator", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ConsecutiveDatapointsToAlarm": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-behaviorcriteria.html#cfn-iot-securityprofile-behaviorcriteria-consecutivedatapointstoalarm", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "ConsecutiveDatapointsToClear": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-behaviorcriteria.html#cfn-iot-securityprofile-behaviorcriteria-consecutivedatapointstoclear", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "DurationSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-behaviorcriteria.html#cfn-iot-securityprofile-behaviorcriteria-durationseconds", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "MlDetectionConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-behaviorcriteria.html#cfn-iot-securityprofile-behaviorcriteria-mldetectionconfig", + "Required": false, + "Type": "MachineLearningDetectionConfig", + "UpdateType": "Mutable" + }, + "StatisticalThreshold": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-behaviorcriteria.html#cfn-iot-securityprofile-behaviorcriteria-statisticalthreshold", + "Required": false, + "Type": "StatisticalThreshold", + "UpdateType": "Mutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-behaviorcriteria.html#cfn-iot-securityprofile-behaviorcriteria-value", + "Required": false, + "Type": "MetricValue", + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::SecurityProfile.MachineLearningDetectionConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-machinelearningdetectionconfig.html", + "Properties": { + "ConfidenceLevel": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-machinelearningdetectionconfig.html#cfn-iot-securityprofile-machinelearningdetectionconfig-confidencelevel", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::SecurityProfile.MetricDimension": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-metricdimension.html", + "Properties": { + "DimensionName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-metricdimension.html#cfn-iot-securityprofile-metricdimension-dimensionname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Operator": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-metricdimension.html#cfn-iot-securityprofile-metricdimension-operator", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::SecurityProfile.MetricToRetain": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-metrictoretain.html", + "Properties": { + "Metric": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-metrictoretain.html#cfn-iot-securityprofile-metrictoretain-metric", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "MetricDimension": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-metrictoretain.html#cfn-iot-securityprofile-metrictoretain-metricdimension", + "Required": false, + "Type": "MetricDimension", + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::SecurityProfile.MetricValue": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-metricvalue.html", + "Properties": { + "Cidrs": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-metricvalue.html#cfn-iot-securityprofile-metricvalue-cidrs", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Count": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-metricvalue.html#cfn-iot-securityprofile-metricvalue-count", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Number": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-metricvalue.html#cfn-iot-securityprofile-metricvalue-number", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Mutable" + }, + "Numbers": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-metricvalue.html#cfn-iot-securityprofile-metricvalue-numbers", + "DuplicatesAllowed": false, + "PrimitiveItemType": "Double", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Ports": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-metricvalue.html#cfn-iot-securityprofile-metricvalue-ports", + "DuplicatesAllowed": false, + "PrimitiveItemType": "Integer", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Strings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-metricvalue.html#cfn-iot-securityprofile-metricvalue-strings", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::SecurityProfile.StatisticalThreshold": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-statisticalthreshold.html", + "Properties": { + "Statistic": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-securityprofile-statisticalthreshold.html#cfn-iot-securityprofile-statisticalthreshold-statistic", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::IoT::Thing.AttributePayload": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-thing-attributepayload.html", "Properties": { @@ -46954,6 +47443,93 @@ } } }, + "AWS::S3Outposts::AccessPoint.VpcConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3outposts-accesspoint-vpcconfiguration.html", + "Properties": { + "VpcId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3outposts-accesspoint-vpcconfiguration.html#cfn-s3outposts-accesspoint-vpcconfiguration-vpcid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::S3Outposts::Bucket.AbortIncompleteMultipartUpload": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3outposts-bucket-abortincompletemultipartupload.html", + "Properties": { + "DaysAfterInitiation": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3outposts-bucket-abortincompletemultipartupload.html#cfn-s3outposts-bucket-abortincompletemultipartupload-daysafterinitiation", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::S3Outposts::Bucket.LifecycleConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3outposts-bucket-lifecycleconfiguration.html", + "Properties": { + "Rules": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3outposts-bucket-lifecycleconfiguration.html#cfn-s3outposts-bucket-lifecycleconfiguration-rules", + "DuplicatesAllowed": false, + "ItemType": "Rule", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::S3Outposts::Bucket.Rule": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3outposts-bucket-rule.html", + "Properties": { + "AbortIncompleteMultipartUpload": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3outposts-bucket-rule.html#cfn-s3outposts-bucket-rule-abortincompletemultipartupload", + "Required": false, + "Type": "AbortIncompleteMultipartUpload", + "UpdateType": "Mutable" + }, + "ExpirationDate": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3outposts-bucket-rule.html#cfn-s3outposts-bucket-rule-expirationdate", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ExpirationInDays": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3outposts-bucket-rule.html#cfn-s3outposts-bucket-rule-expirationindays", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "Filter": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3outposts-bucket-rule.html#cfn-s3outposts-bucket-rule-filter", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, + "Id": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3outposts-bucket-rule.html#cfn-s3outposts-bucket-rule-id", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Status": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3outposts-bucket-rule.html#cfn-s3outposts-bucket-rule-status", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::S3Outposts::Endpoint.NetworkInterface": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3outposts-endpoint-networkinterface.html", + "Properties": { + "NetworkInterfaceId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3outposts-endpoint-networkinterface.html#cfn-s3outposts-endpoint-networkinterface-networkinterfaceid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::SES::ConfigurationSetEventDestination.CloudWatchDestination": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ses-configurationseteventdestination-cloudwatchdestination.html", "Properties": { @@ -50395,6 +50971,23 @@ } } }, + "AWS::SecretsManager::Secret.ReplicaRegion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-secret-replicaregion.html", + "Properties": { + "KmsKeyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-secret-replicaregion.html#cfn-secretsmanager-secret-replicaregion-kmskeyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Region": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-secret-replicaregion.html#cfn-secretsmanager-secret-replicaregion-region", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::ServiceCatalog::CloudFormationProduct.ProvisioningArtifactProperties": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-servicecatalog-cloudformationproduct-provisioningartifactproperties.html", "Properties": { @@ -53056,7 +53649,7 @@ } } }, - "ResourceSpecificationVersion": "30.0.0", + "ResourceSpecificationVersion": "30.1.0", "ResourceTypes": { "AWS::ACMPCA::Certificate": { "Attributes": { @@ -57096,6 +57689,9 @@ "Attributes": { "CreationTime": { "PrimitiveType": "String" + }, + "EffectiveEngineVersion": { + "PrimitiveType": "String" } }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-athena-workgroup.html", @@ -57810,8 +58406,9 @@ }, "BackupVaultTags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-backup-backupvault.html#cfn-backup-backupvault-backupvaulttags", - "PrimitiveType": "Json", + "PrimitiveItemType": "String", "Required": false, + "Type": "Map", "UpdateType": "Mutable" }, "EncryptionKeyArn": { @@ -58229,6 +58826,12 @@ "Required": false, "UpdateType": "Mutable" }, + "ImageId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloud9-environmentec2.html#cfn-cloud9-environmentec2-imageid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "InstanceType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloud9-environmentec2.html#cfn-cloud9-environmentec2-instancetype", "PrimitiveType": "String", @@ -58372,12 +58975,12 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-moduleversion.html#cfn-cloudformation-moduleversion-modulename", "PrimitiveType": "String", "Required": true, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "ModulePackage": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-moduleversion.html#cfn-cloudformation-moduleversion-modulepackage", "PrimitiveType": "String", - "Required": false, + "Required": true, "UpdateType": "Immutable" } } @@ -61879,6 +62482,12 @@ "AWS::DataBrew::Dataset": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-dataset.html", "Properties": { + "Format": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-dataset.html#cfn-databrew-dataset-format", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "FormatOptions": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-dataset.html#cfn-databrew-dataset-formatoptions", "PrimitiveType": "Json", @@ -62638,6 +63247,12 @@ "AWS::Detective::MemberInvitation": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-detective-memberinvitation.html", "Properties": { + "DisableEmailNotification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-detective-memberinvitation.html#cfn-detective-memberinvitation-disableemailnotification", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, "GraphArn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-detective-memberinvitation.html#cfn-detective-memberinvitation-grapharn", "PrimitiveType": "String", @@ -69094,6 +69709,52 @@ } } }, + "AWS::Events::ApiDestination": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-apidestination.html", + "Properties": { + "ConnectionArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-apidestination.html#cfn-events-apidestination-connectionarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-apidestination.html#cfn-events-apidestination-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "HttpMethod": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-apidestination.html#cfn-events-apidestination-httpmethod", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "InvocationEndpoint": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-apidestination.html#cfn-events-apidestination-invocationendpoint", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "InvocationRateLimitPerSecond": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-apidestination.html#cfn-events-apidestination-invocationratelimitpersecond", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-apidestination.html#cfn-events-apidestination-name", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::Events::Archive": { "Attributes": { "ArchiveName": { @@ -69137,6 +69798,43 @@ } } }, + "AWS::Events::Connection": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "SecretArn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-connection.html", + "Properties": { + "AuthParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-connection.html#cfn-events-connection-authparameters", + "PrimitiveType": "Json", + "Required": true, + "UpdateType": "Mutable" + }, + "AuthorizationType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-connection.html#cfn-events-connection-authorizationtype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-connection.html#cfn-events-connection-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-connection.html#cfn-events-connection-name", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::Events::EventBus": { "Attributes": { "Arn": { @@ -72094,12 +72792,6 @@ "Required": false, "UpdateType": "Mutable" }, - "RecordingConfigurationArn": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ivs-channel.html#cfn-ivs-channel-recordingconfigurationarn", - "PrimitiveType": "String", - "Required": false, - "UpdateType": "Mutable" - }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ivs-channel.html#cfn-ivs-channel-tags", "DuplicatesAllowed": false, @@ -72847,6 +73539,35 @@ } } }, + "AWS::IoT::AccountAuditConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-accountauditconfiguration.html", + "Properties": { + "AccountId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-accountauditconfiguration.html#cfn-iot-accountauditconfiguration-accountid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "AuditCheckConfigurations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-accountauditconfiguration.html#cfn-iot-accountauditconfiguration-auditcheckconfigurations", + "Required": true, + "Type": "AuditCheckConfigurations", + "UpdateType": "Mutable" + }, + "AuditNotificationTargetConfigurations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-accountauditconfiguration.html#cfn-iot-accountauditconfiguration-auditnotificationtargetconfigurations", + "Required": false, + "Type": "AuditNotificationTargetConfigurations", + "UpdateType": "Mutable" + }, + "RoleArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-accountauditconfiguration.html#cfn-iot-accountauditconfiguration-rolearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::IoT::Authorizer": { "Attributes": { "Arn": { @@ -72944,6 +73665,80 @@ } } }, + "AWS::IoT::CustomMetric": { + "Attributes": { + "MetricArn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-custommetric.html", + "Properties": { + "DisplayName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-custommetric.html#cfn-iot-custommetric-displayname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "MetricName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-custommetric.html#cfn-iot-custommetric-metricname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "MetricType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-custommetric.html#cfn-iot-custommetric-metrictype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-custommetric.html#cfn-iot-custommetric-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::Dimension": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-dimension.html", + "Properties": { + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-dimension.html#cfn-iot-dimension-name", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "StringValues": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-dimension.html#cfn-iot-dimension-stringvalues", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-dimension.html#cfn-iot-dimension-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Type": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-dimension.html#cfn-iot-dimension-type", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::IoT::DomainConfiguration": { "Attributes": { "Arn": { @@ -73011,6 +73806,45 @@ } } }, + "AWS::IoT::MitigationAction": { + "Attributes": { + "MitigationActionArn": { + "PrimitiveType": "String" + }, + "MitigationActionId": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-mitigationaction.html", + "Properties": { + "ActionName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-mitigationaction.html#cfn-iot-mitigationaction-actionname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "ActionParams": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-mitigationaction.html#cfn-iot-mitigationaction-actionparams", + "Required": true, + "Type": "ActionParams", + "UpdateType": "Mutable" + }, + "RoleArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-mitigationaction.html#cfn-iot-mitigationaction-rolearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-mitigationaction.html#cfn-iot-mitigationaction-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::IoT::Policy": { "Attributes": { "Arn": { @@ -73103,6 +73937,117 @@ } } }, + "AWS::IoT::ScheduledAudit": { + "Attributes": { + "ScheduledAuditArn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-scheduledaudit.html", + "Properties": { + "DayOfMonth": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-scheduledaudit.html#cfn-iot-scheduledaudit-dayofmonth", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "DayOfWeek": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-scheduledaudit.html#cfn-iot-scheduledaudit-dayofweek", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Frequency": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-scheduledaudit.html#cfn-iot-scheduledaudit-frequency", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ScheduledAuditName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-scheduledaudit.html#cfn-iot-scheduledaudit-scheduledauditname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-scheduledaudit.html#cfn-iot-scheduledaudit-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "TargetCheckNames": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-scheduledaudit.html#cfn-iot-scheduledaudit-targetchecknames", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::IoT::SecurityProfile": { + "Attributes": { + "SecurityProfileArn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-securityprofile.html", + "Properties": { + "AdditionalMetricsToRetainV2": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-securityprofile.html#cfn-iot-securityprofile-additionalmetricstoretainv2", + "DuplicatesAllowed": false, + "ItemType": "MetricToRetain", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "AlertTargets": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-securityprofile.html#cfn-iot-securityprofile-alerttargets", + "ItemType": "AlertTarget", + "Required": false, + "Type": "Map", + "UpdateType": "Mutable" + }, + "Behaviors": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-securityprofile.html#cfn-iot-securityprofile-behaviors", + "DuplicatesAllowed": false, + "ItemType": "Behavior", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "SecurityProfileDescription": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-securityprofile.html#cfn-iot-securityprofile-securityprofiledescription", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SecurityProfileName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-securityprofile.html#cfn-iot-securityprofile-securityprofilename", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-securityprofile.html#cfn-iot-securityprofile-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "TargetArns": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-securityprofile.html#cfn-iot-securityprofile-targetarns", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::IoT::Thing": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-thing.html", "Properties": { @@ -81983,6 +82928,138 @@ } } }, + "AWS::S3Outposts::AccessPoint": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-accesspoint.html", + "Properties": { + "Bucket": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-accesspoint.html#cfn-s3outposts-accesspoint-bucket", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-accesspoint.html#cfn-s3outposts-accesspoint-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Policy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-accesspoint.html#cfn-s3outposts-accesspoint-policy", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, + "VpcConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-accesspoint.html#cfn-s3outposts-accesspoint-vpcconfiguration", + "Required": true, + "Type": "VpcConfiguration", + "UpdateType": "Immutable" + } + } + }, + "AWS::S3Outposts::Bucket": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-bucket.html", + "Properties": { + "BucketName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-bucket.html#cfn-s3outposts-bucket-bucketname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "LifecycleConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-bucket.html#cfn-s3outposts-bucket-lifecycleconfiguration", + "Required": false, + "Type": "LifecycleConfiguration", + "UpdateType": "Mutable" + }, + "OutpostId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-bucket.html#cfn-s3outposts-bucket-outpostid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-bucket.html#cfn-s3outposts-bucket-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::S3Outposts::BucketPolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-bucketpolicy.html", + "Properties": { + "Bucket": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-bucketpolicy.html#cfn-s3outposts-bucketpolicy-bucket", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "PolicyDocument": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-bucketpolicy.html#cfn-s3outposts-bucketpolicy-policydocument", + "PrimitiveType": "Json", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::S3Outposts::Endpoint": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "CidrBlock": { + "PrimitiveType": "String" + }, + "CreationTime": { + "PrimitiveType": "String" + }, + "Id": { + "PrimitiveType": "String" + }, + "NetworkInterfaces": { + "DuplicatesAllowed": false, + "ItemType": "NetworkInterface", + "Type": "List" + }, + "Status": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-endpoint.html", + "Properties": { + "OutpostId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-endpoint.html#cfn-s3outposts-endpoint-outpostid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "SecurityGroupId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-endpoint.html#cfn-s3outposts-endpoint-securitygroupid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "SubnetId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-endpoint.html#cfn-s3outposts-endpoint-subnetid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::SDB::Domain": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-simpledb.html", "Properties": { @@ -84282,6 +85359,13 @@ "Required": false, "UpdateType": "Immutable" }, + "ReplicaRegions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-secret.html#cfn-secretsmanager-secret-replicaregions", + "ItemType": "ReplicaRegion", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, "SecretString": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-secret.html#cfn-secretsmanager-secret-secretstring", "PrimitiveType": "String", @@ -85124,7 +86208,7 @@ "ItemType": "Tag", "Required": false, "Type": "List", - "UpdateType": "Immutable" + "UpdateType": "Mutable" } } }, @@ -85179,7 +86263,7 @@ "ItemType": "Tag", "Required": false, "Type": "List", - "UpdateType": "Immutable" + "UpdateType": "Mutable" }, "Vpc": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-servicediscovery-privatednsnamespace.html#cfn-servicediscovery-privatednsnamespace-vpc", @@ -85217,7 +86301,7 @@ "ItemType": "Tag", "Required": false, "Type": "List", - "UpdateType": "Immutable" + "UpdateType": "Mutable" } } }, @@ -85276,7 +86360,7 @@ "ItemType": "Tag", "Required": false, "Type": "List", - "UpdateType": "Immutable" + "UpdateType": "Mutable" } } }, diff --git a/packages/@aws-cdk/cloudformation-include/package.json b/packages/@aws-cdk/cloudformation-include/package.json index f9b2e79888365..a177759248e07 100644 --- a/packages/@aws-cdk/cloudformation-include/package.json +++ b/packages/@aws-cdk/cloudformation-include/package.json @@ -186,6 +186,7 @@ "@aws-cdk/aws-route53": "0.0.0", "@aws-cdk/aws-route53resolver": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-s3outposts": "0.0.0", "@aws-cdk/aws-sagemaker": "0.0.0", "@aws-cdk/aws-sam": "0.0.0", "@aws-cdk/aws-sdb": "0.0.0", @@ -334,6 +335,7 @@ "@aws-cdk/aws-route53": "0.0.0", "@aws-cdk/aws-route53resolver": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-s3outposts": "0.0.0", "@aws-cdk/aws-sagemaker": "0.0.0", "@aws-cdk/aws-sam": "0.0.0", "@aws-cdk/aws-sdb": "0.0.0", diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index 89dbc64979387..bd2ce09408acc 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -247,6 +247,7 @@ "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/aws-s3-deployment": "0.0.0", "@aws-cdk/aws-s3-notifications": "0.0.0", + "@aws-cdk/aws-s3outposts": "0.0.0", "@aws-cdk/aws-sagemaker": "0.0.0", "@aws-cdk/aws-sam": "0.0.0", "@aws-cdk/aws-sdb": "0.0.0", diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 8b8b17d52f621..4b2ec6dc2fdc0 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -35,8 +35,8 @@ "@aws-cdk/aws-amplify": "0.0.0", "@aws-cdk/aws-apigateway": "0.0.0", "@aws-cdk/aws-apigatewayv2": "0.0.0", - "@aws-cdk/aws-apigatewayv2-integrations": "0.0.0", "@aws-cdk/aws-apigatewayv2-authorizers": "0.0.0", + "@aws-cdk/aws-apigatewayv2-integrations": "0.0.0", "@aws-cdk/aws-appconfig": "0.0.0", "@aws-cdk/aws-appflow": "0.0.0", "@aws-cdk/aws-applicationautoscaling": "0.0.0", @@ -173,6 +173,7 @@ "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/aws-s3-deployment": "0.0.0", "@aws-cdk/aws-s3-notifications": "0.0.0", + "@aws-cdk/aws-s3outposts": "0.0.0", "@aws-cdk/aws-sagemaker": "0.0.0", "@aws-cdk/aws-sam": "0.0.0", "@aws-cdk/aws-sdb": "0.0.0", diff --git a/packages/monocdk/package.json b/packages/monocdk/package.json index c71d755b3c2bb..382f47c2be9d0 100644 --- a/packages/monocdk/package.json +++ b/packages/monocdk/package.json @@ -252,6 +252,7 @@ "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/aws-s3-deployment": "0.0.0", "@aws-cdk/aws-s3-notifications": "0.0.0", + "@aws-cdk/aws-s3outposts": "0.0.0", "@aws-cdk/aws-sagemaker": "0.0.0", "@aws-cdk/aws-sam": "0.0.0", "@aws-cdk/aws-sdb": "0.0.0", From 8dca5079e1893122057f9e2c54c0da0ba644926e Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Wed, 10 Mar 2021 11:51:11 +0000 Subject: [PATCH 02/41] chore(core): update CDK Metadata to report construct-level details (#13423) See CDK RFC 253 (aws/aws-cdk-rfcs#254) for background and details. Currently -- if a user has not opted out -- an AWS::CDK::Metadata resource is added to each generated stack template with details about each loaded module and version that matches an Amazon-specific allow list. This modules list is used to: - Track what library versions customers are using so they can be contacted in the event of a severe (security) issue with a library. - Get business metrics on the adoption of CDK and its libraries. This modules list is sometimes inaccurate (a module may be loaded into memory without actually being used) and too braod to support CDK v2. This feature (mostly) implements the specification proposed in RFC 253 to include metadata about what constructs are present in each stack, rather than modules loaded into memory. The allow-list is still used to ensure only CDK/AWS constructs are reported on. Implementation notes: - The format of the Analytics property has changed slightly since the RFC. See the service-side code for justification and latest spec. - How to handle the jsii runtime information was left un-spec'd. I've chosen to create a psuedo-Construct to add to the list as the simplest solution. - `runtime-info.test.ts` leaps through some serious hoops to work equally well for both v1 and v2, and to fail somewhat gracefully locally if `tsc` was used to compile the module instead of `jsii`. Critques of this approach welcome! - I removed an annoyance from `resolve-version-lib.js` that produced error messages when running unit tests. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../core/lib/private/metadata-resource.ts | 121 ++++++--- .../@aws-cdk/core/lib/private/runtime-info.ts | 143 +++++----- .../core/lib/private/tree-metadata.ts | 30 +-- packages/@aws-cdk/core/test/app.test.ts | 124 --------- .../core/test/metadata-resource.test.ts | 151 +++++++++++ .../@aws-cdk/core/test/runtime-info.test.ts | 252 +++++++++++++----- scripts/resolve-version-lib.js | 1 - 7 files changed, 480 insertions(+), 342 deletions(-) create mode 100644 packages/@aws-cdk/core/test/metadata-resource.test.ts diff --git a/packages/@aws-cdk/core/lib/private/metadata-resource.ts b/packages/@aws-cdk/core/lib/private/metadata-resource.ts index ff84b931f819b..1cccc4f24ff3d 100644 --- a/packages/@aws-cdk/core/lib/private/metadata-resource.ts +++ b/packages/@aws-cdk/core/lib/private/metadata-resource.ts @@ -1,4 +1,4 @@ -import * as cxapi from '@aws-cdk/cx-api'; +import * as zlib from 'zlib'; import { RegionInfo } from '@aws-cdk/region-info'; import { CfnCondition } from '../cfn-condition'; import { Fn } from '../cfn-fn'; @@ -8,41 +8,12 @@ import { Construct } from '../construct-compat'; import { Lazy } from '../lazy'; import { Stack } from '../stack'; import { Token } from '../token'; -import { collectRuntimeInformation } from './runtime-info'; +import { ConstructInfo, constructInfoFromStack } from './runtime-info'; /** * Construct that will render the metadata resource */ export class MetadataResource extends Construct { - /** - * Clear the modules cache - * - * The next time the MetadataResource is rendered, it will do a lookup of the - * modules from the NodeJS module cache again. - * - * Used only for unit tests. - */ - public static clearModulesCache() { - this._modulesPropertyCache = undefined; - } - - /** - * Cached version of the _modulesProperty() accessor - * - * No point in calculating this fairly expensive list more than once. - */ - private static _modulesPropertyCache?: string; - - /** - * Calculate the modules property - */ - private static modulesProperty(): string { - if (this._modulesPropertyCache === undefined) { - this._modulesPropertyCache = formatModules(collectRuntimeInformation()); - } - return this._modulesPropertyCache; - } - constructor(scope: Stack, id: string) { super(scope, id); @@ -51,7 +22,7 @@ export class MetadataResource extends Construct { const resource = new CfnResource(this, 'Default', { type: 'AWS::CDK::Metadata', properties: { - Modules: Lazy.string({ produce: () => MetadataResource.modulesProperty() }), + Analytics: Lazy.string({ produce: () => formatAnalytics(constructInfoFromStack(scope)) }), }, }); @@ -76,17 +47,81 @@ function makeCdkMetadataAvailableCondition() { .map(ri => Fn.conditionEquals(Aws.REGION, ri.name))); } -function formatModules(runtime: cxapi.RuntimeInfo): string { - const modules = new Array(); +/** Convenience type for arbitrarily-nested map */ +class Trie extends Map { } - // inject toolkit version to list of modules - const cliVersion = process.env[cxapi.CLI_VERSION_ENV]; - if (cliVersion) { - modules.push(`aws-cdk=${cliVersion}`); - } +/** + * Formats a list of construct fully-qualified names (FQNs) and versions into a (possibly compressed) prefix-encoded string. + * + * The list of ConstructInfos is logically formatted into: + * ${version}!${fqn} (e.g., "1.90.0!aws-cdk-lib.Stack") + * and then all of the construct-versions are grouped with common prefixes together, grouping common parts in '{}' and separating items with ','. + * + * Example: + * [1.90.0!aws-cdk-lib.Stack, 1.90.0!aws-cdk-lib.Construct, 1.90.0!aws-cdk-lib.service.Resource, 0.42.1!aws-cdk-lib-experiments.NewStuff] + * Becomes: + * 1.90.0!aws-cdk-lib.{Stack,Construct,service.Resource},0.42.1!aws-cdk-lib-experiments.NewStuff + * + * The whole thing is then either included directly as plaintext as: + * v2:plaintext:{prefixEncodedList} + * Or is compressed and base64-encoded, and then formatted as: + * v2:deflate64:{prefixEncodedListCompressedAndEncoded} + * + * Exported/visible for ease of testing. + */ +export function formatAnalytics(infos: ConstructInfo[]) { + const trie = new Trie(); + infos.forEach(info => insertFqnInTrie(`${info.version}!${info.fqn}`, trie)); - for (const key of Object.keys(runtime.libraries).sort()) { - modules.push(`${key}=${runtime.libraries[key]}`); + const plaintextEncodedConstructs = prefixEncodeTrie(trie); + const compressedConstructs = zlib.gzipSync(Buffer.from(plaintextEncodedConstructs)).toString('base64'); + + return `v2:deflate64:${compressedConstructs}`; +} + +/** + * Splits after non-alphanumeric characters (e.g., '.', '/') in the FQN + * and insert each piece of the FQN in nested map (i.e., simple trie). + */ +function insertFqnInTrie(fqn: string, trie: Trie) { + for (const fqnPart of fqn.replace(/[^a-z0-9]/gi, '$& ').split(' ')) { + const nextLevelTreeRef = trie.get(fqnPart) ?? new Trie(); + trie.set(fqnPart, nextLevelTreeRef); + trie = nextLevelTreeRef; } - return modules.join(','); -} \ No newline at end of file + return trie; +} + +/** + * Prefix-encodes a "trie-ish" structure, using '{}' to group and ',' to separate siblings. + * + * Example input: + * ABC,ABD,AEF + * + * Example trie: + * A --> B --> C + * | \--> D + * \--> E --> F + * + * Becomes: + * A{B{C,D},EF} + */ +function prefixEncodeTrie(trie: Trie) { + let prefixEncoded = ''; + let isFirstEntryAtLevel = true; + [...trie.entries()].forEach(([key, value]) => { + if (!isFirstEntryAtLevel) { + prefixEncoded += ','; + } + isFirstEntryAtLevel = false; + prefixEncoded += key; + if (value.size > 1) { + prefixEncoded += '{'; + prefixEncoded += prefixEncodeTrie(value); + prefixEncoded += '}'; + } else { + prefixEncoded += prefixEncodeTrie(value); + } + }); + return prefixEncoded; +} diff --git a/packages/@aws-cdk/core/lib/private/runtime-info.ts b/packages/@aws-cdk/core/lib/private/runtime-info.ts index b0cf266e8e11d..da4fbdcbe99d8 100644 --- a/packages/@aws-cdk/core/lib/private/runtime-info.ts +++ b/packages/@aws-cdk/core/lib/private/runtime-info.ts @@ -1,95 +1,82 @@ -import { basename, dirname } from 'path'; -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import { major as nodeMajorVersion } from './node-version'; +import { IConstruct } from '../construct-compat'; +import { Stack } from '../stack'; +import { Stage } from '../stage'; -// list of NPM scopes included in version reporting e.g. @aws-cdk and @aws-solutions-konstruk -const ALLOWED_SCOPES = ['@aws-cdk', '@aws-cdk-containers', '@aws-solutions-konstruk', '@aws-solutions-constructs', '@amzn']; -// list of NPM packages included in version reporting -const ALLOWED_PACKAGES = ['aws-rfdk', 'aws-cdk-lib', 'monocdk']; +const ALLOWED_FQN_PREFIXES = [ + // SCOPES + '@aws-cdk/', '@aws-cdk-containers/', '@aws-solutions-konstruk/', '@aws-solutions-constructs/', '@amzn/', + // PACKAGES + 'aws-rfdk.', 'aws-cdk-lib.', 'monocdk.', +]; /** - * Returns a list of loaded modules and their versions. + * Symbol for accessing jsii runtime information + * + * Introduced in jsii 1.19.0, cdk 1.90.0. */ -export function collectRuntimeInformation(): cxschema.RuntimeInfo { - const libraries: { [name: string]: string } = {}; - - for (const fileName of Object.keys(require.cache)) { - const pkg = findNpmPackage(fileName); - if (pkg && !pkg.private) { - libraries[pkg.name] = pkg.version; - } - } +const JSII_RUNTIME_SYMBOL = Symbol.for('jsii.rtti'); - // include only libraries that are in the allowlistLibraries list - for (const name of Object.keys(libraries)) { - let foundMatch = false; - for (const scope of ALLOWED_SCOPES) { - if (name.startsWith(`${scope}/`)) { - foundMatch = true; - } - } - foundMatch = foundMatch || ALLOWED_PACKAGES.includes(name); +/** + * Source information on a construct (class fqn and version) + */ +export interface ConstructInfo { + readonly fqn: string; + readonly version: string; +} - if (!foundMatch) { - delete libraries[name]; - } +export function constructInfoFromConstruct(construct: IConstruct): ConstructInfo | undefined { + const jsiiRuntimeInfo = Object.getPrototypeOf(construct).constructor[JSII_RUNTIME_SYMBOL]; + if (typeof jsiiRuntimeInfo === 'object' + && jsiiRuntimeInfo !== null + && typeof jsiiRuntimeInfo.fqn === 'string' + && typeof jsiiRuntimeInfo.version === 'string') { + return { fqn: jsiiRuntimeInfo.fqn, version: jsiiRuntimeInfo.version }; + } else if (jsiiRuntimeInfo) { + // There is something defined, but doesn't match our expectations. Fail fast and hard. + throw new Error(`malformed jsii runtime info for construct: '${construct.node.path}'`); } - - // add jsii runtime version - libraries['jsii-runtime'] = getJsiiAgentVersion(); - - return { libraries }; + return undefined; } /** - * Determines which NPM module a given loaded javascript file is from. - * - * The only infromation that is available locally is a list of Javascript files, - * and every source file is associated with a search path to resolve the further - * ``require`` calls made from there, which includes its own directory on disk, - * and parent directories - for example: - * - * [ '...repo/packages/aws-cdk-resources/lib/cfn/node_modules', - * '...repo/packages/aws-cdk-resources/lib/node_modules', - * '...repo/packages/aws-cdk-resources/node_modules', - * '...repo/packages/node_modules', - * // etc... - * ] - * - * We are looking for ``package.json`` that is anywhere in the tree, except it's - * in the parent directory, not in the ``node_modules`` directory. For this - * reason, we strip the ``/node_modules`` suffix off each path and use regular - * module resolution to obtain a reference to ``package.json``. - * - * @param fileName a javascript file name. - * @returns the NPM module infos (aka ``package.json`` contents), or - * ``undefined`` if the lookup was unsuccessful. + * For a given stack, walks the tree and finds the runtime info for all constructs within the tree. + * Returns the unique list of construct info present in the stack, + * as long as the construct fully-qualified names match the defined allow list. */ -function findNpmPackage(fileName: string): { name: string, version: string, private?: boolean } | undefined { - const mod = require.cache[fileName]; +export function constructInfoFromStack(stack: Stack): ConstructInfo[] { + const isDefined = (value: ConstructInfo | undefined): value is ConstructInfo => value !== undefined; - if (!mod?.paths) { - // sometimes this can be undefined. for example when querying for .json modules - // inside a jest runtime environment. - // see https://github.com/aws/aws-cdk/issues/7657 - // potentially we can remove this if it turns out to be a bug in how jest implemented the 'require' module. - return undefined; - } + const allConstructInfos = constructsInStack(stack) + .map(construct => constructInfoFromConstruct(construct)) + .filter(isDefined) + .filter(info => ALLOWED_FQN_PREFIXES.find(prefix => info.fqn.startsWith(prefix))); - // For any path in ``mod.paths`` that is a node_modules folder, use its parent directory instead. - const paths = mod?.paths.map((path: string) => basename(path) === 'node_modules' ? dirname(path) : path); + // Adds the jsii runtime as a psuedo construct for reporting purposes. + allConstructInfos.push({ + fqn: 'jsii-runtime.Runtime', + version: getJsiiAgentVersion(), + }); - try { - const packagePath = require.resolve( - // Resolution behavior changed in node 12.0.0 - https://github.com/nodejs/node/issues/27583 - nodeMajorVersion >= 12 ? './package.json' : 'package.json', - { paths }, - ); - // eslint-disable-next-line @typescript-eslint/no-require-imports - return require(packagePath); - } catch (e) { - return undefined; - } + // Filter out duplicate values + const uniqKeys = new Set(); + return allConstructInfos.filter(construct => { + const constructKey = `${construct.fqn}@${construct.version}`; + const isDuplicate = uniqKeys.has(constructKey); + uniqKeys.add(constructKey); + return !isDuplicate; + }); +} + +/** + * Returns all constructs under the parent construct (including the parent), + * stopping when it reaches a boundary of another stack (e.g., Stack, Stage, NestedStack). + */ +function constructsInStack(construct: IConstruct): IConstruct[] { + const constructs = [construct]; + construct.node.children + .filter(child => !Stage.isStage(child) && !Stack.isStack(child)) + .forEach(child => constructs.push(...constructsInStack(child))); + return constructs; } function getJsiiAgentVersion() { diff --git a/packages/@aws-cdk/core/lib/private/tree-metadata.ts b/packages/@aws-cdk/core/lib/private/tree-metadata.ts index caa5c37a5940d..97fe514bb4d87 100644 --- a/packages/@aws-cdk/core/lib/private/tree-metadata.ts +++ b/packages/@aws-cdk/core/lib/private/tree-metadata.ts @@ -6,16 +6,10 @@ import { Annotations } from '../annotations'; import { Construct, IConstruct, ISynthesisSession } from '../construct-compat'; import { Stack } from '../stack'; import { IInspectable, TreeInspector } from '../tree'; +import { ConstructInfo, constructInfoFromConstruct } from './runtime-info'; const FILE_PATH = 'tree.json'; -/** - * Symbol for accessing jsii runtime information - * - * Introduced in jsii 1.19.0, cdk 1.90.0. - */ -const JSII_RUNTIME_SYMBOL = Symbol.for('jsii.rtti'); - /** * Construct that is automatically attached to the top-level `App`. * This generates, as part of synthesis, a file containing the construct tree and the metadata for each node in the tree. @@ -48,14 +42,12 @@ export class TreeMetadata extends Construct { .filter((child) => child !== undefined) .reduce((map, child) => Object.assign(map, { [child!.id]: child }), {}); - const jsiiRuntimeInfo = Object.getPrototypeOf(construct).constructor[JSII_RUNTIME_SYMBOL]; - const node: Node = { id: construct.node.id || 'App', path: construct.node.path, children: Object.keys(childrenMap).length === 0 ? undefined : childrenMap, attributes: this.synthAttributes(construct), - constructInfo: constructInfoFromRuntimeInfo(jsiiRuntimeInfo), + constructInfo: constructInfoFromConstruct(construct), }; lookup[node.path] = node; @@ -96,16 +88,6 @@ export class TreeMetadata extends Construct { } } -function constructInfoFromRuntimeInfo(jsiiRuntimeInfo: any): ConstructInfo | undefined { - if (typeof jsiiRuntimeInfo === 'object' - && jsiiRuntimeInfo !== null - && typeof jsiiRuntimeInfo.fqn === 'string' - && typeof jsiiRuntimeInfo.version === 'string') { - return { fqn: jsiiRuntimeInfo.fqn, version: jsiiRuntimeInfo.version }; - } - return undefined; -} - interface Node { readonly id: string; readonly path: string; @@ -117,11 +99,3 @@ interface Node { */ readonly constructInfo?: ConstructInfo; } - -/** - * Source information on a construct (class fqn and version) - */ -interface ConstructInfo { - readonly fqn: string; - readonly version: string; -} diff --git a/packages/@aws-cdk/core/test/app.test.ts b/packages/@aws-cdk/core/test/app.test.ts index 69486987f0085..199b36dc87465 100644 --- a/packages/@aws-cdk/core/test/app.test.ts +++ b/packages/@aws-cdk/core/test/app.test.ts @@ -4,7 +4,6 @@ import { nodeunitShim, Test } from 'nodeunit-shim'; import { CfnResource, Construct, Stack, StackProps } from '../lib'; import { Annotations } from '../lib/annotations'; import { App, AppProps } from '../lib/app'; -import { MetadataResource } from '../lib/private/metadata-resource'; function withApp(props: AppProps, block: (app: App) => void): cxapi.CloudAssembly { const app = new App({ @@ -260,90 +259,6 @@ nodeunitShim({ test.done(); }, - 'runtime library versions'(test: Test) { - v1(() => { - MetadataResource.clearModulesCache(); - - const response = withApp({ analyticsReporting: true }, app => { - const stack = new Stack(app, 'stack1'); - new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); - }); - - const stackTemplate = response.getStackByName('stack1').template; - const libs = parseModules(stackTemplate.Resources?.CDKMetadata?.Properties?.Modules); - - // eslint-disable-next-line @typescript-eslint/no-require-imports - const version = require('../package.json').version; - test.deepEqual(libs['@aws-cdk/core'], version); - test.deepEqual(libs['@aws-cdk/cx-api'], version); - test.deepEqual(libs['jsii-runtime'], `node.js/${process.version}`); - }); - test.done(); - }, - - 'CDK version'(test: Test) { - MetadataResource.clearModulesCache(); - - withCliVersion(() => { - const response = withApp({ analyticsReporting: true }, app => { - const stack = new Stack(app, 'stack1'); - new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); - }); - - const stackTemplate = response.getStackByName('stack1').template; - const libs = parseModules(stackTemplate.Resources?.CDKMetadata?.Properties?.Modules); - - // eslint-disable-next-line @typescript-eslint/no-require-imports - test.deepEqual(libs['aws-cdk'], '1.2.3'); - }); - - test.done(); - }, - - 'jsii-runtime version loaded from JSII_AGENT'(test: Test) { - process.env.JSII_AGENT = 'Java/1.2.3.4'; - MetadataResource.clearModulesCache(); - - withCliVersion(() => { - const response = withApp({ analyticsReporting: true }, app => { - const stack = new Stack(app, 'stack1'); - new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); - }); - - const stackTemplate = response.getStackByName('stack1').template; - const libs = parseModules(stackTemplate.Resources?.CDKMetadata?.Properties?.Modules); - - test.deepEqual(libs['jsii-runtime'], 'Java/1.2.3.4'); - }); - - delete process.env.JSII_AGENT; - test.done(); - }, - - 'version reporting includes only @aws-cdk, aws-cdk and jsii libraries'(test: Test) { - v1(() => { - MetadataResource.clearModulesCache(); - - const response = withApp({ analyticsReporting: true }, app => { - const stack = new Stack(app, 'stack1'); - new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); - }); - - const stackTemplate = response.getStackByName('stack1').template; - const libs = parseModules(stackTemplate.Resources?.CDKMetadata?.Properties?.Modules); - const libNames = Object.keys(libs).sort(); - - test.deepEqual(libNames, [ - '@aws-cdk/cloud-assembly-schema', - '@aws-cdk/core', - '@aws-cdk/cx-api', - '@aws-cdk/region-info', - 'jsii-runtime', - ]); - }); - test.done(); - }, - 'deep stack is shown and synthesized properly'(test: Test) { // WHEN const response = withApp({}, (app) => { @@ -420,42 +335,3 @@ class MyConstruct extends Construct { new CfnResource(this, 'r2', { type: 'ResourceType2', properties: { FromContext: this.node.tryGetContext('ctx1') } }); } } - -function parseModules(x?: string): Record { - if (x === undefined) { return {}; } - - const ret: Record = {}; - for (const clause of x.split(',')) { - const [key, value] = clause.split('='); - if (key !== undefined && value !== undefined) { - ret[key] = value; - } - } - return ret; -} - -/** - * Set the CLI_VERSION_ENV environment variable - * - * This is necessary to get the Stack to emit the metadata resource - */ -function withCliVersion(block: () => A): A { - process.env[cxapi.CLI_VERSION_ENV] = '1.2.3'; - try { - return block(); - } finally { - delete process.env[cxapi.CLI_VERSION_ENV]; - } -} - -function v1(block: () => void) { - onVersion(1, block); -} - -function onVersion(version: number, block: () => void) { - // eslint-disable-next-line @typescript-eslint/no-require-imports - const mv: number = require('../../../../release.json').majorVersion; - if (version === mv) { - block(); - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/core/test/metadata-resource.test.ts b/packages/@aws-cdk/core/test/metadata-resource.test.ts new file mode 100644 index 0000000000000..2275bcf7dee9d --- /dev/null +++ b/packages/@aws-cdk/core/test/metadata-resource.test.ts @@ -0,0 +1,151 @@ +import * as zlib from 'zlib'; +import { App, Stack } from '../lib'; +import { formatAnalytics } from '../lib/private/metadata-resource'; + +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '../lib'; +import { ConstructInfo } from '../lib/private/runtime-info'; + +describe('MetadataResource', () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App({ + analyticsReporting: true, + }); + stack = new Stack(app, 'Stack'); + }); + + test('is not included if the region is known and metadata is not available', () => { + new Stack(app, 'StackUnavailable', { + env: { region: 'definitely-no-metadata-resource-available-here' }, + }); + + const stackTemplate = app.synth().getStackByName('StackUnavailable').template; + + expect(stackTemplate.Resources?.CDKMetadata).toBeUndefined(); + }); + + test('is included if the region is known and metadata is available', () => { + new Stack(app, 'StackPresent', { + env: { region: 'us-east-1' }, + }); + + const stackTemplate = app.synth().getStackByName('StackPresent').template; + + expect(stackTemplate.Resources?.CDKMetadata).toBeDefined(); + }); + + test('is included if the region is unknown with conditions', () => { + new Stack(app, 'StackUnknown'); + + const stackTemplate = app.synth().getStackByName('StackUnknown').template; + + expect(stackTemplate.Resources?.CDKMetadata).toBeDefined(); + expect(stackTemplate.Resources?.CDKMetadata?.Condition).toBeDefined(); + }); + + test('includes the formatted Analytics property', () => { + // A very simple check that the jsii runtime psuedo-construct is present. + // This check works whether we're running locally or on CodeBuild, on v1 or v2. + // Other tests(in app.test.ts) will test version-specific results. + expect(stackAnalytics()).toMatch(/jsii-runtime.Runtime/); + }); + + test('includes the current jsii runtime version', () => { + process.env.JSII_AGENT = 'Java/1.2.3.4'; + + expect(stackAnalytics()).toContain('Java/1.2.3.4!jsii-runtime.Runtime'); + delete process.env.JSII_AGENT; + }); + + test('includes constructs added to the stack', () => { + new TestConstruct(stack, 'Test'); + + expect(stackAnalytics()).toContain('1.2.3!@amzn/core.TestConstruct'); + }); + + test('only includes constructs in the allow list', () => { + new TestThirdPartyConstruct(stack, 'Test'); + + expect(stackAnalytics()).not.toContain('TestConstruct'); + }); + + function stackAnalytics(stackName: string = 'Stack') { + const encodedAnalytics = app.synth().getStackByName(stackName).template.Resources?.CDKMetadata?.Properties?.Analytics as string; + return plaintextConstructsFromAnalytics(encodedAnalytics); + } +}); + +describe('formatAnalytics', () => { + test('analytics are formatted with a prefix of v2:deflate64:', () => { + const constructInfo = [{ fqn: 'aws-cdk-lib.Construct', version: '1.2.3' }]; + + expect(formatAnalytics(constructInfo)).toMatch(/v2:deflate64:.*/); + }); + + test('single construct', () => { + const constructInfo = [{ fqn: 'aws-cdk-lib.Construct', version: '1.2.3' }]; + + expectAnalytics(constructInfo, '1.2.3!aws-cdk-lib.Construct'); + }); + + test('common prefixes with same versions are combined', () => { + const constructInfo = [ + { fqn: 'aws-cdk-lib.Construct', version: '1.2.3' }, + { fqn: 'aws-cdk-lib.CfnResource', version: '1.2.3' }, + { fqn: 'aws-cdk-lib.Stack', version: '1.2.3' }, + ]; + + expectAnalytics(constructInfo, '1.2.3!aws-cdk-lib.{Construct,CfnResource,Stack}'); + }); + + test('nested modules with common prefixes and same versions are combined', () => { + const constructInfo = [ + { fqn: 'aws-cdk-lib.Construct', version: '1.2.3' }, + { fqn: 'aws-cdk-lib.CfnResource', version: '1.2.3' }, + { fqn: 'aws-cdk-lib.Stack', version: '1.2.3' }, + { fqn: 'aws-cdk-lib.aws_servicefoo.CoolResource', version: '1.2.3' }, + { fqn: 'aws-cdk-lib.aws_servicefoo.OtherResource', version: '1.2.3' }, + ]; + + expectAnalytics(constructInfo, '1.2.3!aws-cdk-lib.{Construct,CfnResource,Stack,aws_servicefoo.{CoolResource,OtherResource}}'); + }); + + test('constructs are grouped by version', () => { + const constructInfo = [ + { fqn: 'aws-cdk-lib.Construct', version: '1.2.3' }, + { fqn: 'aws-cdk-lib.CfnResource', version: '1.2.3' }, + { fqn: 'aws-cdk-lib.Stack', version: '1.2.3' }, + { fqn: 'aws-cdk-lib.CoolResource', version: '0.1.2' }, + { fqn: 'aws-cdk-lib.OtherResource', version: '0.1.2' }, + ]; + + expectAnalytics(constructInfo, '1.2.3!aws-cdk-lib.{Construct,CfnResource,Stack},0.1.2!aws-cdk-lib.{CoolResource,OtherResource}'); + }); + + // Compares the output of formatAnalytics with an expected (plaintext) output. + // For ease of testing, the plaintext versions are compared rather than the encoded versions. + function expectAnalytics(constructs: ConstructInfo[], expectedPlaintext: string) { + expect(plaintextConstructsFromAnalytics(formatAnalytics(constructs))).toEqual(expectedPlaintext); + } + +}); + +function plaintextConstructsFromAnalytics(analytics: string) { + return zlib.gunzipSync(Buffer.from(analytics.split(':')[2], 'base64')).toString('utf-8'); +} + +const JSII_RUNTIME_SYMBOL = Symbol.for('jsii.rtti'); + +class TestConstruct extends Construct { + // @ts-ignore + private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: '@amzn/core.TestConstruct', version: '1.2.3' } +} + +class TestThirdPartyConstruct extends Construct { + // @ts-ignore + private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: 'mycoolthing.TestConstruct', version: '1.2.3' } +} + diff --git a/packages/@aws-cdk/core/test/runtime-info.test.ts b/packages/@aws-cdk/core/test/runtime-info.test.ts index 67f931bb63ec5..7da4f78d74b46 100644 --- a/packages/@aws-cdk/core/test/runtime-info.test.ts +++ b/packages/@aws-cdk/core/test/runtime-info.test.ts @@ -1,73 +1,189 @@ -import * as fs from 'fs'; -import * as os from 'os'; import * as path from 'path'; -import { nodeunitShim, Test } from 'nodeunit-shim'; -import { collectRuntimeInformation } from '../lib/private/runtime-info'; - -nodeunitShim({ - 'version reporting includes @aws-solutions-konstruk libraries'(test: Test) { - const pkgdir = fs.mkdtempSync(path.join(os.tmpdir(), 'runtime-info-konstruk-fixture')); - const mockVersion = '1.2.3'; - - fs.writeFileSync(path.join(pkgdir, 'index.js'), 'module.exports = \'this is foo\';'); - fs.writeFileSync(path.join(pkgdir, 'package.json'), JSON.stringify({ - name: '@aws-solutions-konstruk/foo', - version: mockVersion, - })); - - // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies - require(pkgdir); - - const runtimeInfo = collectRuntimeInformation(); - - // eslint-disable-next-line @typescript-eslint/no-require-imports - test.deepEqual(runtimeInfo.libraries['@aws-solutions-konstruk/foo'], mockVersion); - test.done(); - }, - - 'version reporting finds aws-rfdk package'(test: Test) { - const pkgdir = fs.mkdtempSync(path.join(os.tmpdir(), 'runtime-info-rfdk')); - const mockVersion = '1.2.3'; - - fs.writeFileSync(path.join(pkgdir, 'index.js'), 'module.exports = \'this is foo\';'); - fs.writeFileSync(path.join(pkgdir, 'package.json'), JSON.stringify({ - name: 'aws-rfdk', - version: mockVersion, - })); +import { App, NestedStack, Stack, Stage } from '../lib'; +import { constructInfoFromConstruct, constructInfoFromStack } from '../lib/private/runtime-info'; + +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '../lib'; + +const JSII_RUNTIME_SYMBOL = Symbol.for('jsii.rtti'); + +let app: App; +let stack: Stack; +let _cdkVersion: string | undefined = undefined; +const modulePrefix = cdkMajorVersion() === 1 ? '@aws-cdk/core' : 'aws-cdk-lib'; + +// The runtime metadata this test relies on is only available if the most +// recent compile has happened using 'jsii', as the jsii compiler injects +// this metadata. +// +// If the most recent compile was using 'tsc', the metadata will not have +// been injected, and the test suite will fail. +// +// Tolerate `tsc` builds locally, but not on CodeBuild. +const codeBuild = !!process.env.CODEBUILD_BUILD_ID; +const moduleCompiledWithTsc = constructInfoFromConstruct(new Stack())?.fqn === 'constructs.Construct'; +let describeTscSafe = describe; +if (moduleCompiledWithTsc && !codeBuild) { + // eslint-disable-next-line + console.error('It appears this module was compiled with `tsc` instead of `jsii` in a local build. Skipping this test suite.'); + describeTscSafe = describe.skip; +} + +beforeEach(() => { + app = new App(); + stack = new Stack(app, 'Stack', { + analyticsReporting: true, + }); +}); - // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies - require(pkgdir); +describeTscSafe('constructInfoFromConstruct', () => { + test('returns fqn and version for core constructs', () => { + const constructInfo = constructInfoFromConstruct(stack); + expect(constructInfo).toBeDefined(); + expect(constructInfo?.fqn).toEqual(`${modulePrefix}.Stack`); + expect(constructInfo?.version).toEqual(localCdkVersion()); + }); + + test('returns base construct info if no more specific info is present', () => { + const simpleConstruct = new class extends Construct { }(stack, 'Simple'); + const constructInfo = constructInfoFromConstruct(simpleConstruct); + expect(constructInfo?.fqn).toEqual(`${modulePrefix}.Construct`); + }); + + test('returns more specific subclass info if present', () => { + const construct = new class extends Construct { + // @ts-ignore + private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: 'aws-cdk-lib.TestConstruct', version: localCdkVersion() } + }(stack, 'TestConstruct'); + + const constructInfo = constructInfoFromConstruct(construct); + expect(constructInfo?.fqn).toEqual('aws-cdk-lib.TestConstruct'); + }); + + test('throws if the jsii runtime info is not as expected', () => { + const constructRuntimeInfoNotObject = new class extends Construct { + // @ts-ignore + private static readonly [JSII_RUNTIME_SYMBOL] = 'HelloWorld'; + }(stack, 'RuntimeNotObject'); + const constructWithWrongRuntimeInfoMembers = new class extends Construct { + // @ts-ignore + private static readonly [JSII_RUNTIME_SYMBOL] = { foo: 'bar' }; + }(stack, 'RuntimeWrongMembers'); + const constructWithWrongRuntimeInfoTypes = new class extends Construct { + // @ts-ignore + private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: 42, version: { name: '0.0.0' } }; + }(stack, 'RuntimeWrongTypes'); + + const errorMessage = 'malformed jsii runtime info for construct'; + [constructRuntimeInfoNotObject, constructWithWrongRuntimeInfoMembers, constructWithWrongRuntimeInfoTypes].forEach(construct => { + expect(() => constructInfoFromConstruct(construct)).toThrow(errorMessage); + }); + }); +}); - const runtimeInfo = collectRuntimeInformation(); +describeTscSafe('constructInfoForStack', () => { + test('returns stack itself and jsii runtime if stack is empty', () => { + const constructInfos = constructInfoFromStack(stack); + + expect(constructInfos.length).toEqual(2); + + const stackInfo = constructInfos.find(i => /Stack/.test(i.fqn)); + const jsiiInfo = constructInfos.find(i => i.fqn === 'jsii-runtime.Runtime'); + expect(stackInfo?.fqn).toEqual(`${modulePrefix}.Stack`); + expect(stackInfo?.version).toEqual(localCdkVersion()); + expect(jsiiInfo?.version).toMatch(/node.js/); + }); + + test('returns info for constructs added to the stack', () => { + new class extends Construct { }(stack, 'Simple'); + + const constructInfos = constructInfoFromStack(stack); + + expect(constructInfos.length).toEqual(3); + expect(constructInfos.map(info => info.fqn)).toContain(`${modulePrefix}.Construct`); + }); + + test('returns unique info (no duplicates)', () => { + new class extends Construct { }(stack, 'Simple1'); + new class extends Construct { }(stack, 'Simple2'); + + const constructInfos = constructInfoFromStack(stack); + + expect(constructInfos.length).toEqual(3); + expect(constructInfos.map(info => info.fqn)).toContain(`${modulePrefix}.Construct`); + }); + + test('returns info from nested constructs', () => { + new class extends Construct { + constructor(scope: Construct, id: string) { + super(scope, id); + return new class extends Construct { + // @ts-ignore + private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: '@aws-cdk/test.TestV1Construct', version: localCdkVersion() } + }(this, 'TestConstruct'); + } + }(stack, 'Nested'); + + const constructInfos = constructInfoFromStack(stack); + + expect(constructInfos.length).toEqual(4); + expect(constructInfos.map(info => info.fqn)).toContain('@aws-cdk/test.TestV1Construct'); + }); + + test('does not return info from nested stacks', () => { + new class extends Construct { + constructor(scope: Construct, id: string) { + super(scope, id); + + new class extends Construct { + // @ts-ignore + private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: '@aws-cdk/test.TestV1Construct', version: localCdkVersion() } + }(this, 'TestConstruct'); + + new class extends Stack { + // @ts-ignore + private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: '@aws-cdk/test.TestStackInsideStack', version: localCdkVersion() } + }(this, 'StackInsideStack'); + + new class extends NestedStack { + // @ts-ignore + private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: '@aws-cdk/test.TestNestedStackInsideStack', version: localCdkVersion() } + }(this, 'NestedStackInsideStack'); + + new class extends Stage { + // @ts-ignore + private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: '@aws-cdk/test.TestStageInsideStack', version: localCdkVersion() } + }(this, 'StageInsideStack'); + } + }(stack, 'ParentConstruct'); + + const constructInfos = constructInfoFromStack(stack); + + const fqns = constructInfos.map(info => info.fqn); + expect(fqns).toContain('@aws-cdk/test.TestV1Construct'); + expect(fqns).not.toContain('@aws-cdk/test.TestStackInsideStack'); + expect(fqns).not.toContain('@aws-cdk/test.TestNestedStackInsideStack'); + expect(fqns).not.toContain('@aws-cdk/test.TestStageInsideStack'); + }); +}); +function cdkMajorVersion(): number { + // eslint-disable-next-line @typescript-eslint/no-require-imports + return require('../../../../release.json').majorVersion; +} + +/** + * The exact values we expect from testing against version numbers in this suite depend on whether we're running + * on a development or release branch. Returns the local package.json version, which will be '0.0.0' unless we're + * on a release branch, in which case it should be the real version numbers (e.g., 1.91.0). + */ +function localCdkVersion(): string { + if (!_cdkVersion) { // eslint-disable-next-line @typescript-eslint/no-require-imports - test.deepEqual(runtimeInfo.libraries['aws-rfdk'], mockVersion); - test.done(); - }, - - 'version reporting finds no version with no associated package.json'(test: Test) { - const pkgdir = fs.mkdtempSync(path.join(os.tmpdir(), 'runtime-info-find-npm-package-fixture')); - const mockVersion = '1.2.3'; - - fs.writeFileSync(path.join(pkgdir, 'index.js'), 'module.exports = \'this is bar\';'); - fs.mkdirSync(path.join(pkgdir, 'bar')); - fs.writeFileSync(path.join(pkgdir, 'bar', 'package.json'), JSON.stringify({ - name: '@aws-solutions-konstruk/bar', - version: mockVersion, - })); - - // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies - require(pkgdir); - - const cwd = process.cwd(); - - // Switch to `bar` where the package.json is, then resolve version. Fails when module.resolve - // is passed an empty string in the paths array. - process.chdir(path.join(pkgdir, 'bar')); - const runtimeInfo = collectRuntimeInformation(); - process.chdir(cwd); - - test.equal(runtimeInfo.libraries['@aws-solutions-konstruk/bar'], undefined); - test.done(); - }, -}); + _cdkVersion = require(path.join('..', 'package.json')).version; + if (!_cdkVersion) { + throw new Error('Unable to determine CDK version'); + } + } + return _cdkVersion; +} diff --git a/scripts/resolve-version-lib.js b/scripts/resolve-version-lib.js index 2a7f0e4eecebc..21a13c0eb4ab2 100755 --- a/scripts/resolve-version-lib.js +++ b/scripts/resolve-version-lib.js @@ -38,7 +38,6 @@ function resolveVersion(rootdir) { // const currentVersion = require(versionFilePath).version; - console.error(`current version: ${currentVersion}`); if (!currentVersion.startsWith(`${majorVersion}.`)) { throw new Error(`current version "${currentVersion}" does not use the expected major version ${majorVersion}`); } From bc1293b8062a0792d6d1d9f058f308c72475e778 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 10 Mar 2021 13:25:51 +0100 Subject: [PATCH 03/41] refactor(core): refactor `CloudFormationLang.toJSON()` (#11224) Our previous implementation of `toJSON()` was quite hacky. It replaced values inside the structure with objects that had a custom `toJSON()` serializer, and then called `JSON.stringify()` on the result. The resulting JSON would have special markers in it where the Token values would be string-substituted back in. It's actually easier and gives us more control to just implement JSONification ourselves in a Token-aware recursive function. This change has been split off from a larger, upcoming PR in order to make the individual reviews smaller. Incidentally also fixes #13465, as the type of encoded tokens is assumed to match the type of the encoded value (e.g., a `string[]`-encoded token is assumed to produce a list at deploy-time and so will not be quoted). ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../core/lib/private/cloudformation-lang.ts | 346 ++++++++++++++---- packages/@aws-cdk/core/lib/private/resolve.ts | 137 ++++++- .../@aws-cdk/core/lib/private/token-map.ts | 3 +- packages/@aws-cdk/core/lib/resolvable.ts | 14 +- .../core/test/cloudformation-json.test.ts | 329 ++++++++++++----- packages/@aws-cdk/core/test/evaluate-cfn.ts | 14 +- 6 files changed, 645 insertions(+), 198 deletions(-) diff --git a/packages/@aws-cdk/core/lib/private/cloudformation-lang.ts b/packages/@aws-cdk/core/lib/private/cloudformation-lang.ts index 4a74665b8f338..310a4632f4e8f 100644 --- a/packages/@aws-cdk/core/lib/private/cloudformation-lang.ts +++ b/packages/@aws-cdk/core/lib/private/cloudformation-lang.ts @@ -1,10 +1,7 @@ import { Lazy } from '../lazy'; -import { Reference } from '../reference'; -import { DefaultTokenResolver, IFragmentConcatenator, IPostProcessor, IResolvable, IResolveContext } from '../resolvable'; -import { TokenizedStringFragments } from '../string-fragments'; +import { DefaultTokenResolver, IFragmentConcatenator, IResolveContext } from '../resolvable'; import { Token } from '../token'; -import { Intrinsic } from './intrinsic'; -import { resolve } from './resolve'; +import { INTRINSIC_KEY_PREFIX, ResolutionTypeHint, resolvedTypeHint } from './resolve'; /** * Routines that know how to do operations at the CloudFormation document language level @@ -24,59 +21,12 @@ export class CloudFormationLang { * @param space Indentation to use (default: no pretty-printing) */ public static toJSON(obj: any, space?: number): string { - // This works in two stages: - // - // First, resolve everything. This gets rid of the lazy evaluations, evaluation - // to the real types of things (for example, would a function return a string, an - // intrinsic, or a number? We have to resolve to know). - // - // We then to through the returned result, identify things that evaluated to - // CloudFormation intrinsics, and re-wrap those in Tokens that have a - // toJSON() method returning their string representation. If we then call - // JSON.stringify() on that result, that gives us essentially the same - // string that we started with, except with the non-token characters quoted. - // - // {"field": "${TOKEN}"} --> {\"field\": \"${TOKEN}\"} - // - // A final resolve() on that string (done by the framework) will yield the string - // we're after. - // - // Resolving and wrapping are done in go using the resolver framework. - class IntrinsincWrapper extends DefaultTokenResolver { - constructor() { - super(CLOUDFORMATION_CONCAT); - } - - public resolveToken(t: IResolvable, context: IResolveContext, postProcess: IPostProcessor) { - // Return References directly, so their type is maintained and the references will - // continue to work. Only while preparing, because we do need the final value of the - // token while resolving. - if (Reference.isReference(t) && context.preparing) { return wrap(t); } - - // Deep-resolve and wrap. This is necessary for Lazy tokens so we can see "inside" them. - return wrap(super.resolveToken(t, context, postProcess)); - } - public resolveString(fragments: TokenizedStringFragments, context: IResolveContext) { - return wrap(super.resolveString(fragments, context)); - } - public resolveList(l: string[], context: IResolveContext) { - return wrap(super.resolveList(l, context)); - } - } - - // We need a ResolveContext to get started so return a Token - return Lazy.stringValue({ - produce: (ctx: IResolveContext) => - JSON.stringify(resolve(obj, { - preparing: ctx.preparing, - scope: ctx.scope, - resolver: new IntrinsincWrapper(), - }), undefined, space), + return Lazy.uncachedString({ + // We used to do this by hooking into `JSON.stringify()` by adding in objects + // with custom `toJSON()` functions, but it's ultimately simpler just to + // reimplement the `stringify()` function from scratch. + produce: (ctx) => tokenAwareStringify(obj, space ?? 0, ctx), }); - - function wrap(value: any): any { - return isIntrinsic(value) ? new JsonToken(deepQuoteStringsForJSON(value)) : value; - } } /** @@ -97,44 +47,227 @@ export class CloudFormationLang { // Otherwise return a Join intrinsic (already in the target document language to avoid taking // circular dependencies on FnJoin & friends) - return { 'Fn::Join': ['', minimalCloudFormationJoin('', parts)] }; + return fnJoinConcat(parts); } } /** - * Token that also stringifies in the toJSON() operation. + * Return a CFN intrinsic mass concatting any number of CloudFormation expressions */ -class JsonToken extends Intrinsic { - /** - * Special handler that gets called when JSON.stringify() is used. - */ - public toJSON() { - return this.toString(); - } +function fnJoinConcat(parts: any[]) { + return { 'Fn::Join': ['', minimalCloudFormationJoin('', parts)] }; } /** - * Deep escape strings for use in a JSON context + * Perform a JSON.stringify()-like operation, except aware of Tokens and CloudFormation intrincics + * + * Tokens will be resolved and if any resolve to CloudFormation intrinsics, the intrinsics + * will be lifted to the top of a giant `{ Fn::Join }` expression. + * + * If Tokens resolve to primitive types (for example, by using Lazies), we'll + * use the primitive type to determine how to encode the value into the JSON. + * + * If Tokens resolve to CloudFormation intrinsics, we'll use the type of the encoded + * value as a type hint to determine how to encode the value into the JSON. The difference + * is that we add quotes (") around strings, and don't add anything around non-strings. + * + * The following structure: + * + * { SomeAttr: resource.someAttr } + * + * Will JSONify to either: + * + * '{ "SomeAttr": "' ++ { Fn::GetAtt: [Resource, SomeAttr] } ++ '" }' + * or '{ "SomeAttr": ' ++ { Fn::GetAtt: [Resource, SomeAttr] } ++ ' }' + * + * Depending on whether `someAttr` is type-hinted to be a string or not. + * + * (Where ++ is the CloudFormation string-concat operation (`{ Fn::Join }`). + * + * ----------------------- + * + * This work requires 2 features from the `resolve()` function: + * + * - INTRINSICS TYPE HINTS: intrinsics are represented by values like + * `{ Ref: 'XYZ' }`. These values can reference either a string or a list/number at + * deploy time, and from the value alone there's no way to know which. We need + * to know the type to know whether to JSONify this reference to: + * + * '{ "referencedValue": "' ++ { Ref: XYZ } ++ '"}' + * or '{ "referencedValue": ' ++ { Ref: XYZ } ++ '}' + * + * I.e., whether or not we need to enclose the reference in quotes or not. + * + * We COULD have done this by resolving one token at a time, and looking at the + * type of the encoded token we were resolving to obtain a type hint. However, + * the `resolve()` and Token system resist a level-at-a-time resolve + * operation: because of the existence of post-processors, we must have done a + * complete recursive resolution of a token before we can look at its result + * (after which any type information about the sources of nested resolved + * values is lost). + * + * To fix this, "type hints" have been added to the `resolve()` function, + * giving an idea of the type of the source value for compplex result values. + * This only works for objects (not strings and numbers) but fortunately + * we only care about the types of intrinsics, which are always complex values. + * + * Type hinting could have been added to the `IResolvable` protocol as well, + * but for now we just use the type of an encoded value as a type hint. That way + * we don't need to annotate anything more at the L1 level--we will use the type + * encodings added by construct authors at the L2 levels. L1 users can escape the + * default decision of "string" by using `Token.asList()`. + * + * - COMPLEX KEYS: since tokens can be string-encoded, we can use string-encoded tokens + * as the keys in JavaScript objects. However, after resolution, those string-encoded + * tokens could resolve to intrinsics (`{ Ref: ... }`), which CANNOT be stored in + * JavaScript objects anymore. + * + * We therefore need a protocol to store the resolved values somewhere in the JavaScript + * type model, which can be returned by `resolve()`, and interpreted by `tokenAwareStringify()` + * to produce the correct JSON. + * + * And example will quickly show the point: + * + * User writes: + * { [resource.resourceName]: 'SomeValue' } + * ------ string actually looks like ------> + * { '${Token[1234]}': 'SomeValue' } + * ------ resolve -------> + * { '$IntrinsicKey$0': [ {Ref: Resource}, 'SomeValue' ] } + * ------ tokenAwareStringify -------> + * '{ "' ++ { Ref: Resource } ++ '": "SomeValue" }' */ -function deepQuoteStringsForJSON(x: any): any { - if (typeof x === 'string') { - // Whenever we escape a string we strip off the outermost quotes - // since we're already in a quoted context. - const stringified = JSON.stringify(x); - return stringified.substring(1, stringified.length - 1); +function tokenAwareStringify(root: any, space: number, ctx: IResolveContext) { + let indent = 0; + + const ret = new Array(); + + // First completely resolve the tree, then encode to JSON while respecting the type + // hints we got for the resolved intrinsics. + recurse(ctx.resolve(root, { allowIntrinsicKeys: true })); + + switch (ret.length) { + case 0: return undefined; + case 1: return renderSegment(ret[0]); + default: + return fnJoinConcat(ret.map(renderSegment)); } - if (Array.isArray(x)) { - return x.map(deepQuoteStringsForJSON); + /** + * Stringify a JSON element + */ + function recurse(obj: any): void { + if (obj === undefined) { return; } + + if (Token.isUnresolved(obj)) { + throw new Error('This shouldnt happen anymore'); + } + if (Array.isArray(obj)) { + return renderCollection('[', ']', obj, recurse); + } + if (typeof obj === 'object' && obj != null && !(obj instanceof Date)) { + // Treat as an intrinsic if this LOOKS like a CFN intrinsic (`{ Ref: ... }`) + // AND it's the result of a token resolution. Otherwise, we just treat this + // value as a regular old JSON object (that happens to look a lot like an intrinsic). + if (isIntrinsic(obj) && resolvedTypeHint(obj)) { + return renderIntrinsic(obj); + } + + return renderCollection('{', '}', definedEntries(obj), ([key, value]) => { + if (key.startsWith(INTRINSIC_KEY_PREFIX)) { + [key, value] = value; + } + + recurse(key); + pushLiteral(prettyPunctuation(':')); + recurse(value); + }); + } + // Otherwise we have a scalar, defer to JSON.stringify()s serialization + pushLiteral(JSON.stringify(obj)); } - if (typeof x === 'object') { - for (const key of Object.keys(x)) { - x[key] = deepQuoteStringsForJSON(x[key]); + /** + * Render an object or list + */ + function renderCollection(pre: string, post: string, xs: Iterable, each: (x: A) => void) { + pushLiteral(pre); + indent += space; + let atLeastOne = false; + for (const [comma, item] of sepIter(xs)) { + if (comma) { pushLiteral(','); } + pushLineBreak(); + each(item); + atLeastOne = true; } + indent -= space; + if (atLeastOne) { pushLineBreak(); } + pushLiteral(post); } - return x; + function renderIntrinsic(intrinsic: any) { + switch (resolvedTypeHint(intrinsic)) { + case ResolutionTypeHint.STRING: + pushLiteral('"'); + pushIntrinsic(deepQuoteStringLiterals(intrinsic)); + pushLiteral('"'); + break; + + default: + pushIntrinsic(intrinsic); + break; + } + } + + /** + * Push a literal onto the current segment if it's also a literal, otherwise open a new Segment + */ + function pushLiteral(lit: string) { + let last = ret[ret.length - 1]; + if (last?.type !== 'literal') { + last = { type: 'literal', parts: [] }; + ret.push(last); + } + last.parts.push(lit); + } + + /** + * Add a new intrinsic segment + */ + function pushIntrinsic(intrinsic: any) { + ret.push({ type: 'intrinsic', intrinsic }); + } + + /** + * Push a line break if we are pretty-printing, otherwise don't + */ + function pushLineBreak() { + if (space > 0) { + pushLiteral(`\n${' '.repeat(indent)}`); + } + } + + /** + * Add a space after the punctuation if we are pretty-printing, no space if not + */ + function prettyPunctuation(punc: string) { + return space > 0 ? `${punc} ` : punc; + } +} + +/** + * A Segment is either a literal string or a CloudFormation intrinsic + */ +type Segment = { type: 'literal'; parts: string[] } | { type: 'intrinsic'; intrinsic: any }; + +/** + * Render a segment + */ +function renderSegment(s: Segment): NonNullable { + switch (s.type) { + case 'literal': return s.parts.join(''); + case 'intrinsic': return s.intrinsic; + } } const CLOUDFORMATION_CONCAT: IFragmentConcatenator = { @@ -204,3 +337,58 @@ export function isNameOfCloudFormationIntrinsic(name: string): boolean { // these are 'fake' intrinsics, only usable inside the parameter overrides of a CFN CodePipeline Action return name !== 'Fn::GetArtifactAtt' && name !== 'Fn::GetParam'; } + +/** + * Separated iterator + */ +function* sepIter(xs: Iterable): IterableIterator<[boolean, A]> { + let comma = false; + for (const item of xs) { + yield [comma, item]; + comma = true; + } +} + +/** + * Object.entries() but skipping undefined values + */ +function* definedEntries(xs: A): IterableIterator<[string, any]> { + for (const [key, value] of Object.entries(xs)) { + if (value !== undefined) { + yield [key, value]; + } + } +} + +/** + * Quote string literals inside an intrinsic + * + * Formally, this should only match string literals that will be interpreted as + * string literals. Fortunately, the strings that should NOT be quoted are + * Logical IDs and attribute names, which cannot contain quotes anyway. Hence, + * we can get away not caring about the distinction and just quoting everything. + */ +function deepQuoteStringLiterals(x: any): any { + if (Array.isArray(x)) { + return x.map(deepQuoteStringLiterals); + } + if (typeof x === 'object' && x != null) { + const ret: any = {}; + for (const [key, value] of Object.entries(x)) { + ret[deepQuoteStringLiterals(key)] = deepQuoteStringLiterals(value); + } + return ret; + } + if (typeof x === 'string') { + return quoteString(x); + } + return x; +} + +/** + * Quote the characters inside a string, for use inside toJSON + */ +function quoteString(s: string) { + s = JSON.stringify(s); + return s.substring(1, s.length - 1); +} \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/private/resolve.ts b/packages/@aws-cdk/core/lib/private/resolve.ts index d6ae73cdb8796..5f9620ecb759c 100644 --- a/packages/@aws-cdk/core/lib/private/resolve.ts +++ b/packages/@aws-cdk/core/lib/private/resolve.ts @@ -1,5 +1,5 @@ import { IConstruct } from 'constructs'; -import { DefaultTokenResolver, IPostProcessor, IResolvable, IResolveContext, ITokenResolver, StringConcat } from '../resolvable'; +import { DefaultTokenResolver, IPostProcessor, IResolvable, IResolveContext, ITokenResolver, ResolveChangeContextOptions, StringConcat } from '../resolvable'; import { TokenizedStringFragments } from '../string-fragments'; import { containsListTokenElement, TokenString, unresolved } from './encoding'; import { TokenMap } from './token-map'; @@ -9,9 +9,38 @@ import { TokenMap } from './token-map'; import { IConstruct as ICoreConstruct } from '../construct-compat'; // This file should not be exported to consumers, resolving should happen through Construct.resolve() - const tokenMap = TokenMap.instance(); +/** + * Resolved complex values will have a type hint applied. + * + * The type hint will be based on the type of the input value that was resolved. + * + * If the value was encoded, the type hint will be the type of the encoded value. In case + * of a plain `IResolvable`, a type hint of 'string' will be assumed. + */ +const RESOLUTION_TYPEHINT_SYM = Symbol.for('@aws-cdk/core.resolvedTypeHint'); + +/** + * Prefix used for intrinsic keys + * + * If a key with this prefix is found in an object, the actual value of the + * key doesn't matter. The value of this key will be an `[ actualKey, actualValue ]` + * tuple, and the `actualKey` will be a value which otherwise couldn't be represented + * in the types of `string | number | symbol`, which are the only possible JavaScript + * object keys. + */ +export const INTRINSIC_KEY_PREFIX = '$IntrinsicKey$'; + +/** + * Type hints for resolved values + */ +export enum ResolutionTypeHint { + STRING = 'string', + NUMBER = 'number', + LIST = 'list', +} + /** * Options to the resolve() operation * @@ -25,6 +54,36 @@ export interface IResolveOptions { preparing: boolean; resolver: ITokenResolver; prefix?: string[]; + + /** + * Whether or not to allow intrinsics in keys of an object + * + * Because keys of an object must be strings, a (resolved) intrinsic, which + * is an object, cannot be stored in that position. By default, we reject these + * intrinsics if we encounter them. + * + * If this is set to `true`, in order to store the complex value in a map, + * keys that happen to evaluate to intrinsics will be added with a unique key + * identified by an uncomming prefix, mapped to a tuple that represents the + * actual key/value-pair. The map will look like this: + * + * { + * '$IntrinsicKey$0': [ { Ref: ... }, 'value1' ], + * '$IntrinsicKey$1': [ { Ref: ... }, 'value2' ], + * 'regularKey': 'value3', + * ... + * } + * + * Callers should only set this option to `true` if they are prepared to deal with + * the object in this weird shape, and massage it back into a correct object afterwards. + * + * (A regular but uncommon string was chosen over something like symbols or + * other ways of tagging the extra values in order to simplify the implementation which + * maintains the desired behavior `resolve(resolve(x)) == resolve(x)`). + * + * @default false + */ + allowIntrinsicKeys?: boolean; } /** @@ -50,7 +109,7 @@ export function resolve(obj: any, options: IResolveOptions): any { preparing: options.preparing, scope: options.scope as ICoreConstruct, registerPostProcessor(pp) { postProcessor = pp; }, - resolve(x: any) { return resolve(x, { ...options, prefix: newPrefix }); }, + resolve(x: any, changeOptions?: ResolveChangeContextOptions) { return resolve(x, { ...options, ...changeOptions, prefix: newPrefix }); }, }; return [context, { postProcess(x) { return postProcessor ? postProcessor.postProcess(x, context) : x; } }]; @@ -98,7 +157,7 @@ export function resolve(obj: any, options: IResolveOptions): any { const str = TokenString.forString(obj); if (str.test()) { const fragments = str.split(tokenMap.lookupToken.bind(tokenMap)); - return options.resolver.resolveString(fragments, makeContext()[0]); + return tagResolvedValue(options.resolver.resolveString(fragments, makeContext()[0]), ResolutionTypeHint.STRING); } return obj; } @@ -107,7 +166,7 @@ export function resolve(obj: any, options: IResolveOptions): any { // number - potentially decode Tokenized number // if (typeof(obj) === 'number') { - return resolveNumberToken(obj, makeContext()[0]); + return tagResolvedValue(resolveNumberToken(obj, makeContext()[0]), ResolutionTypeHint.NUMBER); } // @@ -124,7 +183,7 @@ export function resolve(obj: any, options: IResolveOptions): any { if (Array.isArray(obj)) { if (containsListTokenElement(obj)) { - return options.resolver.resolveList(obj, makeContext()[0]); + return tagResolvedValue(options.resolver.resolveList(obj, makeContext()[0]), ResolutionTypeHint.LIST); } const arr = obj @@ -140,7 +199,8 @@ export function resolve(obj: any, options: IResolveOptions): any { if (unresolved(obj)) { const [context, postProcessor] = makeContext(); - return options.resolver.resolveToken(obj, context, postProcessor); + const ret = tagResolvedValue(options.resolver.resolveToken(obj, context, postProcessor), ResolutionTypeHint.STRING); + return ret; } // @@ -155,24 +215,40 @@ export function resolve(obj: any, options: IResolveOptions): any { } const result: any = { }; + let intrinsicKeyCtr = 0; for (const key of Object.keys(obj)) { - const resolvedKey = makeContext()[0].resolve(key); - if (typeof(resolvedKey) !== 'string') { - // eslint-disable-next-line max-len - throw new Error(`"${key}" is used as the key in a map so must resolve to a string, but it resolves to: ${JSON.stringify(resolvedKey)}. Consider using "CfnJson" to delay resolution to deployment-time`); - } - - const value = makeContext(key)[0].resolve(obj[key]); + const value = makeContext(String(key))[0].resolve(obj[key]); // skip undefined if (typeof(value) === 'undefined') { continue; } - result[resolvedKey] = value; + // Simple case -- not an unresolved key + if (!unresolved(key)) { + result[key] = value; + continue; + } + + const resolvedKey = makeContext()[0].resolve(key); + if (typeof(resolvedKey) === 'string') { + result[resolvedKey] = value; + } else { + if (!options.allowIntrinsicKeys) { + // eslint-disable-next-line max-len + throw new Error(`"${String(key)}" is used as the key in a map so must resolve to a string, but it resolves to: ${JSON.stringify(resolvedKey)}. Consider using "CfnJson" to delay resolution to deployment-time`); + } + + // Can't represent this object in a JavaScript key position, but we can store it + // in value position. Use a unique symbol as the key. + result[`${INTRINSIC_KEY_PREFIX}${intrinsicKeyCtr++}`] = [resolvedKey, value]; + } } - return result; + // Because we may be called to recurse on already resolved values (that already have type hints applied) + // and we just copied those values into a fresh object, be sure to retain any type hints. + const previousTypeHint = resolvedTypeHint(obj); + return previousTypeHint ? tagResolvedValue(result, previousTypeHint) : result; } /** @@ -222,3 +298,32 @@ function resolveNumberToken(x: number, context: IResolveContext): any { if (token === undefined) { return x; } return context.resolve(token); } + +/** + * Apply a type hint to a resolved value + * + * The type hint will only be applied to objects. + * + * These type hints are used for correct JSON-ification of intrinsic values. + */ +function tagResolvedValue(value: any, typeHint: ResolutionTypeHint): any { + if (typeof value !== 'object' || value == null) { return value; } + Object.defineProperty(value, RESOLUTION_TYPEHINT_SYM, { + value: typeHint, + configurable: true, + }); + return value; +} + +/** + * Return the type hint from the given value + * + * If the value is not a resolved value (i.e, the result of resolving a token), + * `undefined` will be returned. + * + * These type hints are used for correct JSON-ification of intrinsic values. + */ +export function resolvedTypeHint(value: any): ResolutionTypeHint | undefined { + if (typeof value !== 'object' || value == null) { return undefined; } + return value[RESOLUTION_TYPEHINT_SYM]; +} \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/private/token-map.ts b/packages/@aws-cdk/core/lib/private/token-map.ts index 2523037724ef0..1a5b0e1f29547 100644 --- a/packages/@aws-cdk/core/lib/private/token-map.ts +++ b/packages/@aws-cdk/core/lib/private/token-map.ts @@ -1,6 +1,6 @@ import { IResolvable } from '../resolvable'; import { TokenizedStringFragments } from '../string-fragments'; -import { Token } from '../token'; +import { isResolvableObject, Token } from '../token'; import { BEGIN_LIST_TOKEN_MARKER, BEGIN_STRING_TOKEN_MARKER, createTokenDouble, END_TOKEN_MARKER, extractTokenDouble, TokenString, VALID_KEY_CHARS, @@ -104,6 +104,7 @@ export class TokenMap { * Lookup a token from an encoded value */ public tokenFromEncoding(x: any): IResolvable | undefined { + if (isResolvableObject(x)) { return x; } if (typeof x === 'string') { return this.lookupString(x); } if (Array.isArray(x)) { return this.lookupList(x); } if (Token.isUnresolved(x)) { return x; } diff --git a/packages/@aws-cdk/core/lib/resolvable.ts b/packages/@aws-cdk/core/lib/resolvable.ts index 2ddbd544ffbbb..9004cd111bb33 100644 --- a/packages/@aws-cdk/core/lib/resolvable.ts +++ b/packages/@aws-cdk/core/lib/resolvable.ts @@ -20,7 +20,7 @@ export interface IResolveContext { /** * Resolve an inner object */ - resolve(x: any): any; + resolve(x: any, options?: ResolveChangeContextOptions): any; /** * Use this postprocessor after the entire token structure has been resolved @@ -28,6 +28,18 @@ export interface IResolveContext { registerPostProcessor(postProcessor: IPostProcessor): void; } +/** + * Options that can be changed while doing a recursive resolve + */ +export interface ResolveChangeContextOptions { + /** + * Change the 'allowIntrinsicKeys' option + * + * @default - Unchanged + */ + readonly allowIntrinsicKeys?: boolean; +} + /** * Interface for values that can be resolvable later * diff --git a/packages/@aws-cdk/core/test/cloudformation-json.test.ts b/packages/@aws-cdk/core/test/cloudformation-json.test.ts index e9d850eb178a2..cb96020e04904 100644 --- a/packages/@aws-cdk/core/test/cloudformation-json.test.ts +++ b/packages/@aws-cdk/core/test/cloudformation-json.test.ts @@ -1,12 +1,36 @@ -import { nodeunitShim, Test } from 'nodeunit-shim'; -import { App, CfnOutput, Fn, Lazy, Stack, Token } from '../lib'; +import { App, Aws, CfnOutput, Fn, IPostProcessor, IResolvable, IResolveContext, Lazy, Stack, Token } from '../lib'; import { Intrinsic } from '../lib/private/intrinsic'; import { evaluateCFN } from './evaluate-cfn'; -nodeunitShim({ - 'string tokens can be JSONified and JSONification can be reversed'(test: Test) { - const stack = new Stack(); +let app: App; +let stack: Stack; +beforeEach(() => { + app = new App(); + stack = new Stack(app, 'Stack'); +}); + +test('JSONification of literals looks like JSON.stringify', () => { + const structure = { + undefinedProp: undefined, + nestedObject: { + prop1: undefined, + prop2: 'abc', + prop3: 42, + prop4: [1, 2, 3], + }, + }; + + expect(stack.resolve(stack.toJsonString(structure))).toEqual(JSON.stringify(structure)); + expect(stack.resolve(stack.toJsonString(structure, 2))).toEqual(JSON.stringify(structure, undefined, 2)); +}); + +test('JSONification of undefined leads to undefined', () => { + expect(stack.resolve(stack.toJsonString(undefined))).toEqual(undefined); +}); + +describe('tokens that return literals', () => { + test('string tokens can be JSONified and JSONification can be reversed', () => { for (const token of tokensThatResolveTo('woof woof')) { // GIVEN const fido = { name: 'Fido', speaks: token }; @@ -15,15 +39,11 @@ nodeunitShim({ const resolved = stack.resolve(stack.toJsonString(fido)); // THEN - test.deepEqual(evaluateCFN(resolved), '{"name":"Fido","speaks":"woof woof"}'); + expect(evaluateCFN(resolved)).toEqual('{"name":"Fido","speaks":"woof woof"}'); } + }); - test.done(); - }, - - 'string tokens can be embedded while being JSONified'(test: Test) { - const stack = new Stack(); - + test('string tokens can be embedded while being JSONified', () => { for (const token of tokensThatResolveTo('woof woof')) { // GIVEN const fido = { name: 'Fido', speaks: `deep ${token}` }; @@ -32,57 +52,104 @@ nodeunitShim({ const resolved = stack.resolve(stack.toJsonString(fido)); // THEN - test.deepEqual(evaluateCFN(resolved), '{"name":"Fido","speaks":"deep woof woof"}'); + expect(evaluateCFN(resolved)).toEqual('{"name":"Fido","speaks":"deep woof woof"}'); } + }); - test.done(); - }, - - 'constant string has correct amount of quotes applied'(test: Test) { - const stack = new Stack(); - + test('constant string has correct amount of quotes applied', () => { const inputString = 'Hello, "world"'; // WHEN const resolved = stack.resolve(stack.toJsonString(inputString)); // THEN - test.deepEqual(evaluateCFN(resolved), JSON.stringify(inputString)); - - test.done(); - }, + expect(evaluateCFN(resolved)).toEqual(JSON.stringify(inputString)); + }); - 'integer Tokens behave correctly in stringification and JSONification'(test: Test) { + test('integer Tokens behave correctly in stringification and JSONification', () => { // GIVEN - const stack = new Stack(); const num = new Intrinsic(1); const embedded = `the number is ${num}`; // WHEN - test.equal(evaluateCFN(stack.resolve(embedded)), 'the number is 1'); - test.equal(evaluateCFN(stack.resolve(stack.toJsonString({ embedded }))), '{"embedded":"the number is 1"}'); - test.equal(evaluateCFN(stack.resolve(stack.toJsonString({ num }))), '{"num":1}'); + expect(evaluateCFN(stack.resolve(embedded))).toEqual('the number is 1'); + expect(evaluateCFN(stack.resolve(stack.toJsonString({ embedded })))).toEqual('{"embedded":"the number is 1"}'); + expect(evaluateCFN(stack.resolve(stack.toJsonString({ num })))).toEqual('{"num":1}'); + }); + + test('String-encoded lazies do not have quotes applied if they return objects', () => { + // This is unfortunately crazy behavior, but we have some clients already taking a + // dependency on the fact that `Lazy.stringValue({ produce: () => [...some list...] })` + // does not apply quotes but just renders the list. + + // GIVEN + const someList = Lazy.stringValue({ produce: () => [1, 2, 3] as any }); + + // WHEN + expect(evaluateCFN(stack.resolve(stack.toJsonString({ someList })))).toEqual('{"someList":[1,2,3]}'); + }); + + test('Literal-resolving List Tokens do not have quotes applied', () => { + // GIVEN + const someList = Token.asList([1, 2, 3]); + + // WHEN + expect(evaluateCFN(stack.resolve(stack.toJsonString({ someList })))).toEqual('{"someList":[1,2,3]}'); + }); + + test('Intrinsic-resolving List Tokens do not have quotes applied', () => { + // GIVEN + const someList = Token.asList(new Intrinsic({ Ref: 'Thing' })); + + // WHEN + expect(stack.resolve(stack.toJsonString({ someList }))).toEqual({ + 'Fn::Join': ['', ['{"someList":', { Ref: 'Thing' }, '}']], + }); + }); - test.done(); - }, - 'tokens in strings survive additional TokenJSON.stringification()'(test: Test) { + test('tokens in strings survive additional TokenJSON.stringification()', () => { // GIVEN - const stack = new Stack(); for (const token of tokensThatResolveTo('pong!')) { // WHEN const stringified = stack.toJsonString(`ping? ${token}`); // THEN - test.equal(evaluateCFN(stack.resolve(stringified)), '"ping? pong!"'); + expect(evaluateCFN(stack.resolve(stringified))).toEqual('"ping? pong!"'); } + }); + + test('Doubly nested strings evaluate correctly in JSON context', () => { + // WHEN + const fidoSays = Lazy.stringValue({ produce: () => 'woof' }); + + // WHEN + const resolved = stack.resolve(stack.toJsonString({ + information: `Did you know that Fido says: ${fidoSays}`, + })); + + // THEN + expect(evaluateCFN(resolved)).toEqual('{"information":"Did you know that Fido says: woof"}'); + }); + + test('Quoted strings in embedded JSON context are escaped', () => { + // GIVEN + const fidoSays = Lazy.stringValue({ produce: () => '"woof"' }); - test.done(); - }, + // WHEN + const resolved = stack.resolve(stack.toJsonString({ + information: `Did you know that Fido says: ${fidoSays}`, + })); + + // THEN + expect(evaluateCFN(resolved)).toEqual('{"information":"Did you know that Fido says: \\"woof\\""}'); + }); - 'intrinsic Tokens embed correctly in JSONification'(test: Test) { +}); + +describe('tokens returning CloudFormation intrinsics', () => { + test('intrinsic Tokens embed correctly in JSONification', () => { // GIVEN - const stack = new Stack(); const bucketName = new Intrinsic({ Ref: 'MyBucket' }); // WHEN @@ -90,13 +157,10 @@ nodeunitShim({ // THEN const context = { MyBucket: 'TheName' }; - test.equal(evaluateCFN(resolved, context), '{"theBucket":"TheName"}'); - - test.done(); - }, + expect(evaluateCFN(resolved, context)).toEqual('{"theBucket":"TheName"}'); + }); - 'fake intrinsics are serialized to objects'(test: Test) { - const stack = new Stack(); + test('fake intrinsics are serialized to objects', () => { const fakeIntrinsics = new Intrinsic({ a: { 'Fn::GetArtifactAtt': { @@ -112,16 +176,13 @@ nodeunitShim({ }); const stringified = stack.toJsonString(fakeIntrinsics); - test.equal(evaluateCFN(stack.resolve(stringified)), + expect(evaluateCFN(stack.resolve(stringified))).toEqual( '{"a":{"Fn::GetArtifactAtt":{"key":"val"}},"b":{"Fn::GetParam":["val1","val2"]}}'); + }); - test.done(); - }, - - 'embedded string literals in intrinsics are escaped when calling TokenJSON.stringify()'(test: Test) { + test('embedded string literals in intrinsics are escaped when calling TokenJSON.stringify()', () => { // GIVEN - const stack = new Stack(); - const token = Fn.join('', ['Hello', 'This\nIs', 'Very "cool"']); + const token = Fn.join('', ['Hello ', Token.asString({ Ref: 'Planet' }), ', this\nIs', 'Very "cool"']); // WHEN const resolved = stack.resolve(stack.toJsonString({ @@ -130,15 +191,42 @@ nodeunitShim({ })); // THEN - const expected = '{"literal":"I can also \\"contain\\" quotes","token":"HelloThis\\nIsVery \\"cool\\""}'; - test.equal(evaluateCFN(resolved), expected); + const context = { Planet: 'World' }; + const expected = '{"literal":"I can also \\"contain\\" quotes","token":"Hello World, this\\nIsVery \\"cool\\""}'; + expect(evaluateCFN(resolved, context)).toEqual(expected); + }); - test.done(); - }, + test('embedded string literals are escaped in Fn.sub (implicit references)', () => { + // GIVEN + const token = Fn.sub('I am in account "${AWS::AccountId}"'); - 'Tokens in Tokens are handled correctly'(test: Test) { + // WHEN + const resolved = stack.resolve(stack.toJsonString({ token })); + + // THEN + const context = { 'AWS::AccountId': '1234' }; + const expected = '{"token":"I am in account \\"1234\\""}'; + expect(evaluateCFN(resolved, context)).toEqual(expected); + }); + + test('embedded string literals are escaped in Fn.sub (explicit references)', () => { + // GIVEN + const token = Fn.sub('I am in account "${Acct}", also wanted to say: ${Also}', { + Acct: Aws.ACCOUNT_ID, + Also: '"hello world"', + }); + + // WHEN + const resolved = stack.resolve(stack.toJsonString({ token })); + + // THEN + const context = { 'AWS::AccountId': '1234' }; + const expected = '{"token":"I am in account \\"1234\\", also wanted to say: \\"hello world\\""}'; + expect(evaluateCFN(resolved, context)).toEqual(expected); + }); + + test('Tokens in Tokens are handled correctly', () => { // GIVEN - const stack = new Stack(); const bucketName = new Intrinsic({ Ref: 'MyBucket' }); const combinedName = Fn.join('', ['The bucket name is ', bucketName.toString()]); @@ -147,14 +235,25 @@ nodeunitShim({ // THEN const context = { MyBucket: 'TheName' }; - test.equal(evaluateCFN(resolved, context), '{"theBucket":"The bucket name is TheName"}'); + expect(evaluateCFN(resolved, context)).toEqual('{"theBucket":"The bucket name is TheName"}'); + }); + + test('Intrinsics in postprocessors are handled correctly', () => { + // GIVEN + const bucketName = new Intrinsic({ Ref: 'MyBucket' }); + const combinedName = new DummyPostProcessor(['this', 'is', bucketName]); + + // WHEN + const resolved = stack.resolve(stack.toJsonString({ theBucket: combinedName })); - test.done(); - }, + // THEN + expect(resolved).toEqual({ + 'Fn::Join': ['', ['{"theBucket":["this","is","', { Ref: 'MyBucket' }, '"]}']], + }); + }); - 'Doubly nested strings evaluate correctly in JSON context'(test: Test) { + test('Doubly nested strings evaluate correctly in JSON context', () => { // WHEN - const stack = new Stack(); const fidoSays = Lazy.string({ produce: () => 'woof' }); // WHEN @@ -163,14 +262,11 @@ nodeunitShim({ })); // THEN - test.deepEqual(evaluateCFN(resolved), '{"information":"Did you know that Fido says: woof"}'); + expect(evaluateCFN(resolved)).toEqual('{"information":"Did you know that Fido says: woof"}'); + }); - test.done(); - }, - - 'Doubly nested intrinsics evaluate correctly in JSON context'(test: Test) { + test('Doubly nested intrinsics evaluate correctly in JSON context', () => { // GIVEN - const stack = new Stack(); const fidoSays = Lazy.any({ produce: () => ({ Ref: 'Something' }) }); // WHEN @@ -180,14 +276,10 @@ nodeunitShim({ // THEN const context = { Something: 'woof woof' }; - test.deepEqual(evaluateCFN(resolved, context), '{"information":"Did you know that Fido says: woof woof"}'); - - test.done(); - }, + expect(evaluateCFN(resolved, context)).toEqual('{"information":"Did you know that Fido says: woof woof"}'); + }); - 'Quoted strings in embedded JSON context are escaped'(test: Test) { - // GIVEN - const stack = new Stack(); + test('Nested strings are quoted correctly', () => { const fidoSays = Lazy.string({ produce: () => '"woof"' }); // WHEN @@ -196,14 +288,11 @@ nodeunitShim({ })); // THEN - test.deepEqual(evaluateCFN(resolved), '{"information":"Did you know that Fido says: \\"woof\\""}'); + expect(evaluateCFN(resolved)).toEqual('{"information":"Did you know that Fido says: \\"woof\\""}'); + }); - test.done(); - }, - - 'cross-stack references are also properly converted by toJsonString()'(test: Test) { + test('cross-stack references are also properly converted by toJsonString()', () => { // GIVEN - const app = new App(); const stack1 = new Stack(app, 'Stack1'); const stack2 = new Stack(app, 'Stack2'); @@ -217,7 +306,7 @@ nodeunitShim({ // THEN const asm = app.synth(); - test.deepEqual(asm.getStackByName('Stack2').template, { + expect(asm.getStackByName('Stack2').template).toEqual({ Outputs: { Stack1Id: { Value: { @@ -232,11 +321,40 @@ nodeunitShim({ }, }, }); + }); - test.done(); - }, + test('Intrinsics can occur in key position', () => { + // GIVEN + const bucketName = Token.asString({ Ref: 'MyBucket' }); - 'Every Token used inside a JSONified string is given an opportunity to be uncached'(test: Test) { + // WHEN + const resolved = stack.resolve(stack.toJsonString({ + [bucketName]: 'Is Cool', + [`${bucketName} Is`]: 'Cool', + })); + + // THEN + const context = { MyBucket: 'Harry' }; + expect(evaluateCFN(resolved, context)).toEqual('{"Harry":"Is Cool","Harry Is":"Cool"}'); + }); + + test('toJsonString() can be used recursively', () => { + // GIVEN + const bucketName = Token.asString({ Ref: 'MyBucket' }); + + // WHEN + const embeddedJson = stack.toJsonString({ message: `the bucket name is ${bucketName}` }); + const outerJson = stack.toJsonString({ embeddedJson }); + + // THEN + const evaluatedJson = evaluateCFN(stack.resolve(outerJson), { + MyBucket: 'Bucky', + }); + expect(evaluatedJson).toEqual('{"embeddedJson":"{\\"message\\":\\"the bucket name is Bucky\\"}"}'); + expect(JSON.parse(JSON.parse(evaluatedJson).embeddedJson).message).toEqual('the bucket name is Bucky'); + }); + + test('Every Token used inside a JSONified string is given an opportunity to be uncached', () => { // Check that tokens aren't accidentally fully resolved by the first invocation/resolution // of toJsonString(). On every evaluation, Tokens referenced inside the structure should be // given a chance to be either cached or uncached. @@ -244,10 +362,6 @@ nodeunitShim({ // (NOTE: This does not check whether the implementation of toJsonString() itself is cached or // not; that depends on aws/aws-cdk#11224 and should be done in a different PR). - // GIVEN - const app = new App(); - const stack = new Stack(app, 'Stack1'); - // WHEN let counter = 0; const counterString = Token.asString({ resolve: () => `${++counter}` }); @@ -256,11 +370,29 @@ nodeunitShim({ // THEN expect(stack.resolve(jsonString)).toEqual('{"counterString":"1"}'); expect(stack.resolve(jsonString)).toEqual('{"counterString":"2"}'); + }); +}); + +test('JSON strings nested inside JSON strings have correct quoting', () => { + // GIVEN + const payload = stack.toJsonString({ + message: Fn.sub('I am in account "${AWS::AccountId}"'), + }); + + // WHEN + const resolved = stack.resolve(stack.toJsonString({ payload })); + + // THEN + const context = { 'AWS::AccountId': '1234' }; + const expected = '{"payload":"{\\"message\\":\\"I am in account \\\\\\"1234\\\\\\"\\"}"}'; + const evaluated = evaluateCFN(resolved, context); + expect(evaluated).toEqual(expected); - test.done(); - }, + // Is this even correct? Let's ask JavaScript because I have trouble reading this many backslashes. + expect(JSON.parse(JSON.parse(evaluated).payload).message).toEqual('I am in account "1234"'); }); + /** * Return two Tokens, one of which evaluates to a Token directly, one which evaluates to it lazily */ @@ -270,3 +402,20 @@ function tokensThatResolveTo(value: any): Token[] { Lazy.any({ produce: () => value }), ]; } + +class DummyPostProcessor implements IResolvable, IPostProcessor { + public readonly creationStack: string[]; + + constructor(private readonly value: any) { + this.creationStack = ['test']; + } + + public resolve(context: IResolveContext) { + context.registerPostProcessor(this); + return context.resolve(this.value); + } + + public postProcess(o: any, _context: IResolveContext): any { + return o; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/core/test/evaluate-cfn.ts b/packages/@aws-cdk/core/test/evaluate-cfn.ts index 6d60949cc3193..af07209c7e5a7 100644 --- a/packages/@aws-cdk/core/test/evaluate-cfn.ts +++ b/packages/@aws-cdk/core/test/evaluate-cfn.ts @@ -42,16 +42,8 @@ export function evaluateCFN(object: any, context: {[key: string]: string} = {}): return context[key]; }, - 'Fn::Sub'(argument: string | [string, Record]) { - let template; - let placeholders: Record; - if (Array.isArray(argument)) { - template = argument[0]; - placeholders = evaluate(argument[1]); - } else { - template = argument; - placeholders = context; - } + 'Fn::Sub'(template: string, explicitPlaceholders?: Record) { + const placeholders = explicitPlaceholders ? evaluate(explicitPlaceholders) : context; if (typeof template !== 'string') { throw new Error('The first argument to {Fn::Sub} must be a string literal (cannot be the result of an expression)'); @@ -79,7 +71,7 @@ export function evaluateCFN(object: any, context: {[key: string]: string} = {}): const ret: {[key: string]: any} = {}; for (const key of Object.keys(obj)) { - ret[key] = evaluateCFN(obj[key]); + ret[key] = evaluate(obj[key]); } return ret; } From e635dac49c66773cd82fd260e6747469728ffbc1 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Wed, 10 Mar 2021 08:33:42 -0800 Subject: [PATCH 04/41] chore: change the parameter used for 'find' in link-all.sh (#13510) The parameter currently used for `find` in `link-all.sh`, `-perm /111`, fails on my Mac. Switch to using `-perm +111`, which works fine, and that's also what JSII uses in [its `link-all.sh` script](https://github.com/aws/jsii/blob/f8bde4a01bf7c707c87ab00748eeeb7632e7c820/scripts/link-all.sh#L26-L26). ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- link-all.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/link-all.sh b/link-all.sh index 6df7b1838ad56..03a94f7e12a05 100755 --- a/link-all.sh +++ b/link-all.sh @@ -26,7 +26,7 @@ for module in ${modules}; do # according to spec (we look in the bin/ directory instead of the { "scripts" # } entry in package.json but it's quite a bit easier. if [[ -d $module/bin ]]; then - for script in $(find $module/bin -perm /111); do + for script in $(find $module/bin -perm +111); do echo "${script} => node_modules/.bin/$(basename $script)" ln -fs ${script} node_modules/.bin done From 8d592ea89c0eda19329d5a31517522ec02ceb874 Mon Sep 17 00:00:00 2001 From: Darren Date: Wed, 10 Mar 2021 11:29:38 -0600 Subject: [PATCH 05/41] fix(iam): policy statement tries to validate tokens (#13493) Looking for guidance on error messaging and/or docs to update Fixes #13479 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-iam/lib/policy-statement.ts | 3 ++- .../@aws-cdk/aws-iam/test/policy-document.test.ts | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-iam/lib/policy-statement.ts b/packages/@aws-cdk/aws-iam/lib/policy-statement.ts index ce817a58e508e..78a588760c9d6 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy-statement.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy-statement.ts @@ -64,7 +64,8 @@ export class PolicyStatement { constructor(props: PolicyStatementProps = {}) { // Validate actions for (const action of [...props.actions || [], ...props.notActions || []]) { - if (!/^(\*|[a-zA-Z0-9-]+:[a-zA-Z0-9*]+)$/.test(action)) { + + if (!/^(\*|[a-zA-Z0-9-]+:[a-zA-Z0-9*]+)$/.test(action) && !cdk.Token.isUnresolved(action)) { throw new Error(`Action '${action}' is invalid. An action string consists of a service namespace, a colon, and the name of an action. Action names can include wildcards.`); } } diff --git a/packages/@aws-cdk/aws-iam/test/policy-document.test.ts b/packages/@aws-cdk/aws-iam/test/policy-document.test.ts index d8a9b1337c21c..bd3bd6fd31aa3 100644 --- a/packages/@aws-cdk/aws-iam/test/policy-document.test.ts +++ b/packages/@aws-cdk/aws-iam/test/policy-document.test.ts @@ -102,6 +102,19 @@ describe('IAM policy document', () => { }).toThrow(/Action 'in:val:id' is invalid/); }); + // https://github.com/aws/aws-cdk/issues/13479 + test('Does not validate unresolved tokens', () => { + const stack = new Stack(); + const perm = new PolicyStatement({ + actions: [`${Lazy.string({ produce: () => 'sqs:sendMessage' })}`], + }); + + expect(stack.resolve(perm.toStatementJson())).toEqual({ + Effect: 'Allow', + Action: 'sqs:sendMessage', + }); + }); + test('Cannot combine Resources and NotResources', () => { expect(() => { new PolicyStatement({ From 78b265cba23b438cb53655dee40b286852b9b3b7 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 10 Mar 2021 19:04:52 +0100 Subject: [PATCH 06/41] chore(aws-cdk-lib): change namespaces/package names in line with RFC 6 (#13494) Changes: * .NET: Namespace changed from `Amazon.CDK.Lib` -> `Amazon.CDK` (so `Stack` has the same FQN, same namespace as in Monocdk) * Java: Package name changed from `software.amazon.awscdk.lib` -> `software.amazon.awscdk.core` (so `Stack` has the same FQN, same namespace as in Monocdk) * Java: Changed artifact ID to match what's written in [RFC 6] * Python: Changed dist name to match what's written in [RFC 6] * Python: Change namespace to `aws_cdk` instead of `aws_cdk_lib` for minimal interference. Still need to test whether it's okay to change this to `aws_cdk.core` (like for Java) so `Stack` will keep the same FQN. Monocdk does something different for Python. [RFC 6]: https://github.com/aws/aws-cdk-rfcs/blob/master/text/0006-monolothic-packaging.md ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/aws-cdk-lib/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index bd2ce09408acc..5380837857d8f 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -50,22 +50,22 @@ "outdir": "dist", "targets": { "dotnet": { - "namespace": "Amazon.CDK.Lib", + "namespace": "Amazon.CDK", "packageId": "Amazon.CDK.Lib", "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", "versionSuffix": "-devpreview" }, "java": { - "package": "software.amazon.awscdk.lib", + "package": "software.amazon.awscdk.core", "maven": { "groupId": "software.amazon.awscdk", - "artifactId": "lib", + "artifactId": "aws-cdk-lib", "versionSuffix": ".DEVPREVIEW" } }, "python": { - "distName": "aws-cdk.lib", - "module": "aws_cdk.lib" + "distName": "aws-cdk-lib", + "module": "aws_cdk" } }, "projectReferences": false From cc608d055ffefb798ad6378ab07f36cb241897da Mon Sep 17 00:00:00 2001 From: Ayush Goyal Date: Thu, 11 Mar 2021 00:08:21 +0530 Subject: [PATCH 07/41] feat(stepfunctions-tasks): Support calling ApiGateway REST and HTTP APIs (#13033) feat(stepfunctions-tasks): Support calling APIGW REST and HTTP APIs Taking ownership of the original PR #11565 by @Sumeet-Badyal API as per documentation here: https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html closes #11566 closes #11565 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-stepfunctions-tasks/README.md | 44 ++ .../lib/apigateway/base-types.ts | 79 ++++ .../lib/apigateway/base.ts | 69 +++ .../lib/apigateway/call-http-api.ts | 62 +++ .../lib/apigateway/call-rest-api.ts | 51 +++ .../lib/apigateway/index.ts | 3 + .../aws-stepfunctions-tasks/lib/index.ts | 1 + .../aws-stepfunctions-tasks/package.json | 6 + .../test/apigateway/call-http-api.test.ts | 145 +++++++ .../test/apigateway/call-rest-api.test.ts | 151 +++++++ .../integ.call-http-api.expected.json | 263 ++++++++++++ .../test/apigateway/integ.call-http-api.ts | 48 +++ .../integ.call-rest-api.expected.json | 394 ++++++++++++++++++ .../test/apigateway/integ.call-rest-api.ts | 43 ++ 14 files changed, 1359 insertions(+) create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/base-types.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/base.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-http-api.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-rest-api.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/index.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/call-http-api.test.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/call-rest-api.test.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-http-api.expected.json create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-http-api.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-rest-api.expected.json create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-rest-api.ts diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md index 69d97936aabcb..8d024db58e552 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md @@ -28,6 +28,9 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw - [ResultPath](#resultpath) - [Parameters](#task-parameters-from-the-state-json) - [Evaluate Expression](#evaluate-expression) +- [API Gateway](#api-gateway) + - [Call REST API Endpoint](#call-rest-api-endpoint) + - [Call HTTP API Endpoint](#call-http-api-endpoint) - [Athena](#athena) - [StartQueryExecution](#startQueryExecution) - [GetQueryExecution](#getQueryExecution) @@ -217,6 +220,47 @@ The `EvaluateExpression` supports a `runtime` prop to specify the Lambda runtime to use to evaluate the expression. Currently, only runtimes of the Node.js family are supported. +## API Gateway + +Step Functions supports [API Gateway](https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html) through the service integration pattern. + +HTTP APIs are designed for low-latency, cost-effective integrations with AWS services, including AWS Lambda, and HTTP endpoints. +HTTP APIs support OIDC and OAuth 2.0 authorization, and come with built-in support for CORS and automatic deployments. +Previous-generation REST APIs currently offer more features. More details can be found [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html). + +### Call REST API Endpoint + +The `CallApiGatewayRestApiEndpoint` calls the REST API endpoint. + +```ts +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as tasks from `@aws-cdk/aws-stepfunctions-tasks`; + +const restApi = new apigateway.RestApi(stack, 'MyRestApi'); + +const invokeTask = new tasks.CallApiGatewayRestApiEndpoint(stack, 'Call REST API', { + api: restApi, + stageName: 'prod', + method: HttpMethod.GET, +}); +``` + +### Call HTTP API Endpoint + +The `CallApiGatewayHttpApiEndpoint` calls the HTTP API endpoint. + +```ts +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as tasks from `@aws-cdk/aws-stepfunctions-tasks`; + +const httpApi = new apigatewayv2.HttpApi(stack, 'MyHttpApi'); + +const invokeTask = new tasks.CallApiGatewayHttpApiEndpoint(stack, 'Call HTTP API', { + api: httpApi, + method: HttpMethod.GET, +}); +``` + ## Athena Step Functions supports [Athena](https://docs.aws.amazon.com/step-functions/latest/dg/connect-athena.html) through the service integration pattern. diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/base-types.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/base-types.ts new file mode 100644 index 0000000000000..64c649063e57c --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/base-types.ts @@ -0,0 +1,79 @@ +import * as sfn from '@aws-cdk/aws-stepfunctions'; + +/** Http Methods that API Gateway supports */ +export enum HttpMethod { + /** Retreive data from a server at the specified resource */ + GET = 'GET', + + /** Send data to the API endpoint to create or udpate a resource */ + POST = 'POST', + + /** Send data to the API endpoint to update or create a resource */ + PUT = 'PUT', + + /** Delete the resource at the specified endpoint */ + DELETE = 'DELETE', + + /** Apply partial modifications to the resource */ + PATCH = 'PATCH', + + /** Retreive data from a server at the specified resource without the response body */ + HEAD = 'HEAD', + + /** Return data describing what other methods and operations the server supports */ + OPTIONS = 'OPTIONS' +} + +/** + * The authentication method used to call the endpoint + */ +export enum AuthType { + /** Call the API direclty with no authorization method */ + NO_AUTH = 'NO_AUTH', + + /** Use the IAM role associated with the current state machine for authorization */ + IAM_ROLE = 'IAM_ROLE', + + /** Use the resource policy of the API for authorization */ + RESOURCE_POLICY = 'RESOURCE_POLICY', +} + +/** + * Base CallApiGatewayEdnpoint Task Props + */ +export interface CallApiGatewayEndpointBaseProps extends sfn.TaskStateBaseProps { + /** + * Http method for the API + */ + readonly method: HttpMethod; + + /** + * HTTP request information that does not relate to contents of the request + * @default - No headers + */ + readonly headers?: sfn.TaskInput; + + /** + * Path parameters appended after API endpoint + * @default - No path + */ + readonly apiPath?: string; + + /** + * Query strings attatched to end of request + * @default - No query parameters + */ + readonly queryParameters?: sfn.TaskInput; + + /** + * HTTP Request body + * @default - No request body + */ + readonly requestBody?: sfn.TaskInput; + + /** + * Authentication methods + * @default AuthType.NO_AUTH + */ + readonly authType?: AuthType; +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/base.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/base.ts new file mode 100644 index 0000000000000..edce3aa0f627c --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/base.ts @@ -0,0 +1,69 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { Construct } from 'constructs'; +import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; +import { AuthType, CallApiGatewayEndpointBaseProps } from './base-types'; + +/** + * Base CallApiGatewayEndpoint Task + * @internal + */ +export abstract class CallApiGatewayEndpointBase extends sfn.TaskStateBase { + private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ + sfn.IntegrationPattern.REQUEST_RESPONSE, + sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, + ]; + + private readonly baseProps: CallApiGatewayEndpointBaseProps; + private readonly integrationPattern: sfn.IntegrationPattern; + + protected abstract readonly apiEndpoint: string; + protected abstract readonly arnForExecuteApi: string; + protected abstract readonly stageName?: string; + + constructor(scope: Construct, id: string, props: CallApiGatewayEndpointBaseProps) { + super(scope, id, props); + + this.baseProps = props; + this.integrationPattern = props.integrationPattern ?? sfn.IntegrationPattern.REQUEST_RESPONSE; + validatePatternSupported(this.integrationPattern, CallApiGatewayEndpointBase.SUPPORTED_INTEGRATION_PATTERNS); + + if (this.integrationPattern === sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN) { + if (!sfn.FieldUtils.containsTaskToken(this.baseProps.headers)) { + throw new Error('Task Token is required in `headers` for WAIT_FOR_TASK_TOKEN pattern. Use JsonPath.taskToken to set the token.'); + } + } + } + + /** + * @internal + */ + protected _renderTask() { + return { + Resource: integrationResourceArn('apigateway', 'invoke', this.integrationPattern), + Parameters: sfn.FieldUtils.renderObject({ + ApiEndpoint: this.apiEndpoint, + Method: this.baseProps.method, + Headers: this.baseProps.headers?.value, + Stage: this.stageName, + Path: this.baseProps.apiPath, + QueryParameters: this.baseProps.queryParameters?.value, + RequestBody: this.baseProps.requestBody?.value, + AuthType: this.baseProps.authType ? this.baseProps.authType : 'NO_AUTH', + }), + }; + } + + protected createPolicyStatements(): iam.PolicyStatement[] { + if (this.baseProps.authType === AuthType.NO_AUTH) { + return []; + } + + return [ + new iam.PolicyStatement({ + resources: [this.arnForExecuteApi], + actions: ['execute-api:Invoke'], + }), + ]; + } +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-http-api.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-http-api.ts new file mode 100644 index 0000000000000..e06e46c2580b0 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-http-api.ts @@ -0,0 +1,62 @@ +import * as apigatewayv2 from '@aws-cdk/aws-apigatewayv2'; +import * as iam from '@aws-cdk/aws-iam'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CallApiGatewayEndpointBase } from './base'; +import { CallApiGatewayEndpointBaseProps } from './base-types'; + +/** + * Properties for calling an HTTP API Endpoint + */ +export interface CallApiGatewayHttpApiEndpointProps extends CallApiGatewayEndpointBaseProps { + /** + * API to call + */ + readonly api: apigatewayv2.IHttpApi; + + /** + * Name of the stage where the API is deployed to in API Gateway + * @default '$default' + */ + readonly stageName?: string; +} + +/** + * Call HTTP API endpoint as a Task + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html + */ +export class CallApiGatewayHttpApiEndpoint extends CallApiGatewayEndpointBase { + protected readonly taskMetrics?: sfn.TaskMetricsConfig | undefined; + protected readonly taskPolicies?: iam.PolicyStatement[] | undefined; + + protected readonly apiEndpoint: string; + protected readonly arnForExecuteApi: string; + protected readonly stageName?: string; + + constructor(scope: Construct, id: string, private readonly props: CallApiGatewayHttpApiEndpointProps) { + super(scope, id, props); + + this.apiEndpoint = this.getApiEndpoint(); + this.arnForExecuteApi = this.getArnForExecuteApi(); + + this.taskPolicies = this.createPolicyStatements(); + } + + private getApiEndpoint(): string { + const apiStack = cdk.Stack.of(this.props.api); + return `${this.props.api.apiId}.execute-api.${apiStack.region}.${apiStack.urlSuffix}`; + } + + private getArnForExecuteApi(): string { + const { api, stageName, method, apiPath } = this.props; + + return cdk.Stack.of(api).formatArn({ + service: 'execute-api', + resource: api.apiId, + sep: '/', + resourceName: `${stageName}/${method}${apiPath}`, + }); + } +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-rest-api.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-rest-api.ts new file mode 100644 index 0000000000000..0352777e9c06a --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-rest-api.ts @@ -0,0 +1,51 @@ +import * as apigateway from '@aws-cdk/aws-apigateway'; +import * as iam from '@aws-cdk/aws-iam'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CallApiGatewayEndpointBase } from './base'; +import { CallApiGatewayEndpointBaseProps } from './base-types'; + +/** + * Properties for calling an REST API Endpoint + */ +export interface CallApiGatewayRestApiEndpointProps extends CallApiGatewayEndpointBaseProps { + /** + * API to call + */ + readonly api: apigateway.IRestApi; + + /** + * Name of the stage where the API is deployed to in API Gateway + */ + readonly stageName: string; +} + +/** + * Call REST API endpoint as a Task + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html + */ +export class CallApiGatewayRestApiEndpoint extends CallApiGatewayEndpointBase { + protected readonly taskMetrics?: sfn.TaskMetricsConfig | undefined; + protected readonly taskPolicies?: iam.PolicyStatement[] | undefined; + + protected readonly apiEndpoint: string; + protected readonly arnForExecuteApi: string; + protected readonly stageName?: string; + + constructor(scope: Construct, id: string, private readonly props: CallApiGatewayRestApiEndpointProps) { + super(scope, id, props); + + this.apiEndpoint = this.getApiEndpoint(); + this.arnForExecuteApi = props.api.arnForExecuteApi(props.method, props.apiPath, props.stageName); + this.stageName = props.stageName; + + this.taskPolicies = this.createPolicyStatements(); + } + + private getApiEndpoint(): string { + const apiStack = cdk.Stack.of(this.props.api); + return `${this.props.api.restApiId}.execute-api.${apiStack.region}.${apiStack.urlSuffix}`; + } +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/index.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/index.ts new file mode 100644 index 0000000000000..3d82ca2e7d548 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/index.ts @@ -0,0 +1,3 @@ +export * from './base-types'; +export * from './call-rest-api'; +export * from './call-http-api'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts index 32e684f6d1adf..7b566bbbe4dad 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts @@ -45,3 +45,4 @@ export * from './athena/get-query-execution'; export * from './athena/get-query-results'; export * from './databrew/start-job-run'; export * from './eks/call'; +export * from './apigateway'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json index a6137f570b1a3..b18cd8fc7704c 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json @@ -72,6 +72,9 @@ "pkglint": "0.0.0" }, "dependencies": { + "@aws-cdk/aws-apigateway": "0.0.0", + "@aws-cdk/aws-apigatewayv2": "0.0.0", + "@aws-cdk/aws-apigatewayv2-integrations": "0.0.0", "@aws-cdk/aws-batch": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", @@ -95,6 +98,9 @@ }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { + "@aws-cdk/aws-apigateway": "0.0.0", + "@aws-cdk/aws-apigatewayv2": "0.0.0", + "@aws-cdk/aws-apigatewayv2-integrations": "0.0.0", "@aws-cdk/aws-batch": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/call-http-api.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/call-http-api.test.ts new file mode 100644 index 0000000000000..0e7a2cf616b9a --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/call-http-api.test.ts @@ -0,0 +1,145 @@ +import * as apigatewayv2 from '@aws-cdk/aws-apigatewayv2'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import { HttpMethod, CallApiGatewayHttpApiEndpoint } from '../../lib'; + +describe('CallApiGatewayHttpApiEndpoint', () => { + test('default', () => { + // GIVEN + const stack = new cdk.Stack(); + const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi'); + + // WHEN + const task = new CallApiGatewayHttpApiEndpoint(stack, 'Call', { + api: httpApi, + method: HttpMethod.GET, + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + End: true, + Parameters: { + ApiEndpoint: { + 'Fn::Join': [ + '', + [ + { + Ref: 'HttpApiF5A9A8A7', + }, + '.execute-api.', + { + Ref: 'AWS::Region', + }, + '.', + { + Ref: 'AWS::URLSuffix', + }, + ], + ], + }, + AuthType: 'NO_AUTH', + Method: 'GET', + }, + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::apigateway:invoke', + ], + ], + }, + }); + }); + + test('wait for task token', () => { + // GIVEN + const stack = new cdk.Stack(); + const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi'); + + // WHEN + const task = new CallApiGatewayHttpApiEndpoint(stack, 'Call', { + api: httpApi, + method: HttpMethod.GET, + integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, + headers: sfn.TaskInput.fromObject({ TaskToken: sfn.JsonPath.taskToken }), + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + End: true, + Parameters: { + ApiEndpoint: { + 'Fn::Join': [ + '', + [ + { + Ref: 'HttpApiF5A9A8A7', + }, + '.execute-api.', + { + Ref: 'AWS::Region', + }, + '.', + { + Ref: 'AWS::URLSuffix', + }, + ], + ], + }, + AuthType: 'NO_AUTH', + Headers: { + 'TaskToken.$': '$$.Task.Token', + }, + Method: 'GET', + }, + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::apigateway:invoke.waitForTaskToken', + ], + ], + }, + }); + }); + + test('wait for task token - missing token', () => { + // GIVEN + const stack = new cdk.Stack(); + const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi'); + + // THEN + expect(() => { + new CallApiGatewayHttpApiEndpoint(stack, 'Call', { + api: httpApi, + method: HttpMethod.GET, + integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, + }); + }).toThrow(/Task Token is required in `headers` for WAIT_FOR_TASK_TOKEN pattern. Use JsonPath.taskToken to set the token./); + }); + + test('unsupported integration pattern - RUN_JOB', () => { + // GIVEN + const stack = new cdk.Stack(); + const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi'); + + // THEN + expect(() => { + new CallApiGatewayHttpApiEndpoint(stack, 'Call', { + api: httpApi, + method: HttpMethod.GET, + integrationPattern: sfn.IntegrationPattern.RUN_JOB, + }); + }).toThrow(/Unsupported service integration pattern./); + }); +}); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/call-rest-api.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/call-rest-api.test.ts new file mode 100644 index 0000000000000..37a083fb2cc95 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/call-rest-api.test.ts @@ -0,0 +1,151 @@ +import * as apigateway from '@aws-cdk/aws-apigateway'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import { HttpMethod, CallApiGatewayRestApiEndpoint } from '../../lib'; + +describe('CallApiGatewayRestApiEndpoint', () => { + test('default', () => { + // GIVEN + const stack = new cdk.Stack(); + const restApi = new apigateway.RestApi(stack, 'RestApi'); + + // WHEN + const task = new CallApiGatewayRestApiEndpoint(stack, 'Call', { + api: restApi, + method: HttpMethod.GET, + stageName: 'dev', + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + End: true, + Parameters: { + ApiEndpoint: { + 'Fn::Join': [ + '', + [ + { + Ref: 'RestApi0C43BF4B', + }, + '.execute-api.', + { + Ref: 'AWS::Region', + }, + '.', + { + Ref: 'AWS::URLSuffix', + }, + ], + ], + }, + AuthType: 'NO_AUTH', + Method: 'GET', + Stage: 'dev', + }, + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::apigateway:invoke', + ], + ], + }, + }); + }); + + test('wait for task token', () => { + // GIVEN + const stack = new cdk.Stack(); + const restApi = new apigateway.RestApi(stack, 'RestApi'); + + // WHEN + const task = new CallApiGatewayRestApiEndpoint(stack, 'Call', { + api: restApi, + method: HttpMethod.GET, + stageName: 'dev', + integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, + headers: sfn.TaskInput.fromObject({ TaskToken: sfn.JsonPath.taskToken }), + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + End: true, + Parameters: { + ApiEndpoint: { + 'Fn::Join': [ + '', + [ + { + Ref: 'RestApi0C43BF4B', + }, + '.execute-api.', + { + Ref: 'AWS::Region', + }, + '.', + { + Ref: 'AWS::URLSuffix', + }, + ], + ], + }, + AuthType: 'NO_AUTH', + Headers: { + 'TaskToken.$': '$$.Task.Token', + }, + Method: 'GET', + Stage: 'dev', + }, + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::apigateway:invoke.waitForTaskToken', + ], + ], + }, + }); + }); + + test('wait for task token - missing token', () => { + // GIVEN + const stack = new cdk.Stack(); + const restApi = new apigateway.RestApi(stack, 'RestApi'); + + // THEN + expect(() => { + new CallApiGatewayRestApiEndpoint(stack, 'Call', { + api: restApi, + method: HttpMethod.GET, + stageName: 'dev', + integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, + }); + }).toThrow(/Task Token is required in `headers` for WAIT_FOR_TASK_TOKEN pattern. Use JsonPath.taskToken to set the token./); + }); + + test('unsupported integration pattern - RUN_JOB', () => { + // GIVEN + const stack = new cdk.Stack(); + const restApi = new apigateway.RestApi(stack, 'RestApi'); + + // THEN + expect(() => { + new CallApiGatewayRestApiEndpoint(stack, 'Call', { + api: restApi, + method: HttpMethod.GET, + stageName: 'dev', + integrationPattern: sfn.IntegrationPattern.RUN_JOB, + }); + }).toThrow(/Unsupported service integration pattern./); + }); +}); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-http-api.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-http-api.expected.json new file mode 100644 index 0000000000000..6afe44cfecda5 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-http-api.expected.json @@ -0,0 +1,263 @@ +{ + "Resources": { + "MyHttpApi8AEAAC21": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "MyHttpApi", + "ProtocolType": "HTTP" + } + }, + "MyHttpApiDefaultStageDCB9BC49": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyHttpApi8AEAAC21" + }, + "StageName": "$default", + "AutoDeploy": true + } + }, + "MyHttpApiANYCallHttpApiIntegMyHttpApiANY7E6F12A3Permission59116CA6": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "HelloHandler2E4FBA4D", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "MyHttpApi8AEAAC21" + }, + "/*/*/" + ] + ] + } + } + }, + "MyHttpApiANYHttpIntegration71abbf75d6f8e5ea93ec2120c0d78b754BBCECF5": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "MyHttpApi8AEAAC21" + }, + "IntegrationType": "AWS_PROXY", + "IntegrationUri": { + "Fn::GetAtt": [ + "HelloHandler2E4FBA4D", + "Arn" + ] + }, + "PayloadFormatVersion": "2.0" + } + }, + "MyHttpApiANYC3543576": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "MyHttpApi8AEAAC21" + }, + "RouteKey": "ANY /", + "AuthorizationScopes": [], + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "MyHttpApiANYHttpIntegration71abbf75d6f8e5ea93ec2120c0d78b754BBCECF5" + } + ] + ] + } + } + }, + "HelloHandlerServiceRole11EF7C63": { + "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" + ] + ] + } + ] + } + }, + "HelloHandler2E4FBA4D": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async function(event, context) { return { statusCode: 200, body: \"hello, world!\" }; };" + }, + "Role": { + "Fn::GetAtt": [ + "HelloHandlerServiceRole11EF7C63", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs10.x" + }, + "DependsOn": [ + "HelloHandlerServiceRole11EF7C63" + ] + }, + "StateMachineRoleB840431D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachineRoleDefaultPolicyDF1E6607": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "MyHttpApi8AEAAC21" + }, + "/undefined/GETundefined" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "StateMachineRoleDefaultPolicyDF1E6607", + "Roles": [ + { + "Ref": "StateMachineRoleB840431D" + } + ] + } + }, + "StateMachine2E01A3A5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"Call APIGW\",\"States\":{\"Call APIGW\":{\"End\":true,\"Type\":\"Task\",\"OutputPath\":\"$.ResponseBody\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::apigateway:invoke\",\"Parameters\":{\"ApiEndpoint\":\"", + { + "Ref": "MyHttpApi8AEAAC21" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "\",\"Method\":\"GET\",\"AuthType\":\"IAM_ROLE\"}}},\"TimeoutSeconds\":30}" + ] + ] + } + }, + "DependsOn": [ + "StateMachineRoleDefaultPolicyDF1E6607", + "StateMachineRoleB840431D" + ] + } + }, + "Outputs": { + "stateMachineArn": { + "Value": { + "Ref": "StateMachine2E01A3A5" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-http-api.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-http-api.ts new file mode 100644 index 0000000000000..4eb1f3b896e92 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-http-api.ts @@ -0,0 +1,48 @@ +import * as apigatewayv2 from '@aws-cdk/aws-apigatewayv2'; +import * as integrations from '@aws-cdk/aws-apigatewayv2-integrations'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import { AuthType, HttpMethod, CallApiGatewayHttpApiEndpoint } from '../../lib'; + +/* + * Stack verification steps: + * * aws stepfunctions start-execution --state-machine-arn : should return execution arn + * + * * aws stepfunctions describe-execution --execution-arn --query 'status': should return status as SUCCEEDED + * * aws stepfunctions describe-execution --execution-arn --query 'output': should return the string \"hello, world!\" + */ + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'CallHttpApiInteg'); +const httpApi = new apigatewayv2.HttpApi(stack, 'MyHttpApi'); + +const handler = new lambda.Function(stack, 'HelloHandler', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: new lambda.InlineCode('exports.handler = async function(event, context) { return { statusCode: 200, body: "hello, world!" }; };'), +}); +httpApi.addRoutes({ + path: '/', + integration: new integrations.LambdaProxyIntegration({ + handler, + }), +}); + +const callEndpointJob = new CallApiGatewayHttpApiEndpoint(stack, 'Call APIGW', { + api: httpApi, + method: HttpMethod.GET, + authType: AuthType.IAM_ROLE, + outputPath: sfn.JsonPath.stringAt('$.ResponseBody'), +}); + +const chain = sfn.Chain.start(callEndpointJob); + +const sm = new sfn.StateMachine(stack, 'StateMachine', { + definition: chain, + timeout: cdk.Duration.seconds(30), +}); + +new cdk.CfnOutput(stack, 'stateMachineArn', { + value: sm.stateMachineArn, +}); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-rest-api.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-rest-api.expected.json new file mode 100644 index 0000000000000..5970499935354 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-rest-api.expected.json @@ -0,0 +1,394 @@ +{ + "Resources": { + "MyRestApi2D1F47A9": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Name": "MyRestApi" + } + }, + "MyRestApiCloudWatchRoleD4042E8E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + ] + } + ] + } + }, + "MyRestApiAccount2FB6DB7A": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "MyRestApiCloudWatchRoleD4042E8E", + "Arn" + ] + } + }, + "DependsOn": [ + "MyRestApi2D1F47A9" + ] + }, + "MyRestApiDeploymentB555B582d61dc696e12272a0706c826196fa8d62": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyRestApi2D1F47A9" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "MyRestApiANY05143F93" + ] + }, + "MyRestApiDeploymentStageprodC33B8E5F": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "MyRestApi2D1F47A9" + }, + "DeploymentId": { + "Ref": "MyRestApiDeploymentB555B582d61dc696e12272a0706c826196fa8d62" + }, + "StageName": "prod" + } + }, + "MyRestApiANYApiPermissionCallRestApiIntegMyRestApiB570839CANY0C27C1E3": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Hello4A628BD4", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "MyRestApi2D1F47A9" + }, + "/", + { + "Ref": "MyRestApiDeploymentStageprodC33B8E5F" + }, + "/*/" + ] + ] + } + } + }, + "MyRestApiANYApiPermissionTestCallRestApiIntegMyRestApiB570839CANY379723EF": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Hello4A628BD4", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "MyRestApi2D1F47A9" + }, + "/test-invoke-stage/*/" + ] + ] + } + } + }, + "MyRestApiANY05143F93": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Fn::GetAtt": [ + "MyRestApi2D1F47A9", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "MyRestApi2D1F47A9" + }, + "AuthorizationType": "NONE", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "Hello4A628BD4", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + } + }, + "HelloServiceRole1E55EA16": { + "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" + ] + ] + } + ] + } + }, + "Hello4A628BD4": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async function(event, context) { return { statusCode: 200, body: \"hello, world!\" }; };" + }, + "Role": { + "Fn::GetAtt": [ + "HelloServiceRole1E55EA16", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs10.x" + }, + "DependsOn": [ + "HelloServiceRole1E55EA16" + ] + }, + "StateMachineRoleB840431D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachineRoleDefaultPolicyDF1E6607": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "MyRestApi2D1F47A9" + }, + "/prod/GET/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "StateMachineRoleDefaultPolicyDF1E6607", + "Roles": [ + { + "Ref": "StateMachineRoleB840431D" + } + ] + } + }, + "StateMachine2E01A3A5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"Call APIGW\",\"States\":{\"Call APIGW\":{\"End\":true,\"Type\":\"Task\",\"OutputPath\":\"$.ResponseBody\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::apigateway:invoke\",\"Parameters\":{\"ApiEndpoint\":\"", + { + "Ref": "MyRestApi2D1F47A9" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "\",\"Method\":\"GET\",\"Stage\":\"prod\",\"AuthType\":\"IAM_ROLE\"}}},\"TimeoutSeconds\":30}" + ] + ] + } + }, + "DependsOn": [ + "StateMachineRoleDefaultPolicyDF1E6607", + "StateMachineRoleB840431D" + ] + } + }, + "Outputs": { + "MyRestApiEndpoint4C55E4CB": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "MyRestApi2D1F47A9" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "MyRestApiDeploymentStageprodC33B8E5F" + }, + "/" + ] + ] + } + }, + "stateMachineArn": { + "Value": { + "Ref": "StateMachine2E01A3A5" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-rest-api.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-rest-api.ts new file mode 100644 index 0000000000000..7cfe3c85ab12b --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-rest-api.ts @@ -0,0 +1,43 @@ +import * as apigateway from '@aws-cdk/aws-apigateway'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import { AuthType, HttpMethod, CallApiGatewayRestApiEndpoint } from '../../lib'; + +/* + * Stack verification steps: + * * aws stepfunctions start-execution --state-machine-arn : should return execution arn + * + * * aws stepfunctions describe-execution --execution-arn --query 'status': should return status as SUCCEEDED + * * aws stepfunctions describe-execution --execution-arn --query 'output': should return the string \"hello, world!\" + */ + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'CallRestApiInteg'); +const restApi = new apigateway.RestApi(stack, 'MyRestApi'); + +const hello = new apigateway.LambdaIntegration(new lambda.Function(stack, 'Hello', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: new lambda.InlineCode('exports.handler = async function(event, context) { return { statusCode: 200, body: "hello, world!" }; };'), +})); +restApi.root.addMethod('ANY', hello); + +const callEndpointJob = new CallApiGatewayRestApiEndpoint(stack, 'Call APIGW', { + api: restApi, + stageName: 'prod', + method: HttpMethod.GET, + authType: AuthType.IAM_ROLE, + outputPath: sfn.JsonPath.stringAt('$.ResponseBody'), +}); + +const chain = sfn.Chain.start(callEndpointJob); + +const sm = new sfn.StateMachine(stack, 'StateMachine', { + definition: chain, + timeout: cdk.Duration.seconds(30), +}); + +new cdk.CfnOutput(stack, 'stateMachineArn', { + value: sm.stateMachineArn, +}); From 66f7053a6c1f5cab540e975b30f5a2c6e35df58a Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Wed, 10 Mar 2021 18:31:45 -0700 Subject: [PATCH 08/41] feat(appmesh): add route retry policies (#13353) Adds route retry policies for http/http2 and gRPC routes. Closes #11642 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-appmesh/README.md | 44 +++ .../@aws-cdk/aws-appmesh/lib/route-spec.ts | 198 ++++++++++++ .../aws-appmesh/test/integ.mesh.expected.json | 129 +++++++- .../@aws-cdk/aws-appmesh/test/integ.mesh.ts | 26 ++ .../@aws-cdk/aws-appmesh/test/test.route.ts | 294 +++++++++++++++++- 5 files changed, 678 insertions(+), 13 deletions(-) diff --git a/packages/@aws-cdk/aws-appmesh/README.md b/packages/@aws-cdk/aws-appmesh/README.md index c400cbb0af05d..74aead1f02a02 100644 --- a/packages/@aws-cdk/aws-appmesh/README.md +++ b/packages/@aws-cdk/aws-appmesh/README.md @@ -320,6 +320,50 @@ router.addRoute('route-http', { }); ``` +Add an http2 route with retries: + +```ts +router.addRoute('route-http2-retry', { + routeSpec: appmesh.RouteSpec.http2({ + weightedTargets: [{ virtualNode: node }], + retryPolicy: { + // Retry if the connection failed + tcpRetryEvents: [appmesh.TcpRetryEvent.CONNECTION_ERROR], + // Retry if HTTP responds with a gateway error (502, 503, 504) + httpRetryEvents: [appmesh.HttpRetryEvent.GATEWAY_ERROR], + // Retry five times + retryAttempts: 5, + // Use a 1 second timeout per retry + retryTimeout: cdk.Duration.seconds(1), + }, + }), +}); +``` + +Add a gRPC route with retries: + +```ts +router.addRoute('route-grpc-retry', { + routeSpec: appmesh.RouteSpec.grpc({ + weightedTargets: [{ virtualNode: node }], + match: { serviceName: 'servicename' }, + retryPolicy: { + tcpRetryEvents: [appmesh.TcpRetryEvent.CONNECTION_ERROR], + httpRetryEvents: [appmesh.HttpRetryEvent.GATEWAY_ERROR], + // Retry if gRPC responds that the request was cancelled, a resource + // was exhausted, or if the service is unavailable + grpcRetryEvents: [ + appmesh.GrpcRetryEvent.CANCELLED, + appmesh.GrpcRetryEvent.RESOURCE_EXHAUSTED, + appmesh.GrpcRetryEvent.UNAVAILABLE, + ], + retryAttempts: 5, + retryTimeout: cdk.Duration.seconds(1), + }, + }), +}); +``` + The _RouteSpec_ class provides an easy interface for defining new protocol specific route specs. The `tcp()`, `http()` and `http2()` methods provide the spec necessary to define a protocol specific spec. diff --git a/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts b/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts index 74b16976b69ca..11f629c4aee91 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts @@ -1,3 +1,4 @@ +import * as cdk from '@aws-cdk/core'; import { CfnRoute } from './appmesh.generated'; import { Protocol, HttpTimeout, GrpcTimeout, TcpTimeout } from './shared-interfaces'; import { IVirtualNode } from './virtual-node'; @@ -68,6 +69,81 @@ export interface HttpRouteSpecOptions { * @default - None */ readonly timeout?: HttpTimeout; + + /** + * The retry policy + * + * @default - no retry policy + */ + readonly retryPolicy?: HttpRetryPolicy; +} + +/** + * HTTP retry policy + */ +export interface HttpRetryPolicy { + /** + * Specify HTTP events on which to retry. You must specify at least one value + * for at least one types of retry events. + * + * @default - no retries for http events + */ + readonly httpRetryEvents?: HttpRetryEvent[]; + + /** + * The maximum number of retry attempts + */ + readonly retryAttempts: number; + + /** + * The timeout for each retry attempt + */ + readonly retryTimeout: cdk.Duration; + + /** + * TCP events on which to retry. The event occurs before any processing of a + * request has started and is encountered when the upstream is temporarily or + * permanently unavailable. You must specify at least one value for at least + * one types of retry events. + * + * @default - no retries for tcp events + */ + readonly tcpRetryEvents?: TcpRetryEvent[]; +} + +/** + * HTTP events on which to retry. + */ +export enum HttpRetryEvent { + /** + * HTTP status codes 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, and 511 + */ + SERVER_ERROR = 'server-error', + + /** + * HTTP status codes 502, 503, and 504 + */ + GATEWAY_ERROR = 'gateway-error', + + /** + * HTTP status code 409 + */ + CLIENT_ERROR = 'client-error', + + /** + * Retry on refused stream + */ + STREAM_ERROR = 'stream-error', +} + +/** + * TCP events on which you may retry + */ +export enum TcpRetryEvent { + /** + * A connection error + */ + CONNECTION_ERROR = 'connection-error', } /** @@ -107,6 +183,64 @@ export interface GrpcRouteSpecOptions { * List of targets that traffic is routed to when a request matches the route */ readonly weightedTargets: WeightedTarget[]; + + /** + * The retry policy + * + * @default - no retry policy + */ + readonly retryPolicy?: GrpcRetryPolicy; +} + +/** gRPC retry policy */ +export interface GrpcRetryPolicy extends HttpRetryPolicy { + /** + * gRPC events on which to retry. You must specify at least one value + * for at least one types of retry events. + * + * @default - no retries for gRPC events + */ + readonly grpcRetryEvents?: GrpcRetryEvent[]; +} + +/** + * gRPC events + */ +export enum GrpcRetryEvent { + /** + * Request was cancelled + * + * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html + */ + CANCELLED = 'cancelled', + + /** + * The deadline was exceeded + * + * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html + */ + DEADLINE_EXCEEDED = 'deadline-exceeded', + + /** + * Internal error + * + * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html + */ + INTERNAL_ERROR = 'internal', + + /** + * A resource was exhausted + * + * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html + */ + RESOURCE_EXHAUSTED = 'resource-exhausted', + + /** + * The service is unavailable + * + * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html + */ + UNAVAILABLE = 'unavailable', } /** @@ -203,12 +337,32 @@ class HttpRouteSpec extends RouteSpec { */ public readonly weightedTargets: WeightedTarget[]; + /** + * The retry policy + */ + public readonly retryPolicy?: HttpRetryPolicy; + constructor(props: HttpRouteSpecOptions, protocol: Protocol) { super(); this.protocol = protocol; this.match = props.match; this.weightedTargets = props.weightedTargets; this.timeout = props.timeout; + + if (props.retryPolicy) { + const httpRetryEvents = props.retryPolicy.httpRetryEvents ?? []; + const tcpRetryEvents = props.retryPolicy.tcpRetryEvents ?? []; + + if (httpRetryEvents.length + tcpRetryEvents.length === 0) { + throw new Error('You must specify one value for at least one of `httpRetryEvents` or `tcpRetryEvents`'); + } + + this.retryPolicy = { + ...props.retryPolicy, + httpRetryEvents: httpRetryEvents.length > 0 ? httpRetryEvents : undefined, + tcpRetryEvents: tcpRetryEvents.length > 0 ? tcpRetryEvents : undefined, + }; + } } public bind(_scope: Construct): RouteSpecConfig { @@ -216,6 +370,7 @@ class HttpRouteSpec extends RouteSpec { if (prefixPath[0] != '/') { throw new Error(`Prefix Path must start with \'/\', got: ${prefixPath}`); } + const httpConfig: CfnRoute.HttpRouteProperty = { action: { weightedTargets: renderWeightedTargets(this.weightedTargets), @@ -224,6 +379,7 @@ class HttpRouteSpec extends RouteSpec { prefix: prefixPath, }, timeout: renderTimeout(this.timeout), + retryPolicy: this.retryPolicy ? renderHttpRetryPolicy(this.retryPolicy) : undefined, }; return { httpRouteSpec: this.protocol === Protocol.HTTP ? httpConfig : undefined, @@ -266,11 +422,33 @@ class GrpcRouteSpec extends RouteSpec { public readonly match: GrpcRouteMatch; public readonly timeout?: GrpcTimeout; + /** + * The retry policy. + */ + public readonly retryPolicy?: GrpcRetryPolicy; + constructor(props: GrpcRouteSpecOptions) { super(); this.weightedTargets = props.weightedTargets; this.match = props.match; this.timeout = props.timeout; + + if (props.retryPolicy) { + const grpcRetryEvents = props.retryPolicy.grpcRetryEvents ?? []; + const httpRetryEvents = props.retryPolicy.httpRetryEvents ?? []; + const tcpRetryEvents = props.retryPolicy.tcpRetryEvents ?? []; + + if (grpcRetryEvents.length + httpRetryEvents.length + tcpRetryEvents.length === 0) { + throw new Error('You must specify one value for at least one of `grpcRetryEvents`, `httpRetryEvents` or `tcpRetryEvents`'); + } + + this.retryPolicy = { + ...props.retryPolicy, + grpcRetryEvents: grpcRetryEvents.length > 0 ? grpcRetryEvents : undefined, + httpRetryEvents: httpRetryEvents.length > 0 ? httpRetryEvents : undefined, + tcpRetryEvents: tcpRetryEvents.length > 0 ? tcpRetryEvents : undefined, + }; + } } public bind(_scope: Construct): RouteSpecConfig { @@ -283,6 +461,7 @@ class GrpcRouteSpec extends RouteSpec { serviceName: this.match.serviceName, }, timeout: renderTimeout(this.timeout), + retryPolicy: this.retryPolicy ? renderGrpcRetryPolicy(this.retryPolicy) : undefined, }, }; } @@ -323,3 +502,22 @@ function renderTimeout(timeout?: HttpTimeout): CfnRoute.HttpTimeoutProperty | un } : undefined; } + +function renderHttpRetryPolicy(retryPolicy: HttpRetryPolicy): CfnRoute.HttpRetryPolicyProperty { + return { + maxRetries: retryPolicy.retryAttempts, + perRetryTimeout: { + unit: 'ms', + value: retryPolicy.retryTimeout.toMilliseconds(), + }, + httpRetryEvents: retryPolicy.httpRetryEvents, + tcpRetryEvents: retryPolicy.tcpRetryEvents, + }; +} + +function renderGrpcRetryPolicy(retryPolicy: GrpcRetryPolicy): CfnRoute.GrpcRetryPolicyProperty { + return { + ...renderHttpRetryPolicy(retryPolicy), + grpcRetryEvents: retryPolicy.grpcRetryEvents, + }; +} diff --git a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json index 5f4a9ca206725..f951953924b44 100644 --- a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json +++ b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json @@ -497,7 +497,6 @@ "MeshName" ] }, - "RouteName": "route-1", "Spec": { "HttpRoute": { "Action": { @@ -533,7 +532,8 @@ "meshrouter81B8087E", "VirtualRouterName" ] - } + }, + "RouteName": "route-1" } }, "meshrouterroute2486D9DEF": { @@ -545,7 +545,6 @@ "MeshName" ] }, - "RouteName": "route-2", "Spec": { "HttpRoute": { "Action": { @@ -581,7 +580,8 @@ "meshrouter81B8087E", "VirtualRouterName" ] - } + }, + "RouteName": "route-2" } }, "meshrouterroute3BD0FA22F": { @@ -593,7 +593,6 @@ "MeshName" ] }, - "RouteName": "route-3", "Spec": { "TcpRoute": { "Action": { @@ -622,7 +621,113 @@ "meshrouter81B8087E", "VirtualRouterName" ] - } + }, + "RouteName": "route-3" + } + }, + "meshrouterroutehttp2retryCC41345F": { + "Type": "AWS::AppMesh::Route", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "meshACDFE68E", + "MeshName" + ] + }, + "Spec": { + "Http2Route": { + "Action": { + "WeightedTargets": [ + { + "VirtualNode": { + "Fn::GetAtt": [ + "meshnode3D2A19CF2", + "VirtualNodeName" + ] + }, + "Weight": 1 + } + ] + }, + "Match": { + "Prefix": "/" + }, + "RetryPolicy": { + "HttpRetryEvents": [ + "client-error" + ], + "MaxRetries": 5, + "PerRetryTimeout": { + "Unit": "ms", + "Value": 1000 + }, + "TcpRetryEvents": [ + "connection-error" + ] + } + } + }, + "VirtualRouterName": { + "Fn::GetAtt": [ + "meshrouter81B8087E", + "VirtualRouterName" + ] + }, + "RouteName": "route-http2-retry" + } + }, + "meshrouterroutegrpcretry9BEB798A": { + "Type": "AWS::AppMesh::Route", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "meshACDFE68E", + "MeshName" + ] + }, + "Spec": { + "GrpcRoute": { + "Action": { + "WeightedTargets": [ + { + "VirtualNode": { + "Fn::GetAtt": [ + "meshnode3D2A19CF2", + "VirtualNodeName" + ] + }, + "Weight": 1 + } + ] + }, + "Match": { + "ServiceName": "servicename" + }, + "RetryPolicy": { + "GrpcRetryEvents": [ + "deadline-exceeded" + ], + "HttpRetryEvents": [ + "client-error" + ], + "MaxRetries": 5, + "PerRetryTimeout": { + "Unit": "ms", + "Value": 1000 + }, + "TcpRetryEvents": [ + "connection-error" + ] + } + } + }, + "VirtualRouterName": { + "Fn::GetAtt": [ + "meshrouter81B8087E", + "VirtualRouterName" + ] + }, + "RouteName": "route-grpc-retry" } }, "meshnode726C787D": { @@ -832,7 +937,6 @@ "meshgateway1gateway1routehttpE8D6F433": { "Type": "AWS::AppMesh::GatewayRoute", "Properties": { - "GatewayRouteName": "meshstackmeshgateway1gateway1routehttpBA921D42", "MeshName": { "Fn::GetAtt": [ "meshACDFE68E", @@ -863,13 +967,13 @@ "meshgateway1B02387E8", "VirtualGatewayName" ] - } + }, + "GatewayRouteName": "meshstackmeshgateway1gateway1routehttpBA921D42" } }, "meshgateway1gateway1routehttp2FD69C306": { "Type": "AWS::AppMesh::GatewayRoute", "Properties": { - "GatewayRouteName": "meshstackmeshgateway1gateway1routehttp255781963", "MeshName": { "Fn::GetAtt": [ "meshACDFE68E", @@ -900,13 +1004,13 @@ "meshgateway1B02387E8", "VirtualGatewayName" ] - } + }, + "GatewayRouteName": "meshstackmeshgateway1gateway1routehttp255781963" } }, "meshgateway1gateway1routegrpc76486062": { "Type": "AWS::AppMesh::GatewayRoute", "Properties": { - "GatewayRouteName": "meshstackmeshgateway1gateway1routegrpcCD4D891D", "MeshName": { "Fn::GetAtt": [ "meshACDFE68E", @@ -942,7 +1046,8 @@ "meshgateway1B02387E8", "VirtualGatewayName" ] - } + }, + "GatewayRouteName": "meshstackmeshgateway1gateway1routegrpcCD4D891D" } }, "service6D174F83": { diff --git a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts index 90e54586f7f51..c1e909e38d75b 100644 --- a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts @@ -140,6 +140,32 @@ router.addRoute('route-3', { }), }); +router.addRoute('route-http2-retry', { + routeSpec: appmesh.RouteSpec.http2({ + weightedTargets: [{ virtualNode: node3 }], + retryPolicy: { + httpRetryEvents: [appmesh.HttpRetryEvent.CLIENT_ERROR], + tcpRetryEvents: [appmesh.TcpRetryEvent.CONNECTION_ERROR], + retryAttempts: 5, + retryTimeout: cdk.Duration.seconds(1), + }, + }), +}); + +router.addRoute('route-grpc-retry', { + routeSpec: appmesh.RouteSpec.grpc({ + weightedTargets: [{ virtualNode: node3 }], + match: { serviceName: 'servicename' }, + retryPolicy: { + grpcRetryEvents: [appmesh.GrpcRetryEvent.DEADLINE_EXCEEDED], + httpRetryEvents: [appmesh.HttpRetryEvent.CLIENT_ERROR], + tcpRetryEvents: [appmesh.TcpRetryEvent.CONNECTION_ERROR], + retryAttempts: 5, + retryTimeout: cdk.Duration.seconds(1), + }, + }), +}); + const gateway = mesh.addVirtualGateway('gateway1', { accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), virtualGatewayName: 'gateway1', diff --git a/packages/@aws-cdk/aws-appmesh/test/test.route.ts b/packages/@aws-cdk/aws-appmesh/test/test.route.ts index cb5c92e6464cf..43c2d942a669b 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.route.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.route.ts @@ -1,4 +1,4 @@ -import { expect, haveResourceLike } from '@aws-cdk/assert'; +import { ABSENT, expect, haveResourceLike } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; @@ -282,6 +282,298 @@ export = { })); test.done(); }, + + 'should allow http retries'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + const router = new appmesh.VirtualRouter(stack, 'router', { + mesh, + }); + const virtualNode = mesh.addVirtualNode('test-node', { + serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), + listeners: [appmesh.VirtualNodeListener.http()], + }); + + // WHEN + router.addRoute('test-http-route', { + routeSpec: appmesh.RouteSpec.http({ + weightedTargets: [{ virtualNode }], + retryPolicy: { + httpRetryEvents: [appmesh.HttpRetryEvent.CLIENT_ERROR], + tcpRetryEvents: [appmesh.TcpRetryEvent.CONNECTION_ERROR], + retryAttempts: 5, + retryTimeout: cdk.Duration.seconds(10), + }, + }), + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::Route', { + Spec: { + HttpRoute: { + RetryPolicy: { + HttpRetryEvents: ['client-error'], + TcpRetryEvents: ['connection-error'], + MaxRetries: 5, + PerRetryTimeout: { + Unit: 'ms', + Value: 10000, + }, + }, + }, + }, + })); + + test.done(); + }, + + 'http retry events are ABSENT when specified as an empty array'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + const router = new appmesh.VirtualRouter(stack, 'router', { + mesh, + }); + const virtualNode = mesh.addVirtualNode('test-node', { + serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), + listeners: [appmesh.VirtualNodeListener.http()], + }); + + // WHEN + router.addRoute('test-http-route', { + routeSpec: appmesh.RouteSpec.http({ + weightedTargets: [{ virtualNode }], + retryPolicy: { + httpRetryEvents: [], + tcpRetryEvents: [appmesh.TcpRetryEvent.CONNECTION_ERROR], + retryAttempts: 5, + retryTimeout: cdk.Duration.seconds(10), + }, + }), + }); + + router.addRoute('test-http-route2', { + routeSpec: appmesh.RouteSpec.http({ + weightedTargets: [{ virtualNode }], + retryPolicy: { + httpRetryEvents: [appmesh.HttpRetryEvent.CLIENT_ERROR], + tcpRetryEvents: [], + retryAttempts: 5, + retryTimeout: cdk.Duration.seconds(10), + }, + }), + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::Route', { + Spec: { + HttpRoute: { + RetryPolicy: { + HttpRetryEvents: ABSENT, + TcpRetryEvents: ['connection-error'], + }, + }, + }, + })); + expect(stack).to(haveResourceLike('AWS::AppMesh::Route', { + Spec: { + HttpRoute: { + RetryPolicy: { + HttpRetryEvents: ['client-error'], + TcpRetryEvents: ABSENT, + }, + }, + }, + })); + + test.done(); + }, + + 'errors when http retry policy has no events'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + const router = new appmesh.VirtualRouter(stack, 'router', { + mesh, + }); + const virtualNode = mesh.addVirtualNode('test-node', { + serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), + listeners: [appmesh.VirtualNodeListener.http()], + }); + + // WHEN + test.throws(() => { + router.addRoute('test-http-route', { + routeSpec: appmesh.RouteSpec.http({ + weightedTargets: [{ virtualNode }], + retryPolicy: { + retryAttempts: 5, + retryTimeout: cdk.Duration.seconds(10), + }, + }), + }); + }, /specify one value for at least/i); + + test.done(); + }, + + 'should allow grpc retries'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + const router = new appmesh.VirtualRouter(stack, 'router', { + mesh, + }); + const virtualNode = mesh.addVirtualNode('test-node', { + serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), + listeners: [appmesh.VirtualNodeListener.http()], + }); + + // WHEN + router.addRoute('test-grpc-route', { + routeSpec: appmesh.RouteSpec.grpc({ + weightedTargets: [{ virtualNode }], + match: { serviceName: 'servicename' }, + retryPolicy: { + grpcRetryEvents: [appmesh.GrpcRetryEvent.DEADLINE_EXCEEDED], + httpRetryEvents: [appmesh.HttpRetryEvent.CLIENT_ERROR], + tcpRetryEvents: [appmesh.TcpRetryEvent.CONNECTION_ERROR], + retryAttempts: 5, + retryTimeout: cdk.Duration.seconds(10), + }, + }), + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::Route', { + Spec: { + GrpcRoute: { + RetryPolicy: { + GrpcRetryEvents: ['deadline-exceeded'], + HttpRetryEvents: ['client-error'], + TcpRetryEvents: ['connection-error'], + MaxRetries: 5, + PerRetryTimeout: { + Unit: 'ms', + Value: 10000, + }, + }, + }, + }, + })); + + test.done(); + }, + + 'grpc retry events are ABSENT when specified as an empty array'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + const router = new appmesh.VirtualRouter(stack, 'router', { + mesh, + }); + const virtualNode = mesh.addVirtualNode('test-node', { + serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), + listeners: [appmesh.VirtualNodeListener.http()], + }); + + // WHEN + router.addRoute('test-grpc-route', { + routeSpec: appmesh.RouteSpec.grpc({ + weightedTargets: [{ virtualNode }], + match: { serviceName: 'example' }, + retryPolicy: { + grpcRetryEvents: [], + httpRetryEvents: [], + tcpRetryEvents: [appmesh.TcpRetryEvent.CONNECTION_ERROR], + retryAttempts: 5, + retryTimeout: cdk.Duration.seconds(10), + }, + }), + }); + + router.addRoute('test-grpc-route2', { + routeSpec: appmesh.RouteSpec.grpc({ + weightedTargets: [{ virtualNode }], + match: { serviceName: 'example' }, + retryPolicy: { + grpcRetryEvents: [appmesh.GrpcRetryEvent.CANCELLED], + httpRetryEvents: [], + tcpRetryEvents: [], + retryAttempts: 5, + retryTimeout: cdk.Duration.seconds(10), + }, + }), + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::Route', { + Spec: { + GrpcRoute: { + RetryPolicy: { + GrpcRetryEvents: ABSENT, + HttpRetryEvents: ABSENT, + TcpRetryEvents: ['connection-error'], + }, + }, + }, + })); + expect(stack).to(haveResourceLike('AWS::AppMesh::Route', { + Spec: { + GrpcRoute: { + RetryPolicy: { + GrpcRetryEvents: ['cancelled'], + HttpRetryEvents: ABSENT, + TcpRetryEvents: ABSENT, + }, + }, + }, + })); + + test.done(); + }, + + 'errors when grpc retry policy has no events'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + const router = new appmesh.VirtualRouter(stack, 'router', { + mesh, + }); + const virtualNode = mesh.addVirtualNode('test-node', { + serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), + listeners: [appmesh.VirtualNodeListener.http()], + }); + + // WHEN + test.throws(() => { + router.addRoute('test-grpc-route', { + routeSpec: appmesh.RouteSpec.grpc({ + weightedTargets: [{ virtualNode }], + match: { serviceName: 'servicename' }, + retryPolicy: { + retryAttempts: 5, + retryTimeout: cdk.Duration.seconds(10), + }, + }), + }); + }, /specify one value for at least/i); + + test.done(); + }, }, 'Can import Routes using an ARN'(test: Test) { From 4c63f09f1e9644877eaffbe78eede3854bec08ab Mon Sep 17 00:00:00 2001 From: Janario Oliveira Date: Thu, 11 Mar 2021 07:39:45 +0100 Subject: [PATCH 09/41] feat(amplify-domain): Added config for auto subdomain creation (#13342) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-amplify/README.md | 5 +- packages/@aws-cdk/aws-amplify/lib/app.ts | 1 + packages/@aws-cdk/aws-amplify/lib/domain.ts | 24 ++++ .../@aws-cdk/aws-amplify/test/domain.test.ts | 111 ++++++++++++++++++ 4 files changed, 140 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-amplify/README.md b/packages/@aws-cdk/aws-amplify/README.md index 7c15079d914d6..b8e3bd91c2306 100644 --- a/packages/@aws-cdk/aws-amplify/README.md +++ b/packages/@aws-cdk/aws-amplify/README.md @@ -122,7 +122,10 @@ mySinglePageApp.addCustomRule(amplify.CustomRule.SINGLE_PAGE_APPLICATION_REDIREC Add a domain and map sub domains to branches: ```ts -const domain = amplifyApp.addDomain('example.com'); +const domain = amplifyApp.addDomain('example.com', { + enableAutoSubdomain: true, // in case subdomains should be auto registered for branches + autoSubdomainCreationPatterns: ['*', 'pr*'], // regex for branches that should auto register subdomains +}); domain.mapRoot(master); // map master branch to domain root domain.mapSubDomain(master, 'www'); domain.mapSubDomain(dev); // sub domain prefix defaults to branch name diff --git a/packages/@aws-cdk/aws-amplify/lib/app.ts b/packages/@aws-cdk/aws-amplify/lib/app.ts index bf1d5bc3d5e89..43f8e308cb8f9 100644 --- a/packages/@aws-cdk/aws-amplify/lib/app.ts +++ b/packages/@aws-cdk/aws-amplify/lib/app.ts @@ -289,6 +289,7 @@ export class App extends Resource implements IApp, iam.IGrantable { return new Domain(this, id, { ...options, app: this, + autoSubDomainIamRole: this.grantPrincipal as iam.IRole, }); } } diff --git a/packages/@aws-cdk/aws-amplify/lib/domain.ts b/packages/@aws-cdk/aws-amplify/lib/domain.ts index bf6ba7afe8017..f8683f44b123d 100644 --- a/packages/@aws-cdk/aws-amplify/lib/domain.ts +++ b/packages/@aws-cdk/aws-amplify/lib/domain.ts @@ -1,3 +1,4 @@ +import * as iam from '@aws-cdk/aws-iam'; import { Lazy, Resource, IResolvable } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnDomain } from './amplify.generated'; @@ -21,6 +22,20 @@ export interface DomainOptions { * @default - use `addSubDomain()` to add subdomains */ readonly subDomains?: SubDomain[]; + + /** + * Automatically create subdomains for connected branches + * + * @default false + */ + readonly enableAutoSubdomain?: boolean; + + /** + * Branches which should automatically create subdomains + * + * @default - all repository branches ['*', 'pr*'] + */ + readonly autoSubdomainCreationPatterns?: string[]; } /** @@ -31,6 +46,12 @@ export interface DomainProps extends DomainOptions { * The application to which the domain must be connected */ readonly app: IApp; + + /** + * The IAM role with access to Route53 when using enableAutoSubdomain + * @default the IAM role from App.grantPrincipal + */ + readonly autoSubDomainIamRole?: iam.IRole; } /** @@ -106,6 +127,9 @@ export class Domain extends Resource { appId: props.app.appId, domainName, subDomainSettings: Lazy.any({ produce: () => this.renderSubDomainSettings() }, { omitEmptyArray: true }), + enableAutoSubDomain: !!props.enableAutoSubdomain, + autoSubDomainCreationPatterns: props.autoSubdomainCreationPatterns || ['*', 'pr*'], + autoSubDomainIamRole: props.autoSubDomainIamRole?.roleArn, }); this.arn = domain.attrArn; diff --git a/packages/@aws-cdk/aws-amplify/test/domain.test.ts b/packages/@aws-cdk/aws-amplify/test/domain.test.ts index ca7c211d14094..7b0f28f75837d 100644 --- a/packages/@aws-cdk/aws-amplify/test/domain.test.ts +++ b/packages/@aws-cdk/aws-amplify/test/domain.test.ts @@ -1,3 +1,4 @@ +import * as iam from '@aws-cdk/aws-iam'; import '@aws-cdk/assert/jest'; import { App, SecretValue, Stack } from '@aws-cdk/core'; import * as amplify from '../lib'; @@ -120,3 +121,113 @@ test('throws at synthesis without subdomains', () => { // THEN expect(() => app.synth()).toThrow(/The domain doesn't contain any subdomains/); }); + +test('auto subdomain all branches', () => { + // GIVEN + const stack = new Stack(); + const app = new amplify.App(stack, 'App', { + sourceCodeProvider: new amplify.GitHubSourceCodeProvider({ + owner: 'aws', + repository: 'aws-cdk', + oauthToken: SecretValue.plainText('secret'), + }), + }); + const prodBranch = app.addBranch('master'); + + // WHEN + const domain = app.addDomain('amazon.com', { + enableAutoSubdomain: true, + }); + domain.mapRoot(prodBranch); + + // THEN + expect(stack).toHaveResource('AWS::Amplify::Domain', { + EnableAutoSubDomain: true, + AutoSubDomainCreationPatterns: [ + '*', + 'pr*', + ], + AutoSubDomainIAMRole: { + 'Fn::GetAtt': [ + 'AppRole1AF9B530', + 'Arn', + ], + }, + }); +}); + +test('auto subdomain some branches', () => { + // GIVEN + const stack = new Stack(); + const app = new amplify.App(stack, 'App', { + sourceCodeProvider: new amplify.GitHubSourceCodeProvider({ + owner: 'aws', + repository: 'aws-cdk', + oauthToken: SecretValue.plainText('secret'), + }), + }); + const prodBranch = app.addBranch('master'); + + // WHEN + const domain = app.addDomain('amazon.com', { + enableAutoSubdomain: true, + autoSubdomainCreationPatterns: ['features/**'], + }); + domain.mapRoot(prodBranch); + + // THEN + expect(stack).toHaveResource('AWS::Amplify::Domain', { + EnableAutoSubDomain: true, + AutoSubDomainCreationPatterns: ['features/**'], + AutoSubDomainIAMRole: { + 'Fn::GetAtt': [ + 'AppRole1AF9B530', + 'Arn', + ], + }, + }); +}); + +test('auto subdomain with IAM role', () => { + // GIVEN + const stack = new Stack(); + const app = new amplify.App(stack, 'App', { + sourceCodeProvider: new amplify.GitHubSourceCodeProvider({ + owner: 'aws', + repository: 'aws-cdk', + oauthToken: SecretValue.plainText('secret'), + }), + role: iam.Role.fromRoleArn( + stack, + 'AmplifyRole', + `arn:aws:iam::${Stack.of(stack).account}:role/AmplifyRole`, + { mutable: false }, + ), + }); + const prodBranch = app.addBranch('master'); + + // WHEN + const domain = app.addDomain('amazon.com', { + enableAutoSubdomain: true, + autoSubdomainCreationPatterns: ['features/**'], + }); + domain.mapRoot(prodBranch); + + // THEN + expect(stack).toHaveResource('AWS::Amplify::Domain', { + EnableAutoSubDomain: true, + AutoSubDomainCreationPatterns: ['features/**'], + AutoSubDomainIAMRole: { + 'Fn::Join': [ + '', + [ + 'arn:aws:iam::', + { + Ref: 'AWS::AccountId', + }, + ':role/AmplifyRole', + ], + ], + }, + }); +}); \ No newline at end of file From e9cd1e84df3a99bca4ac98890c729f8dec899fd7 Mon Sep 17 00:00:00 2001 From: Romain Marcadier Date: Thu, 11 Mar 2021 11:20:26 +0100 Subject: [PATCH 10/41] chore(test): make metadata resource test immune to encoding (#13538) The prefix-encoded trie could occasionally encode the tested resource name in a way that prevents the test to match. Using a "fake" version number ensures a unique prefix is always present, and hence the tested entry will never be encoded in unexpected ways. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/core/test/metadata-resource.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/core/test/metadata-resource.test.ts b/packages/@aws-cdk/core/test/metadata-resource.test.ts index 2275bcf7dee9d..00869746b1e25 100644 --- a/packages/@aws-cdk/core/test/metadata-resource.test.ts +++ b/packages/@aws-cdk/core/test/metadata-resource.test.ts @@ -63,7 +63,7 @@ describe('MetadataResource', () => { test('includes constructs added to the stack', () => { new TestConstruct(stack, 'Test'); - expect(stackAnalytics()).toContain('1.2.3!@amzn/core.TestConstruct'); + expect(stackAnalytics()).toContain('FakeVersion.2.3!@amzn/core.TestConstruct'); }); test('only includes constructs in the allow list', () => { @@ -141,11 +141,10 @@ const JSII_RUNTIME_SYMBOL = Symbol.for('jsii.rtti'); class TestConstruct extends Construct { // @ts-ignore - private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: '@amzn/core.TestConstruct', version: '1.2.3' } + private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: '@amzn/core.TestConstruct', version: 'FakeVersion.2.3' } } class TestThirdPartyConstruct extends Construct { // @ts-ignore private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: 'mycoolthing.TestConstruct', version: '1.2.3' } } - From b5d4b923ea55a034b90eb7a30b0e647daf7524ec Mon Sep 17 00:00:00 2001 From: AWS CDK Team Date: Thu, 11 Mar 2021 12:33:11 +0000 Subject: [PATCH 11/41] chore(release): 1.93.0 --- CHANGELOG.md | 36 ++++++++++++++++++++++++++++++++++++ version.v1.json | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fad4611dfad6c..ca6ebeb1b8572 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,42 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.93.0](https://github.com/aws/aws-cdk/compare/v1.92.0...v1.93.0) (2021-03-11) + + +### Features + +* **amplify-domain:** Added config for auto subdomain creation ([#13342](https://github.com/aws/aws-cdk/issues/13342)) ([4c63f09](https://github.com/aws/aws-cdk/commit/4c63f09f1e9644877eaffbe78eede3854bec08ab)) +* **appmesh:** add route retry policies ([#13353](https://github.com/aws/aws-cdk/issues/13353)) ([66f7053](https://github.com/aws/aws-cdk/commit/66f7053a6c1f5cab540e975b30f5a2c6e35df58a)), closes [#11642](https://github.com/aws/aws-cdk/issues/11642) +* **cfnspec:** cloudformation spec v30.1.0 ([#13519](https://github.com/aws/aws-cdk/issues/13519)) ([7711981](https://github.com/aws/aws-cdk/commit/7711981ea30bfdffd21dd840d676be4a2b45c9ba)) +* **codebuild:** allow setting queued timeout ([#13467](https://github.com/aws/aws-cdk/issues/13467)) ([e09250b](https://github.com/aws/aws-cdk/commit/e09250bc92c62cb8ee0a8706ce90d0e82faf2d84)), closes [#11364](https://github.com/aws/aws-cdk/issues/11364) +* **dynamodb:** custom timeout for replication operation ([#13354](https://github.com/aws/aws-cdk/issues/13354)) ([6a5a4f2](https://github.com/aws/aws-cdk/commit/6a5a4f2d9bb6b09ad0d10066200fe53bb45f0737)), closes [#10249](https://github.com/aws/aws-cdk/issues/10249) +* **ec2:** ESP and AH IPsec protocols for Security Groups ([#13471](https://github.com/aws/aws-cdk/issues/13471)) ([f5a6647](https://github.com/aws/aws-cdk/commit/f5a6647bbe1885ba86029d10550a3ffaf80b6561)), closes [#13403](https://github.com/aws/aws-cdk/issues/13403) +* **ec2:** multipart user data ([#11843](https://github.com/aws/aws-cdk/issues/11843)) ([ed94c5e](https://github.com/aws/aws-cdk/commit/ed94c5ef1b9dd3042128b0e0c5bb14b3d9c7d497)), closes [#8315](https://github.com/aws/aws-cdk/issues/8315) +* **ecr:** add imageTagMutability prop ([#10557](https://github.com/aws/aws-cdk/issues/10557)) ([c4dc3bc](https://github.com/aws/aws-cdk/commit/c4dc3bce02790903593d80b070fca81fe7b7f08c)), closes [#4640](https://github.com/aws/aws-cdk/issues/4640) +* **ecs:** ability to access tag parameter value of TagParameterContainerImage ([#13340](https://github.com/aws/aws-cdk/issues/13340)) ([e567a41](https://github.com/aws/aws-cdk/commit/e567a410d47366855ee3e6011aa096ba987b8099)), closes [#13202](https://github.com/aws/aws-cdk/issues/13202) +* **ecs:** allow users to provide a CloudMap service to associate with an ECS service ([#13192](https://github.com/aws/aws-cdk/issues/13192)) ([a7d314c](https://github.com/aws/aws-cdk/commit/a7d314c73b9473208d94bac29ad9bd8018e00204)), closes [#10057](https://github.com/aws/aws-cdk/issues/10057) +* **events:** `EventBus.grantPutEventsTo` method for granular grants ([#13429](https://github.com/aws/aws-cdk/issues/13429)) ([122a232](https://github.com/aws/aws-cdk/commit/122a232343699304d8f206d3024fcddfb2a94bc8)), closes [#11228](https://github.com/aws/aws-cdk/issues/11228) +* **events:** dead-letter queue support for CodeBuild ([#13448](https://github.com/aws/aws-cdk/issues/13448)) ([abfc0ea](https://github.com/aws/aws-cdk/commit/abfc0ea63c10d8033a529b7497cf093e318fdf12)), closes [#13447](https://github.com/aws/aws-cdk/issues/13447) +* **events:** dead-letter queue support for StepFunctions ([#13450](https://github.com/aws/aws-cdk/issues/13450)) ([0ebcb41](https://github.com/aws/aws-cdk/commit/0ebcb4160ee16f0f7ff1072a40c8951f9a983048)), closes [#13449](https://github.com/aws/aws-cdk/issues/13449) +* **events,applicationautoscaling:** schedule can be a token ([#13064](https://github.com/aws/aws-cdk/issues/13064)) ([b1449a1](https://github.com/aws/aws-cdk/commit/b1449a178b0f9a8a951c2546428f8d75c6431f0f)) +* **iam:** SAML identity provider ([#13393](https://github.com/aws/aws-cdk/issues/13393)) ([faa0c06](https://github.com/aws/aws-cdk/commit/faa0c060dad9a5045495707e28fc85f223d4db5d)), closes [#5320](https://github.com/aws/aws-cdk/issues/5320) +* **neptune:** Support IAM authentication ([#13462](https://github.com/aws/aws-cdk/issues/13462)) ([6c5b1f4](https://github.com/aws/aws-cdk/commit/6c5b1f42fb73a132d47945b529bab73557f2b9d8)), closes [#13461](https://github.com/aws/aws-cdk/issues/13461) +* **region-info:** added AppMesh ECR account for af-south-1 region ([#12814](https://github.com/aws/aws-cdk/issues/12814)) ([b3fba43](https://github.com/aws/aws-cdk/commit/b3fba43a047df61e713e8d2271d6deee7e07b716)) +* **stepfunctions-tasks:** Support calling ApiGateway REST and HTTP APIs ([#13033](https://github.com/aws/aws-cdk/issues/13033)) ([cc608d0](https://github.com/aws/aws-cdk/commit/cc608d055ffefb798ad6378ab07f36cb241897da)), closes [#11565](https://github.com/aws/aws-cdk/issues/11565) [#11566](https://github.com/aws/aws-cdk/issues/11566) [#11565](https://github.com/aws/aws-cdk/issues/11565) + + +### Bug Fixes + +* **cfn-include:** allow boolean values for string-typed properties ([#13508](https://github.com/aws/aws-cdk/issues/13508)) ([e5dab7c](https://github.com/aws/aws-cdk/commit/e5dab7cbc67c234d191c38a8b8b84b634070b15b)) +* **ec2:** fix typo's in WindowsImage constants ([#13446](https://github.com/aws/aws-cdk/issues/13446)) ([781aa97](https://github.com/aws/aws-cdk/commit/781aa97d53fdb7511c34ddde884fdcd84c3f68a6)) +* **elasticloadbalancingv2:** upgrade to v1.92.0 drops certificates on ALB if more than 2 certificates exist ([#13490](https://github.com/aws/aws-cdk/issues/13490)) ([01b94f8](https://github.com/aws/aws-cdk/commit/01b94f8aa6c88b5e676c784aec4c879acddc042f)), closes [#13332](https://github.com/aws/aws-cdk/issues/13332) [#13437](https://github.com/aws/aws-cdk/issues/13437) +* **events:** imported EventBus does not correctly register source account ([#13481](https://github.com/aws/aws-cdk/issues/13481)) ([57e5404](https://github.com/aws/aws-cdk/commit/57e540432c1446f2233a9b0c0f4caba4e9e155d9)), closes [#13469](https://github.com/aws/aws-cdk/issues/13469) +* **iam:** oidc-provider can't pull from hosts requiring SNI ([#13397](https://github.com/aws/aws-cdk/issues/13397)) ([90dbfb5](https://github.com/aws/aws-cdk/commit/90dbfb5eec19559717ac6b30f25451461027e731)) +* **iam:** policy statement tries to validate tokens ([#13493](https://github.com/aws/aws-cdk/issues/13493)) ([8d592ea](https://github.com/aws/aws-cdk/commit/8d592ea89c0eda19329d5a31517522ec02ceb874)), closes [#13479](https://github.com/aws/aws-cdk/issues/13479) +* **init:** Python init template's stack ID doesn't match other languages ([#13480](https://github.com/aws/aws-cdk/issues/13480)) ([3f1c02d](https://github.com/aws/aws-cdk/commit/3f1c02dac7a50ce7caebce1e7f8953f6e4937e6b)) +* **stepfunctions:** no validation on state machine name ([#13387](https://github.com/aws/aws-cdk/issues/13387)) ([6c3d407](https://github.com/aws/aws-cdk/commit/6c3d4071746179dde30f615602592c2523daa56e)), closes [#13289](https://github.com/aws/aws-cdk/issues/13289) + ## [1.92.0](https://github.com/aws/aws-cdk/compare/v1.91.0...v1.92.0) (2021-03-06) * **ecs-patterns**: the `desiredCount` property stored on the above constructs will be optional, allowing them to be undefined. This is enabled through the `@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount` feature flag. We would recommend all CDK users to set the `@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount` flag to `true` for all of their existing applications. diff --git a/version.v1.json b/version.v1.json index c2a1515792517..097cc55f8cc18 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.92.0" + "version": "1.93.0" } From 77449f61e7075fef1240fc52becb8ea60b9ea9ad Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 11 Mar 2021 15:26:41 +0100 Subject: [PATCH 12/41] fix(lambda): fromDockerBuild output is located under /asset (#13539) Ensure `imagePath` ends with `/.` so that the content at that location is copied. See https://docs.docker.com/engine/reference/commandline/cp/ Closes #13439 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-lambda/lib/code.ts | 12 +++- .../@aws-cdk/aws-lambda/test/code.test.ts | 56 ++++++++++++++++++- .../test/docker-build-lambda/Dockerfile | 2 +- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/code.ts b/packages/@aws-cdk/aws-lambda/lib/code.ts index b4f41b2804257..fec1e1821270e 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code.ts @@ -67,9 +67,19 @@ export abstract class Code { * @param options Docker build options */ public static fromDockerBuild(path: string, options: DockerBuildAssetOptions = {}): AssetCode { + let imagePath = options.imagePath ?? '/asset/.'; + + // ensure imagePath ends with /. to copy the **content** at this path + if (imagePath.endsWith('/')) { + imagePath = `${imagePath}.`; + } else if (!imagePath.endsWith('/.')) { + imagePath = `${imagePath}/.`; + } + const assetPath = cdk.DockerImage .fromBuild(path, options) - .cp(options.imagePath ?? '/asset', options.outputPath); + .cp(imagePath, options.outputPath); + return new AssetCode(assetPath); } diff --git a/packages/@aws-cdk/aws-lambda/test/code.test.ts b/packages/@aws-cdk/aws-lambda/test/code.test.ts index 91de07a17c5a6..c976f0a1dabf2 100644 --- a/packages/@aws-cdk/aws-lambda/test/code.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/code.test.ts @@ -331,6 +331,23 @@ describe('code', () => { }); describe('lambda.Code.fromDockerBuild', () => { + let fromBuildMock: jest.SpyInstance; + let cpMock: jest.Mock; + + beforeEach(() => { + cpMock = jest.fn().mockReturnValue(path.join(__dirname, 'docker-build-lambda')); + fromBuildMock = jest.spyOn(cdk.DockerImage, 'fromBuild').mockImplementation(() => ({ + cp: cpMock, + image: 'tag', + run: jest.fn(), + toJSON: jest.fn(), + })); + }); + + afterEach(() => { + fromBuildMock.mockRestore(); + }); + test('can use the result of a Docker build as an asset', () => { // given const stack = new cdk.Stack(); @@ -346,10 +363,47 @@ describe('code', () => { // then expect(stack).toHaveResource('AWS::Lambda::Function', { Metadata: { - [cxapi.ASSET_RESOURCE_METADATA_PATH_KEY]: 'asset.38cd320fa97b348accac88e48d9cede4923f7cab270ce794c95a665be83681a8', + [cxapi.ASSET_RESOURCE_METADATA_PATH_KEY]: 'asset.fbafdbb9ae8d1bae0def415b791a93c486d18ebc63270c748abecc3ac0ab9533', [cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY]: 'Code', }, }, ResourcePart.CompleteDefinition); + + expect(fromBuildMock).toHaveBeenCalledWith(path.join(__dirname, 'docker-build-lambda'), {}); + expect(cpMock).toHaveBeenCalledWith('/asset/.', undefined); + }); + + test('fromDockerBuild appends /. to an image path not ending with a /', () => { + // given + const stack = new cdk.Stack(); + + // when + new lambda.Function(stack, 'Fn', { + code: lambda.Code.fromDockerBuild(path.join(__dirname, 'docker-build-lambda'), { + imagePath: '/my/image/path', + }), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_12_X, + }); + + // then + expect(cpMock).toHaveBeenCalledWith('/my/image/path/.', undefined); + }); + + test('fromDockerBuild appends . to an image path ending with a /', () => { + // given + const stack = new cdk.Stack(); + + // when + new lambda.Function(stack, 'Fn', { + code: lambda.Code.fromDockerBuild(path.join(__dirname, 'docker-build-lambda'), { + imagePath: '/my/image/path/', + }), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_12_X, + }); + + // then + expect(cpMock).toHaveBeenCalledWith('/my/image/path/.', undefined); }); }); }); diff --git a/packages/@aws-cdk/aws-lambda/test/docker-build-lambda/Dockerfile b/packages/@aws-cdk/aws-lambda/test/docker-build-lambda/Dockerfile index 4643fde141850..f22181359dc11 100644 --- a/packages/@aws-cdk/aws-lambda/test/docker-build-lambda/Dockerfile +++ b/packages/@aws-cdk/aws-lambda/test/docker-build-lambda/Dockerfile @@ -1,3 +1,3 @@ FROM public.ecr.aws/amazonlinux/amazonlinux:latest -COPY index.js /asset +COPY index.js /asset/ From b71efd9d12843ab4b495d53e565cec97d60748f3 Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Thu, 11 Mar 2021 16:55:21 -0700 Subject: [PATCH 13/41] feat(appmesh): add missing route match features (#13350) Adds route priority, header matching and matching by scheme and method. Closes #11645 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-appmesh/README.md | 24 ++ .../@aws-cdk/aws-appmesh/lib/route-spec.ts | 312 +++++++++++++++++- packages/@aws-cdk/aws-appmesh/lib/route.ts | 1 + .../aws-appmesh/test/integ.mesh.expected.json | 157 +++++++++ .../@aws-cdk/aws-appmesh/test/integ.mesh.ts | 30 ++ .../@aws-cdk/aws-appmesh/test/test.route.ts | 272 +++++++++++++++ 6 files changed, 778 insertions(+), 18 deletions(-) diff --git a/packages/@aws-cdk/aws-appmesh/README.md b/packages/@aws-cdk/aws-appmesh/README.md index 74aead1f02a02..63203d6b365d1 100644 --- a/packages/@aws-cdk/aws-appmesh/README.md +++ b/packages/@aws-cdk/aws-appmesh/README.md @@ -298,6 +298,30 @@ router.addRoute('route-http', { }); ``` +Add an HTTP2 route that matches based on method, scheme and header: + +```ts +router.addRoute('route-http2', { + routeSpec: appmesh.RouteSpec.http2({ + weightedTargets: [ + { + virtualNode: node, + }, + ], + match: { + prefixPath: '/', + method: appmesh.HttpRouteMatchMethod.POST, + protocol: appmesh.HttpRouteProtocol.HTTPS, + headers: [ + // All specified headers must match for the route to match. + appmesh.HttpHeaderMatch.valueIs('Content-Type', 'application/json'), + appmesh.HttpHeaderMatch.valueIsNot('Content-Type', 'application/json'), + ] + }, + }), +}); +``` + Add a single route with multiple targets and split traffic 50/50 ```ts diff --git a/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts b/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts index 11f629c4aee91..add785c02c286 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts @@ -35,6 +35,255 @@ export interface HttpRouteMatch { * and you want the route to match requests to my-service.local/metrics, your prefix should be /metrics. */ readonly prefixPath: string; + + /** + * Specifies the client request headers to match on. All specified headers + * must match for the route to match. + * + * @default - do not match on headers + */ + readonly headers?: HttpHeaderMatch[]; + + /** + * The HTTP client request method to match on. + * + * @default - do not match on request method + */ + readonly method?: HttpRouteMatchMethod; + + /** + * The client request protocol to match on. Applicable only for HTTP2 routes. + * + * @default - do not match on HTTP2 request protocol + */ + readonly protocol?: HttpRouteProtocol; +} + +/** + * Supported values for matching routes based on the HTTP request method + */ +export enum HttpRouteMatchMethod { + /** + * GET request + */ + GET = 'GET', + + /** + * HEAD request + */ + HEAD = 'HEAD', + + /** + * POST request + */ + POST = 'POST', + + /** + * PUT request + */ + PUT = 'PUT', + + /** + * DELETE request + */ + DELETE = 'DELETE', + + /** + * CONNECT request + */ + CONNECT = 'CONNECT', + + /** + * OPTIONS request + */ + OPTIONS = 'OPTIONS', + + /** + * TRACE request + */ + TRACE = 'TRACE', + + /** + * PATCH request + */ + PATCH = 'PATCH', +} + +/** + * Supported :scheme options for HTTP2 + */ +export enum HttpRouteProtocol { + /** + * Match HTTP requests + */ + HTTP = 'http', + + /** + * Match HTTPS requests + */ + HTTPS = 'https', +} + +/** + * Configuration for `HeaderMatch` + */ +export interface HttpHeaderMatchConfig { + /** + * The HTTP route header. + */ + readonly httpRouteHeader: CfnRoute.HttpRouteHeaderProperty; +} + +/** + * Used to generate header matching methods. + */ +export abstract class HttpHeaderMatch { + /** + * The value of the header with the given name in the request must match the + * specified value exactly. + * + * @param headerName the name of the HTTP header to match against + * @param headerValue The exact value to test against + */ + static valueIs(headerName: string, headerValue: string): HttpHeaderMatch { + return new HeaderMatchImpl(headerName, false, { exact: headerValue }); + } + + /** + * The value of the header with the given name in the request must not match + * the specified value exactly. + * + * @param headerName the name of the HTTP header to match against + * @param headerValue The exact value to test against + */ + static valueIsNot(headerName: string, headerValue: string): HttpHeaderMatch { + return new HeaderMatchImpl(headerName, true, { exact: headerValue }); + } + + /** + * The value of the header with the given name in the request must start with + * the specified characters. + * + * @param headerName the name of the HTTP header to match against + * @param prefix The prefix to test against + */ + static valueStartsWith(headerName: string, prefix: string): HttpHeaderMatch { + return new HeaderMatchImpl(headerName, false, { prefix }); + } + + /** + * The value of the header with the given name in the request must not start + * with the specified characters. + * + * @param headerName the name of the HTTP header to match against + * @param prefix The prefix to test against + */ + static valueDoesNotStartWith(headerName: string, prefix: string): HttpHeaderMatch { + return new HeaderMatchImpl(headerName, true, { prefix }); + } + + /** + * The value of the header with the given name in the request must end with + * the specified characters. + * + * @param headerName the name of the HTTP header to match against + * @param suffix The suffix to test against + */ + static valueEndsWith(headerName: string, suffix: string): HttpHeaderMatch { + return new HeaderMatchImpl(headerName, false, { suffix }); + } + + /** + * The value of the header with the given name in the request must not end + * with the specified characters. + * + * @param headerName the name of the HTTP header to match against + * @param suffix The suffix to test against + */ + static valueDoesNotEndWith(headerName: string, suffix: string): HttpHeaderMatch { + return new HeaderMatchImpl(headerName, true, { suffix }); + } + + /** + * The value of the header with the given name in the request must include + * the specified characters. + * + * @param headerName the name of the HTTP header to match against + * @param regex The regex to test against + */ + static valueMatchesRegex(headerName: string, regex: string): HttpHeaderMatch { + return new HeaderMatchImpl(headerName, false, { regex }); + } + + /** + * The value of the header with the given name in the request must not + * include the specified characters. + * + * @param headerName the name of the HTTP header to match against + * @param regex The regex to test against + */ + static valueDoesNotMatchRegex(headerName: string, regex: string): HttpHeaderMatch { + return new HeaderMatchImpl(headerName, true, { regex }); + } + + /** + * The value of the header with the given name in the request must be in a + * range of values. + * + * @param headerName the name of the HTTP header to match against + * @param start Match on values starting at and including this value + * @param end Match on values up to but not including this value + */ + static valuesIsInRange(headerName: string, start: number, end: number): HttpHeaderMatch { + return new HeaderMatchImpl(headerName, false, { + range: { + start, + end, + }, + }); + } + + /** + * The value of the header with the given name in the request must not be in + * a range of values. + * + * @param headerName the name of the HTTP header to match against + * @param start Match on values starting at and including this value + * @param end Match on values up to but not including this value + */ + static valuesIsNotInRange(headerName: string, start: number, end: number): HttpHeaderMatch { + return new HeaderMatchImpl(headerName, true, { + range: { + start, + end, + }, + }); + } + + /** + * Returns the header match configuration. + */ + abstract bind(scope: Construct): HttpHeaderMatchConfig; +} + +class HeaderMatchImpl extends HttpHeaderMatch { + constructor( + private readonly headerName: string, + private readonly invert: boolean, + private readonly matchProperty: CfnRoute.HeaderMatchMethodProperty, + ) { + super(); + } + + bind(_scope: Construct): HttpHeaderMatchConfig { + return { + httpRouteHeader: { + name: this.headerName, + invert: this.invert, + match: this.matchProperty, + }, + }; + } } /** @@ -47,10 +296,23 @@ export interface GrpcRouteMatch { readonly serviceName: string; } +/** + * Base options for all route specs. + */ +export interface RouteSpecOptionsBase { + /** + * The priority for the route. Routes are matched based on the specified + * value, where 0 is the highest priority. + * + * @default - no particular priority + */ + readonly priority?: number; +} + /** * Properties specific for HTTP Based Routes */ -export interface HttpRouteSpecOptions { +export interface HttpRouteSpecOptions extends RouteSpecOptionsBase { /** * The criterion for determining a request match for this Route * @@ -149,7 +411,7 @@ export enum TcpRetryEvent { /** * Properties specific for a TCP Based Routes */ -export interface TcpRouteSpecOptions { +export interface TcpRouteSpecOptions extends RouteSpecOptionsBase { /** * List of targets that traffic is routed to when a request matches the route */ @@ -166,7 +428,7 @@ export interface TcpRouteSpecOptions { /** * Properties specific for a GRPC Based Routes */ -export interface GrpcRouteSpecOptions { +export interface GrpcRouteSpecOptions extends RouteSpecOptionsBase { /** * The criterion for determining a request match for this Route */ @@ -274,6 +536,14 @@ export interface RouteSpecConfig { * @default - no tcp spec */ readonly tcpRouteSpec?: CfnRoute.TcpRouteProperty; + + /** + * The priority for the route. Routes are matched based on the specified + * value, where 0 is the highest priority. + * + * @default - no particular priority + */ + readonly priority?: number; } /** @@ -317,24 +587,11 @@ export abstract class RouteSpec { } class HttpRouteSpec extends RouteSpec { - /** - * Type of route you are creating - */ + public readonly priority?: number; public readonly protocol: Protocol; - - /** - * The criteria for determining a request match - */ public readonly match?: HttpRouteMatch; - - /** - * The criteria for determining a timeout configuration - */ public readonly timeout?: HttpTimeout; - /** - * List of targets that traffic is routed to when a request matches the route - */ public readonly weightedTargets: WeightedTarget[]; /** @@ -348,6 +605,7 @@ class HttpRouteSpec extends RouteSpec { this.match = props.match; this.weightedTargets = props.weightedTargets; this.timeout = props.timeout; + this.priority = props.priority; if (props.retryPolicy) { const httpRetryEvents = props.retryPolicy.httpRetryEvents ?? []; @@ -365,7 +623,7 @@ class HttpRouteSpec extends RouteSpec { } } - public bind(_scope: Construct): RouteSpecConfig { + public bind(scope: Construct): RouteSpecConfig { const prefixPath = this.match ? this.match.prefixPath : '/'; if (prefixPath[0] != '/') { throw new Error(`Prefix Path must start with \'/\', got: ${prefixPath}`); @@ -377,11 +635,15 @@ class HttpRouteSpec extends RouteSpec { }, match: { prefix: prefixPath, + headers: this.match?.headers?.map(header => header.bind(scope).httpRouteHeader), + method: this.match?.method, + scheme: this.match?.protocol, }, timeout: renderTimeout(this.timeout), retryPolicy: this.retryPolicy ? renderHttpRetryPolicy(this.retryPolicy) : undefined, }; return { + priority: this.priority, httpRouteSpec: this.protocol === Protocol.HTTP ? httpConfig : undefined, http2RouteSpec: this.protocol === Protocol.HTTP2 ? httpConfig : undefined, }; @@ -389,6 +651,11 @@ class HttpRouteSpec extends RouteSpec { } class TcpRouteSpec extends RouteSpec { + /** + * The priority for the route. + */ + public readonly priority?: number; + /* * List of targets that traffic is routed to when a request matches the route */ @@ -403,10 +670,12 @@ class TcpRouteSpec extends RouteSpec { super(); this.weightedTargets = props.weightedTargets; this.timeout = props.timeout; + this.priority = props.priority; } public bind(_scope: Construct): RouteSpecConfig { return { + priority: this.priority, tcpRouteSpec: { action: { weightedTargets: renderWeightedTargets(this.weightedTargets), @@ -418,6 +687,11 @@ class TcpRouteSpec extends RouteSpec { } class GrpcRouteSpec extends RouteSpec { + /** + * The priority for the route. + */ + public readonly priority?: number; + public readonly weightedTargets: WeightedTarget[]; public readonly match: GrpcRouteMatch; public readonly timeout?: GrpcTimeout; @@ -432,6 +706,7 @@ class GrpcRouteSpec extends RouteSpec { this.weightedTargets = props.weightedTargets; this.match = props.match; this.timeout = props.timeout; + this.priority = props.priority; if (props.retryPolicy) { const grpcRetryEvents = props.retryPolicy.grpcRetryEvents ?? []; @@ -453,6 +728,7 @@ class GrpcRouteSpec extends RouteSpec { public bind(_scope: Construct): RouteSpecConfig { return { + priority: this.priority, grpcRouteSpec: { action: { weightedTargets: renderWeightedTargets(this.weightedTargets), diff --git a/packages/@aws-cdk/aws-appmesh/lib/route.ts b/packages/@aws-cdk/aws-appmesh/lib/route.ts index 7b9bd2aeb94d2..7800e3e08e53f 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/route.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/route.ts @@ -126,6 +126,7 @@ export class Route extends cdk.Resource implements IRoute { httpRoute: spec.httpRouteSpec, http2Route: spec.http2RouteSpec, grpcRoute: spec.grpcRouteSpec, + priority: spec.priority, }, }); diff --git a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json index f951953924b44..c3139e2b75582 100644 --- a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json +++ b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json @@ -625,6 +625,124 @@ "RouteName": "route-3" } }, + "meshrouterroutematchingACC12F04": { + "Type": "AWS::AppMesh::Route", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "meshACDFE68E", + "MeshName" + ] + }, + "Spec": { + "Http2Route": { + "Action": { + "WeightedTargets": [ + { + "VirtualNode": { + "Fn::GetAtt": [ + "meshnode3D2A19CF2", + "VirtualNodeName" + ] + }, + "Weight": 1 + } + ] + }, + "Match": { + "Headers": [ + { + "Invert": false, + "Match": { + "Exact": "application/json" + }, + "Name": "Content-Type" + }, + { + "Invert": false, + "Match": { + "Prefix": "application/json" + }, + "Name": "Content-Type" + }, + { + "Invert": false, + "Match": { + "Suffix": "application/json" + }, + "Name": "Content-Type" + }, + { + "Invert": false, + "Match": { + "Regex": "application/.*" + }, + "Name": "Content-Type" + }, + { + "Invert": false, + "Match": { + "Range": { + "End": 5, + "Start": 1 + } + }, + "Name": "Content-Type" + }, + { + "Invert": true, + "Match": { + "Exact": "application/json" + }, + "Name": "Content-Type" + }, + { + "Invert": true, + "Match": { + "Prefix": "application/json" + }, + "Name": "Content-Type" + }, + { + "Invert": true, + "Match": { + "Suffix": "application/json" + }, + "Name": "Content-Type" + }, + { + "Invert": true, + "Match": { + "Regex": "application/.*" + }, + "Name": "Content-Type" + }, + { + "Invert": true, + "Match": { + "Range": { + "End": 5, + "Start": 1 + } + }, + "Name": "Content-Type" + } + ], + "Method": "POST", + "Prefix": "/", + "Scheme": "https" + } + } + }, + "VirtualRouterName": { + "Fn::GetAtt": [ + "meshrouter81B8087E", + "VirtualRouterName" + ] + }, + "RouteName": "route-matching" + } + }, "meshrouterroutehttp2retryCC41345F": { "Type": "AWS::AppMesh::Route", "Properties": { @@ -676,6 +794,45 @@ "RouteName": "route-http2-retry" } }, + "meshrouterroute53F46B0FE": { + "Type": "AWS::AppMesh::Route", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "meshACDFE68E", + "MeshName" + ] + }, + "Spec": { + "Http2Route": { + "Action": { + "WeightedTargets": [ + { + "VirtualNode": { + "Fn::GetAtt": [ + "meshnode2092BA426", + "VirtualNodeName" + ] + }, + "Weight": 1 + } + ] + }, + "Match": { + "Prefix": "/" + } + }, + "Priority": 10 + }, + "VirtualRouterName": { + "Fn::GetAtt": [ + "meshrouter81B8087E", + "VirtualRouterName" + ] + }, + "RouteName": "route-5" + } + }, "meshrouterroutegrpcretry9BEB798A": { "Type": "AWS::AppMesh::Route", "Properties": { diff --git a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts index c1e909e38d75b..4b62e8e12ee30 100644 --- a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts @@ -140,6 +140,29 @@ router.addRoute('route-3', { }), }); +router.addRoute('route-matching', { + routeSpec: appmesh.RouteSpec.http2({ + weightedTargets: [{ virtualNode: node3 }], + match: { + prefixPath: '/', + method: appmesh.HttpRouteMatchMethod.POST, + protocol: appmesh.HttpRouteProtocol.HTTPS, + headers: [ + appmesh.HttpHeaderMatch.valueIs('Content-Type', 'application/json'), + appmesh.HttpHeaderMatch.valueStartsWith('Content-Type', 'application/json'), + appmesh.HttpHeaderMatch.valueEndsWith('Content-Type', 'application/json'), + appmesh.HttpHeaderMatch.valueMatchesRegex('Content-Type', 'application/.*'), + appmesh.HttpHeaderMatch.valuesIsInRange('Content-Type', 1, 5), + appmesh.HttpHeaderMatch.valueIsNot('Content-Type', 'application/json'), + appmesh.HttpHeaderMatch.valueDoesNotStartWith('Content-Type', 'application/json'), + appmesh.HttpHeaderMatch.valueDoesNotEndWith('Content-Type', 'application/json'), + appmesh.HttpHeaderMatch.valueDoesNotMatchRegex('Content-Type', 'application/.*'), + appmesh.HttpHeaderMatch.valuesIsNotInRange('Content-Type', 1, 5), + ], + }, + }), +}); + router.addRoute('route-http2-retry', { routeSpec: appmesh.RouteSpec.http2({ weightedTargets: [{ virtualNode: node3 }], @@ -152,6 +175,13 @@ router.addRoute('route-http2-retry', { }), }); +router.addRoute('route-5', { + routeSpec: appmesh.RouteSpec.http2({ + priority: 10, + weightedTargets: [{ virtualNode: node2 }], + }), +}); + router.addRoute('route-grpc-retry', { routeSpec: appmesh.RouteSpec.grpc({ weightedTargets: [{ virtualNode: node3 }], diff --git a/packages/@aws-cdk/aws-appmesh/test/test.route.ts b/packages/@aws-cdk/aws-appmesh/test/test.route.ts index 43c2d942a669b..b3c1ae674a6f4 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.route.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.route.ts @@ -576,6 +576,278 @@ export = { }, }, + 'should match routes based on headers'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + const router = new appmesh.VirtualRouter(stack, 'router', { + mesh, + }); + + const virtualNode = mesh.addVirtualNode('test-node', { + serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), + listeners: [appmesh.VirtualNodeListener.http()], + }); + + // WHEN + router.addRoute('route', { + routeSpec: appmesh.RouteSpec.http2({ + weightedTargets: [{ virtualNode }], + match: { + prefixPath: '/', + headers: [ + appmesh.HttpHeaderMatch.valueIs('Content-Type', 'application/json'), + appmesh.HttpHeaderMatch.valueIsNot('Content-Type', 'text/html'), + appmesh.HttpHeaderMatch.valueStartsWith('Content-Type', 'application/'), + appmesh.HttpHeaderMatch.valueDoesNotStartWith('Content-Type', 'text/'), + appmesh.HttpHeaderMatch.valueEndsWith('Content-Type', '/json'), + appmesh.HttpHeaderMatch.valueDoesNotEndWith('Content-Type', '/json+foobar'), + appmesh.HttpHeaderMatch.valueMatchesRegex('Content-Type', 'application/.*'), + appmesh.HttpHeaderMatch.valueDoesNotMatchRegex('Content-Type', 'text/.*'), + appmesh.HttpHeaderMatch.valuesIsInRange('Max-Forward', 1, 5), + appmesh.HttpHeaderMatch.valuesIsNotInRange('Max-Forward', 1, 5), + ], + }, + }), + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::Route', { + Spec: { + Http2Route: { + Match: { + Prefix: '/', + Headers: [ + { + Invert: false, + Match: { Exact: 'application/json' }, + Name: 'Content-Type', + }, + { + Invert: true, + Match: { Exact: 'text/html' }, + Name: 'Content-Type', + }, + { + Invert: false, + Match: { Prefix: 'application/' }, + Name: 'Content-Type', + }, + { + Invert: true, + Match: { Prefix: 'text/' }, + Name: 'Content-Type', + }, + { + Invert: false, + Match: { Suffix: '/json' }, + Name: 'Content-Type', + }, + { + Invert: true, + Match: { Suffix: '/json+foobar' }, + Name: 'Content-Type', + }, + { + Invert: false, + Match: { Regex: 'application/.*' }, + Name: 'Content-Type', + }, + { + Invert: true, + Match: { Regex: 'text/.*' }, + Name: 'Content-Type', + }, + { + Invert: false, + Match: { + Range: { + End: 5, + Start: 1, + }, + }, + Name: 'Max-Forward', + }, + { + Invert: true, + Match: { + Range: { + End: 5, + Start: 1, + }, + }, + Name: 'Max-Forward', + }, + ], + }, + }, + }, + })); + + test.done(); + }, + + 'should match routes based on method'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + const router = new appmesh.VirtualRouter(stack, 'router', { + mesh, + }); + + const virtualNode = mesh.addVirtualNode('test-node', { + serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), + listeners: [appmesh.VirtualNodeListener.http()], + }); + + // WHEN + router.addRoute('route', { + routeSpec: appmesh.RouteSpec.http2({ + weightedTargets: [{ virtualNode }], + match: { + prefixPath: '/', + method: appmesh.HttpRouteMatchMethod.GET, + }, + }), + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::Route', { + Spec: { + Http2Route: { + Match: { + Prefix: '/', + Method: 'GET', + }, + }, + }, + })); + + test.done(); + }, + + 'should match routes based on scheme'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + const router = new appmesh.VirtualRouter(stack, 'router', { + mesh, + }); + + const virtualNode = mesh.addVirtualNode('test-node', { + serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), + listeners: [appmesh.VirtualNodeListener.http()], + }); + + // WHEN + router.addRoute('route', { + routeSpec: appmesh.RouteSpec.http2({ + weightedTargets: [{ virtualNode }], + match: { + prefixPath: '/', + protocol: appmesh.HttpRouteProtocol.HTTP, + }, + }), + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::Route', { + Spec: { + Http2Route: { + Match: { + Prefix: '/', + Scheme: 'http', + }, + }, + }, + })); + + test.done(); + }, + + 'should allow route priority'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + const router = new appmesh.VirtualRouter(stack, 'router', { + mesh, + }); + + const virtualNode = mesh.addVirtualNode('test-node', { + serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), + listeners: [appmesh.VirtualNodeListener.http()], + }); + + // WHEN + router.addRoute('http2', { + routeSpec: appmesh.RouteSpec.http2({ + priority: 0, + weightedTargets: [{ virtualNode }], + }), + }); + router.addRoute('http', { + routeSpec: appmesh.RouteSpec.http({ + priority: 10, + weightedTargets: [{ virtualNode }], + }), + }); + router.addRoute('grpc', { + routeSpec: appmesh.RouteSpec.grpc({ + priority: 20, + weightedTargets: [{ virtualNode }], + match: { + serviceName: 'test', + }, + }), + }); + router.addRoute('tcp', { + routeSpec: appmesh.RouteSpec.tcp({ + priority: 30, + weightedTargets: [{ virtualNode }], + }), + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::Route', { + Spec: { + Priority: 0, + Http2Route: {}, + }, + })); + expect(stack).to(haveResourceLike('AWS::AppMesh::Route', { + Spec: { + Priority: 10, + HttpRoute: {}, + }, + })); + expect(stack).to(haveResourceLike('AWS::AppMesh::Route', { + Spec: { + Priority: 20, + GrpcRoute: {}, + }, + })); + expect(stack).to(haveResourceLike('AWS::AppMesh::Route', { + Spec: { + Priority: 30, + TcpRoute: {}, + }, + })); + + test.done(); + }, + 'Can import Routes using an ARN'(test: Test) { const app = new cdk.App(); // GIVEN From d3f428435976c55ca950279cfc841665fd504370 Mon Sep 17 00:00:00 2001 From: Alex Johnson Date: Thu, 11 Mar 2021 16:53:55 -0800 Subject: [PATCH 14/41] fix(appmesh): Move Client Policy from Virtual Service to backend structure (#12943) @sshver: > Client Policies are inherently not related to the Virtual Service. It should be thought of as the client (the VN) telling envoy what connections they want to allow to the server (the Virtual Service). The server shouldn't be the one to define what policies are used to enforce connections with itself. ## Description of changes I refactored the client policy from Virtual Service to a separate backend structure. This mirrors how our API is designed. Also ran `npm run lint -- --fix` and removed some comments to fix lint warnings. ```ts /* Old backend defaults */ backendsDefaultClientPolicy: appmesh.ClientPolicy.fileTrust({ certificateChain: 'path-to-certificate', }), /* result of this PR */ backendDefaults: { clientPolicy: appmesh.ClientPolicy.fileTrust({ certificateChain: 'path-to-certificate', }), }, ``` ```ts /* Old Virtual Service with client policy */ const service1 = new appmesh.VirtualService(stack, 'service-1', { virtualServiceName: 'service1.domain.local', virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), clientPolicy: appmesh.ClientPolicy.fileTrust({ certificateChain: 'path-to-certificate', ports: [8080, 8081], }), }); /* result of this PR; client policy is defined in the Virtual Node */ const service1 = new appmesh.VirtualService(stack, 'service-1', { virtualServiceName: 'service1.domain.local', virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), }); const node = new appmesh.VirtualNode(stack, 'test-node', { mesh, serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), }); node.addBackend({ virtualService: service1, clientPolicy: appmesh.ClientPolicy.fileTrust({ certificateChain: 'path-to-certificate', ports: [8080, 8081], }), }); ``` BREAKING CHANGE: Backend, backend default and Virtual Service client policies structures are being altered * **appmesh**: you must use the backend default interface to define backend defaults in `VirtualGateway`. The property name also changed from `backendsDefaultClientPolicy` to `backendDefaults` * **appmesh**: you must use the backend default interface to define backend defaults in `VirtualNode`, (the property name also changed from `backendsDefaultClientPolicy` to `backendDefaults`), and the `Backend` class to define a backend * **appmesh**: you can no longer attach a client policy to a `VirtualService` Resolves #11996 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/extensions/appmesh.ts | 2 +- packages/@aws-cdk/aws-appmesh/README.md | 24 +++--- .../aws-appmesh/lib/shared-interfaces.ts | 79 +++++++++++++++++++ .../aws-appmesh/lib/virtual-gateway.ts | 11 ++- .../@aws-cdk/aws-appmesh/lib/virtual-node.ts | 23 +++--- .../aws-appmesh/lib/virtual-service.ts | 24 ------ .../@aws-cdk/aws-appmesh/test/integ.mesh.ts | 35 ++++---- .../aws-appmesh/test/test.health-check.ts | 2 - .../@aws-cdk/aws-appmesh/test/test.mesh.ts | 4 +- .../aws-appmesh/test/test.virtual-gateway.ts | 8 +- .../aws-appmesh/test/test.virtual-node.ts | 21 ++--- .../aws-appmesh/test/test.virtual-router.ts | 18 ++--- 12 files changed, 153 insertions(+), 98 deletions(-) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts index 7749683fb4235..95220dc1ea3b4 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts @@ -347,7 +347,7 @@ export class AppMeshExtension extends ServiceExtension { // Next update the app mesh config so that the local Envoy // proxy on this service knows how to route traffic to // nodes from the other service. - this.virtualNode.addBackend(otherAppMesh.virtualService); + this.virtualNode.addBackend(appmesh.Backend.virtualService(otherAppMesh.virtualService)); } private routeSpec(weightedTargets: appmesh.WeightedTarget[], serviceName: string): appmesh.RouteSpec { diff --git a/packages/@aws-cdk/aws-appmesh/README.md b/packages/@aws-cdk/aws-appmesh/README.md index 63203d6b365d1..678bbe22a2c20 100644 --- a/packages/@aws-cdk/aws-appmesh/README.md +++ b/packages/@aws-cdk/aws-appmesh/README.md @@ -186,9 +186,11 @@ const node = new VirtualNode(this, 'node', { idle: cdk.Duration.seconds(5), }, })], - backendsDefaultClientPolicy: appmesh.ClientPolicy.fileTrust({ - certificateChain: '/keys/local_cert_chain.pem', - }), + backendDefaults: { + clientPolicy: appmesh.ClientPolicy.fileTrust({ + certificateChain: '/keys/local_cert_chain.pem', + }), + }, accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), }); @@ -230,14 +232,14 @@ const virtualService = new appmesh.VirtualService(stack, 'service-1', { }), }); -node.addBackend(virtualService); +node.addBackend(appmesh.Backend.virtualService(virtualService)); ``` The `listeners` property can be left blank and added later with the `node.addListener()` method. The `healthcheck` and `timeout` properties are optional but if specifying a listener, the `port` must be added. The `backends` property can be added with `node.addBackend()`. We define a virtual service and add it to the virtual node to allow egress traffic to other node. -The `backendsDefaultClientPolicy` property are added to the node while creating the virtual node. These are virtual node's service backends client policy defaults. +The `backendDefaults` property are added to the node while creating the virtual node. These are virtual node's default settings for all backends. ## Adding TLS to a listener @@ -437,10 +439,12 @@ const gateway = new appmesh.VirtualGateway(stack, 'gateway', { interval: cdk.Duration.seconds(10), }, })], - backendsDefaultClientPolicy: appmesh.ClientPolicy.acmTrust({ - certificateAuthorities: [acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'certificate', certificateAuthorityArn)], - ports: [8080, 8081], - }), + backendDefaults: { + clientPolicy: appmesh.ClientPolicy.acmTrust({ + certificateAuthorities: [acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'certificate', certificateAuthorityArn)], + ports: [8080, 8081], + }), + }, accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), virtualGatewayName: 'virtualGateway', }); @@ -464,7 +468,7 @@ const gateway = mesh.addVirtualGateway('gateway', { The listeners field can be omitted which will default to an HTTP Listener on port 8080. A gateway route can be added using the `gateway.addGatewayRoute()` method. -The `backendsDefaultClientPolicy` property are added to the node while creating the virtual gateway. These are virtual gateway's service backends client policy defaults. +The `backendDefaults` property is added to the node while creating the virtual gateway. These are virtual gateway's default settings for all backends. ## Adding a Gateway Route diff --git a/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts b/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts index 831db66e49e0c..007f67c4a7a9b 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts @@ -1,5 +1,7 @@ import * as cdk from '@aws-cdk/core'; import { CfnVirtualGateway, CfnVirtualNode } from './appmesh.generated'; +import { ClientPolicy } from './client-policy'; +import { IVirtualService } from './virtual-service'; // 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 @@ -194,3 +196,80 @@ class FileAccessLog extends AccessLog { } } +/** + * Represents the properties needed to define backend defaults + */ +export interface BackendDefaults { + /** + * Client policy for backend defaults + * + * @default none + */ + readonly clientPolicy?: ClientPolicy; +} + +/** + * Represents the properties needed to define a Virtual Service backend + */ +export interface VirtualServiceBackendOptions { + + /** + * Client policy for the backend + * + * @default none + */ + readonly clientPolicy?: ClientPolicy; +} + +/** + * Properties for a backend + */ +export interface BackendConfig { + /** + * Config for a Virtual Service backend + */ + readonly virtualServiceBackend: CfnVirtualNode.BackendProperty; +} + + +/** + * Contains static factory methods to create backends + */ +export abstract class Backend { + /** + * Construct a Virtual Service backend + */ + public static virtualService(virtualService: IVirtualService, props: VirtualServiceBackendOptions = {}): Backend { + return new VirtualServiceBackend(virtualService, props.clientPolicy); + } + + /** + * Return backend config + */ + public abstract bind(_scope: Construct): BackendConfig; +} + +/** + * Represents the properties needed to define a Virtual Service backend + */ +class VirtualServiceBackend extends Backend { + + constructor (private readonly virtualService: IVirtualService, + private readonly clientPolicy: ClientPolicy | undefined) { + super(); + } + + /** + * Return config for a Virtual Service backend + */ + public bind(_scope: Construct): BackendConfig { + return { + virtualServiceBackend: { + virtualService: { + virtualServiceName: this.virtualService.virtualServiceName, + clientPolicy: this.clientPolicy?.bind(_scope).clientPolicy, + }, + }, + }; + } +} diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway.ts index 1e1144fed1038..d2f0a873a0849 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway.ts @@ -1,10 +1,9 @@ import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnVirtualGateway } from './appmesh.generated'; -import { ClientPolicy } from './client-policy'; import { GatewayRoute, GatewayRouteBaseProps } from './gateway-route'; import { IMesh, Mesh } from './mesh'; -import { AccessLog } from './shared-interfaces'; +import { AccessLog, BackendDefaults } from './shared-interfaces'; import { VirtualGatewayListener, VirtualGatewayListenerConfig } from './virtual-gateway-listener'; /** @@ -66,7 +65,7 @@ export interface VirtualGatewayBaseProps { * * @default - No Config */ - readonly backendsDefaultClientPolicy?: ClientPolicy; + readonly backendDefaults?: BackendDefaults; } /** @@ -180,7 +179,11 @@ export class VirtualGateway extends VirtualGatewayBase { meshName: this.mesh.meshName, spec: { listeners: this.listeners.map(listener => listener.listener), - backendDefaults: props.backendsDefaultClientPolicy?.bind(this), + backendDefaults: props.backendDefaults !== undefined + ? { + clientPolicy: props.backendDefaults?.clientPolicy?.bind(this).clientPolicy, + } + : undefined, logging: accessLogging !== undefined ? { accessLog: accessLogging.virtualGatewayAccessLog, } : undefined, diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts index 2cf56c74631a2..60ca92bb142ca 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts @@ -1,12 +1,10 @@ import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnVirtualNode } from './appmesh.generated'; -import { ClientPolicy } from './client-policy'; import { IMesh, Mesh } from './mesh'; import { ServiceDiscovery } from './service-discovery'; -import { AccessLog } from './shared-interfaces'; +import { AccessLog, BackendDefaults, Backend } from './shared-interfaces'; import { VirtualNodeListener, VirtualNodeListenerConfig } from './virtual-node-listener'; -import { IVirtualService } from './virtual-service'; /** * Interface which all VirtualNode based classes must implement @@ -61,7 +59,7 @@ export interface VirtualNodeBaseProps { * * @default - No backends */ - readonly backends?: IVirtualService[]; + readonly backends?: Backend[]; /** * Initial listener for the virtual node @@ -82,7 +80,7 @@ export interface VirtualNodeBaseProps { * * @default - No Config */ - readonly backendsDefaultClientPolicy?: ClientPolicy; + readonly backendDefaults?: BackendDefaults; } /** @@ -185,7 +183,11 @@ export class VirtualNode extends VirtualNodeBase { spec: { backends: cdk.Lazy.anyValue({ produce: () => this.backends }, { omitEmptyArray: true }), listeners: cdk.Lazy.anyValue({ produce: () => this.listeners.map(listener => listener.listener) }, { omitEmptyArray: true }), - backendDefaults: props.backendsDefaultClientPolicy?.bind(this), + backendDefaults: props.backendDefaults !== undefined + ? { + clientPolicy: props.backendDefaults?.clientPolicy?.bind(this).clientPolicy, + } + : undefined, serviceDiscovery: { dns: serviceDiscovery?.dns, awsCloudMap: serviceDiscovery?.cloudmap, @@ -214,13 +216,8 @@ export class VirtualNode extends VirtualNodeBase { /** * Add a Virtual Services that this node is expected to send outbound traffic to */ - public addBackend(virtualService: IVirtualService) { - this.backends.push({ - virtualService: { - virtualServiceName: virtualService.virtualServiceName, - clientPolicy: virtualService.clientPolicy?.bind(this).clientPolicy, - }, - }); + public addBackend(backend: Backend) { + this.backends.push(backend.bind(this).virtualServiceBackend); } } diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts index 5685b8b08c1f8..d41b47d554178 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts @@ -1,7 +1,6 @@ import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnVirtualService } from './appmesh.generated'; -import { ClientPolicy } from './client-policy'; import { IMesh, Mesh } from './mesh'; import { IVirtualNode } from './virtual-node'; import { IVirtualRouter } from './virtual-router'; @@ -28,11 +27,6 @@ export interface IVirtualService extends cdk.IResource { * The Mesh which the VirtualService belongs to */ readonly mesh: IMesh; - - /** - * Client policy for this Virtual Service - */ - readonly clientPolicy?: ClientPolicy; } /** @@ -50,13 +44,6 @@ export interface VirtualServiceProps { */ readonly virtualServiceName?: string; - /** - * Client policy for this Virtual Service - * - * @default - none - */ - readonly clientPolicy?: ClientPolicy; - /** * The VirtualNode or VirtualRouter which the VirtualService uses as its provider */ @@ -90,7 +77,6 @@ export class VirtualService extends cdk.Resource implements IVirtualService { return new class extends cdk.Resource implements IVirtualService { readonly virtualServiceName = attrs.virtualServiceName; readonly mesh = attrs.mesh; - readonly clientPolicy = attrs.clientPolicy; readonly virtualServiceArn = cdk.Stack.of(this).formatArn({ service: 'appmesh', resource: `mesh/${attrs.mesh.meshName}/virtualService`, @@ -114,14 +100,11 @@ export class VirtualService extends cdk.Resource implements IVirtualService { */ public readonly mesh: IMesh; - public readonly clientPolicy?: ClientPolicy; - constructor(scope: Construct, id: string, props: VirtualServiceProps) { super(scope, id, { physicalName: props.virtualServiceName || cdk.Lazy.string({ produce: () => cdk.Names.uniqueId(this) }), }); - this.clientPolicy = props.clientPolicy; const providerConfig = props.virtualServiceProvider.bind(this); this.mesh = providerConfig.mesh; @@ -160,13 +143,6 @@ export interface VirtualServiceAttributes { * The Mesh which the VirtualService belongs to */ readonly mesh: IMesh; - - /** - * Client policy for this Virtual Service - * - * @default - none - */ - readonly clientPolicy?: ClientPolicy; } /** diff --git a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts index 4b62e8e12ee30..68709def26f95 100644 --- a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts @@ -36,16 +36,15 @@ const node = mesh.addVirtualNode('node', { path: '/check-path', }, })], - backends: [ - virtualService, - ], + backends: [appmesh.Backend.virtualService(virtualService)], }); -node.addBackend(new appmesh.VirtualService(stack, 'service-2', { - virtualServiceName: 'service2.domain.local', - virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), -}), -); +node.addBackend(appmesh.Backend.virtualService( + new appmesh.VirtualService(stack, 'service-2', { + virtualServiceName: 'service2.domain.local', + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), + }), +)); router.addRoute('route-1', { routeSpec: appmesh.RouteSpec.http({ @@ -78,15 +77,17 @@ const node2 = mesh.addVirtualNode('node2', { unhealthyThreshold: 2, }, })], - backendsDefaultClientPolicy: appmesh.ClientPolicy.fileTrust({ - certificateChain: 'path/to/cert', - }), - backends: [ + backendDefaults: { + clientPolicy: appmesh.ClientPolicy.fileTrust({ + certificateChain: 'path/to/cert', + }), + }, + backends: [appmesh.Backend.virtualService( new appmesh.VirtualService(stack, 'service-3', { virtualServiceName: 'service3.domain.local', virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), }), - ], + )], }); const node3 = mesh.addVirtualNode('node3', { @@ -102,9 +103,11 @@ const node3 = mesh.addVirtualNode('node3', { unhealthyThreshold: 2, }, })], - backendsDefaultClientPolicy: appmesh.ClientPolicy.fileTrust({ - certificateChain: 'path-to-certificate', - }), + backendDefaults: { + clientPolicy: appmesh.ClientPolicy.fileTrust({ + certificateChain: 'path-to-certificate', + }), + }, accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), }); diff --git a/packages/@aws-cdk/aws-appmesh/test/test.health-check.ts b/packages/@aws-cdk/aws-appmesh/test/test.health-check.ts index 7eec2b6d450b9..1ba7dc425da07 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.health-check.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.health-check.ts @@ -66,8 +66,6 @@ export = { // THEN test.doesNotThrow(() => toThrow(min)); test.doesNotThrow(() => toThrow(max)); - // falsy, falls back to portMapping.port - // test.throws(() => toThrow(min - 1), /below the minimum threshold/); test.throws(() => toThrow(max + 1), /above the maximum threshold/); test.done(); diff --git a/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts b/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts index ce50c1402a7c3..5c9c1cea7a9a1 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts @@ -270,9 +270,7 @@ export = { listeners: [appmesh.VirtualNodeListener.http({ port: 8080, })], - backends: [ - service1, - ], + backends: [appmesh.Backend.virtualService(service1)], }); // THEN diff --git a/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts b/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts index b9d3ed70cae43..25b7974983f2a 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts @@ -392,9 +392,11 @@ export = { new appmesh.VirtualGateway(stack, 'virtual-gateway', { virtualGatewayName: 'virtual-gateway', mesh: mesh, - backendsDefaultClientPolicy: appmesh.ClientPolicy.fileTrust({ - certificateChain: 'path-to-certificate', - }), + backendDefaults: { + clientPolicy: appmesh.ClientPolicy.fileTrust({ + certificateChain: 'path-to-certificate', + }), + }, }); // THEN diff --git a/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts b/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts index 4337973230854..c09bdef5badbd 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts @@ -29,10 +29,10 @@ export = { const node = new appmesh.VirtualNode(stack, 'test-node', { mesh, serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), - backends: [service1], + backends: [appmesh.Backend.virtualService(service1)], }); - node.addBackend(service2); + node.addBackend(appmesh.Backend.virtualService(service2)); // THEN expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', { @@ -272,10 +272,12 @@ export = { new appmesh.VirtualNode(stack, 'test-node', { mesh, serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), - backendsDefaultClientPolicy: appmesh.ClientPolicy.acmTrust({ - certificateAuthorities: [acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'certificate', certificateAuthorityArn)], - ports: [8080, 8081], - }), + backendDefaults: { + clientPolicy: appmesh.ClientPolicy.acmTrust({ + certificateAuthorities: [acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'certificate', certificateAuthorityArn)], + ports: [8080, 8081], + }), + }, }); // THEN @@ -320,13 +322,14 @@ export = { const service1 = new appmesh.VirtualService(stack, 'service-1', { virtualServiceName: 'service1.domain.local', virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), + }); + + node.addBackend(appmesh.Backend.virtualService(service1, { clientPolicy: appmesh.ClientPolicy.fileTrust({ certificateChain: 'path-to-certificate', ports: [8080, 8081], }), - }); - - node.addBackend(service1); + })); // THEN expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', { diff --git a/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts b/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts index 2732adb4cba17..fef86e6bd7e7a 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts @@ -109,7 +109,7 @@ export = { listeners: [appmesh.VirtualNodeListener.http({ port: 8080, })], - backends: [service1], + backends: [appmesh.Backend.virtualService(service1)], }); router.addRoute('route-1', { @@ -182,27 +182,21 @@ export = { listeners: [appmesh.VirtualNodeListener.http({ port: 8080, })], - backends: [ - service1, - ], + backends: [appmesh.Backend.virtualService(service1)], }); const node2 = mesh.addVirtualNode('test-node2', { serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), listeners: [appmesh.VirtualNodeListener.http({ port: 8080, })], - backends: [ - service2, - ], + backends: [appmesh.Backend.virtualService(service2)], }); const node3 = mesh.addVirtualNode('test-node3', { serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), listeners: [appmesh.VirtualNodeListener.http({ port: 8080, })], - backends: [ - service1, - ], + backends: [appmesh.Backend.virtualService(service1)], }); router.addRoute('route-1', { @@ -340,9 +334,7 @@ export = { listeners: [appmesh.VirtualNodeListener.http({ port: 8080, })], - backends: [ - service1, - ], + backends: [appmesh.Backend.virtualService(service1)], }); router.addRoute('route-tcp-1', { From 278029f25b41d956091835364e5a8de91429712c Mon Sep 17 00:00:00 2001 From: Benura Abeywardena <43112139+BLasan@users.noreply.github.com> Date: Fri, 12 Mar 2021 15:34:56 +0530 Subject: [PATCH 15/41] fix(cloudwatch): cannot create Alarms from labeled metrics that start with a digit (#13560) fixes #13434 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts | 3 +-- .../aws-cloudwatch/test/integ.alarm-with-label.expected.json | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts index d8c93f66aa910..ef97dc3d79c7f 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts @@ -257,7 +257,6 @@ export class Alarm extends AlarmBase { return dispatchMetric(metric, { withStat(stat, conf) { self.validateMetricStat(stat, metric); - if (conf.renderingProperties?.label == undefined) { return dropUndefined({ dimensions: stat.dimensions, @@ -283,7 +282,7 @@ export class Alarm extends AlarmBase { stat: stat.statistic, unit: stat.unitFilter, }, - id: stat.metricName, + id: 'm1', label: conf.renderingProperties?.label, returnData: true, } as CfnAlarm.MetricDataQueryProperty, diff --git a/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-with-label.expected.json b/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-with-label.expected.json index 9ab6e14f29a6e..6ac734ed6e534 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-with-label.expected.json +++ b/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-with-label.expected.json @@ -5,7 +5,7 @@ "Properties": { "Metrics": [ { - "Id": "Metric", + "Id": "m1", "Label": "Metric [AVG: ${AVG}]", "MetricStat": { "Metric": { @@ -28,7 +28,7 @@ "Properties": { "Metrics": [ { - "Id": "Metric", + "Id": "m1", "Label": "Metric [AVG: ${AVG}]", "MetricStat": { "Metric": { From 4769b313570e7d0f4afb05f216fc17faaa1ed2c7 Mon Sep 17 00:00:00 2001 From: Benura Abeywardena <43112139+BLasan@users.noreply.github.com> Date: Fri, 12 Mar 2021 16:14:44 +0530 Subject: [PATCH 16/41] chore(aws-cdk-readme): replace deprecated method used in aws-chatbot README.md (#13521) Currently addLambdaInvokeCommandPermissions method used to get the permissions, which is a deprecated method now. Use addToPolicy method to get necessary permissions fix: #13444 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-chatbot/README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/@aws-cdk/aws-chatbot/README.md b/packages/@aws-cdk/aws-chatbot/README.md index d46f0e704110e..2c4cee1196900 100644 --- a/packages/@aws-cdk/aws-chatbot/README.md +++ b/packages/@aws-cdk/aws-chatbot/README.md @@ -34,11 +34,6 @@ const slackChannel = new chatbot.SlackChannelConfiguration(this, 'MySlackChannel slackChannelId: 'YOUR_SLACK_CHANNEL_ID', }); -slackChannel.addLambdaInvokeCommandPermissions(); -slackChannel.addNotificationPermissions(); -slackChannel.addSupportCommandPermissions(); -slackChannel.addReadOnlyCommandPermissions(); - slackChannel.addToPrincipalPolicy(new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: [ From 46114bb1f4702019a8873b9162d0a9f10763bc61 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 12 Mar 2021 12:27:49 +0100 Subject: [PATCH 17/41] fix(autoscaling): AutoScaling on percentile metrics doesn't work (#13366) AutoScaling on percentile metrics did not work because the `MetricAggregationType` was trying to be derived from the metric, and it can only be MIN, MAX or AVG. Figure out what the metric aggregation type does, default it to AVERAGE if no other suitable value can be determined, and also make it and the evaluation periods configurable while we're at it. Fixes #13144. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/step-scaling-policy.ts | 29 +++++-- .../test/test.step-scaling-policy.ts | 77 ++++++++++++++++++- .../lib/step-scaling-policy.ts | 29 +++++-- .../aws-autoscaling/test/scaling.test.ts | 67 +++++++++++++++- 4 files changed, 190 insertions(+), 12 deletions(-) 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 417ecf34f1970..8b9f7b2644267 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts @@ -51,6 +51,25 @@ export interface BasicStepScalingPolicyProps { * @default No minimum scaling effect */ readonly minAdjustmentMagnitude?: number; + + /** + * How many evaluation periods of the metric to wait before triggering a scaling action + * + * Raising this value can be used to smooth out the metric, at the expense + * of slower response times. + * + * @default 1 + */ + readonly evaluationPeriods?: number; + + /** + * Aggregation to apply to all data points over the evaluation periods + * + * Only has meaning if `evaluationPeriods != 1`. + * + * @default - The statistic from the metric if applicable (MIN, MAX, AVERAGE), otherwise AVERAGE. + */ + readonly metricAggregationType?: MetricAggregationType; } export interface StepScalingPolicyProps extends BasicStepScalingPolicyProps { @@ -92,7 +111,7 @@ export class StepScalingPolicy extends CoreConstruct { this.lowerAction = new StepScalingAction(this, 'LowerPolicy', { adjustmentType, cooldown: props.cooldown, - metricAggregationType: aggregationTypeFromMetric(props.metric), + metricAggregationType: props.metricAggregationType ?? aggregationTypeFromMetric(props.metric), minAdjustmentMagnitude: props.minAdjustmentMagnitude, scalingTarget: props.scalingTarget, }); @@ -110,7 +129,7 @@ export class StepScalingPolicy extends CoreConstruct { metric: props.metric, alarmDescription: 'Lower threshold scaling alarm', comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD, - evaluationPeriods: 1, + evaluationPeriods: props.evaluationPeriods ?? 1, threshold, }); this.lowerAlarm.addAlarmAction(new StepScalingAlarmAction(this.lowerAction)); @@ -122,7 +141,7 @@ export class StepScalingPolicy extends CoreConstruct { this.upperAction = new StepScalingAction(this, 'UpperPolicy', { adjustmentType, cooldown: props.cooldown, - metricAggregationType: aggregationTypeFromMetric(props.metric), + metricAggregationType: props.metricAggregationType ?? aggregationTypeFromMetric(props.metric), minAdjustmentMagnitude: props.minAdjustmentMagnitude, scalingTarget: props.scalingTarget, }); @@ -140,7 +159,7 @@ export class StepScalingPolicy extends CoreConstruct { metric: props.metric, alarmDescription: 'Upper threshold scaling alarm', comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, - evaluationPeriods: 1, + evaluationPeriods: props.evaluationPeriods ?? 1, threshold, }); this.upperAlarm.addAlarmAction(new StepScalingAlarmAction(this.upperAction)); @@ -197,7 +216,7 @@ function aggregationTypeFromMetric(metric: cloudwatch.IMetric): MetricAggregatio case 'Maximum': return MetricAggregationType.MAXIMUM; default: - throw new Error(`Cannot only scale on 'Minimum', 'Maximum', 'Average' metrics, got ${statistic}`); + return MetricAggregationType.AVERAGE; } } diff --git a/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts index 4474cc6a46f58..fbcf70eb49f75 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts @@ -1,4 +1,4 @@ -import { expect, haveResource, SynthUtils } from '@aws-cdk/assert'; +import { expect, haveResource, haveResourceLike, SynthUtils } from '@aws-cdk/assert'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as cdk from '@aws-cdk/core'; import * as fc from 'fast-check'; @@ -152,6 +152,81 @@ export = { test.done(); }, + + 'step scaling from percentile metric'(test: Test) { + // 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 }, + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::ApplicationAutoScaling::ScalingPolicy', { + PolicyType: 'StepScaling', + StepScalingPolicyConfiguration: { + AdjustmentType: 'ChangeInCapacity', + MetricAggregationType: 'Average', + }, + })); + expect(stack).to(haveResource('AWS::CloudWatch::Alarm', { + ComparisonOperator: 'GreaterThanOrEqualToThreshold', + EvaluationPeriods: 1, + AlarmActions: [ + { Ref: 'TargetTrackingUpperPolicy72CEFA77' }, + ], + ExtendedStatistic: 'p99', + MetricName: 'Metric', + Namespace: 'Test', + Threshold: 100, + })); + + test.done(); + }, + + 'step scaling with evaluation period configured'(test: Test) { + // 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, + metricAggregationType: appscaling.MetricAggregationType.MAXIMUM, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::ApplicationAutoScaling::ScalingPolicy', { + PolicyType: 'StepScaling', + StepScalingPolicyConfiguration: { + AdjustmentType: 'ChangeInCapacity', + MetricAggregationType: 'Maximum', + }, + })); + expect(stack).to(haveResource('AWS::CloudWatch::Alarm', { + ComparisonOperator: 'GreaterThanOrEqualToThreshold', + EvaluationPeriods: 10, + ExtendedStatistic: 'p99', + MetricName: 'Metric', + Namespace: 'Test', + Threshold: 100, + })); + + test.done(); + }, }; /** diff --git a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts index c3b51a892c222..307eaf525ae55 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts @@ -52,6 +52,25 @@ export interface BasicStepScalingPolicyProps { * @default No minimum scaling effect */ readonly minAdjustmentMagnitude?: number; + + /** + * How many evaluation periods of the metric to wait before triggering a scaling action + * + * Raising this value can be used to smooth out the metric, at the expense + * of slower response times. + * + * @default 1 + */ + readonly evaluationPeriods?: number; + + /** + * Aggregation to apply to all data points over the evaluation periods + * + * Only has meaning if `evaluationPeriods != 1`. + * + * @default - The statistic from the metric if applicable (MIN, MAX, AVERAGE), otherwise AVERAGE. + */ + readonly metricAggregationType?: MetricAggregationType; } export interface StepScalingPolicyProps extends BasicStepScalingPolicyProps { @@ -93,7 +112,7 @@ export class StepScalingPolicy extends CoreConstruct { this.lowerAction = new StepScalingAction(this, 'LowerPolicy', { adjustmentType: props.adjustmentType, cooldown: props.cooldown, - metricAggregationType: aggregationTypeFromMetric(props.metric), + metricAggregationType: props.metricAggregationType ?? aggregationTypeFromMetric(props.metric), minAdjustmentMagnitude: props.minAdjustmentMagnitude, autoScalingGroup: props.autoScalingGroup, }); @@ -111,7 +130,7 @@ export class StepScalingPolicy extends CoreConstruct { metric: props.metric, alarmDescription: 'Lower threshold scaling alarm', comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD, - evaluationPeriods: 1, + evaluationPeriods: props.evaluationPeriods ?? 1, threshold, }); this.lowerAlarm.addAlarmAction(new StepScalingAlarmAction(this.lowerAction)); @@ -123,7 +142,7 @@ export class StepScalingPolicy extends CoreConstruct { this.upperAction = new StepScalingAction(this, 'UpperPolicy', { adjustmentType: props.adjustmentType, cooldown: props.cooldown, - metricAggregationType: aggregationTypeFromMetric(props.metric), + metricAggregationType: props.metricAggregationType ?? aggregationTypeFromMetric(props.metric), minAdjustmentMagnitude: props.minAdjustmentMagnitude, autoScalingGroup: props.autoScalingGroup, }); @@ -141,7 +160,7 @@ export class StepScalingPolicy extends CoreConstruct { metric: props.metric, alarmDescription: 'Upper threshold scaling alarm', comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, - evaluationPeriods: 1, + evaluationPeriods: props.evaluationPeriods ?? 1, threshold, }); this.upperAlarm.addAlarmAction(new StepScalingAlarmAction(this.upperAction)); @@ -161,7 +180,7 @@ function aggregationTypeFromMetric(metric: cloudwatch.IMetric): MetricAggregatio case 'Maximum': return MetricAggregationType.MAXIMUM; default: - throw new Error(`Cannot only scale on 'Minimum', 'Maximum', 'Average' metrics, got ${statistic}`); + return MetricAggregationType.AVERAGE; } } diff --git a/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts b/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts index 5207897cd1786..5c1f8947b09bd 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts @@ -1,4 +1,4 @@ -import { expect, haveResource } from '@aws-cdk/assert'; +import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; @@ -277,6 +277,71 @@ nodeunitShim({ }, }); +test('step scaling from percentile metric', () => { + // GIVEN + const stack = new cdk.Stack(); + const fixture = new ASGFixture(stack, 'Fixture'); + + // WHEN + fixture.asg.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 }, + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AutoScaling::ScalingPolicy', { + PolicyType: 'StepScaling', + MetricAggregationType: 'Average', + })); + expect(stack).to(haveResource('AWS::CloudWatch::Alarm', { + ComparisonOperator: 'GreaterThanOrEqualToThreshold', + EvaluationPeriods: 1, + AlarmActions: [ + { Ref: 'FixtureASGTrackingUpperPolicy27D4301F' }, + ], + ExtendedStatistic: 'p99', + MetricName: 'Metric', + Namespace: 'Test', + Threshold: 100, + })); +}); + +test('step scaling with evaluation period configured', () => { + // GIVEN + const stack = new cdk.Stack(); + const fixture = new ASGFixture(stack, 'Fixture'); + + // WHEN + fixture.asg.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, + metricAggregationType: autoscaling.MetricAggregationType.MAXIMUM, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AutoScaling::ScalingPolicy', { + PolicyType: 'StepScaling', + MetricAggregationType: 'Maximum', + })); + expect(stack).to(haveResource('AWS::CloudWatch::Alarm', { + ComparisonOperator: 'GreaterThanOrEqualToThreshold', + EvaluationPeriods: 10, + ExtendedStatistic: 'p99', + MetricName: 'Metric', + Namespace: 'Test', + Threshold: 100, + })); +}); + class ASGFixture extends Construct { public readonly vpc: ec2.Vpc; public readonly asg: autoscaling.AutoScalingGroup; From 81cf548b115e0e65d8dedf54d3efabdcbfda536b Mon Sep 17 00:00:00 2001 From: Robert Djurasaj Date: Fri, 12 Mar 2021 09:14:35 -0700 Subject: [PATCH 18/41] chore(docs): fix typos across the board (#13435) Fix bunch of docstring, docs and param typos. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-apigateway/lib/cors.ts | 2 +- .../@aws-cdk/aws-apigateway/lib/gateway-response.ts | 2 +- packages/@aws-cdk/aws-apigateway/lib/resource.ts | 6 +++--- packages/@aws-cdk/aws-apigateway/lib/restapi.ts | 4 ++-- packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts | 2 +- packages/@aws-cdk/aws-apigatewayv2/README.md | 2 +- packages/@aws-cdk/aws-batch/README.md | 4 ++-- packages/@aws-cdk/aws-batch/lib/exposed-secret.ts | 12 ++++++------ .../aws-batch/lib/job-definition-image-config.ts | 2 +- packages/@aws-cdk/aws-certificatemanager/README.md | 2 +- .../aws-certificatemanager/lib/certificate.ts | 8 ++++---- .../lib/dns-validated-certificate.ts | 2 +- packages/@aws-cdk/aws-dynamodb/README.md | 4 ++-- packages/@aws-cdk/aws-dynamodb/lib/table.ts | 4 ++-- packages/@aws-cdk/aws-ec2/lib/launch-template.ts | 2 +- packages/@aws-cdk/aws-ec2/lib/network-util.ts | 4 ++-- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 2 +- packages/@aws-cdk/aws-lambda/lib/alias.ts | 2 +- packages/@aws-cdk/aws-lambda/lib/code.ts | 2 +- packages/@aws-cdk/aws-lambda/lib/function.ts | 4 ++-- packages/@aws-cdk/aws-lambda/lib/lambda-version.ts | 2 +- packages/@aws-cdk/aws-route53/README.md | 2 +- .../lib/vpc-endpoint-service-domain-name.ts | 6 +++--- packages/@aws-cdk/aws-s3-assets/README.md | 2 +- packages/@aws-cdk/aws-s3/lib/bucket.ts | 6 +++--- packages/@aws-cdk/aws-stepfunctions/README.md | 2 +- .../aws-stepfunctions/lib/step-functions-task.ts | 4 ++-- packages/@aws-cdk/pipelines/README.md | 6 +++--- 28 files changed, 51 insertions(+), 51 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/cors.ts b/packages/@aws-cdk/aws-apigateway/lib/cors.ts index feb7572779de9..d0cc6ee3e5a79 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/cors.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/cors.ts @@ -64,7 +64,7 @@ export interface CorsOptions { * Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) * can be cached. * - * To disable caching altogther use `disableCache: true`. + * To disable caching altogether use `disableCache: true`. * * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age * @default - browser-specific (see reference) diff --git a/packages/@aws-cdk/aws-apigateway/lib/gateway-response.ts b/packages/@aws-cdk/aws-apigateway/lib/gateway-response.ts index 7c2aabbd0704e..0ab490a893051 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/gateway-response.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/gateway-response.ts @@ -206,7 +206,7 @@ export class ResponseType { */ public static readonly WAF_FILTERED = new ResponseType('WAF_FILTERED'); - /** A custom response type to suppport future cases. */ + /** A custom response type to support future cases. */ public static of(type: string): ResponseType { return new ResponseType(type.toUpperCase()); } diff --git a/packages/@aws-cdk/aws-apigateway/lib/resource.ts b/packages/@aws-cdk/aws-apigateway/lib/resource.ts index 49d0bf0356d80..714e99bce7a0b 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/resource.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/resource.ts @@ -282,12 +282,12 @@ export abstract class ResourceBase extends ResourceConstruct implements IResourc // prepare responseParams const integrationResponseParams: { [p: string]: string } = { }; - const methodReponseParams: { [p: string]: boolean } = { }; + const methodResponseParams: { [p: string]: boolean } = { }; for (const [name, value] of Object.entries(headers)) { const key = `method.response.header.${name}`; integrationResponseParams[key] = value; - methodReponseParams[key] = true; + methodResponseParams[key] = true; } return this.addMethod('OPTIONS', new MockIntegration({ @@ -297,7 +297,7 @@ export abstract class ResourceBase extends ResourceConstruct implements IResourc ], }), { methodResponses: [ - { statusCode: `${statusCode}`, responseParameters: methodReponseParams }, + { statusCode: `${statusCode}`, responseParameters: methodResponseParams }, ], }); diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 32c400c52d23e..51a6ae53a7040 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -174,7 +174,7 @@ export interface RestApiBaseProps { /** * Represents the props that all Rest APIs share. - * @deprecated - superceded by `RestApiBaseProps` + * @deprecated - superseded by `RestApiBaseProps` */ export interface RestApiOptions extends RestApiBaseProps, ResourceOptions { } @@ -441,7 +441,7 @@ export abstract class RestApiBase extends Resource implements IRestApi { /** * Metric for the total number API requests in a given period. * - * Default: samplecount over 5 minutes + * Default: sample count over 5 minutes */ public metricCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.cannedMetric(ApiGatewayMetrics.countSum, { diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index ad807d4a7d2d0..49b3ee19cd017 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -181,7 +181,7 @@ export class UsagePlan extends Resource { public addApiKey(apiKey: IApiKey): void { const prefix = 'UsagePlanKeyResource'; - // Postfixing apikey id only from the 2nd child, to keep physicalIds of UsagePlanKey for existing CDK apps unmodifed. + // Postfixing apikey id only from the 2nd child, to keep physicalIds of UsagePlanKey for existing CDK apps unmodified. const id = this.node.tryFindChild(prefix) ? `${prefix}:${Names.nodeUniqueId(apiKey.node)}` : prefix; new CfnUsagePlanKey(this, id, { diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index d8278a800a00f..7a71fc00491a1 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -187,7 +187,7 @@ const api = new HttpApi(stack, 'HttpProxyProdApi', { }); ``` -To associate a specifc `Stage` to a custom domain mapping - +To associate a specific `Stage` to a custom domain mapping - ```ts api.addStage('beta', { diff --git a/packages/@aws-cdk/aws-batch/README.md b/packages/@aws-cdk/aws-batch/README.md index 99ea697ae4f28..48d5b7edf65d8 100644 --- a/packages/@aws-cdk/aws-batch/README.md +++ b/packages/@aws-cdk/aws-batch/README.md @@ -148,7 +148,7 @@ const computeEnv = batch.ComputeEnvironment.fromComputeEnvironmentArn(this, 'imp ### Change the baseline AMI of the compute resources -Ocassionally, you will need to deviate from the default processing AMI. +Occasionally, you will need to deviate from the default processing AMI. ECS Optimized Amazon Linux 2 example: @@ -186,7 +186,7 @@ const jobQueue = new batch.JobQueue(stack, 'JobQueue', { { // Defines a collection of compute resources to handle assigned batch jobs computeEnvironment, - // Order determines the allocation order for jobs (i.e. Lower means higher preferance for job assignment) + // Order determines the allocation order for jobs (i.e. Lower means higher preference for job assignment) order: 1, }, ], diff --git a/packages/@aws-cdk/aws-batch/lib/exposed-secret.ts b/packages/@aws-cdk/aws-batch/lib/exposed-secret.ts index 351095536add0..6ee6e3587208b 100644 --- a/packages/@aws-cdk/aws-batch/lib/exposed-secret.ts +++ b/packages/@aws-cdk/aws-batch/lib/exposed-secret.ts @@ -7,20 +7,20 @@ import * as ssm from '@aws-cdk/aws-ssm'; export class ExposedSecret { /** * Use Secrets Manager Secret - * @param optionaName - The name of the option + * @param optionName - The name of the option * @param secret - A secret from secrets manager */ - public static fromSecretsManager(optionaName: string, secret: secretsmanager.ISecret): ExposedSecret { - return new ExposedSecret(optionaName, secret.secretArn); + public static fromSecretsManager(optionName: string, secret: secretsmanager.ISecret): ExposedSecret { + return new ExposedSecret(optionName, secret.secretArn); } /** * User Parameters Store Parameter - * @param optionaName - The name of the option + * @param optionName - The name of the option * @param parameter - A parameter from parameters store */ - public static fromParametersStore(optionaName: string, parameter: ssm.IParameter): ExposedSecret { - return new ExposedSecret(optionaName, parameter.parameterArn); + public static fromParametersStore(optionName: string, parameter: ssm.IParameter): ExposedSecret { + return new ExposedSecret(optionName, parameter.parameterArn); } /** diff --git a/packages/@aws-cdk/aws-batch/lib/job-definition-image-config.ts b/packages/@aws-cdk/aws-batch/lib/job-definition-image-config.ts index 191024a197a11..ba3c0c1740597 100644 --- a/packages/@aws-cdk/aws-batch/lib/job-definition-image-config.ts +++ b/packages/@aws-cdk/aws-batch/lib/job-definition-image-config.ts @@ -37,7 +37,7 @@ class TaskDefinition { } /** - * Internal function to allow the Batch Job task defintion + * Internal function to allow the Batch Job task definition * to match the CDK requirements of an EC2 task definition. * * @internal diff --git a/packages/@aws-cdk/aws-certificatemanager/README.md b/packages/@aws-cdk/aws-certificatemanager/README.md index 24202429ac5df..24f1e72a3cb20 100644 --- a/packages/@aws-cdk/aws-certificatemanager/README.md +++ b/packages/@aws-cdk/aws-certificatemanager/README.md @@ -102,7 +102,7 @@ new acm.Certificate(this, 'Certificate', { ## Cross-region Certificates ACM certificates that are used with CloudFront -- or higher-level constructs which rely on CloudFront -- must be in the `us-east-1` region. -The `DnsValidatedCertificate` construct exists to faciliate creating these certificates cross-region. This resource can only be used with +The `DnsValidatedCertificate` construct exists to facilitate creating these certificates cross-region. This resource can only be used with Route53-based DNS validation. ```ts diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts b/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts index df3d3988847ad..eac04dcb6df05 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts @@ -55,7 +55,7 @@ export interface CertificateProps { readonly validationMethod?: ValidationMethod; /** - * How to validate this certifcate + * How to validate this certificate * * @default CertificateValidation.fromEmail() */ @@ -100,7 +100,7 @@ export interface CertificationValidationProps { */ export class CertificateValidation { /** - * Validate the certifcate with DNS + * Validate the certificate with DNS * * IMPORTANT: If `hostedZone` is not specified, DNS records must be added * manually and the stack will not complete creating until the records are @@ -116,7 +116,7 @@ export class CertificateValidation { } /** - * Validate the certifcate with automatically created DNS records in multiple + * Validate the certificate with automatically created DNS records in multiple * Amazon Route 53 hosted zones. * * @param hostedZones a map of hosted zones where DNS records must be created @@ -130,7 +130,7 @@ export class CertificateValidation { } /** - * Validate the certifcate with Email + * Validate the certificate with Email * * IMPORTANT: if you are creating a certificate as part of your stack, the stack * will not complete creating until you read and follow the instructions in the diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts b/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts index f1a5cd442e9d1..98637e43409ed 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts @@ -32,7 +32,7 @@ export interface DnsValidatedCertificateProps extends CertificateProps { * aws-cn partition, the default endpoint is not working now, hence the right endpoint * need to be specified through this prop. * - * Route53 is not been offically launched in China, it is only available for AWS + * Route53 is not been officially launched in China, it is only available for AWS * internal accounts now. To make DnsValidatedCertificate work for internal accounts * now, a special endpoint needs to be provided. * diff --git a/packages/@aws-cdk/aws-dynamodb/README.md b/packages/@aws-cdk/aws-dynamodb/README.md index ac540dd11670c..2f43d83f88c4f 100644 --- a/packages/@aws-cdk/aws-dynamodb/README.md +++ b/packages/@aws-cdk/aws-dynamodb/README.md @@ -65,7 +65,7 @@ https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.Read You can have DynamoDB automatically raise and lower the read and write capacities of your table by setting up autoscaling. You can use this to either keep your -tables at a desired utilization level, or by scaling up and down at preconfigured +tables at a desired utilization level, or by scaling up and down at pre-configured times of the day: Auto-scaling is only relevant for tables with the billing mode, PROVISIONED. @@ -125,7 +125,7 @@ const globalTable = new dynamodb.Table(this, 'Table', { All user data stored in Amazon DynamoDB is fully encrypted at rest. When creating a new table, you can choose to encrypt using the following customer master keys (CMK) to encrypt your table: * AWS owned CMK - By default, all tables are encrypted under an AWS owned customer master key (CMK) in the DynamoDB service account (no additional charges apply). -* AWS managed CMK - AWS KMS keys (one per region) are created in your account, managed, and used on your behalf by AWS DynamoDB (AWS KMS chages apply). +* AWS managed CMK - AWS KMS keys (one per region) are created in your account, managed, and used on your behalf by AWS DynamoDB (AWS KMS charges apply). * Customer managed CMK - You have full control over the KMS key used to encrypt the DynamoDB Table (AWS KMS charges apply). Creating a Table encrypted with a customer managed CMK: diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index 9334192f5610d..84156abcec214 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -1555,7 +1555,7 @@ export class Table extends TableBase { if (encryptionType === undefined) { encryptionType = props.encryptionKey != null - // If there is a configured encyptionKey, the encryption is implicitly CUSTOMER_MANAGED + // If there is a configured encryptionKey, the encryption is implicitly CUSTOMER_MANAGED ? TableEncryption.CUSTOMER_MANAGED // Otherwise, if severSideEncryption is enabled, it's AWS_MANAGED; else undefined (do not set anything) : props.serverSideEncryption ? TableEncryption.AWS_MANAGED : undefined; @@ -1613,7 +1613,7 @@ export enum AttributeType { } /** - * DyanmoDB's Read/Write capacity modes. + * DynamoDB's Read/Write capacity modes. */ export enum BillingMode { /** diff --git a/packages/@aws-cdk/aws-ec2/lib/launch-template.ts b/packages/@aws-cdk/aws-ec2/lib/launch-template.ts index 3b5b39f9b6370..fdc03755c0268 100644 --- a/packages/@aws-cdk/aws-ec2/lib/launch-template.ts +++ b/packages/@aws-cdk/aws-ec2/lib/launch-template.ts @@ -646,7 +646,7 @@ export class LaunchTemplate extends Resource implements ILaunchTemplate, iam.IGr */ public get connections(): Connections { if (!this._connections) { - throw new Error('LaunchTemplate can only be used as IConnectable if a securityGroup is provided when contructing it.'); + throw new Error('LaunchTemplate can only be used as IConnectable if a securityGroup is provided when constructing it.'); } return this._connections; } diff --git a/packages/@aws-cdk/aws-ec2/lib/network-util.ts b/packages/@aws-cdk/aws-ec2/lib/network-util.ts index 301c8ad5ed980..5c7a7c9b7027e 100644 --- a/packages/@aws-cdk/aws-ec2/lib/network-util.ts +++ b/packages/@aws-cdk/aws-ec2/lib/network-util.ts @@ -244,7 +244,7 @@ export class CidrBlock { } /* - * The maximum IP in the CIDR Blcok e.g. '10.0.8.255' + * The maximum IP in the CIDR Block e.g. '10.0.8.255' */ public maxIp(): string { // min + (2^(32-mask)) - 1 [zero needs to count] @@ -252,7 +252,7 @@ export class CidrBlock { } /* - * The minimum IP in the CIDR Blcok e.g. '10.0.0.0' + * The minimum IP in the CIDR Block e.g. '10.0.0.0' */ public minIp(): string { return NetworkUtils.numToIp(this.minAddress()); diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 2a4e524fc5150..8d878dde26024 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -1597,7 +1597,7 @@ export class Subnet extends Resource implements ISubnet { } /** - * Adds an entry to this subnets route table that points to the passed NATGatwayId + * Adds an entry to this subnets route table that points to the passed NATGatewayId * @param natGatewayId The ID of the NAT gateway */ public addDefaultNatRoute(natGatewayId: string) { diff --git a/packages/@aws-cdk/aws-lambda/lib/alias.ts b/packages/@aws-cdk/aws-lambda/lib/alias.ts index 3e22118644ac6..4287ffde73d6e 100644 --- a/packages/@aws-cdk/aws-lambda/lib/alias.ts +++ b/packages/@aws-cdk/aws-lambda/lib/alias.ts @@ -196,7 +196,7 @@ export class Alias extends QualifiedFunctionBase implements IAlias { } public metric(metricName: string, props: cloudwatch.MetricOptions = {}): cloudwatch.Metric { - // Metrics on Aliases need the "bare" function name, and the alias' ARN, this differes from the base behavior. + // Metrics on Aliases need the "bare" function name, and the alias' ARN, this differs from the base behavior. return super.metric(metricName, { dimensions: { FunctionName: this.lambda.functionName, diff --git a/packages/@aws-cdk/aws-lambda/lib/code.ts b/packages/@aws-cdk/aws-lambda/lib/code.ts index fec1e1821270e..b78859bf3515c 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code.ts @@ -60,7 +60,7 @@ export abstract class Code { /** * Loads the function code from an asset created by a Docker build. * - * By defaut, the asset is expected to be located at `/asset` in the + * By default, the asset is expected to be located at `/asset` in the * image. * * @param path The path to the directory containing the Docker file diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 8d487276a6176..1f9857ac35b1a 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -214,7 +214,7 @@ export interface FunctionOptions extends EventInvokeConfigOptions { /** * A list of layers to add to the function's execution environment. You can configure your Lambda function to pull in * additional code during initialization in the form of layers. Layers are packages of libraries or other dependencies - * that can be used by mulitple functions. + * that can be used by multiple functions. * * @default - No layers. */ @@ -563,7 +563,7 @@ export class Function extends FunctionBase { }); this.grantPrincipal = this.role; - // add additonal managed policies when necessary + // add additional managed policies when necessary if (props.filesystem) { const config = props.filesystem.config; if (config.policies) { diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts index b51d866ac815d..94f3e0b326b16 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts @@ -221,7 +221,7 @@ export class Version extends QualifiedFunctionBase implements IVersion { } public metric(metricName: string, props: cloudwatch.MetricOptions = {}): cloudwatch.Metric { - // Metrics on Aliases need the "bare" function name, and the alias' ARN, this differes from the base behavior. + // Metrics on Aliases need the "bare" function name, and the alias' ARN, this differs from the base behavior. return super.metric(metricName, { dimensions: { FunctionName: this.lambda.functionName, diff --git a/packages/@aws-cdk/aws-route53/README.md b/packages/@aws-cdk/aws-route53/README.md index aa5bf109b7473..9f36aa7f58765 100644 --- a/packages/@aws-cdk/aws-route53/README.md +++ b/packages/@aws-cdk/aws-route53/README.md @@ -191,7 +191,7 @@ This DNS name can also be guaranteed to match up with the backend certificate. Before consumers can use the private DNS name, you must verify that you have control of the domain/subdomain. -Assuming your account has ownership of the particlar domain/subdomain, +Assuming your account has ownership of the particular domain/subdomain, this construct sets up the private DNS configuration on the endpoint service, creates all the necessary Route53 entries, and verifies domain ownership. diff --git a/packages/@aws-cdk/aws-route53/lib/vpc-endpoint-service-domain-name.ts b/packages/@aws-cdk/aws-route53/lib/vpc-endpoint-service-domain-name.ts index 2af889661098a..9098503b625f4 100644 --- a/packages/@aws-cdk/aws-route53/lib/vpc-endpoint-service-domain-name.ts +++ b/packages/@aws-cdk/aws-route53/lib/vpc-endpoint-service-domain-name.ts @@ -138,7 +138,7 @@ export class VpcEndpointServiceDomainName extends CoreConstruct { // Create the custom resource to look up the name/value pair generated by AWS // after the previous API call - const retriveNameValuePairAction = { + const retrieveNameValuePairAction = { service: 'EC2', action: 'describeVpcEndpointServiceConfigurations', parameters: { @@ -147,8 +147,8 @@ export class VpcEndpointServiceDomainName extends CoreConstruct { physicalResourceId: PhysicalResourceId.of(lookup), }; const getNames = new AwsCustomResource(this, 'GetNames', { - onCreate: retriveNameValuePairAction, - onUpdate: retriveNameValuePairAction, + onCreate: retrieveNameValuePairAction, + onUpdate: retrieveNameValuePairAction, // describeVpcEndpointServiceConfigurations can't take an ARN for granular permissions policy: AwsCustomResourcePolicy.fromSdkCalls({ resources: AwsCustomResourcePolicy.ANY_RESOURCE, diff --git a/packages/@aws-cdk/aws-s3-assets/README.md b/packages/@aws-cdk/aws-s3-assets/README.md index 7a751410a2b22..8dae008aa5444 100644 --- a/packages/@aws-cdk/aws-s3-assets/README.md +++ b/packages/@aws-cdk/aws-s3-assets/README.md @@ -143,7 +143,7 @@ const asset = new assets.Asset(this, 'BundledAsset', { ``` Use `BundlingOutput.ARCHIVED` if the bundling output contains a single archive file and -you don't want it to be zippped. +you don't want it to be zipped. ## CloudFormation Resource Metadata diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 630aa9f02bfcc..c4c472a01ca94 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -183,7 +183,7 @@ export interface IBucket extends IResource { grantPutAcl(identity: iam.IGrantable, objectsKeyPattern?: string): iam.Grant; /** - * Grants s3:DeleteObject* permission to an IAM pricipal for objects + * Grants s3:DeleteObject* permission to an IAM principal for objects * in this bucket. * * @param identity The principal @@ -628,7 +628,7 @@ abstract class BucketBase extends Resource implements IBucket { } /** - * Grants s3:DeleteObject* permission to an IAM pricipal for objects + * Grants s3:DeleteObject* permission to an IAM principal for objects * in this bucket. * * @param identity The principal @@ -1620,7 +1620,7 @@ export class Bucket extends BucketBase { } /** - * Parse the lifecycle configuration out of the uucket props + * Parse the lifecycle configuration out of the bucket props * @param props Par */ private parseLifecycleConfiguration(): CfnBucket.LifecycleConfigurationProperty | undefined { diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index 8682de8dd13a9..f22d45f9f475d 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -295,7 +295,7 @@ parallel.next(closeOrder); ### Succeed Reaching a `Succeed` state terminates the state machine execution with a -succesful status. +successful status. ```ts const success = new sfn.Succeed(this, 'We did it!'); diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/step-functions-task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/step-functions-task.ts index 1d0dfcecbf773..e722594345930 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/step-functions-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/step-functions-task.ts @@ -84,7 +84,7 @@ export interface StepFunctionsTaskConfig { * Three ways to call an integrated service: Request Response, Run a Job and Wait for a Callback with Task Token. * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html * - * Here, they are named as FIRE_AND_FORGET, SYNC and WAIT_FOR_TASK_TOKEN respectly. + * Here, they are named as FIRE_AND_FORGET, SYNC and WAIT_FOR_TASK_TOKEN respectfully. * * @default FIRE_AND_FORGET */ @@ -100,7 +100,7 @@ export enum ServiceIntegrationPattern { SYNC = 'SYNC', /** - * Call a service with a task token and wait until that token is returned by SendTaskSuccess/SendTaskFailure with paylaod + * Call a service with a task token and wait until that token is returned by SendTaskSuccess/SendTaskFailure with payload. */ WAIT_FOR_TASK_TOKEN = 'WAIT_FOR_TASK_TOKEN' } diff --git a/packages/@aws-cdk/pipelines/README.md b/packages/@aws-cdk/pipelines/README.md index 15e6da27613d3..5413b38fa0d16 100644 --- a/packages/@aws-cdk/pipelines/README.md +++ b/packages/@aws-cdk/pipelines/README.md @@ -432,7 +432,7 @@ the `additionalArtifacts` property. Here are some typical examples for how you might want to bring in additional files from several sources: -* Directoy from the source repository +* Directory from the source repository * Additional compiled artifacts from the synth step ### Controlling IAM permissions @@ -523,7 +523,7 @@ const validationAction = new ShellScriptAction({ }); ``` -#### Add Additional permissions to the CodeBuild Project Role for building and synthing +#### Add Additional permissions to the CodeBuild Project Role for building and synthesizing You can customize the role permissions used by the CodeBuild project so it has access to the needed resources. eg: Adding CodeArtifact repo permissions so we pull npm packages @@ -677,7 +677,7 @@ contains: assets in these storage locations *without* the use of CloudFormation template parameters. * A set of roles with permissions to access these asset locations and to execute - CloudFormation, assumeable from whatever accounts you specify under `--trust`. + CloudFormation, assumable from whatever accounts you specify under `--trust`. It is possible and safe to migrate from the old bootstrap stack to the new bootstrap stack. This will create a new S3 file asset bucket in your account From 64da84be5c60bb8132551bcc27a7ca9c7effe95d Mon Sep 17 00:00:00 2001 From: Romain Marcadier Date: Fri, 12 Mar 2021 17:59:42 +0100 Subject: [PATCH 19/41] fix(region-info): ap-northeast-3 data not correctly registered (#13564) The region information for ap-northeast-3 was not correctly registered as the region was missing from the `AWS_REGIONS` list in the `aws-entities.ts` file. This addresses the gap, and adds a validation at the beginning of `generate-static-data.ts` to ensure no "new" region is introduced here without also being introduced in the `AWS_REGIONS` list. Fixes #13561 Credits to @robertd who had a draft PR with similar changes, which I only saw once it was too late. I've retro-fitted all the good ideas they had which I did not have on first intention - so thank you @robertd. Co-Authored-By: @robertd ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../region-info/build-tools/aws-entities.ts | 53 +++++------ .../region-info/build-tools/fact-tables.ts | 93 +++++++++---------- .../build-tools/generate-static-data.ts | 18 ++++ .../__snapshots__/region-info.test.js.snap | 20 ++++ 4 files changed, 110 insertions(+), 74 deletions(-) diff --git a/packages/@aws-cdk/region-info/build-tools/aws-entities.ts b/packages/@aws-cdk/region-info/build-tools/aws-entities.ts index 1588af66c6384..28c59828f7477 100644 --- a/packages/@aws-cdk/region-info/build-tools/aws-entities.ts +++ b/packages/@aws-cdk/region-info/build-tools/aws-entities.ts @@ -4,32 +4,33 @@ * Not in the list ==> no built-in data for that region. */ export const AWS_REGIONS = [ - 'us-east-2', - 'us-east-1', - 'us-west-1', - 'us-west-2', - 'us-gov-east-1', - 'us-gov-west-1', - 'us-iso-east-1', - 'us-isob-east-1', - 'af-south-1', - 'ap-east-1', - 'ap-south-1', - 'ap-northeast-2', - 'ap-southeast-1', - 'ap-southeast-2', - 'ap-northeast-1', - 'ca-central-1', - 'cn-north-1', - 'cn-northwest-1', - 'eu-central-1', - 'eu-west-1', - 'eu-west-2', - 'eu-west-3', - 'eu-north-1', - 'eu-south-1', - 'me-south-1', - 'sa-east-1', + 'af-south-1', // Africa (Cape Town) + 'ap-east-1', // Asia Pacific (Hong Kong) + 'ap-northeast-1', // Asia Pacific (Tokyo) + 'ap-northeast-2', // Asia Pacific (Seoul) + 'ap-northeast-3', // Asia Pacific (Osaka) + 'ap-south-1', // Asia Pacific (Mumbai) + 'ap-southeast-1', // Asia Pacific (Singapore) + 'ap-southeast-2', // Asia Pacific (Sydney) + 'ca-central-1', // Canada (Central) + 'cn-north-1', // China (Beijing) + 'cn-northwest-1', // China (Ningxia) + 'eu-central-1', // Europe (Frankfurt) + 'eu-north-1', // Europe (Stockholm) + 'eu-south-1', // Europe (Milan) + 'eu-west-1', // Europe (Ireland) + 'eu-west-2', // Europe (London) + 'eu-west-3', // Europe (Paris) + 'me-south-1', // Middle East (Bahrain) + 'sa-east-1', // South America (São Paulo) + 'us-east-1', // US East (N. Virginia) + 'us-east-2', // US East (Ohio) + 'us-gov-east-1', // AWS GovCloud (US-East) + 'us-gov-west-1', // AWS GovCloud (US-West) + 'us-iso-east-1', // AWS ISO + 'us-isob-east-1', // AWS ISO-B + 'us-west-1', // US West (N. California) + 'us-west-2', // US West (Oregon) ].sort(); /** 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 dc7f8ff586449..2f47e02f64da4 100644 --- a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts +++ b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts @@ -46,29 +46,29 @@ export const AWS_CDK_METADATA = new Set([ * @see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_website_region_endpoints */ export const ROUTE_53_BUCKET_WEBSITE_ZONE_IDS: { [region: string]: string } = { - 'us-east-2': 'Z2O1EMRO9K5GLX', - 'us-east-1': 'Z3AQBSTGFYJSTF', - 'us-west-1': 'Z2F56UZL2M1ACD', - 'us-west-2': 'Z3BJ6K6RIION7M', - 'us-gov-east-1': 'Z2NIFVYYW2VKV1', - 'us-gov-west-1': 'Z31GFT0UA1I2HV', 'af-south-1': 'Z11KHD8FBVPUYU', 'ap-east-1': 'ZNB98KWMFR0R6', - 'ap-south-1': 'Z11RGJOFQNVJUP', - 'ap-northeast-3': 'Z2YQB5RD63NC85', + 'ap-northeast-1': 'Z2M4EHUR26P7ZW', 'ap-northeast-2': 'Z3W03O7B5YMIYP', + 'ap-northeast-3': 'Z2YQB5RD63NC85', + 'ap-south-1': 'Z11RGJOFQNVJUP', 'ap-southeast-1': 'Z3O0J2DXBE1FTB', 'ap-southeast-2': 'Z1WCIGYICN2BYD', - 'ap-northeast-1': 'Z2M4EHUR26P7ZW', 'ca-central-1': 'Z1QDHH18159H29', 'eu-central-1': 'Z21DNDUVLTQW6Q', + 'eu-north-1': 'Z3BAZG2TWCNX0D', + 'eu-south-1': 'Z3IXVV8C73GIO3', 'eu-west-1': 'Z1BKCTXD74EZPE', 'eu-west-2': 'Z3GKZC51ZF0DB4', 'eu-west-3': 'Z3R1K369G5AVDG', - 'eu-north-1': 'Z3BAZG2TWCNX0D', - 'eu-south-1': 'Z3IXVV8C73GIO3', - 'sa-east-1': 'Z7KQH4QJS55SO', 'me-south-1': 'Z1MPMWCPA7YB62', + 'sa-east-1': 'Z7KQH4QJS55SO', + 'us-east-1': 'Z3AQBSTGFYJSTF', + 'us-east-2': 'Z2O1EMRO9K5GLX', + 'us-gov-east-1': 'Z2NIFVYYW2VKV1', + 'us-gov-west-1': 'Z31GFT0UA1I2HV', + 'us-west-1': 'Z2F56UZL2M1ACD', + 'us-west-2': 'Z3BJ6K6RIION7M', }; interface Region { partition: string, domainSuffix: string } @@ -83,63 +83,64 @@ export const PARTITION_MAP: { [region: string]: Region } = { // https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-logging-bucket-permissions export const ELBV2_ACCOUNTS: { [region: string]: string } = { - 'us-east-1': '127311923021', - 'us-east-2': '033677994240', - 'us-west-1': '027434742980', - 'us-west-2': '797873946194', 'af-south-1': '098369216593', - 'ca-central-1': '985666609251', - 'eu-central-1': '054676820928', - 'eu-west-1': '156460612806', - 'eu-west-2': '652711504416', - 'eu-west-3': '009996457667', - 'eu-south-1': '635631232127', - 'eu-north-1': '897822967062', 'ap-east-1': '754344448648', 'ap-northeast-1': '582318560864', 'ap-northeast-2': '600734575887', 'ap-northeast-3': '383597477331', + 'ap-south-1': '718504428378', 'ap-southeast-1': '114774131450', 'ap-southeast-2': '783225319266', - 'ap-south-1': '718504428378', + 'ca-central-1': '985666609251', + 'cn-north-1': '638102146993', + 'cn-northwest-1': '037604701340', + 'eu-central-1': '054676820928', + 'eu-north-1': '897822967062', + 'eu-south-1': '635631232127', + 'eu-west-1': '156460612806', + 'eu-west-2': '652711504416', + 'eu-west-3': '009996457667', 'me-south-1': '076674570225', 'sa-east-1': '507241528517', - 'us-gov-west-1': '048591011584', + 'us-east-1': '127311923021', + 'us-east-2': '033677994240', 'us-gov-east-1': '190560391635', - 'cn-north-1': '638102146993', - 'cn-northwest-1': '037604701340', + 'us-gov-west-1': '048591011584', + 'us-west-1': '027434742980', + 'us-west-2': '797873946194', }; // https://aws.amazon.com/releasenotes/available-deep-learning-containers-images export const DLC_REPOSITORY_ACCOUNTS: { [region: string]: string } = { - 'us-east-1': '763104351884', - 'us-east-2': '763104351884', - 'us-west-1': '763104351884', - 'us-west-2': '763104351884', - 'ca-central-1': '763104351884', - 'eu-west-1': '763104351884', - 'eu-west-2': '763104351884', - 'eu-west-3': '763104351884', - 'eu-central-1': '763104351884', - 'eu-north-1': '763104351884', - 'sa-east-1': '763104351884', - 'ap-south-1': '763104351884', + 'ap-east-1': '871362719292', 'ap-northeast-1': '763104351884', 'ap-northeast-2': '763104351884', + 'ap-south-1': '763104351884', 'ap-southeast-1': '763104351884', 'ap-southeast-2': '763104351884', - - 'ap-east-1': '871362719292', - 'me-south-1': '217643126080', - + 'ca-central-1': '763104351884', 'cn-north-1': '727897471807', 'cn-northwest-1': '727897471807', + 'eu-central-1': '763104351884', + 'eu-north-1': '763104351884', + 'eu-west-1': '763104351884', + 'eu-west-2': '763104351884', + 'eu-west-3': '763104351884', + 'me-south-1': '217643126080', + 'sa-east-1': '763104351884', + 'us-east-1': '763104351884', + 'us-east-2': '763104351884', + 'us-west-1': '763104351884', + 'us-west-2': '763104351884', }; // https://docs.aws.amazon.com/app-mesh/latest/userguide/envoy.html export const APPMESH_ECR_ACCOUNTS: { [region: string]: string } = { + 'af-south-1': '924023996002', + 'ap-east-1': '856666278305', 'ap-northeast-1': '840364872350', 'ap-northeast-2': '840364872350', + 'ap-northeast-3': '840364872350', 'ap-south-1': '840364872350', 'ap-southeast-1': '840364872350', 'ap-southeast-2': '840364872350', @@ -150,14 +151,10 @@ export const APPMESH_ECR_ACCOUNTS: { [region: string]: string } = { 'eu-west-1': '840364872350', 'eu-west-2': '840364872350', 'eu-west-3': '840364872350', + 'me-south-1': '772975370895', 'sa-east-1': '840364872350', 'us-east-1': '840364872350', 'us-east-2': '840364872350', 'us-west-1': '840364872350', 'us-west-2': '840364872350', - - 'me-south-1': '772975370895', - 'ap-east-1': '856666278305', - 'af-south-1': '924023996002', - }; 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 08a9f79a72b2b..d23704b6d0062 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 @@ -8,6 +8,11 @@ import { } from './fact-tables'; async function main(): Promise { + checkRegions(APPMESH_ECR_ACCOUNTS); + checkRegions(DLC_REPOSITORY_ACCOUNTS); + checkRegions(ELBV2_ACCOUNTS); + checkRegions(ROUTE_53_BUCKET_WEBSITE_ZONE_IDS); + const lines = [ "import { Fact, FactName } from './fact';", '', @@ -76,6 +81,19 @@ async function main(): Promise { } } +/** + * Verifies that the provided map of region to fact does not contain an entry + * for a region that was not registered in `AWS_REGIONS`. + */ +function checkRegions(map: Record) { + const allRegions = new Set(AWS_REGIONS); + for (const region of Object.keys(map)) { + if (!allRegions.has(region)) { + throw new Error(`Un-registered region fact found: ${region}. Add to AWS_REGIONS list!`); + } + } +} + main().catch(e => { // eslint-disable-next-line no-console console.error(e); 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 55df3731f4eec..ab12430e57c84 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 @@ -82,6 +82,26 @@ Object { }, "vpcEndPointServiceNamePrefix": "com.amazonaws.vpce", }, + "ap-northeast-3": Object { + "cdkMetadataResourceAvailable": false, + "domainSuffix": "amazonaws.com", + "partition": "aws", + "s3StaticWebsiteEndpoint": "s3-website.ap-northeast-3.amazonaws.com", + "servicePrincipals": Object { + "application-autoscaling": "application-autoscaling.amazonaws.com", + "autoscaling": "autoscaling.amazonaws.com", + "codedeploy": "codedeploy.ap-northeast-3.amazonaws.com", + "ec2": "ec2.amazonaws.com", + "events": "events.amazonaws.com", + "lambda": "lambda.amazonaws.com", + "logs": "logs.ap-northeast-3.amazonaws.com", + "s3": "s3.amazonaws.com", + "sns": "sns.amazonaws.com", + "sqs": "sqs.amazonaws.com", + "states": "states.ap-northeast-3.amazonaws.com", + }, + "vpcEndPointServiceNamePrefix": "com.amazonaws.vpce", + }, "ap-south-1": Object { "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", From 165a3d877b7ab23f29e42e1e74ee7c5cb35b7f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christophe=20Boug=C3=A8re?= Date: Fri, 12 Mar 2021 18:39:53 +0100 Subject: [PATCH 20/41] feat(aws-elasticloadbalancingv2): add protocol version for ALB TargetGroups (#13570) Fixes #12869 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-elasticloadbalancingv2/README.md | 14 +++++++++++++ .../lib/alb/application-target-group.ts | 11 +++++++++- .../lib/shared/enums.ts | 20 +++++++++++++++++++ .../test/alb/target-group.test.ts | 18 +++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md b/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md index e0f3b1f4e5ea5..b04d39e1862d7 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md @@ -273,6 +273,20 @@ const tg2 = new elbv2.ApplicationTargetGroup(stack, 'TG2', { For more information see: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/sticky-sessions.html#application-based-stickiness +### Setting the target group protocol version + +By default, Application Load Balancers send requests to targets using HTTP/1.1. You can use the [protocol version](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html#target-group-protocol-version) to send requests to targets using HTTP/2 or gRPC. + +```ts +const tg = new elbv2.ApplicationTargetGroup(stack, 'TG', { + targetType: elbv2.TargetType.IP, + port: 50051, + protocol: elbv2.ApplicationProtocol.HTTP, + protocolVersion: elbv2.ApplicationProtocolVersion.GRPC, + vpc, +}); +``` + ## Using Lambda Targets To use a Lambda Function as a target, use the integration class in the diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index 74938a08ee745..e7a6b2eef27d1 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -7,7 +7,7 @@ import { BaseTargetGroupProps, ITargetGroup, loadBalancerNameFromListenerArn, LoadBalancerTargetProps, TargetGroupAttributes, TargetGroupBase, TargetGroupImportProps, } from '../shared/base-target-group'; -import { ApplicationProtocol, Protocol, TargetType } from '../shared/enums'; +import { ApplicationProtocol, ApplicationProtocolVersion, Protocol, TargetType } from '../shared/enums'; import { ImportedTargetGroupBase } from '../shared/imported'; import { determineProtocolAndPort } from '../shared/util'; import { IApplicationListener } from './application-listener'; @@ -28,6 +28,13 @@ export interface ApplicationTargetGroupProps extends BaseTargetGroupProps { */ readonly protocol?: ApplicationProtocol; + /** + * The protocol version to use + * + * @default ApplicationProtocolVersion.HTTP1 + */ + readonly protocolVersion?: ApplicationProtocolVersion; + /** * The port on which the listener listens for requests. * @@ -110,8 +117,10 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat constructor(scope: Construct, id: string, props: ApplicationTargetGroupProps = {}) { const [protocol, port] = determineProtocolAndPort(props.protocol, props.port); + const { protocolVersion } = props; super(scope, id, { ...props }, { protocol, + protocolVersion, port, }); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/enums.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/enums.ts index be25c49b96513..8c5f183bb2cc2 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/enums.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/enums.ts @@ -63,6 +63,26 @@ export enum ApplicationProtocol { HTTPS = 'HTTPS' } +/** + * Load balancing protocol version for application load balancers + */ +export enum ApplicationProtocolVersion { + /** + * GRPC + */ + GRPC = 'GRPC', + + /** + * HTTP1 + */ + HTTP1 = 'HTTP1', + + /** + * HTTP2 + */ + HTTP2 = 'HTTP2', +} + /** * Elastic Load Balancing provides the following security policies for Application Load Balancers * diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts index 77858e9c21af4..ea028b543096f 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts @@ -156,6 +156,24 @@ describe('tests', () => { }); }); + test('Can set a protocol version', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + const vpc = new ec2.Vpc(stack, 'VPC', {}); + + // WHEN + new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { + vpc, + protocolVersion: elbv2.ApplicationProtocolVersion.GRPC, + }); + + // THEN + expect(stack).toHaveResource('AWS::ElasticLoadBalancingV2::TargetGroup', { + ProtocolVersion: 'GRPC', + }); + }); + test('Bad stickiness cookie names', () => { // GIVEN const app = new cdk.App(); From 5d56940a53f788c9d64d707528c42025cdb066ea Mon Sep 17 00:00:00 2001 From: Yunchao Wu Date: Mon, 15 Mar 2021 07:43:08 -0400 Subject: [PATCH 21/41] docs(aws-events): Fix typo in docs (#13554) Fix minor typo in aws-events docs. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-events/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-events/README.md b/packages/@aws-cdk/aws-events/README.md index bb0e434837131..4bf4c2b390dbe 100644 --- a/packages/@aws-cdk/aws-events/README.md +++ b/packages/@aws-cdk/aws-events/README.md @@ -15,7 +15,7 @@ Amazon EventBridge delivers a near real-time stream of system events that describe changes in AWS resources. For example, an AWS CodePipeline emits the [State Change](https://docs.aws.amazon.com/eventbridge/latest/userguide/event-types.html#codepipeline-event-type) -event when the pipeline changes it's state. +event when the pipeline changes its state. * __Events__: An event indicates a change in your AWS environment. AWS resources can generate events when their state changes. For example, Amazon EC2 From 20a2820ee4d022663fcd0928fbc0f61153ae953f Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 15 Mar 2021 16:47:13 +0100 Subject: [PATCH 22/41] fix: use NodeJS 14 for all packaged custom resources (#13488) NodeJS 10 is reaching end of life soon, it's time to move to NodeJS 14. Fixes https://github.com/aws/aws-cdk/issues/13225 Fixes #13534 Fixes #13484 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../integ.all-service-addons.expected.json | 12 +-- .../test/integ.assign-public-ip.expected.json | 2 +- .../integ.multiple-environments.expected.json | 8 +- .../lib/dns-validated-certificate.ts | 2 +- .../test/dns-validated-certificate.test.ts | 2 +- .../test/integ.dynamodb.global.expected.json | 3 +- .../test/integ.dynamodb.sse.expected.json | 2 +- ....global-replicas-provisioned.expected.json | 18 ++--- .../test/integ.global.expected.json | 18 ++--- ...teg.eks-cluster-handlers-vpc.expected.json | 36 ++++----- ...eks-cluster-private-endpoint.expected.json | 36 ++++----- .../test/integ.eks-cluster.expected.json | 56 +++++++------- .../integ.eks-oidc-provider.expected.json | 18 ++--- .../test/integ.fargate-cluster.expected.json | 36 ++++----- .../test/integ.dynamodb.expected.json | 10 +-- .../test/integ.kinesis.expected.json | 10 +-- .../test/integ.s3.expected.json | 4 +- .../test/integ.sns.expected.json | 2 +- .../test/integ.notifications.expected.json | 2 +- .../integ.bucket-notifications.expected.json | 4 +- ...teg.sns-bucket-notifications.expected.json | 2 +- .../integ.bucket-notifications.expected.json | 2 +- .../notifications-resource-handler.ts | 2 +- .../lib/evaluate-expression.ts | 29 ++++--- .../batch/integ.run-batch-job.expected.json | 14 ++-- .../test/batch/integ.submit-job.expected.json | 14 ++-- .../codebuild/integ.start-build.expected.json | 6 +- .../test/ecs/integ.ec2-task.expected.json | 30 ++++---- .../test/ecs/integ.fargate-task.expected.json | 14 ++-- .../test/eks/integ.call.expected.json | 36 ++++----- .../test/evaluate-expression.test.ts | 6 +- .../test/glue/integ.glue-task.expected.json | 12 +-- .../glue/integ.start-job-run.expected.json | 12 +-- .../integ.evaluate-expression.expected.json | 16 ++-- .../test/integ.job-poller.expected.json | 12 +-- .../test/integ.start-execution.expected.json | 16 ++-- .../integ.invoke-function.expected.json | 16 ++-- .../test/lambda/integ.invoke.expected.json | 4 +- .../integ.invoke.payload.only.expected.json | 4 +- .../lambda/integ.run-lambda.expected.json | 16 ++-- .../integ.call-sagemaker.expected.json | 2 +- .../integ.create-training-job.expected.json | 2 +- .../integ.start-execution.expected.json | 40 +++++++--- .../lib/provider-framework/provider.ts | 2 +- .../integ.provider.expected.json | 76 +++++++++---------- 45 files changed, 347 insertions(+), 319 deletions(-) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.expected.json b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.expected.json index a6e83c8b6ad66..47ba97c3ee10d 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.expected.json +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.expected.json @@ -1349,7 +1349,6 @@ "MeshName" ] }, - "RouteName": "name-route", "Spec": { "HttpRoute": { "Action": { @@ -1375,7 +1374,8 @@ "namevirtualrouterC00E1ACE", "VirtualRouterName" ] - } + }, + "RouteName": "name-route" } }, "namevirtualservice3DDDDF1E": { @@ -2218,7 +2218,6 @@ "MeshName" ] }, - "RouteName": "greeting-route", "Spec": { "HttpRoute": { "Action": { @@ -2244,7 +2243,8 @@ "greetingvirtualrouter0F898D1A", "VirtualRouterName" ] - } + }, + "RouteName": "greeting-route" } }, "greetingvirtualservice60AD3AD9": { @@ -3242,7 +3242,6 @@ "MeshName" ] }, - "RouteName": "greeter-route", "Spec": { "HttpRoute": { "Action": { @@ -3268,7 +3267,8 @@ "greetervirtualrouter193840BB", "VirtualRouterName" ] - } + }, + "RouteName": "greeter-route" } }, "greetervirtualservice6559950C": { diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.assign-public-ip.expected.json b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.assign-public-ip.expected.json index 0f176c27b6ab7..9193f8c5f76a4 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.assign-public-ip.expected.json +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.assign-public-ip.expected.json @@ -1044,7 +1044,7 @@ } }, "Handler": "framework.onEvent", - "Runtime": "nodejs10.x", + "Runtime": "nodejs14.x", "Timeout": 900 }, "DependsOn": [ diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.expected.json b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.expected.json index 85c6a59e919de..3a3aaddac6fce 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.expected.json +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.expected.json @@ -1537,7 +1537,6 @@ "MeshName" ] }, - "RouteName": "name-production-route", "Spec": { "HttpRoute": { "Action": { @@ -1563,7 +1562,8 @@ "nameproductionvirtualrouter00E3366D", "VirtualRouterName" ] - } + }, + "RouteName": "name-production-route" } }, "nameproductionvirtualservice4D49D5F6": { @@ -2061,7 +2061,6 @@ "MeshName" ] }, - "RouteName": "name-development-route", "Spec": { "HttpRoute": { "Action": { @@ -2087,7 +2086,8 @@ "namedevelopmentvirtualrouter0AE5105D", "VirtualRouterName" ] - } + }, + "RouteName": "name-development-route" } }, "namedevelopmentvirtualserviceD936E3FD": { diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts b/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts index 98637e43409ed..6bb389afea97e 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts @@ -77,7 +77,7 @@ export class DnsValidatedCertificate extends cdk.Resource implements ICertificat const requestorFunction = new lambda.Function(this, 'CertificateRequestorFunction', { code: lambda.Code.fromAsset(path.resolve(__dirname, '..', 'lambda-packages', 'dns_validated_certificate_handler', 'lib')), handler: 'index.certificateRequestHandler', - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, timeout: cdk.Duration.minutes(15), role: props.customResourceRole, }); diff --git a/packages/@aws-cdk/aws-certificatemanager/test/dns-validated-certificate.test.ts b/packages/@aws-cdk/aws-certificatemanager/test/dns-validated-certificate.test.ts index 1e283f558880e..17e89b68e4343 100644 --- a/packages/@aws-cdk/aws-certificatemanager/test/dns-validated-certificate.test.ts +++ b/packages/@aws-cdk/aws-certificatemanager/test/dns-validated-certificate.test.ts @@ -31,7 +31,7 @@ test('creates CloudFormation Custom Resource', () => { }); expect(stack).toHaveResource('AWS::Lambda::Function', { Handler: 'index.certificateRequestHandler', - Runtime: 'nodejs10.x', + Runtime: 'nodejs14.x', Timeout: 900, }); expect(stack).toHaveResource('AWS::IAM::Policy', { diff --git a/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.expected.json b/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.expected.json index 5b4f51877ccfe..b4dfc40358bd1 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.expected.json +++ b/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.expected.json @@ -196,7 +196,6 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC12ServiceRoleD9686810", @@ -205,6 +204,8 @@ }, "Runtime": "nodejs14.x", "Description": "Lambda to make DynamoDB a global table", + "Handler": "index.handler", + "Runtime": "nodejs14.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.sse.expected.json b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.sse.expected.json index 2f49a9b48157d..879532d0a8879 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.sse.expected.json +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.sse.expected.json @@ -571,4 +571,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.global-replicas-provisioned.expected.json b/packages/@aws-cdk/aws-dynamodb/test/integ.global-replicas-provisioned.expected.json index b4ea44f2709c4..af1e8defceed6 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.global-replicas-provisioned.expected.json +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.global-replicas-provisioned.expected.json @@ -278,7 +278,7 @@ }, "/", { - "Ref": "AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadS3Bucket806FEB2C" + "Ref": "AssetParameters9971e87ecc84219610f1dfbd0fbdd30e29f8d1f408df3f645299eb48b1c1ed00S3BucketC986830C" }, "/", { @@ -288,7 +288,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadS3VersionKey81C7BC5B" + "Ref": "AssetParameters9971e87ecc84219610f1dfbd0fbdd30e29f8d1f408df3f645299eb48b1c1ed00S3VersionKeyE0DA9F9E" } ] } @@ -301,7 +301,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadS3VersionKey81C7BC5B" + "Ref": "AssetParameters9971e87ecc84219610f1dfbd0fbdd30e29f8d1f408df3f645299eb48b1c1ed00S3VersionKeyE0DA9F9E" } ] } @@ -380,17 +380,17 @@ "Type": "String", "Description": "Artifact hash for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" }, - "AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadS3Bucket806FEB2C": { + "AssetParameters9971e87ecc84219610f1dfbd0fbdd30e29f8d1f408df3f645299eb48b1c1ed00S3BucketC986830C": { "Type": "String", - "Description": "S3 bucket for asset \"d56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aad\"" + "Description": "S3 bucket for asset \"9971e87ecc84219610f1dfbd0fbdd30e29f8d1f408df3f645299eb48b1c1ed00\"" }, - "AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadS3VersionKey81C7BC5B": { + "AssetParameters9971e87ecc84219610f1dfbd0fbdd30e29f8d1f408df3f645299eb48b1c1ed00S3VersionKeyE0DA9F9E": { "Type": "String", - "Description": "S3 key for asset version \"d56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aad\"" + "Description": "S3 key for asset version \"9971e87ecc84219610f1dfbd0fbdd30e29f8d1f408df3f645299eb48b1c1ed00\"" }, - "AssetParametersd56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aadArtifactHashD0230F6F": { + "AssetParameters9971e87ecc84219610f1dfbd0fbdd30e29f8d1f408df3f645299eb48b1c1ed00ArtifactHash57FC5CA2": { "Type": "String", - "Description": "Artifact hash for asset \"d56d097acd2563516c51a0e04dcf8d9bf3638678f723d5b80f95d5c240836aad\"" + "Description": "Artifact hash for asset \"9971e87ecc84219610f1dfbd0fbdd30e29f8d1f408df3f645299eb48b1c1ed00\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.global.expected.json b/packages/@aws-cdk/aws-dynamodb/test/integ.global.expected.json index 3896ac3a355b2..b1ca3b99819a0 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.global.expected.json +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.global.expected.json @@ -253,7 +253,7 @@ }, "/", { - "Ref": "AssetParametersa789639d6caa7a94b8135bc6ff3a6935f95624a9ed88014b5e7b3d340f20c3b4S3Bucket8BB0CECD" + "Ref": "AssetParameters96d72e249e15863715342dcc64ec41ea99be4dece8798e9e96a0da55763aa4b6S3BucketB5739B2A" }, "/", { @@ -263,7 +263,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersa789639d6caa7a94b8135bc6ff3a6935f95624a9ed88014b5e7b3d340f20c3b4S3VersionKeyC531296D" + "Ref": "AssetParameters96d72e249e15863715342dcc64ec41ea99be4dece8798e9e96a0da55763aa4b6S3VersionKey5404A90E" } ] } @@ -276,7 +276,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersa789639d6caa7a94b8135bc6ff3a6935f95624a9ed88014b5e7b3d340f20c3b4S3VersionKeyC531296D" + "Ref": "AssetParameters96d72e249e15863715342dcc64ec41ea99be4dece8798e9e96a0da55763aa4b6S3VersionKey5404A90E" } ] } @@ -329,17 +329,17 @@ "Type": "String", "Description": "Artifact hash for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" }, - "AssetParametersa789639d6caa7a94b8135bc6ff3a6935f95624a9ed88014b5e7b3d340f20c3b4S3Bucket8BB0CECD": { + "AssetParameters96d72e249e15863715342dcc64ec41ea99be4dece8798e9e96a0da55763aa4b6S3BucketB5739B2A": { "Type": "String", - "Description": "S3 bucket for asset \"a789639d6caa7a94b8135bc6ff3a6935f95624a9ed88014b5e7b3d340f20c3b4\"" + "Description": "S3 bucket for asset \"96d72e249e15863715342dcc64ec41ea99be4dece8798e9e96a0da55763aa4b6\"" }, - "AssetParametersa789639d6caa7a94b8135bc6ff3a6935f95624a9ed88014b5e7b3d340f20c3b4S3VersionKeyC531296D": { + "AssetParameters96d72e249e15863715342dcc64ec41ea99be4dece8798e9e96a0da55763aa4b6S3VersionKey5404A90E": { "Type": "String", - "Description": "S3 key for asset version \"a789639d6caa7a94b8135bc6ff3a6935f95624a9ed88014b5e7b3d340f20c3b4\"" + "Description": "S3 key for asset version \"96d72e249e15863715342dcc64ec41ea99be4dece8798e9e96a0da55763aa4b6\"" }, - "AssetParametersa789639d6caa7a94b8135bc6ff3a6935f95624a9ed88014b5e7b3d340f20c3b4ArtifactHash9D92B407": { + "AssetParameters96d72e249e15863715342dcc64ec41ea99be4dece8798e9e96a0da55763aa4b6ArtifactHash539C11C9": { "Type": "String", - "Description": "Artifact hash for asset \"a789639d6caa7a94b8135bc6ff3a6935f95624a9ed88014b5e7b3d340f20c3b4\"" + "Description": "Artifact hash for asset \"96d72e249e15863715342dcc64ec41ea99be4dece8798e9e96a0da55763aa4b6\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-handlers-vpc.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-handlers-vpc.expected.json index e7e1f394cdc7f..7f612f0788b59 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-handlers-vpc.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-handlers-vpc.expected.json @@ -1115,7 +1115,7 @@ }, "/", { - "Ref": "AssetParameters70396475d85a52e5c6ccad77894979d07433a207ea3c2668b929f3e70ffde081S3BucketFCD070AE" + "Ref": "AssetParameters7b7589d8284d359f531f595891e11720582fb63d0f0051cb08b5bfbcd6d60f60S3BucketE63A0899" }, "/", { @@ -1125,7 +1125,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters70396475d85a52e5c6ccad77894979d07433a207ea3c2668b929f3e70ffde081S3VersionKeyD47BE42B" + "Ref": "AssetParameters7b7589d8284d359f531f595891e11720582fb63d0f0051cb08b5bfbcd6d60f60S3VersionKey91A6BB03" } ] } @@ -1138,7 +1138,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters70396475d85a52e5c6ccad77894979d07433a207ea3c2668b929f3e70ffde081S3VersionKeyD47BE42B" + "Ref": "AssetParameters7b7589d8284d359f531f595891e11720582fb63d0f0051cb08b5bfbcd6d60f60S3VersionKey91A6BB03" } ] } @@ -1196,7 +1196,7 @@ }, "/", { - "Ref": "AssetParameters93bb4c1c6bbced2c1adce602d7643d5475c6e463f4f57f4cd863e064d82a3d82S3Bucket8670C328" + "Ref": "AssetParameters8bdecc7d36d3a82fef0f8acd79a1b98980868636dd7e1e04ba2982b67127ca90S3BucketC889A64C" }, "/", { @@ -1206,7 +1206,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters93bb4c1c6bbced2c1adce602d7643d5475c6e463f4f57f4cd863e064d82a3d82S3VersionKeyCEFB3AF5" + "Ref": "AssetParameters8bdecc7d36d3a82fef0f8acd79a1b98980868636dd7e1e04ba2982b67127ca90S3VersionKeyB6157388" } ] } @@ -1219,7 +1219,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters93bb4c1c6bbced2c1adce602d7643d5475c6e463f4f57f4cd863e064d82a3d82S3VersionKeyCEFB3AF5" + "Ref": "AssetParameters8bdecc7d36d3a82fef0f8acd79a1b98980868636dd7e1e04ba2982b67127ca90S3VersionKeyB6157388" } ] } @@ -1389,29 +1389,29 @@ "Type": "String", "Description": "Artifact hash for asset \"844c1a4b13479b359ea0e607dccb4a04b73e22cf88cf9b64feed2c5f0de213c0\"" }, - "AssetParameters70396475d85a52e5c6ccad77894979d07433a207ea3c2668b929f3e70ffde081S3BucketFCD070AE": { + "AssetParameters7b7589d8284d359f531f595891e11720582fb63d0f0051cb08b5bfbcd6d60f60S3BucketE63A0899": { "Type": "String", - "Description": "S3 bucket for asset \"70396475d85a52e5c6ccad77894979d07433a207ea3c2668b929f3e70ffde081\"" + "Description": "S3 bucket for asset \"7b7589d8284d359f531f595891e11720582fb63d0f0051cb08b5bfbcd6d60f60\"" }, - "AssetParameters70396475d85a52e5c6ccad77894979d07433a207ea3c2668b929f3e70ffde081S3VersionKeyD47BE42B": { + "AssetParameters7b7589d8284d359f531f595891e11720582fb63d0f0051cb08b5bfbcd6d60f60S3VersionKey91A6BB03": { "Type": "String", - "Description": "S3 key for asset version \"70396475d85a52e5c6ccad77894979d07433a207ea3c2668b929f3e70ffde081\"" + "Description": "S3 key for asset version \"7b7589d8284d359f531f595891e11720582fb63d0f0051cb08b5bfbcd6d60f60\"" }, - "AssetParameters70396475d85a52e5c6ccad77894979d07433a207ea3c2668b929f3e70ffde081ArtifactHashF56FF52E": { + "AssetParameters7b7589d8284d359f531f595891e11720582fb63d0f0051cb08b5bfbcd6d60f60ArtifactHashEB6B332A": { "Type": "String", - "Description": "Artifact hash for asset \"70396475d85a52e5c6ccad77894979d07433a207ea3c2668b929f3e70ffde081\"" + "Description": "Artifact hash for asset \"7b7589d8284d359f531f595891e11720582fb63d0f0051cb08b5bfbcd6d60f60\"" }, - "AssetParameters93bb4c1c6bbced2c1adce602d7643d5475c6e463f4f57f4cd863e064d82a3d82S3Bucket8670C328": { + "AssetParameters8bdecc7d36d3a82fef0f8acd79a1b98980868636dd7e1e04ba2982b67127ca90S3BucketC889A64C": { "Type": "String", - "Description": "S3 bucket for asset \"93bb4c1c6bbced2c1adce602d7643d5475c6e463f4f57f4cd863e064d82a3d82\"" + "Description": "S3 bucket for asset \"8bdecc7d36d3a82fef0f8acd79a1b98980868636dd7e1e04ba2982b67127ca90\"" }, - "AssetParameters93bb4c1c6bbced2c1adce602d7643d5475c6e463f4f57f4cd863e064d82a3d82S3VersionKeyCEFB3AF5": { + "AssetParameters8bdecc7d36d3a82fef0f8acd79a1b98980868636dd7e1e04ba2982b67127ca90S3VersionKeyB6157388": { "Type": "String", - "Description": "S3 key for asset version \"93bb4c1c6bbced2c1adce602d7643d5475c6e463f4f57f4cd863e064d82a3d82\"" + "Description": "S3 key for asset version \"8bdecc7d36d3a82fef0f8acd79a1b98980868636dd7e1e04ba2982b67127ca90\"" }, - "AssetParameters93bb4c1c6bbced2c1adce602d7643d5475c6e463f4f57f4cd863e064d82a3d82ArtifactHashBEC324DA": { + "AssetParameters8bdecc7d36d3a82fef0f8acd79a1b98980868636dd7e1e04ba2982b67127ca90ArtifactHash8A7D59E8": { "Type": "String", - "Description": "Artifact hash for asset \"93bb4c1c6bbced2c1adce602d7643d5475c6e463f4f57f4cd863e064d82a3d82\"" + "Description": "Artifact hash for asset \"8bdecc7d36d3a82fef0f8acd79a1b98980868636dd7e1e04ba2982b67127ca90\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.expected.json index acbe47374bead..8f59f96ca7a6b 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.expected.json @@ -1048,7 +1048,7 @@ }, "/", { - "Ref": "AssetParameters75667ab2bbef2c8efc57fb73bf352f345af1d471fb09cb11f5b7bc27d009b609S3BucketA8C94679" + "Ref": "AssetParametersf2b553bd53fbb997e6f23206daa298a3f8bfa7c9805c8c7c99155b4acc2d0cd6S3BucketFA2AD206" }, "/", { @@ -1058,7 +1058,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters75667ab2bbef2c8efc57fb73bf352f345af1d471fb09cb11f5b7bc27d009b609S3VersionKey3777DB64" + "Ref": "AssetParametersf2b553bd53fbb997e6f23206daa298a3f8bfa7c9805c8c7c99155b4acc2d0cd6S3VersionKeyD5C722C7" } ] } @@ -1071,7 +1071,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters75667ab2bbef2c8efc57fb73bf352f345af1d471fb09cb11f5b7bc27d009b609S3VersionKey3777DB64" + "Ref": "AssetParametersf2b553bd53fbb997e6f23206daa298a3f8bfa7c9805c8c7c99155b4acc2d0cd6S3VersionKeyD5C722C7" } ] } @@ -1117,7 +1117,7 @@ }, "/", { - "Ref": "AssetParameters8d4ffe9194a1ca97e2e9377049ddbeb3adbdaa984c8a90c08a5d8d6b328bdf7cS3BucketFEA5F85E" + "Ref": "AssetParameters5988c5261a097d0fc0f9ce78bb67db66f6ddf5c934fc58fa695dc27acaf6e69fS3Bucket5150D79C" }, "/", { @@ -1127,7 +1127,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters8d4ffe9194a1ca97e2e9377049ddbeb3adbdaa984c8a90c08a5d8d6b328bdf7cS3VersionKey226CF52C" + "Ref": "AssetParameters5988c5261a097d0fc0f9ce78bb67db66f6ddf5c934fc58fa695dc27acaf6e69fS3VersionKey7F05152C" } ] } @@ -1140,7 +1140,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters8d4ffe9194a1ca97e2e9377049ddbeb3adbdaa984c8a90c08a5d8d6b328bdf7cS3VersionKey226CF52C" + "Ref": "AssetParameters5988c5261a097d0fc0f9ce78bb67db66f6ddf5c934fc58fa695dc27acaf6e69fS3VersionKey7F05152C" } ] } @@ -1310,29 +1310,29 @@ "Type": "String", "Description": "Artifact hash for asset \"844c1a4b13479b359ea0e607dccb4a04b73e22cf88cf9b64feed2c5f0de213c0\"" }, - "AssetParameters75667ab2bbef2c8efc57fb73bf352f345af1d471fb09cb11f5b7bc27d009b609S3BucketA8C94679": { + "AssetParametersf2b553bd53fbb997e6f23206daa298a3f8bfa7c9805c8c7c99155b4acc2d0cd6S3BucketFA2AD206": { "Type": "String", - "Description": "S3 bucket for asset \"75667ab2bbef2c8efc57fb73bf352f345af1d471fb09cb11f5b7bc27d009b609\"" + "Description": "S3 bucket for asset \"f2b553bd53fbb997e6f23206daa298a3f8bfa7c9805c8c7c99155b4acc2d0cd6\"" }, - "AssetParameters75667ab2bbef2c8efc57fb73bf352f345af1d471fb09cb11f5b7bc27d009b609S3VersionKey3777DB64": { + "AssetParametersf2b553bd53fbb997e6f23206daa298a3f8bfa7c9805c8c7c99155b4acc2d0cd6S3VersionKeyD5C722C7": { "Type": "String", - "Description": "S3 key for asset version \"75667ab2bbef2c8efc57fb73bf352f345af1d471fb09cb11f5b7bc27d009b609\"" + "Description": "S3 key for asset version \"f2b553bd53fbb997e6f23206daa298a3f8bfa7c9805c8c7c99155b4acc2d0cd6\"" }, - "AssetParameters75667ab2bbef2c8efc57fb73bf352f345af1d471fb09cb11f5b7bc27d009b609ArtifactHash14CC8C95": { + "AssetParametersf2b553bd53fbb997e6f23206daa298a3f8bfa7c9805c8c7c99155b4acc2d0cd6ArtifactHashD053AD55": { "Type": "String", - "Description": "Artifact hash for asset \"75667ab2bbef2c8efc57fb73bf352f345af1d471fb09cb11f5b7bc27d009b609\"" + "Description": "Artifact hash for asset \"f2b553bd53fbb997e6f23206daa298a3f8bfa7c9805c8c7c99155b4acc2d0cd6\"" }, - "AssetParameters8d4ffe9194a1ca97e2e9377049ddbeb3adbdaa984c8a90c08a5d8d6b328bdf7cS3BucketFEA5F85E": { + "AssetParameters5988c5261a097d0fc0f9ce78bb67db66f6ddf5c934fc58fa695dc27acaf6e69fS3Bucket5150D79C": { "Type": "String", - "Description": "S3 bucket for asset \"8d4ffe9194a1ca97e2e9377049ddbeb3adbdaa984c8a90c08a5d8d6b328bdf7c\"" + "Description": "S3 bucket for asset \"5988c5261a097d0fc0f9ce78bb67db66f6ddf5c934fc58fa695dc27acaf6e69f\"" }, - "AssetParameters8d4ffe9194a1ca97e2e9377049ddbeb3adbdaa984c8a90c08a5d8d6b328bdf7cS3VersionKey226CF52C": { + "AssetParameters5988c5261a097d0fc0f9ce78bb67db66f6ddf5c934fc58fa695dc27acaf6e69fS3VersionKey7F05152C": { "Type": "String", - "Description": "S3 key for asset version \"8d4ffe9194a1ca97e2e9377049ddbeb3adbdaa984c8a90c08a5d8d6b328bdf7c\"" + "Description": "S3 key for asset version \"5988c5261a097d0fc0f9ce78bb67db66f6ddf5c934fc58fa695dc27acaf6e69f\"" }, - "AssetParameters8d4ffe9194a1ca97e2e9377049ddbeb3adbdaa984c8a90c08a5d8d6b328bdf7cArtifactHashA6BF0EB3": { + "AssetParameters5988c5261a097d0fc0f9ce78bb67db66f6ddf5c934fc58fa695dc27acaf6e69fArtifactHashEE0F431C": { "Type": "String", - "Description": "Artifact hash for asset \"8d4ffe9194a1ca97e2e9377049ddbeb3adbdaa984c8a90c08a5d8d6b328bdf7c\"" + "Description": "Artifact hash for asset \"5988c5261a097d0fc0f9ce78bb67db66f6ddf5c934fc58fa695dc27acaf6e69f\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json index c157211d73f87..1ea62568197d4 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json @@ -3810,7 +3810,7 @@ }, "/", { - "Ref": "AssetParameters264acf17cbf0c643f47bec1f4dbaed805e3bd1bad3f018c093d16fb936227daaS3Bucket862E8D6F" + "Ref": "AssetParameters5598bd5ce38da10f7a9c6f8e54d4f50d7c0befd5309540ab64d64985236f2ef9S3BucketC3BFBE73" }, "/", { @@ -3820,7 +3820,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters264acf17cbf0c643f47bec1f4dbaed805e3bd1bad3f018c093d16fb936227daaS3VersionKey9466BE3D" + "Ref": "AssetParameters5598bd5ce38da10f7a9c6f8e54d4f50d7c0befd5309540ab64d64985236f2ef9S3VersionKeyFA8225D5" } ] } @@ -3833,7 +3833,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters264acf17cbf0c643f47bec1f4dbaed805e3bd1bad3f018c093d16fb936227daaS3VersionKey9466BE3D" + "Ref": "AssetParameters5598bd5ce38da10f7a9c6f8e54d4f50d7c0befd5309540ab64d64985236f2ef9S3VersionKeyFA8225D5" } ] } @@ -3879,7 +3879,7 @@ }, "/", { - "Ref": "AssetParameterse00474136b86298d67c0ad626f1dafe622c51168233097531c5ba6791d43aba3S3Bucket5829AD66" + "Ref": "AssetParameterse334ff007b5126b62d66d4baac94004f4281dc2b0b0c4685ec93e330cb59e921S3Bucket706500B1" }, "/", { @@ -3889,7 +3889,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse00474136b86298d67c0ad626f1dafe622c51168233097531c5ba6791d43aba3S3VersionKey44FDB4A8" + "Ref": "AssetParameterse334ff007b5126b62d66d4baac94004f4281dc2b0b0c4685ec93e330cb59e921S3VersionKeyAB3509E4" } ] } @@ -3902,7 +3902,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse00474136b86298d67c0ad626f1dafe622c51168233097531c5ba6791d43aba3S3VersionKey44FDB4A8" + "Ref": "AssetParameterse334ff007b5126b62d66d4baac94004f4281dc2b0b0c4685ec93e330cb59e921S3VersionKeyAB3509E4" } ] } @@ -4068,7 +4068,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3Bucket14156880" + "Ref": "AssetParametersb7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4S3BucketF7BC1777" }, "S3Key": { "Fn::Join": [ @@ -4081,7 +4081,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3VersionKey5225BCA4" + "Ref": "AssetParametersb7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4S3VersionKey1C340B30" } ] } @@ -4094,7 +4094,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3VersionKey5225BCA4" + "Ref": "AssetParametersb7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4S3VersionKey1C340B30" } ] } @@ -4469,7 +4469,7 @@ } }, "Handler": "framework.onEvent", - "Runtime": "nodejs10.x", + "Runtime": "nodejs14.x", "Timeout": 900 }, "DependsOn": [ @@ -4662,17 +4662,17 @@ "Type": "String", "Description": "Artifact hash for asset \"844c1a4b13479b359ea0e607dccb4a04b73e22cf88cf9b64feed2c5f0de213c0\"" }, - "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3Bucket14156880": { + "AssetParametersb7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4S3BucketF7BC1777": { "Type": "String", - "Description": "S3 bucket for asset \"b075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0de\"" + "Description": "S3 bucket for asset \"b7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4\"" }, - "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3VersionKey5225BCA4": { + "AssetParametersb7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4S3VersionKey1C340B30": { "Type": "String", - "Description": "S3 key for asset version \"b075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0de\"" + "Description": "S3 key for asset version \"b7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4\"" }, - "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deArtifactHashC509349A": { + "AssetParametersb7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4ArtifactHashD6EA1BC7": { "Type": "String", - "Description": "Artifact hash for asset \"b075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0de\"" + "Description": "Artifact hash for asset \"b7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4\"" }, "AssetParameters952bd1c03e8201c4c1c67d6de0f3fdaaf88fda05f89a1232c3f6364343cd5344S3Bucket055DC235": { "Type": "String", @@ -4698,29 +4698,29 @@ "Type": "String", "Description": "Artifact hash for asset \"5f49893093e1ad14831626016699156d48da5f0890f19eb930bc3c46cf5f636d\"" }, - "AssetParameters264acf17cbf0c643f47bec1f4dbaed805e3bd1bad3f018c093d16fb936227daaS3Bucket862E8D6F": { + "AssetParameters5598bd5ce38da10f7a9c6f8e54d4f50d7c0befd5309540ab64d64985236f2ef9S3BucketC3BFBE73": { "Type": "String", - "Description": "S3 bucket for asset \"264acf17cbf0c643f47bec1f4dbaed805e3bd1bad3f018c093d16fb936227daa\"" + "Description": "S3 bucket for asset \"5598bd5ce38da10f7a9c6f8e54d4f50d7c0befd5309540ab64d64985236f2ef9\"" }, - "AssetParameters264acf17cbf0c643f47bec1f4dbaed805e3bd1bad3f018c093d16fb936227daaS3VersionKey9466BE3D": { + "AssetParameters5598bd5ce38da10f7a9c6f8e54d4f50d7c0befd5309540ab64d64985236f2ef9S3VersionKeyFA8225D5": { "Type": "String", - "Description": "S3 key for asset version \"264acf17cbf0c643f47bec1f4dbaed805e3bd1bad3f018c093d16fb936227daa\"" + "Description": "S3 key for asset version \"5598bd5ce38da10f7a9c6f8e54d4f50d7c0befd5309540ab64d64985236f2ef9\"" }, - "AssetParameters264acf17cbf0c643f47bec1f4dbaed805e3bd1bad3f018c093d16fb936227daaArtifactHashC5F5158C": { + "AssetParameters5598bd5ce38da10f7a9c6f8e54d4f50d7c0befd5309540ab64d64985236f2ef9ArtifactHash8F06BD93": { "Type": "String", - "Description": "Artifact hash for asset \"264acf17cbf0c643f47bec1f4dbaed805e3bd1bad3f018c093d16fb936227daa\"" + "Description": "Artifact hash for asset \"5598bd5ce38da10f7a9c6f8e54d4f50d7c0befd5309540ab64d64985236f2ef9\"" }, - "AssetParameterse00474136b86298d67c0ad626f1dafe622c51168233097531c5ba6791d43aba3S3Bucket5829AD66": { + "AssetParameterse334ff007b5126b62d66d4baac94004f4281dc2b0b0c4685ec93e330cb59e921S3Bucket706500B1": { "Type": "String", - "Description": "S3 bucket for asset \"e00474136b86298d67c0ad626f1dafe622c51168233097531c5ba6791d43aba3\"" + "Description": "S3 bucket for asset \"e334ff007b5126b62d66d4baac94004f4281dc2b0b0c4685ec93e330cb59e921\"" }, - "AssetParameterse00474136b86298d67c0ad626f1dafe622c51168233097531c5ba6791d43aba3S3VersionKey44FDB4A8": { + "AssetParameterse334ff007b5126b62d66d4baac94004f4281dc2b0b0c4685ec93e330cb59e921S3VersionKeyAB3509E4": { "Type": "String", - "Description": "S3 key for asset version \"e00474136b86298d67c0ad626f1dafe622c51168233097531c5ba6791d43aba3\"" + "Description": "S3 key for asset version \"e334ff007b5126b62d66d4baac94004f4281dc2b0b0c4685ec93e330cb59e921\"" }, - "AssetParameterse00474136b86298d67c0ad626f1dafe622c51168233097531c5ba6791d43aba3ArtifactHashB6A3908A": { + "AssetParameterse334ff007b5126b62d66d4baac94004f4281dc2b0b0c4685ec93e330cb59e921ArtifactHash31BE978F": { "Type": "String", - "Description": "Artifact hash for asset \"e00474136b86298d67c0ad626f1dafe622c51168233097531c5ba6791d43aba3\"" + "Description": "Artifact hash for asset \"e334ff007b5126b62d66d4baac94004f4281dc2b0b0c4685ec93e330cb59e921\"" }, "SsmParameterValueawsserviceeksoptimizedami119amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-oidc-provider.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-oidc-provider.expected.json index b6a2cad449f9c..0f0a5c61cdf67 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-oidc-provider.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-oidc-provider.expected.json @@ -79,7 +79,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3Bucket14156880" + "Ref": "AssetParametersb7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4S3BucketF7BC1777" }, "S3Key": { "Fn::Join": [ @@ -92,7 +92,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3VersionKey5225BCA4" + "Ref": "AssetParametersb7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4S3VersionKey1C340B30" } ] } @@ -105,7 +105,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3VersionKey5225BCA4" + "Ref": "AssetParametersb7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4S3VersionKey1C340B30" } ] } @@ -132,17 +132,17 @@ } }, "Parameters": { - "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3Bucket14156880": { + "AssetParametersb7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4S3BucketF7BC1777": { "Type": "String", - "Description": "S3 bucket for asset \"b075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0de\"" + "Description": "S3 bucket for asset \"b7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4\"" }, - "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3VersionKey5225BCA4": { + "AssetParametersb7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4S3VersionKey1C340B30": { "Type": "String", - "Description": "S3 key for asset version \"b075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0de\"" + "Description": "S3 key for asset version \"b7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4\"" }, - "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deArtifactHashC509349A": { + "AssetParametersb7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4ArtifactHashD6EA1BC7": { "Type": "String", - "Description": "Artifact hash for asset \"b075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0de\"" + "Description": "Artifact hash for asset \"b7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json index 441cac51ffd69..c8d1c3a68bdf9 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json @@ -1125,7 +1125,7 @@ }, "/", { - "Ref": "AssetParameters3d252d05ccf0ae2934dd20707e8a709b466b2b8ea00c04ee8735667f90b17ac1S3Bucket01B07207" + "Ref": "AssetParameters8f90971131eea6a356f7cfb4997f4b70bb577cf34d007550f5e28c342f6c47f7S3Bucket3C7D361B" }, "/", { @@ -1135,7 +1135,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3d252d05ccf0ae2934dd20707e8a709b466b2b8ea00c04ee8735667f90b17ac1S3VersionKey3EEF52BA" + "Ref": "AssetParameters8f90971131eea6a356f7cfb4997f4b70bb577cf34d007550f5e28c342f6c47f7S3VersionKey455B40AC" } ] } @@ -1148,7 +1148,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3d252d05ccf0ae2934dd20707e8a709b466b2b8ea00c04ee8735667f90b17ac1S3VersionKey3EEF52BA" + "Ref": "AssetParameters8f90971131eea6a356f7cfb4997f4b70bb577cf34d007550f5e28c342f6c47f7S3VersionKey455B40AC" } ] } @@ -1194,7 +1194,7 @@ }, "/", { - "Ref": "AssetParametersedcd50bb250cf7cf664a737720e623ad66ada9b9e609205686c476e516cf2f23S3Bucket586F6135" + "Ref": "AssetParameters66581105512a058e4f8b54fb8be68bf58cd32bfa5e87be3574d5e53eab943e49S3Bucket860C7E3D" }, "/", { @@ -1204,7 +1204,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersedcd50bb250cf7cf664a737720e623ad66ada9b9e609205686c476e516cf2f23S3VersionKey6EFBFC1D" + "Ref": "AssetParameters66581105512a058e4f8b54fb8be68bf58cd32bfa5e87be3574d5e53eab943e49S3VersionKey11F1F533" } ] } @@ -1217,7 +1217,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersedcd50bb250cf7cf664a737720e623ad66ada9b9e609205686c476e516cf2f23S3VersionKey6EFBFC1D" + "Ref": "AssetParameters66581105512a058e4f8b54fb8be68bf58cd32bfa5e87be3574d5e53eab943e49S3VersionKey11F1F533" } ] } @@ -1387,29 +1387,29 @@ "Type": "String", "Description": "Artifact hash for asset \"844c1a4b13479b359ea0e607dccb4a04b73e22cf88cf9b64feed2c5f0de213c0\"" }, - "AssetParameters3d252d05ccf0ae2934dd20707e8a709b466b2b8ea00c04ee8735667f90b17ac1S3Bucket01B07207": { + "AssetParameters8f90971131eea6a356f7cfb4997f4b70bb577cf34d007550f5e28c342f6c47f7S3Bucket3C7D361B": { "Type": "String", - "Description": "S3 bucket for asset \"3d252d05ccf0ae2934dd20707e8a709b466b2b8ea00c04ee8735667f90b17ac1\"" + "Description": "S3 bucket for asset \"8f90971131eea6a356f7cfb4997f4b70bb577cf34d007550f5e28c342f6c47f7\"" }, - "AssetParameters3d252d05ccf0ae2934dd20707e8a709b466b2b8ea00c04ee8735667f90b17ac1S3VersionKey3EEF52BA": { + "AssetParameters8f90971131eea6a356f7cfb4997f4b70bb577cf34d007550f5e28c342f6c47f7S3VersionKey455B40AC": { "Type": "String", - "Description": "S3 key for asset version \"3d252d05ccf0ae2934dd20707e8a709b466b2b8ea00c04ee8735667f90b17ac1\"" + "Description": "S3 key for asset version \"8f90971131eea6a356f7cfb4997f4b70bb577cf34d007550f5e28c342f6c47f7\"" }, - "AssetParameters3d252d05ccf0ae2934dd20707e8a709b466b2b8ea00c04ee8735667f90b17ac1ArtifactHash812ED4D5": { + "AssetParameters8f90971131eea6a356f7cfb4997f4b70bb577cf34d007550f5e28c342f6c47f7ArtifactHashA258B8DC": { "Type": "String", - "Description": "Artifact hash for asset \"3d252d05ccf0ae2934dd20707e8a709b466b2b8ea00c04ee8735667f90b17ac1\"" + "Description": "Artifact hash for asset \"8f90971131eea6a356f7cfb4997f4b70bb577cf34d007550f5e28c342f6c47f7\"" }, - "AssetParametersedcd50bb250cf7cf664a737720e623ad66ada9b9e609205686c476e516cf2f23S3Bucket586F6135": { + "AssetParameters66581105512a058e4f8b54fb8be68bf58cd32bfa5e87be3574d5e53eab943e49S3Bucket860C7E3D": { "Type": "String", - "Description": "S3 bucket for asset \"edcd50bb250cf7cf664a737720e623ad66ada9b9e609205686c476e516cf2f23\"" + "Description": "S3 bucket for asset \"66581105512a058e4f8b54fb8be68bf58cd32bfa5e87be3574d5e53eab943e49\"" }, - "AssetParametersedcd50bb250cf7cf664a737720e623ad66ada9b9e609205686c476e516cf2f23S3VersionKey6EFBFC1D": { + "AssetParameters66581105512a058e4f8b54fb8be68bf58cd32bfa5e87be3574d5e53eab943e49S3VersionKey11F1F533": { "Type": "String", - "Description": "S3 key for asset version \"edcd50bb250cf7cf664a737720e623ad66ada9b9e609205686c476e516cf2f23\"" + "Description": "S3 key for asset version \"66581105512a058e4f8b54fb8be68bf58cd32bfa5e87be3574d5e53eab943e49\"" }, - "AssetParametersedcd50bb250cf7cf664a737720e623ad66ada9b9e609205686c476e516cf2f23ArtifactHashB34EB8FE": { + "AssetParameters66581105512a058e4f8b54fb8be68bf58cd32bfa5e87be3574d5e53eab943e49ArtifactHash660928E1": { "Type": "String", - "Description": "Artifact hash for asset \"edcd50bb250cf7cf664a737720e623ad66ada9b9e609205686c476e516cf2f23\"" + "Description": "Artifact hash for asset \"66581105512a058e4f8b54fb8be68bf58cd32bfa5e87be3574d5e53eab943e49\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.expected.json index 6a16698ea1668..406a123559d74 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.expected.json @@ -72,13 +72,13 @@ "Code": { "ZipFile": "exports.handler = async function handler(event) {\n console.log('event:', JSON.stringify(event, undefined, 2));\n return { event };\n}" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "FServiceRole3AC82EE1", "Arn" ] }, + "Handler": "index.handler", "Runtime": "nodejs10.x" }, "DependsOn": [ @@ -89,16 +89,16 @@ "FDynamoDBEventSourcelambdaeventsourcedynamodbT7967476AE652DA48": { "Type": "AWS::Lambda::EventSourceMapping", "Properties": { + "FunctionName": { + "Ref": "FC4345940" + }, + "BatchSize": 5, "EventSourceArn": { "Fn::GetAtt": [ "TD925BC7E", "StreamArn" ] }, - "FunctionName": { - "Ref": "FC4345940" - }, - "BatchSize": 5, "StartingPosition": "TRIM_HORIZON" } }, diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json index 670d7d05ced59..54bfa6f32f361 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json @@ -79,13 +79,13 @@ "Code": { "ZipFile": "exports.handler = async function handler(event) {\n console.log('event:', JSON.stringify(event, undefined, 2));\n return { event };\n}" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "FServiceRole3AC82EE1", "Arn" ] }, + "Handler": "index.handler", "Runtime": "nodejs10.x" }, "DependsOn": [ @@ -96,16 +96,16 @@ "FKinesisEventSourcelambdaeventsourcekinesisQ645CE7DB2D6BCCF5": { "Type": "AWS::Lambda::EventSourceMapping", "Properties": { + "FunctionName": { + "Ref": "FC4345940" + }, + "BatchSize": 100, "EventSourceArn": { "Fn::GetAtt": [ "Q63C6E3AB", "Arn" ] }, - "FunctionName": { - "Ref": "FC4345940" - }, - "BatchSize": 100, "StartingPosition": "TRIM_HORIZON" } }, diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json index 53b599aa22c5a..e4b5d64e2d04e 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json @@ -37,13 +37,13 @@ "Code": { "ZipFile": "exports.handler = async function handler(event) {\n console.log('event:', JSON.stringify(event, undefined, 2));\n return { event };\n}" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "FServiceRole3AC82EE1", "Arn" ] }, + "Handler": "index.handler", "Runtime": "nodejs10.x" }, "DependsOn": [ @@ -185,7 +185,7 @@ "Arn" ] }, - "Runtime": "nodejs10.x", + "Runtime": "nodejs14.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json index f756b4707d4c1..9a9c44e67d95f 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json @@ -37,13 +37,13 @@ "Code": { "ZipFile": "exports.handler = async function handler(event) {\n console.log('event:', JSON.stringify(event, undefined, 2));\n return { event };\n}" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "FServiceRole3AC82EE1", "Arn" ] }, + "Handler": "index.handler", "Runtime": "nodejs10.x" }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json index 4e12e4dd9badf..f0babb81fc0a6 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json @@ -220,7 +220,7 @@ "Arn" ] }, - "Runtime": "nodejs10.x", + "Runtime": "nodejs14.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json index 8a1c134ff2651..918c4a9d09334 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json @@ -106,13 +106,13 @@ "Code": { "ZipFile": "exports.handler = function handler(event, _context, callback) {\n console.log(JSON.stringify(event, undefined, 2));\n return callback(null, event);\n}" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "MyFunctionServiceRole3C357FF2", "Arn" ] }, + "Handler": "index.handler", "Runtime": "nodejs10.x" }, "DependsOn": [ @@ -244,7 +244,7 @@ "Arn" ] }, - "Runtime": "nodejs10.x", + "Runtime": "nodejs14.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json index 0aabfdd9f8e19..ecdd9d831e74f 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json @@ -203,7 +203,7 @@ "Arn" ] }, - "Runtime": "nodejs10.x", + "Runtime": "nodejs14.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json index 0da38cd2026c5..ea40221e82f15 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json @@ -192,7 +192,7 @@ "Arn" ] }, - "Runtime": "nodejs10.x", + "Runtime": "nodejs14.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts index 3f07a0d5b7bdf..8a47247be1b1e 100644 --- a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts +++ b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts @@ -82,7 +82,7 @@ export class NotificationsResourceHandler extends Construct { Code: { ZipFile: `exports.handler = ${handler.toString()};` }, Handler: 'index.handler', Role: role.roleArn, - Runtime: 'nodejs10.x', + Runtime: 'nodejs14.x', Timeout: 300, }, }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts index 64c25d5e3dd3a..cf36dc806ebfb 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts @@ -99,19 +99,28 @@ export class EvaluateExpression extends sfn.TaskStateBase { function createEvalFn(runtime: lambda.Runtime, scope: Construct) { const lambdaPurpose = 'Eval'; + const nodeJsGuids = { + [lambda.Runtime.NODEJS_14_X.name]: 'da2d1181-604e-4a45-8694-1a6abd7fe42d', + [lambda.Runtime.NODEJS_12_X.name]: '2b81e383-aad2-44db-8aaf-b4809ae0e3b4', + [lambda.Runtime.NODEJS_10_X.name]: 'a0d2ce44-871b-4e74-87a1-f5e63d7c3bdc', + }; + switch (runtime) { case lambda.Runtime.NODEJS_14_X: case lambda.Runtime.NODEJS_12_X: case lambda.Runtime.NODEJS_10_X: - return new lambda.SingletonFunction(scope, 'EvalFunction', { - runtime, - handler: 'index.handler', - uuid: 'a0d2ce44-871b-4e74-87a1-f5e63d7c3bdc', - lambdaPurpose, - code: lambda.Code.fromAsset(path.join(__dirname, 'eval-nodejs-handler')), - }); - // TODO: implement other runtimes - default: - throw new Error(`The runtime ${runtime.name} is currently not supported.`); + const uuid = nodeJsGuids[runtime.name]; + if (uuid) { + return new lambda.SingletonFunction(scope, 'EvalFunction', { + runtime, + uuid, + handler: 'index.handler', + lambdaPurpose, + code: lambda.Code.fromAsset(path.join(__dirname, 'eval-nodejs-handler')), + }); + } + break; } + + throw new Error(`The runtime ${runtime.name} is currently not supported.`); } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.expected.json index d7c5476ab3488..252eeef05dea4 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.expected.json @@ -988,6 +988,12 @@ "StateMachine2E01A3A5": { "Type": "AWS::StepFunctions::StateMachine", "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, "DefinitionString": { "Fn::Join": [ "", @@ -1007,12 +1013,6 @@ ":states:::batch:submitJob.sync\"}}}" ] ] - }, - "RoleArn": { - "Fn::GetAtt": [ - "StateMachineRoleB840431D", - "Arn" - ] } }, "DependsOn": [ @@ -1033,4 +1033,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.expected.json index 165ab49ae11a7..6b45fb3889e9b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.expected.json @@ -988,6 +988,12 @@ "StateMachine2E01A3A5": { "Type": "AWS::StepFunctions::StateMachine", "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, "DefinitionString": { "Fn::Join": [ "", @@ -1007,12 +1013,6 @@ "\",\"Parameters\":{\"foo.$\":\"$.bar\"},\"ContainerOverrides\":{\"Environment\":[{\"Name\":\"key\",\"Value\":\"value\"}],\"Memory\":256,\"Vcpus\":1},\"RetryStrategy\":{\"Attempts\":3},\"Timeout\":{\"AttemptDurationSeconds\":60}}}}}" ] ] - }, - "RoleArn": { - "Fn::GetAtt": [ - "StateMachineRoleB840431D", - "Arn" - ] } }, "DependsOn": [ @@ -1033,4 +1033,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json index ad853ea6241c3..432368a1de6d6 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json @@ -154,8 +154,8 @@ "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"echo \\\"Hello, CodeBuild!\\\"\"\n ]\n }\n }\n}", "Type": "NO_SOURCE" }, - "Name": "MyTestProject", - "EncryptionKey": "alias/aws/s3" + "EncryptionKey": "alias/aws/s3", + "Name": "MyTestProject" } }, "StateMachineRoleB840431D": { @@ -261,4 +261,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.ec2-task.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.ec2-task.expected.json index 26ef883551481..2b574e0b9baa4 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.ec2-task.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.ec2-task.expected.json @@ -80,8 +80,6 @@ "ecs:Poll", "ecs:StartTelemetrySession" ], - "Effect": "Allow", - "Resource": "*", "Condition": { "ArnEquals": { "ecs:cluster": { @@ -91,7 +89,9 @@ ] } } - } + }, + "Effect": "Allow", + "Resource": "*" }, { "Action": [ @@ -268,8 +268,6 @@ "ecs:DescribeContainerInstances", "ecs:DescribeTasks" ], - "Effect": "Allow", - "Resource": "*", "Condition": { "ArnEquals": { "ecs:cluster": { @@ -279,7 +277,9 @@ ] } } - } + }, + "Effect": "Allow", + "Resource": "*" }, { "Action": [ @@ -330,14 +330,12 @@ "Code": { "ZipFile": "import boto3, json, os, time\n\necs = boto3.client('ecs')\nautoscaling = boto3.client('autoscaling')\n\n\ndef lambda_handler(event, context):\n print(json.dumps(event))\n cluster = os.environ['CLUSTER']\n snsTopicArn = event['Records'][0]['Sns']['TopicArn']\n lifecycle_event = json.loads(event['Records'][0]['Sns']['Message'])\n instance_id = lifecycle_event.get('EC2InstanceId')\n if not instance_id:\n print('Got event without EC2InstanceId: %s', json.dumps(event))\n return\n\n instance_arn = container_instance_arn(cluster, instance_id)\n print('Instance %s has container instance ARN %s' % (lifecycle_event['EC2InstanceId'], instance_arn))\n\n if not instance_arn:\n return\n\n while has_tasks(cluster, instance_arn):\n time.sleep(10)\n\n try:\n print('Terminating instance %s' % instance_id)\n autoscaling.complete_lifecycle_action(\n LifecycleActionResult='CONTINUE',\n **pick(lifecycle_event, 'LifecycleHookName', 'LifecycleActionToken', 'AutoScalingGroupName'))\n except Exception as e:\n # Lifecycle action may have already completed.\n print(str(e))\n\n\ndef container_instance_arn(cluster, instance_id):\n \"\"\"Turn an instance ID into a container instance ARN.\"\"\"\n arns = ecs.list_container_instances(cluster=cluster, filter='ec2InstanceId==' + instance_id)['containerInstanceArns']\n if not arns:\n return None\n return arns[0]\n\n\ndef has_tasks(cluster, instance_arn):\n \"\"\"Return True if the instance is running tasks for the given cluster.\"\"\"\n instances = ecs.describe_container_instances(cluster=cluster, containerInstances=[instance_arn])['containerInstances']\n if not instances:\n return False\n instance = instances[0]\n\n if instance['status'] == 'ACTIVE':\n # Start draining, then try again later\n set_container_instance_to_draining(cluster, instance_arn)\n return True\n\n tasks = instance['runningTasksCount'] + instance['pendingTasksCount']\n print('Instance %s has %s tasks' % (instance_arn, tasks))\n\n return tasks > 0\n\n\ndef set_container_instance_to_draining(cluster, instance_arn):\n ecs.update_container_instances_state(\n cluster=cluster,\n containerInstances=[instance_arn], status='DRAINING')\n\n\ndef pick(dct, *keys):\n \"\"\"Pick a subset of a dict.\"\"\"\n return {k: v for k, v in dct.items() if k in keys}\n" }, - "Handler": "index.lambda_handler", "Role": { "Fn::GetAtt": [ "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32", "Arn" ] }, - "Runtime": "python3.6", "Environment": { "Variables": { "CLUSTER": { @@ -345,6 +343,8 @@ } } }, + "Handler": "index.lambda_handler", + "Runtime": "python3.6", "Tags": [ { "Key": "Name", @@ -703,6 +703,12 @@ "StateMachine2E01A3A5": { "Type": "AWS::StepFunctions::StateMachine", "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, "DefinitionString": { "Fn::Join": [ "", @@ -725,12 +731,6 @@ ":states:::ecs:runTask.sync\"}}}" ] ] - }, - "RoleArn": { - "Fn::GetAtt": [ - "StateMachineRoleB840431D", - "Arn" - ] } }, "DependsOn": [ @@ -745,4 +745,4 @@ "Default": "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id" } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.fargate-task.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.fargate-task.expected.json index 7f15f160b7c39..00682997b2a31 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.fargate-task.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.fargate-task.expected.json @@ -252,6 +252,12 @@ "StateMachine2E01A3A5": { "Type": "AWS::StepFunctions::StateMachine", "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, "DefinitionString": { "Fn::Join": [ "", @@ -281,12 +287,6 @@ ":states:::ecs:runTask.sync\"}}}" ] ] - }, - "RoleArn": { - "Fn::GetAtt": [ - "StateMachineRoleB840431D", - "Arn" - ] } }, "DependsOn": [ @@ -295,4 +295,4 @@ ] } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/eks/integ.call.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/eks/integ.call.expected.json index 23ad4160b9f7b..64b6481aab74f 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/eks/integ.call.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/eks/integ.call.expected.json @@ -1200,7 +1200,7 @@ }, "/", { - "Ref": "AssetParameters3aee2b76026cd725af3b14456bf03061e83d56cce0e0354c7c8e88ee1150e527S3Bucket7ED14FA7" + "Ref": "AssetParametersf14dbf982bfb4e61285bf9e6e91f20eb00e8a36c79c981ceec2437b42a02e216S3BucketE0529475" }, "/", { @@ -1210,7 +1210,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3aee2b76026cd725af3b14456bf03061e83d56cce0e0354c7c8e88ee1150e527S3VersionKeyF4EF0775" + "Ref": "AssetParametersf14dbf982bfb4e61285bf9e6e91f20eb00e8a36c79c981ceec2437b42a02e216S3VersionKey736B6614" } ] } @@ -1223,7 +1223,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3aee2b76026cd725af3b14456bf03061e83d56cce0e0354c7c8e88ee1150e527S3VersionKeyF4EF0775" + "Ref": "AssetParametersf14dbf982bfb4e61285bf9e6e91f20eb00e8a36c79c981ceec2437b42a02e216S3VersionKey736B6614" } ] } @@ -1273,7 +1273,7 @@ }, "/", { - "Ref": "AssetParameterscad1ae036643e3cd17cd3b2b30a2f9e07c1aacaf5284314f41437e4c20447aeaS3BucketED16A657" + "Ref": "AssetParameters9d41118c36f364bd9f7de59dda09afbef182ac0f726496523af8ffd24aafc604S3BucketCAFDB26C" }, "/", { @@ -1283,7 +1283,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterscad1ae036643e3cd17cd3b2b30a2f9e07c1aacaf5284314f41437e4c20447aeaS3VersionKey37A80BBF" + "Ref": "AssetParameters9d41118c36f364bd9f7de59dda09afbef182ac0f726496523af8ffd24aafc604S3VersionKey3CF353E9" } ] } @@ -1296,7 +1296,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterscad1ae036643e3cd17cd3b2b30a2f9e07c1aacaf5284314f41437e4c20447aeaS3VersionKey37A80BBF" + "Ref": "AssetParameters9d41118c36f364bd9f7de59dda09afbef182ac0f726496523af8ffd24aafc604S3VersionKey3CF353E9" } ] } @@ -1552,29 +1552,29 @@ "Type": "String", "Description": "Artifact hash for asset \"844c1a4b13479b359ea0e607dccb4a04b73e22cf88cf9b64feed2c5f0de213c0\"" }, - "AssetParameters3aee2b76026cd725af3b14456bf03061e83d56cce0e0354c7c8e88ee1150e527S3Bucket7ED14FA7": { + "AssetParametersf14dbf982bfb4e61285bf9e6e91f20eb00e8a36c79c981ceec2437b42a02e216S3BucketE0529475": { "Type": "String", - "Description": "S3 bucket for asset \"3aee2b76026cd725af3b14456bf03061e83d56cce0e0354c7c8e88ee1150e527\"" + "Description": "S3 bucket for asset \"f14dbf982bfb4e61285bf9e6e91f20eb00e8a36c79c981ceec2437b42a02e216\"" }, - "AssetParameters3aee2b76026cd725af3b14456bf03061e83d56cce0e0354c7c8e88ee1150e527S3VersionKeyF4EF0775": { + "AssetParametersf14dbf982bfb4e61285bf9e6e91f20eb00e8a36c79c981ceec2437b42a02e216S3VersionKey736B6614": { "Type": "String", - "Description": "S3 key for asset version \"3aee2b76026cd725af3b14456bf03061e83d56cce0e0354c7c8e88ee1150e527\"" + "Description": "S3 key for asset version \"f14dbf982bfb4e61285bf9e6e91f20eb00e8a36c79c981ceec2437b42a02e216\"" }, - "AssetParameters3aee2b76026cd725af3b14456bf03061e83d56cce0e0354c7c8e88ee1150e527ArtifactHash94EFED5E": { + "AssetParametersf14dbf982bfb4e61285bf9e6e91f20eb00e8a36c79c981ceec2437b42a02e216ArtifactHash85D00783": { "Type": "String", - "Description": "Artifact hash for asset \"3aee2b76026cd725af3b14456bf03061e83d56cce0e0354c7c8e88ee1150e527\"" + "Description": "Artifact hash for asset \"f14dbf982bfb4e61285bf9e6e91f20eb00e8a36c79c981ceec2437b42a02e216\"" }, - "AssetParameterscad1ae036643e3cd17cd3b2b30a2f9e07c1aacaf5284314f41437e4c20447aeaS3BucketED16A657": { + "AssetParameters9d41118c36f364bd9f7de59dda09afbef182ac0f726496523af8ffd24aafc604S3BucketCAFDB26C": { "Type": "String", - "Description": "S3 bucket for asset \"cad1ae036643e3cd17cd3b2b30a2f9e07c1aacaf5284314f41437e4c20447aea\"" + "Description": "S3 bucket for asset \"9d41118c36f364bd9f7de59dda09afbef182ac0f726496523af8ffd24aafc604\"" }, - "AssetParameterscad1ae036643e3cd17cd3b2b30a2f9e07c1aacaf5284314f41437e4c20447aeaS3VersionKey37A80BBF": { + "AssetParameters9d41118c36f364bd9f7de59dda09afbef182ac0f726496523af8ffd24aafc604S3VersionKey3CF353E9": { "Type": "String", - "Description": "S3 key for asset version \"cad1ae036643e3cd17cd3b2b30a2f9e07c1aacaf5284314f41437e4c20447aea\"" + "Description": "S3 key for asset version \"9d41118c36f364bd9f7de59dda09afbef182ac0f726496523af8ffd24aafc604\"" }, - "AssetParameterscad1ae036643e3cd17cd3b2b30a2f9e07c1aacaf5284314f41437e4c20447aeaArtifactHash11CEC9E5": { + "AssetParameters9d41118c36f364bd9f7de59dda09afbef182ac0f726496523af8ffd24aafc604ArtifactHashEC502675": { "Type": "String", - "Description": "Artifact hash for asset \"cad1ae036643e3cd17cd3b2b30a2f9e07c1aacaf5284314f41437e4c20447aea\"" + "Description": "Artifact hash for asset \"9d41118c36f364bd9f7de59dda09afbef182ac0f726496523af8ffd24aafc604\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts index 7a2aa196b3de2..92a451b908993 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts @@ -25,7 +25,7 @@ test('Eval with Node.js', () => { [ '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', { - 'Fn::GetAtt': ['Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1', 'Arn'], + 'Fn::GetAtt': ['Evalda2d1181604e4a4586941a6abd7fe42dF371675D', 'Arn'], }, '","Parameters":{"expression":"$.a + $.b","expressionAttributeValues":{"$.a.$":"$.a","$.b.$":"$.b"}}}}}', ], @@ -54,7 +54,7 @@ test('expression does not contain paths', () => { [ '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', { - 'Fn::GetAtt': ['Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1', 'Arn'], + 'Fn::GetAtt': ['Evalda2d1181604e4a4586941a6abd7fe42dF371675D', 'Arn'], }, '","Parameters":{"expression":"2 + 2","expressionAttributeValues":{}}}}}', ], @@ -79,7 +79,7 @@ test('with dash and underscore in path', () => { [ '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', { - 'Fn::GetAtt': ['Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1', 'Arn'], + 'Fn::GetAtt': ['Evalda2d1181604e4a4586941a6abd7fe42dF371675D', 'Arn'], }, '","Parameters":{"expression":"$.a_b + $.c-d + $[_e]","expressionAttributeValues":{"$.a_b.$":"$.a_b","$.c-d.$":"$.c-d","$[_e].$":"$[_e]"}}}}}', ], diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/integ.glue-task.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/integ.glue-task.expected.json index 8f9e9aba0102f..150ecb4c32161 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/integ.glue-task.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/integ.glue-task.expected.json @@ -233,6 +233,12 @@ "StateMachine81935E76": { "Type": "AWS::StepFunctions::StateMachine", "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRole543B9670", + "Arn" + ] + }, "DefinitionString": { "Fn::Join": [ "", @@ -244,12 +250,6 @@ ":states:::glue:startJobRun.sync\"},\"End Task\":{\"Type\":\"Pass\",\"End\":true}}}" ] ] - }, - "RoleArn": { - "Fn::GetAtt": [ - "StateMachineRole543B9670", - "Arn" - ] } }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/integ.start-job-run.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/integ.start-job-run.expected.json index 1f916f6be06f1..217b47176d936 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/integ.start-job-run.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/integ.start-job-run.expected.json @@ -233,6 +233,12 @@ "StateMachine81935E76": { "Type": "AWS::StepFunctions::StateMachine", "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRole543B9670", + "Arn" + ] + }, "DefinitionString": { "Fn::Join": [ "", @@ -244,12 +250,6 @@ ":states:::glue:startJobRun.sync\",\"Parameters\":{\"JobName\":\"My Glue Job\",\"Arguments\":{\"--enable-metrics\":\"true\"}}},\"End Task\":{\"Type\":\"Pass\",\"End\":true}}}" ] ] - }, - "RoleArn": { - "Fn::GetAtt": [ - "StateMachineRole543B9670", - "Arn" - ] } }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.evaluate-expression.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.evaluate-expression.expected.json index c48b04a826783..39a3502ba6c80 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.evaluate-expression.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.evaluate-expression.expected.json @@ -1,6 +1,6 @@ { "Resources": { - "Evala0d2ce44871b4e7487a1f5e63d7c3bdcServiceRoleDC85DDD3": { + "Evalda2d1181604e4a4586941a6abd7fe42dServiceRoleED144118": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -31,7 +31,7 @@ ] } }, - "Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1": { + "Evalda2d1181604e4a4586941a6abd7fe42dF371675D": { "Type": "AWS::Lambda::Function", "Properties": { "Code": { @@ -74,7 +74,7 @@ }, "Role": { "Fn::GetAtt": [ - "Evala0d2ce44871b4e7487a1f5e63d7c3bdcServiceRoleDC85DDD3", + "Evalda2d1181604e4a4586941a6abd7fe42dServiceRoleED144118", "Arn" ] }, @@ -82,7 +82,7 @@ "Runtime": "nodejs14.x" }, "DependsOn": [ - "Evala0d2ce44871b4e7487a1f5e63d7c3bdcServiceRoleDC85DDD3" + "Evalda2d1181604e4a4586941a6abd7fe42dServiceRoleED144118" ] }, "StateMachineRoleB840431D": { @@ -123,7 +123,7 @@ "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1", + "Evalda2d1181604e4a4586941a6abd7fe42dF371675D", "Arn" ] } @@ -155,21 +155,21 @@ "{\"StartAt\":\"Sum\",\"States\":{\"Sum\":{\"Next\":\"Multiply\",\"Type\":\"Task\",\"ResultPath\":\"$.c\",\"Resource\":\"", { "Fn::GetAtt": [ - "Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1", + "Evalda2d1181604e4a4586941a6abd7fe42dF371675D", "Arn" ] }, "\",\"Parameters\":{\"expression\":\"$.a + $.b\",\"expressionAttributeValues\":{\"$.a.$\":\"$.a\",\"$.b.$\":\"$.b\"}}},\"Multiply\":{\"Next\":\"Wait\",\"Type\":\"Task\",\"ResultPath\":\"$.d\",\"Resource\":\"", { "Fn::GetAtt": [ - "Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1", + "Evalda2d1181604e4a4586941a6abd7fe42dF371675D", "Arn" ] }, "\",\"Parameters\":{\"expression\":\"$.c * 2\",\"expressionAttributeValues\":{\"$.c.$\":\"$.c\"}}},\"Wait\":{\"Type\":\"Wait\",\"SecondsPath\":\"$.d\",\"Next\":\"Now\"},\"Now\":{\"End\":true,\"Type\":\"Task\",\"ResultPath\":\"$.now\",\"Resource\":\"", { "Fn::GetAtt": [ - "Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1", + "Evalda2d1181604e4a4586941a6abd7fe42dF371675D", "Arn" ] }, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.expected.json index 5087e1d80e41e..0a90d0093ec04 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.expected.json @@ -43,6 +43,12 @@ "StateMachine2E01A3A5": { "Type": "AWS::StepFunctions::StateMachine", "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, "DefinitionString": { "Fn::Join": [ "", @@ -62,12 +68,6 @@ "\"}},\"TimeoutSeconds\":30}" ] ] - }, - "RoleArn": { - "Fn::GetAtt": [ - "StateMachineRoleB840431D", - "Arn" - ] } }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.start-execution.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.start-execution.expected.json index 40010a8634abe..7916ba084ade1 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.start-execution.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.start-execution.expected.json @@ -31,13 +31,13 @@ "ChildDAB30558": { "Type": "AWS::StepFunctions::StateMachine", "Properties": { - "DefinitionString": "{\"StartAt\":\"Pass\",\"States\":{\"Pass\":{\"Type\":\"Pass\",\"End\":true}}}", "RoleArn": { "Fn::GetAtt": [ "ChildRole1E3E0EF5", "Arn" ] - } + }, + "DefinitionString": "{\"StartAt\":\"Pass\",\"States\":{\"Pass\":{\"Type\":\"Pass\",\"End\":true}}}" }, "DependsOn": [ "ChildRole1E3E0EF5" @@ -166,6 +166,12 @@ "Parent8B210403": { "Type": "AWS::StepFunctions::StateMachine", "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "ParentRole5F0C366C", + "Arn" + ] + }, "DefinitionString": { "Fn::Join": [ "", @@ -181,12 +187,6 @@ ":states:::states:startExecution.sync\"}}}" ] ] - }, - "RoleArn": { - "Fn::GetAtt": [ - "ParentRole5F0C366C", - "Arn" - ] } }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke-function.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke-function.expected.json index 7306c708756c4..dec16cb9e6ba8 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke-function.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke-function.expected.json @@ -72,13 +72,13 @@ ] } }, - "Handler": "index.main", "Role": { "Fn::GetAtt": [ "HandlerServiceRoleFCDC14AE", "Arn" ] }, + "Handler": "index.main", "Runtime": "python3.6" }, "DependsOn": [ @@ -157,13 +157,13 @@ ] } }, - "Handler": "index.main", "Role": { "Fn::GetAtt": [ "CallbackHandlerServiceRole3689695E", "Arn" ] }, + "Handler": "index.main", "Runtime": "python3.6" }, "DependsOn": [ @@ -237,6 +237,12 @@ "StateMachine2E01A3A5": { "Type": "AWS::StepFunctions::StateMachine", "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, "DefinitionString": { "Fn::Join": [ "", @@ -259,12 +265,6 @@ ":states:::lambda:invoke.waitForTaskToken\",\"ResultPath\":\"$.status\"},\"Job Complete?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.status\",\"StringEquals\":\"FAILED\",\"Next\":\"Job Failed\"},{\"Variable\":\"$.status\",\"StringEquals\":\"SUCCEEDED\",\"Next\":\"Final step\"}]},\"Job Failed\":{\"Type\":\"Fail\",\"Error\":\"DescribeJob returned FAILED\",\"Cause\":\"AWS Batch Job Failed\"},\"Final step\":{\"Type\":\"Pass\",\"End\":true}},\"TimeoutSeconds\":30}" ] ] - }, - "RoleArn": { - "Fn::GetAtt": [ - "StateMachineRoleB840431D", - "Arn" - ] } }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.expected.json index efa3aac3cf73a..1d0e7fcdc6f51 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.expected.json @@ -37,13 +37,13 @@ "Code": { "ZipFile": "exports.handler = async () => {\n return {\n statusCode: '200',\n body: 'hello, world!'\n };\n };" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "submitJobLambdaServiceRole4D897ABD", "Arn" ] }, + "Handler": "index.handler", "Runtime": "nodejs10.x" }, "DependsOn": [ @@ -87,13 +87,13 @@ "Code": { "ZipFile": "exports.handler = async function(event, context) {\n return {\n status: event.statusCode === '200' ? 'SUCCEEDED' : 'FAILED'\n };\n };" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "checkJobStateLambdaServiceRoleB8B57B65", "Arn" ] }, + "Handler": "index.handler", "Runtime": "nodejs10.x" }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.payload.only.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.payload.only.expected.json index 32bec4cb4a2fe..d0a6cdda262dc 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.payload.only.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.payload.only.expected.json @@ -37,13 +37,13 @@ "Code": { "ZipFile": "exports.handler = async () => {\n return {\n statusCode: '200',\n body: 'hello, world!'\n };\n };" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "submitJobLambdaServiceRole4D897ABD", "Arn" ] }, + "Handler": "index.handler", "Runtime": "nodejs10.x" }, "DependsOn": [ @@ -87,13 +87,13 @@ "Code": { "ZipFile": "exports.handler = async function(event, context) {\n return {\n status: event.statusCode === '200' ? 'SUCCEEDED' : 'FAILED'\n };\n };" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "checkJobStateLambdaServiceRoleB8B57B65", "Arn" ] }, + "Handler": "index.handler", "Runtime": "nodejs10.x" }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.run-lambda.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.run-lambda.expected.json index 64f8d2444d7f2..6c483349d059b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.run-lambda.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.run-lambda.expected.json @@ -37,13 +37,13 @@ "Code": { "ZipFile": "exports.handler = async () => {\n return {\n statusCode: '200',\n body: 'hello, world!'\n };\n };" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "submitJobLambdaServiceRole4D897ABD", "Arn" ] }, + "Handler": "index.handler", "Runtime": "nodejs10.x" }, "DependsOn": [ @@ -87,13 +87,13 @@ "Code": { "ZipFile": "exports.handler = async function(event, context) {\n return {\n status: event.statusCode === '200' ? 'SUCCEEDED' : 'FAILED'\n };\n };" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "checkJobStateLambdaServiceRoleB8B57B65", "Arn" ] }, + "Handler": "index.handler", "Runtime": "nodejs10.x" }, "DependsOn": [ @@ -167,6 +167,12 @@ "StateMachine2E01A3A5": { "Type": "AWS::StepFunctions::StateMachine", "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, "DefinitionString": { "Fn::Join": [ "", @@ -190,12 +196,6 @@ ":states:::lambda:invoke\"},\"Job Complete?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.status\",\"StringEquals\":\"FAILED\",\"Next\":\"Job Failed\"},{\"Variable\":\"$.status\",\"StringEquals\":\"SUCCEEDED\",\"Next\":\"Final step\"}]},\"Job Failed\":{\"Type\":\"Fail\",\"Error\":\"Received a status that was not 200\",\"Cause\":\"Job Failed\"},\"Final step\":{\"Type\":\"Pass\",\"End\":true}},\"TimeoutSeconds\":30}" ] ] - }, - "RoleArn": { - "Fn::GetAtt": [ - "StateMachineRoleB840431D", - "Arn" - ] } }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.expected.json index c163ebd11cf77..107fd16780144 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.expected.json @@ -608,4 +608,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.create-training-job.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.create-training-job.expected.json index 209c5520c1c64..0cd3166d73138 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.create-training-job.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.create-training-job.expected.json @@ -358,4 +358,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.start-execution.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.start-execution.expected.json index 7cd5dd9eed8f6..cd4621ee4c7f1 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.start-execution.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.start-execution.expected.json @@ -31,12 +31,17 @@ "ChildDAB30558": { "Type": "AWS::StepFunctions::StateMachine", "Properties": { - "DefinitionString": "{\"StartAt\":\"Pass\",\"States\":{\"Pass\":{\"Type\":\"Pass\",\"End\":true}}}", "RoleArn": { - "Fn::GetAtt": ["ChildRole1E3E0EF5", "Arn"] - } + "Fn::GetAtt": [ + "ChildRole1E3E0EF5", + "Arn" + ] + }, + "DefinitionString": "{\"StartAt\":\"Pass\",\"States\":{\"Pass\":{\"Type\":\"Pass\",\"End\":true}}}" }, - "DependsOn": ["ChildRole1E3E0EF5"] + "DependsOn": [ + "ChildRole1E3E0EF5" + ] }, "ParentRole5F0C366C": { "Type": "AWS::IAM::Role", @@ -79,7 +84,10 @@ } }, { - "Action": ["states:DescribeExecution", "states:StopExecution"], + "Action": [ + "states:DescribeExecution", + "states:StopExecution" + ], "Effect": "Allow", "Resource": { "Fn::Join": [ @@ -117,7 +125,11 @@ } }, { - "Action": ["events:PutTargets", "events:PutRule", "events:DescribeRule"], + "Action": [ + "events:PutTargets", + "events:PutRule", + "events:DescribeRule" + ], "Effect": "Allow", "Resource": { "Fn::Join": [ @@ -154,6 +166,12 @@ "Parent8B210403": { "Type": "AWS::StepFunctions::StateMachine", "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "ParentRole5F0C366C", + "Arn" + ] + }, "DefinitionString": { "Fn::Join": [ "", @@ -169,12 +187,12 @@ "\"}}}}" ] ] - }, - "RoleArn": { - "Fn::GetAtt": ["ParentRole5F0C366C", "Arn"] } }, - "DependsOn": ["ParentRoleDefaultPolicy9BDC56DC", "ParentRole5F0C366C"] + "DependsOn": [ + "ParentRoleDefaultPolicy9BDC56DC", + "ParentRole5F0C366C" + ] } }, "Outputs": { @@ -184,4 +202,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts index 2139866ae3d06..8f8d751649c35 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts @@ -188,7 +188,7 @@ export class Provider extends CoreConstruct implements cfn.ICustomResourceProvid const fn = new lambda.Function(this, `framework-${entrypoint}`, { code: lambda.Code.fromAsset(RUNTIME_HANDLER_PATH), description: `AWS CDK resource provider framework - ${entrypoint} (${this.node.path})`.slice(0, 256), - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: `framework.${entrypoint}`, timeout: FRAMEWORK_HANDLER_TIMEOUT, logRetention: this.logRetention, diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json b/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json index e2f123073b19e..b9a9edfe958cc 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json @@ -88,7 +88,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersfd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2S3BucketD831F708" + "Ref": "AssetParameters192597c3e09c72bcb5fca6899fca0b42745cb003a702e275a7f96123a9baf590S3BucketF43FE553" }, "S3Key": { "Fn::Join": [ @@ -101,7 +101,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersfd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2S3VersionKeyEAC17D61" + "Ref": "AssetParameters192597c3e09c72bcb5fca6899fca0b42745cb003a702e275a7f96123a9baf590S3VersionKey1CA1DD38" } ] } @@ -114,7 +114,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersfd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2S3VersionKeyEAC17D61" + "Ref": "AssetParameters192597c3e09c72bcb5fca6899fca0b42745cb003a702e275a7f96123a9baf590S3VersionKey1CA1DD38" } ] } @@ -124,13 +124,13 @@ ] } }, - "Handler": "index.onEvent", "Role": { "Fn::GetAtt": [ "comamazonawscdkcustomresourcess3fileproviders3fileoneventServiceRole999CEEB6", "Arn" ] }, + "Handler": "index.onEvent", "Runtime": "nodejs10.x" }, "DependsOn": [ @@ -200,7 +200,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket0EEA1C2E" + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1" }, "S3Key": { "Fn::Join": [ @@ -213,7 +213,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" } ] } @@ -226,7 +226,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" } ] } @@ -236,14 +236,12 @@ ] } }, - "Handler": "framework.onEvent", "Role": { "Fn::GetAtt": [ "comamazonawscdkcustomresourcess3fileproviderframeworkonEventServiceRoleABFCDA11", "Arn" ] }, - "Runtime": "nodejs10.x", "Description": "AWS CDK resource provider framework - onEvent (integ-provider-framework/com.amazonaws.cdk.custom-resources.s3file-provider/s3file-provider)", "Environment": { "Variables": { @@ -255,6 +253,8 @@ } } }, + "Handler": "framework.onEvent", + "Runtime": "nodejs14.x", "Timeout": 900 }, "DependsOn": [ @@ -377,13 +377,13 @@ ] } }, - "Handler": "index.on_event", "Role": { "Fn::GetAtt": [ "comamazonawscdkcustomresourcess3assertproviders3assertoneventServiceRole012C0033", "Arn" ] }, + "Handler": "index.on_event", "Runtime": "python3.7" }, "DependsOn": [ @@ -487,13 +487,13 @@ ] } }, - "Handler": "index.is_complete", "Role": { "Fn::GetAtt": [ "comamazonawscdkcustomresourcess3assertproviders3assertiscompleteServiceRoleACAA755A", "Arn" ] }, + "Handler": "index.is_complete", "Runtime": "python3.7" }, "DependsOn": [ @@ -580,7 +580,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket0EEA1C2E" + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1" }, "S3Key": { "Fn::Join": [ @@ -593,7 +593,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" } ] } @@ -606,7 +606,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" } ] } @@ -616,14 +616,12 @@ ] } }, - "Handler": "framework.onEvent", "Role": { "Fn::GetAtt": [ "comamazonawscdkcustomresourcess3assertproviderframeworkonEventServiceRole34070F2C", "Arn" ] }, - "Runtime": "nodejs10.x", "Description": "AWS CDK resource provider framework - onEvent (integ-provider-framework/com.amazonaws.cdk.custom-resources.s3assert-provider/s3assert-provider)", "Environment": { "Variables": { @@ -644,6 +642,8 @@ } } }, + "Handler": "framework.onEvent", + "Runtime": "nodejs14.x", "Timeout": 900 }, "DependsOn": [ @@ -723,7 +723,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket0EEA1C2E" + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1" }, "S3Key": { "Fn::Join": [ @@ -736,7 +736,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" } ] } @@ -749,7 +749,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" } ] } @@ -759,14 +759,12 @@ ] } }, - "Handler": "framework.isComplete", "Role": { "Fn::GetAtt": [ "comamazonawscdkcustomresourcess3assertproviderframeworkisCompleteServiceRole2C8C7983", "Arn" ] }, - "Runtime": "nodejs10.x", "Description": "AWS CDK resource provider framework - isComplete (integ-provider-framework/com.amazonaws.cdk.custom-resources.s3assert-provider/s3assert-provider)", "Environment": { "Variables": { @@ -784,6 +782,8 @@ } } }, + "Handler": "framework.isComplete", + "Runtime": "nodejs14.x", "Timeout": 900 }, "DependsOn": [ @@ -863,7 +863,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket0EEA1C2E" + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1" }, "S3Key": { "Fn::Join": [ @@ -876,7 +876,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" } ] } @@ -889,7 +889,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" } ] } @@ -899,14 +899,12 @@ ] } }, - "Handler": "framework.onTimeout", "Role": { "Fn::GetAtt": [ "comamazonawscdkcustomresourcess3assertproviderframeworkonTimeoutServiceRole15F6DFA2", "Arn" ] }, - "Runtime": "nodejs10.x", "Description": "AWS CDK resource provider framework - onTimeout (integ-provider-framework/com.amazonaws.cdk.custom-resources.s3assert-provider/s3assert-provider)", "Environment": { "Variables": { @@ -924,6 +922,8 @@ } } }, + "Handler": "framework.onTimeout", + "Runtime": "nodejs14.x", "Timeout": 900 }, "DependsOn": [ @@ -1034,29 +1034,29 @@ } }, "Parameters": { - "AssetParametersfd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2S3BucketD831F708": { + "AssetParameters192597c3e09c72bcb5fca6899fca0b42745cb003a702e275a7f96123a9baf590S3BucketF43FE553": { "Type": "String", - "Description": "S3 bucket for asset \"fd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2\"" + "Description": "S3 bucket for asset \"192597c3e09c72bcb5fca6899fca0b42745cb003a702e275a7f96123a9baf590\"" }, - "AssetParametersfd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2S3VersionKeyEAC17D61": { + "AssetParameters192597c3e09c72bcb5fca6899fca0b42745cb003a702e275a7f96123a9baf590S3VersionKey1CA1DD38": { "Type": "String", - "Description": "S3 key for asset version \"fd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2\"" + "Description": "S3 key for asset version \"192597c3e09c72bcb5fca6899fca0b42745cb003a702e275a7f96123a9baf590\"" }, - "AssetParametersfd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2ArtifactHashDD841113": { + "AssetParameters192597c3e09c72bcb5fca6899fca0b42745cb003a702e275a7f96123a9baf590ArtifactHashBBCB8F76": { "Type": "String", - "Description": "Artifact hash for asset \"fd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2\"" + "Description": "Artifact hash for asset \"192597c3e09c72bcb5fca6899fca0b42745cb003a702e275a7f96123a9baf590\"" }, - "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket0EEA1C2E": { + "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1": { "Type": "String", - "Description": "S3 bucket for asset \"956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3\"" + "Description": "S3 bucket for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" }, - "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9": { + "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F": { "Type": "String", - "Description": "S3 key for asset version \"956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3\"" + "Description": "S3 key for asset version \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" }, - "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3ArtifactHash2CBB11D2": { + "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1ArtifactHashA521A16F": { "Type": "String", - "Description": "Artifact hash for asset \"956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3\"" + "Description": "Artifact hash for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" }, "AssetParameters4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8S3Bucket0DB889DF": { "Type": "String", From e80a98aa8839e9b9b89701158d82b991e9ebaa65 Mon Sep 17 00:00:00 2001 From: Abdul Date: Mon, 15 Mar 2021 15:09:05 -0700 Subject: [PATCH 23/41] feat(ecs-patterns): Add ECS deployment circuit breaker support to higher-level constructs (#12719) Fixes #12534 Fixes #12360 This change adds the option to set the `circuitBreaker` on the higher-level constructs such as ApplicationLoadBalancedFargateService ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-ecs-patterns/README.md | 21 +- .../application-load-balanced-service-base.ts | 13 +- .../network-load-balanced-service-base.ts | 12 +- .../lib/base/queue-processing-service-base.ts | 12 +- .../application-load-balanced-ecs-service.ts | 1 + .../ecs/network-load-balanced-ecs-service.ts | 1 + .../lib/ecs/queue-processing-ecs-service.ts | 1 + ...plication-load-balanced-fargate-service.ts | 1 + .../network-load-balanced-fargate-service.ts | 1 + .../queue-processing-fargate-service.ts | 1 + .../aws-ecs-patterns/test/ec2/test.l3s.ts | 66 ++ .../ec2/test.queue-processing-ecs-service.ts | 10 +- ...oad-balanced-fargate-service.expected.json | 702 +++++++++++++++ ...t-breaker-load-balanced-fargate-service.ts | 21 + ...e-processing-fargate-service.expected.json | 848 ++++++++++++++++++ ...reaker-queue-processing-fargate-service.ts | 21 + .../test.load-balanced-fargate-service.ts | 52 ++ .../test.queue-processing-fargate-service.ts | 10 +- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 6 +- .../test/fargate/fargate-service.test.ts | 7 +- 20 files changed, 1791 insertions(+), 16 deletions(-) create mode 100644 packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-load-balanced-fargate-service.expected.json create mode 100644 packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-load-balanced-fargate-service.ts create mode 100644 packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-queue-processing-fargate-service.expected.json create mode 100644 packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-queue-processing-fargate-service.ts diff --git a/packages/@aws-cdk/aws-ecs-patterns/README.md b/packages/@aws-cdk/aws-ecs-patterns/README.md index a1072d47fe700..2293c129ee245 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/README.md +++ b/packages/@aws-cdk/aws-ecs-patterns/README.md @@ -394,6 +394,25 @@ const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(sta }); ``` +### Deployment circuit breaker and rollback + +Amazon ECS [deployment circuit breaker](https://aws.amazon.com/tw/blogs/containers/announcing-amazon-ecs-deployment-circuit-breaker/) +automatically rolls back unhealthy service deployments without the need for manual intervention. Use `circuitBreaker` to enable +deployment circuit breaker and optionally enable `rollback` for automatic rollback. See [Using the deployment circuit breaker](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-type-ecs.html) +for more details. + +```ts +const service = new ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + desiredCount: 1, + cpu: 512, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), + }, + circuitBreaker: { rollback: true }, +}); +``` ### Set deployment configuration on QueueProcessingService @@ -469,7 +488,7 @@ const scheduledFargateTask = new ScheduledFargateTask(stack, 'ScheduledFargateTa ### Use the REMOVE_DEFAULT_DESIRED_COUNT feature flag -The REMOVE_DEFAULT_DESIRED_COUNT feature flag is used to override the default desiredCount that is autogenerated by the CDK. This will set the desiredCount of any service created by any of the following constructs to be undefined. +The REMOVE_DEFAULT_DESIRED_COUNT feature flag is used to override the default desiredCount that is autogenerated by the CDK. This will set the desiredCount of any service created by any of the following constructs to be undefined. * ApplicationLoadBalancedEc2Service * ApplicationLoadBalancedFargateService diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts index dde07a16e114c..2593853cd0350 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts @@ -1,6 +1,9 @@ import { Certificate, CertificateValidation, ICertificate } from '@aws-cdk/aws-certificatemanager'; import { IVpc } from '@aws-cdk/aws-ec2'; -import { AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerImage, DeploymentController, ICluster, LogDriver, PropagatedTagSource, Secret } from '@aws-cdk/aws-ecs'; +import { + AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerImage, DeploymentController, DeploymentCircuitBreaker, + ICluster, LogDriver, PropagatedTagSource, Secret, +} from '@aws-cdk/aws-ecs'; import { ApplicationListener, ApplicationLoadBalancer, ApplicationProtocol, ApplicationTargetGroup, IApplicationLoadBalancer, ListenerCertificate, ListenerAction, AddApplicationTargetsProps, @@ -226,6 +229,14 @@ export interface ApplicationLoadBalancedServiceBaseProps { * @default - Rolling update (ECS) */ readonly deploymentController?: DeploymentController; + + /** + * Whether to enable the deployment circuit breaker. If this property is defined, circuit breaker will be implicitly + * enabled. + * @default - disabled + */ + readonly circuitBreaker?: DeploymentCircuitBreaker; + } export interface ApplicationLoadBalancedTaskImageOptions { diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts index 949b052aebbad..5872217a43f22 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts @@ -1,5 +1,8 @@ import { IVpc } from '@aws-cdk/aws-ec2'; -import { AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerImage, DeploymentController, ICluster, LogDriver, PropagatedTagSource, Secret } from '@aws-cdk/aws-ecs'; +import { + AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerImage, DeploymentController, DeploymentCircuitBreaker, + ICluster, LogDriver, PropagatedTagSource, Secret, +} from '@aws-cdk/aws-ecs'; import { INetworkLoadBalancer, NetworkListener, NetworkLoadBalancer, NetworkTargetGroup } from '@aws-cdk/aws-elasticloadbalancingv2'; import { IRole } from '@aws-cdk/aws-iam'; import { ARecord, CnameRecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53'; @@ -176,6 +179,13 @@ export interface NetworkLoadBalancedServiceBaseProps { * @default - Rolling update (ECS) */ readonly deploymentController?: DeploymentController; + + /** + * Whether to enable the deployment circuit breaker. If this property is defined, circuit breaker will be implicitly + * enabled. + * @default - disabled + */ + readonly circuitBreaker?: DeploymentCircuitBreaker; } export interface NetworkLoadBalancedTaskImageOptions { diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts index 2f72c6345c469..7ec268c241249 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts @@ -1,6 +1,9 @@ import { ScalingInterval } from '@aws-cdk/aws-applicationautoscaling'; import { IVpc } from '@aws-cdk/aws-ec2'; -import { AwsLogDriver, BaseService, Cluster, ContainerImage, DeploymentController, ICluster, LogDriver, PropagatedTagSource, Secret } from '@aws-cdk/aws-ecs'; +import { + AwsLogDriver, BaseService, Cluster, ContainerImage, DeploymentController, DeploymentCircuitBreaker, + ICluster, LogDriver, PropagatedTagSource, Secret, +} from '@aws-cdk/aws-ecs'; import { IQueue, Queue } from '@aws-cdk/aws-sqs'; import { CfnOutput, Duration, Stack } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; @@ -189,6 +192,13 @@ export interface QueueProcessingServiceBaseProps { * @default - Rolling update (ECS) */ readonly deploymentController?: DeploymentController; + + /** + * Whether to enable the deployment circuit breaker. If this property is defined, circuit breaker will be implicitly + * enabled. + * @default - disabled + */ + readonly circuitBreaker?: DeploymentCircuitBreaker; } /** diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts index 2915fce6a48ff..ed606bb72c907 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts @@ -133,6 +133,7 @@ export class ApplicationLoadBalancedEc2Service extends ApplicationLoadBalancedSe enableECSManagedTags: props.enableECSManagedTags, cloudMapOptions: props.cloudMapOptions, deploymentController: props.deploymentController, + circuitBreaker: props.circuitBreaker, }); this.addServiceAsTarget(this.service); } diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts index 881a346c74f8a..4bae918ca67fc 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts @@ -131,6 +131,7 @@ export class NetworkLoadBalancedEc2Service extends NetworkLoadBalancedServiceBas enableECSManagedTags: props.enableECSManagedTags, cloudMapOptions: props.cloudMapOptions, deploymentController: props.deploymentController, + circuitBreaker: props.circuitBreaker, }); this.addServiceAsTarget(this.service); } diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/queue-processing-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/queue-processing-ecs-service.ts index 6858813dfa9cc..985a4d2390e6b 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/queue-processing-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/queue-processing-ecs-service.ts @@ -114,6 +114,7 @@ export class QueueProcessingEc2Service extends QueueProcessingServiceBase { propagateTags: props.propagateTags, enableECSManagedTags: props.enableECSManagedTags, deploymentController: props.deploymentController, + circuitBreaker: props.circuitBreaker, }); this.configureAutoscalingForService(this.service); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts index fbb68aef84b2f..326a68529272b 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts @@ -170,6 +170,7 @@ export class ApplicationLoadBalancedFargateService extends ApplicationLoadBalanc cloudMapOptions: props.cloudMapOptions, platformVersion: props.platformVersion, deploymentController: props.deploymentController, + circuitBreaker: props.circuitBreaker, securityGroups: props.securityGroups, vpcSubnets: props.taskSubnets, }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts index 404d5429acfed..1f2618bbca314 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts @@ -157,6 +157,7 @@ export class NetworkLoadBalancedFargateService extends NetworkLoadBalancedServic cloudMapOptions: props.cloudMapOptions, platformVersion: props.platformVersion, deploymentController: props.deploymentController, + circuitBreaker: props.circuitBreaker, vpcSubnets: props.taskSubnets, }); this.addServiceAsTarget(this.service); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts index 6444d05f81da6..cb4b8d77a8188 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts @@ -148,6 +148,7 @@ export class QueueProcessingFargateService extends QueueProcessingServiceBase { securityGroups: props.securityGroups, vpcSubnets: props.taskSubnets, assignPublicIp: props.assignPublicIp, + circuitBreaker: props.circuitBreaker, }); this.configureAutoscalingForService(this.service); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts index 6f5a4aa0b3337..d41a7684fd677 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts @@ -1098,6 +1098,72 @@ export = { test.done(); }, + 'ALB with circuit breaker'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + circuitBreaker: { rollback: true }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::ECS::Service', { + DeploymentConfiguration: { + DeploymentCircuitBreaker: { + Enable: true, + Rollback: true, + }, + }, + DeploymentController: { + Type: 'ECS', + }, + })); + + test.done(); + }, + + 'NLB with circuit breaker'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + + // WHEN + new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + circuitBreaker: { rollback: true }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::ECS::Service', { + DeploymentConfiguration: { + DeploymentCircuitBreaker: { + Enable: true, + Rollback: true, + }, + }, + DeploymentController: { + Type: 'ECS', + }, + })); + + test.done(); + }, + 'NetworkLoadbalancedEC2Service accepts previously created load balancer'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.queue-processing-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.queue-processing-ecs-service.ts index 2c410f6581b1e..77d822d443048 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.queue-processing-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.queue-processing-ecs-service.ts @@ -209,9 +209,7 @@ export = { maxHealthyPercent: 150, serviceName: 'ecs-test-service', family: 'ecs-task-family', - deploymentController: { - type: ecs.DeploymentControllerType.CODE_DEPLOY, - }, + circuitBreaker: { rollback: true }, }); // THEN - QueueWorker is of EC2 launch type, an SQS queue is created and all optional properties are set. @@ -220,11 +218,15 @@ export = { DeploymentConfiguration: { MinimumHealthyPercent: 60, MaximumPercent: 150, + DeploymentCircuitBreaker: { + Enable: true, + Rollback: true, + }, }, LaunchType: 'EC2', ServiceName: 'ecs-test-service', DeploymentController: { - Type: 'CODE_DEPLOY', + Type: 'ECS', }, })); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-load-balanced-fargate-service.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-load-balanced-fargate-service.expected.json new file mode 100644 index 0000000000000..19af7964b18be --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-load-balanced-fargate-service.expected.json @@ -0,0 +1,702 @@ +{ + "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": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "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": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "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" + } + } + }, + "ClusterEB0386A7": { + "Type": "AWS::ECS::Cluster" + }, + "myServiceLB168895E1": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "LoadBalancerAttributes": [ + { + "Key": "deletion_protection.enabled", + "Value": "false" + } + ], + "Scheme": "internet-facing", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "myServiceLBSecurityGroupFE0ED608", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + ], + "Type": "application" + }, + "DependsOn": [ + "VpcPublicSubnet1DefaultRoute3DA9E72A", + "VpcPublicSubnet2DefaultRoute97F91067" + ] + }, + "myServiceLBSecurityGroupFE0ED608": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Automatically created Security Group for ELB awsecsintegmyServiceLB1F7A535D", + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow from anyone on port 80", + "FromPort": 80, + "IpProtocol": "tcp", + "ToPort": 80 + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "myServiceLBSecurityGrouptoawsecsintegmyServiceSecurityGroup8DAB521180B6703B07": { + "Type": "AWS::EC2::SecurityGroupEgress", + "Properties": { + "GroupId": { + "Fn::GetAtt": [ + "myServiceLBSecurityGroupFE0ED608", + "GroupId" + ] + }, + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "DestinationSecurityGroupId": { + "Fn::GetAtt": [ + "myServiceSecurityGroupC3B9D4E0", + "GroupId" + ] + }, + "FromPort": 80, + "ToPort": 80 + } + }, + "myServiceLBPublicListenerC78AE8A0": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "TargetGroupArn": { + "Ref": "myServiceLBPublicListenerECSGroup17E9BBC1" + }, + "Type": "forward" + } + ], + "LoadBalancerArn": { + "Ref": "myServiceLB168895E1" + }, + "Port": 80, + "Protocol": "HTTP" + } + }, + "myServiceLBPublicListenerECSGroup17E9BBC1": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "Port": 80, + "Protocol": "HTTP", + "TargetType": "ip", + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "myServiceTaskDefTaskRole1C1DE6CC": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "myServiceTaskDef7FB8322A": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": "amazon/amazon-ecs-sample", + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "myServiceTaskDefwebLogGroupA1767F2C" + }, + "awslogs-stream-prefix": "myService", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "Name": "web", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ] + } + ], + "Cpu": "256", + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "myServiceTaskDefExecutionRole618CD311", + "Arn" + ] + }, + "Family": "awsecsintegmyServiceTaskDefA3A33D18", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "myServiceTaskDefTaskRole1C1DE6CC", + "Arn" + ] + } + } + }, + "myServiceTaskDefwebLogGroupA1767F2C": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "myServiceTaskDefExecutionRole618CD311": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "myServiceTaskDefExecutionRoleDefaultPolicyBDAEC571": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "myServiceTaskDefwebLogGroupA1767F2C", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "myServiceTaskDefExecutionRoleDefaultPolicyBDAEC571", + "Roles": [ + { + "Ref": "myServiceTaskDefExecutionRole618CD311" + } + ] + } + }, + "myServiceB0B6FAA0": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "ClusterEB0386A7" + }, + "DeploymentConfiguration": { + "DeploymentCircuitBreaker": { + "Enable": true, + "Rollback": true + }, + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "DeploymentController": { + "Type": "ECS" + }, + "EnableECSManagedTags": false, + "HealthCheckGracePeriodSeconds": 60, + "LaunchType": "FARGATE", + "LoadBalancers": [ + { + "ContainerName": "web", + "ContainerPort": 80, + "TargetGroupArn": { + "Ref": "myServiceLBPublicListenerECSGroup17E9BBC1" + } + } + ], + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "myServiceSecurityGroupC3B9D4E0", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + ] + } + }, + "TaskDefinition": { + "Ref": "myServiceTaskDef7FB8322A" + } + }, + "DependsOn": [ + "myServiceLBPublicListenerECSGroup17E9BBC1", + "myServiceLBPublicListenerC78AE8A0" + ] + }, + "myServiceSecurityGroupC3B9D4E0": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ/myService/Service/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "myServiceSecurityGroupfromawsecsintegmyServiceLBSecurityGroupFA544FE5800A81885C": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "FromPort": 80, + "GroupId": { + "Fn::GetAtt": [ + "myServiceSecurityGroupC3B9D4E0", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "myServiceLBSecurityGroupFE0ED608", + "GroupId" + ] + }, + "ToPort": 80 + } + } + }, + "Outputs": { + "myServiceLoadBalancerDNS3A083E9F": { + "Value": { + "Fn::GetAtt": [ + "myServiceLB168895E1", + "DNSName" + ] + } + }, + "myServiceServiceURL1258C56B": { + "Value": { + "Fn::Join":[ + "", + [ + "http://", + { + "Fn::GetAtt": [ + "myServiceLB168895E1", + "DNSName" + ] + } + ] + ] + } + } + } +} diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-load-balanced-fargate-service.ts new file mode 100644 index 0000000000000..57eeb8db17d5b --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-load-balanced-fargate-service.ts @@ -0,0 +1,21 @@ +import { Vpc } from '@aws-cdk/aws-ec2'; +import { Cluster, ContainerImage } from '@aws-cdk/aws-ecs'; +import { App, Stack } from '@aws-cdk/core'; + +import { ApplicationLoadBalancedFargateService } from '../../lib'; + +const app = new App(); +const stack = new Stack(app, 'aws-ecs-integ'); +const vpc = new Vpc(stack, 'Vpc', { maxAzs: 2 }); +const cluster = new Cluster(stack, 'Cluster', { vpc }); + +new ApplicationLoadBalancedFargateService(stack, 'myService', { + cluster, + memoryLimitMiB: 512, + taskImageOptions: { + image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + circuitBreaker: { rollback: true }, +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-queue-processing-fargate-service.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-queue-processing-fargate-service.expected.json new file mode 100644 index 0000000000000..3b8fad89fcf97 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-queue-processing-fargate-service.expected.json @@ -0,0 +1,848 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-patterns-queue/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "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-patterns-queue/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-patterns-queue/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociation0B0896DC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-patterns-queue/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-patterns-queue/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "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-patterns-queue/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-patterns-queue/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-patterns-queue/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-patterns-queue/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "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-patterns-queue/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-patterns-queue/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociation347902D1": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "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-patterns-queue/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-patterns-queue/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-patterns-queue/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "QueueProcessingServiceEcsProcessingDeadLetterQueueD47A7C6B": { + "Type": "AWS::SQS::Queue", + "Properties": { + "MessageRetentionPeriod": 1209600 + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "QueueProcessingServiceEcsProcessingQueue552F0B37": { + "Type": "AWS::SQS::Queue", + "Properties": { + "RedrivePolicy": { + "deadLetterTargetArn": { + "Fn::GetAtt": [ + "QueueProcessingServiceEcsProcessingDeadLetterQueueD47A7C6B", + "Arn" + ] + }, + "maxReceiveCount": 3 + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "QueueProcessingServiceQueueProcessingTaskDefTaskRole782B79A6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "QueueProcessingServiceQueueProcessingTaskDefTaskRoleDefaultPolicyAE808B19": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:ReceiveMessage", + "sqs:ChangeMessageVisibility", + "sqs:GetQueueUrl", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "QueueProcessingServiceEcsProcessingQueue552F0B37", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "QueueProcessingServiceQueueProcessingTaskDefTaskRoleDefaultPolicyAE808B19", + "Roles": [ + { + "Ref": "QueueProcessingServiceQueueProcessingTaskDefTaskRole782B79A6" + } + ] + } + }, + "QueueProcessingServiceQueueProcessingTaskDef4982F68B": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Environment": [ + { + "Name": "QUEUE_NAME", + "Value": { + "Fn::GetAtt": [ + "QueueProcessingServiceEcsProcessingQueue552F0B37", + "QueueName" + ] + } + } + ], + "Essential": true, + "Image": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::AccountId" + }, + ".dkr.ecr.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/aws-cdk/assets:3a8ba3ad06ed212b075efa3157fb407649c5996812bc64eeb5209e220aab4be5" + ] + ] + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "QueueProcessingServiceQueueProcessingTaskDefQueueProcessingContainerLogGroupCC92448A" + }, + "awslogs-stream-prefix": "QueueProcessingService", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "Name": "QueueProcessingContainer" + } + ], + "Cpu": "256", + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "QueueProcessingServiceQueueProcessingTaskDefExecutionRole37838985", + "Arn" + ] + }, + "Family": "awsecspatternsqueueQueueProcessingServiceQueueProcessingTaskDef2D9F8C2B", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "QueueProcessingServiceQueueProcessingTaskDefTaskRole782B79A6", + "Arn" + ] + } + } + }, + "QueueProcessingServiceQueueProcessingTaskDefQueueProcessingContainerLogGroupCC92448A": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "QueueProcessingServiceQueueProcessingTaskDefExecutionRole37838985": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "QueueProcessingServiceQueueProcessingTaskDefExecutionRoleDefaultPolicyA83D332D": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/aws-cdk/assets" + ] + ] + } + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "QueueProcessingServiceQueueProcessingTaskDefQueueProcessingContainerLogGroupCC92448A", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "QueueProcessingServiceQueueProcessingTaskDefExecutionRoleDefaultPolicyA83D332D", + "Roles": [ + { + "Ref": "QueueProcessingServiceQueueProcessingTaskDefExecutionRole37838985" + } + ] + } + }, + "QueueProcessingServiceQueueProcessingFargateService0340DB9F": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "EcsDefaultClusterMnL3mNNYNVPC9C1EC7A3" + }, + "DeploymentConfiguration": { + "DeploymentCircuitBreaker": { + "Enable": true, + "Rollback": true + }, + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "DeploymentController": { + "Type": "ECS" + }, + "EnableECSManagedTags": false, + "LaunchType": "FARGATE", + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "QueueProcessingServiceQueueProcessingFargateServiceSecurityGroup8FDF413D", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ] + } + }, + "TaskDefinition": { + "Ref": "QueueProcessingServiceQueueProcessingTaskDef4982F68B" + } + } + }, + "QueueProcessingServiceQueueProcessingFargateServiceSecurityGroup8FDF413D": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-patterns-queue/QueueProcessingService/QueueProcessingFargateService/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "QueueProcessingServiceQueueProcessingFargateServiceTaskCountTargetA9D54444": { + "Type": "AWS::ApplicationAutoScaling::ScalableTarget", + "Properties": { + "MaxCapacity": 2, + "MinCapacity": 1, + "ResourceId": { + "Fn::Join": [ + "", + [ + "service/", + { + "Ref": "EcsDefaultClusterMnL3mNNYNVPC9C1EC7A3" + }, + "/", + { + "Fn::GetAtt": [ + "QueueProcessingServiceQueueProcessingFargateService0340DB9F", + "Name" + ] + } + ] + ] + }, + "RoleARN": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService" + ] + ] + }, + "ScalableDimension": "ecs:service:DesiredCount", + "ServiceNamespace": "ecs" + } + }, + "QueueProcessingServiceQueueProcessingFargateServiceTaskCountTargetCpuScaling330150E9": { + "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", + "Properties": { + "PolicyName": "awsecspatternsqueueQueueProcessingServiceQueueProcessingFargateServiceTaskCountTargetCpuScaling374CE648", + "PolicyType": "TargetTrackingScaling", + "ScalingTargetId": { + "Ref": "QueueProcessingServiceQueueProcessingFargateServiceTaskCountTargetA9D54444" + }, + "TargetTrackingScalingPolicyConfiguration": { + "PredefinedMetricSpecification": { + "PredefinedMetricType": "ECSServiceAverageCPUUtilization" + }, + "TargetValue": 50 + } + } + }, + "QueueProcessingServiceQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingLowerPolicy332E2644": { + "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", + "Properties": { + "PolicyName": "awsecspatternsqueueQueueProcessingServiceQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingLowerPolicy74582401", + "PolicyType": "StepScaling", + "ScalingTargetId": { + "Ref": "QueueProcessingServiceQueueProcessingFargateServiceTaskCountTargetA9D54444" + }, + "StepScalingPolicyConfiguration": { + "AdjustmentType": "ChangeInCapacity", + "MetricAggregationType": "Maximum", + "StepAdjustments": [ + { + "MetricIntervalUpperBound": 0, + "ScalingAdjustment": -1 + } + ] + } + } + }, + "QueueProcessingServiceQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingLowerAlarm20C30A06": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "LessThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmActions": [ + { + "Ref": "QueueProcessingServiceQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingLowerPolicy332E2644" + } + ], + "AlarmDescription": "Lower threshold scaling alarm", + "Dimensions": [ + { + "Name": "QueueName", + "Value": { + "Fn::GetAtt": [ + "QueueProcessingServiceEcsProcessingQueue552F0B37", + "QueueName" + ] + } + } + ], + "MetricName": "ApproximateNumberOfMessagesVisible", + "Namespace": "AWS/SQS", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 0 + } + }, + "QueueProcessingServiceQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingUpperPolicy84DD739A": { + "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", + "Properties": { + "PolicyName": "awsecspatternsqueueQueueProcessingServiceQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingUpperPolicy23C5F983", + "PolicyType": "StepScaling", + "ScalingTargetId": { + "Ref": "QueueProcessingServiceQueueProcessingFargateServiceTaskCountTargetA9D54444" + }, + "StepScalingPolicyConfiguration": { + "AdjustmentType": "ChangeInCapacity", + "MetricAggregationType": "Maximum", + "StepAdjustments": [ + { + "MetricIntervalLowerBound": 0, + "MetricIntervalUpperBound": 400, + "ScalingAdjustment": 1 + }, + { + "MetricIntervalLowerBound": 400, + "ScalingAdjustment": 5 + } + ] + } + } + }, + "QueueProcessingServiceQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingUpperAlarm2660BEDF": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmActions": [ + { + "Ref": "QueueProcessingServiceQueueProcessingFargateServiceTaskCountTargetQueueMessagesVisibleScalingUpperPolicy84DD739A" + } + ], + "AlarmDescription": "Upper threshold scaling alarm", + "Dimensions": [ + { + "Name": "QueueName", + "Value": { + "Fn::GetAtt": [ + "QueueProcessingServiceEcsProcessingQueue552F0B37", + "QueueName" + ] + } + } + ], + "MetricName": "ApproximateNumberOfMessagesVisible", + "Namespace": "AWS/SQS", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 100 + } + }, + "EcsDefaultClusterMnL3mNNYNVPC9C1EC7A3": { + "Type": "AWS::ECS::Cluster" + } + }, + "Outputs": { + "QueueProcessingServiceSQSDeadLetterQueueE9058015": { + "Value": { + "Fn::GetAtt": [ + "QueueProcessingServiceEcsProcessingDeadLetterQueueD47A7C6B", + "QueueName" + ] + } + }, + "QueueProcessingServiceSQSDeadLetterQueueArnF7C6D3A8": { + "Value": { + "Fn::GetAtt": [ + "QueueProcessingServiceEcsProcessingDeadLetterQueueD47A7C6B", + "Arn" + ] + } + }, + "QueueProcessingServiceSQSQueue1AD9CD9C": { + "Value": { + "Fn::GetAtt": [ + "QueueProcessingServiceEcsProcessingQueue552F0B37", + "QueueName" + ] + } + }, + "QueueProcessingServiceSQSQueueArn8C4AE4AE": { + "Value": { + "Fn::GetAtt": [ + "QueueProcessingServiceEcsProcessingQueue552F0B37", + "Arn" + ] + } + } + } +} diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-queue-processing-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-queue-processing-fargate-service.ts new file mode 100644 index 0000000000000..6ba895ec2f1c9 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-queue-processing-fargate-service.ts @@ -0,0 +1,21 @@ +import * as path from 'path'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; +import { App, Stack } from '@aws-cdk/core'; + +import { QueueProcessingFargateService } from '../../lib'; + +const app = new App(); +const stack = new Stack(app, 'aws-ecs-patterns-queue'); +const vpc = new ec2.Vpc(stack, 'VPC', { + maxAzs: 2, +}); + +new QueueProcessingFargateService(stack, 'QueueProcessingService', { + vpc, + memoryLimitMiB: 512, + circuitBreaker: { rollback: true }, + image: new ecs.AssetImage(path.join(__dirname, '..', 'sqs-reader')), +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts index 1feffcb2e70fd..3a17962c8b230 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts @@ -427,6 +427,58 @@ export = { test.done(); }, + 'setting ALB circuitBreaker works'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + circuitBreaker: { rollback: true }, + }); + // THEN + expect(stack).to(haveResourceLike('AWS::ECS::Service', { + DeploymentConfiguration: { + DeploymentCircuitBreaker: { + Enable: true, + Rollback: true, + }, + }, + DeploymentController: { + Type: 'ECS', + }, + })); + test.done(); + }, + + 'setting NLB circuitBreaker works'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + circuitBreaker: { rollback: true }, + }); + // THEN + expect(stack).to(haveResourceLike('AWS::ECS::Service', { + DeploymentConfiguration: { + DeploymentCircuitBreaker: { + Enable: true, + Rollback: true, + }, + }, + DeploymentController: { + Type: 'ECS', + }, + })); + test.done(); + }, + 'setting NLB special listener port to create the listener'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.queue-processing-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.queue-processing-fargate-service.ts index 27671ae22e70d..5207f92629c10 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.queue-processing-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.queue-processing-fargate-service.ts @@ -344,9 +344,7 @@ export = { serviceName: 'fargate-test-service', family: 'fargate-task-family', platformVersion: ecs.FargatePlatformVersion.VERSION1_4, - deploymentController: { - type: ecs.DeploymentControllerType.CODE_DEPLOY, - }, + circuitBreaker: { rollback: true }, }); // THEN - QueueWorker is of FARGATE launch type, an SQS queue is created and all optional properties are set. @@ -355,12 +353,16 @@ export = { DeploymentConfiguration: { MinimumHealthyPercent: 60, MaximumPercent: 150, + DeploymentCircuitBreaker: { + Enable: true, + Rollback: true, + }, }, LaunchType: 'FARGATE', ServiceName: 'fargate-test-service', PlatformVersion: ecs.FargatePlatformVersion.VERSION1_4, DeploymentController: { - Type: 'CODE_DEPLOY', + Type: 'ECS', }, })); diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 3ff3f20fd8acd..306b2bced3477 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -387,7 +387,9 @@ export abstract class BaseService extends Resource }, propagateTags: props.propagateTags === PropagatedTagSource.NONE ? undefined : props.propagateTags, enableEcsManagedTags: props.enableECSManagedTags ?? false, - deploymentController: props.deploymentController, + deploymentController: props.circuitBreaker ? { + type: DeploymentControllerType.ECS, + } : props.deploymentController, launchType: launchType, capacityProviderStrategy: props.capacityProviderStrategies, healthCheckGracePeriodSeconds: this.evaluateHealthGracePeriod(props.healthCheckGracePeriod), @@ -984,4 +986,4 @@ function determineContainerNameAndPort(options: DetermineContainerNameAndPortOpt } return {}; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index 5531b53413f46..cbd43a8d44f67 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts @@ -389,7 +389,7 @@ nodeunitShim({ maxHealthyPercent: 150, minHealthyPercent: 55, deploymentController: { - type: ecs.DeploymentControllerType.CODE_DEPLOY, + type: ecs.DeploymentControllerType.ECS, }, circuitBreaker: { rollback: true }, securityGroup: new ec2.SecurityGroup(stack, 'SecurityGroup1', { @@ -421,7 +421,7 @@ nodeunitShim({ }, }, DeploymentController: { - Type: ecs.DeploymentControllerType.CODE_DEPLOY, + Type: ecs.DeploymentControllerType.ECS, }, DesiredCount: 2, HealthCheckGracePeriodSeconds: 60, @@ -2073,6 +2073,9 @@ nodeunitShim({ Rollback: true, }, }, + DeploymentController: { + Type: ecs.DeploymentControllerType.ECS, + }, })); test.done(); From 0ec302f4845d755b6e91fb863393c341f42f6f98 Mon Sep 17 00:00:00 2001 From: Romain Marcadier Date: Tue, 16 Mar 2021 11:43:37 +0100 Subject: [PATCH 24/41] chore(region-info): metadata service is in eu-south-1, af-south-1 (#13598) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/region-info/build-tools/fact-tables.ts | 4 ++-- .../region-info/test/__snapshots__/region-info.test.js.snap | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 2f47e02f64da4..c2ce689f3aaf3 100644 --- a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts +++ b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts @@ -19,7 +19,7 @@ export const AWS_CDK_METADATA = new Set([ // 'us-gov-west-1', // 'us-iso-east-1', // 'us-isob-east-1', - // 'af-south-1', + 'af-south-1', 'ap-south-1', 'ap-east-1', // 'ap-northeast-3', @@ -35,7 +35,7 @@ export const AWS_CDK_METADATA = new Set([ 'eu-west-2', 'eu-west-3', 'eu-north-1', - // 'eu-south-1', + 'eu-south-1', 'me-south-1', 'sa-east-1', ]); 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 ab12430e57c84..eb7c82b6eb28d 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 @@ -3,7 +3,7 @@ exports[`built-in data is correct 1`] = ` Object { "af-south-1": Object { - "cdkMetadataResourceAvailable": false, + "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", "partition": "aws", "s3StaticWebsiteEndpoint": "s3-website.af-south-1.amazonaws.com", @@ -263,7 +263,7 @@ Object { "vpcEndPointServiceNamePrefix": "com.amazonaws.vpce", }, "eu-south-1": Object { - "cdkMetadataResourceAvailable": false, + "cdkMetadataResourceAvailable": true, "domainSuffix": "amazonaws.com", "partition": "aws", "s3StaticWebsiteEndpoint": "s3-website.eu-south-1.amazonaws.com", From 8c6b3ebea464e27f68ffcab32857d8baec29c413 Mon Sep 17 00:00:00 2001 From: Darren Date: Tue, 16 Mar 2021 08:28:00 -0500 Subject: [PATCH 25/41] fix(ec2): Security Groups support all protocols (#13593) Satisfies #13497 to close #13403 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-ec2/lib/port.ts | 151 +++++++++++++++++- packages/@aws-cdk/aws-ec2/package.json | 148 ++++++++++++++++- .../aws-ec2/test/integ.vpc.expected.json | 14 -- packages/@aws-cdk/aws-ec2/test/integ.vpc.ts | 2 - 4 files changed, 290 insertions(+), 25 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/port.ts b/packages/@aws-cdk/aws-ec2/lib/port.ts index 314c8d615b0dd..8436f3455cda1 100644 --- a/packages/@aws-cdk/aws-ec2/lib/port.ts +++ b/packages/@aws-cdk/aws-ec2/lib/port.ts @@ -2,17 +2,158 @@ import { Token } from '@aws-cdk/core'; /** * Protocol for use in Connection Rules + * + * https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml */ export enum Protocol { ALL = '-1', + HOPOPT = '0', + ICMP = 'icmp', + IGMP = '2', + GGP = '3', + IPV4 = '4', + ST = '5', TCP = 'tcp', + CBT = '7', + EGP = '8', + IGP = '9', + BBN_RCC_MON = '10', + NVP_II = '11', + PUP = '12', + EMCON = '14', + XNET = '15', + CHAOS = '16', UDP = 'udp', - ICMP = 'icmp', - ICMPV6 = '58', - ESP = 'esp', - AH = 'ah', + MUX = '18', + DCN_MEAS = '19', + HMP = '20', + PRM = '21', + XNS_IDP = '22', + TRUNK_1 = '23', + TRUNK_2 = '24', + LEAF_1 = '25', + LEAF_2 = '26', + RDP = '27', + IRTP = '28', + ISO_TP4 = '29', + NETBLT = '30', + MFE_NSP = '31', + MERIT_INP = '32', + DCCP = '33', + THREEPC = '34', + IDPR = '35', + XTP = '36', + DDP = '37', + IDPR_CMTP = '38', + TPPLUSPLUS = '39', + IL = '40', + IPV6 = '41', + SDRP = '42', + IPV6_ROUTE = '43', + IPV6_FRAG = '44', + IDRP = '45', + RSVP = '46', + GRE = '47', + DSR = '48', + BNA = '49', + ESP = '50', + AH = '51', + I_NLSP = '52', + SWIPE = '53', + NARP = '54', + MOBILE = '55', + TLSP = '56', + SKIP = '57', + ICMPV6 = 'icmpv6', + IPV6_NONXT = '59', + IPV6_OPTS = '60', + CFTP = '62', + ANY_LOCAL = '63', + SAT_EXPAK = '64', + KRYPTOLAN = '65', + RVD = '66', + IPPC = '67', + ANY_DFS = '68', + SAT_MON = '69', + VISA = '70', + IPCV = '71', + CPNX = '72', + CPHB = '73', + WSN = '74', + PVP = '75', + BR_SAT_MON = '76', + SUN_ND = '77', + WB_MON = '78', + WB_EXPAK = '79', + ISO_IP = '80', + VMTP = '81', + SECURE_VMTP = '82', + VINES = '83', + TTP = '84', + IPTM = '84', + NSFNET_IGP = '85', + DGP = '86', + TCF = '87', + EIGRP = '88', + OSPFIGP = '89', + SPRITE_RPC = '90', + LARP = '91', + MTP = '92', + AX_25 = '93', + IPIP = '94', + MICP = '95', + SCC_SP = '96', + ETHERIP = '97', + ENCAP = '98', + ANY_ENC = '99', + GMTP = '100', + IFMP = '101', + PNNI = '102', + PIM = '103', + ARIS = '104', + SCPS = '105', + QNX = '106', + A_N = '107', + IPCOMP = '108', + SNP = '109', + COMPAQ_PEER = '110', + IPX_IN_IP = '111', + VRRP = '112', + PGM = '113', + ANY_0_HOP = '114', + L2_T_P = '115', + DDX = '116', + IATP = '117', + STP = '118', + SRP = '119', + UTI = '120', + SMP = '121', + SM = '122', + PTP = '123', + ISIS_IPV4 = '124', + FIRE = '125', + CRTP = '126', + CRUDP = '127', + SSCOPMCE = '128', + IPLT = '129', + SPS = '130', + PIPE = '131', + SCTP = '132', + FC = '133', + RSVP_E2E_IGNORE = '134', + MOBILITY_HEADER = '135', + UDPLITE = '136', + MPLS_IN_IP = '137', + MANET = '138', + HIP = '139', + SHIM6 = '140', + WESP = '141', + ROHC = '142', + ETHERNET = '143', + EXPERIMENT_1 = '253', + EXPERIMENT_2 = '254', + RESERVED = '255', } - /** * Properties to create a port range */ diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index c5d5d9be6a64e..c32f701574ffc 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -314,13 +314,153 @@ "docs-public-apis:@aws-cdk/aws-ec2.AmazonLinuxStorage", "docs-public-apis:@aws-cdk/aws-ec2.OperatingSystemType.LINUX", "docs-public-apis:@aws-cdk/aws-ec2.OperatingSystemType.WINDOWS", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.AH", "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ALL", - "docs-public-apis:@aws-cdk/aws-ec2.Protocol.TCP", - "docs-public-apis:@aws-cdk/aws-ec2.Protocol.UDP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ANY_0_HOP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ANY_DFS", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ANY_ENC", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ANY_LOCAL", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ARIS", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.AX_25", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.A_N", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.BBN_RCC_MON", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.BNA", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.BR_SAT_MON", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.CBT", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.CFTP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.CHAOS", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.COMPAQ_PEER", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.CPHB", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.CPNX", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.CRTP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.CRUDP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.DCCP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.DCN_MEAS", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.DDP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.DDX", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.DGP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.DSR", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.EGP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.EIGRP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.EMCON", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ENCAP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ESP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ETHERIP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ETHERNET", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.EXPERIMENT_1", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.EXPERIMENT_2", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.FC", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.FIRE", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.GGP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.GMTP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.GRE", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.HIP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.HMP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.HOPOPT", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IATP", "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ICMP", "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ICMPV6", - "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ESP", - "docs-public-apis:@aws-cdk/aws-ec2.Protocol.AH", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IDPR", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IDPR_CMTP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IDRP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IFMP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IGMP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IGP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IL", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IPCOMP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IPCV", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IPIP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IPLT", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IPPC", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IPTM", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IPV4", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IPV6", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IPV6_FRAG", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IPV6_NONXT", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IPV6_OPTS", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IPV6_ROUTE", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IPX_IN_IP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.IRTP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ISIS_IPV4", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ISO_IP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ISO_TP4", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.I_NLSP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.KRYPTOLAN", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.L2_T_P", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.LARP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.LEAF_1", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.LEAF_2", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.MANET", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.MERIT_INP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.MFE_NSP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.MICP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.MOBILE", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.MOBILITY_HEADER", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.MPLS_IN_IP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.MTP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.MUX", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.NARP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.NETBLT", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.NSFNET_IGP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.NVP_II", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.OSPFIGP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.PGM", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.PIM", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.PIPE", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.PNNI", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.PRM", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.PTP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.PUP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.PVP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.QNX", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.RDP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.RESERVED", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ROHC", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.RSVP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.RSVP_E2E_IGNORE", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.RVD", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SAT_EXPAK", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SAT_MON", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SCC_SP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SCPS", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SCTP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SDRP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SECURE_VMTP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SHIM6", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SKIP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SM", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SMP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SNP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SPRITE_RPC", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SPS", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SRP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SSCOPMCE", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ST", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.STP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SUN_ND", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.SWIPE", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.TCF", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.TCP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.THREEPC", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.TLSP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.TPPLUSPLUS", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.TRUNK_1", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.TRUNK_2", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.TTP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.UDP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.UDPLITE", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.UTI", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.VINES", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.VISA", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.VMTP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.VRRP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.WB_EXPAK", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.WB_MON", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.WESP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.WSN", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.XNET", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.XNS_IDP", + "docs-public-apis:@aws-cdk/aws-ec2.Protocol.XTP", "docs-public-apis:@aws-cdk/aws-ec2.WindowsVersion.WINDOWS_SERVER_2008_SP2_ENGLISH_64BIT_SQL_2008_SP4_EXPRESS", "docs-public-apis:@aws-cdk/aws-ec2.WindowsVersion.WINDOWS_SERVER_2012_R2_RTM_CHINESE_SIMPLIFIED_64BIT_BASE", "docs-public-apis:@aws-cdk/aws-ec2.WindowsVersion.WINDOWS_SERVER_2012_R2_RTM_CHINESE_TRADITIONAL_64BIT_BASE", diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.vpc.expected.json index 641b97b4ddbd5..8aad8918d8ace 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.vpc.expected.json +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc.expected.json @@ -567,20 +567,6 @@ "FromPort": 800, "IpProtocol": "udp", "ToPort": 801 - }, - { - "CidrIp": "0.0.0.0/0", - "Description": "from 0.0.0.0/0:ESP 50", - "FromPort": 50, - "IpProtocol": "esp", - "ToPort": 50 - }, - { - "CidrIp": "0.0.0.0/0", - "Description": "from 0.0.0.0/0:AH 51", - "FromPort": 51, - "IpProtocol": "ah", - "ToPort": 51 } ], "VpcId": { diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc.ts b/packages/@aws-cdk/aws-ec2/test/integ.vpc.ts index 88e4dacf9839a..2ffd5653e33f4 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc.ts @@ -16,8 +16,6 @@ const rules = [ ec2.Port.allUdp(), ec2.Port.udp(123), ec2.Port.udpRange(800, 801), - ec2.Port.esp(), - ec2.Port.ah(), ]; for (const rule of rules) { From 64dea7cf4af0a3cbc0dd8ea1b292e99dc67e3f07 Mon Sep 17 00:00:00 2001 From: AWS CDK Team Date: Tue, 16 Mar 2021 13:52:35 +0000 Subject: [PATCH 26/41] chore(release): 1.94.0 --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ version.v1.json | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca6ebeb1b8572..9cc821cbde1c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,36 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.94.0](https://github.com/aws/aws-cdk/compare/v1.93.0...v1.94.0) (2021-03-16) + + +### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES + +* **appmesh:** Backend, backend default and Virtual Service client policies structures are being altered +* **appmesh**: you must use the backend default interface to define backend defaults in `VirtualGateway`. + The property name also changed from `backendsDefaultClientPolicy` to `backendDefaults` +* **appmesh**: you must use the backend default interface to define backend defaults in `VirtualNode`, + (the property name also changed from `backendsDefaultClientPolicy` to `backendDefaults`), + and the `Backend` class to define a backend +* **appmesh**: you can no longer attach a client policy to a `VirtualService` + +### Features + +* **appmesh:** add missing route match features ([#13350](https://github.com/aws/aws-cdk/issues/13350)) ([b71efd9](https://github.com/aws/aws-cdk/commit/b71efd9d12843ab4b495d53e565cec97d60748f3)), closes [#11645](https://github.com/aws/aws-cdk/issues/11645) +* **aws-elasticloadbalancingv2:** add protocol version for ALB TargetGroups ([#13570](https://github.com/aws/aws-cdk/issues/13570)) ([165a3d8](https://github.com/aws/aws-cdk/commit/165a3d877b7ab23f29e42e1e74ee7c5cb35b7f24)), closes [#12869](https://github.com/aws/aws-cdk/issues/12869) +* **ecs-patterns:** Add ECS deployment circuit breaker support to higher-level constructs ([#12719](https://github.com/aws/aws-cdk/issues/12719)) ([e80a98a](https://github.com/aws/aws-cdk/commit/e80a98aa8839e9b9b89701158d82b991e9ebaa65)), closes [#12534](https://github.com/aws/aws-cdk/issues/12534) [#12360](https://github.com/aws/aws-cdk/issues/12360) + + +### Bug Fixes + +* **appmesh:** Move Client Policy from Virtual Service to backend structure ([#12943](https://github.com/aws/aws-cdk/issues/12943)) ([d3f4284](https://github.com/aws/aws-cdk/commit/d3f428435976c55ca950279cfc841665fd504370)), closes [#11996](https://github.com/aws/aws-cdk/issues/11996) +* **autoscaling:** AutoScaling on percentile metrics doesn't work ([#13366](https://github.com/aws/aws-cdk/issues/13366)) ([46114bb](https://github.com/aws/aws-cdk/commit/46114bb1f4702019a8873b9162d0a9f10763bc61)), closes [#13144](https://github.com/aws/aws-cdk/issues/13144) +* **cloudwatch:** cannot create Alarms from labeled metrics that start with a digit ([#13560](https://github.com/aws/aws-cdk/issues/13560)) ([278029f](https://github.com/aws/aws-cdk/commit/278029f25b41d956091835364e5a8de91429712c)), closes [#13434](https://github.com/aws/aws-cdk/issues/13434) +* use NodeJS 14 for all packaged custom resources ([#13488](https://github.com/aws/aws-cdk/issues/13488)) ([20a2820](https://github.com/aws/aws-cdk/commit/20a2820ee4d022663fcd0928fbc0f61153ae953f)), closes [#13534](https://github.com/aws/aws-cdk/issues/13534) [#13484](https://github.com/aws/aws-cdk/issues/13484) +* **ec2:** Security Groups support all protocols ([#13593](https://github.com/aws/aws-cdk/issues/13593)) ([8c6b3eb](https://github.com/aws/aws-cdk/commit/8c6b3ebea464e27f68ffcab32857d8baec29c413)), closes [#13403](https://github.com/aws/aws-cdk/issues/13403) +* **lambda:** fromDockerBuild output is located under /asset ([#13539](https://github.com/aws/aws-cdk/issues/13539)) ([77449f6](https://github.com/aws/aws-cdk/commit/77449f61e7075fef1240fc52becb8ea60b9ea9ad)), closes [#13439](https://github.com/aws/aws-cdk/issues/13439) +* **region-info:** ap-northeast-3 data not correctly registered ([#13564](https://github.com/aws/aws-cdk/issues/13564)) ([64da84b](https://github.com/aws/aws-cdk/commit/64da84be5c60bb8132551bcc27a7ca9c7effe95d)), closes [#13561](https://github.com/aws/aws-cdk/issues/13561) + ## [1.93.0](https://github.com/aws/aws-cdk/compare/v1.92.0...v1.93.0) (2021-03-11) diff --git a/version.v1.json b/version.v1.json index 097cc55f8cc18..3280de4395415 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.93.0" + "version": "1.94.0" } From f15d249cb7dce8f221316a25d078e909119286c4 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 16 Mar 2021 15:07:06 +0100 Subject: [PATCH 27/41] chore(cloudformation-include): build fails on .DS_Store (#13595) The build script was assuming everything in the package directory was itself a directory; not necessarily true on macOS machines. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/cloudformation-include/build.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@aws-cdk/cloudformation-include/build.js b/packages/@aws-cdk/cloudformation-include/build.js index ab36ffd345d7f..454a10a5014d5 100644 --- a/packages/@aws-cdk/cloudformation-include/build.js +++ b/packages/@aws-cdk/cloudformation-include/build.js @@ -29,6 +29,8 @@ async function main() { for (const constructLibraryDir of constructLibrariesDirs) { const absConstructLibraryDir = path.resolve(constructLibrariesRoot, constructLibraryDir); + if (!fs.statSync(absConstructLibraryDir).isDirectory()) { continue; } // .DS_Store + const libraryPackageJson = require(path.join(absConstructLibraryDir, 'package.json')); const libraryDependencyVersion = dependencies[libraryPackageJson.name]; From 5dbb0ba836d089d2f64ae33db68e2efc675b17d5 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Tue, 16 Mar 2021 14:51:50 +0000 Subject: [PATCH 28/41] chore: update and simplify contribution guide (#13525) I've received feedback from some folks who would like to start contributing to the CDK but found the contribution guide unwieldy. Attempting to wittle down and simplify. Move content around so that useful information is at the top. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- CONTRIBUTING.md | 876 ++++++------------ .../DESIGN_GUIDELINES.md | 0 docs/release.md | 53 ++ 3 files changed, 354 insertions(+), 575 deletions(-) rename DESIGN_GUIDELINES.md => docs/DESIGN_GUIDELINES.md (100%) create mode 100644 docs/release.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7615d7b10db4e..fab4ce03200c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,40 +7,28 @@ and let us know if it's not up-to-date (even better, submit a PR with your corr - [Getting Started](#getting-started) - [Pull Requests](#pull-requests) - - [Pull Request Checklist](#pull-request-checklist) - - [Step 1: Open Issue](#step-1-open-issue) + - [Step 1: Find something to work on](#step-1-find-something-to-work-on) - [Step 2: Design (optional)](#step-2-design-optional) - [Step 3: Work your Magic](#step-3-work-your-magic) - [Step 4: Commit](#step-4-commit) - [Step 5: Pull Request](#step-5-pull-request) - [Step 6: Merge](#step-6-merge) - [Breaking Changes](#breaking-changes) +- [Documentation](#documentation) + - [rosetta](#rosetta) - [Tools](#tools) - - [Main build scripts](#main-build-scripts) - - [Partial build tools](#partial-build-tools) - - [Useful aliases](#useful-aliases) - [Linters](#linters) - [cfn2ts](#cfn2ts) - [scripts/foreach.sh](#scriptsforeachsh) - [Jetbrains support (WebStorm/IntelliJ)](#jetbrains-support-webstormintellij) -- [Workflows](#workflows) - - [Full clean build](#full-clean-build) - - [Full Docker build](#full-docker-build) - - [Partial build](#partial-build) - - [Partial pack](#partial-pack) - - [Quick Iteration](#quick-iteration) - [Linking against this repository](#linking-against-this-repository) - [Running integration tests in parallel](#running-integration-tests-in-parallel) - [Visualizing dependencies in a CloudFormation Template](#visualizing-dependencies-in-a-cloudformation-template) - - [Adding Dependencies](#adding-dependencies) - - [Finding dependency cycles between packages](#finding-dependency-cycles-between-packages) - - [Updating all Dependencies](#updating-all-dependencies) - - [Running CLI integration tests](#running-cli-integration-tests) - - [Changing the Cloud Assembly Schema](#changing-cloud-assembly-schema) - - [API Compatibility Checks](#api-compatibility-checks) - - [Examples](#examples) - - [Feature Flags](#feature-flags) - - [Versioning and Release](#versioning-and-release) + - [Find dependency cycles between packages](#find-dependency-cycles-between-packages) +- [Running CLI integration tests](#running-cli-integration-tests) +- [Changing the Cloud Assembly Schema](#changing-cloud-assembly-schema) +- [Feature Flags](#feature-flags) +- [Versioning and Release](#versioning-and-release) - [Troubleshooting](#troubleshooting) - [Debugging](#debugging) - [Connecting the VS Code Debugger](#connecting-the-vs-code-debugger) @@ -49,134 +37,183 @@ and let us know if it's not up-to-date (even better, submit a PR with your corr ## Getting Started -### Gitpod +The following steps describe how to set up the AWS CDK repository on your local machine. +The alternative is to use [Gitpod](https://www.gitpod.io/), a Cloud IDE for your development. +See [Gitpod section](#gitpod) on how to set up the CDK repo on Gitpod. -For setting up a local development environment, -we recommend using [Gitpod](http://gitpod.io) - -a service that allows you to spin up an in-browser -Visual Studio Code-compatible editor, -with everything set up and ready to go for CDK development. -Just click the button below to create your private workspace: - -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/aws/aws-cdk) - -This will start a new Gitpod workspace, -and immediately kick off a build of the CDK code. -Once it's done (it takes around an hour, unfortunately), -you can work on any package that you want to modify, -as described in ['Quick Iteration'](#quick-iteration) below. - -Gitpod is free for 50 hours per month - -make sure to stop your workspace when you're done -(you can always resume it later, and it won't need to run the build again). - -### Local dependencies +### Setup -If you don't want to use Gitpod, -you need to have the following SDKs and tools locally: +The following tools need to be installed on your system prior to installing the CDK: - [Node.js >= 10.13.0](https://nodejs.org/download/release/latest-v10.x/) - We recommend using a version in [Active LTS](https://nodejs.org/en/about/releases/) - ⚠️ versions `13.0.0` to `13.6.0` are not supported due to compatibility issues with our dependencies. - [Yarn >= 1.19.1, < 2](https://yarnpkg.com/lang/en/docs/install) -- [Java >= OpenJDK 8, 11, 14](https://docs.aws.amazon.com/corretto/latest/corretto-8-ug/downloads-list.html) -- [Apache Maven >= 3.6.0, < 4.0](http://maven.apache.org/install.html) - [.NET Core SDK 3.1.x](https://www.microsoft.com/net/download) - [Python >= 3.6.5, < 4.0](https://www.python.org/downloads/release/python-365/) -- [Docker >= 19.03](https://docs.docker.com/get-docker/) -The basic commands to get the repository cloned and built locally follow: +Run the following commands to clone the repository locally. ```console $ git clone https://github.com/aws/aws-cdk.git $ cd aws-cdk +$ yarn install +``` + +We recommend that you use [Visual Studio Code](https://code.visualstudio.com/) to work on the CDK. +We use `eslint` to keep our consistent in terms of style and reducing defects. We recommend installing the +the [eslint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) as well. + +### Repo Layout + +The AWS CDK is a [NPM](https://www.npmjs.com/about) project written in [typescript](https://www.typescriptlang.org/). +More specifically, it is a [monorepo managed using lerna](https://github.com/lerna/lerna#about). +If you're unfamiliar with any of these technologies, it is useful to learn about them and will make understanding the +AWS CDK codebase easier but strictly not necessary for simple contributions. + +The CDK uses [jsii](https://github.com/aws/jsii/) as its primary build system. jsii enables us to write +typescript-compliant source code and produce polyglot libraries, such as, in Java, .NET, Python and Go. + +The repo contains `packages/` directory that contains the CDK public modules. The source code for the IAM module in the +CDK can be found at the location `packages/@aws-cdk/aws-iam`. +The repo also contains the `tools/` directory that holds custom build tooling (modeled as private npm packages) +specific to the CDK. + +### Build + +The full build of the CDK takes a long time complete; 1-2 hours depending on the performance of the build machine. +However, most first time contributions will require changing only one CDK module, sometimes two. A full build of the +CDK is not required in these cases. + +If you want to work on the `@aws-cdk/aws-ec2` module, the following command will build just the EC2 module and any +necessary dependencies. + +```console +$ cd packages/@aws-cdk/aws-ec2 +$ ../../../scripts/buildup +``` + +Note: The `buildup` command is resumable. If your build fails, you can fix the issue and run `buildup --resume` to +resume. + +At this point, you can run build and test the `aws-ec2` module by running + +```console +$ cd packages/@aws-cdk/aws-ec2 $ yarn build +$ yarn test ``` -If you get compiler errors when building, a common cause is a globally installed typescript. Try uninstalling it. +However, if you wish to build the the entire repository, the following command will achieve this. +```console +cd +yarn build ``` -npm uninstall -g typescript + +You are now ready to start contributing to the CDK. See the [Pull Requests](#pull-requests) section on how to make your +changes and submit it as a pull request. + +### Pack + +As called out in the above sections, the AWS CDK uses jsii to produce polyglot targets. This means that each CDK module +produces artifact in all of its target languages. + +Packing involves generating CDK code in the various target languages and packaging them up to be published to their +respective package managers. Once in a while, these will need to be generated either to test the experience of a new +feature, or reproduce a packaging failure. + +To package a specific module, say the `@aws-cdk/aws-ec2` module: + +```console +$ cd +$ docker run --rm --net=host -it -v $PWD:$PWD -w $PWD jsii/superchain +docker$ cd packages/@aws-cdk/aws-ec2 +docker$ ../../../scripts/foreach.sh --up yarn run package +docker$ exit ``` -Alternatively, the [Full Docker build](#full-docker-build) workflow can be used so -that you don't have to worry about installing all those tools on your local machine -and instead only depend on having a working Docker install. +The `dist/` folder within each module contains the packaged up language artifacts. -## Pull Requests +## Docker Build (Alternative) -### Pull Request Checklist - -* [ ] Testing - - Unit test added (prefer not to modify an existing test, otherwise, it's probably a breaking change) - - __CLI change?:__ coordinate update of integration tests with team - - __cdk-init template change?:__ coordinated update of integration tests with team -* [ ] Docs - - __jsdocs__: All public APIs documented - - __README__: README and/or documentation topic updated - - __Design__: For significant features, design document added to `design` folder -* [ ] Title and Description - - __Change type__: title prefixed with **fix**, **feat** and module name in parens, which will appear in changelog - - __Title__: use lower-case and doesn't end with a period - - __Breaking?__: last paragraph: "BREAKING CHANGE: " - - __Issues__: Indicate issues fixed via: "**Fixes #xxx**" or "**Closes #xxx**" -* [ ] Sensitive Modules (requires 2 PR approvers) - - IAM Policy Document (in @aws-cdk/aws-iam) - - EC2 Security Groups and ACLs (in @aws-cdk/aws-ec2) - - Grant APIs (only if not based on official documentation with a reference) - ---- - -### Step 1: Open Issue - -If there isn't one already, open an issue describing what you intend to contribute. It's useful to communicate in -advance because if someone is already working in this space, it may be worth collaborating with them -instead of duplicating the effort. +Build the docker image: -### Step 2: Design (optional) +```console +$ docker build -t aws-cdk . +``` -In some cases, it is useful to seek feedback by iterating on a design document. This is useful -when you plan a big change or feature, or you want advice on what would be the best path forward. +This allows you to run the CDK in a CDK-compatible directory with a command like: -Sometimes, the GitHub issue is sufficient for such discussions, and can be sufficient to get -clarity on what you plan to do. Sometimes, a design document would work better, so people can provide -iterative feedback. +```console +$ docker run -v $(pwd):/app -w /app aws-cdk +``` -Before starting on a design, read through the [design guidelines](DESIGN_GUIDELINES.md) for general -patterns and tips. +## Gitpod (Alternative) -In such cases, use the GitHub issue description to collect **requirements** and -**use cases** for your feature. +You may also set up your local development environment using [Gitpod](http://gitpod.io) - +a service that allows you to spin up an in-browser Visual Studio Code-compatible editor, +with everything set up and ready to go for CDK development. +Just click the button below to create your private workspace: + +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/aws/aws-cdk) + +This will start a new Gitpod workspace, with the CDK repository [pre-built](https://www.gitpod.io/docs/prebuilds/). +You can now work on your CDK repository, as described in the [Getting Started](#getting-started) section. -Then, create a design document in markdown format under the `design/` directory -and request feedback through a pull request. Prefix the PR title with "**RFC:**" -(request for comments). +Gitpod is free for 50 hours per month - make sure to stop your workspace when you're done +(you can always resume it later, and it won't need to run the build again). -Once the design is finalized, you can re-purpose this PR for the implementation, -or open a new PR to that end. +For Gitpod users only! The best way to supply CDK with your AWS credentials is to add them as +[persisting environment variables](https://www.gitpod.io/docs/environment-variables). +Adding them works as follows via terminal: + +```shell +eval $(gp env -e AWS_ACCESS_KEY_ID=XXXXXXXXX) +eval $(gp env -e AWS_SECRET_ACCESS_KEY=YYYYYYY) +eval $(gp env -e AWS_DEFAULT_REGION=ZZZZZZZZ) +eval $(gp env -e) +``` + +## Pull Requests + +### Step 1: Find something to work on + +If you want to contribute a specific feature or fix you have in mind, look at active [pull +requests](https://github.com/aws/aws-cdk/pulls) to see if someone else is already working on it. If not, you can start +contributing your changes. + +On the other hand, if you are here looking for an issue to work on, explore our [backlog of +issues](https://github.com/aws/aws-cdk/issues) and find something that piques your interest. We have labeled all of our +issues for easy filtration. +If you are looking for your first contribution, the ['good first issue' +label](https://github.com/aws/aws-cdk/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) will be of help. + +### Step 2: Design (optional) + +In some cases, it is useful to seek feedback by iterating on a design document. This is useful +when you plan a big change or feature, or you want advice on what would be the best path forward. + +In most cases, the GitHub issue is sufficient for such discussions, and can be sufficient to get +clarity on what you plan to do. If the changes are significant or intrusive to the existing CDK experience, +consider writing an RFC in our [RFC repository](https://github.com/aws/aws-cdk-rfcs) before jumping into our code base. ### Step 3: Work your Magic Work your magic. Here are some guidelines: -* Coding style (abbreviated): - * In general, follow the style of the code around you - * 2 space indentation - * 120 characters wide - * ATX style headings in markdown (e.g. `## H2 heading`) +* Coding style. + * If your change introduces a new construct, take a look at our [design guidelines](./docs/DESIGN_GUIDELINES.md) for + construct libraries. + We also have an [example construct library](packages/@aws-cdk/example-construct-library) that showcases a simple + construct library with a single construct. + * We have a number of linters that run during standard build that will enforce coding consistency and correctness. + Watch out for their error messages and adjust your code accordingly. * Every change requires a unit test * If you change APIs, make sure to update the module's README file * Try to maintain a single feature/bugfix per pull request. It's okay to introduce a little bit of housekeeping - changes along the way, but try to avoid conflating multiple features. Eventually, all these are going to go into a - single commit, so you can use that to frame your scope. -* If your change introduces a new construct, take a look at the our - [example Construct Library](packages/@aws-cdk/example-construct-library) for an explanation of the common patterns we use. - Feel free to start your contribution by copy&pasting files from that project, - and then edit and rename them as appropriate - - it might be easier to get started that way. -* If your change includes code examples (in the `README.md` file or as part of regular TSDoc tags), - you should probably validate those examples can be successfully compiled and trans-literated by - running `yarn rosetta:extract` (this requires other packages used by code examples are built). + changes along the way, but try to avoid conflating multiple features. Eventually, all these are going to go into a + single commit, so you can use that to frame your scope. #### Integration Tests @@ -189,17 +226,6 @@ Integration tests perform a few functions in the CDK code base - 3. (Optionally) Acts as a way to validate that constructs set up the CloudFormation resources as expected. A successful CloudFormation deployment does not mean that the resources are set up correctly. -For Gitpod users only! The best way to supply CDK with your AWS credentials is to add them as -[persisting environment variables](https://www.gitpod.io/docs/environment-variables). -Adding them works as follows via terminal: - -```shell -eval $(gp env -e AWS_ACCESS_KEY_ID=XXXXXXXXX) -eval $(gp env -e AWS_SECRET_ACCESS_KEY=YYYYYYY) -eval $(gp env -e AWS_DEFAULT_REGION=ZZZZZZZZ) -eval $(gp env -e) -``` - If you are working on a new feature that is using previously unused CloudFormation resource types, or involves configuring resource types across services, you need to write integration tests that use these resource types or features. @@ -220,6 +246,20 @@ Examples: * [integ.destinations.ts](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-lambda-destinations/test/integ.destinations.ts#L7) * [integ.token-authorizer.ts](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.ts#L6) +#### yarn watch (Optional) + +We've added a watch feature to the CDK that builds your code as you type it. Start this by running `yarn watch` for +each module that you are modifying. + +For example, watch the EC2 and IAM modules in a second terminal session: + +```console +$ cd packages/@aws-cdk/aws-ec2 +$ yarn watch & # runs in the background +$ cd packages/@aws-cdk/aws-iam +$ yarn watch & # runs in the background +``` + ### Step 4: Commit Create a commit with the proposed changes: @@ -250,21 +290,19 @@ BREAKING CHANGE: Description of what broke and how to achieve this behavior now ### Step 5: Pull Request -* Push to a GitHub fork or to a branch (naming convention: `/`). +* Push to a GitHub fork. + * CDK core members can push to a branch on the AWS CDK repo (naming convention: `/`). * Submit a Pull Request on GitHub. A reviewer will later be assigned by the maintainers. -* Please follow the PR checklist written below. We trust our contributors to self-check, and this helps that process! -* Discuss review comments and iterate until you get at least one “Approve”. When iterating, push new commits to the +* Discuss review comments and iterate until you get at least one "Approve". When iterating, push new commits to the same branch. Usually all these are going to be squashed when you merge to master. The commit messages should be hints for you when you finalize your merge commit message. * Make sure to update the PR title/description if things change. The PR title/description are going to be used as the commit title/message and will appear in the CHANGELOG, so maintain them all the way throughout the process. - - ### Step 6: Merge * Make sure your PR builds successfully (we have CodeBuild setup to automatically build all PRs). -* Once approved and tested, a maintainer will squash-merge to master and will use your PR title/description as the +* Once approved and tested, one of our bots will squash-merge to master and will use your PR title/description as the commit message. ## Breaking Changes @@ -275,13 +313,9 @@ programs that customers could have been writing against the current version of the CDK, that will no longer "work correctly" with the proposed new version of the CDK. -Breaking changes are not allowed in *stable* libraries¹. They are permissible -but still *highly discouraged* in experimental libraries, and require explicit -callouts in the bodies of Pull Requests that introduce them. - -> ¹) Note that starting in version 2 of the CDK, the majority of library code will be -> bundled into a single main CDK library which will be considered stable, and so -> no code in there can undergo breaking changes. +Breaking changes are not allowed in *stable* libraries. They are permitted +in experimental libraries, unless the maintainer of the module decides that it should be avoided. +Breaking changes require explicit callouts in the bodies of Pull Requests that introduce them. Breaking changes come in two flavors: @@ -327,8 +361,11 @@ $ yarn build $ yarn compat ``` -To figure out if the changes you made were breaking. See the section [API Compatibility -Checks](#api-compatibility-checks) for more information. +The only case where it is legitimate to break a public API is if the existing +API is a bug that blocked the usage of a feature. This means that by breaking +this API we will not break anyone, because they weren't able to use it. The file +`allowed-breaking-changes.txt` in the root of the repo is an exclusion file that +can be used in these cases. #### Dealing with breaking API surface changes @@ -404,57 +441,134 @@ If the new behavior is going to be breaking, the user must opt in to it, either Of these two, the first one is preferred if possible (as feature flags have non-local effects which can cause unintended effects). -## Tools +## Documentation + +Every module's README is rendered as the landing page of the official documentation. For example, this is +the README for the `aws-ec2` module - https://docs.aws.amazon.com/cdk/api/latest/docs/aws-ec2-readme.html. + +### Rosetta + +The README file contains code snippets written as typescript code. Code snippets typed in fenced code blocks +(such as `` ```ts ``) will be automatically extracted, compiled and translated to other languages when the +during the [pack](#pack) step. We call this feature 'rosetta'. -The CDK is a big project, and at the moment, all of the CDK modules are mastered in a single monolithic repository -(uses [lerna](https://github.com/lerna/lerna)). There are pros and cons to this approach, and it's especially valuable -to maintain integrity in the early stage of the project where things constantly change across the stack. In the future, -we believe many of these modules will be extracted to their own repositories. +You can run rosetta on the EC2 module (or any other module) by running: -Another complexity is that the CDK is packaged using [jsii](https://github.com/aws/jsii) to multiple programming -languages. This means that when a full build is complete, there will be a version of each module for each supported -language. +```console +$ cd packages/@aws-cdk/aws-ec2 +$ yarn rosetta:extract --strict +``` -However, in many cases, you can probably get away with just building a portion of the project, based on areas that you -want to work on. +To successfully do that, they must be compilable. The easiest way to do that is using +a *fixture*, which looks like this: -We recommend that you use [Visual Studio Code](https://code.visualstudio.com/) to work on the CDK. Be sure to install -the [eslint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) for it as well, since we have -strict linting rules that will prevent your code from compiling, but with VSCode and this extension can be automatically -fixed for you by hitting `Ctrl-.` when your cursor is on a red underline. +```` +```ts fixture=with-bucket +bucket.addLifecycleTransition({ ...props }); +``` +```` -### Main build scripts +While processing the examples, the tool will look for a file called +`rosetta/with-bucket.ts-fixture` in the package directory. This file will be +treated as a regular TypeScript source file, but it must also contain the text +`/// here`, at which point the example will be inserted. The complete file must +compile properly. -The build process is divided into stages, so you can invoke them as needed from the root of the repo: +Before the `/// here` marker, the fixture should import the necessary packages +and initialize the required variables. -- __`yarn build`__: runs the `build` and `test` commands in all modules (in topological order). -- __`yarn pack`__: packages all modules to all supported languages and produces a `dist/` directory with all the outputs - (running this script requires that you installed the [toolchains](#getting-started) for all target languages on your - system). +When no fixture is specified, the fixture with the name +`rosetta/default.ts-fixture` will be used if present. `nofixture` can be used to +opt out of that behavior. -### Partial build tools +In an `@example` block, which is unfenced, the first line of the example can +contain three slashes to achieve the same effect: -There are also two useful scripts in the `scripts` directory that can help you build part of the repo: +``` +/** + * @example + * /// fixture=with-bucket + * bucket.addLifecycleTransition({ ...props }); + */ +``` -- __`scripts/buildup`__: builds the current module and all of its dependencies (in topological order). -- __`scripts/builddown`__: builds the current module and all of its consumers (in topological order). +For a practical example of how making sample code compilable works, see the +`aws-ec2` package. -### Useful aliases +#### Recommendations -You can also add a few useful aliases to your shell profile: +In order to offer a consistent documentation style throughout the AWS CDK +codebase, example code should follow the following recommendations (there may be +cases where some of those do not apply - good judgement is to be applied): -```bash -# runs an npm script via lerna for a the current module -alias lr='lerna run --stream --scope $(node -p "require(\"./package.json\").name")' +- Types from the documented module should be **un-qualified**: -# runs "yarn build" (build + test) for the current module -alias lb='lr build' -alias lt='lr test' + ```ts + // An example in the @aws-cdk/core library, which defines Duration + Duration.minutes(15); + ``` + +- Types from other modules should be **qualified**: -# runs "yarn watch" for the current module (recommended to run in a separate terminal session): -alias lw='lr watch' + ```ts + // An example in the @aws-cdk/core library, using something from @aws-cdk/aws-s3 + const bucket = new s3.Bucket(this, 'Bucket'); + // ...rest of the example... + ``` + +- Within `.ts-fixture` files, make use of `declare` statements instead of + writing a compatible value (this will make your fixtures more durable): + + ```ts + // An hypothetical 'rosetta/default.ts-fixture' file in `@aws-cdk/core` + import * as kms from '@aws-cdk/aws-kms'; + import * as s3 from '@aws-cdk/aws-s3'; + import { StackProps } from '@aws-cdk/core'; + + declare const kmsKey: kms.IKey; + declare const bucket: s3.Bucket; + + declare const props: StackProps; + ``` + +## Tools (Advanced) + +### scripts/foreach.sh + +This wonderful tool allows you to execute a command for all modules in this repo +in topological order, but has the incredible property of being stateful. This +means that if a command fails, you can fix the issue and resume from where you +left off. + +To start a session, run: + +```console +$ scripts/foreach.sh COMMAND +``` + +This will execute "COMMAND" for each module in the repo (cwd will be the directory of the module). +If a task fails, it will stop. To resume, simply run `foreach.sh` again (with or without the same command). + +To reset the session (either when all tasks finished or if you wish to run a different session), run: + +```console +$ scripts/foreach.sh --reset +``` + +If you wish to run a command only against a module's dependency closure, use: + +```console +$ cd packages/my-module +$ ../scripts/foreach.sh --up COMMAND ``` +This will execute `COMMAND` against `my-module` and all its deps (in a topological order, of course). + +Consequently, there are two useful scripts that are built on top of `foreach.sh`, and lets you build modules. + +- __`scripts/buildup`__: builds the current module and all of its dependencies (in topological order). +- __`scripts/builddown`__: builds the current module and all of its consumers (in topological order). + ### Linters All linters are executed automatically as part of the build script, `yarn build`. @@ -501,14 +615,14 @@ $ lr pkglint **awslint** is a linter for the AWS Construct Library APIs. It is executed as a part of the build of all AWS modules in the project and enforces the [AWS -Construct Library Design Guidelines](./DESIGN_GUIDELINES.md). +Construct Library Design Guidelines](./docs/DESIGN_GUIDELINES.md). For more information about this tool, see the [awslint README](./packages/awslint/README.md). Generally speaking, if you make any changes which violate an awslint rule, build will fail with appropriate messages. All rules are documented and explained in -the [guidelines](./DESIGN_GUIDELINES.md). +the [guidelines](./docs/DESIGN_GUIDELINES.md). Here are a few useful commands: @@ -520,31 +634,6 @@ Here are a few useful commands: evaluate only the rule specified [awslint README](./packages/awslint/README.md) for details on include/exclude rule patterns. - -#### jsii-rosetta - -**jsii-rosetta** can be used to verify that all code examples included in documentation for a package (including those -in `README.md`) successfully compile against the library they document. It is recommended to run it to ensure all -examples are still accurate. Successfully building examples is also necessary to ensure the best possible translation to -other supported languages (`C#`, `Java`, `Python`, ...). - -> Note that examples may use libraries that are not part of the `dependencies` or `devDependencies` of the documented -> package. For example, `@aws-cdk/core` contains many examples that leverage libraries built *on top of it* (such as -> `@aws-cdk/aws-sns`). Such libraries must be built (using `yarn build`) before **jsii-rosetta** can verify that -> examples are correct. - -To run **jsii-rosetta** in *strict* mode (so that it always fails when encountering examples that fail to compile), use -the following command: - -```console -$ yarn rosetta:extract --strict -``` - -For more information on how you can address examples that fail compiling due to missing fixtures (declarations that are -necessary for the example to compile, but which would distract the reader away from what is being demonstrated), you -might need to introduce [rosetta fixtures](https://github.com/aws/jsii/tree/main/packages/jsii-rosetta#fixtures). Refer -to the [Examples](#examples) section. - ### cfn2ts This tool is used to generate our low-level CloudFormation resources @@ -561,37 +650,6 @@ Each module also has an npm script called `cfn2ts`: * `yarn cfn2ts`: generates L1 for a specific module * `lerna run cfn2ts`: generates L1 for the entire repo -### scripts/foreach.sh - -This wonderful tool allows you to execute a command for all modules in this repo -in topological order, but has the incredible property of being stateful. This -means that if a command fails, you can fix the issue and resume from where you -left off. - -To start a session, run: - -```console -$ scripts/foreach.sh COMMAND -``` - -This will execute "COMMAND" for each module in the repo (cwd will be the directory of the module). -If a task fails, it will stop. To resume, simply run `foreach.sh` again (with or without the same command). - -To reset the session (either when all tasks finished or if you wish to run a different session), run: - -```console -$ scripts/foreach.sh --reset -``` - -If you wish to run a command only against a module's dependency closure, use: - -```console -$ cd packages/my-module -$ ../scripts/foreach.sh --up COMMAND -``` - -This will execute `COMMAND` against `my-module` and all its deps (in a topological order, of course). - ### Jetbrains support (WebStorm/IntelliJ) This project uses lerna and utilizes symlinks inside nested `node_modules` directories. You may encounter an issue during @@ -599,146 +657,19 @@ indexing where the IDE attempts to index these directories and keeps following l available memory and crashes. To fix this, you can run ```node ./scripts/jetbrains-remove-node-modules.js``` to exclude these directories. -## Workflows - -This section includes step-by-step descriptions of common workflows. - -### Full clean build - -Clone the repo: - -```console -$ git clone https://github.com/aws/aws-cdk.git -$ cd aws-cdk -``` - -If you already have a local repo and you want a fresh build, run `git clean -fdx` from the root. - -Install and build: - -```console -$ ./install.sh -$ yarn build -``` - -If you also wish to package to all languages, make sure you have all the [toolchains](#getting-started) and now run: - -``` -$ ./pack.sh -``` - -> NOTE: in local builds, pack.sh will finish but will fail with an error -> indicating the build artifacts use the marker version (`0.0.0`). This is -> normal, and you can trust the output in `dist/` despite the failure. This is a -> protection we have to make sure we don't accidentally release artifacts with -> the marker version. - -### Full Docker build - -Clone the repo: - -```console -$ git clone https://github.com/aws/aws-cdk.git -$ cd aws-cdk -``` - -If you already have a local repo and you want a fresh build, run `git clean -fdx` from the root. - -Build the docker image: - -```console -$ docker build -t aws-cdk . -``` - -This allows you to run the CDK in a CDK-compatible directory with a command like: - -```console -$ docker run -v $(pwd):/app -w /app aws-cdk -``` - -### Partial build - -In many cases, you don't really need to build the entire project. Say you want to work on the `@aws-cdk/aws-ec2` module: - -```console -$ yarn install -$ cd packages/@aws-cdk/aws-ec2 -$ ../../../scripts/buildup -``` - -Note that `buildup` uses `foreach.sh`, which means it is resumable. If your build fails and you wish to resume, just run -`buildup --resume`. If you wish to restart, run `buildup` again. - -### Partial pack - -Packing involves generating CDK code in the various target languages and packaging them up to be published to their -respective package managers. Once in a while, these will need to be generated either to test the experience of a new -feature, or reproduce a packaging failure. - -Before running this, make sure either that the CDK module and all of its dependencies are already built. See [Partial -build](#partial-build) or [Full clean build](#full-clean-build). - -To package a specific module, say the `@aws-cdk/aws-ec2` module: - -```console -$ cd -$ docker run --rm --net=host -it -v $PWD:$PWD -w $PWD jsii/superchain -docker$ cd packages/@aws-cdk/aws-ec2 -docker$ ../../../scripts/foreach.sh --up yarn run package -docker$ exit -``` - -The `dist/` folder within each module contains the packaged up language artifacts. - -### Quick Iteration - -After you've built the modules you want to work on once, use `yarn watch` for each module that you are modifying. - -Watch the EC2 and IAM modules in a second terminal session: - -```console -$ cd packages/@aws-cdk/aws-ec2 -$ yarn watch & # runs in the background -$ cd packages/@aws-cdk/aws-iam -$ yarn watch & # runs in the background -``` - -Code... - -Now to test, you can either use `yarn test` or invoke nodeunit/jest directly: - -Running nodeunit tests directly on a module: -```console -$ cd packages/@aws-cdk/aws-iam -$ nodeunit test/test.*.js - -``` - -Running jest tests directly on a module: -```console -$ cd packages/@aws-cdk/aws-iam -$ jest test/*test.js - -``` - ### Linking against this repository -The script `./link-all.sh` can be used to generate symlinks to all modules in this repository under some `node_module` -directory. This can be used to develop against this repo as a local dependency. +If you are developing your own CDK application or library and want to use the locally checked out version of the +AWS CDK, instead of the the version of npm, the `./link-all.sh` script will help here. -One can use the `postinstall` script to symlink this repo: +This script symlinks the built modules from the local AWS CDK repo under the `node_modules/` folder of the CDK app or +library. -```json -{ - "scripts": { - "postinstall": "../aws-cdk/link-all.sh" - } -} +```console +$ cd +$ /link-all.sh ``` -This assumes this repo is a sibling of the target repo and will install the CDK as a linked dependency during -`yarn install`. - ### Running integration tests in parallel Integration tests may take a long time to complete. We can speed this up by running them in parallel @@ -760,23 +691,7 @@ Use GraphViz with `template-deps-to-dot`: $ cdk -a some.app.js synth | $awscdk/scripts/template-deps-to-dot | dot -Tpng > deps.png ``` -### Adding Dependencies - -The root [package.json](./package.json) includes global devDependencies (see -[lerna docs](https://github.com/lerna/lerna#common-devdependencies)) on the topic. - - * To add a global dependency, run `yarn add --dev` at the root. - * To add a dependency for a specific module, run `yarn add ` inside the module's directory. - -Guidelines: - - * We cannot accept dependencies that use non-permissive open source licenses (Apache, MIT, etc). - * Make sure dependencies are defined using [caret - ranges](https://docs.npmjs.com/misc/semver#caret-ranges-123-025-004) (e.g. `^1.2.3`). This enables non-breaking - updates to automatically be picked up. - * Make sure `yarn.lock` is included in your commit. - -### Finding dependency cycles between packages +### Find dependency cycles between packages You can use `find-cycles` to print a list of internal dependency cycles: @@ -792,159 +707,19 @@ Cycle: @aws-cdk/aws-sns => @aws-cdk/aws-lambda => @aws-cdk/aws-codecommit => @aw Cycle: @aws-cdk/aws-sns => @aws-cdk/aws-lambda => @aws-cdk/aws-codecommit => @aws-cdk/aws-codepipeline => @aws-cdk/aws-sns ``` -### Updating all Dependencies - -To update all dependencies (without bumping major versions): - -1. Obtain a fresh clone from "master". -2. Run `yarn install` -3. Run `./scripts/update-dependencies.sh --mode full` (use `--mode semver` to avoid bumping major versions) -4. Submit a Pull Request. - -### Running CLI integration tests +## Running CLI integration tests The CLI package (`packages/aws-cdk`) has some integration tests that aren't run as part of the regular build, since they have some particular requirements. See the [CLI CONTRIBUTING.md file](packages/aws-cdk/CONTRIBUTING.md) for more information on running those tests. -### Changing Cloud Assembly Schema +## Changing Cloud Assembly Schema If you plan on making changes to the `cloud-assembly-schema` package, make sure you familiarize yourself with its own [contribution guide](./packages/@aws-cdk/cloud-assembly-schema/CONTRIBUTING.md) -### API Compatibility Checks - -All stable APIs in the CDK go through a compatibility check during build using -the [jsii-diff] tool. This tool downloads the latest released version from npm -and verifies that the APIs in the current build have not changed in a breaking -way. - -[jsii-diff]: https://www.npmjs.com/package/jsii-diff - -Compatibility checks always run as part of a full build (`yarn build`). - -You can use `yarn compat` to run compatibility checks for all modules: - -```shell -(working directory is repo root) -$ yarn build -$ yarn compat -``` - -You can also run `compat` from individual package directories: - -```shell -$ cd packages/@aws-cdk/aws-sns -$ yarn build -$ yarn compat -``` - -The only case where it is legitimate to break a public API is if the existing -API is a bug that blocked the usage of a feature. This means that by breaking -this API we will not break anyone, because they weren't able to use it. The file -`allowed-breaking-changes.txt` in the root of the repo is an exclusion file that -can be used in these cases. - -### Examples - -#### Fixture Files - -Examples typed in fenced code blocks (looking like `'''ts`, but then with backticks -instead of regular quotes) will be automatically extracted, compiled and translated -to other languages when the bindings are generated. - -To successfully do that, they must be compilable. The easiest way to do that is using -a *fixture*, which looks like this: - -``` -'''ts fixture=with-bucket -bucket.addLifecycleTransition({ ...props }); -''' -``` - -While processing the examples, the tool will look for a file called -`rosetta/with-bucket.ts-fixture` in the package directory. This file will be -treated as a regular TypeScript source file, but it must also contain the text -`/// here`, at which point the example will be inserted. The complete file must -compile properly. - -Before the `/// here` marker, the fixture should import the necessary packages -and initialize the required variables. - -If no fixture is specified, the fixture with the name -`rosetta/default.ts-fixture` will be used if present. `nofixture` can be used to -opt out of that behavior. - -In an `@example` block, which is unfenced, the first line of the example can -contain three slashes to achieve the same effect: - -``` -/** - * @example - * /// fixture=with-bucket - * bucket.addLifecycleTransition({ ...props }); - */ -``` - -When including packages in your examples (even the package you're writing the -examples for), use the full package name (e.g. `import s3 = -require('@aws-cdk/aws-s3);`). The example will be compiled in an environment -where all CDK packages are available using their public names. In this way, -it's also possible to import packages that are not in the dependency set of -the current package. - -For a practical example of how making sample code compilable works, see the -`aws-ec2` package. - -#### Recommendations - -In order to offer a consistent documentation style throughout the AWS CDK -codebase, example code should follow the following recommendations (there may be -cases where some of those do not apply - good judgement is to be applied): - -- Types from the documented module should be **un-qualified**: - - ```ts - // An example in the @aws-cdk/core library, which defines Duration - Duration.minutes(15); - ``` - -- Types from other modules should be **qualified**: - - ```ts - // An example in the @aws-cdk/core library, using something from @aws-cdk/aws-s3 - const bucket = new s3.Bucket(this, 'Bucket'); - // ...rest of the example... - ``` - -- Within `.ts-fixture` files, make use of `declare` statements instead of - writing a compatible value (this will make your fixtures more durable): - - ```ts - // An hypothetical 'rosetta/default.ts-fixture' file in `@aws-cdk/core` - import * as kms from '@aws-cdk/aws-kms'; - import * as s3 from '@aws-cdk/aws-s3'; - import { StackProps } from '@aws-cdk/core'; - - declare const kmsKey: kms.IKey; - declare const bucket: s3.Bucket; - - declare const props: StackProps; - ``` - -> Those recommendations are not verified or enforced by automated tooling. Pull -> request reviewers may however request that new sample code is edited to meet -> those requirements as needed. - -#### Checking a single package - -Examples of all packages are extracted and compiled as part of the packaging -step. If you are working on getting rid of example compilation errors of a -single package, you can run `yarn rosetta:extract --strict` in the package's -directory (see the [**jsii-rosetta**](#jsii-rosetta) section). - -### Feature Flags +## Feature Flags Sometimes we want to introduce new breaking behavior because we believe this is the correct default behavior for the CDK. The problem of course is that breaking @@ -987,7 +762,7 @@ CDK](https://github.com/aws/aws-cdk/issues/3398) we will either remove the legacy behavior or flip the logic for all these features and then reset the `FEATURE_FLAGS` map for the next cycle. -#### CDKv2 +### Feature Flags - CDKv2 We have started working on the next version of the CDK, specifically CDKv2. This is currently being maintained on a separate branch `v2-main` whereas `master` continues to track versions `1.x`. @@ -1003,59 +778,10 @@ behaviour when flags are enabled or disabled in the two major versions. [jest helper methods]: https://github.com/aws/aws-cdk/blob/master/tools/cdk-build-tools/lib/feature-flag.ts -### Versioning and Release - -The `release.json` file at the root of the repo determines which release line -this branch belongs to. - -```js -{ - "majorVersion": 1 | 2, - "releaseType": "stable" | "alpha" | "rc" -} -``` - -To reduce merge conflicts in automatic merges between version branches, the -current version number is stored under `version.vNN.json` (where `NN` is -`majorVersion`) and changelogs are stored under `CHANGELOG.NN.md` (for -historical reasons, the changelog for 1.x is under `CHANGELOG.md`). When we -fork to a new release branch (e.g. `v2-main`), we will update `release.json` in -this branch to reflect the new version line, and this information will be used -to determine how releases are cut. - -The actual `version` field in all `package.json` files should always be `0.0.0`. -This means that local development builds will use version `0.0.0` instead of the -official version from the version file. - -#### `./bump.sh` - -This script uses [standard-version] to update the version in `version.vNN.json` -to the next version. By default it will perform a **minor** bump, but `./bump.sh -patch` can be used to perform a patch release if that's needed. - -This script will also update the relevant changelog file. - -[standard-version]: https://github.com/conventional-changelog/standard-version - -#### `scripts/resolve-version.js` - -The script evaluates evaluates the configuration in `release.json` and exports an -object like this: - -```js -{ - version: '2.0.0-alpha.1', // the current version - versionFile: 'version.v2.json', // the version file - changelogFile: 'CHANGELOG.v2.md', // changelog file name - prerelease: 'alpha', // prerelease tag (undefined for stable) - marker: '0.0.0' // version marker in package.json files -} -``` - -#### scripts/align-version.sh +## Versioning and Release -In official builds, the `scripts/align-version.sh` is used to update all -`package.json` files based on the version from `version.vNN.json`. +See [release.md](./docs/release.md) for details on how CDK versions are maintained and how +to trigger a new release ## Troubleshooting diff --git a/DESIGN_GUIDELINES.md b/docs/DESIGN_GUIDELINES.md similarity index 100% rename from DESIGN_GUIDELINES.md rename to docs/DESIGN_GUIDELINES.md diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 0000000000000..f7f5f9612ea23 --- /dev/null +++ b/docs/release.md @@ -0,0 +1,53 @@ +# Versioning and Release + +The `release.json` file at the root of the repo determines which release line +this branch belongs to. + +```js +{ + "majorVersion": 1 | 2, + "releaseType": "stable" | "alpha" | "rc" +} +``` + +To reduce merge conflicts in automatic merges between version branches, the +current version number is stored under `version.vNN.json` (where `NN` is +`majorVersion`) and changelogs are stored under `CHANGELOG.NN.md` (for +historical reasons, the changelog for 1.x is under `CHANGELOG.md`). When we +fork to a new release branch (e.g. `v2-main`), we will update `release.json` in +this branch to reflect the new version line, and this information will be used +to determine how releases are cut. + +The actual `version` field in all `package.json` files should always be `0.0.0`. +This means that local development builds will use version `0.0.0` instead of the +official version from the version file. + +## `./bump.sh` + +This script uses [standard-version] to update the version in `version.vNN.json` +to the next version. By default it will perform a **minor** bump, but `./bump.sh +patch` can be used to perform a patch release if that's needed. + +This script will also update the relevant changelog file. + +[standard-version]: https://github.com/conventional-changelog/standard-version + +## `scripts/resolve-version.js` + +The script evaluates evaluates the configuration in `release.json` and exports an +object like this: + +```js +{ + version: '2.0.0-alpha.1', // the current version + versionFile: 'version.v2.json', // the version file + changelogFile: 'CHANGELOG.v2.md', // changelog file name + prerelease: 'alpha', // prerelease tag (undefined for stable) + marker: '0.0.0' // version marker in package.json files +} +``` + +## scripts/align-version.sh + +In official builds, the `scripts/align-version.sh` is used to update all +`package.json` files based on the version from `version.vNN.json`. \ No newline at end of file From 73209e17f314cf61f703d51ef3b9f197d2f1bdc3 Mon Sep 17 00:00:00 2001 From: Jan Brauer Date: Tue, 16 Mar 2021 16:39:38 +0100 Subject: [PATCH 29/41] feat(lambda-event-sources): msk and self-managed kafka event sources (#12507) Fixes #12099 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-lambda-event-sources/README.md | 61 ++++ .../aws-lambda-event-sources/lib/index.ts | 1 + .../aws-lambda-event-sources/lib/kafka.ts | 194 ++++++++++ .../aws-lambda-event-sources/lib/stream.ts | 2 +- .../aws-lambda-event-sources/package.json | 6 + .../test/test.kafka.ts | 334 ++++++++++++++++++ .../aws-lambda/lib/event-source-mapping.ts | 104 +++++- .../test/event-source-mapping.test.ts | 91 +++++ packages/@aws-cdk/aws-msk/README.md | 8 + packages/@aws-cdk/aws-msk/lib/cluster.ts | 34 ++ packages/@aws-cdk/aws-msk/lib/index.ts | 1 + packages/@aws-cdk/aws-msk/package.json | 2 +- 12 files changed, 835 insertions(+), 3 deletions(-) create mode 100644 packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts create mode 100644 packages/@aws-cdk/aws-lambda-event-sources/test/test.kafka.ts create mode 100644 packages/@aws-cdk/aws-msk/lib/cluster.ts diff --git a/packages/@aws-cdk/aws-lambda-event-sources/README.md b/packages/@aws-cdk/aws-lambda-event-sources/README.md index 5e1578d9aefd4..eb4f206c8f3c9 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/README.md +++ b/packages/@aws-cdk/aws-lambda-event-sources/README.md @@ -207,6 +207,67 @@ myFunction.addEventSource(new KinesisEventSource(stream, { })); ``` +## Kafka + +You can write Lambda functions to process data either from [Amazon MSK](https://docs.aws.amazon.com/lambda/latest/dg/with-msk.html) or a [self managed Kafka](https://docs.aws.amazon.com/lambda/latest/dg/kafka-smaa.html) cluster. + +The following code sets up Amazon MSK as an event source for a lambda function. Credentials will need to be configured to access the +MSK cluster, as described in [Username/Password authentication](https://docs.aws.amazon.com/msk/latest/developerguide/msk-password.html). + +```ts +import * as lambda from '@aws-cdk/aws-lambda'; +import * as msk from '@aws-cdk/aws-lambda'; +import { Secret } from '@aws-cdk/aws-secretmanager'; +import { ManagedKafkaEventSource } from '@aws-cdk/aws-lambda-event-sources'; + +// Your MSK cluster +const cluster = msk.Cluster.fromClusterArn(this, 'Cluster', + 'arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-4'); + +// The Kafka topic you want to subscribe to +const topic = 'some-cool-topic' + +// The secret that allows access to your MSK cluster +// You still have to make sure that it is associated with your cluster as described in the documentation +const secret = new Secret(this, 'Secret', { secretName: 'AmazonMSK_KafkaSecret' }); + +myFunction.addEventSource(new ManagedKafkaEventSource({ + cluster: cluster, + topic: topic, + secret: secret, + batchSize: 100, // default + startingPosition: lambda.StartingPosition.TRIM_HORIZON +})); +``` + +The following code sets up a self managed Kafka cluster as an event source. Username and password based authentication +will need to be set up as described in [Managing access and permissions](https://docs.aws.amazon.com/lambda/latest/dg/smaa-permissions.html#smaa-permissions-add-secret). + +```ts +import * as lambda from '@aws-cdk/aws-lambda'; +import { Secret } from '@aws-cdk/aws-secretmanager'; +import { SelfManagedKafkaEventSource } from '@aws-cdk/aws-lambda-event-sources'; + +// The list of Kafka brokers +const bootstrapServers = ['kafka-broker:9092'] + +// The Kafka topic you want to subscribe to +const topic = 'some-cool-topic' + +// The secret that allows access to your self hosted Kafka cluster +const secret = new Secret(this, 'Secret', { ... }); + +myFunction.addEventSource(new SelfManagedKafkaEventSource({ + bootstrapServers: bootstrapServers, + topic: topic, + secret: secret, + batchSize: 100, // default + startingPosition: lambda.StartingPosition.TRIM_HORIZON +})); +``` + +If your self managed Kafka cluster is only reachable via VPC also configure `vpc` `vpcSubnets` and `securityGroup`. + ## Roadmap Eventually, this module will support all the event sources described under diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/index.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/index.ts index 19253a743cae8..555137e51afbf 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/index.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/index.ts @@ -1,5 +1,6 @@ export * from './api'; export * from './dynamodb'; +export * from './kafka'; export * from './kinesis'; export * from './s3'; export * from './sns'; diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts new file mode 100644 index 0000000000000..f0782964d93ce --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts @@ -0,0 +1,194 @@ +import * as crypto from 'crypto'; +import { ISecurityGroup, IVpc, SubnetSelection } from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as msk from '@aws-cdk/aws-msk'; +import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; +import { Stack } from '@aws-cdk/core'; +import { StreamEventSource, StreamEventSourceProps } from './stream'; + +// 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'; + +/** + * Properties for a Kafka event source + */ +export interface KafkaEventSourceProps extends StreamEventSourceProps { + /** + * the Kafka topic to subscribe to + */ + readonly topic: string, + /** + * the secret with the Kafka credentials, see https://docs.aws.amazon.com/msk/latest/developerguide/msk-password.html for details + */ + readonly secret: secretsmanager.ISecret +} + +/** + * Properties for a MSK event source + */ +export interface ManagedKafkaEventSourceProps extends KafkaEventSourceProps { + /** + * an MSK cluster construct + */ + readonly cluster: msk.ICluster +} + +/** + * The authentication method to use with SelfManagedKafkaEventSource + */ +export enum AuthenticationMethod { + /** + * SASL_SCRAM_512_AUTH authentication method for your Kafka cluster + */ + SASL_SCRAM_512_AUTH = 'SASL_SCRAM_512_AUTH', + /** + * SASL_SCRAM_256_AUTH authentication method for your Kafka cluster + */ + SASL_SCRAM_256_AUTH = 'SASL_SCRAM_256_AUTH', +} + +/** + * Properties for a self managed Kafka cluster event source. + * If your Kafka cluster is only reachable via VPC make sure to configure it. + */ +export interface SelfManagedKafkaEventSourceProps extends KafkaEventSourceProps { + /** + * The list of host and port pairs that are the addresses of the Kafka brokers in a "bootstrap" Kafka cluster that + * a Kafka client connects to initially to bootstrap itself. They are in the format `abc.xyz.com:xxxx`. + */ + readonly bootstrapServers: string[] + + /** + * If your Kafka brokers are only reachable via VPC provide the VPC here + * + * @default none + */ + readonly vpc?: IVpc; + + /** + * If your Kafka brokers are only reachable via VPC, provide the subnets selection here + * + * @default - none, required if setting vpc + */ + readonly vpcSubnets?: SubnetSelection, + + /** + * If your Kafka brokers are only reachable via VPC, provide the security group here + * + * @default - none, required if setting vpc + */ + readonly securityGroup?: ISecurityGroup + + /** + * The authentication method for your Kafka cluster + * + * @default AuthenticationMethod.SASL_SCRAM_512_AUTH + */ + readonly authenticationMethod?: AuthenticationMethod +} + +/** + * Use a MSK cluster as a streaming source for AWS Lambda + */ +export class ManagedKafkaEventSource extends StreamEventSource { + // This is to work around JSII inheritance problems + private innerProps: ManagedKafkaEventSourceProps; + + constructor(props: ManagedKafkaEventSourceProps) { + super(props); + this.innerProps = props; + } + + public bind(target: lambda.IFunction) { + target.addEventSourceMapping( + `KafkaEventSource:${this.innerProps.cluster.clusterArn}${this.innerProps.topic}`, + this.enrichMappingOptions({ + eventSourceArn: this.innerProps.cluster.clusterArn, + startingPosition: this.innerProps.startingPosition, + // From https://docs.aws.amazon.com/msk/latest/developerguide/msk-password.html#msk-password-limitations, "Amazon MSK only supports SCRAM-SHA-512 authentication." + sourceAccessConfigurations: [{ type: lambda.SourceAccessConfigurationType.SASL_SCRAM_512_AUTH, uri: this.innerProps.secret.secretArn }], + kafkaTopic: this.innerProps.topic, + }), + ); + + this.innerProps.secret.grantRead(target); + + target.addToRolePolicy(new iam.PolicyStatement( + { + actions: ['kafka:DescribeCluster', 'kafka:GetBootstrapBrokers', 'kafka:ListScramSecrets'], + resources: [this.innerProps.cluster.clusterArn], + }, + )); + + target.role?.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaMSKExecutionRole')); + } +} + +/** + * Use a self hosted Kafka installation as a streaming source for AWS Lambda. + */ +export class SelfManagedKafkaEventSource extends StreamEventSource { + // This is to work around JSII inheritance problems + private innerProps: SelfManagedKafkaEventSourceProps; + + constructor(props: SelfManagedKafkaEventSourceProps) { + super(props); + if (props.vpc) { + if (!props.securityGroup) { + throw new Error('securityGroup must be set when providing vpc'); + } + if (!props.vpcSubnets) { + throw new Error('vpcSubnets must be set when providing vpc'); + } + } + this.innerProps = props; + } + + public bind(target: lambda.IFunction) { + if (!Construct.isConstruct(target)) { throw new Error('Function is not a construct. Unexpected error.'); } + target.addEventSourceMapping( + this.mappingId(target), + this.enrichMappingOptions({ + kafkaBootstrapServers: this.innerProps.bootstrapServers, + kafkaTopic: this.innerProps.topic, + startingPosition: this.innerProps.startingPosition, + sourceAccessConfigurations: this.sourceAccessConfigurations(), + }), + ); + this.innerProps.secret.grantRead(target); + } + + private mappingId(target: lambda.IFunction) { + let hash = crypto.createHash('md5'); + hash.update(JSON.stringify(Stack.of(target).resolve(this.innerProps.bootstrapServers))); + const idHash = hash.digest('hex'); + return `KafkaEventSource:${idHash}:${this.innerProps.topic}`; + } + + private sourceAccessConfigurations() { + let authType; + switch (this.innerProps.authenticationMethod) { + case AuthenticationMethod.SASL_SCRAM_256_AUTH: + authType = lambda.SourceAccessConfigurationType.SASL_SCRAM_256_AUTH; + break; + case AuthenticationMethod.SASL_SCRAM_512_AUTH: + default: + authType = lambda.SourceAccessConfigurationType.SASL_SCRAM_512_AUTH; + break; + } + let sourceAccessConfigurations = [{ type: authType, uri: this.innerProps.secret.secretArn }]; + if (this.innerProps.vpcSubnets !== undefined && this.innerProps.securityGroup !== undefined) { + sourceAccessConfigurations.push({ + type: lambda.SourceAccessConfigurationType.VPC_SECURITY_GROUP, + uri: this.innerProps.securityGroup.securityGroupId, + }, + ); + this.innerProps.vpc?.selectSubnets(this.innerProps.vpcSubnets).subnetIds.forEach((id) => { + sourceAccessConfigurations.push({ type: lambda.SourceAccessConfigurationType.VPC_SUBNET, uri: id }); + }); + } + return sourceAccessConfigurations; + } +} diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/stream.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/stream.ts index d18eaaf3f947c..96907b97835fc 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/stream.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/stream.ts @@ -3,7 +3,7 @@ import { Duration } from '@aws-cdk/core'; /** * The set of properties for event sources that follow the streaming model, - * such as, Dynamo and Kinesis. + * such as, Dynamo, Kinesis and Kafka. */ export interface StreamEventSourceProps { /** diff --git a/packages/@aws-cdk/aws-lambda-event-sources/package.json b/packages/@aws-cdk/aws-lambda-event-sources/package.json index 20112e1029cbf..33906c1f5219e 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/package.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/package.json @@ -72,12 +72,15 @@ "dependencies": { "@aws-cdk/aws-apigateway": "0.0.0", "@aws-cdk/aws-dynamodb": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kinesis": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-msk": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-s3-notifications": "0.0.0", + "@aws-cdk/aws-secretsmanager": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sns-subscriptions": "0.0.0", "@aws-cdk/aws-sqs": "0.0.0", @@ -88,12 +91,15 @@ "peerDependencies": { "@aws-cdk/aws-apigateway": "0.0.0", "@aws-cdk/aws-dynamodb": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kinesis": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-msk": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-s3-notifications": "0.0.0", + "@aws-cdk/aws-secretsmanager": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sns-subscriptions": "0.0.0", "@aws-cdk/aws-sqs": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.kafka.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.kafka.ts new file mode 100644 index 0000000000000..9cf1af9e50507 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.kafka.ts @@ -0,0 +1,334 @@ +import { arrayWith, expect, haveResource } from '@aws-cdk/assert'; +import { SecurityGroup, SubnetType, Vpc } from '@aws-cdk/aws-ec2'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as msk from '@aws-cdk/aws-msk'; +import { Secret } from '@aws-cdk/aws-secretsmanager'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import * as sources from '../lib'; +import { TestFunction } from './test-function'; + +export = { + 'msk': { + 'default'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const cluster = msk.Cluster.fromClusterArn(stack, 'Cluster', 'some-arn'); + const kafkaTopic = 'some-topic'; + const secret = new Secret(stack, 'Secret', { secretName: 'AmazonMSK_KafkaSecret' }); + + // WHEN + fn.addEventSource(new sources.ManagedKafkaEventSource( + { + cluster: cluster, + topic: kafkaTopic, + secret: secret, + startingPosition: lambda.StartingPosition.TRIM_HORIZON, + })); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'secretsmanager:GetSecretValue', + 'secretsmanager:DescribeSecret', + ], + Effect: 'Allow', + Resource: { + Ref: 'SecretA720EF05', + }, + }, + { + Action: [ + 'kafka:DescribeCluster', + 'kafka:GetBootstrapBrokers', + 'kafka:ListScramSecrets', + ], + Effect: 'Allow', + Resource: cluster.clusterArn, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'FnServiceRoleDefaultPolicyC6A839BF', + Roles: [ + { + Ref: 'FnServiceRoleB9001A96', + }, + ], + })); + + expect(stack).to(haveResource('AWS::Lambda::EventSourceMapping', { + EventSourceArn: cluster.clusterArn, + FunctionName: { + Ref: 'Fn9270CBC0', + }, + BatchSize: 100, + StartingPosition: 'TRIM_HORIZON', + Topics: [ + kafkaTopic, + ], + SourceAccessConfigurations: [ + { + Type: 'SASL_SCRAM_512_AUTH', + URI: { + Ref: 'SecretA720EF05', + }, + }, + ], + })); + + test.done(); + }, + }, + + 'self managed kafka': { + 'default'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const kafkaTopic = 'some-topic'; + const secret = new Secret(stack, 'Secret', { secretName: 'AmazonMSK_KafkaSecret' }); + const bootstrapServers = ['kafka-broker:9092']; + + // WHEN + fn.addEventSource(new sources.SelfManagedKafkaEventSource( + { + bootstrapServers: bootstrapServers, + topic: kafkaTopic, + secret: secret, + startingPosition: lambda.StartingPosition.TRIM_HORIZON, + })); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'secretsmanager:GetSecretValue', + 'secretsmanager:DescribeSecret', + ], + Effect: 'Allow', + Resource: { + Ref: 'SecretA720EF05', + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'FnServiceRoleDefaultPolicyC6A839BF', + Roles: [ + { + Ref: 'FnServiceRoleB9001A96', + }, + ], + })); + + expect(stack).to(haveResource('AWS::Lambda::EventSourceMapping', { + FunctionName: { + Ref: 'Fn9270CBC0', + }, + BatchSize: 100, + SelfManagedEventSource: { + Endpoints: { + KafkaBootstrapServers: bootstrapServers, + }, + }, + StartingPosition: 'TRIM_HORIZON', + Topics: [ + kafkaTopic, + ], + SourceAccessConfigurations: [ + { + Type: 'SASL_SCRAM_512_AUTH', + URI: { + Ref: 'SecretA720EF05', + }, + }, + ], + })); + + test.done(); + }, + + VPC: { + 'correctly rendered in the stack'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const kafkaTopic = 'some-topic'; + const secret = new Secret(stack, 'Secret', { secretName: 'AmazonMSK_KafkaSecret' }); + const bootstrapServers = ['kafka-broker:9092']; + const sg = SecurityGroup.fromSecurityGroupId(stack, 'SecurityGroup', 'sg-0123456789'); + const vpc = new Vpc(stack, 'Vpc'); + + // WHEN + fn.addEventSource(new sources.SelfManagedKafkaEventSource( + { + bootstrapServers: bootstrapServers, + topic: kafkaTopic, + secret: secret, + startingPosition: lambda.StartingPosition.TRIM_HORIZON, + vpc: vpc, + vpcSubnets: { subnetType: SubnetType.PRIVATE }, + securityGroup: sg, + })); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'secretsmanager:GetSecretValue', + 'secretsmanager:DescribeSecret', + ], + Effect: 'Allow', + Resource: { + Ref: 'SecretA720EF05', + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'FnServiceRoleDefaultPolicyC6A839BF', + Roles: [ + { + Ref: 'FnServiceRoleB9001A96', + }, + ], + })); + + expect(stack).to(haveResource('AWS::Lambda::EventSourceMapping', { + FunctionName: { + Ref: 'Fn9270CBC0', + }, + BatchSize: 100, + SelfManagedEventSource: { + Endpoints: { + KafkaBootstrapServers: bootstrapServers, + }, + }, + StartingPosition: 'TRIM_HORIZON', + Topics: [ + kafkaTopic, + ], + SourceAccessConfigurations: [ + { + Type: 'SASL_SCRAM_512_AUTH', + URI: { + Ref: 'SecretA720EF05', + }, + }, + { + Type: 'VPC_SECURITY_GROUP', + URI: 'sg-0123456789', + }, + { + Type: 'VPC_SUBNET', + URI: { + Ref: 'VpcPrivateSubnet1Subnet536B997A', + }, + }, + { + Type: 'VPC_SUBNET', + URI: { + Ref: 'VpcPrivateSubnet2Subnet3788AAA1', + }, + }, + ], + })); + + test.done(); + }, + 'setting vpc requires vpcSubnets to be set'(test: Test) { + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const kafkaTopic = 'some-topic'; + const secret = new Secret(stack, 'Secret', { secretName: 'AmazonMSK_KafkaSecret' }); + const bootstrapServers = ['kafka-broker:9092']; + const vpc = new Vpc(stack, 'Vpc'); + + test.throws(() => { + fn.addEventSource(new sources.SelfManagedKafkaEventSource( + { + bootstrapServers: bootstrapServers, + topic: kafkaTopic, + secret: secret, + startingPosition: lambda.StartingPosition.TRIM_HORIZON, + vpc: vpc, + securityGroup: SecurityGroup.fromSecurityGroupId(stack, 'SecurityGroup', 'sg-0123456789'), + + })); + }, /vpcSubnets must be set/); + + test.done(); + }, + + 'setting vpc requires securityGroup to be set'(test: Test) { + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const kafkaTopic = 'some-topic'; + const secret = new Secret(stack, 'Secret', { secretName: 'AmazonMSK_KafkaSecret' }); + const bootstrapServers = ['kafka-broker:9092']; + const vpc = new Vpc(stack, 'Vpc'); + + test.throws(() => { + fn.addEventSource(new sources.SelfManagedKafkaEventSource( + { + bootstrapServers: bootstrapServers, + topic: kafkaTopic, + secret: secret, + startingPosition: lambda.StartingPosition.TRIM_HORIZON, + vpc: vpc, + vpcSubnets: { subnetType: SubnetType.PRIVATE }, + })); + }, /securityGroup must be set/); + + test.done(); + }, + }, + + 'using SCRAM-SHA-256'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const kafkaTopic = 'some-topic'; + const secret = new Secret(stack, 'Secret', { secretName: 'AmazonMSK_KafkaSecret' }); + const bootstrapServers = ['kafka-broker:9092']; + const sg = SecurityGroup.fromSecurityGroupId(stack, 'SecurityGroup', 'sg-0123456789'); + const vpc = new Vpc(stack, 'Vpc'); + + // WHEN + fn.addEventSource(new sources.SelfManagedKafkaEventSource( + { + bootstrapServers: bootstrapServers, + topic: kafkaTopic, + secret: secret, + startingPosition: lambda.StartingPosition.TRIM_HORIZON, + vpc: vpc, + vpcSubnets: { subnetType: SubnetType.PRIVATE }, + securityGroup: sg, + authenticationMethod: sources.AuthenticationMethod.SASL_SCRAM_256_AUTH, + })); + + expect(stack).to(haveResource('AWS::Lambda::EventSourceMapping', { + SourceAccessConfigurations: arrayWith( + { + Type: 'SASL_SCRAM_256_AUTH', + URI: { + Ref: 'SecretA720EF05', + }, + }, + ), + })); + + test.done(); + }, + }, + +} diff --git a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts index d44ce1cbea1b4..239bf58671b7e 100644 --- a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts +++ b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts @@ -4,12 +4,78 @@ import { IEventSourceDlq } from './dlq'; import { IFunction } from './function-base'; import { CfnEventSourceMapping } from './lambda.generated'; +/** + * The type of authentication protocol or the VPC components for your event source's SourceAccessConfiguration + * @see https://docs.aws.amazon.com/lambda/latest/dg/API_SourceAccessConfiguration.html#SSS-Type-SourceAccessConfiguration-Type + */ +export class SourceAccessConfigurationType { + + /** + * (MQ) The Secrets Manager secret that stores your broker credentials. + */ + public static readonly BASIC_AUTH = new SourceAccessConfigurationType('BASIC_AUTH'); + + /** + * The subnets associated with your VPC. Lambda connects to these subnets to fetch data from your Self-Managed Apache Kafka cluster. + */ + public static readonly VPC_SUBNET = new SourceAccessConfigurationType('VPC_SUBNET'); + + /** + * The VPC security group used to manage access to your Self-Managed Apache Kafka brokers. + */ + public static readonly VPC_SECURITY_GROUP = new SourceAccessConfigurationType('VPC_SECURITY_GROUP'); + + /** + * The Secrets Manager ARN of your secret key used for SASL SCRAM-256 authentication of your Self-Managed Apache Kafka brokers. + */ + public static readonly SASL_SCRAM_256_AUTH = new SourceAccessConfigurationType('SASL_SCRAM_256_AUTH'); + + /** + * The Secrets Manager ARN of your secret key used for SASL SCRAM-512 authentication of your Self-Managed Apache Kafka brokers. + */ + public static readonly SASL_SCRAM_512_AUTH = new SourceAccessConfigurationType('SASL_SCRAM_512_AUTH'); + + /** A custom source access configuration property */ + public static of(name: string): SourceAccessConfigurationType { + return new SourceAccessConfigurationType(name); + } + + /** + * The key to use in `SourceAccessConfigurationProperty.Type` property in CloudFormation + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-eventsourcemapping-sourceaccessconfiguration.html#cfn-lambda-eventsourcemapping-sourceaccessconfiguration-type + */ + public readonly type: string; + + private constructor(type: string) { + this.type = type; + } +} + +/** + * Specific settings like the authentication protocol or the VPC components to secure access to your event source. + */ +export interface SourceAccessConfiguration { + /** + * The type of authentication protocol or the VPC components for your event source. For example: "SASL_SCRAM_512_AUTH". + */ + readonly type: SourceAccessConfigurationType, + /** + * The value for your chosen configuration in type. + * For example: "URI": "arn:aws:secretsmanager:us-east-1:01234567890:secret:MyBrokerSecretName". + * The exact string depends on the type. + * @see SourceAccessConfigurationType + */ + readonly uri: string +} + export interface EventSourceMappingOptions { /** * The Amazon Resource Name (ARN) of the event source. Any record added to * this stream can invoke the Lambda function. + * + * @default - not set if using a self managed Kafka cluster, throws an error otherwise */ - readonly eventSourceArn: string; + readonly eventSourceArn?: string; /** * The largest number of records that AWS Lambda will retrieve from your event @@ -101,6 +167,23 @@ export interface EventSourceMappingOptions { * @default - no topic */ readonly kafkaTopic?: string; + + /** + * A list of host and port pairs that are the addresses of the Kafka brokers in a self managed "bootstrap" Kafka cluster + * that a Kafka client connects to initially to bootstrap itself. + * They are in the format `abc.example.com:9096`. + * + * @default - none + */ + readonly kafkaBootstrapServers?: string[] + + /** + * Specific settings like the authentication protocol or the VPC components to secure access to your event source. + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-eventsourcemapping-sourceaccessconfiguration.html + * + * @default - none + */ + readonly sourceAccessConfigurations?: SourceAccessConfiguration[] } /** @@ -154,6 +237,18 @@ export class EventSourceMapping extends cdk.Resource implements IEventSourceMapp constructor(scope: Construct, id: string, props: EventSourceMappingProps) { super(scope, id); + if (props.eventSourceArn == undefined && props.kafkaBootstrapServers == undefined) { + throw new Error('Either eventSourceArn or kafkaBootstrapServers must be set'); + } + + if (props.eventSourceArn !== undefined && props.kafkaBootstrapServers !== undefined) { + throw new Error('eventSourceArn and kafkaBootstrapServers are mutually exclusive'); + } + + if (props.kafkaBootstrapServers && (props.kafkaBootstrapServers?.length < 1)) { + throw new Error('kafkaBootStrapServers must not be empty if set'); + } + if (props.maxBatchingWindow && props.maxBatchingWindow.toSeconds() > 300) { throw new Error(`maxBatchingWindow cannot be over 300 seconds, got ${props.maxBatchingWindow.toSeconds()}`); } @@ -183,6 +278,11 @@ export class EventSourceMapping extends cdk.Resource implements IEventSourceMapp }; } + let selfManagedEventSource; + if (props.kafkaBootstrapServers) { + selfManagedEventSource = { endpoints: { kafkaBootstrapServers: props.kafkaBootstrapServers } }; + } + const cfnEventSourceMapping = new CfnEventSourceMapping(this, 'Resource', { batchSize: props.batchSize, bisectBatchOnFunctionError: props.bisectBatchOnError, @@ -196,6 +296,8 @@ export class EventSourceMapping extends cdk.Resource implements IEventSourceMapp maximumRetryAttempts: props.retryAttempts, parallelizationFactor: props.parallelizationFactor, topics: props.kafkaTopic !== undefined ? [props.kafkaTopic] : undefined, + sourceAccessConfigurations: props.sourceAccessConfigurations?.map((o) => {return { type: o.type.type, uri: o.uri };}), + selfManagedEventSource, }); this.eventSourceMappingId = cfnEventSourceMapping.ref; } diff --git a/packages/@aws-cdk/aws-lambda/test/event-source-mapping.test.ts b/packages/@aws-cdk/aws-lambda/test/event-source-mapping.test.ts index be42067f263f1..5d833a2d865c6 100644 --- a/packages/@aws-cdk/aws-lambda/test/event-source-mapping.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/event-source-mapping.test.ts @@ -170,4 +170,95 @@ describe('event source mapping', () => { }], }); }); + + test('throws if neither eventSourceArn nor kafkaBootstrapServers are set', () => { + const stack = new cdk.Stack(); + const fn = new Function(stack, 'fn', { + handler: 'index.handler', + code: Code.fromInline('exports.handler = ${handler.toString()}'), + runtime: Runtime.NODEJS_10_X, + }); + + expect(() => new EventSourceMapping(stack, 'test', { + target: fn, + })).toThrow(/Either eventSourceArn or kafkaBootstrapServers must be set/); + }); + + test('throws if both eventSourceArn and kafkaBootstrapServers are set', () => { + const stack = new cdk.Stack(); + const fn = new Function(stack, 'fn', { + handler: 'index.handler', + code: Code.fromInline('exports.handler = ${handler.toString()}'), + runtime: Runtime.NODEJS_10_X, + }); + + expect(() => new EventSourceMapping(stack, 'test', { + eventSourceArn: '', + kafkaBootstrapServers: [], + target: fn, + })).toThrow(/eventSourceArn and kafkaBootstrapServers are mutually exclusive/); + }); + + test('throws if both kafkaBootstrapServers is set but empty', () => { + const stack = new cdk.Stack(); + const fn = new Function(stack, 'fn', { + handler: 'index.handler', + code: Code.fromInline('exports.handler = ${handler.toString()}'), + runtime: Runtime.NODEJS_10_X, + }); + + expect(() => new EventSourceMapping(stack, 'test', { + kafkaBootstrapServers: [], + target: fn, + })).toThrow(/kafkaBootStrapServers must not be empty if set/); + }); + + test('eventSourceArn appears in stack', () => { + const stack = new cdk.Stack(); + const topicNameParam = new cdk.CfnParameter(stack, 'TopicNameParam', { + type: 'String', + }); + + const fn = new Function(stack, 'fn', { + handler: 'index.handler', + code: Code.fromInline('exports.handler = ${handler.toString()}'), + runtime: Runtime.NODEJS_10_X, + }); + + let eventSourceArn = 'some-arn'; + + new EventSourceMapping(stack, 'test', { + target: fn, + eventSourceArn: eventSourceArn, + kafkaTopic: topicNameParam.valueAsString, + }); + + expect(stack).toHaveResourceLike('AWS::Lambda::EventSourceMapping', { + EventSourceArn: eventSourceArn, + }); + }); + + test('kafkaBootstrapServers appears in stack', () => { + const stack = new cdk.Stack(); + const topicNameParam = new cdk.CfnParameter(stack, 'TopicNameParam', { + type: 'String', + }); + + const fn = new Function(stack, 'fn', { + handler: 'index.handler', + code: Code.fromInline('exports.handler = ${handler.toString()}'), + runtime: Runtime.NODEJS_10_X, + }); + + let kafkaBootstrapServers = ['kafka-broker.example.com:9092']; + new EventSourceMapping(stack, 'test', { + target: fn, + kafkaBootstrapServers: kafkaBootstrapServers, + kafkaTopic: topicNameParam.valueAsString, + }); + + expect(stack).toHaveResourceLike('AWS::Lambda::EventSourceMapping', { + SelfManagedEventSource: { Endpoints: { KafkaBootstrapServers: kafkaBootstrapServers } }, + }); + }); }); diff --git a/packages/@aws-cdk/aws-msk/README.md b/packages/@aws-cdk/aws-msk/README.md index 1de05861fc74f..51d93453f1eef 100644 --- a/packages/@aws-cdk/aws-msk/README.md +++ b/packages/@aws-cdk/aws-msk/README.md @@ -9,6 +9,14 @@ > > [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. + --- diff --git a/packages/@aws-cdk/aws-msk/lib/cluster.ts b/packages/@aws-cdk/aws-msk/lib/cluster.ts new file mode 100644 index 0000000000000..313bd3de5b107 --- /dev/null +++ b/packages/@aws-cdk/aws-msk/lib/cluster.ts @@ -0,0 +1,34 @@ +import { IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; + +/** + * Represents an MSK cluster + */ +export interface ICluster extends IResource { + /** + * the ARN of the MSK cluster + */ + readonly clusterArn: string; +} + +/** + * An MSK cluster + */ +export class Cluster { + /** + * Creates a Cluster construct that represents an existing MSK cluster. + * @param scope + * @param id + * @param clusterArn + */ + public static fromClusterArn(scope: Construct, id: string, clusterArn: string): ICluster { + class Imported extends Resource implements ICluster { + public readonly clusterArn: string; + constructor() { + super(scope, id); + this.clusterArn = clusterArn; + } + } + return new Imported(); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-msk/lib/index.ts b/packages/@aws-cdk/aws-msk/lib/index.ts index 3cf150cbd7076..9cc4acdc7d61f 100644 --- a/packages/@aws-cdk/aws-msk/lib/index.ts +++ b/packages/@aws-cdk/aws-msk/lib/index.ts @@ -1,2 +1,3 @@ +export * from './cluster'; // AWS::MSK CloudFormation Resources: export * from './msk.generated'; diff --git a/packages/@aws-cdk/aws-msk/package.json b/packages/@aws-cdk/aws-msk/package.json index 939b66619cda7..efa91da07d39f 100644 --- a/packages/@aws-cdk/aws-msk/package.json +++ b/packages/@aws-cdk/aws-msk/package.json @@ -90,7 +90,7 @@ "node": ">= 10.13.0 <13 || >=13.7.0" }, "stability": "experimental", - "maturity": "cfn-only", + "maturity": "experimental", "awscdkio": { "announce": false }, From a5be04270c2a372132964ab13d080a16f1a6f00c Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 16 Mar 2021 17:13:55 +0100 Subject: [PATCH 30/41] fix(core): `toJsonString()` cannot handle list intrinsics (#13544) The previous attempt at a fix missed one important case: the types of the values involved in the `{ Fn::Join }` expression didn't actually match up. They all needed to be strings, but the previous implentation just dropped list-typed values in there. Unfortunately, there is no way to do it correctly with just string manipulation in CloudFormation (`{ Fn::Join }` etc), so we'll have to resort to using a Custom Resource if we encounter list values. Actually fixes #13465. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../core/lib/private/cfn-utils-provider.ts | 33 ++++++++++++++++ .../lib/private/cfn-utils-provider/consts.ts | 7 +++- .../lib/private/cfn-utils-provider/index.ts | 11 ++++++ .../core/lib/private/cloudformation-lang.ts | 39 ++++++++++++++++--- .../core/test/cloudformation-json.test.ts | 6 ++- 5 files changed, 88 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/core/lib/private/cfn-utils-provider.ts b/packages/@aws-cdk/core/lib/private/cfn-utils-provider.ts index 8200165fbfe34..04a68394fe2f3 100644 --- a/packages/@aws-cdk/core/lib/private/cfn-utils-provider.ts +++ b/packages/@aws-cdk/core/lib/private/cfn-utils-provider.ts @@ -1,5 +1,7 @@ import { Construct } from '../construct-compat'; +import { CustomResource } from '../custom-resource'; import { CustomResourceProvider, CustomResourceProviderRuntime } from '../custom-resource-provider'; +import { CfnUtilsResourceType } from './cfn-utils-provider/consts'; /** * A custom resource provider for CFN utilities such as `CfnJson`. @@ -11,4 +13,35 @@ export class CfnUtilsProvider extends Construct { codeDirectory: `${__dirname}/cfn-utils-provider`, }); } +} + +/** + * Utility functions provided by the CfnUtilsProvider + */ +export abstract class CfnUtils { + /** + * Encode a structure to JSON at CloudFormation deployment time + * + * This would have been suitable for the JSON-encoding of abitrary structures, however: + * + * - It uses a custom resource to do the encoding, and we'd rather not use a custom + * resource if we can avoid it. + * - It cannot be used to encode objects where the keys of the objects can contain + * tokens--because those cannot be represented in the JSON encoding that CloudFormation + * templates use. + * + * This helper is used by `CloudFormationLang.toJSON()` if and only if it encounters + * objects that cannot be stringified any other way. + */ + public static stringify(scope: Construct, id: string, value: any): string { + const resource = new CustomResource(scope, id, { + serviceToken: CfnUtilsProvider.getOrCreate(scope), + resourceType: CfnUtilsResourceType.CFN_JSON_STRINGIFY, + properties: { + Value: value, + }, + }); + + return resource.getAttString('Value'); + } } \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/private/cfn-utils-provider/consts.ts b/packages/@aws-cdk/core/lib/private/cfn-utils-provider/consts.ts index b1571cabd5b42..9718dcef40645 100644 --- a/packages/@aws-cdk/core/lib/private/cfn-utils-provider/consts.ts +++ b/packages/@aws-cdk/core/lib/private/cfn-utils-provider/consts.ts @@ -5,5 +5,10 @@ export const enum CfnUtilsResourceType { /** * CfnJson */ - CFN_JSON = 'Custom::AWSCDKCfnJson' + CFN_JSON = 'Custom::AWSCDKCfnJson', + + /** + * CfnJsonStringify + */ + CFN_JSON_STRINGIFY = 'Custom::AWSCDKCfnJsonStringify', } diff --git a/packages/@aws-cdk/core/lib/private/cfn-utils-provider/index.ts b/packages/@aws-cdk/core/lib/private/cfn-utils-provider/index.ts index 87bd6bb070e16..f082001f80159 100644 --- a/packages/@aws-cdk/core/lib/private/cfn-utils-provider/index.ts +++ b/packages/@aws-cdk/core/lib/private/cfn-utils-provider/index.ts @@ -9,6 +9,9 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent if (event.ResourceType === CfnUtilsResourceType.CFN_JSON) { return cfnJsonHandler(event); } + if (event.ResourceType === CfnUtilsResourceType.CFN_JSON_STRINGIFY) { + return cfnJsonStringifyHandler(event); + } throw new Error(`unexpected resource type "${event.ResourceType}`); } @@ -20,3 +23,11 @@ function cfnJsonHandler(event: AWSLambda.CloudFormationCustomResourceEvent) { }, }; } + +function cfnJsonStringifyHandler(event: AWSLambda.CloudFormationCustomResourceEvent) { + return { + Data: { + Value: JSON.stringify(event.ResourceProperties.Value), + }, + }; +} diff --git a/packages/@aws-cdk/core/lib/private/cloudformation-lang.ts b/packages/@aws-cdk/core/lib/private/cloudformation-lang.ts index 310a4632f4e8f..c2be580474426 100644 --- a/packages/@aws-cdk/core/lib/private/cloudformation-lang.ts +++ b/packages/@aws-cdk/core/lib/private/cloudformation-lang.ts @@ -1,6 +1,8 @@ import { Lazy } from '../lazy'; import { DefaultTokenResolver, IFragmentConcatenator, IResolveContext } from '../resolvable'; +import { Stack } from '../stack'; import { Token } from '../token'; +import { CfnUtils } from './cfn-utils-provider'; import { INTRINSIC_KEY_PREFIX, ResolutionTypeHint, resolvedTypeHint } from './resolve'; /** @@ -170,7 +172,8 @@ function tokenAwareStringify(root: any, space: number, ctx: IResolveContext) { // AND it's the result of a token resolution. Otherwise, we just treat this // value as a regular old JSON object (that happens to look a lot like an intrinsic). if (isIntrinsic(obj) && resolvedTypeHint(obj)) { - return renderIntrinsic(obj); + renderIntrinsic(obj); + return; } return renderCollection('{', '}', definedEntries(obj), ([key, value]) => { @@ -211,12 +214,34 @@ function tokenAwareStringify(root: any, space: number, ctx: IResolveContext) { pushLiteral('"'); pushIntrinsic(deepQuoteStringLiterals(intrinsic)); pushLiteral('"'); - break; - - default: + return; + + case ResolutionTypeHint.LIST: + // We need this to look like: + // + // '{"listValue":' ++ STRINGIFY(CFN_EVAL({ Ref: MyList })) ++ '}' + // + // However, STRINGIFY would need to execute at CloudFormation deployment time, and that doesn't exist. + // + // We could *ALMOST* use: + // + // '{"listValue":["' ++ JOIN('","', { Ref: MyList }) ++ '"]}' + // + // But that has the unfortunate side effect that if `CFN_EVAL({ Ref: MyList }) == []`, then it would + // evaluate to `[""]`, which is a different value. Since CloudFormation does not have arbitrary + // conditionals there's no way to deal with this case properly. + // + // Therefore, if we encounter lists we need to defer to a custom resource to handle + // them properly at deploy time. + pushIntrinsic(CfnUtils.stringify(Stack.of(ctx.scope), `CdkJsonStringify${stringifyCounter++}`, intrinsic)); + return; + + case ResolutionTypeHint.NUMBER: pushIntrinsic(intrinsic); - break; + return; } + + throw new Error(`Unexpected type hint: ${resolvedTypeHint(intrinsic)}`); } /** @@ -391,4 +416,6 @@ function deepQuoteStringLiterals(x: any): any { function quoteString(s: string) { s = JSON.stringify(s); return s.substring(1, s.length - 1); -} \ No newline at end of file +} + +let stringifyCounter = 1; \ No newline at end of file diff --git a/packages/@aws-cdk/core/test/cloudformation-json.test.ts b/packages/@aws-cdk/core/test/cloudformation-json.test.ts index cb96020e04904..e9a0957a86269 100644 --- a/packages/@aws-cdk/core/test/cloudformation-json.test.ts +++ b/packages/@aws-cdk/core/test/cloudformation-json.test.ts @@ -103,7 +103,11 @@ describe('tokens that return literals', () => { // WHEN expect(stack.resolve(stack.toJsonString({ someList }))).toEqual({ - 'Fn::Join': ['', ['{"someList":', { Ref: 'Thing' }, '}']], + 'Fn::Join': ['', [ + '{"someList":', + { 'Fn::GetAtt': [expect.stringContaining('CdkJsonStringify'), 'Value'] }, + '}', + ]], }); }); From e8f6267de8f0915fa950c301f2131efad494a87a Mon Sep 17 00:00:00 2001 From: Romain Marcadier Date: Tue, 16 Mar 2021 18:31:30 +0100 Subject: [PATCH 31/41] chore: replace angle braces with HTML-safe entities (#13619) This is an attempt to make the documentation safer to parse and render. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-efs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-efs/README.md b/packages/@aws-cdk/aws-efs/README.md index 002ba355e51eb..444a3a69d7480 100644 --- a/packages/@aws-cdk/aws-efs/README.md +++ b/packages/@aws-cdk/aws-efs/README.md @@ -90,9 +90,9 @@ efs.AccessPoint.fromAccessPointAttributes(this, 'ap', { ⚠️ Notice: When importing an Access Point using `fromAccessPointAttributes()`, you must make sure the mount targets are deployed and their lifecycle state is `available`. Otherwise, you may encounter the following error when deploying: -> EFS file system referenced by access point has +> EFS file system <ARN of efs> referenced by access point <ARN of access point of EFS> has > mount targets created in all availability zones the function will execute in, but not all ->are in the available life cycle state yet. Please wait for them to become available and +> are in the available life cycle state yet. Please wait for them to become available and > try the request again. ### Connecting From aa32cf64d20e4ba1eb2bc8236daeb05e89e4c12d Mon Sep 17 00:00:00 2001 From: Eli Polonsky Date: Wed, 17 Mar 2021 00:16:23 +0200 Subject: [PATCH 32/41] fix(s3): Notifications fail to deploy due to incompatible node runtime (#13624) This [PR](https://github.com/aws/aws-cdk/pull/13488) upgraded our Node runtimes from `10` to `14`. The problem is that Node14 isn't supported for lambda functions using inline code (i.e `ZipFile`). Change to Node12 specifically for the notification handler since it's the only one using `InlineLambda`. Fixes https://github.com/aws/aws-cdk/issues/13620 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-lambda-event-sources/test/integ.s3.expected.json | 2 +- .../aws-s3-notifications/test/integ.notifications.expected.json | 2 +- .../test/lambda/integ.bucket-notifications.expected.json | 2 +- .../test/sns/integ.sns-bucket-notifications.expected.json | 2 +- .../test/sqs/integ.bucket-notifications.expected.json | 2 +- .../notifications-resource/notifications-resource-handler.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json index e4b5d64e2d04e..96f5a3e6a63d7 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json @@ -185,7 +185,7 @@ "Arn" ] }, - "Runtime": "nodejs14.x", + "Runtime": "nodejs12.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json index f0babb81fc0a6..f8b1a350486f9 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json @@ -220,7 +220,7 @@ "Arn" ] }, - "Runtime": "nodejs14.x", + "Runtime": "nodejs12.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json index 918c4a9d09334..8eb330f53a91e 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json @@ -244,7 +244,7 @@ "Arn" ] }, - "Runtime": "nodejs14.x", + "Runtime": "nodejs12.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json index ecdd9d831e74f..2cd8f17706a09 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json @@ -203,7 +203,7 @@ "Arn" ] }, - "Runtime": "nodejs14.x", + "Runtime": "nodejs12.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json index ea40221e82f15..0e8a6a56cfb6e 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json @@ -192,7 +192,7 @@ "Arn" ] }, - "Runtime": "nodejs14.x", + "Runtime": "nodejs12.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts index 8a47247be1b1e..b013bc0ebd5b8 100644 --- a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts +++ b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts @@ -82,7 +82,7 @@ export class NotificationsResourceHandler extends Construct { Code: { ZipFile: `exports.handler = ${handler.toString()};` }, Handler: 'index.handler', Role: role.roleArn, - Runtime: 'nodejs14.x', + Runtime: 'nodejs12.x', Timeout: 300, }, }); From 26bc3d4951a96a4bdf3e3e10464a4e3b80ed563f Mon Sep 17 00:00:00 2001 From: Eli Polonsky Date: Wed, 17 Mar 2021 00:16:23 +0200 Subject: [PATCH 33/41] fix(s3): Notifications fail to deploy due to incompatible node runtime (#13624) This [PR](https://github.com/aws/aws-cdk/pull/13488) upgraded our Node runtimes from `10` to `14`. The problem is that Node14 isn't supported for lambda functions using inline code (i.e `ZipFile`). Change to Node12 specifically for the notification handler since it's the only one using `InlineLambda`. Fixes https://github.com/aws/aws-cdk/issues/13620 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-lambda-event-sources/test/integ.s3.expected.json | 2 +- .../aws-s3-notifications/test/integ.notifications.expected.json | 2 +- .../test/lambda/integ.bucket-notifications.expected.json | 2 +- .../test/sns/integ.sns-bucket-notifications.expected.json | 2 +- .../test/sqs/integ.bucket-notifications.expected.json | 2 +- .../notifications-resource/notifications-resource-handler.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json index e4b5d64e2d04e..96f5a3e6a63d7 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json @@ -185,7 +185,7 @@ "Arn" ] }, - "Runtime": "nodejs14.x", + "Runtime": "nodejs12.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json index f0babb81fc0a6..f8b1a350486f9 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json @@ -220,7 +220,7 @@ "Arn" ] }, - "Runtime": "nodejs14.x", + "Runtime": "nodejs12.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json index 918c4a9d09334..8eb330f53a91e 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json @@ -244,7 +244,7 @@ "Arn" ] }, - "Runtime": "nodejs14.x", + "Runtime": "nodejs12.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json index ecdd9d831e74f..2cd8f17706a09 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json @@ -203,7 +203,7 @@ "Arn" ] }, - "Runtime": "nodejs14.x", + "Runtime": "nodejs12.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json index ea40221e82f15..0e8a6a56cfb6e 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json @@ -192,7 +192,7 @@ "Arn" ] }, - "Runtime": "nodejs14.x", + "Runtime": "nodejs12.x", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts index 8a47247be1b1e..b013bc0ebd5b8 100644 --- a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts +++ b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts @@ -82,7 +82,7 @@ export class NotificationsResourceHandler extends Construct { Code: { ZipFile: `exports.handler = ${handler.toString()};` }, Handler: 'index.handler', Role: role.roleArn, - Runtime: 'nodejs14.x', + Runtime: 'nodejs12.x', Timeout: 300, }, }); From 818d943d3f0f4ff3b33e1ebc0e8054a581197ce2 Mon Sep 17 00:00:00 2001 From: epolon Date: Wed, 17 Mar 2021 00:49:53 +0200 Subject: [PATCH 34/41] chore(release): 1.94.1 --- CHANGELOG.md | 7 +++++++ version.v1.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cc821cbde1c3..392f795c1771e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [1.94.1](https://github.com/aws/aws-cdk/compare/v1.94.0...v1.94.1) (2021-03-16) + + +### Bug Fixes + +* **s3:** Notifications fail to deploy due to incompatible node runtime ([#13624](https://github.com/aws/aws-cdk/issues/13624)) ([26bc3d4](https://github.com/aws/aws-cdk/commit/26bc3d4951a96a4bdf3e3e10464a4e3b80ed563f)) + ## [1.94.0](https://github.com/aws/aws-cdk/compare/v1.93.0...v1.94.0) (2021-03-16) diff --git a/version.v1.json b/version.v1.json index 3280de4395415..0a93d433950d0 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.94.0" + "version": "1.94.1" } From f3102ed2955bcebb639a0df109b8a9b99edaa066 Mon Sep 17 00:00:00 2001 From: epolon Date: Wed, 17 Mar 2021 00:51:26 +0200 Subject: [PATCH 35/41] fix changelog heading --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 392f795c1771e..a9fe72678f70a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. -### [1.94.1](https://github.com/aws/aws-cdk/compare/v1.94.0...v1.94.1) (2021-03-16) +## [1.94.1](https://github.com/aws/aws-cdk/compare/v1.94.0...v1.94.1) (2021-03-16) ### Bug Fixes @@ -77,7 +77,7 @@ All notable changes to this project will be documented in this file. See [standa ## [1.92.0](https://github.com/aws/aws-cdk/compare/v1.91.0...v1.92.0) (2021-03-06) -* **ecs-patterns**: the `desiredCount` property stored on the above constructs will be optional, allowing them to be undefined. This is enabled through the `@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount` feature flag. We would recommend all CDK users to set the `@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount` flag to `true` for all of their existing applications. +* **ecs-patterns**: the `desiredCount` property stored on the above constructs will be optional, allowing them to be undefined. This is enabled through the `@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount` feature flag. We would recommend all CDK users to set the `@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount` flag to `true` for all of their existing applications. ### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES From 3d651249d390ae22fadaaa3451e4e31eeb0ca1a7 Mon Sep 17 00:00:00 2001 From: epolon Date: Wed, 17 Mar 2021 00:51:53 +0200 Subject: [PATCH 36/41] Error instead of error --- scripts/bump.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bump.js b/scripts/bump.js index 5d8b4d63a62d0..bcbe8785c436b 100755 --- a/scripts/bump.js +++ b/scripts/bump.js @@ -11,7 +11,7 @@ const forTesting = process.env.BUMP_CANDIDATE || false; async function main() { if (releaseAs !== 'minor' && releaseAs !== 'patch') { - throw new error(`invalid bump type "${releaseAs}". only "minor" (the default) and "patch" are allowed. major version bumps require *slightly* more intention`); + throw new Error(`invalid bump type "${releaseAs}". only "minor" (the default) and "patch" are allowed. major version bumps require *slightly* more intention`); } console.error(`Starting ${releaseAs} version bump`); From e5297b148e81483b956461c2d349f1e68c34ed5e Mon Sep 17 00:00:00 2001 From: Hsing-Hui Hsu Date: Tue, 16 Mar 2021 17:10:46 -0700 Subject: [PATCH 37/41] docs(aws-ecs): fix typo in example for classic LB (#13627) Closes #13583 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-ecs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index cc4bd432e7dba..5ad2855c8f642 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -449,10 +449,10 @@ const service = new ecs.Ec2Service(this, 'Service', { /* ... */ }); const lb = new elb.LoadBalancer(stack, 'LB', { vpc }); lb.addListener({ externalPort: 80 }); -lb.addTarget(service.loadBalancerTarget{ +lb.addTarget(service.loadBalancerTarget({ containerName: 'MyContainer', containerPort: 80 -}); +})); ``` There are two higher-level constructs available which include a load balancer for you that can be found in the aws-ecs-patterns module: From 0d9c300f5244d3e5720832343830947f6cc5b352 Mon Sep 17 00:00:00 2001 From: Apoorv Munshi Date: Tue, 16 Mar 2021 17:48:06 -0700 Subject: [PATCH 38/41] feat(sns): enable passing PolicyDocument to TopicPolicy (#10559) Adds optional `policyDocument` prop to `TopicPolicyProps` to allow passing existing policy documents. fixes #7934 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-sns/README.md | 40 ++++++++++ packages/@aws-cdk/aws-sns/lib/policy.ts | 8 ++ packages/@aws-cdk/aws-sns/test/test.sns.ts | 91 ++++++++++++++++++++++ 3 files changed, 139 insertions(+) diff --git a/packages/@aws-cdk/aws-sns/README.md b/packages/@aws-cdk/aws-sns/README.md index b5d9f52c3d9b9..ca80f72f9aad3 100644 --- a/packages/@aws-cdk/aws-sns/README.md +++ b/packages/@aws-cdk/aws-sns/README.md @@ -131,3 +131,43 @@ codeCommitRepository.onCommit(new targets.SnsTopic(myTopic)); This will result in adding a target to the event rule and will also modify the topic resource policy to allow CloudWatch events to publish to the topic. + +## Topic Policy + +A topic policy is automatically created when `addToResourcePolicy` is called, if +one doesn't already exist. Using `addToResourcePolicy` is the simplest way to +add policies, but a `TopicPolicy` can also be created manually. + +```ts +const topic = new sns.Topic(stack, 'Topic'); +const topicPolicy = new sns.TopicPolicy(stack, 'TopicPolicy', { + topics: [topic], +}); + +topicPolicy.document.addStatements(new iam.PolicyStatement({ + actions: ["sns:Subscribe"], + principals: [new iam.AnyPrincipal()], + resources: [topic.topicArn], +})); +``` + +A policy document can also be passed on `TopicPolicy` construction + +```ts +const topic = new sns.Topic(stack, 'Topic'); +const policyDocument = new iam.PolicyDocument({ + assignSids: true, + statements: [ + new iam.PolicyStatement({ + actions: ["sns:Subscribe"], + principals: [new iam.AnyPrincipal()], + resources: [topic.topicArn] + }), + ], +}); + +const topicPolicy = new sns.TopicPolicy(this, 'Policy', { + topics: [topic], + policyDocument, +}); +``` diff --git a/packages/@aws-cdk/aws-sns/lib/policy.ts b/packages/@aws-cdk/aws-sns/lib/policy.ts index 7d93d863a75f0..03a791bd57814 100644 --- a/packages/@aws-cdk/aws-sns/lib/policy.ts +++ b/packages/@aws-cdk/aws-sns/lib/policy.ts @@ -12,6 +12,12 @@ export interface TopicPolicyProps { * The set of topics this policy applies to. */ readonly topics: ITopic[]; + /** + * IAM policy document to apply to topic(s). + * @default empty policy document + */ + readonly policyDocument?: PolicyDocument; + } /** @@ -32,6 +38,8 @@ export class TopicPolicy extends Resource { constructor(scope: Construct, id: string, props: TopicPolicyProps) { super(scope, id); + this.document = props.policyDocument ?? this.document; + new CfnTopicPolicy(this, 'Resource', { policyDocument: this.document, topics: props.topics.map(t => t.topicArn), diff --git a/packages/@aws-cdk/aws-sns/test/test.sns.ts b/packages/@aws-cdk/aws-sns/test/test.sns.ts index cc4b50aed717c..5261900f9059b 100644 --- a/packages/@aws-cdk/aws-sns/test/test.sns.ts +++ b/packages/@aws-cdk/aws-sns/test/test.sns.ts @@ -276,6 +276,97 @@ export = { test.done(); }, + 'TopicPolicy passed document'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'MyTopic'); + const ps = new iam.PolicyStatement({ + actions: ['service:statement0'], + principals: [new iam.ArnPrincipal('arn')], + }); + + // WHEN + new sns.TopicPolicy(stack, 'topicpolicy', { topics: [topic], policyDocument: new iam.PolicyDocument({ assignSids: true, statements: [ps] }) }); + + // THEN + expect(stack).toMatch({ + 'Resources': { + 'MyTopic86869434': { + 'Type': 'AWS::SNS::Topic', + }, + 'topicpolicyF8CF12FD': { + 'Type': 'AWS::SNS::TopicPolicy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': 'service:statement0', + 'Effect': 'Allow', + 'Principal': { 'AWS': 'arn' }, + 'Sid': '0', + }, + ], + 'Version': '2012-10-17', + }, + 'Topics': [ + { + 'Ref': 'MyTopic86869434', + }, + ], + }, + }, + }, + }); + + test.done(); + }, + + 'Add statements to policy'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'MyTopic'); + + // WHEN + const topicPolicy = new sns.TopicPolicy(stack, 'TopicPolicy', { + topics: [topic], + }); + topicPolicy.document.addStatements(new iam.PolicyStatement({ + actions: ['service:statement0'], + principals: [new iam.ArnPrincipal('arn')], + })); + + // THEN + expect(stack).toMatch({ + 'Resources': { + 'MyTopic86869434': { + 'Type': 'AWS::SNS::Topic', + }, + 'TopicPolicyA24B096F': { + 'Type': 'AWS::SNS::TopicPolicy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': 'service:statement0', + 'Effect': 'Allow', + 'Principal': { 'AWS': 'arn' }, + 'Sid': '0', + }, + ], + 'Version': '2012-10-17', + }, + 'Topics': [ + { + 'Ref': 'MyTopic86869434', + }, + ], + }, + }, + }, + }); + test.done(); + }, + 'topic resource policy includes unique SIDs'(test: Test) { const stack = new cdk.Stack(); From 015ba4991002dbc70d32652b7ded8279ac29f8ee Mon Sep 17 00:00:00 2001 From: iliapolo Date: Wed, 17 Mar 2021 20:45:05 +0000 Subject: [PATCH 39/41] automatic pkglint fixes --- packages/@aws-cdk/aws-s3outposts/package.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-s3outposts/package.json b/packages/@aws-cdk/aws-s3outposts/package.json index 7965518ea1d24..4c2236ec82a9c 100644 --- a/packages/@aws-cdk/aws-s3outposts/package.json +++ b/packages/@aws-cdk/aws-s3outposts/package.json @@ -81,10 +81,12 @@ "pkglint": "0.0.0" }, "dependencies": { - "@aws-cdk/core": "0.0.0" + "@aws-cdk/core": "0.0.0", + "constructs": "10.0.0-pre.5" }, "peerDependencies": { - "@aws-cdk/core": "0.0.0" + "@aws-cdk/core": "0.0.0", + "constructs": "10.0.0-pre.5" }, "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" @@ -95,6 +97,7 @@ "announce": false }, "publishConfig": { - "tag": "latest" - } + "tag": "next" + }, + "private": true } From 52cd1b37b147600085a862213fd8674b1759afc0 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Thu, 18 Mar 2021 10:39:53 +0000 Subject: [PATCH 40/41] chore(core): fix runtime-info tests and imports for v2 --- .../@aws-cdk/core/lib/private/runtime-info.ts | 2 +- .../core/test/metadata-resource.test.ts | 3 +- .../@aws-cdk/core/test/runtime-info.test.ts | 55 ++++++------------- 3 files changed, 20 insertions(+), 40 deletions(-) diff --git a/packages/@aws-cdk/core/lib/private/runtime-info.ts b/packages/@aws-cdk/core/lib/private/runtime-info.ts index da4fbdcbe99d8..09962305a152f 100644 --- a/packages/@aws-cdk/core/lib/private/runtime-info.ts +++ b/packages/@aws-cdk/core/lib/private/runtime-info.ts @@ -1,4 +1,4 @@ -import { IConstruct } from '../construct-compat'; +import { IConstruct } from 'constructs'; import { Stack } from '../stack'; import { Stage } from '../stage'; diff --git a/packages/@aws-cdk/core/test/metadata-resource.test.ts b/packages/@aws-cdk/core/test/metadata-resource.test.ts index 00869746b1e25..1fc7d1e80fe19 100644 --- a/packages/@aws-cdk/core/test/metadata-resource.test.ts +++ b/packages/@aws-cdk/core/test/metadata-resource.test.ts @@ -1,9 +1,8 @@ import * as zlib from 'zlib'; +import { Construct } from 'constructs'; import { App, Stack } from '../lib'; import { formatAnalytics } from '../lib/private/metadata-resource'; -// eslint-disable-next-line no-duplicate-imports, import/order -import { Construct } from '../lib'; import { ConstructInfo } from '../lib/private/runtime-info'; describe('MetadataResource', () => { diff --git a/packages/@aws-cdk/core/test/runtime-info.test.ts b/packages/@aws-cdk/core/test/runtime-info.test.ts index 7da4f78d74b46..43f542e6e84cd 100644 --- a/packages/@aws-cdk/core/test/runtime-info.test.ts +++ b/packages/@aws-cdk/core/test/runtime-info.test.ts @@ -1,16 +1,13 @@ import * as path from 'path'; +import { Construct } from 'constructs'; import { App, NestedStack, Stack, Stage } from '../lib'; import { constructInfoFromConstruct, constructInfoFromStack } from '../lib/private/runtime-info'; -// eslint-disable-next-line no-duplicate-imports, import/order -import { Construct } from '../lib'; - const JSII_RUNTIME_SYMBOL = Symbol.for('jsii.rtti'); let app: App; let stack: Stack; let _cdkVersion: string | undefined = undefined; -const modulePrefix = cdkMajorVersion() === 1 ? '@aws-cdk/core' : 'aws-cdk-lib'; // The runtime metadata this test relies on is only available if the most // recent compile has happened using 'jsii', as the jsii compiler injects @@ -40,24 +37,15 @@ describeTscSafe('constructInfoFromConstruct', () => { test('returns fqn and version for core constructs', () => { const constructInfo = constructInfoFromConstruct(stack); expect(constructInfo).toBeDefined(); - expect(constructInfo?.fqn).toEqual(`${modulePrefix}.Stack`); + expect(constructInfo?.fqn).toEqual('@aws-cdk/core.Stack'); expect(constructInfo?.version).toEqual(localCdkVersion()); }); - test('returns base construct info if no more specific info is present', () => { - const simpleConstruct = new class extends Construct { }(stack, 'Simple'); - const constructInfo = constructInfoFromConstruct(simpleConstruct); - expect(constructInfo?.fqn).toEqual(`${modulePrefix}.Construct`); - }); - - test('returns more specific subclass info if present', () => { - const construct = new class extends Construct { - // @ts-ignore - private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: 'aws-cdk-lib.TestConstruct', version: localCdkVersion() } - }(stack, 'TestConstruct'); + test('returns jsii runtime info if present', () => { + const construct = new TestConstruct(stack, 'TestConstruct'); const constructInfo = constructInfoFromConstruct(construct); - expect(constructInfo?.fqn).toEqual('aws-cdk-lib.TestConstruct'); + expect(constructInfo?.fqn).toEqual('@aws-cdk/test.TestConstruct'); }); test('throws if the jsii runtime info is not as expected', () => { @@ -89,45 +77,41 @@ describeTscSafe('constructInfoForStack', () => { const stackInfo = constructInfos.find(i => /Stack/.test(i.fqn)); const jsiiInfo = constructInfos.find(i => i.fqn === 'jsii-runtime.Runtime'); - expect(stackInfo?.fqn).toEqual(`${modulePrefix}.Stack`); + expect(stackInfo?.fqn).toEqual('@aws-cdk/core.Stack'); expect(stackInfo?.version).toEqual(localCdkVersion()); expect(jsiiInfo?.version).toMatch(/node.js/); }); test('returns info for constructs added to the stack', () => { - new class extends Construct { }(stack, 'Simple'); + new TestConstruct(stack, 'TestConstruct'); const constructInfos = constructInfoFromStack(stack); expect(constructInfos.length).toEqual(3); - expect(constructInfos.map(info => info.fqn)).toContain(`${modulePrefix}.Construct`); + expect(constructInfos.map(info => info.fqn)).toContain('@aws-cdk/test.TestConstruct'); }); test('returns unique info (no duplicates)', () => { - new class extends Construct { }(stack, 'Simple1'); - new class extends Construct { }(stack, 'Simple2'); + new TestConstruct(stack, 'TestConstruct1'); + new TestConstruct(stack, 'TestConstruct2'); const constructInfos = constructInfoFromStack(stack); expect(constructInfos.length).toEqual(3); - expect(constructInfos.map(info => info.fqn)).toContain(`${modulePrefix}.Construct`); + expect(constructInfos.map(info => info.fqn)).toContain('@aws-cdk/test.TestConstruct'); }); test('returns info from nested constructs', () => { new class extends Construct { constructor(scope: Construct, id: string) { super(scope, id); - return new class extends Construct { - // @ts-ignore - private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: '@aws-cdk/test.TestV1Construct', version: localCdkVersion() } - }(this, 'TestConstruct'); + new TestConstruct(this, 'TestConstruct'); } }(stack, 'Nested'); const constructInfos = constructInfoFromStack(stack); - expect(constructInfos.length).toEqual(4); - expect(constructInfos.map(info => info.fqn)).toContain('@aws-cdk/test.TestV1Construct'); + expect(constructInfos.map(info => info.fqn)).toContain('@aws-cdk/test.TestConstruct'); }); test('does not return info from nested stacks', () => { @@ -135,10 +119,7 @@ describeTscSafe('constructInfoForStack', () => { constructor(scope: Construct, id: string) { super(scope, id); - new class extends Construct { - // @ts-ignore - private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: '@aws-cdk/test.TestV1Construct', version: localCdkVersion() } - }(this, 'TestConstruct'); + new TestConstruct(this, 'TestConstruct'); new class extends Stack { // @ts-ignore @@ -160,16 +141,16 @@ describeTscSafe('constructInfoForStack', () => { const constructInfos = constructInfoFromStack(stack); const fqns = constructInfos.map(info => info.fqn); - expect(fqns).toContain('@aws-cdk/test.TestV1Construct'); + expect(fqns).toContain('@aws-cdk/test.TestConstruct'); expect(fqns).not.toContain('@aws-cdk/test.TestStackInsideStack'); expect(fqns).not.toContain('@aws-cdk/test.TestNestedStackInsideStack'); expect(fqns).not.toContain('@aws-cdk/test.TestStageInsideStack'); }); }); -function cdkMajorVersion(): number { - // eslint-disable-next-line @typescript-eslint/no-require-imports - return require('../../../../release.json').majorVersion; +class TestConstruct extends Construct { + // @ts-ignore + private static readonly [JSII_RUNTIME_SYMBOL] = { fqn: '@aws-cdk/test.TestConstruct', version: localCdkVersion() } } /** From b866d09af1f367d510d21d6097ca11ad55c068dc Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Thu, 18 Mar 2021 11:30:35 +0000 Subject: [PATCH 41/41] another import fix --- packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts index f0782964d93ce..56e6b2a64ce26 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts @@ -5,12 +5,9 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as msk from '@aws-cdk/aws-msk'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import { Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { StreamEventSource, StreamEventSourceProps } from './stream'; -// 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'; - /** * Properties for a Kafka event source */ @@ -147,7 +144,7 @@ export class SelfManagedKafkaEventSource extends StreamEventSource { } public bind(target: lambda.IFunction) { - if (!Construct.isConstruct(target)) { throw new Error('Function is not a construct. Unexpected error.'); } + if (!(target instanceof Construct)) { throw new Error('Function is not a construct. Unexpected error.'); } target.addEventSourceMapping( this.mappingId(target), this.enrichMappingOptions({