From 271d948883c7b26d2afd773ae1b3b05478bb6abd Mon Sep 17 00:00:00 2001 From: AWS CDK Automation <43080478+aws-cdk-automation@users.noreply.github.com> Date: Thu, 10 Jun 2021 18:22:51 +0200 Subject: [PATCH 01/34] feat(cfnspec): cloudformation spec v38.0.0 (#15044) * feat: cloudformation spec v38.0.0 * chore: removing outdated cfn spec patch * chore: ignore new attribute from cfnspec update * automatic pkglint fixes Co-authored-by: AWS CDK Team Co-authored-by: Nick Lynch Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- packages/@aws-cdk/aws-cloudfront/package.json | 3 +- packages/@aws-cdk/aws-location/.eslintrc.js | 3 + packages/@aws-cdk/aws-location/.gitignore | 19 + packages/@aws-cdk/aws-location/.npmignore | 28 + packages/@aws-cdk/aws-location/LICENSE | 201 +++++ packages/@aws-cdk/aws-location/NOTICE | 2 + packages/@aws-cdk/aws-location/README.md | 20 + packages/@aws-cdk/aws-location/jest.config.js | 2 + packages/@aws-cdk/aws-location/lib/index.ts | 2 + packages/@aws-cdk/aws-location/package.json | 103 +++ .../aws-location/test/location.test.ts | 6 + packages/@aws-cdk/cfnspec/CHANGELOG.md | 452 ++++++++++ packages/@aws-cdk/cfnspec/cfn.version | 2 +- ...0_CloudFormationResourceSpecification.json | 779 ++++++++++++++---- .../540_SSM_Association_Parameters_patch.json | 21 - .../cloudformation-include/package.json | 2 + packages/aws-cdk-lib/package.json | 1 + packages/decdk/package.json | 1 + packages/monocdk/package.json | 1 + 19 files changed, 1462 insertions(+), 186 deletions(-) create mode 100644 packages/@aws-cdk/aws-location/.eslintrc.js create mode 100644 packages/@aws-cdk/aws-location/.gitignore create mode 100644 packages/@aws-cdk/aws-location/.npmignore create mode 100644 packages/@aws-cdk/aws-location/LICENSE create mode 100644 packages/@aws-cdk/aws-location/NOTICE create mode 100644 packages/@aws-cdk/aws-location/README.md create mode 100644 packages/@aws-cdk/aws-location/jest.config.js create mode 100644 packages/@aws-cdk/aws-location/lib/index.ts create mode 100644 packages/@aws-cdk/aws-location/package.json create mode 100644 packages/@aws-cdk/aws-location/test/location.test.ts delete mode 100644 packages/@aws-cdk/cfnspec/spec-source/540_SSM_Association_Parameters_patch.json diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index 7ae097b628922..6a50b35ccd349 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -157,7 +157,8 @@ "resource-attribute:@aws-cdk/aws-cloudfront.OriginRequestPolicy.originRequestPolicyLastModifiedTime", "resource-attribute:@aws-cdk/aws-cloudfront.KeyGroup.keyGroupLastModifiedTime", "resource-attribute:@aws-cdk/aws-cloudfront.PublicKey.publicKeyCreatedTime", - "resource-attribute:@aws-cdk/aws-cloudfront.OriginAccessIdentity.cloudFrontOriginAccessIdentityId" + "resource-attribute:@aws-cdk/aws-cloudfront.OriginAccessIdentity.cloudFrontOriginAccessIdentityId", + "resource-attribute:@aws-cdk/aws-cloudfront.Function.functionMetadataFunctionArn" ] }, "awscdkio": { diff --git a/packages/@aws-cdk/aws-location/.eslintrc.js b/packages/@aws-cdk/aws-location/.eslintrc.js new file mode 100644 index 0000000000000..61dd8dd001f63 --- /dev/null +++ b/packages/@aws-cdk/aws-location/.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-location/.gitignore b/packages/@aws-cdk/aws-location/.gitignore new file mode 100644 index 0000000000000..62ebc95d75ce6 --- /dev/null +++ b/packages/@aws-cdk/aws-location/.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-location/.npmignore b/packages/@aws-cdk/aws-location/.npmignore new file mode 100644 index 0000000000000..e4486030fcb17 --- /dev/null +++ b/packages/@aws-cdk/aws-location/.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-location/LICENSE b/packages/@aws-cdk/aws-location/LICENSE new file mode 100644 index 0000000000000..28e4bdcec77ec --- /dev/null +++ b/packages/@aws-cdk/aws-location/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-location/NOTICE b/packages/@aws-cdk/aws-location/NOTICE new file mode 100644 index 0000000000000..5fc3826926b5b --- /dev/null +++ b/packages/@aws-cdk/aws-location/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-location/README.md b/packages/@aws-cdk/aws-location/README.md new file mode 100644 index 0000000000000..de1c9d9a20e6a --- /dev/null +++ b/packages/@aws-cdk/aws-location/README.md @@ -0,0 +1,20 @@ +# AWS::Location 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 location = require('@aws-cdk/aws-location'); +``` diff --git a/packages/@aws-cdk/aws-location/jest.config.js b/packages/@aws-cdk/aws-location/jest.config.js new file mode 100644 index 0000000000000..54e28beb9798b --- /dev/null +++ b/packages/@aws-cdk/aws-location/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-location/lib/index.ts b/packages/@aws-cdk/aws-location/lib/index.ts new file mode 100644 index 0000000000000..83c72abef42d3 --- /dev/null +++ b/packages/@aws-cdk/aws-location/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::Location CloudFormation Resources: +export * from './location.generated'; diff --git a/packages/@aws-cdk/aws-location/package.json b/packages/@aws-cdk/aws-location/package.json new file mode 100644 index 0000000000000..14524818b5118 --- /dev/null +++ b/packages/@aws-cdk/aws-location/package.json @@ -0,0 +1,103 @@ +{ + "name": "@aws-cdk/aws-location", + "version": "0.0.0", + "description": "The CDK Construct Library for AWS::Location", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "projectReferences": true, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.Location", + "packageId": "Amazon.CDK.AWS.Location", + "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.location", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "location" + } + }, + "python": { + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ], + "distName": "aws-cdk.aws-location", + "module": "aws_cdk.aws_location" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-location" + }, + "homepage": "https://github.com/aws/aws-cdk", + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts", + "build+test": "yarn build && yarn test", + "build+test+package": "yarn build+test && yarn package", + "compat": "cdk-compat", + "gen": "cfn2ts", + "rosetta:extract": "yarn --silent jsii-rosetta extract", + "build+extract": "yarn build && yarn rosetta:extract", + "build+test+extract": "yarn build+test && yarn rosetta:extract" + }, + "cdk-build": { + "cloudformation": "AWS::Location", + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "AWS::Location", + "aws-location" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/jest": "^26.0.22", + "@aws-cdk/assert-internal": "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-location/test/location.test.ts b/packages/@aws-cdk/aws-location/test/location.test.ts new file mode 100644 index 0000000000000..c4505ad966984 --- /dev/null +++ b/packages/@aws-cdk/aws-location/test/location.test.ts @@ -0,0 +1,6 @@ +import '@aws-cdk/assert-internal/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 2889a32cbcd2e..216f3c2f975e0 100644 --- a/packages/@aws-cdk/cfnspec/CHANGELOG.md +++ b/packages/@aws-cdk/cfnspec/CHANGELOG.md @@ -1,3 +1,455 @@ +# CloudFormation Resource Specification v38.0.0 + +## New Resource Types + +* AWS::Location::GeofenceCollection +* AWS::Location::Map +* AWS::Location::PlaceIndex +* AWS::Location::RouteCalculator +* AWS::Location::Tracker +* AWS::Location::TrackerConsumer + +## Attribute Changes + +* AWS::Athena::WorkGroup EffectiveEngineVersion (__deleted__) +* AWS::Athena::WorkGroup WorkGroupConfiguration.EngineVersion.EffectiveEngineVersion (__added__) +* AWS::CloudFront::Function FunctionMetadata.FunctionARN (__added__) +* AWS::DAX::Cluster ClusterDiscoveryEndpointURL (__added__) +* AWS::EC2::EC2Fleet FleetId (__added__) +* AWS::EC2::SpotFleet Id (__added__) +* AWS::FSx::FileSystem DNSName (__added__) +* AWS::FraudDetector::Detector EventType.Arn (__added__) +* AWS::FraudDetector::Detector EventType.CreatedTime (__added__) +* AWS::FraudDetector::Detector EventType.LastUpdatedTime (__added__) +* AWS::IoTWireless::ServiceProfile ChannelMask (__deleted__) +* AWS::IoTWireless::ServiceProfile DevStatusReqFreq (__deleted__) +* AWS::IoTWireless::ServiceProfile DlBucketSize (__deleted__) +* AWS::IoTWireless::ServiceProfile DlRate (__deleted__) +* AWS::IoTWireless::ServiceProfile DlRatePolicy (__deleted__) +* AWS::IoTWireless::ServiceProfile DrMax (__deleted__) +* AWS::IoTWireless::ServiceProfile DrMin (__deleted__) +* AWS::IoTWireless::ServiceProfile HrAllowed (__deleted__) +* AWS::IoTWireless::ServiceProfile MinGwDiversity (__deleted__) +* AWS::IoTWireless::ServiceProfile NwkGeoLoc (__deleted__) +* AWS::IoTWireless::ServiceProfile PrAllowed (__deleted__) +* AWS::IoTWireless::ServiceProfile RaAllowed (__deleted__) +* AWS::IoTWireless::ServiceProfile ReportDevStatusBattery (__deleted__) +* AWS::IoTWireless::ServiceProfile ReportDevStatusMargin (__deleted__) +* AWS::IoTWireless::ServiceProfile TargetPer (__deleted__) +* AWS::IoTWireless::ServiceProfile UlBucketSize (__deleted__) +* AWS::IoTWireless::ServiceProfile UlRate (__deleted__) +* AWS::IoTWireless::ServiceProfile UlRatePolicy (__deleted__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.ChannelMask (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.DevStatusReqFreq (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.DlBucketSize (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.DlRate (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.DlRatePolicy (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.DrMax (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.DrMin (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.HrAllowed (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.MinGwDiversity (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.NwkGeoLoc (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.PrAllowed (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.RaAllowed (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.ReportDevStatusBattery (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.ReportDevStatusMargin (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.TargetPer (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.UlBucketSize (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.UlRate (__added__) +* AWS::IoTWireless::ServiceProfile LoRaWAN.UlRatePolicy (__added__) +* AWS::MWAA::Environment CloudWatchLogGroupArn (__deleted__) +* AWS::MWAA::Environment LoggingConfiguration.DagProcessingLogs.CloudWatchLogGroupArn (__added__) +* AWS::MediaConnect::Flow IngestIp (__deleted__) +* AWS::MediaConnect::Flow SourceArn (__deleted__) +* AWS::MediaConnect::Flow Source.IngestIp (__added__) +* AWS::MediaConnect::Flow Source.SourceArn (__added__) +* AWS::S3::StorageLens StorageLensArn (__deleted__) +* AWS::S3::StorageLens StorageLensConfiguration.StorageLensArn (__added__) + +## Property Changes + +* AWS::ApiGatewayV2::Authorizer IdentitySource.Required (__changed__) + * Old: true + * New: false +* AWS::DAX::Cluster ClusterEndpointEncryptionType (__added__) +* AWS::EC2::EC2Fleet LaunchTemplateConfigs.DuplicatesAllowed (__added__) +* AWS::EC2::EC2Fleet TagSpecifications.DuplicatesAllowed (__added__) +* AWS::EC2::SpotFleet SpotFleetRequestConfigData.UpdateType (__changed__) + * Old: Conditional + * New: Mutable +* AWS::KinesisAnalyticsV2::Application ApplicationMode (__added__) +* AWS::SSM::Association Parameters.ItemType (__deleted__) +* AWS::SSM::Association Parameters.PrimitiveItemType (__added__) + +## Property Type Changes + +* AWS::KinesisAnalyticsV2::Application.CatalogConfiguration (__added__) +* AWS::KinesisAnalyticsV2::Application.CustomArtifactConfiguration (__added__) +* AWS::KinesisAnalyticsV2::Application.CustomArtifactsConfiguration (__added__) +* AWS::KinesisAnalyticsV2::Application.DeployAsApplicationConfiguration (__added__) +* AWS::KinesisAnalyticsV2::Application.GlueDataCatalogConfiguration (__added__) +* AWS::KinesisAnalyticsV2::Application.MavenReference (__added__) +* AWS::KinesisAnalyticsV2::Application.S3ContentBaseLocation (__added__) +* AWS::KinesisAnalyticsV2::Application.ZeppelinApplicationConfiguration (__added__) +* AWS::KinesisAnalyticsV2::Application.ZeppelinMonitoringConfiguration (__added__) +* AWS::EC2::EC2Fleet.FleetLaunchTemplateConfigRequest Overrides.DuplicatesAllowed (__added__) +* AWS::EC2::EC2Fleet.TagSpecification Tags.DuplicatesAllowed (__added__) +* AWS::EC2::SpotFleet.BlockDeviceMapping DeviceName.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings.html#cfn-ec2-spotfleet-blockdevicemapping-devicename + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-blockdevicemapping.html#cfn-ec2-spotfleet-blockdevicemapping-devicename +* AWS::EC2::SpotFleet.BlockDeviceMapping DeviceName.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.BlockDeviceMapping Ebs.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings.html#cfn-ec2-spotfleet-blockdevicemapping-ebs + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-blockdevicemapping.html#cfn-ec2-spotfleet-blockdevicemapping-ebs +* AWS::EC2::SpotFleet.BlockDeviceMapping Ebs.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.BlockDeviceMapping NoDevice.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings.html#cfn-ec2-spotfleet-blockdevicemapping-nodevice + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-blockdevicemapping.html#cfn-ec2-spotfleet-blockdevicemapping-nodevice +* AWS::EC2::SpotFleet.BlockDeviceMapping NoDevice.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.BlockDeviceMapping VirtualName.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings.html#cfn-ec2-spotfleet-blockdevicemapping-virtualname + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-blockdevicemapping.html#cfn-ec2-spotfleet-blockdevicemapping-virtualname +* AWS::EC2::SpotFleet.BlockDeviceMapping VirtualName.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.ClassicLoadBalancer Name.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.ClassicLoadBalancersConfig ClassicLoadBalancers.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.EbsBlockDevice DeleteOnTermination.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings-ebs.html#cfn-ec2-spotfleet-ebsblockdevice-deleteontermination + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-ebsblockdevice.html#cfn-ec2-spotfleet-ebsblockdevice-deleteontermination +* AWS::EC2::SpotFleet.EbsBlockDevice DeleteOnTermination.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.EbsBlockDevice Encrypted.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings-ebs.html#cfn-ec2-spotfleet-ebsblockdevice-encrypted + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-ebsblockdevice.html#cfn-ec2-spotfleet-ebsblockdevice-encrypted +* AWS::EC2::SpotFleet.EbsBlockDevice Encrypted.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.EbsBlockDevice Iops.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings-ebs.html#cfn-ec2-spotfleet-ebsblockdevice-iops + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-ebsblockdevice.html#cfn-ec2-spotfleet-ebsblockdevice-iops +* AWS::EC2::SpotFleet.EbsBlockDevice Iops.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.EbsBlockDevice SnapshotId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings-ebs.html#cfn-ec2-spotfleet-ebsblockdevice-snapshotid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-ebsblockdevice.html#cfn-ec2-spotfleet-ebsblockdevice-snapshotid +* AWS::EC2::SpotFleet.EbsBlockDevice SnapshotId.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.EbsBlockDevice VolumeSize.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings-ebs.html#cfn-ec2-spotfleet-ebsblockdevice-volumesize + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-ebsblockdevice.html#cfn-ec2-spotfleet-ebsblockdevice-volumesize +* AWS::EC2::SpotFleet.EbsBlockDevice VolumeSize.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.EbsBlockDevice VolumeType.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings-ebs.html#cfn-ec2-spotfleet-ebsblockdevice-volumetype + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-ebsblockdevice.html#cfn-ec2-spotfleet-ebsblockdevice-volumetype +* AWS::EC2::SpotFleet.EbsBlockDevice VolumeType.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.FleetLaunchTemplateSpecification LaunchTemplateId.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.FleetLaunchTemplateSpecification LaunchTemplateName.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.FleetLaunchTemplateSpecification Version.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.GroupIdentifier GroupId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-securitygroups.html#cfn-ec2-spotfleet-groupidentifier-groupid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-groupidentifier.html#cfn-ec2-spotfleet-groupidentifier-groupid +* AWS::EC2::SpotFleet.GroupIdentifier GroupId.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.IamInstanceProfileSpecification Arn.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-iaminstanceprofile.html#cfn-ec2-spotfleet-iaminstanceprofilespecification-arn + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-iaminstanceprofilespecification.html#cfn-ec2-spotfleet-iaminstanceprofilespecification-arn +* AWS::EC2::SpotFleet.IamInstanceProfileSpecification Arn.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.InstanceIpv6Address Ipv6Address.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification AssociatePublicIpAddress.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-associatepublicipaddress + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-associatepublicipaddress +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification AssociatePublicIpAddress.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification DeleteOnTermination.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-deleteontermination + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-deleteontermination +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification DeleteOnTermination.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification Description.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-description + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-description +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification Description.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification DeviceIndex.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-deviceindex + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-deviceindex +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification DeviceIndex.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification Groups.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-groups + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-groups +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification Groups.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification Ipv6AddressCount.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-ipv6addresscount + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-ipv6addresscount +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification Ipv6AddressCount.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification Ipv6Addresses.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-ipv6addresses + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-ipv6addresses +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification Ipv6Addresses.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification NetworkInterfaceId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-networkinterfaceid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-networkinterfaceid +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification NetworkInterfaceId.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification PrivateIpAddresses.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-privateipaddresses + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-privateipaddresses +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification PrivateIpAddresses.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification SecondaryPrivateIpAddressCount.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-secondaryprivateipaddresscount + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-secondaryprivateipaddresscount +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification SecondaryPrivateIpAddressCount.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification SubnetId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-subnetid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-subnetid +* AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification SubnetId.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.LaunchTemplateConfig LaunchTemplateSpecification.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.LaunchTemplateConfig Overrides.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.LaunchTemplateOverrides Priority (__deleted__) +* AWS::EC2::SpotFleet.LaunchTemplateOverrides AvailabilityZone.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.LaunchTemplateOverrides InstanceType.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.LaunchTemplateOverrides SpotPrice.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.LaunchTemplateOverrides SubnetId.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.LaunchTemplateOverrides WeightedCapacity.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.LoadBalancersConfig ClassicLoadBalancersConfig.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.LoadBalancersConfig TargetGroupsConfig.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.PrivateIpAddressSpecification Primary.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces-privateipaddresses.html#cfn-ec2-spotfleet-privateipaddressspecification-primary + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-privateipaddressspecification.html#cfn-ec2-spotfleet-privateipaddressspecification-primary +* AWS::EC2::SpotFleet.PrivateIpAddressSpecification Primary.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.PrivateIpAddressSpecification PrivateIpAddress.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces-privateipaddresses.html#cfn-ec2-spotfleet-privateipaddressspecification-privateipaddress + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-privateipaddressspecification.html#cfn-ec2-spotfleet-privateipaddressspecification-privateipaddress +* AWS::EC2::SpotFleet.PrivateIpAddressSpecification PrivateIpAddress.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotCapacityRebalance ReplacementStrategy.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification BlockDeviceMappings.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-blockdevicemappings + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-blockdevicemappings +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification BlockDeviceMappings.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification EbsOptimized.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-ebsoptimized + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-ebsoptimized +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification EbsOptimized.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification IamInstanceProfile.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-iaminstanceprofile + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-iaminstanceprofile +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification IamInstanceProfile.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification ImageId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-imageid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-imageid +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification ImageId.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification InstanceType.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-instancetype + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-instancetype +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification InstanceType.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification KernelId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-kernelid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-kernelid +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification KernelId.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification KeyName.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-keyname + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-keyname +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification KeyName.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification Monitoring.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-monitoring + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-monitoring +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification Monitoring.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification NetworkInterfaces.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-networkinterfaces + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-networkinterfaces +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification NetworkInterfaces.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification Placement.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-placement + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-placement +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification Placement.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification RamdiskId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-ramdiskid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-ramdiskid +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification RamdiskId.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification SecurityGroups.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-securitygroups + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-securitygroups +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification SecurityGroups.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification SpotPrice.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-spotprice + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-spotprice +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification SpotPrice.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification SubnetId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-subnetid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-subnetid +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification SubnetId.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification TagSpecifications.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-tagspecifications + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-tagspecifications +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification TagSpecifications.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification UserData.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-userdata + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-userdata +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification UserData.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification WeightedCapacity.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-weightedcapacity + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-weightedcapacity +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification WeightedCapacity.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetMonitoring Enabled.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-monitoring.html#cfn-ec2-spotfleet-spotfleetmonitoring-enabled + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetmonitoring.html#cfn-ec2-spotfleet-spotfleetmonitoring-enabled +* AWS::EC2::SpotFleet.SpotFleetMonitoring Enabled.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetTagSpecification ResourceType.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-tagspecifications.html#cfn-ec2-spotfleet-spotfleettagspecification-resourcetype + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleettagspecification.html#cfn-ec2-spotfleet-spotfleettagspecification-resourcetype +* AWS::EC2::SpotFleet.SpotFleetTagSpecification ResourceType.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotFleetTagSpecification Tags.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-tagspecifications.html#cfn-ec2-spotfleet-tags + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleettagspecification.html#cfn-ec2-spotfleet-spotfleettagspecification-tags +* AWS::EC2::SpotFleet.SpotFleetTagSpecification Tags.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotMaintenanceStrategies CapacityRebalance.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotPlacement AvailabilityZone.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-placement.html#cfn-ec2-spotfleet-spotplacement-availabilityzone + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotplacement.html#cfn-ec2-spotfleet-spotplacement-availabilityzone +* AWS::EC2::SpotFleet.SpotPlacement AvailabilityZone.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotPlacement GroupName.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-placement.html#cfn-ec2-spotfleet-spotplacement-groupname + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotplacement.html#cfn-ec2-spotfleet-spotplacement-groupname +* AWS::EC2::SpotFleet.SpotPlacement GroupName.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.SpotPlacement Tenancy.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-placement.html#cfn-ec2-spotfleet-spotplacement-tenancy + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotplacement.html#cfn-ec2-spotfleet-spotplacement-tenancy +* AWS::EC2::SpotFleet.SpotPlacement Tenancy.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.TargetGroup Arn.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::SpotFleet.TargetGroupsConfig TargetGroups.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::KinesisAnalyticsV2::Application.ApplicationConfiguration ZeppelinApplicationConfiguration (__added__) +* AWS::Transfer::Server.IdentityProviderDetails DirectoryId (__added__) +* AWS::Transfer::Server.IdentityProviderDetails InvocationRole.Required (__changed__) + * Old: true + * New: false +* AWS::Transfer::Server.IdentityProviderDetails Url.Required (__changed__) + * Old: true + * New: false + + # CloudFormation Resource Specification v37.1.0 ## New Resource Types diff --git a/packages/@aws-cdk/cfnspec/cfn.version b/packages/@aws-cdk/cfnspec/cfn.version index b16ec85c58260..531d2a40b95b9 100644 --- a/packages/@aws-cdk/cfnspec/cfn.version +++ b/packages/@aws-cdk/cfnspec/cfn.version @@ -1 +1 @@ -37.1.0 +38.0.0 diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json index 643c97ee1a78f..227509975eb77 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json @@ -17963,6 +17963,7 @@ }, "Overrides": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-fleetlaunchtemplateconfigrequest.html#cfn-ec2-ec2fleet-fleetlaunchtemplateconfigrequest-overrides", + "DuplicatesAllowed": true, "ItemType": "FleetLaunchTemplateOverridesRequest", "Required": false, "Type": "List", @@ -18192,6 +18193,7 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-tagspecification.html#cfn-ec2-ec2fleet-tagspecification-tags", + "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, "Type": "List", @@ -20118,31 +20120,31 @@ } }, "AWS::EC2::SpotFleet.BlockDeviceMapping": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-blockdevicemapping.html", "Properties": { "DeviceName": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings.html#cfn-ec2-spotfleet-blockdevicemapping-devicename", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-blockdevicemapping.html#cfn-ec2-spotfleet-blockdevicemapping-devicename", "PrimitiveType": "String", "Required": true, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "Ebs": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings.html#cfn-ec2-spotfleet-blockdevicemapping-ebs", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-blockdevicemapping.html#cfn-ec2-spotfleet-blockdevicemapping-ebs", "Required": false, "Type": "EbsBlockDevice", - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "NoDevice": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings.html#cfn-ec2-spotfleet-blockdevicemapping-nodevice", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-blockdevicemapping.html#cfn-ec2-spotfleet-blockdevicemapping-nodevice", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "VirtualName": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings.html#cfn-ec2-spotfleet-blockdevicemapping-virtualname", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-blockdevicemapping.html#cfn-ec2-spotfleet-blockdevicemapping-virtualname", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -20153,7 +20155,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-classicloadbalancer.html#cfn-ec2-spotfleet-classicloadbalancer-name", "PrimitiveType": "String", "Required": true, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -20166,48 +20168,48 @@ "ItemType": "ClassicLoadBalancer", "Required": true, "Type": "List", - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, "AWS::EC2::SpotFleet.EbsBlockDevice": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings-ebs.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-ebsblockdevice.html", "Properties": { "DeleteOnTermination": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings-ebs.html#cfn-ec2-spotfleet-ebsblockdevice-deleteontermination", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-ebsblockdevice.html#cfn-ec2-spotfleet-ebsblockdevice-deleteontermination", "PrimitiveType": "Boolean", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "Encrypted": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings-ebs.html#cfn-ec2-spotfleet-ebsblockdevice-encrypted", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-ebsblockdevice.html#cfn-ec2-spotfleet-ebsblockdevice-encrypted", "PrimitiveType": "Boolean", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "Iops": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings-ebs.html#cfn-ec2-spotfleet-ebsblockdevice-iops", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-ebsblockdevice.html#cfn-ec2-spotfleet-ebsblockdevice-iops", "PrimitiveType": "Integer", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "SnapshotId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings-ebs.html#cfn-ec2-spotfleet-ebsblockdevice-snapshotid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-ebsblockdevice.html#cfn-ec2-spotfleet-ebsblockdevice-snapshotid", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "VolumeSize": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings-ebs.html#cfn-ec2-spotfleet-ebsblockdevice-volumesize", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-ebsblockdevice.html#cfn-ec2-spotfleet-ebsblockdevice-volumesize", "PrimitiveType": "Integer", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "VolumeType": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings-ebs.html#cfn-ec2-spotfleet-ebsblockdevice-volumetype", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-ebsblockdevice.html#cfn-ec2-spotfleet-ebsblockdevice-volumetype", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -20218,41 +20220,41 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-fleetlaunchtemplatespecification.html#cfn-ec2-spotfleet-fleetlaunchtemplatespecification-launchtemplateid", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "LaunchTemplateName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-fleetlaunchtemplatespecification.html#cfn-ec2-spotfleet-fleetlaunchtemplatespecification-launchtemplatename", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "Version": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-fleetlaunchtemplatespecification.html#cfn-ec2-spotfleet-fleetlaunchtemplatespecification-version", "PrimitiveType": "String", "Required": true, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, "AWS::EC2::SpotFleet.GroupIdentifier": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-securitygroups.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-groupidentifier.html", "Properties": { "GroupId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-securitygroups.html#cfn-ec2-spotfleet-groupidentifier-groupid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-groupidentifier.html#cfn-ec2-spotfleet-groupidentifier-groupid", "PrimitiveType": "String", "Required": true, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, "AWS::EC2::SpotFleet.IamInstanceProfileSpecification": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-iaminstanceprofile.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-iaminstanceprofilespecification.html", "Properties": { "Arn": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-iaminstanceprofile.html#cfn-ec2-spotfleet-iaminstanceprofilespecification-arn", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-iaminstanceprofilespecification.html#cfn-ec2-spotfleet-iaminstanceprofilespecification-arn", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -20263,84 +20265,84 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instanceipv6address.html#cfn-ec2-spotfleet-instanceipv6address-ipv6address", "PrimitiveType": "String", "Required": true, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, "AWS::EC2::SpotFleet.InstanceNetworkInterfaceSpecification": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html", "Properties": { "AssociatePublicIpAddress": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-associatepublicipaddress", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-associatepublicipaddress", "PrimitiveType": "Boolean", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "DeleteOnTermination": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-deleteontermination", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-deleteontermination", "PrimitiveType": "Boolean", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "Description": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-description", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-description", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "DeviceIndex": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-deviceindex", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-deviceindex", "PrimitiveType": "Integer", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "Groups": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-groups", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-groups", "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "List", - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "Ipv6AddressCount": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-ipv6addresscount", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-ipv6addresscount", "PrimitiveType": "Integer", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "Ipv6Addresses": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-ipv6addresses", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-ipv6addresses", "DuplicatesAllowed": false, "ItemType": "InstanceIpv6Address", "Required": false, "Type": "List", - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "NetworkInterfaceId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-networkinterfaceid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-networkinterfaceid", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "PrivateIpAddresses": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-privateipaddresses", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-privateipaddresses", "DuplicatesAllowed": false, "ItemType": "PrivateIpAddressSpecification", "Required": false, "Type": "List", - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "SecondaryPrivateIpAddressCount": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-secondaryprivateipaddresscount", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-secondaryprivateipaddresscount", "PrimitiveType": "Integer", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "SubnetId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-subnetid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancenetworkinterfacespecification.html#cfn-ec2-spotfleet-instancenetworkinterfacespecification-subnetid", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -20351,7 +20353,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-launchtemplateconfig.html#cfn-ec2-spotfleet-launchtemplateconfig-launchtemplatespecification", "Required": false, "Type": "FleetLaunchTemplateSpecification", - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "Overrides": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-launchtemplateconfig.html#cfn-ec2-spotfleet-launchtemplateconfig-overrides", @@ -20359,7 +20361,7 @@ "ItemType": "LaunchTemplateOverrides", "Required": false, "Type": "List", - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -20370,37 +20372,31 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-launchtemplateoverrides.html#cfn-ec2-spotfleet-launchtemplateoverrides-availabilityzone", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "InstanceType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-launchtemplateoverrides.html#cfn-ec2-spotfleet-launchtemplateoverrides-instancetype", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" - }, - "Priority": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-launchtemplateoverrides.html#cfn-ec2-spotfleet-launchtemplateoverrides-priority", - "PrimitiveType": "Double", - "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "SpotPrice": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-launchtemplateoverrides.html#cfn-ec2-spotfleet-launchtemplateoverrides-spotprice", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "SubnetId": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-launchtemplateoverrides.html#cfn-ec2-spotfleet-launchtemplateoverrides-subnetid", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "WeightedCapacity": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-launchtemplateoverrides.html#cfn-ec2-spotfleet-launchtemplateoverrides-weightedcapacity", "PrimitiveType": "Double", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -20411,30 +20407,30 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-loadbalancersconfig.html#cfn-ec2-spotfleet-loadbalancersconfig-classicloadbalancersconfig", "Required": false, "Type": "ClassicLoadBalancersConfig", - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "TargetGroupsConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-loadbalancersconfig.html#cfn-ec2-spotfleet-loadbalancersconfig-targetgroupsconfig", "Required": false, "Type": "TargetGroupsConfig", - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, "AWS::EC2::SpotFleet.PrivateIpAddressSpecification": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces-privateipaddresses.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-privateipaddressspecification.html", "Properties": { "Primary": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces-privateipaddresses.html#cfn-ec2-spotfleet-privateipaddressspecification-primary", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-privateipaddressspecification.html#cfn-ec2-spotfleet-privateipaddressspecification-primary", "PrimitiveType": "Boolean", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "PrivateIpAddress": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-networkinterfaces-privateipaddresses.html#cfn-ec2-spotfleet-privateipaddressspecification-privateipaddress", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-privateipaddressspecification.html#cfn-ec2-spotfleet-privateipaddressspecification-privateipaddress", "PrimitiveType": "String", "Required": true, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -20445,133 +20441,133 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotcapacityrebalance.html#cfn-ec2-spotfleet-spotcapacityrebalance-replacementstrategy", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, "AWS::EC2::SpotFleet.SpotFleetLaunchSpecification": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html", "Properties": { "BlockDeviceMappings": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-blockdevicemappings", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-blockdevicemappings", "DuplicatesAllowed": false, "ItemType": "BlockDeviceMapping", "Required": false, "Type": "List", - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "EbsOptimized": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-ebsoptimized", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-ebsoptimized", "PrimitiveType": "Boolean", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "IamInstanceProfile": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-iaminstanceprofile", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-iaminstanceprofile", "Required": false, "Type": "IamInstanceProfileSpecification", - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "ImageId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-imageid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-imageid", "PrimitiveType": "String", "Required": true, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "InstanceType": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-instancetype", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-instancetype", "PrimitiveType": "String", "Required": true, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "KernelId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-kernelid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-kernelid", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "KeyName": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-keyname", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-keyname", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "Monitoring": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-monitoring", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-monitoring", "Required": false, "Type": "SpotFleetMonitoring", - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "NetworkInterfaces": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-networkinterfaces", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-networkinterfaces", "DuplicatesAllowed": false, "ItemType": "InstanceNetworkInterfaceSpecification", "Required": false, "Type": "List", - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "Placement": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-placement", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-placement", "Required": false, "Type": "SpotPlacement", - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "RamdiskId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-ramdiskid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-ramdiskid", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "SecurityGroups": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-securitygroups", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-securitygroups", "DuplicatesAllowed": false, "ItemType": "GroupIdentifier", "Required": false, "Type": "List", - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "SpotPrice": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-spotprice", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-spotprice", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "SubnetId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-subnetid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-subnetid", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "TagSpecifications": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-tagspecifications", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-tagspecifications", "DuplicatesAllowed": false, "ItemType": "SpotFleetTagSpecification", "Required": false, "Type": "List", - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "UserData": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-userdata", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-userdata", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "WeightedCapacity": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-weightedcapacity", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-weightedcapacity", "PrimitiveType": "Double", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, "AWS::EC2::SpotFleet.SpotFleetMonitoring": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-monitoring.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetmonitoring.html", "Properties": { "Enabled": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-monitoring.html#cfn-ec2-spotfleet-spotfleetmonitoring-enabled", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetmonitoring.html#cfn-ec2-spotfleet-spotfleetmonitoring-enabled", "PrimitiveType": "Boolean", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -20705,21 +20701,21 @@ } }, "AWS::EC2::SpotFleet.SpotFleetTagSpecification": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-tagspecifications.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleettagspecification.html", "Properties": { "ResourceType": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-tagspecifications.html#cfn-ec2-spotfleet-spotfleettagspecification-resourcetype", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleettagspecification.html#cfn-ec2-spotfleet-spotfleettagspecification-resourcetype", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "Tags": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-tagspecifications.html#cfn-ec2-spotfleet-tags", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleettagspecification.html#cfn-ec2-spotfleet-spotfleettagspecification-tags", "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, "Type": "List", - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -20730,30 +20726,30 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotmaintenancestrategies.html#cfn-ec2-spotfleet-spotmaintenancestrategies-capacityrebalance", "Required": false, "Type": "SpotCapacityRebalance", - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, "AWS::EC2::SpotFleet.SpotPlacement": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-placement.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotplacement.html", "Properties": { "AvailabilityZone": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-placement.html#cfn-ec2-spotfleet-spotplacement-availabilityzone", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotplacement.html#cfn-ec2-spotfleet-spotplacement-availabilityzone", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "GroupName": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-placement.html#cfn-ec2-spotfleet-spotplacement-groupname", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotplacement.html#cfn-ec2-spotfleet-spotplacement-groupname", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "Tenancy": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-placement.html#cfn-ec2-spotfleet-spotplacement-tenancy", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotplacement.html#cfn-ec2-spotfleet-spotplacement-tenancy", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -20764,7 +20760,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-targetgroup.html#cfn-ec2-spotfleet-targetgroup-arn", "PrimitiveType": "String", "Required": true, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -20777,7 +20773,7 @@ "ItemType": "TargetGroup", "Required": true, "Type": "List", - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -37189,6 +37185,12 @@ "Required": false, "Type": "SqlApplicationConfiguration", "UpdateType": "Mutable" + }, + "ZeppelinApplicationConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-applicationconfiguration.html#cfn-kinesisanalyticsv2-application-applicationconfiguration-zeppelinapplicationconfiguration", + "Required": false, + "Type": "ZeppelinApplicationConfiguration", + "UpdateType": "Mutable" } } }, @@ -37220,6 +37222,17 @@ } } }, + "AWS::KinesisAnalyticsV2::Application.CatalogConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-catalogconfiguration.html", + "Properties": { + "GlueDataCatalogConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-catalogconfiguration.html#cfn-kinesisanalyticsv2-application-catalogconfiguration-gluedatacatalogconfiguration", + "Required": false, + "Type": "GlueDataCatalogConfiguration", + "UpdateType": "Mutable" + } + } + }, "AWS::KinesisAnalyticsV2::Application.CheckpointConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-checkpointconfiguration.html", "Properties": { @@ -37272,6 +37285,47 @@ } } }, + "AWS::KinesisAnalyticsV2::Application.CustomArtifactConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-customartifactconfiguration.html", + "Properties": { + "ArtifactType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-customartifactconfiguration.html#cfn-kinesisanalyticsv2-application-customartifactconfiguration-artifacttype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "MavenReference": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-customartifactconfiguration.html#cfn-kinesisanalyticsv2-application-customartifactconfiguration-mavenreference", + "Required": false, + "Type": "MavenReference", + "UpdateType": "Mutable" + }, + "S3ContentLocation": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-customartifactconfiguration.html#cfn-kinesisanalyticsv2-application-customartifactconfiguration-s3contentlocation", + "Required": false, + "Type": "S3ContentLocation", + "UpdateType": "Mutable" + } + } + }, + "AWS::KinesisAnalyticsV2::Application.CustomArtifactsConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-customartifactsconfiguration.html", + "ItemType": "CustomArtifactConfiguration", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "AWS::KinesisAnalyticsV2::Application.DeployAsApplicationConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-deployasapplicationconfiguration.html", + "Properties": { + "S3ContentLocation": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-deployasapplicationconfiguration.html#cfn-kinesisanalyticsv2-application-deployasapplicationconfiguration-s3contentlocation", + "Required": true, + "Type": "S3ContentBaseLocation", + "UpdateType": "Mutable" + } + } + }, "AWS::KinesisAnalyticsV2::Application.EnvironmentProperties": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-environmentproperties.html", "Properties": { @@ -37307,6 +37361,17 @@ } } }, + "AWS::KinesisAnalyticsV2::Application.GlueDataCatalogConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-gluedatacatalogconfiguration.html", + "Properties": { + "DatabaseARN": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-gluedatacatalogconfiguration.html#cfn-kinesisanalyticsv2-application-gluedatacatalogconfiguration-databasearn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::KinesisAnalyticsV2::Application.Input": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-input.html", "Properties": { @@ -37455,6 +37520,29 @@ } } }, + "AWS::KinesisAnalyticsV2::Application.MavenReference": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-mavenreference.html", + "Properties": { + "ArtifactId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-mavenreference.html#cfn-kinesisanalyticsv2-application-mavenreference-artifactid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "GroupId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-mavenreference.html#cfn-kinesisanalyticsv2-application-mavenreference-groupid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Version": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-mavenreference.html#cfn-kinesisanalyticsv2-application-mavenreference-version", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::KinesisAnalyticsV2::Application.MonitoringConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-monitoringconfiguration.html", "Properties": { @@ -37564,6 +37652,23 @@ } } }, + "AWS::KinesisAnalyticsV2::Application.S3ContentBaseLocation": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-s3contentbaselocation.html", + "Properties": { + "BasePath": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-s3contentbaselocation.html#cfn-kinesisanalyticsv2-application-s3contentbaselocation-basepath", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "BucketARN": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-s3contentbaselocation.html#cfn-kinesisanalyticsv2-application-s3contentbaselocation-bucketarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::KinesisAnalyticsV2::Application.S3ContentLocation": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-s3contentlocation.html", "Properties": { @@ -37599,6 +37704,46 @@ } } }, + "AWS::KinesisAnalyticsV2::Application.ZeppelinApplicationConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-zeppelinapplicationconfiguration.html", + "Properties": { + "CatalogConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-zeppelinapplicationconfiguration.html#cfn-kinesisanalyticsv2-application-zeppelinapplicationconfiguration-catalogconfiguration", + "Required": false, + "Type": "CatalogConfiguration", + "UpdateType": "Mutable" + }, + "CustomArtifactsConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-zeppelinapplicationconfiguration.html#cfn-kinesisanalyticsv2-application-zeppelinapplicationconfiguration-customartifactsconfiguration", + "Required": false, + "Type": "CustomArtifactsConfiguration", + "UpdateType": "Mutable" + }, + "DeployAsApplicationConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-zeppelinapplicationconfiguration.html#cfn-kinesisanalyticsv2-application-zeppelinapplicationconfiguration-deployasapplicationconfiguration", + "Required": false, + "Type": "DeployAsApplicationConfiguration", + "UpdateType": "Mutable" + }, + "MonitoringConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-zeppelinapplicationconfiguration.html#cfn-kinesisanalyticsv2-application-zeppelinapplicationconfiguration-monitoringconfiguration", + "Required": false, + "Type": "ZeppelinMonitoringConfiguration", + "UpdateType": "Mutable" + } + } + }, + "AWS::KinesisAnalyticsV2::Application.ZeppelinMonitoringConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-zeppelinmonitoringconfiguration.html", + "Properties": { + "LogLevel": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-application-zeppelinmonitoringconfiguration.html#cfn-kinesisanalyticsv2-application-zeppelinmonitoringconfiguration-loglevel", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::KinesisAnalyticsV2::ApplicationCloudWatchLoggingOption.CloudWatchLoggingOption": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisanalyticsv2-applicationcloudwatchloggingoption-cloudwatchloggingoption.html", "Properties": { @@ -39488,6 +39633,28 @@ } } }, + "AWS::Location::Map.MapConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-location-map-mapconfiguration.html", + "Properties": { + "Style": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-location-map-mapconfiguration.html#cfn-location-map-mapconfiguration-style", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Location::PlaceIndex.DataSourceConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-location-placeindex-datasourceconfiguration.html", + "Properties": { + "IntendedUse": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-location-placeindex-datasourceconfiguration.html#cfn-location-placeindex-datasourceconfiguration-intendeduse", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::Logs::MetricFilter.MetricTransformation": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-logs-metricfilter-metrictransformation.html", "Properties": { @@ -57522,16 +57689,22 @@ "AWS::Transfer::Server.IdentityProviderDetails": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-transfer-server-identityproviderdetails.html", "Properties": { + "DirectoryId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-transfer-server-identityproviderdetails.html#cfn-transfer-server-identityproviderdetails-directoryid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "InvocationRole": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-transfer-server-identityproviderdetails.html#cfn-transfer-server-identityproviderdetails-invocationrole", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Url": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-transfer-server-identityproviderdetails.html#cfn-transfer-server-identityproviderdetails-url", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" } } @@ -59836,7 +60009,7 @@ } } }, - "ResourceSpecificationVersion": "37.1.0", + "ResourceSpecificationVersion": "38.0.0", "ResourceTypes": { "AWS::ACMPCA::Certificate": { "Attributes": { @@ -61580,7 +61753,7 @@ "IdentitySource": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-authorizer.html#cfn-apigatewayv2-authorizer-identitysource", "PrimitiveItemType": "String", - "Required": true, + "Required": false, "Type": "List", "UpdateType": "Mutable" }, @@ -64001,7 +64174,7 @@ "CreationTime": { "PrimitiveType": "String" }, - "EffectiveEngineVersion": { + "WorkGroupConfiguration.EngineVersion.EffectiveEngineVersion": { "PrimitiveType": "String" } }, @@ -65902,6 +66075,9 @@ "FunctionARN": { "PrimitiveType": "String" }, + "FunctionMetadata.FunctionARN": { + "PrimitiveType": "String" + }, "Stage": { "PrimitiveType": "String" } @@ -68671,6 +68847,9 @@ }, "ClusterDiscoveryEndpoint": { "PrimitiveType": "String" + }, + "ClusterDiscoveryEndpointURL": { + "PrimitiveType": "String" } }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dax-cluster.html", @@ -68682,6 +68861,12 @@ "Type": "List", "UpdateType": "Mutable" }, + "ClusterEndpointEncryptionType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dax-cluster.html#cfn-dax-cluster-clusterendpointencryptiontype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "ClusterName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dax-cluster.html#cfn-dax-cluster-clustername", "PrimitiveType": "String", @@ -71117,6 +71302,11 @@ } }, "AWS::EC2::EC2Fleet": { + "Attributes": { + "FleetId": { + "PrimitiveType": "String" + } + }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-ec2fleet.html", "Properties": { "ExcessCapacityTerminationPolicy": { @@ -71127,6 +71317,7 @@ }, "LaunchTemplateConfigs": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-ec2fleet.html#cfn-ec2-ec2fleet-launchtemplateconfigs", + "DuplicatesAllowed": true, "ItemType": "FleetLaunchTemplateConfigRequest", "Required": true, "Type": "List", @@ -71152,6 +71343,7 @@ }, "TagSpecifications": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-ec2fleet.html#cfn-ec2-ec2fleet-tagspecifications", + "DuplicatesAllowed": true, "ItemType": "TagSpecification", "Required": false, "Type": "List", @@ -72525,13 +72717,18 @@ } }, "AWS::EC2::SpotFleet": { + "Attributes": { + "Id": { + "PrimitiveType": "String" + } + }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-spotfleet.html", "Properties": { "SpotFleetRequestConfigData": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-spotfleet.html#cfn-ec2-spotfleet-spotfleetrequestconfigdata", "Required": true, "Type": "SpotFleetRequestConfigData", - "UpdateType": "Conditional" + "UpdateType": "Mutable" } } }, @@ -77274,6 +77471,9 @@ }, "AWS::FSx::FileSystem": { "Attributes": { + "DNSName": { + "PrimitiveType": "String" + }, "LustreMountName": { "PrimitiveType": "String" } @@ -77414,6 +77614,15 @@ "DetectorVersionId": { "PrimitiveType": "String" }, + "EventType.Arn": { + "PrimitiveType": "String" + }, + "EventType.CreatedTime": { + "PrimitiveType": "String" + }, + "EventType.LastUpdatedTime": { + "PrimitiveType": "String" + }, "LastUpdatedTime": { "PrimitiveType": "String" } @@ -82693,61 +82902,61 @@ "Arn": { "PrimitiveType": "String" }, - "ChannelMask": { + "Id": { "PrimitiveType": "String" }, - "DevStatusReqFreq": { + "LoRaWAN.ChannelMask": { + "PrimitiveType": "String" + }, + "LoRaWAN.DevStatusReqFreq": { "PrimitiveType": "Integer" }, - "DlBucketSize": { + "LoRaWAN.DlBucketSize": { "PrimitiveType": "Integer" }, - "DlRate": { + "LoRaWAN.DlRate": { "PrimitiveType": "Integer" }, - "DlRatePolicy": { + "LoRaWAN.DlRatePolicy": { "PrimitiveType": "String" }, - "DrMax": { + "LoRaWAN.DrMax": { "PrimitiveType": "Integer" }, - "DrMin": { + "LoRaWAN.DrMin": { "PrimitiveType": "Integer" }, - "HrAllowed": { + "LoRaWAN.HrAllowed": { "PrimitiveType": "Boolean" }, - "Id": { - "PrimitiveType": "String" - }, - "MinGwDiversity": { + "LoRaWAN.MinGwDiversity": { "PrimitiveType": "Integer" }, - "NwkGeoLoc": { + "LoRaWAN.NwkGeoLoc": { "PrimitiveType": "Boolean" }, - "PrAllowed": { + "LoRaWAN.PrAllowed": { "PrimitiveType": "Boolean" }, - "RaAllowed": { + "LoRaWAN.RaAllowed": { "PrimitiveType": "Boolean" }, - "ReportDevStatusBattery": { + "LoRaWAN.ReportDevStatusBattery": { "PrimitiveType": "Boolean" }, - "ReportDevStatusMargin": { + "LoRaWAN.ReportDevStatusMargin": { "PrimitiveType": "Boolean" }, - "TargetPer": { + "LoRaWAN.TargetPer": { "PrimitiveType": "Integer" }, - "UlBucketSize": { + "LoRaWAN.UlBucketSize": { "PrimitiveType": "Integer" }, - "UlRate": { + "LoRaWAN.UlRate": { "PrimitiveType": "Integer" }, - "UlRatePolicy": { + "LoRaWAN.UlRatePolicy": { "PrimitiveType": "String" } }, @@ -83375,6 +83584,12 @@ "Required": false, "UpdateType": "Mutable" }, + "ApplicationMode": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kinesisanalyticsv2-application.html#cfn-kinesisanalyticsv2-application-applicationmode", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "ApplicationName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kinesisanalyticsv2-application.html#cfn-kinesisanalyticsv2-application-applicationname", "PrimitiveType": "String", @@ -84253,6 +84468,244 @@ } } }, + "AWS::Location::GeofenceCollection": { + "Attributes": { + "CollectionArn": { + "PrimitiveType": "String" + }, + "CreateTime": { + "PrimitiveType": "String" + }, + "UpdateTime": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-geofencecollection.html", + "Properties": { + "CollectionName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-geofencecollection.html#cfn-location-geofencecollection-collectionname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-geofencecollection.html#cfn-location-geofencecollection-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "KmsKeyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-geofencecollection.html#cfn-location-geofencecollection-kmskeyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "PricingPlan": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-geofencecollection.html#cfn-location-geofencecollection-pricingplan", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "PricingPlanDataSource": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-geofencecollection.html#cfn-location-geofencecollection-pricingplandatasource", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::Location::Map": { + "Attributes": { + "CreateTime": { + "PrimitiveType": "String" + }, + "DataSource": { + "PrimitiveType": "String" + }, + "MapArn": { + "PrimitiveType": "String" + }, + "UpdateTime": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-map.html", + "Properties": { + "Configuration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-map.html#cfn-location-map-configuration", + "Required": true, + "Type": "MapConfiguration", + "UpdateType": "Immutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-map.html#cfn-location-map-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "MapName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-map.html#cfn-location-map-mapname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "PricingPlan": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-map.html#cfn-location-map-pricingplan", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Location::PlaceIndex": { + "Attributes": { + "CreateTime": { + "PrimitiveType": "String" + }, + "IndexArn": { + "PrimitiveType": "String" + }, + "UpdateTime": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-placeindex.html", + "Properties": { + "DataSource": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-placeindex.html#cfn-location-placeindex-datasource", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "DataSourceConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-placeindex.html#cfn-location-placeindex-datasourceconfiguration", + "Required": false, + "Type": "DataSourceConfiguration", + "UpdateType": "Immutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-placeindex.html#cfn-location-placeindex-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "IndexName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-placeindex.html#cfn-location-placeindex-indexname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "PricingPlan": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-placeindex.html#cfn-location-placeindex-pricingplan", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Location::RouteCalculator": { + "Attributes": { + "CalculatorArn": { + "PrimitiveType": "String" + }, + "CreateTime": { + "PrimitiveType": "String" + }, + "UpdateTime": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-routecalculator.html", + "Properties": { + "CalculatorName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-routecalculator.html#cfn-location-routecalculator-calculatorname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "DataSource": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-routecalculator.html#cfn-location-routecalculator-datasource", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-routecalculator.html#cfn-location-routecalculator-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "PricingPlan": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-routecalculator.html#cfn-location-routecalculator-pricingplan", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Location::Tracker": { + "Attributes": { + "CreateTime": { + "PrimitiveType": "String" + }, + "TrackerArn": { + "PrimitiveType": "String" + }, + "UpdateTime": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-tracker.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-tracker.html#cfn-location-tracker-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "KmsKeyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-tracker.html#cfn-location-tracker-kmskeyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "PricingPlan": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-tracker.html#cfn-location-tracker-pricingplan", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "PricingPlanDataSource": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-tracker.html#cfn-location-tracker-pricingplandatasource", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "TrackerName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-tracker.html#cfn-location-tracker-trackername", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Location::TrackerConsumer": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-trackerconsumer.html", + "Properties": { + "ConsumerArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-trackerconsumer.html#cfn-location-trackerconsumer-consumerarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "TrackerName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-trackerconsumer.html#cfn-location-trackerconsumer-trackername", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::Logs::Destination": { "Attributes": { "Arn": { @@ -84588,7 +85041,7 @@ "Arn": { "PrimitiveType": "String" }, - "CloudWatchLogGroupArn": { + "LoggingConfiguration.DagProcessingLogs.CloudWatchLogGroupArn": { "PrimitiveType": "String" }, "WebserverUrl": { @@ -84924,10 +85377,10 @@ "FlowAvailabilityZone": { "PrimitiveType": "String" }, - "IngestIp": { + "Source.IngestIp": { "PrimitiveType": "String" }, - "SourceArn": { + "Source.SourceArn": { "PrimitiveType": "String" } }, @@ -91728,7 +92181,7 @@ }, "AWS::S3::StorageLens": { "Attributes": { - "StorageLensArn": { + "StorageLensConfiguration.StorageLensArn": { "PrimitiveType": "String" } }, @@ -92367,7 +92820,7 @@ }, "Parameters": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-association.html#cfn-ssm-association-parameters", - "ItemType": "List", + "PrimitiveItemType": "Json", "Required": false, "Type": "Map", "UpdateType": "Mutable" diff --git a/packages/@aws-cdk/cfnspec/spec-source/540_SSM_Association_Parameters_patch.json b/packages/@aws-cdk/cfnspec/spec-source/540_SSM_Association_Parameters_patch.json deleted file mode 100644 index 815f593a3cfb4..0000000000000 --- a/packages/@aws-cdk/cfnspec/spec-source/540_SSM_Association_Parameters_patch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ResourceTypes": { - "AWS::SSM::Association": { - "patch": { - "description": "Removes 'ItemType' property since 'ParameterValues' is (currently) not defined in the spec and the documentation states it to be a list of String", - "operations": [ - { - "op": "replace", - "path": "/Properties/Parameters/ItemType", - "value": "List" - }, - { - "op": "add", - "path": "/Properties/Parameters/PrimitiveItemItemType", - "value": "String" - } - ] - } - } - } -} diff --git a/packages/@aws-cdk/cloudformation-include/package.json b/packages/@aws-cdk/cloudformation-include/package.json index 9e1c99e7331fd..5de894785e6ae 100644 --- a/packages/@aws-cdk/cloudformation-include/package.json +++ b/packages/@aws-cdk/cloudformation-include/package.json @@ -168,6 +168,7 @@ "@aws-cdk/aws-lakeformation": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-licensemanager": "0.0.0", + "@aws-cdk/aws-location": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-lookoutmetrics": "0.0.0", "@aws-cdk/aws-lookoutvision": "0.0.0", @@ -333,6 +334,7 @@ "@aws-cdk/aws-lakeformation": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-licensemanager": "0.0.0", + "@aws-cdk/aws-location": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-lookoutmetrics": "0.0.0", "@aws-cdk/aws-lookoutvision": "0.0.0", diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index 8923c9b0cf15d..49e0428916cdf 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -234,6 +234,7 @@ "@aws-cdk/aws-lambda-nodejs": "0.0.0", "@aws-cdk/aws-lambda-python": "0.0.0", "@aws-cdk/aws-licensemanager": "0.0.0", + "@aws-cdk/aws-location": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-logs-destinations": "0.0.0", "@aws-cdk/aws-lookoutmetrics": "0.0.0", diff --git a/packages/decdk/package.json b/packages/decdk/package.json index fcd346e420200..a5dd457367a6c 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -151,6 +151,7 @@ "@aws-cdk/aws-lambda-nodejs": "0.0.0", "@aws-cdk/aws-lambda-python": "0.0.0", "@aws-cdk/aws-licensemanager": "0.0.0", + "@aws-cdk/aws-location": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-logs-destinations": "0.0.0", "@aws-cdk/aws-lookoutmetrics": "0.0.0", diff --git a/packages/monocdk/package.json b/packages/monocdk/package.json index e578bd26d353b..7901556f2b976 100644 --- a/packages/monocdk/package.json +++ b/packages/monocdk/package.json @@ -235,6 +235,7 @@ "@aws-cdk/aws-lambda-nodejs": "0.0.0", "@aws-cdk/aws-lambda-python": "0.0.0", "@aws-cdk/aws-licensemanager": "0.0.0", + "@aws-cdk/aws-location": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-logs-destinations": "0.0.0", "@aws-cdk/aws-lookoutmetrics": "0.0.0", From af7435494ba938b036e85435b5dcb590082fc378 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Fri, 11 Jun 2021 13:59:31 +0100 Subject: [PATCH 02/34] feat: cloudformation spec v39.1.0 --- packages/@aws-cdk/cfnspec/CHANGELOG.md | 35 ++++++ packages/@aws-cdk/cfnspec/cfn.version | 2 +- ...0_CloudFormationResourceSpecification.json | 102 ++++++++++++++---- 3 files changed, 118 insertions(+), 21 deletions(-) diff --git a/packages/@aws-cdk/cfnspec/CHANGELOG.md b/packages/@aws-cdk/cfnspec/CHANGELOG.md index 216f3c2f975e0..4f368495066d3 100644 --- a/packages/@aws-cdk/cfnspec/CHANGELOG.md +++ b/packages/@aws-cdk/cfnspec/CHANGELOG.md @@ -1,3 +1,38 @@ +# CloudFormation Resource Specification v39.1.0 + +## New Resource Types + + +## Attribute Changes + +* AWS::AuditManager::Assessment FrameworkId (__deleted__) +* AWS::EC2::TransitGateway Id (__added__) + +## Property Changes + +* AWS::AutoScaling::AutoScalingGroup Context (__added__) +* AWS::EC2::NatGateway ConnectivityType (__added__) +* AWS::EC2::NatGateway AllocationId.Required (__changed__) + * Old: true + * New: false +* AWS::EC2::TransitGateway Tags.DuplicatesAllowed (__added__) +* AWS::Lambda::Function Id (__deleted__) +* AWS::Lambda::LayerVersion CompatibleArchitectures (__deleted__) +* AWS::RAM::ResourceShare PermissionArns (__added__) +* AWS::SQS::Queue DeduplicationScope (__added__) +* AWS::SQS::Queue FifoThroughputLimit (__added__) +* AWS::SageMaker::CodeRepository Tags (__added__) + +## Property Type Changes + +* AWS::SSMContacts::Contact.ChannelTargetInfo (__added__) +* AWS::SSMContacts::Contact.ContactTargetInfo (__added__) +* AWS::SSMContacts::Contact.Targets ChannelTargetInfo.PrimitiveType (__deleted__) +* AWS::SSMContacts::Contact.Targets ChannelTargetInfo.Type (__added__) +* AWS::SSMContacts::Contact.Targets ContactTargetInfo.PrimitiveType (__deleted__) +* AWS::SSMContacts::Contact.Targets ContactTargetInfo.Type (__added__) + + # CloudFormation Resource Specification v38.0.0 ## New Resource Types diff --git a/packages/@aws-cdk/cfnspec/cfn.version b/packages/@aws-cdk/cfnspec/cfn.version index 531d2a40b95b9..b0440a7d2a4fb 100644 --- a/packages/@aws-cdk/cfnspec/cfn.version +++ b/packages/@aws-cdk/cfnspec/cfn.version @@ -1 +1 @@ -38.0.0 +39.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 227509975eb77..c19f34263b04b 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json @@ -54485,6 +54485,40 @@ } } }, + "AWS::SSMContacts::Contact.ChannelTargetInfo": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ssmcontacts-contact-channeltargetinfo.html", + "Properties": { + "ChannelId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ssmcontacts-contact-channeltargetinfo.html#cfn-ssmcontacts-contact-channeltargetinfo-channelid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "RetryIntervalInMinutes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ssmcontacts-contact-channeltargetinfo.html#cfn-ssmcontacts-contact-channeltargetinfo-retryintervalinminutes", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::SSMContacts::Contact.ContactTargetInfo": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ssmcontacts-contact-contacttargetinfo.html", + "Properties": { + "ContactId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ssmcontacts-contact-contacttargetinfo.html#cfn-ssmcontacts-contact-contacttargetinfo-contactid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "IsEssential": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ssmcontacts-contact-contacttargetinfo.html#cfn-ssmcontacts-contact-contacttargetinfo-isessential", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::SSMContacts::Contact.Stage": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ssmcontacts-contact-stage.html", "Properties": { @@ -54508,14 +54542,14 @@ "Properties": { "ChannelTargetInfo": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ssmcontacts-contact-targets.html#cfn-ssmcontacts-contact-targets-channeltargetinfo", - "PrimitiveType": "Json", "Required": false, + "Type": "ChannelTargetInfo", "UpdateType": "Mutable" }, "ContactTargetInfo": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ssmcontacts-contact-targets.html#cfn-ssmcontacts-contact-targets-contacttargetinfo", - "PrimitiveType": "Json", "Required": false, + "Type": "ContactTargetInfo", "UpdateType": "Mutable" } } @@ -60009,7 +60043,7 @@ } } }, - "ResourceSpecificationVersion": "38.0.0", + "ResourceSpecificationVersion": "39.1.0", "ResourceTypes": { "AWS::ACMPCA::Certificate": { "Attributes": { @@ -64239,9 +64273,6 @@ "Delegations": { "ItemType": "Delegation", "Type": "List" - }, - "FrameworkId": { - "PrimitiveType": "String" } }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-auditmanager-assessment.html", @@ -64344,6 +64375,12 @@ "Required": false, "UpdateType": "Mutable" }, + "Context": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html#cfn-as-group-context", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "Cooldown": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html#cfn-as-group-cooldown", "PrimitiveType": "String", @@ -72004,7 +72041,13 @@ "AllocationId": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-natgateway.html#cfn-ec2-natgateway-allocationid", "PrimitiveType": "String", - "Required": true, + "Required": false, + "UpdateType": "Immutable" + }, + "ConnectivityType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-natgateway.html#cfn-ec2-natgateway-connectivitytype", + "PrimitiveType": "String", + "Required": false, "UpdateType": "Immutable" }, "SubnetId": { @@ -73036,6 +73079,11 @@ } }, "AWS::EC2::TransitGateway": { + "Attributes": { + "Id": { + "PrimitiveType": "String" + } + }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-transitgateway.html", "Properties": { "AmazonSideAsn": { @@ -73082,6 +73130,7 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-transitgateway.html#cfn-ec2-transitgateway-tags", + "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, "Type": "List", @@ -84101,12 +84150,6 @@ "Required": false, "UpdateType": "Mutable" }, - "Id": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-id", - "PrimitiveType": "String", - "Required": false, - "UpdateType": "Mutable" - }, "ImageConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-imageconfig", "Required": false, @@ -84188,13 +84231,6 @@ "AWS::Lambda::LayerVersion": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-layerversion.html", "Properties": { - "CompatibleArchitectures": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-layerversion.html#cfn-lambda-layerversion-compatiblearchitectures", - "PrimitiveItemType": "String", - "Required": false, - "Type": "List", - "UpdateType": "Immutable" - }, "CompatibleRuntimes": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-layerversion.html#cfn-lambda-layerversion-compatibleruntimes", "PrimitiveItemType": "String", @@ -89697,6 +89733,13 @@ "Required": true, "UpdateType": "Mutable" }, + "PermissionArns": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ram-resourceshare.html#cfn-ram-resourceshare-permissionarns", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, "Principals": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ram-resourceshare.html#cfn-ram-resourceshare-principals", "PrimitiveItemType": "String", @@ -92654,6 +92697,12 @@ "Required": false, "UpdateType": "Mutable" }, + "DeduplicationScope": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sqs-queues.html#aws-sqs-queue-deduplicationscope", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "DelaySeconds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sqs-queues.html#aws-sqs-queue-delayseconds", "PrimitiveType": "Integer", @@ -92666,6 +92715,12 @@ "Required": false, "UpdateType": "Immutable" }, + "FifoThroughputLimit": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sqs-queues.html#aws-sqs-queue-fifothroughputlimit", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "KmsDataKeyReusePeriodSeconds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sqs-queues.html#aws-sqs-queue-kmsdatakeyreuseperiodseconds", "PrimitiveType": "Integer", @@ -93703,6 +93758,13 @@ "Required": true, "Type": "GitConfig", "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-coderepository.html#cfn-sagemaker-coderepository-tags", + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" } } }, From b75e41b56cf9c939584fb3bb2a2711387d5fd082 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Fri, 11 Jun 2021 14:14:37 +0100 Subject: [PATCH 03/34] chore(release): 1.108.1 --- CHANGELOG.md | 9 ++++++++- version.v1.json | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b8b3d4e4c906..2121b5a6d8653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,17 @@ 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.108.1](https://github.com/aws/aws-cdk/compare/v1.108.0...v1.108.1) (2021-06-11) + + +### Features + +* **cfnspec:** cloudformation spec v39.1.0 ([af74354](https://github.com/aws/aws-cdk/commit/af7435494ba938b036e85435b5dcb590082fc378)) + ## [1.108.0](https://github.com/aws/aws-cdk/compare/v1.107.0...v1.108.0) (2021-06-09) -### ⚠ BREAKING CHANGES +### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES * **cfnspec:** `imageScanningConfiguration` property of `ecr.CfnRepository` now accepts `scanOnPush` instead of `ScanOnPush` (notice the casing change). diff --git a/version.v1.json b/version.v1.json index 986a6e0396196..773fc3b689a17 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.108.0" + "version": "1.108.1" } From 29726fcab2faf422c0b925251f6ad30a5f74eff9 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Fri, 11 Jun 2021 15:02:00 +0100 Subject: [PATCH 04/34] chore: ignore asset hashes in integ test --- .../ecs-service-extensions/test/integ.imported-environment.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.ts index 899d9e4a4f7c4..8f6da80a87732 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import { Vpc } from '@aws-cdk/aws-ec2'; import { Cluster, ContainerImage } from '@aws-cdk/aws-ecs'; import { App, NestedStack, Stack } from '@aws-cdk/core'; From 92cadef4b969d4fd199042d926470d1abb1c734e Mon Sep 17 00:00:00 2001 From: Kaito Udagawa Date: Mon, 14 Jun 2021 22:56:27 +0900 Subject: [PATCH 05/34] chore(bootstrap): enable s3 versioning (#14987) It would be great to enable S3 versioning on CDK's staging bucket. This would be a safer option and consistent with UpdateReplacePolicy and DeletionPolicy being Retain. ---- *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/api/bootstrap/bootstrap-template.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml index eec900592e641..166e2fa6d4ba2 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml @@ -178,6 +178,8 @@ Resources: IgnorePublicAcls: true RestrictPublicBuckets: true - Ref: AWS::NoValue + VersioningConfiguration: + Status: Enabled UpdateReplacePolicy: Retain DeletionPolicy: Retain StagingBucketPolicy: From 0bc8a8ac2f4e593c31e64d3e4c2f8c54f9896663 Mon Sep 17 00:00:00 2001 From: Thorsten Hoeger Date: Mon, 14 Jun 2021 16:45:07 +0200 Subject: [PATCH 06/34] feat(pipelines): add test commands to standard synth actions (#14979) This makes the `testCommands` array available on the standard synth actions for npm and yarn. No README change as the options are not explicitly described there. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/synths/simple-synth-action.ts | 20 +++++++++++ .../@aws-cdk/pipelines/test/builds.test.ts | 35 ++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts b/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts index b43355789feb0..7393ca89f6cf3 100644 --- a/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts +++ b/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts @@ -474,6 +474,16 @@ export interface StandardNpmSynthOptions extends SimpleSynthOptions { * @default 'npx cdk synth' */ readonly synthCommand?: string; + + /** + * Test commands + * + * These commands are run after the build commands but before the + * synth command. + * + * @default - No test commands + */ + readonly testCommands?: string[]; } /** @@ -505,6 +515,16 @@ export interface StandardYarnSynthOptions extends SimpleSynthOptions { * @default 'npx cdk synth' */ readonly synthCommand?: string; + + /** + * Test commands + * + * These commands are run after the build commands but before the + * synth command. + * + * @default - No test commands + */ + readonly testCommands?: string[]; } function hash(obj: A) { diff --git a/packages/@aws-cdk/pipelines/test/builds.test.ts b/packages/@aws-cdk/pipelines/test/builds.test.ts index 1a8f9b5c41208..c15b42ab850fb 100644 --- a/packages/@aws-cdk/pipelines/test/builds.test.ts +++ b/packages/@aws-cdk/pipelines/test/builds.test.ts @@ -217,7 +217,7 @@ test('environmentVariables must be rendered in the action', () => { }); }); -test('complex setup with environemnt variables still renders correct project', () => { +test('complex setup with environment variables still renders correct project', () => { // WHEN new TestGitHubNpmPipeline(pipelineStack, 'Cdk', { sourceArtifact, @@ -298,6 +298,39 @@ test.each([['npm'], ['yarn']])('%s can have its install command overridden', (np }); }); +test.each([['npm'], ['yarn']])('%s can have its test commands set', (npmYarn) => { + // WHEN + new TestGitHubNpmPipeline(pipelineStack, 'Cdk', { + sourceArtifact, + cloudAssemblyArtifact, + synthAction: npmYarnBuild(npmYarn)({ + sourceArtifact, + cloudAssemblyArtifact, + installCommand: '/bin/true', + testCommands: ['echo "Running tests"'], + }), + }); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Environment: { + Image: 'aws/codebuild/standard:5.0', + }, + Source: { + BuildSpec: encodedJson(objectLike({ + phases: { + pre_build: { + commands: ['/bin/true'], + }, + build: { + commands: ['echo "Running tests"', 'npx cdk synth'], + }, + }, + })), + }, + }); +}); + test('Standard (NPM) synth can output additional artifacts', () => { // WHEN const addlArtifact = new codepipeline.Artifact('IntegTest'); From 0189a9af921dcaffab8a44868be27df0608503d6 Mon Sep 17 00:00:00 2001 From: Anthony Lukach Date: Mon, 14 Jun 2021 09:41:26 -0600 Subject: [PATCH 07/34] feat(core): Support platform flag during asset build (#14908) Fixes https://github.com/aws/aws-cdk/issues/12472 Adds an option to specify the platform for the `docker build` command would allow users to build for other targets (e.g. Apple M1 users can build for amd64 architectures) ---- *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/lib/bundling.ts | 10 ++++++ packages/@aws-cdk/core/test/bundling.test.ts | 37 ++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/packages/@aws-cdk/core/lib/bundling.ts b/packages/@aws-cdk/core/lib/bundling.ts index 24af4f1b3139f..232c986b8b212 100644 --- a/packages/@aws-cdk/core/lib/bundling.ts +++ b/packages/@aws-cdk/core/lib/bundling.ts @@ -259,6 +259,7 @@ export class DockerImage extends BundlingDockerImage { const dockerArgs: string[] = [ 'build', '-t', tag, ...(options.file ? ['-f', join(path, options.file)] : []), + ...(options.platform ? ['--platform', options.platform] : []), ...flatten(Object.entries(buildArgs).map(([k, v]) => ['--build-arg', `${k}=${v}`])), path, ]; @@ -423,6 +424,15 @@ export interface DockerBuildOptions { * @default `Dockerfile` */ readonly file?: string; + + /** + * Set platform if server is multi-platform capable. _Requires Docker Engine API v1.38+_. + * + * @example 'linux/amd64' + * + * @default - no platform specified + */ + readonly platform?: string; } function flatten(x: string[][]) { diff --git a/packages/@aws-cdk/core/test/bundling.test.ts b/packages/@aws-cdk/core/test/bundling.test.ts index 97b9d5969c2b3..28f150f7b7d66 100644 --- a/packages/@aws-cdk/core/test/bundling.test.ts +++ b/packages/@aws-cdk/core/test/bundling.test.ts @@ -88,6 +88,43 @@ nodeunitShim({ test.done(); }, + 'bundling with image from asset with platform'(test: Test) { + const spawnSyncStub = sinon.stub(child_process, 'spawnSync').returns({ + status: 0, + stderr: Buffer.from('stderr'), + stdout: Buffer.from('stdout'), + pid: 123, + output: ['stdout', 'stderr'], + signal: null, + }); + + const imageHash = '123456abcdef'; + const fingerprintStub = sinon.stub(FileSystem, 'fingerprint'); + fingerprintStub.callsFake(() => imageHash); + const platform = 'linux/someArch99'; + + const image = DockerImage.fromBuild('docker-path', { platform }); + image.run(); + + const tagHash = crypto.createHash('sha256').update(JSON.stringify({ + path: 'docker-path', + platform, + })).digest('hex'); + const tag = `cdk-${tagHash}`; + + test.ok(spawnSyncStub.firstCall.calledWith('docker', [ + 'build', '-t', tag, + '--platform', platform, + 'docker-path', + ])); + + test.ok(spawnSyncStub.secondCall.calledWith('docker', [ + 'run', '--rm', + tag, + ])); + test.done(); + }, + 'throws in case of spawnSync error'(test: Test) { sinon.stub(child_process, 'spawnSync').returns({ status: 0, From 23c58d6908ae56d2ea3328bf2beef1a8c0ac4e76 Mon Sep 17 00:00:00 2001 From: Masaharu Komuro Date: Tue, 15 Jun 2021 01:31:41 +0900 Subject: [PATCH 08/34] fix(cli): HTTP timeout is too low for some asset uploads (#13575) Close #13183 I checked the code base and I understacd that the setting parameter when construct `SdkProvider` in cdk.ts is used when construct actual sdk instance. If there are no problems with my implementation, I would like to write a test and complete the PR. ---- *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/api/aws-auth/sdk-provider.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts index 62484006583ab..1b72f9ee80035 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts @@ -348,6 +348,9 @@ export interface Account { readonly partition: string; } +const DEFAULT_CONNECTION_TIMEOUT = 10000; +const DEFAULT_TIMEOUT = 300000; + /** * Get HTTP options for the SDK * @@ -360,6 +363,9 @@ function parseHttpOptions(options: SdkHttpOptions) { const config: ConfigurationOptions = {}; config.httpOptions = {}; + config.httpOptions.connectTimeout = DEFAULT_CONNECTION_TIMEOUT; + config.httpOptions.timeout = DEFAULT_TIMEOUT; + let userAgent = options.userAgent; if (userAgent == null) { // Find the package.json from the main toolkit From cbd755298f6ab73e604b96cab7d1e7c5f0dd8af1 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Mon, 14 Jun 2021 18:17:53 +0100 Subject: [PATCH 09/34] fix(pipelines): self-update role assumes hard-coded role names (#14969) Right now, the selfupdate action assumes hardcoded `*-publishing-role-*` and `*-deploy-role-*` roles, because we can't know the actual role names at the time the policies are being created. This means pipelines' self-update is unusable for customers with custom bootstrap role names. To solve this -- and other -- issue(s), tag the roles to identify them, and change out the policy to use these tags instead. This requires bumping the bootstrap stack template version, and the minimum required version in the default synthesizer. fixes #14877 related #9271 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../stack-synthesizers/default-synthesizer.ts | 4 +- .../new-style-synthesis.test.ts | 2 +- .../lib/actions/update-pipeline-action.ts | 8 +- .../actions/update-pipeline-action.test.ts | 50 ++++++++ ...ne-with-assets-single-upload.expected.json | 110 ++++++++++-------- .../integ.pipeline-with-assets.expected.json | 20 +++- .../test/integ.pipeline.expected.json | 20 +++- .../lib/api/bootstrap/bootstrap-template.yaml | 9 ++ 8 files changed, 156 insertions(+), 67 deletions(-) create mode 100644 packages/@aws-cdk/pipelines/test/actions/update-pipeline-action.test.ts diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts index 1dc8ff754c99a..3030b687f1244 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts @@ -19,7 +19,7 @@ export const BOOTSTRAP_QUALIFIER_CONTEXT = '@aws-cdk/core:bootstrapQualifier'; /** * The minimum bootstrap stack version required by this app. */ -const MIN_BOOTSTRAP_STACK_VERSION = 4; +const MIN_BOOTSTRAP_STACK_VERSION = 6; /** * Configuration properties for DefaultStackSynthesizer @@ -643,4 +643,4 @@ function validateDockerImageAssetSource(asset: DockerImageAssetSource) { throw new Error(`'${key}' is only allowed in combination with 'directoryName', got: ${JSON.stringify(asset)}`); } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts b/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts index 2bd29743f6941..93ba7be190e48 100644 --- a/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts +++ b/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts @@ -76,7 +76,7 @@ nodeunitShim({ test.deepEqual(assertions.length, 1); test.deepEqual(assertions[0].Assert, { 'Fn::Not': [ - { 'Fn::Contains': [['1', '2', '3'], { Ref: 'BootstrapVersion' }] }, + { 'Fn::Contains': [['1', '2', '3', '4', '5'], { Ref: 'BootstrapVersion' }] }, ], }); diff --git a/packages/@aws-cdk/pipelines/lib/actions/update-pipeline-action.ts b/packages/@aws-cdk/pipelines/lib/actions/update-pipeline-action.ts index c2bf636f02ca1..7a6aba1c41bd5 100644 --- a/packages/@aws-cdk/pipelines/lib/actions/update-pipeline-action.ts +++ b/packages/@aws-cdk/pipelines/lib/actions/update-pipeline-action.ts @@ -3,6 +3,7 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as cpactions from '@aws-cdk/aws-codepipeline-actions'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; +import { Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { embeddedAsmPath } from '../private/construct-internals'; @@ -97,7 +98,12 @@ export class UpdatePipelineAction extends CoreConstruct implements codepipeline. // allow the self-mutating project permissions to assume the bootstrap Action role selfMutationProject.addToRolePolicy(new iam.PolicyStatement({ actions: ['sts:AssumeRole'], - resources: ['arn:*:iam::*:role/*-deploy-role-*', 'arn:*:iam::*:role/*-publishing-role-*'], + resources: [`arn:*:iam::${Stack.of(this).account}:role/*`], + conditions: { + 'ForAnyValue:StringEquals': { + 'iam:ResourceTag/aws-cdk:bootstrap-role': ['image-publishing', 'file-publishing', 'deploy'], + }, + }, })); selfMutationProject.addToRolePolicy(new iam.PolicyStatement({ actions: ['cloudformation:DescribeStacks'], diff --git a/packages/@aws-cdk/pipelines/test/actions/update-pipeline-action.test.ts b/packages/@aws-cdk/pipelines/test/actions/update-pipeline-action.test.ts new file mode 100644 index 0000000000000..536df4e73086e --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/actions/update-pipeline-action.test.ts @@ -0,0 +1,50 @@ +import { + arrayWith, +} from '@aws-cdk/assert-internal'; +import '@aws-cdk/assert-internal/jest'; +import * as cp from '@aws-cdk/aws-codepipeline'; +import { Stack } from '@aws-cdk/core'; +import * as cdkp from '../../lib'; +import { TestApp } from '../testutil'; + +let app: TestApp; +let pipelineStack: Stack; + +test('self-update project role has proper permissions', () => { + app = new TestApp(); + pipelineStack = new Stack(app, 'PipelineStack'); + + new cdkp.UpdatePipelineAction(pipelineStack, 'Update', { + cloudAssemblyInput: new cp.Artifact(), + pipelineStackHierarchicalId: pipelineStack.node.path, + projectName: 'pipeline-selfupdate', + }); + + expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: arrayWith( + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Resource: { 'Fn::Join': ['', ['arn:*:iam::', { Ref: 'AWS::AccountId' }, ':role/*']] }, + Condition: { + 'ForAnyValue:StringEquals': { + 'iam:ResourceTag/aws-cdk:bootstrap-role': ['image-publishing', 'file-publishing', 'deploy'], + }, + }, + }, + { + Action: 'cloudformation:DescribeStacks', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 's3:ListBucket', + Effect: 'Allow', + Resource: '*', + }, + ), + }, + }); + +}); diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json index 2e46bbe95546b..e0d1ca7c3cb12 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json @@ -1,36 +1,4 @@ { - "Parameters": { - "BootstrapVersion": { - "Type": "AWS::SSM::Parameter::Value", - "Default": "/cdk-bootstrap/hnb659fds/version", - "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store." - } - }, - "Rules": { - "CheckBootstrapVersion": { - "Assertions": [ - { - "Assert": { - "Fn::Not": [ - { - "Fn::Contains": [ - [ - "1", - "2", - "3" - ], - { - "Ref": "BootstrapVersion" - } - ] - } - ] - }, - "AssertDescription": "CDK bootstrap stack version 4 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." - } - ] - } - }, "Resources": { "PipelineArtifactsBucketEncryptionKeyF5BF0670": { "Type": "AWS::KMS::Key", @@ -63,6 +31,20 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "PipelineArtifactsBucketEncryptionKeyAlias94A07392": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/codepipeline-pipelinestackpipelinee95eedaa", + "TargetKeyId": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "PipelineArtifactsBucketAEA9A052": { "Type": "AWS::S3::Bucket", "Properties": { @@ -91,20 +73,6 @@ "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, - "PipelineArtifactsBucketEncryptionKeyAlias94A07392": { - "Type": "AWS::KMS::Alias", - "Properties": { - "AliasName": "alias/codepipeline-pipelinestackpipelinee95eedaa", - "TargetKeyId": { - "Fn::GetAtt": [ - "PipelineArtifactsBucketEncryptionKeyF5BF0670", - "Arn" - ] - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "PipelineRoleB27FAA37": { "Type": "AWS::IAM::Role", "Properties": { @@ -1166,11 +1134,17 @@ }, { "Action": "sts:AssumeRole", + "Condition": { + "ForAnyValue:StringEquals": { + "iam:ResourceTag/aws-cdk:bootstrap-role": [ + "image-publishing", + "file-publishing", + "deploy" + ] + } + }, "Effect": "Allow", - "Resource": [ - "arn:*:iam::*:role/*-deploy-role-*", - "arn:*:iam::*:role/*-publishing-role-*" - ] + "Resource": "arn:*:iam::12345678:role/*" }, { "Action": "cloudformation:DescribeStacks", @@ -1457,5 +1431,39 @@ } } } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store." + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json index 9d869296f2148..5d58f648d7fbb 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json @@ -1160,11 +1160,17 @@ }, { "Action": "sts:AssumeRole", + "Condition": { + "ForAnyValue:StringEquals": { + "iam:ResourceTag/aws-cdk:bootstrap-role": [ + "image-publishing", + "file-publishing", + "deploy" + ] + } + }, "Effect": "Allow", - "Resource": [ - "arn:*:iam::*:role/*-deploy-role-*", - "arn:*:iam::*:role/*-publishing-role-*" - ] + "Resource": "arn:*:iam::12345678:role/*" }, { "Action": "cloudformation:DescribeStacks", @@ -1501,7 +1507,9 @@ [ "1", "2", - "3" + "3", + "4", + "5" ], { "Ref": "BootstrapVersion" @@ -1510,7 +1518,7 @@ } ] }, - "AssertDescription": "CDK bootstrap stack version 4 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." } ] } diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json index 304126cf148e6..06619c012f73e 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json @@ -1093,11 +1093,17 @@ }, { "Action": "sts:AssumeRole", + "Condition": { + "ForAnyValue:StringEquals": { + "iam:ResourceTag/aws-cdk:bootstrap-role": [ + "image-publishing", + "file-publishing", + "deploy" + ] + } + }, "Effect": "Allow", - "Resource": [ - "arn:*:iam::*:role/*-deploy-role-*", - "arn:*:iam::*:role/*-publishing-role-*" - ] + "Resource": "arn:*:iam::12345678:role/*" }, { "Action": "cloudformation:DescribeStacks", @@ -1228,7 +1234,9 @@ [ "1", "2", - "3" + "3", + "4", + "5" ], { "Ref": "BootstrapVersion" @@ -1237,7 +1245,7 @@ } ] }, - "AssertDescription": "CDK bootstrap stack version 4 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." } ] } diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml index 166e2fa6d4ba2..813fcc5d4f063 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml @@ -227,6 +227,9 @@ Resources: - Ref: AWS::NoValue RoleName: Fn::Sub: cdk-${Qualifier}-file-publishing-role-${AWS::AccountId}-${AWS::Region} + Tags: + - Key: aws-cdk:bootstrap-role + Value: file-publishing ImagePublishingRole: Type: AWS::IAM::Role Properties: @@ -247,6 +250,9 @@ Resources: - Ref: AWS::NoValue RoleName: Fn::Sub: cdk-${Qualifier}-image-publishing-role-${AWS::AccountId}-${AWS::Region} + Tags: + - Key: aws-cdk:bootstrap-role + Value: image-publishing LookupRole: Type: AWS::IAM::Role Properties: @@ -430,6 +436,9 @@ Resources: PolicyName: default RoleName: Fn::Sub: cdk-${Qualifier}-deploy-role-${AWS::AccountId}-${AWS::Region} + Tags: + - Key: aws-cdk:bootstrap-role + Value: deploy CloudFormationExecutionRole: Type: AWS::IAM::Role Properties: From 7ea9e4882119eca5273a3da45f8fd4f8785d5bbe Mon Sep 17 00:00:00 2001 From: Hsing-Hui Hsu Date: Mon, 14 Jun 2021 11:12:52 -0700 Subject: [PATCH 10/34] refactor(aws-ecs): DRY up propagateTaskTagsFrom (#15105) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 17 +++++++++++- .../aws-ecs/lib/base/task-definition.ts | 2 +- .../@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts | 18 +------------ .../aws-ecs/lib/fargate/fargate-service.ts | 20 +------------- .../aws-ecs/test/ec2/ec2-service.test.ts | 15 ++++++----- .../test/fargate/fargate-service.test.ts | 26 ++++++++++++++++++- 6 files changed, 52 insertions(+), 46 deletions(-) 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 d4023a4df6f9e..c33718905c5bb 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -159,6 +159,15 @@ export interface BaseServiceOptions { */ readonly propagateTags?: PropagatedTagSource; + /** + * Specifies whether to propagate the tags from the task definition or the service to the tasks in the service. + * Tags can only be propagated to the tasks within the service during service creation. + * + * @deprecated Use `propagateTags` instead. + * @default PropagatedTagSource.NONE + */ + readonly propagateTaskTagsFrom?: PropagatedTagSource; + /** * Specifies whether to enable Amazon ECS managed tags for the tasks within the service. For more information, see * [Tagging Your Amazon ECS Resources](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-using-tags.html) @@ -373,6 +382,10 @@ export abstract class BaseService extends Resource physicalName: props.serviceName, }); + if (props.propagateTags && props.propagateTaskTagsFrom) { + throw new Error('You can only specify either propagateTags or propagateTaskTagsFrom. Alternatively, you can leave both blank'); + } + this.taskDefinition = taskDefinition; // launchType will set to undefined if using external DeploymentController or capacityProviderStrategies @@ -380,6 +393,8 @@ export abstract class BaseService extends Resource props.capacityProviderStrategies !== undefined ? undefined : props.launchType; + const propagateTagsFromSource = props.propagateTaskTagsFrom ?? props.propagateTags ?? PropagatedTagSource.NONE; + this.resource = new CfnService(this, 'Service', { desiredCount: props.desiredCount, serviceName: this.physicalName, @@ -392,7 +407,7 @@ export abstract class BaseService extends Resource rollback: props.circuitBreaker.rollback ?? false, } : undefined, }, - propagateTags: props.propagateTags === PropagatedTagSource.NONE ? undefined : props.propagateTags, + propagateTags: propagateTagsFromSource === PropagatedTagSource.NONE ? undefined : props.propagateTags, enableEcsManagedTags: props.enableECSManagedTags ?? false, deploymentController: props.circuitBreaker ? { type: DeploymentControllerType.ECS, diff --git a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts index 0cc89d633160f..2889c4d94df3d 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -584,7 +584,7 @@ export class TaskDefinition extends TaskDefinitionBase { } /** - * Adds the specified extention to the task definition. + * Adds the specified extension to the task definition. * * Extension can be used to apply a packaged modification to * a task definition. diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index 4cf4de8a83292..df2ad7819543c 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -1,7 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import { Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { BaseService, BaseServiceOptions, DeploymentControllerType, IBaseService, IService, LaunchType, PropagatedTagSource } from '../base/base-service'; +import { BaseService, BaseServiceOptions, DeploymentControllerType, IBaseService, IService, LaunchType } from '../base/base-service'; import { fromServiceAtrributes } from '../base/from-service-attributes'; import { NetworkMode, TaskDefinition } from '../base/task-definition'; import { ICluster } from '../cluster'; @@ -82,15 +82,6 @@ export interface Ec2ServiceProps extends BaseServiceOptions { * @default false */ readonly daemon?: boolean; - - /** - * Specifies whether to propagate the tags from the task definition or the service to the tasks in the service. - * Tags can only be propagated to the tasks within the service during service creation. - * - * @deprecated Use `propagateTags` instead. - * @default PropagatedTagSource.NONE - */ - readonly propagateTaskTagsFrom?: PropagatedTagSource; } /** @@ -173,23 +164,16 @@ export class Ec2Service extends BaseService implements IEc2Service { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } - if (props.propagateTags && props.propagateTaskTagsFrom) { - throw new Error('You can only specify either propagateTags or propagateTaskTagsFrom. Alternatively, you can leave both blank'); - } - if (props.securityGroup !== undefined && props.securityGroups !== undefined) { throw new Error('Only one of SecurityGroup or SecurityGroups can be populated.'); } - const propagateTagsFromSource = props.propagateTaskTagsFrom ?? props.propagateTags ?? PropagatedTagSource.NONE; - super(scope, id, { ...props, desiredCount: props.desiredCount, maxHealthyPercent: props.daemon && props.maxHealthyPercent === undefined ? 100 : props.maxHealthyPercent, minHealthyPercent: props.daemon && props.minHealthyPercent === undefined ? 0 : props.minHealthyPercent, launchType: LaunchType.EC2, - propagateTags: propagateTagsFromSource, enableECSManagedTags: props.enableECSManagedTags, }, { diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts index 793fb633e83d0..7979b98f0b0fa 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts @@ -1,7 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { BaseService, BaseServiceOptions, DeploymentControllerType, IBaseService, IService, LaunchType, PropagatedTagSource } from '../base/base-service'; +import { BaseService, BaseServiceOptions, DeploymentControllerType, IBaseService, IService, LaunchType } from '../base/base-service'; import { fromServiceAtrributes } from '../base/from-service-attributes'; import { TaskDefinition } from '../base/task-definition'; import { ICluster } from '../cluster'; @@ -58,16 +58,6 @@ export interface FargateServiceProps extends BaseServiceOptions { * @default Latest */ readonly platformVersion?: FargatePlatformVersion; - - /** - * Specifies whether to propagate the tags from the task definition or the service to the tasks in the service. - * Tags can only be propagated to the tasks within the service during service creation. - * - * @deprecated Use `propagateTags` instead. - * @default PropagatedTagSource.NONE - */ - readonly propagateTaskTagsFrom?: PropagatedTagSource; - } /** @@ -134,10 +124,6 @@ export class FargateService extends BaseService implements IFargateService { throw new Error('Supplied TaskDefinition is not configured for compatibility with Fargate'); } - if (props.propagateTags && props.propagateTaskTagsFrom) { - throw new Error('You can only specify either propagateTags or propagateTaskTagsFrom. Alternatively, you can leave both blank'); - } - if (props.securityGroup !== undefined && props.securityGroups !== undefined) { throw new Error('Only one of SecurityGroup or SecurityGroups can be populated.'); } @@ -147,15 +133,11 @@ export class FargateService extends BaseService implements IFargateService { && SECRET_JSON_FIELD_UNSUPPORTED_PLATFORM_VERSIONS.includes(props.platformVersion)) { throw new Error(`The task definition of this service uses at least one container that references a secret JSON field. This feature requires platform version ${FargatePlatformVersion.VERSION1_4} or later.`); } - - const propagateTagsFromSource = props.propagateTaskTagsFrom ?? props.propagateTags ?? PropagatedTagSource.NONE; - super(scope, id, { ...props, desiredCount: props.desiredCount, launchType: LaunchType.FARGATE, capacityProviderStrategies: props.capacityProviderStrategies, - propagateTags: propagateTagsFromSource, enableECSManagedTags: props.enableECSManagedTags, }, { cluster: props.cluster.clusterName, diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts index f53f66d8755ad..7e942c866b213 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts @@ -1859,13 +1859,14 @@ nodeunitShim({ memoryLimitMiB: 512, }); - test.throws(() => new ecs.Ec2Service(stack, 'Ec2Service', { - cluster, - taskDefinition, - propagateTags: PropagatedTagSource.SERVICE, - propagateTaskTagsFrom: PropagatedTagSource.SERVICE, - })); - + test.throws(() => { + new ecs.Ec2Service(stack, 'Ec2Service', { + cluster, + taskDefinition, + propagateTags: PropagatedTagSource.SERVICE, + propagateTaskTagsFrom: PropagatedTagSource.SERVICE, + }); + }, /You can only specify either propagateTags or propagateTaskTagsFrom. Alternatively, you can leave both blank/); test.done(); }, diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index f1fdbedc01064..f7c33eb7b8af9 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 @@ -11,7 +11,7 @@ import * as cloudmap from '@aws-cdk/aws-servicediscovery'; import * as cdk from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; import * as ecs from '../../lib'; -import { DeploymentControllerType, LaunchType } from '../../lib/base/base-service'; +import { DeploymentControllerType, LaunchType, PropagatedTagSource } from '../../lib/base/base-service'; nodeunitShim({ 'When creating a Fargate Service': { @@ -2948,5 +2948,29 @@ nodeunitShim({ test.done(); }, + + 'with both propagateTags and propagateTaskTagsFrom defined'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + // THEN + test.throws(() => { + new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + propagateTags: PropagatedTagSource.SERVICE, + propagateTaskTagsFrom: PropagatedTagSource.SERVICE, + }); + }, /You can only specify either propagateTags or propagateTaskTagsFrom. Alternatively, you can leave both blank/); + test.done(); + }, }, }); From ed9b07a1dc10da23f0ff517a8249cccbd65ff754 Mon Sep 17 00:00:00 2001 From: MyannaHarris Date: Mon, 14 Jun 2021 12:06:35 -0700 Subject: [PATCH 11/34] docs(eks): minimum size of managed node group can now also be zero (#15103) Update CDK property doc to reflect new minimum value for EKS managed nodegroup * Unit Tests : NA * Integration Tests : NA ---- *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-eks/lib/managed-nodegroup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts b/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts index 859a94d9aee67..99b7d9ce8e0da 100644 --- a/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts +++ b/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts @@ -132,7 +132,7 @@ export interface NodegroupOptions { */ readonly maxSize?: number; /** - * The minimum number of worker nodes that the managed node group can scale in to. This number must be greater than zero. + * The minimum number of worker nodes that the managed node group can scale in to. This number must be greater than or equal to zero. * * @default 1 */ From 8ad33b8b1ca23b46bd40e768f0fc44e113ea84e7 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Mon, 14 Jun 2021 14:12:55 -0700 Subject: [PATCH 12/34] fix(cfn-include): NestedStack's Parameters are not converted to strings (#15098) When we include a NestedStack when creating a CfnInclude instance, the conversion of the Parameters property of the AWS::CloudFormation::Stack resource is performed manually (because of the eventuality that one of those Parameters was also requested to be removed when including the nested stack). In that manual conversion, we did not correctly convert the values to strings, which is what the underlying CfnStack class expects. And so, if the parent stack passed a non-string primitive value to a nested stack Parameter, like a number or boolean, including the nested stack would fail with a validation exception. Fixes #15092 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../cloudformation-include/lib/cfn-include.ts | 16 +++-- .../test/nested-stacks.test.ts | 63 ++++++++++++++++--- .../nested/child-with-number-parameter.yaml | 9 +++ .../nested/parent-number-in-child-params.yaml | 8 +++ .../nested/parent-with-attributes.json | 15 ++++- 5 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/nested/child-with-number-parameter.yaml create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-number-in-child-params.yaml diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index 5a54d36aefcc8..957cb68594128 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -682,8 +682,8 @@ export class CfnInclude extends core.CfnElement { const nestedStackProps = cfnParser.parseValue(nestedStackAttributes.Properties); const nestedStack = new core.NestedStack(this, nestedStackId, { parameters: this.parametersForNestedStack(nestedStackProps.Parameters, nestedStackId), - notificationArns: nestedStackProps.NotificationArns, - timeout: nestedStackProps.Timeout, + notificationArns: cfn_parse.FromCloudFormation.getStringArray(nestedStackProps.NotificationARNs).value, + timeout: this.timeoutForNestedStack(nestedStackProps.TimeoutInMinutes), }); const template = new CfnInclude(nestedStack, nestedStackId, this.nestedStacksToInclude[nestedStackId]); this.nestedStacks[nestedStackId] = { stack: nestedStack, includedTemplate: template }; @@ -694,7 +694,7 @@ export class CfnInclude extends core.CfnElement { return nestedStackResource; } - private parametersForNestedStack(parameters: any, nestedStackId: string): { [key: string]: any } | undefined { + private parametersForNestedStack(parameters: any, nestedStackId: string): { [key: string]: string } | undefined { if (parameters == null) { return undefined; } @@ -703,12 +703,20 @@ export class CfnInclude extends core.CfnElement { const ret: { [key: string]: string } = {}; for (const paramName of Object.keys(parameters)) { if (!(paramName in parametersToReplace)) { - ret[paramName] = parameters[paramName]; + ret[paramName] = cfn_parse.FromCloudFormation.getString(parameters[paramName]).value; } } return ret; } + private timeoutForNestedStack(value: any): core.Duration | undefined { + if (value == null) { + return undefined; + } + + return core.Duration.minutes(cfn_parse.FromCloudFormation.getNumber(value).value); + } + private overrideLogicalIdIfNeeded(element: core.CfnElement, id: string): void { if (this.preserveLogicalIds) { element.overrideLogicalId(id); diff --git a/packages/@aws-cdk/cloudformation-include/test/nested-stacks.test.ts b/packages/@aws-cdk/cloudformation-include/test/nested-stacks.test.ts index 1a0d9763280ca..2a323657f089e 100644 --- a/packages/@aws-cdk/cloudformation-include/test/nested-stacks.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/nested-stacks.test.ts @@ -334,6 +334,28 @@ describe('CDK Include for nested stacks', () => { }).toThrow(/Nested Stack 'AnotherChildStack' was not included in the parent template/); }); + test('correctly handles references in nested stacks Parameters', () => { + new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('cross-stack-refs.json'), + loadNestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('child-import-stack.json'), + }, + }, + }); + + expect(stack).toHaveResourceLike('AWS::CloudFormation::Stack', { + "Parameters": { + "Param1": { + "Ref": "Param", + }, + "Param2": { + "Fn::GetAtt": ["Bucket", "Arn"], + }, + }, + }); + }); + test('correctly handles renaming of references across nested stacks', () => { const parentTemplate = new inc.CfnInclude(stack, 'ParentStack', { templateFile: testTemplateFilePath('cross-stack-refs.json'), @@ -386,15 +408,12 @@ describe('CDK Include for nested stacks', () => { }); test("handles Metadata, DeletionPolicy, and UpdateReplacePolicy attributes of the nested stack's resource", () => { - const cfnTemplate = new inc.CfnInclude(stack, 'ParentStack', { + new inc.CfnInclude(stack, 'ParentStack', { templateFile: testTemplateFilePath('parent-with-attributes.json'), loadNestedStacks: { 'ChildStack': { templateFile: testTemplateFilePath('child-import-stack.json'), }, - 'AnotherChildStack': { - templateFile: testTemplateFilePath('child-import-stack.json'), - }, }, }); @@ -408,20 +427,50 @@ describe('CDK Include for nested stacks', () => { ], "UpdateReplacePolicy": "Retain", }, ResourcePart.CompleteDefinition); - - cfnTemplate.getNestedStack('AnotherChildStack'); }); test('correctly parses NotificationsARNs, Timeout', () => { new inc.CfnInclude(stack, 'ParentStack', { templateFile: testTemplateFilePath('parent-with-attributes.json'), + loadNestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('custom-resource.json'), + }, + 'AnotherChildStack': { + templateFile: testTemplateFilePath('custom-resource.json'), + }, + }, }); expect(stack).toHaveResourceLike('AWS::CloudFormation::Stack', { - "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/child-import-stack.json", "NotificationARNs": ["arn1"], "TimeoutInMinutes": 5, }); + expect(stack).toHaveResourceLike('AWS::CloudFormation::Stack', { + "NotificationARNs": { "Ref": "ArrayParam" }, + "TimeoutInMinutes": { + "Fn::Select": [0, { + "Ref": "ArrayParam", + }], + }, + }); + }); + + test('can ingest a NestedStack with a Number CFN Parameter passed as a number', () => { + new inc.CfnInclude(stack, 'MyScope', { + templateFile: testTemplateFilePath('parent-number-in-child-params.yaml'), + loadNestedStacks: { + 'NestedStack': { + templateFile: testTemplateFilePath('child-with-number-parameter.yaml'), + }, + }, + }); + + expect(stack).toHaveResourceLike('AWS::CloudFormation::Stack', { + "Parameters": { + "Number": "60", + }, + }); }); test('can lazily include a single child nested stack', () => { diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/child-with-number-parameter.yaml b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/child-with-number-parameter.yaml new file mode 100644 index 0000000000000..0101bc0c91baa --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/child-with-number-parameter.yaml @@ -0,0 +1,9 @@ +AWSTemplateFormatVersion: '2010-09-09' +Parameters: + Number: + Type: Number +Resources: + S3Bucket: + Type: AWS::S3::Bucket + Properties: + BucketName: 'testbucket1234unique' diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-number-in-child-params.yaml b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-number-in-child-params.yaml new file mode 100644 index 0000000000000..10ad404e4dc44 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-number-in-child-params.yaml @@ -0,0 +1,8 @@ +AWSTemplateFormatVersion: '2010-09-09' +Resources: + NestedStack: + Type: AWS::CloudFormation::Stack + Properties: + TemplateURL: 'https://s3.amazonaws.com/masonme-cdk-test/templates/nested-bucket.yaml' + Parameters: + Number: 60 diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-with-attributes.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-with-attributes.json index c12bd223063c1..8307976b35ba1 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-with-attributes.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-with-attributes.json @@ -1,9 +1,20 @@ { + "Parameters": { + "ArrayParam": { + "Type": "CommaDelimitedList" + } + }, "Resources": { "ChildStack": { "Type": "AWS::CloudFormation::Stack", "Properties": { - "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/child-import-stack.json" + "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/child-import-stack.json", + "NotificationARNs": { "Ref": "ArrayParam" }, + "TimeoutInMinutes": { + "Fn::Select": [0, { + "Ref": "ArrayParam" + }] + } }, "DependsOn": [ "AnotherChildStack" @@ -23,4 +34,4 @@ } } } -} \ No newline at end of file +} From 9d01b4fabdf50a1e6691c054a674d768e5816a3c Mon Sep 17 00:00:00 2001 From: Berend de Boer Date: Tue, 15 Jun 2021 10:29:24 +1200 Subject: [PATCH 13/34] fix(codebuild): Project's Role has permissions to the entire Bucket when using S3 as the source (#15112) The CodeBuild role was given read permissions to the entire bucket. Limit that to just the source path it needs. ---- *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-codebuild/README.md | 4 +++- packages/@aws-cdk/aws-codebuild/lib/source.ts | 2 +- .../aws-codebuild/test/integ.project-bucket.expected.json | 2 +- .../integ.project-secondary-sources-artifacts.expected.json | 2 +- packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 2fb82c887ba39..3c26927fd39ce 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -76,12 +76,14 @@ import * as s3 from '@aws-cdk/aws-s3'; const bucket = new s3.Bucket(this, 'MyBucket'); new codebuild.Project(this, 'MyProject', { source: codebuild.Source.s3({ - bucket, + bucket: bucket, path: 'path/to/file.zip', }), }); ``` +The CodeBuild role will be granted to read just the given path from the given `bucket`. + ### `GitHubSource` and `GitHubEnterpriseSource` These source types can be used to build code from a GitHub repository. diff --git a/packages/@aws-cdk/aws-codebuild/lib/source.ts b/packages/@aws-cdk/aws-codebuild/lib/source.ts index a14026da98711..ce118ccec7fe6 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/source.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/source.ts @@ -623,7 +623,7 @@ class S3Source extends Source { } public bind(_scope: CoreConstruct, project: IProject): SourceConfig { - this.bucket.grantRead(project); + this.bucket.grantRead(project, this.path); const superConfig = super.bind(_scope, project); return { diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json index f8f5aa2e67aad..2177b1bbc0810 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json @@ -51,7 +51,7 @@ "Arn" ] }, - "/*" + "/path/to/my/source.zip" ] ] } diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.expected.json index c346580c024f1..b5e964b69bb80 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.expected.json @@ -51,7 +51,7 @@ "Arn" ] }, - "/*" + "/some/path" ] ] } diff --git a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts index fb28c29a1185d..b287b7de0b2b2 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts @@ -410,7 +410,7 @@ export = { 'Arn', ], }, - '/*', + '/path/to/source.zip', ], ], }, From 6bdf1c0382e4cce4e300a7ff50ddb9f2adf3d76b Mon Sep 17 00:00:00 2001 From: Hsing-Hui Hsu Date: Mon, 14 Jun 2021 18:05:08 -0700 Subject: [PATCH 14/34] feat(ecs-patterns): allow specifying security groups on ScheduledTask pattern (#15096) Closes #5213, #14220 ---- *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 | 19 +++++++ .../lib/base/scheduled-task-base.ts | 16 +++++- .../lib/fargate/scheduled-fargate-task.ts | 1 + .../test/ec2/test.scheduled-ecs-task.ts | 55 +++++++++++++++++++ .../fargate/test.scheduled-fargate-task.ts | 52 ++++++++++++++++++ 5 files changed, 142 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-ecs-patterns/README.md b/packages/@aws-cdk/aws-ecs-patterns/README.md index b6729d4385744..c39592f28cbb7 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/README.md +++ b/packages/@aws-cdk/aws-ecs-patterns/README.md @@ -499,6 +499,25 @@ const scheduledFargateTask = new ScheduledFargateTask(stack, 'ScheduledFargateTa }); ``` +### Set SecurityGroups for ScheduledFargateTask + +```ts +const stack = new cdk.Stack(); +const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); +const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); +const securityGroup = new ec2.SecurityGroup(stack, 'SG', { vpc }); + +const scheduledFargateTask = new ScheduledFargateTask(stack, 'ScheduledFargateTask', { + cluster, + scheduledFargateTaskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }, + schedule: events.Schedule.expression('rate(1 minute)'), + securityGroups: [securityGroup], +}); +``` + ### 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. diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/scheduled-task-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/scheduled-task-base.ts index 7dc6492f42919..acc2846988837 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/scheduled-task-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/scheduled-task-base.ts @@ -1,5 +1,5 @@ import { Schedule } from '@aws-cdk/aws-applicationautoscaling'; -import { IVpc, SubnetSelection, SubnetType } from '@aws-cdk/aws-ec2'; +import { ISecurityGroup, IVpc, SubnetSelection, SubnetType } from '@aws-cdk/aws-ec2'; import { AwsLogDriver, Cluster, ContainerImage, ICluster, LogDriver, Secret, TaskDefinition } from '@aws-cdk/aws-ecs'; import { Rule } from '@aws-cdk/aws-events'; import { EcsTask } from '@aws-cdk/aws-events-targets'; @@ -68,6 +68,13 @@ export interface ScheduledTaskBaseProps { * @default Private subnets */ readonly subnetSelection?: SubnetSelection; + + /** + * Existing security groups to use for your service. + * + * @default - a new security group will be created. + */ + readonly securityGroups?: ISecurityGroup[] } export interface ScheduledTaskImageProps { @@ -138,6 +145,11 @@ export abstract class ScheduledTaskBase extends CoreConstruct { */ public readonly eventRule: Rule; + /** + * The security group to use for the ECS Task. + */ + private readonly _securityGroups?: ISecurityGroup[]; + /** * Constructs a new instance of the ScheduledTaskBase class. */ @@ -150,6 +162,7 @@ export abstract class ScheduledTaskBase extends CoreConstruct { } this.desiredTaskCount = props.desiredTaskCount || 1; this.subnetSelection = props.subnetSelection || { subnetType: SubnetType.PRIVATE }; + this._securityGroups = props.securityGroups; // An EventRule that describes the event trigger (in this case a scheduled run) this.eventRule = new Rule(this, 'ScheduledEventRule', { @@ -171,6 +184,7 @@ export abstract class ScheduledTaskBase extends CoreConstruct { taskDefinition, taskCount: this.desiredTaskCount, subnetSelection: this.subnetSelection, + securityGroups: this._securityGroups, }); this.addTaskAsTarget(eventRuleTarget); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts index ab46883fa9c90..34360bd67f216 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts @@ -128,6 +128,7 @@ export class ScheduledFargateTask extends ScheduledTaskBase { taskCount: this.desiredTaskCount, subnetSelection: this.subnetSelection, platformVersion: props.platformVersion, + securityGroups: props.securityGroups, }); this.addTaskAsTarget(eventRuleTarget); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.scheduled-ecs-task.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.scheduled-ecs-task.ts index 2a6d94784bc5d..e270e7a0662f7 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.scheduled-ecs-task.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.scheduled-ecs-task.ts @@ -73,6 +73,7 @@ export = { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro'), }); @@ -141,6 +142,60 @@ export = { test.done(); }, + 'Scheduled ECS Task - with securityGroups defined'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { + networkMode: ecs.NetworkMode.AWS_VPC, + }); + const sg = new ec2.SecurityGroup(stack, 'MySG', { vpc }); + + new ScheduledEc2Task(stack, 'ScheduledEc2Task', { + cluster, + scheduledEc2TaskDefinitionOptions: { + taskDefinition, + }, + schedule: events.Schedule.expression('rate(1 minute)'), + securityGroups: [sg], + }); + + // THEN + expect(stack).to(haveResource('AWS::Events::Rule', { + Targets: [ + { + Arn: { 'Fn::GetAtt': ['EcsCluster97242B84', 'Arn'] }, + EcsParameters: { + LaunchType: 'EC2', + NetworkConfiguration: { + AwsVpcConfiguration: { + AssignPublicIp: 'DISABLED', + SecurityGroups: [{ + 'Fn::GetAtt': [ + 'MySG94FE69A8', + 'GroupId', + ], + }], + Subnets: [ + { + Ref: 'VpcPrivateSubnet1Subnet536B997A', + }, + ], + }, + }, + TaskCount: 1, + TaskDefinitionArn: { Ref: 'Ec2TaskDef0226F28C' }, + }, + Id: 'Target0', + Input: '{}', + RoleArn: { 'Fn::GetAtt': ['Ec2TaskDefEventsRoleA0756175', 'Arn'] }, + }, + ], + })); + + test.done(); + }, 'Scheduled Ec2 Task - with MemoryReservation defined'(test: Test) { // GIVEN diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.scheduled-fargate-task.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.scheduled-fargate-task.ts index 67370131045f3..491ed762d5f92 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.scheduled-fargate-task.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.scheduled-fargate-task.ts @@ -351,6 +351,58 @@ export = { ], })); + test.done(); + }, + 'Scheduled Fargate Task - with securityGroups defined'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); + + new ScheduledFargateTask(stack, 'ScheduledFargateTask', { + cluster, + scheduledFargateTaskImageOptions: { + image: ecs.ContainerImage.fromRegistry('henk'), + memoryLimitMiB: 512, + }, + schedule: events.Schedule.expression('rate(1 minute)'), + securityGroups: [sg], + }); + + // THEN + expect(stack).to(haveResource('AWS::Events::Rule', { + Targets: [ + { + Arn: { 'Fn::GetAtt': ['EcsCluster97242B84', 'Arn'] }, + EcsParameters: { + LaunchType: 'FARGATE', + NetworkConfiguration: { + AwsVpcConfiguration: { + AssignPublicIp: 'DISABLED', + SecurityGroups: [{ + 'Fn::GetAtt': [ + 'SGADB53937', + 'GroupId', + ], + }], + Subnets: [ + { + Ref: 'VpcPrivateSubnet1Subnet536B997A', + }, + ], + }, + }, + TaskCount: 1, + TaskDefinitionArn: { Ref: 'ScheduledFargateTaskScheduledTaskDef521FA675' }, + }, + Id: 'Target0', + Input: '{}', + RoleArn: { 'Fn::GetAtt': ['ScheduledFargateTaskScheduledTaskDefEventsRole6CE19522', 'Arn'] }, + }, + ], + })); + test.done(); }, }; From 486f2e5518ab5abb69a3e3986e4f3581aa42d15b Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Mon, 14 Jun 2021 18:51:41 -0700 Subject: [PATCH 15/34] fix(ecs): TagParameterContainerImage cannot be used across accounts (#15073) When TagParameterContainerImage is used from a different acocunt than the service itself is in, it fails resolution, because the Task's execution Role does not have a physical name set by default. Create it with PhysicalName.GENERATE_IF_NEEDED, so that it assigns a name when accessed from a cross-account context. Fixes #15070 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-ecs/lib/base/task-definition.ts | 4 +- .../tag-parameter-container-image.test.ts | 97 ++++++++++++++++++- 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts index 2889c4d94df3d..fe17ce6cb2317 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -1,6 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { IResource, Lazy, Names, Resource } from '@aws-cdk/core'; +import { IResource, Lazy, Names, PhysicalName, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { ContainerDefinition, ContainerDefinitionOptions, PortMapping, Protocol } from '../container-definition'; import { CfnTaskDefinition } from '../ecs.generated'; @@ -610,6 +610,8 @@ export class TaskDefinition extends TaskDefinitionBase { if (!this._executionRole) { this._executionRole = new iam.Role(this, 'ExecutionRole', { assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + // needed for cross-account access with TagParameterContainerImage + roleName: PhysicalName.GENERATE_IF_NEEDED, }); } return this._executionRole; diff --git a/packages/@aws-cdk/aws-ecs/test/images/tag-parameter-container-image.test.ts b/packages/@aws-cdk/aws-ecs/test/images/tag-parameter-container-image.test.ts index beaa9a23308c8..d782d39d874cb 100644 --- a/packages/@aws-cdk/aws-ecs/test/images/tag-parameter-container-image.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/images/tag-parameter-container-image.test.ts @@ -1,4 +1,4 @@ -import { SynthUtils } from '@aws-cdk/assert-internal'; +import { expect, haveResourceLike, SynthUtils } from '@aws-cdk/assert-internal'; import * as ecr from '@aws-cdk/aws-ecr'; import * as cdk from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; @@ -37,5 +37,100 @@ nodeunitShim({ test.done(); }, + + 'can be used in a cross-account manner'(test: Test) { + // GIVEN + const app = new cdk.App(); + const pipelineStack = new cdk.Stack(app, 'PipelineStack', { + env: { + account: 'pipeline-account', + region: 'us-west-1', + }, + }); + const repositoryName = 'my-ecr-repo'; + const repository = new ecr.Repository(pipelineStack, 'Repository', { + repositoryName: repositoryName, + }); + const tagParameterContainerImage = new ecs.TagParameterContainerImage(repository); + + const serviceStack = new cdk.Stack(app, 'ServiceStack', { + env: { + account: 'service-account', + region: 'us-west-1', + }, + }); + const fargateTaskDefinition = new ecs.FargateTaskDefinition(serviceStack, 'ServiceTaskDefinition'); + + // WHEN + fargateTaskDefinition.addContainer('Container', { + image: tagParameterContainerImage, + }); + + // THEN + expect(pipelineStack).to(haveResourceLike('AWS::ECR::Repository', { + RepositoryName: repositoryName, + RepositoryPolicyText: { + Statement: [{ + Action: [ + 'ecr:BatchCheckLayerAvailability', + 'ecr:GetDownloadUrlForLayer', + 'ecr:BatchGetImage', + ], + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::service-account:role/servicestackionexecutionrolee7e2d9a783a54eb795f4', + ]], + }, + }, + }], + }, + })); + expect(serviceStack).to(haveResourceLike('AWS::IAM::Role', { + RoleName: 'servicestackionexecutionrolee7e2d9a783a54eb795f4', + })); + expect(serviceStack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Image: { + 'Fn::Join': ['', [ + { + 'Fn::Select': [4, { + 'Fn::Split': [':', { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + `:ecr:us-west-1:pipeline-account:repository/${repositoryName}`, + ]], + }], + }], + }, + '.dkr.ecr.', + { + 'Fn::Select': [3, { + 'Fn::Split': [':', { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + `:ecr:us-west-1:pipeline-account:repository/${repositoryName}`, + ]], + }], + }], + }, + '.', + { Ref: 'AWS::URLSuffix' }, + `/${repositoryName}:`, + { Ref: 'ServiceTaskDefinitionContainerImageTagParamCEC9D0BA' }, + ]], + }, + }, + ], + })); + + test.done(); + }, }, }); From bad9713f99e7fe1e7b8da3baf1715aff3225df14 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Tue, 15 Jun 2021 13:58:06 +0100 Subject: [PATCH 16/34] fix(pipelines): PublishAssetsAction uses hard-coded role names (#15118) The role(s) created (or used) by PublishAssetsAction currently requests `sts:AssumeRole` on all asset publishing roles (`*-file-publishing-role-*` or `*-image-publishing-role-*`). This fix enables users with custom publishing role names to use pipelines, by pulling the asset publishing role names from the asset manifests. Tested manually against an existing pipeline. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/actions/publish-assets-action.ts | 11 +--- packages/@aws-cdk/pipelines/lib/pipeline.ts | 15 +++-- .../pipelines/lib/private/asset-manifest.ts | 8 +-- packages/@aws-cdk/pipelines/lib/stage.ts | 12 +++- ...ne-with-assets-single-upload.expected.json | 6 +- .../integ.pipeline-with-assets.expected.json | 8 ++- .../pipelines/test/pipeline-assets.test.ts | 65 +++++++++++-------- 7 files changed, 71 insertions(+), 54 deletions(-) diff --git a/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts b/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts index 53ddf1aa0352f..3b055baa3c8a3 100644 --- a/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts +++ b/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts @@ -8,11 +8,11 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import { Lazy, ISynthesisSession, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; +import { toPosixPath } from '../private/fs'; // v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. // eslint-disable-next-line import { Construct as CoreConstruct } from '@aws-cdk/core'; -import { toPosixPath } from '../private/fs'; /** * Type of the asset that is being published @@ -138,15 +138,6 @@ export class PublishAssetsAction extends CoreConstruct implements codepipeline.I role: props.role, }); - const rolePattern = props.assetType === AssetType.DOCKER_IMAGE - ? 'arn:*:iam::*:role/*-image-publishing-role-*' - : 'arn:*:iam::*:role/*-file-publishing-role-*'; - - project.addToRolePolicy(new iam.PolicyStatement({ - actions: ['sts:AssumeRole'], - resources: [rolePattern], - })); - this.action = new codepipeline_actions.CodeBuildAction({ actionName: props.actionName, project, diff --git a/packages/@aws-cdk/pipelines/lib/pipeline.ts b/packages/@aws-cdk/pipelines/lib/pipeline.ts index b1521324adeaf..906188dc7a8a5 100644 --- a/packages/@aws-cdk/pipelines/lib/pipeline.ts +++ b/packages/@aws-cdk/pipelines/lib/pipeline.ts @@ -2,7 +2,7 @@ import * as path from 'path'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { Annotations, App, Aws, CfnOutput, PhysicalName, Stack, Stage } from '@aws-cdk/core'; +import { Annotations, App, Aws, CfnOutput, Fn, Lazy, PhysicalName, Stack, Stage } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { AssetType, DeployCdkStackAction, PublishAssetsAction, UpdatePipelineAction } from './actions'; import { appOf, assemblyBuilderOf } from './private/construct-internals'; @@ -342,7 +342,7 @@ export class CdkPipeline extends CoreConstruct { return flatMap(this._pipeline.stages, s => s.actions.filter(isDeployAction)); } - private * validateDeployOrder(): IterableIterator { + private* validateDeployOrder(): IterableIterator { const stackActions = this.stackActions; for (const stackAction of stackActions) { // For every dependency, it must be executed in an action before this one is prepared. @@ -360,7 +360,7 @@ export class CdkPipeline extends CoreConstruct { } } - private * validateRequestedOutputs(): IterableIterator { + private* validateRequestedOutputs(): IterableIterator { const artifactIds = this.stackActions.map(s => s.stackArtifactId); for (const artifactId of Object.keys(this._outputArtifacts)) { @@ -398,6 +398,7 @@ class AssetPublishing extends CoreConstruct { private readonly publishers: Record = {}; private readonly assetRoles: Record = {}; + private readonly assetPublishingRoles: Record> = {}; private readonly myCxAsmRoot: string; private readonly lastStageBeforePublishing?: codepipeline.IStage; @@ -440,6 +441,7 @@ class AssetPublishing extends CoreConstruct { if (!this.assetRoles[command.assetType]) { this.generateAssetRole(command.assetType); } + this.assetPublishingRoles[command.assetType] = (this.assetPublishingRoles[command.assetType] ?? new Set()).add(command.assetPublishingRoleArn); const publisherKey = this.props.singlePublisherPerType ? command.assetType.toString() : command.assetId; @@ -546,12 +548,11 @@ class AssetPublishing extends CoreConstruct { })); // Publishing role access - const rolePattern = assetType === AssetType.DOCKER_IMAGE - ? 'arn:*:iam::*:role/*-image-publishing-role-*' - : 'arn:*:iam::*:role/*-file-publishing-role-*'; + // The ARNs include raw AWS pseudo parameters (e.g., ${AWS::Partition}), which need to be substituted. + // Lazy-evaluated so all asset publishing roles are included. assetRole.addToPolicy(new iam.PolicyStatement({ actions: ['sts:AssumeRole'], - resources: [rolePattern], + resources: Lazy.list({ produce: () => [...this.assetPublishingRoles[assetType]].map(arn => Fn.sub(arn)) }), })); // Artifact access diff --git a/packages/@aws-cdk/pipelines/lib/private/asset-manifest.ts b/packages/@aws-cdk/pipelines/lib/private/asset-manifest.ts index c7ed8a3bc885f..64e1d94893357 100644 --- a/packages/@aws-cdk/pipelines/lib/private/asset-manifest.ts +++ b/packages/@aws-cdk/pipelines/lib/private/asset-manifest.ts @@ -1,7 +1,7 @@ // FIXME: copied from `ckd-assets`, because this tool needs to read the asset manifest aswell. import * as fs from 'fs'; import * as path from 'path'; -import { AssetManifest, DockerImageDestination, DockerImageSource, FileDestination, FileSource, Manifest } from '@aws-cdk/cloud-assembly-schema'; +import { AssetManifest, AwsDestination, DockerImageDestination, DockerImageSource, FileDestination, FileSource, Manifest } from '@aws-cdk/cloud-assembly-schema'; /** * A manifest of assets @@ -154,7 +154,7 @@ export interface IManifestEntry { /** * Type-dependent destination data */ - readonly genericDestination: unknown; + readonly destination: AwsDestination; } /** @@ -162,7 +162,6 @@ export interface IManifestEntry { */ export class FileManifestEntry implements IManifestEntry { public readonly genericSource: unknown; - public readonly genericDestination: unknown; public readonly type = 'file'; constructor( @@ -174,7 +173,6 @@ export class FileManifestEntry implements IManifestEntry { public readonly destination: FileDestination, ) { this.genericSource = source; - this.genericDestination = destination; } } @@ -183,7 +181,6 @@ export class FileManifestEntry implements IManifestEntry { */ export class DockerImageManifestEntry implements IManifestEntry { public readonly genericSource: unknown; - public readonly genericDestination: unknown; public readonly type = 'docker-image'; constructor( @@ -195,7 +192,6 @@ export class DockerImageManifestEntry implements IManifestEntry { public readonly destination: DockerImageDestination, ) { this.genericSource = source; - this.genericDestination = destination; } } diff --git a/packages/@aws-cdk/pipelines/lib/stage.ts b/packages/@aws-cdk/pipelines/lib/stage.ts index 0fca3f895b584..4d5eda62762d3 100644 --- a/packages/@aws-cdk/pipelines/lib/stage.ts +++ b/packages/@aws-cdk/pipelines/lib/stage.ts @@ -246,7 +246,7 @@ export class CdkStage extends CoreConstruct { if (entry instanceof DockerImageManifestEntry) { assetType = AssetType.DOCKER_IMAGE; } else if (entry instanceof FileManifestEntry) { - // Don't publishg the template for this stack + // Don't publish the template for this stack if (entry.source.packaging === 'file' && entry.source.path === stackArtifact.templateFile) { continue; } @@ -256,11 +256,16 @@ export class CdkStage extends CoreConstruct { throw new Error(`Unrecognized asset type: ${entry.type}`); } + if (!entry.destination.assumeRoleArn) { + throw new Error('assumeRoleArn is missing on asset and required'); + } + this.host.publishAsset({ assetManifestPath: manifestArtifact.file, assetId: entry.id.assetId, assetSelector: entry.id.toString(), assetType, + assetPublishingRoleArn: entry.destination.assumeRoleArn, }); } } @@ -357,6 +362,11 @@ export interface AssetPublishingCommand { * Type of asset to publish */ readonly assetType: AssetType; + + /** + * ARN of the IAM Role used to publish this asset. + */ + readonly assetPublishingRoleArn: string; } /** diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json index e0d1ca7c3cb12..67537424bec57 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json @@ -1344,7 +1344,11 @@ { "Action": "sts:AssumeRole", "Effect": "Allow", - "Resource": "arn:*:iam::*:role/*-file-publishing-role-*" + "Resource": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" + } + ] }, { "Action": [ diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json index 5d58f648d7fbb..a21b2429a79cc 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json @@ -1370,7 +1370,11 @@ { "Action": "sts:AssumeRole", "Effect": "Allow", - "Resource": "arn:*:iam::*:role/*-file-publishing-role-*" + "Resource": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" + } + ] }, { "Action": [ @@ -1523,4 +1527,4 @@ ] } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts b/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts index dbbee2c32104c..fdb39835e09cb 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts +++ b/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts @@ -14,6 +14,9 @@ import { BucketStack, PIPELINE_ENV, TestApp, TestGitHubAction, TestGitHubNpmPipe const FILE_ASSET_SOURCE_HASH = '8289faf53c7da377bb2b90615999171adef5e1d8f6b88810e5fef75e6ca09ba5'; const FILE_ASSET_SOURCE_HASH2 = 'ac76997971c3f6ddf37120660003f1ced72b4fc58c498dfd99c78fa77e721e0e'; +const FILE_PUBLISHING_ROLE = 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}'; +const IMAGE_PUBLISHING_ROLE = 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}'; + let app: TestApp; let pipelineStack: Stack; let pipeline: cdkp.CdkPipeline; @@ -204,7 +207,7 @@ describe('basic pipeline', () => { }); }); - test('file image asset publishers do not use privilegedmode, have right AssumeRole', () => { + test('file image asset publishers do not use privilegedmode', () => { // WHEN pipeline.addApplicationStage(new FileAssetApp(app, 'FileAssetApp')); @@ -224,19 +227,9 @@ describe('basic pipeline', () => { Image: 'aws/codebuild/standard:5.0', }), }); - - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { - PolicyDocument: { - Statement: arrayWith({ - Action: 'sts:AssumeRole', - Effect: 'Allow', - Resource: 'arn:*:iam::*:role/*-file-publishing-role-*', - }), - }, - }); }); - test('docker image asset publishers use privilegedmode, have right AssumeRole', () => { + test('docker image asset publishers use privilegedmode', () => { // WHEN pipeline.addApplicationStage(new DockerAssetApp(app, 'DockerAssetApp')); @@ -256,15 +249,6 @@ describe('basic pipeline', () => { PrivilegedMode: true, }), }); - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { - PolicyDocument: { - Statement: arrayWith({ - Action: 'sts:AssumeRole', - Effect: 'Allow', - Resource: 'arn:*:iam::*:role/*-image-publishing-role-*', - }), - }, - }); }); test('can control fix/CLI version used in pipeline selfupdate', () => { @@ -313,7 +297,30 @@ describe('basic pipeline', () => { }, }); expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', - expectedAssetRolePolicy('arn:*:iam::*:role/*-file-publishing-role-*', 'CdkAssetsFileRole6BE17A07')); + expectedAssetRolePolicy(FILE_PUBLISHING_ROLE, 'CdkAssetsFileRole6BE17A07')); + }); + + test('publishing assets role may assume roles from multiple environments', () => { + pipeline.addApplicationStage(new FileAssetApp(app, 'App1')); + pipeline.addApplicationStage(new FileAssetApp(app, 'App2', { + env: { + account: '0123456789012', + region: 'eu-west-1', + }, + })); + + expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', + expectedAssetRolePolicy([FILE_PUBLISHING_ROLE, 'arn:${AWS::Partition}:iam::0123456789012:role/cdk-hnb659fds-file-publishing-role-0123456789012-eu-west-1'], + 'CdkAssetsFileRole6BE17A07')); + }); + + test('publishing assets role de-dupes assumed roles', () => { + pipeline.addApplicationStage(new FileAssetApp(app, 'App1')); + pipeline.addApplicationStage(new FileAssetApp(app, 'App2')); + pipeline.addApplicationStage(new FileAssetApp(app, 'App3')); + + expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', + expectedAssetRolePolicy(FILE_PUBLISHING_ROLE, 'CdkAssetsFileRole6BE17A07')); }); test('includes image publishing assets role for apps with Docker assets', () => { @@ -336,7 +343,7 @@ describe('basic pipeline', () => { }, }); expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', - expectedAssetRolePolicy('arn:*:iam::*:role/*-image-publishing-role-*', 'CdkAssetsDockerRole484B6DD3')); + expectedAssetRolePolicy(IMAGE_PUBLISHING_ROLE, 'CdkAssetsDockerRole484B6DD3')); }); test('includes both roles for apps with both file and Docker assets', () => { @@ -344,9 +351,9 @@ describe('basic pipeline', () => { pipeline.addApplicationStage(new DockerAssetApp(app, 'App2')); expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', - expectedAssetRolePolicy('arn:*:iam::*:role/*-file-publishing-role-*', 'CdkAssetsFileRole6BE17A07')); + expectedAssetRolePolicy(FILE_PUBLISHING_ROLE, 'CdkAssetsFileRole6BE17A07')); expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', - expectedAssetRolePolicy('arn:*:iam::*:role/*-image-publishing-role-*', 'CdkAssetsDockerRole484B6DD3')); + expectedAssetRolePolicy(IMAGE_PUBLISHING_ROLE, 'CdkAssetsDockerRole484B6DD3')); }); }); }); @@ -449,6 +456,7 @@ describe('pipeline with single asset publisher', () => { expect(buildSpec.phases.build.commands).toContain(`cdk-assets --path "assembly-FileAssetApp/FileAssetAppStackEADD68C5.assets.json" --verbose publish "${FILE_ASSET_SOURCE_HASH2}:current_account-current_region"`); }); }); + class PlainStackApp extends Stage { constructor(scope: Construct, id: string, props?: StageProps) { super(scope, id, props); @@ -515,7 +523,10 @@ class MegaAssetsApp extends Stage { } } -function expectedAssetRolePolicy(assumeRolePattern: string, attachedRole: string) { + +function expectedAssetRolePolicy(assumeRolePattern: string | string[], attachedRole: string) { + if (typeof assumeRolePattern === 'string') { assumeRolePattern = [assumeRolePattern]; } + return { PolicyDocument: { Statement: [{ @@ -548,7 +559,7 @@ function expectedAssetRolePolicy(assumeRolePattern: string, attachedRole: string { Action: 'sts:AssumeRole', Effect: 'Allow', - Resource: assumeRolePattern, + Resource: assumeRolePattern.map(arn => { return { 'Fn::Sub': arn }; }), }, { Action: ['s3:GetObject*', 's3:GetBucket*', 's3:List*'], From 0c50ec920bb7941cc510ac66bc36c21d95c92027 Mon Sep 17 00:00:00 2001 From: Andrew Rodriguez <49878080+zARODz11z@users.noreply.github.com> Date: Tue, 15 Jun 2021 08:15:05 -0700 Subject: [PATCH 17/34] feat(logs): make the addition of permissions to Lambda functions optional (#14222) ### What does this PR do? Makes the addition of permissions to Lambda functions optional for users instantiating the LambdaDestination class. By default the permissions will be added in hopes of not breaking existing user's configurations. Added a unit test which tests the scenario in which a user would like to opt out of adding permissions to their function. ### Motivation Fixes #14198 in which users with a high amount of lambdas will cause `The final policy size (20497) is bigger than the limit (20480)` in our [Datadog CDK Construct Library](https://github.com/DataDog/datadog-cdk-constructs) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-logs-destinations/lib/lambda.ts | 32 +++++++++++++------ .../aws-logs-destinations/test/lambda.test.ts | 21 ++++++++++++ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/packages/@aws-cdk/aws-logs-destinations/lib/lambda.ts b/packages/@aws-cdk/aws-logs-destinations/lib/lambda.ts index c88c3134d16ab..1175eb4fcb957 100644 --- a/packages/@aws-cdk/aws-logs-destinations/lib/lambda.ts +++ b/packages/@aws-cdk/aws-logs-destinations/lib/lambda.ts @@ -4,22 +4,34 @@ import * as logs from '@aws-cdk/aws-logs'; import { Construct } from '@aws-cdk/core'; /** - * Use a Lamda Function as the destination for a log subscription + * Options that may be provided to LambdaDestination + */ +export interface LambdaDestinationOptions{ + /** Whether or not to add Lambda Permissions. + * @default true + */ + readonly addPermissions?: boolean; +} + +/** + * Use a Lambda Function as the destination for a log subscription */ export class LambdaDestination implements logs.ILogSubscriptionDestination { - constructor(private readonly fn: lambda.IFunction) { + /** LambdaDestinationOptions */ + constructor(private readonly fn: lambda.IFunction, private readonly options: LambdaDestinationOptions = {}) { } public bind(scope: Construct, logGroup: logs.ILogGroup): logs.LogSubscriptionDestinationConfig { const arn = logGroup.logGroupArn; - - this.fn.addPermission('CanInvokeLambda', { - principal: new iam.ServicePrincipal('logs.amazonaws.com'), - sourceArn: arn, - // Using SubScription Filter as scope is okay, since every Subscription Filter has only - // one destination. - scope, - }); + if (this.options.addPermissions !== false) { + this.fn.addPermission('CanInvokeLambda', { + principal: new iam.ServicePrincipal('logs.amazonaws.com'), + sourceArn: arn, + // Using SubScription Filter as scope is okay, since every Subscription Filter has only + // one destination. + scope, + }); + } return { arn: this.fn.functionArn }; } } diff --git a/packages/@aws-cdk/aws-logs-destinations/test/lambda.test.ts b/packages/@aws-cdk/aws-logs-destinations/test/lambda.test.ts index e1323ae0f45a9..b1f0048716a74 100644 --- a/packages/@aws-cdk/aws-logs-destinations/test/lambda.test.ts +++ b/packages/@aws-cdk/aws-logs-destinations/test/lambda.test.ts @@ -69,3 +69,24 @@ test('can have multiple subscriptions use the same Lambda', () => { Principal: 'logs.amazonaws.com', }); }); + +test('lambda permissions are not added when addPermissions is false', () => { + // WHEN + new logs.SubscriptionFilter(stack, 'Subscription', { + logGroup, + destination: new dests.LambdaDestination(fn, { addPermissions: false }), + filterPattern: logs.FilterPattern.allEvents(), + }); + + // THEN: subscription target is Lambda + expect(stack).toHaveResource('AWS::Logs::SubscriptionFilter', { + DestinationArn: { 'Fn::GetAtt': ['MyLambdaCCE802FB', 'Arn'] }, + }); + + // THEN: Lambda does not have permissions to be invoked by CWL + expect(stack).not.toHaveResource('AWS::Lambda::Permission', { + Action: 'lambda:InvokeFunction', + FunctionName: { 'Fn::GetAtt': ['MyLambdaCCE802FB', 'Arn'] }, + Principal: 'logs.amazonaws.com', + }); +}); \ No newline at end of file From a8298cb378e8dea21ceca66bfc09dd02baec4158 Mon Sep 17 00:00:00 2001 From: Andrei Alecu Date: Tue, 15 Jun 2021 19:01:47 +0300 Subject: [PATCH 18/34] fix(aws-iam): prevent adding duplicate resources and actions (#14712) fixes #13611 cc @rix0rrr ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-glue/test/integ.table.expected.json | 5 --- packages/@aws-cdk/aws-glue/test/table.test.ts | 1 - .../@aws-cdk/aws-iam/lib/policy-statement.ts | 12 +++---- .../aws-iam/test/policy-document.test.ts | 34 +++++++++++++++++++ 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/packages/@aws-cdk/aws-glue/test/integ.table.expected.json b/packages/@aws-cdk/aws-glue/test/integ.table.expected.json index 9d00dcfffab63..58a72bfcf08fc 100644 --- a/packages/@aws-cdk/aws-glue/test/integ.table.expected.json +++ b/packages/@aws-cdk/aws-glue/test/integ.table.expected.json @@ -442,7 +442,6 @@ "glue:GetTableVersion", "glue:GetTableVersions", "glue:BatchCreatePartition", - "glue:BatchDeletePartition", "glue:CreatePartition", "glue:DeletePartition", "glue:UpdatePartition" @@ -520,7 +519,6 @@ "glue:GetTableVersion", "glue:GetTableVersions", "glue:BatchCreatePartition", - "glue:BatchDeletePartition", "glue:CreatePartition", "glue:DeletePartition", "glue:UpdatePartition" @@ -633,7 +631,6 @@ "glue:GetTableVersion", "glue:GetTableVersions", "glue:BatchCreatePartition", - "glue:BatchDeletePartition", "glue:CreatePartition", "glue:DeletePartition", "glue:UpdatePartition" @@ -711,7 +708,6 @@ "glue:GetTableVersion", "glue:GetTableVersions", "glue:BatchCreatePartition", - "glue:BatchDeletePartition", "glue:CreatePartition", "glue:DeletePartition", "glue:UpdatePartition" @@ -756,7 +752,6 @@ "glue:GetTableVersion", "glue:GetTableVersions", "glue:BatchCreatePartition", - "glue:BatchDeletePartition", "glue:CreatePartition", "glue:DeletePartition", "glue:UpdatePartition" diff --git a/packages/@aws-cdk/aws-glue/test/table.test.ts b/packages/@aws-cdk/aws-glue/test/table.test.ts index 5125c2f5fa411..084eaa67ab564 100644 --- a/packages/@aws-cdk/aws-glue/test/table.test.ts +++ b/packages/@aws-cdk/aws-glue/test/table.test.ts @@ -1189,7 +1189,6 @@ testFutureBehavior('grants: read and write', s3GrantWriteCtx, cdk.App, (app) => 'glue:GetTableVersion', 'glue:GetTableVersions', 'glue:BatchCreatePartition', - 'glue:BatchDeletePartition', 'glue:CreatePartition', 'glue:DeletePartition', 'glue:UpdatePartition', diff --git a/packages/@aws-cdk/aws-iam/lib/policy-statement.ts b/packages/@aws-cdk/aws-iam/lib/policy-statement.ts index 62257140e9c30..c0e7d80a806f0 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy-statement.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy-statement.ts @@ -325,18 +325,18 @@ export class PolicyStatement { */ public toStatementJson(): any { return noUndef({ - Action: _norm(this.action), - NotAction: _norm(this.notAction), + Action: _norm(this.action, { unique: true }), + NotAction: _norm(this.notAction, { unique: true }), Condition: _norm(this.condition), Effect: _norm(this.effect), Principal: _normPrincipal(this.principal), NotPrincipal: _normPrincipal(this.notPrincipal), - Resource: _norm(this.resource), - NotResource: _norm(this.notResource), + Resource: _norm(this.resource, { unique: true }), + NotResource: _norm(this.notResource, { unique: true }), Sid: _norm(this.sid), }); - function _norm(values: any) { + function _norm(values: any, { unique }: { unique: boolean } = { unique: false }) { if (typeof(values) === 'undefined') { return undefined; @@ -355,7 +355,7 @@ export class PolicyStatement { return values[0]; } - return values; + return unique ? [...new Set(values)] : values; } if (typeof(values) === 'object') { 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 e3cc579fb1c16..7c0272a882bbe 100644 --- a/packages/@aws-cdk/aws-iam/test/policy-document.test.ts +++ b/packages/@aws-cdk/aws-iam/test/policy-document.test.ts @@ -334,6 +334,40 @@ describe('IAM policy document', () => { }); }); + test('addResources()/addActions() will not add duplicates', () => { + const stack = new Stack(); + + const statement = new PolicyStatement(); + statement.addActions('a'); + statement.addActions('a'); + + statement.addResources('x'); + statement.addResources('x'); + + expect(stack.resolve(statement.toStatementJson())).toEqual({ + Effect: 'Allow', + Action: ['a'], + Resource: ['x'], + }); + }); + + test('addNotResources()/addNotActions() will not add duplicates', () => { + const stack = new Stack(); + + const statement = new PolicyStatement(); + statement.addNotActions('a'); + statement.addNotActions('a'); + + statement.addNotResources('x'); + statement.addNotResources('x'); + + expect(stack.resolve(statement.toStatementJson())).toEqual({ + Effect: 'Allow', + NotAction: ['a'], + NotResource: ['x'], + }); + }); + test('addCanonicalUserPrincipal can be used to add cannonical user principals', () => { const stack = new Stack(); const p = new PolicyDocument(); From 31c933895e58a68d4d2edc72917fcc43a8e7304e Mon Sep 17 00:00:00 2001 From: Thorsten Hoeger Date: Tue, 15 Jun 2021 19:14:03 +0200 Subject: [PATCH 19/34] feat(cloudfront): add fromFile for CF functions (#14980) add the option to load the CFF code from a file. Currently only reading the file is possible as bundling to ES5 is not yet possible with esbuild. fixes #14967 ---- *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-cloudfront/.gitignore | 1 + packages/@aws-cdk/aws-cloudfront/README.md | 2 + .../@aws-cdk/aws-cloudfront/lib/function.ts | 37 ++++++++++++++++++- .../aws-cloudfront/test/function-code.js | 3 ++ .../aws-cloudfront/test/function.test.ts | 27 ++++++++++++++ 5 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk/aws-cloudfront/test/function-code.js diff --git a/packages/@aws-cdk/aws-cloudfront/.gitignore b/packages/@aws-cdk/aws-cloudfront/.gitignore index e65361cd8269a..c27af0e504b84 100644 --- a/packages/@aws-cdk/aws-cloudfront/.gitignore +++ b/packages/@aws-cdk/aws-cloudfront/.gitignore @@ -16,5 +16,6 @@ nyc.config.js !jest.config.js !lib/experimental/edge-function/index.js +!test/function-code.js junit.xml diff --git a/packages/@aws-cdk/aws-cloudfront/README.md b/packages/@aws-cdk/aws-cloudfront/README.md index 3b35de52533da..88fc86156a2e2 100644 --- a/packages/@aws-cdk/aws-cloudfront/README.md +++ b/packages/@aws-cdk/aws-cloudfront/README.md @@ -408,6 +408,8 @@ new cloudfront.Distribution(stack, 'distro', { It will auto-generate the name of the function and deploy it to the `live` stage. +Additionally, you can load the function's code from a file using the `FunctionCode.fromFile()` method. + ### Logging You can configure CloudFront to create log files that contain detailed information about every user request that CloudFront receives. diff --git a/packages/@aws-cdk/aws-cloudfront/lib/function.ts b/packages/@aws-cdk/aws-cloudfront/lib/function.ts index 8f8f396ca6afa..a17c69ec652d0 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/function.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/function.ts @@ -1,3 +1,4 @@ +import * as fs from 'fs'; import { IResource, Names, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnFunction } from './cloudfront.generated'; @@ -9,19 +10,38 @@ export abstract class FunctionCode { /** * Inline code for function - * @returns `InlineCode` with inline code. + * @returns code object with inline code. * @param code The actual function code */ public static fromInline(code: string): FunctionCode { return new InlineCode(code); } + /** + * Code from external file for function + * @returns code object with contents from file. + * @param options the options for the external file + */ + public static fromFile(options: FileCodeOptions): FunctionCode { + return new FileCode(options); + } + /** * renders the function code */ public abstract render(): string; } +/** + * Options when reading the function's code from an external file + */ +export interface FileCodeOptions { + /** + * The path of the file to read the code from + */ + readonly filePath: string; +} + /** * Represents the function's source code as inline code */ @@ -36,6 +56,21 @@ class InlineCode extends FunctionCode { } } + +/** + * Represents the function's source code loaded from an external file + */ +class FileCode extends FunctionCode { + + constructor(private options: FileCodeOptions) { + super(); + } + + public render(): string { + return fs.readFileSync(this.options.filePath, { encoding: 'utf-8' }); + } +} + /** * Represents a CloudFront Function */ diff --git a/packages/@aws-cdk/aws-cloudfront/test/function-code.js b/packages/@aws-cdk/aws-cloudfront/test/function-code.js new file mode 100644 index 0000000000000..e06f3e2f47e70 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/function-code.js @@ -0,0 +1,3 @@ +function handler(event) { + return event.request; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/function.test.ts b/packages/@aws-cdk/aws-cloudfront/test/function.test.ts index 22b29ba48c857..c2466a71c8ff2 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/function.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/function.test.ts @@ -1,3 +1,4 @@ +import * as path from 'path'; import '@aws-cdk/assert-internal/jest'; import { expect as expectStack } from '@aws-cdk/assert-internal'; import { App, Stack } from '@aws-cdk/core'; @@ -106,4 +107,30 @@ describe('CloudFront Function', () => { }); }); + test('code from external file', () => { + const app = new App(); + const stack = new Stack(app, 'Stack', { + env: { account: '123456789012', region: 'testregion' }, + }); + new Function(stack, 'CF2', { + code: FunctionCode.fromFile({ filePath: path.join(__dirname, 'function-code.js') }), + }); + + expectStack(stack).toMatch({ + Resources: { + CF2D7241DD7: { + Type: 'AWS::CloudFront::Function', + Properties: { + Name: 'testregionStackCF2CE3F783F', + AutoPublish: true, + FunctionCode: 'function handler(event) {\n return event.request;\n}', + FunctionConfig: { + Comment: 'testregionStackCF2CE3F783F', + Runtime: 'cloudfront-js-1.0', + }, + }, + }, + }, + }); + }); }); \ No newline at end of file From c31c59a00cd7a43ddd31b9225785fe96c61e944d Mon Sep 17 00:00:00 2001 From: Hsing-Hui Hsu Date: Tue, 15 Jun 2021 12:11:55 -0700 Subject: [PATCH 20/34] feat(ecs-patterns): expose task target on ScheduledTask pattern (#15127) Exposes `task` as new output property on ECS and Fargate ScheduledTasks. This enables customers to access underlying task properties, i.e. use security groups to grant access to a database. Closes #14971, #14953, #12609 ---- *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 | 18 ++++++++++++++- .../lib/ecs/scheduled-ecs-task.ts | 8 ++++++- .../lib/fargate/scheduled-fargate-task.ts | 9 ++++++-- .../test/ec2/test.scheduled-ecs-task.ts | 21 ++++++++++++++++++ .../fargate/test.scheduled-fargate-task.ts | 22 +++++++++++++++++++ 5 files changed, 74 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs-patterns/README.md b/packages/@aws-cdk/aws-ecs-patterns/README.md index c39592f28cbb7..58dba9f6786cb 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/README.md +++ b/packages/@aws-cdk/aws-ecs-patterns/README.md @@ -293,7 +293,9 @@ when queue not provided by user, CDK will create a primary queue and a dead lett ## Scheduled Tasks -To define a task that runs periodically, instantiate an `ScheduledEc2Task`: +To define a task that runs periodically, there are 2 options: + +* `ScheduledEc2Task` ```ts // Instantiate an Amazon EC2 Task to run at a scheduled interval @@ -310,6 +312,20 @@ const ecsScheduledTask = new ScheduledEc2Task(stack, 'ScheduledTask', { }); ``` +* `ScheduledFargateTask` + +```ts +const scheduledFargateTask = new ScheduledFargateTask(stack, 'ScheduledFargateTask', { + cluster, + scheduledFargateTaskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }, + schedule: events.Schedule.expression('rate(1 minute)'), + platformVersion: ecs.FargatePlatformVersion.LATEST, +}); +``` + ## Additional Examples In addition to using the constructs, users can also add logic to customize these constructs: diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/scheduled-ecs-task.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/scheduled-ecs-task.ts index 7f64a18df9ff8..32599569a892e 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/scheduled-ecs-task.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/scheduled-ecs-task.ts @@ -1,4 +1,5 @@ import { Ec2TaskDefinition } from '@aws-cdk/aws-ecs'; +import { EcsTask } from '@aws-cdk/aws-events-targets'; import { Construct } from 'constructs'; import { ScheduledTaskBase, ScheduledTaskBaseProps, ScheduledTaskImageProps } from '../base/scheduled-task-base'; @@ -85,6 +86,11 @@ export class ScheduledEc2Task extends ScheduledTaskBase { */ public readonly taskDefinition: Ec2TaskDefinition; + /** + * The ECS task in this construct. + */ + public readonly task: EcsTask; + /** * Constructs a new instance of the ScheduledEc2Task class. */ @@ -113,6 +119,6 @@ export class ScheduledEc2Task extends ScheduledTaskBase { throw new Error('You must specify a taskDefinition or image'); } - this.addTaskDefinitionToEventTarget(this.taskDefinition); + this.task = this.addTaskDefinitionToEventTarget(this.taskDefinition); } } diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts index 34360bd67f216..7a5a018920e7a 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts @@ -94,6 +94,11 @@ export class ScheduledFargateTask extends ScheduledTaskBase { */ public readonly taskDefinition: FargateTaskDefinition; + /** + * The ECS task in this construct. + */ + public readonly task: EcsTask; + /** * Constructs a new instance of the ScheduledFargateTask class. */ @@ -122,7 +127,7 @@ export class ScheduledFargateTask extends ScheduledTaskBase { } // Use the EcsTask as the target of the EventRule - const eventRuleTarget = new EcsTask( { + this.task = new EcsTask( { cluster: this.cluster, taskDefinition: this.taskDefinition, taskCount: this.desiredTaskCount, @@ -131,6 +136,6 @@ export class ScheduledFargateTask extends ScheduledTaskBase { securityGroups: props.securityGroups, }); - this.addTaskAsTarget(eventRuleTarget); + this.addTaskAsTarget(this.task); } } diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.scheduled-ecs-task.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.scheduled-ecs-task.ts index e270e7a0662f7..712c83ab83b93 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.scheduled-ecs-task.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.scheduled-ecs-task.ts @@ -317,4 +317,25 @@ export = { test.done(); }, + + 'Scheduled Ec2 Task - exposes ECS Task'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + + const scheduledEc2Task = new ScheduledEc2Task(stack, 'ScheduledEc2Task', { + cluster, + scheduledEc2TaskImageOptions: { + image: ecs.ContainerImage.fromRegistry('henk'), + memoryLimitMiB: 512, + }, + schedule: events.Schedule.expression('rate(1 minute)'), + }); + + // THEN + test.notEqual(scheduledEc2Task.task, undefined); + + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.scheduled-fargate-task.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.scheduled-fargate-task.ts index 491ed762d5f92..497e885460f26 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.scheduled-fargate-task.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.scheduled-fargate-task.ts @@ -299,6 +299,7 @@ export = { test.done(); }, + 'Scheduled Fargate Task - with platformVersion defined'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -405,4 +406,25 @@ export = { test.done(); }, + + 'Scheduled Fargate Task - exposes ECS Task'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + + const scheduledFargateTask = new ScheduledFargateTask(stack, 'ScheduledFargateTask', { + cluster, + scheduledFargateTaskImageOptions: { + image: ecs.ContainerImage.fromRegistry('henk'), + memoryLimitMiB: 512, + }, + schedule: events.Schedule.expression('rate(1 minute)'), + }); + + // THEN + test.notEqual(scheduledFargateTask.task, undefined); + + test.done(); + }, }; From 04c0a076c842683280dc1dc483cfc605641bd0fa Mon Sep 17 00:00:00 2001 From: Alban Esc Date: Tue, 15 Jun 2021 12:56:34 -0700 Subject: [PATCH 21/34] feat(aws-backup): Add arn attribute and grant method to backup vault (#14997) `BackupVault` now have the `backupVaultArn` attribute available. It is also possible to import an existing `BackupVault` by `arn` using the `fromBackupVaultArn` method. You can also grant permission to an existing grantee using the `grant` method. See the example below giving an IAM Role permission to start a backup job: ```ts const importedVault = backup.BackupVault.fromBackupVaultName(this, 'Vault', 'myVaultName'); const role = new iam.Role(this, 'Access Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com') }); importedVault.grant(role, 'backup:StartBackupJob'); ``` Closes #14996. ---- *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-backup/README.md | 41 ++++++--- packages/@aws-cdk/aws-backup/lib/vault.ts | 83 ++++++++++++++--- .../aws-backup/rosetta/default.ts-fixture | 15 ++++ .../aws-backup/rosetta/with-plan.ts-fixture | 16 ++++ .../@aws-cdk/aws-backup/test/vault.test.ts | 89 +++++++++++++++++++ 5 files changed, 222 insertions(+), 22 deletions(-) create mode 100644 packages/@aws-cdk/aws-backup/rosetta/default.ts-fixture create mode 100644 packages/@aws-cdk/aws-backup/rosetta/with-plan.ts-fixture diff --git a/packages/@aws-cdk/aws-backup/README.md b/packages/@aws-cdk/aws-backup/README.md index 3d1e81615fc37..ffe43e83db2bd 100644 --- a/packages/@aws-cdk/aws-backup/README.md +++ b/packages/@aws-cdk/aws-backup/README.md @@ -20,15 +20,16 @@ In AWS Backup, a *backup plan* is a policy expression that defines when and how This module provides ready-made backup plans (similar to the console experience): ```ts -import * as backup from '@aws-cdk/aws-backup'; - // Daily, weekly and monthly with 5 year retention const plan = backup.BackupPlan.dailyWeeklyMonthly5YearRetention(this, 'Plan'); ``` Assigning resources to a plan can be done with `addSelection()`: -```ts +```ts fixture=with-plan +const myTable = dynamodb.Table.fromTableName(this, 'Table', 'myTableName'); +const myCoolConstruct = new Construct(this, 'MyCoolConstruct'); + plan.addSelection('Selection', { resources: [ backup.BackupResource.fromDynamoDbTable(myTable), // A DynamoDB table @@ -43,8 +44,8 @@ created for the selection. The `BackupSelection` implements `IGrantable`. To add rules to a plan, use `addRule()`: -```ts -plan.addRule(new BackupPlanRule({ +```ts fixture=with-plan +plan.addRule(new backup.BackupPlanRule({ completionWindow: Duration.hours(2), startWindow: Duration.hours(1), scheduleExpression: events.Schedule.cron({ // Only cron expressions are supported @@ -58,9 +59,9 @@ plan.addRule(new BackupPlanRule({ Ready-made rules are also available: -```ts -plan.addRule(BackupPlanRule.daily()); -plan.addRule(BackupPlanRule.weekly()); +```ts fixture=with-plan +plan.addRule(backup.BackupPlanRule.daily()); +plan.addRule(backup.BackupPlanRule.weekly()); ``` By default a new [vault](#Backup-vault) is created when creating a plan. @@ -68,8 +69,11 @@ It is also possible to specify a vault either at the plan level or at the rule level. ```ts +const myVault = backup.BackupVault.fromBackupVaultName(this, 'Vault1', 'myVault'); +const otherVault = backup.BackupVault.fromBackupVaultName(this, 'Vault2', 'otherVault'); + const plan = backup.BackupPlan.daily35DayRetention(this, 'Plan', myVault); // Use `myVault` for all plan rules -plan.addRule(BackupPlanRule.monthly1Year(otherVault)); // Use `otherVault` for this specific rule +plan.addRule(backup.BackupPlanRule.monthly1Year(otherVault)); // Use `otherVault` for this specific rule ``` ## Backup vault @@ -77,7 +81,10 @@ plan.addRule(BackupPlanRule.monthly1Year(otherVault)); // Use `otherVault` for t In AWS Backup, a *backup vault* is a container that you organize your backups in. You can use backup vaults to set the AWS Key Management Service (AWS KMS) encryption key that is used to encrypt backups in the backup vault and to control access to the backups in the backup vault. If you require different encryption keys or access policies for different groups of backups, you can optionally create multiple backup vaults. ```ts -const vault = new BackupVault(stack, 'Vault', { +const myKey = kms.Key.fromKeyArn(this, 'MyKey', 'aaa'); +const myTopic = sns.Topic.fromTopicArn(this, 'MyTopic', 'bbb'); + +const vault = new backup.BackupVault(this, 'Vault', { encryptionKey: myKey, // Custom encryption key notificationTopic: myTopic, // Send all vault events to this SNS topic }); @@ -85,3 +92,17 @@ const vault = new BackupVault(stack, 'Vault', { A vault has a default `RemovalPolicy` set to `RETAIN`. Note that removing a vault that contains recovery points will fail. + + +## Importing existing backup vault + +To import an existing backup vault into your CDK application, use the `BackupVault.fromBackupVaultArn` or `BackupVault.fromBackupVaultName` +static method. Here is an example of giving an IAM Role permission to start a backup job: + +```ts +const importedVault = backup.BackupVault.fromBackupVaultName(this, 'Vault', 'myVaultName'); + +const role = new iam.Role(this, 'Access Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com') }); + +importedVault.grant(role, 'backup:StartBackupJob'); +``` diff --git a/packages/@aws-cdk/aws-backup/lib/vault.ts b/packages/@aws-cdk/aws-backup/lib/vault.ts index cfee7f4c6ffa7..4ca43e5875729 100644 --- a/packages/@aws-cdk/aws-backup/lib/vault.ts +++ b/packages/@aws-cdk/aws-backup/lib/vault.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as sns from '@aws-cdk/aws-sns'; -import { IResource, Names, RemovalPolicy, Resource } from '@aws-cdk/core'; +import { IResource, Names, RemovalPolicy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnBackupVault } from './backup.generated'; @@ -15,6 +15,19 @@ export interface IBackupVault extends IResource { * @attribute */ readonly backupVaultName: string; + + /** + * The ARN of the backup vault. + * + * @attribute + */ + readonly backupVaultArn: string; + + /** + * Grant the actions defined in actions to the given grantee + * on this backup vault. + */ + grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant; } /** @@ -108,27 +121,73 @@ export enum BackupVaultEvents { BACKUP_PLAN_MODIFIED = 'BACKUP_PLAN_MODIFIED', } +abstract class BackupVaultBase extends Resource implements IBackupVault { + public abstract readonly backupVaultName: string; + public abstract readonly backupVaultArn: string; + + /** + * Grant the actions defined in actions to the given grantee + * on this Backup Vault resource. + * + * @param grantee Principal to grant right to + * @param actions The actions to grant + */ + public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { + for (const action of actions) { + if (action.indexOf('*') >= 0) { + throw new Error("AWS Backup access policies don't support a wildcard in the Action key."); + } + } + + return iam.Grant.addToPrincipal({ + grantee: grantee, + actions: actions, + resourceArns: [this.backupVaultArn], + }); + } +} + + /** * A backup vault */ -export class BackupVault extends Resource implements IBackupVault { +export class BackupVault extends BackupVaultBase { /** - * Import an existing backup vault + * Import an existing backup vault by name */ public static fromBackupVaultName(scope: Construct, id: string, backupVaultName: string): IBackupVault { - class Import extends Resource implements IBackupVault { - public readonly backupVaultName = backupVaultName; - } - return new Import(scope, id); - } + const backupVaultArn = Stack.of(scope).formatArn({ + service: 'backup', + resource: 'backup-vault', + resourceName: backupVaultName, + sep: ':', + }); - public readonly backupVaultName: string; + return BackupVault.fromBackupVaultArn(scope, id, backupVaultArn); + } /** - * The ARN of the backup vault - * - * @attribute + * Import an existing backup vault by arn */ + public static fromBackupVaultArn(scope: Construct, id: string, backupVaultArn: string): IBackupVault { + const parsedArn = Stack.of(scope).parseArn(backupVaultArn); + + if (!parsedArn.resourceName) { + throw new Error(`Backup Vault Arn ${backupVaultArn} does not have a resource name.`); + } + + class Import extends BackupVaultBase { + public readonly backupVaultName = parsedArn.resourceName!; + public readonly backupVaultArn = backupVaultArn; + } + + return new Import(scope, id, { + account: parsedArn.account, + region: parsedArn.region, + }); + } + + public readonly backupVaultName: string; public readonly backupVaultArn: string; constructor(scope: Construct, id: string, props: BackupVaultProps = {}) { diff --git a/packages/@aws-cdk/aws-backup/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-backup/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..cff23bb514119 --- /dev/null +++ b/packages/@aws-cdk/aws-backup/rosetta/default.ts-fixture @@ -0,0 +1,15 @@ +// Fixture with packages imported, but nothing else +import { Duration, RemovalPolicy, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as backup from '@aws-cdk/aws-backup'; +import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; +import * as sns from '@aws-cdk/aws-sns'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-backup/rosetta/with-plan.ts-fixture b/packages/@aws-cdk/aws-backup/rosetta/with-plan.ts-fixture new file mode 100644 index 0000000000000..8dbfd6ac72c89 --- /dev/null +++ b/packages/@aws-cdk/aws-backup/rosetta/with-plan.ts-fixture @@ -0,0 +1,16 @@ +// Fixture with packages imported, but nothing else +import { Duration, RemovalPolicy, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as backup from '@aws-cdk/aws-backup'; +import * as dynamodb from '@aws-cdk/aws-dynamodb'; +import * as events from '@aws-cdk/aws-events'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + const plan = backup.BackupPlan.dailyWeeklyMonthly5YearRetention(this, 'Plan'); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-backup/test/vault.test.ts b/packages/@aws-cdk/aws-backup/test/vault.test.ts index 183498bae31b3..6f3b4f9446720 100644 --- a/packages/@aws-cdk/aws-backup/test/vault.test.ts +++ b/packages/@aws-cdk/aws-backup/test/vault.test.ts @@ -135,6 +135,95 @@ test('defaults to all notifications', () => { }); }); +test('import from arn', () => { + // WHEN + const vaultArn = stack.formatArn({ + service: 'backup', + resource: 'backup-vault', + resourceName: 'myVaultName', + sep: ':', + }); + const vault = BackupVault.fromBackupVaultArn(stack, 'Vault', vaultArn); + + // THEN + expect(vault.backupVaultName).toEqual('myVaultName'); + expect(vault.backupVaultArn).toEqual(vaultArn); +}); + +test('import from name', () => { + // WHEN + const vaultName = 'myVaultName'; + const vault = BackupVault.fromBackupVaultName(stack, 'Vault', vaultName); + + // THEN + expect(vault.backupVaultName).toEqual(vaultName); + expect(vault.backupVaultArn).toEqual(stack.formatArn({ + service: 'backup', + resource: 'backup-vault', + resourceName: 'myVaultName', + sep: ':', + })); +}); + +test('grant action', () => { + // GIVEN + const vaultName = 'myVaultName'; + const vault = BackupVault.fromBackupVaultName(stack, 'Vault', vaultName); + const role = new iam.Role(stack, 'role', { assumedBy: new iam.ServicePrincipal('lambda') }); + + // WHEN + vault.grant(role, 'backup:StartBackupJob'); + + // THEN + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'backup:StartBackupJob', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':backup:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':backup-vault:myVaultName', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'roleDefaultPolicy7C980EBA', + Roles: [ + { + Ref: 'roleC7B7E775', + }, + ], + }); +}); + +test('throw when grant action includes wildcard', () => { + // GIVEN + const vaultName = 'myVaultName'; + const vault = BackupVault.fromBackupVaultName(stack, 'Vault', vaultName); + const role = new iam.Role(stack, 'role', { assumedBy: new iam.ServicePrincipal('lambda') }); + + // WHEN + expect(() => vault.grant(role, 'backup:*')).toThrow(/AWS Backup access policies don't support a wildcard in the Action key\./); +}); + test('throws with invalid name', () => { expect(() => new BackupVault(stack, 'Vault', { backupVaultName: 'Hello!Inv@lid', From 1137eb70d5a0afd6a39667c41bbb36fea5fca90a Mon Sep 17 00:00:00 2001 From: Pankaj Yadav Date: Wed, 16 Jun 2021 02:18:13 +0530 Subject: [PATCH 22/34] feat(dynamodb): exposes schema method to return partition and sort key of table or secondary indexes (#15111) Exposes schema method to return partition and sort key of table or secondary indexes: ```ts const { partitionKey, sortKey } = table.schema(); // In case you want to get schema details for any secondary index const { partitionKey, sortKey } = table.schema(INDEX_NAME); ``` Closes #7680 ---- *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-dynamodb/README.md | 12 +++ packages/@aws-cdk/aws-dynamodb/lib/table.ts | 68 ++++++++----- .../aws-dynamodb/test/dynamodb.test.ts | 98 +++++++++++++++++++ 3 files changed, 154 insertions(+), 24 deletions(-) diff --git a/packages/@aws-cdk/aws-dynamodb/README.md b/packages/@aws-cdk/aws-dynamodb/README.md index c94bce5101c35..a9391d2534673 100644 --- a/packages/@aws-cdk/aws-dynamodb/README.md +++ b/packages/@aws-cdk/aws-dynamodb/README.md @@ -173,3 +173,15 @@ const table = new dynamodb.Table(stack, 'MyTable', { // In this case, the CMK _cannot_ be accessed through table.encryptionKey. ``` + +## Get schema of table or secondary indexes + +To get the partition key and sort key of the table or indexes you have configured: + +```ts +const { partitionKey, sortKey } = table.schema(); + +// In case you want to get schema details for any secondary index + +const { partitionKey, sortKey } = table.schema(INDEX_NAME); +``` diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index 7894994c98b36..4d5b31460d686 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -110,23 +110,28 @@ export enum TableEncryption { } /** - * Properties of a DynamoDB Table - * - * Use {@link TableProps} for all table properties + * Represents the table schema attributes. */ -export interface TableOptions { +export interface SchemaOptions { /** * Partition key attribute definition. */ readonly partitionKey: Attribute; /** - * Table sort key attribute definition. + * Sort key attribute definition. * * @default no sort key */ readonly sortKey?: Attribute; +} +/** + * Properties of a DynamoDB Table + * + * Use {@link TableProps} for all table properties + */ +export interface TableOptions extends SchemaOptions { /** * The read capacity for the table. Careful if you add Global Secondary Indexes, as * those will share the table's provisioned throughput. @@ -269,18 +274,7 @@ export interface SecondaryIndexProps { /** * Properties for a global secondary index */ -export interface GlobalSecondaryIndexProps extends SecondaryIndexProps { - /** - * The attribute of a partition key for the global secondary index. - */ - readonly partitionKey: Attribute; - - /** - * The attribute of a sort key for the global secondary index. - * @default - No sort key - */ - readonly sortKey?: Attribute; - +export interface GlobalSecondaryIndexProps extends SecondaryIndexProps, SchemaOptions { /** * The read capacity for the global secondary index. * @@ -911,7 +905,7 @@ abstract class TableBase extends Resource implements ITable { */ private combinedGrant( grantee: iam.IGrantable, - opts: {keyActions?: string[], tableActions?: string[], streamActions?: string[]}, + opts: { keyActions?: string[], tableActions?: string[], streamActions?: string[] }, ): iam.Grant { if (opts.tableActions) { const resources = [this.tableArn, @@ -944,7 +938,7 @@ abstract class TableBase extends Resource implements ITable { }); return ret; } - throw new Error(`Unexpected 'action', ${ opts.tableActions || opts.streamActions }`); + throw new Error(`Unexpected 'action', ${opts.tableActions || opts.streamActions}`); } private cannedMetric( @@ -1070,7 +1064,7 @@ export class Table extends TableBase { private readonly globalSecondaryIndexes = new Array(); private readonly localSecondaryIndexes = new Array(); - private readonly secondaryIndexNames = new Set(); + private readonly secondaryIndexSchemas = new Map(); private readonly nonKeyAttributes = new Set(); private readonly tablePartitionKey: Attribute; @@ -1166,7 +1160,6 @@ export class Table extends TableBase { const gsiKeySchema = this.buildIndexKeySchema(props.partitionKey, props.sortKey); const gsiProjection = this.buildIndexProjection(props); - this.secondaryIndexNames.add(props.indexName); this.globalSecondaryIndexes.push({ indexName: props.indexName, keySchema: gsiKeySchema, @@ -1177,6 +1170,11 @@ export class Table extends TableBase { }, }); + this.secondaryIndexSchemas.set(props.indexName, { + partitionKey: props.partitionKey, + sortKey: props.sortKey, + }); + this.indexScaling.set(props.indexName, {}); } @@ -1197,12 +1195,16 @@ export class Table extends TableBase { const lsiKeySchema = this.buildIndexKeySchema(this.tablePartitionKey, props.sortKey); const lsiProjection = this.buildIndexProjection(props); - this.secondaryIndexNames.add(props.indexName); this.localSecondaryIndexes.push({ indexName: props.indexName, keySchema: lsiKeySchema, projection: lsiProjection, }); + + this.secondaryIndexSchemas.set(props.indexName, { + partitionKey: this.tablePartitionKey, + sortKey: props.sortKey, + }); } /** @@ -1305,6 +1307,25 @@ export class Table extends TableBase { }); } + /** + * Get schema attributes of table or index. + * + * @returns Schema of table or index. + */ + public schema(indexName?: string): SchemaOptions { + if (!indexName) { + return { + partitionKey: this.tablePartitionKey, + sortKey: this.tableSortKey, + }; + } + let schema = this.secondaryIndexSchemas.get(indexName); + if (!schema) { + throw new Error(`Cannot find schema for index: ${indexName}. Use 'addGlobalSecondaryIndex' or 'addLocalSecondaryIndex' to add index`); + } + return schema; + } + /** * Validate the table construct. * @@ -1353,11 +1374,10 @@ export class Table extends TableBase { * @param indexName a name of global or local secondary index */ private validateIndexName(indexName: string) { - if (this.secondaryIndexNames.has(indexName)) { + if (this.secondaryIndexSchemas.has(indexName)) { // a duplicate index name causes validation exception, status code 400, while trying to create CFN stack throw new Error(`a duplicate index name, ${indexName}, is not allowed`); } - this.secondaryIndexNames.add(indexName); } /** diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts index 3c7de6e7c55b6..c41771b59b078 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts @@ -770,6 +770,104 @@ describe('when billing mode is PAY_PER_REQUEST', () => { }); }); +describe('schema details', () => { + let stack: Stack; + let table: Table; + + beforeEach(() => { + stack = new Stack(); + table = new Table(stack, 'Table A', { + tableName: TABLE_NAME, + partitionKey: TABLE_PARTITION_KEY, + }); + }); + + test('get scheama for table with hash key only', () => { + expect(table.schema()).toEqual({ + partitionKey: TABLE_PARTITION_KEY, + sortKey: undefined, + }); + }); + + test('get scheama for table with hash key + range key', () => { + table = new Table(stack, 'TableB', { + tableName: TABLE_NAME, + partitionKey: TABLE_PARTITION_KEY, + sortKey: TABLE_SORT_KEY, + }); + + expect(table.schema()).toEqual({ + partitionKey: TABLE_PARTITION_KEY, + sortKey: TABLE_SORT_KEY, + }); + }); + + test('get scheama for GSI with hash key', () => { + table.addGlobalSecondaryIndex({ + indexName: GSI_NAME, + partitionKey: GSI_PARTITION_KEY, + }); + + expect(table.schema(GSI_NAME)).toEqual({ + partitionKey: GSI_PARTITION_KEY, + sortKey: undefined, + }); + }); + + test('get scheama for GSI with hash key + range key', () => { + table.addGlobalSecondaryIndex({ + indexName: GSI_NAME, + partitionKey: GSI_PARTITION_KEY, + sortKey: GSI_SORT_KEY, + }); + + expect(table.schema(GSI_NAME)).toEqual({ + partitionKey: GSI_PARTITION_KEY, + sortKey: GSI_SORT_KEY, + }); + }); + + test('get scheama for LSI', () => { + table.addLocalSecondaryIndex({ + indexName: LSI_NAME, + sortKey: LSI_SORT_KEY, + }); + + expect(table.schema(LSI_NAME)).toEqual({ + partitionKey: TABLE_PARTITION_KEY, + sortKey: LSI_SORT_KEY, + }); + }); + + test('get scheama for multiple secondary indexes', () => { + table.addLocalSecondaryIndex({ + indexName: LSI_NAME, + sortKey: LSI_SORT_KEY, + }); + + table.addGlobalSecondaryIndex({ + indexName: GSI_NAME, + partitionKey: GSI_PARTITION_KEY, + sortKey: GSI_SORT_KEY, + }); + + expect(table.schema(LSI_NAME)).toEqual({ + partitionKey: TABLE_PARTITION_KEY, + sortKey: LSI_SORT_KEY, + }); + + expect(table.schema(GSI_NAME)).toEqual({ + partitionKey: GSI_PARTITION_KEY, + sortKey: GSI_SORT_KEY, + }); + }); + + test('get scheama for unknown secondary index', () => { + expect(() => table.schema(GSI_NAME)) + .toThrow(/Cannot find schema for index: MyGSI. Use 'addGlobalSecondaryIndex' or 'addLocalSecondaryIndex' to add index/); + }); +}); + test('when adding a global secondary index with hash key only', () => { const stack = new Stack(); From 645ebe119f7aa4484e72b83770b8ceb433eb7d2d Mon Sep 17 00:00:00 2001 From: joelzhong Date: Wed, 16 Jun 2021 05:42:05 +0800 Subject: [PATCH 23/34] feat(codestarnotifications): new L2 constructs (#10833) Hi @skinny85 Sorry for the late update PRs, and I am ready for the first run. This PR includes: `aws-codestarnotifications` For NotificationRule L2 construct. `aws-codestarnotifications-targets` This library contains a set of classes which can be used as CodeStar notification targets and allows using `sns` or `chatbot` as notification rule targets(multiple targets). `aws-codestarnotifications-source` This library contains a set of classes which can be used as CodeStar notification source and allows using `codebuild`, `codepipeline`, `codecommit` or `codedeploy` as a notification rule source(single source). These are Separate packages for each target or each source can avoid the dependency cycle. Resolves: #9680 --- .../lib/slack-channel-configuration.ts | 10 +- packages/@aws-cdk/aws-chatbot/package.json | 2 + packages/@aws-cdk/aws-codebuild/README.md | 15 ++ .../@aws-cdk/aws-codebuild/lib/project.ts | 127 +++++++++- packages/@aws-cdk/aws-codebuild/package.json | 2 + .../integ.project-notification.expected.json | 207 +++++++++++++++++ .../test/integ.project-notification.ts | 25 ++ .../test/test.notification-rule.ts | 75 ++++++ .../aws-codepipeline-actions/package.json | 2 + .../cloudformation/pipeline-actions.test.ts | 41 ++++ packages/@aws-cdk/aws-codepipeline/README.md | 17 +- .../@aws-cdk/aws-codepipeline/lib/action.ts | 186 ++++++++++++++- .../@aws-cdk/aws-codepipeline/lib/pipeline.ts | 86 ++++++- .../@aws-cdk/aws-codepipeline/package.json | 2 + .../test/notification-rule.test.ts | 189 +++++++++++++++ .../aws-codestarnotifications/README.md | 68 +++++- .../aws-codestarnotifications/lib/index.ts | 3 + .../lib/notification-rule-source.ts | 22 ++ .../lib/notification-rule-target.ts | 27 +++ .../lib/notification-rule.ts | 185 +++++++++++++++ .../aws-codestarnotifications/package.json | 5 +- .../test/codestarnotifications.test.ts | 6 - .../aws-codestarnotifications/test/helpers.ts | 45 ++++ .../test/notification-rule.test.ts | 218 ++++++++++++++++++ packages/@aws-cdk/aws-sns/lib/topic-base.ts | 18 +- packages/@aws-cdk/aws-sns/package.json | 2 + packages/@aws-cdk/aws-sns/test/test.sns.ts | 35 +++ 27 files changed, 1601 insertions(+), 19 deletions(-) create mode 100644 packages/@aws-cdk/aws-codebuild/test/integ.project-notification.expected.json create mode 100644 packages/@aws-cdk/aws-codebuild/test/integ.project-notification.ts create mode 100644 packages/@aws-cdk/aws-codebuild/test/test.notification-rule.ts create mode 100644 packages/@aws-cdk/aws-codepipeline/test/notification-rule.test.ts create mode 100644 packages/@aws-cdk/aws-codestarnotifications/lib/notification-rule-source.ts create mode 100644 packages/@aws-cdk/aws-codestarnotifications/lib/notification-rule-target.ts create mode 100644 packages/@aws-cdk/aws-codestarnotifications/lib/notification-rule.ts delete mode 100644 packages/@aws-cdk/aws-codestarnotifications/test/codestarnotifications.test.ts create mode 100644 packages/@aws-cdk/aws-codestarnotifications/test/helpers.ts create mode 100644 packages/@aws-cdk/aws-codestarnotifications/test/notification-rule.test.ts diff --git a/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts b/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts index 425c432fdc5a4..69646e783ebd7 100644 --- a/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts +++ b/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts @@ -1,4 +1,5 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import * as notifications from '@aws-cdk/aws-codestarnotifications'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; import * as sns from '@aws-cdk/aws-sns'; @@ -105,7 +106,7 @@ export enum LoggingLevel { /** * Represents a Slack channel configuration */ -export interface ISlackChannelConfiguration extends cdk.IResource, iam.IGrantable { +export interface ISlackChannelConfiguration extends cdk.IResource, iam.IGrantable, notifications.INotificationRuleTarget { /** * The ARN of the Slack channel configuration @@ -179,6 +180,13 @@ abstract class SlackChannelConfigurationBase extends cdk.Resource implements ISl ...props, }); } + + public bindAsNotificationRuleTarget(_scope: Construct): notifications.NotificationRuleTargetConfig { + return { + targetType: 'AWSChatbotSlack', + targetAddress: this.slackChannelConfigurationArn, + }; + } } /** diff --git a/packages/@aws-cdk/aws-chatbot/package.json b/packages/@aws-cdk/aws-chatbot/package.json index 2c98e75a8cd97..e7db76bc38cf2 100644 --- a/packages/@aws-cdk/aws-chatbot/package.json +++ b/packages/@aws-cdk/aws-chatbot/package.json @@ -83,6 +83,7 @@ "@aws-cdk/assert-internal": "0.0.0" }, "dependencies": { + "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", @@ -91,6 +92,7 @@ "constructs": "^3.3.69" }, "peerDependencies": { + "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 3c26927fd39ce..54ded33d63974 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -468,6 +468,21 @@ const rule = project.onStateChange('BuildStateChange', { }); ``` +## CodeStar Notifications + +To define CodeStar Notification rules for Projects, use one of the `notifyOnXxx()` methods. +They are very similar to `onXxx()` methods for CloudWatch events: + +```ts +const target = new chatbot.SlackChannelConfiguration(stack, 'MySlackChannel', { + slackChannelConfigurationName: 'YOUR_CHANNEL_NAME', + slackWorkspaceId: 'YOUR_SLACK_WORKSPACE_ID', + slackChannelId: 'YOUR_SLACK_CHANNEL_ID', +}); + +const rule = project.notifyOnBuildSucceeded('NotifyOnBuildSucceeded', target); +``` + ## Secondary sources and artifacts CodeBuild Projects can get their sources from multiple places, and produce diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 85cf2cbe75789..30f8be9051751 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -1,4 +1,5 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import * as notifications from '@aws-cdk/aws-codestarnotifications'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecr from '@aws-cdk/aws-ecr'; import { DockerImageAsset, DockerImageAssetProps } from '@aws-cdk/aws-ecr-assets'; @@ -36,7 +37,19 @@ export interface BatchBuildConfig { readonly role: iam.IRole; } -export interface IProject extends IResource, iam.IGrantable, ec2.IConnectable { +/** + * Additional options to pass to the notification rule. + */ +export interface ProjectNotifyOnOptions extends notifications.NotificationRuleOptions { + /** + * A list of event types associated with this notification rule for CodeBuild Project. + * For a complete list of event types and IDs, see Notification concepts in the Developer Tools Console User Guide. + * @see https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#concepts-api + */ + readonly events: ProjectNotificationEvents[]; +} + +export interface IProject extends IResource, iam.IGrantable, ec2.IConnectable, notifications.INotificationRuleSource { /** * The ARN of this Project. * @attribute @@ -170,6 +183,42 @@ export interface IProject extends IResource, iam.IGrantable, ec2.IConnectable { * @default sum over 5 minutes */ metricFailedBuilds(props?: cloudwatch.MetricOptions): cloudwatch.Metric; + + /** + * Defines a CodeStar Notification rule triggered when the project + * events emitted by you specified, it very similar to `onEvent` API. + * + * You can also use the methods `notifyOnBuildSucceeded` and + * `notifyOnBuildFailed` to define rules for these specific event emitted. + * + * @param id The logical identifier of the CodeStar Notifications rule that will be created + * @param target The target to register for the CodeStar Notifications destination. + * @param options Customization options for CodeStar Notifications rule + * @returns CodeStar Notifications rule associated with this build project. + */ + notifyOn( + id: string, + target: notifications.INotificationRuleTarget, + options: ProjectNotifyOnOptions, + ): notifications.INotificationRule; + + /** + * Defines a CodeStar notification rule which triggers when a build completes successfully. + */ + notifyOnBuildSucceeded( + id: string, + target: notifications.INotificationRuleTarget, + options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule; + + /** + * Defines a CodeStar notification rule which triggers when a build fails. + */ + notifyOnBuildFailed( + id: string, + target: notifications.INotificationRuleTarget, + options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule; } /** @@ -405,6 +454,46 @@ abstract class ProjectBase extends Resource implements IProject { return this.cannedMetric(CodeBuildMetrics.failedBuildsSum, props); } + public notifyOn( + id: string, + target: notifications.INotificationRuleTarget, + options: ProjectNotifyOnOptions, + ): notifications.INotificationRule { + return new notifications.NotificationRule(this, id, { + ...options, + source: this, + targets: [target], + }); + } + + public notifyOnBuildSucceeded( + id: string, + target: notifications.INotificationRuleTarget, + options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule { + return this.notifyOn(id, target, { + ...options, + events: [ProjectNotificationEvents.BUILD_SUCCEEDED], + }); + } + + public notifyOnBuildFailed( + id: string, + target: notifications.INotificationRuleTarget, + options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule { + return this.notifyOn(id, target, { + ...options, + events: [ProjectNotificationEvents.BUILD_FAILED], + }); + } + + public bindAsNotificationRuleSource(_scope: Construct): notifications.NotificationRuleSourceConfig { + return { + sourceArn: this.projectArn, + }; + } + private cannedMetric( fn: (dims: { ProjectName: string }) => cloudwatch.MetricProps, props?: cloudwatch.MetricOptions): cloudwatch.Metric { @@ -1961,3 +2050,39 @@ export enum BuildEnvironmentVariableType { */ SECRETS_MANAGER = 'SECRETS_MANAGER' } + +/** + * The list of event types for AWS Codebuild + * @see https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#events-ref-buildproject + */ +export enum ProjectNotificationEvents { + /** + * Trigger notification when project build state failed + */ + BUILD_FAILED = 'codebuild-project-build-state-failed', + + /** + * Trigger notification when project build state succeeded + */ + BUILD_SUCCEEDED = 'codebuild-project-build-state-succeeded', + + /** + * Trigger notification when project build state in progress + */ + BUILD_IN_PROGRESS = 'codebuild-project-build-state-in-progress', + + /** + * Trigger notification when project build state stopped + */ + BUILD_STOPPED = 'codebuild-project-build-state-stopped', + + /** + * Trigger notification when project build phase failure + */ + BUILD_PHASE_FAILED = 'codebuild-project-build-phase-failure', + + /** + * Trigger notification when project build phase success + */ + BUILD_PHASE_SUCCEEDED = 'codebuild-project-build-phase-success', +} diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index 9c91cfa4d639b..db40c989dfd01 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -90,6 +90,7 @@ "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", + "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-ecr": "0.0.0", "@aws-cdk/aws-ecr-assets": "0.0.0", @@ -113,6 +114,7 @@ "@aws-cdk/assets": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", + "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-ecr": "0.0.0", "@aws-cdk/aws-ecr-assets": "0.0.0", diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-notification.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.project-notification.expected.json new file mode 100644 index 0000000000000..5cb77017d404d --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-notification.expected.json @@ -0,0 +1,207 @@ +{ + "Resources": { + "MyProjectRole9BBE5233": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codebuild.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyProjectRoleDefaultPolicyB19B7C29": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "MyProject39F7B0AE" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "MyProject39F7B0AE" + }, + ":*" + ] + ] + } + ] + }, + { + "Action": [ + "codebuild:CreateReportGroup", + "codebuild:CreateReport", + "codebuild:UpdateReport", + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codebuild:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":report-group/", + { + "Ref": "MyProject39F7B0AE" + }, + "-*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyProjectRoleDefaultPolicyB19B7C29", + "Roles": [ + { + "Ref": "MyProjectRole9BBE5233" + } + ] + } + }, + "MyProject39F7B0AE": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "NO_ARTIFACTS" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "MyProjectRole9BBE5233", + "Arn" + ] + }, + "Source": { + "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"echo \\\"Nothing to do!\\\"\"\n ]\n }\n }\n}", + "Type": "NO_SOURCE" + }, + "EncryptionKey": "alias/aws/s3" + } + }, + "MyProjectNotifyOnBuildSucceeded225C467F": { + "Type": "AWS::CodeStarNotifications::NotificationRule", + "Properties": { + "DetailType": "FULL", + "EventTypeIds": [ + "codebuild-project-build-state-succeeded" + ], + "Name": "awscdkcodebuildprojectvpcMyProjectNotifyOnBuildSucceeded3BC28121", + "Resource": { + "Fn::GetAtt": [ + "MyProject39F7B0AE", + "Arn" + ] + }, + "Targets": [ + { + "TargetAddress": { + "Ref": "MyTopic86869434" + }, + "TargetType": "SNS" + } + ] + } + }, + "MyTopic86869434": { + "Type": "AWS::SNS::Topic" + }, + "MyTopicPolicy12A5EC17": { + "Type": "AWS::SNS::TopicPolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Effect": "Allow", + "Principal": { + "Service": "codestar-notifications.amazonaws.com" + }, + "Resource": { + "Ref": "MyTopic86869434" + }, + "Sid": "0" + } + ], + "Version": "2012-10-17" + }, + "Topics": [ + { + "Ref": "MyTopic86869434" + } + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-notification.ts b/packages/@aws-cdk/aws-codebuild/test/integ.project-notification.ts new file mode 100644 index 0000000000000..9159a1e61fc32 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-notification.ts @@ -0,0 +1,25 @@ +#!/usr/bin/env node +import * as sns from '@aws-cdk/aws-sns'; +import * as cdk from '@aws-cdk/core'; +import * as codebuild from '../lib'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-codebuild-project-vpc'); + +const project = new codebuild.Project(stack, 'MyProject', { + buildSpec: codebuild.BuildSpec.fromObject({ + version: '0.2', + phases: { + build: { + commands: ['echo "Nothing to do!"'], + }, + }, + }), +}); + +const target = new sns.Topic(stack, 'MyTopic'); + +project.notifyOnBuildSucceeded('NotifyOnBuildSucceeded', target); + +app.synth(); diff --git a/packages/@aws-cdk/aws-codebuild/test/test.notification-rule.ts b/packages/@aws-cdk/aws-codebuild/test/test.notification-rule.ts new file mode 100644 index 0000000000000..4ebb4eed3578e --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/test/test.notification-rule.ts @@ -0,0 +1,75 @@ +import { expect, haveResource } from '@aws-cdk/assert-internal'; +import * as sns from '@aws-cdk/aws-sns'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import * as codebuild from '../lib'; + +export = { + 'notifications rule'(test: Test) { + const stack = new cdk.Stack(); + const project = new codebuild.Project(stack, 'MyCodebuildProject', { + buildSpec: codebuild.BuildSpec.fromObject({ + version: '0.2', + phases: { + build: { + commands: [ + 'echo "Hello, CodeBuild!"', + ], + }, + }, + }), + }); + + const target = new sns.Topic(stack, 'MyTopic'); + + project.notifyOnBuildSucceeded('NotifyOnBuildSucceeded', target); + + project.notifyOnBuildFailed('NotifyOnBuildFailed', target); + + expect(stack).to(haveResource('AWS::CodeStarNotifications::NotificationRule', { + Name: 'MyCodebuildProjectNotifyOnBuildSucceeded77719592', + DetailType: 'FULL', + EventTypeIds: [ + 'codebuild-project-build-state-succeeded', + ], + Resource: { + 'Fn::GetAtt': [ + 'MyCodebuildProjectB0479580', + 'Arn', + ], + }, + Targets: [ + { + TargetAddress: { + Ref: 'MyTopic86869434', + }, + TargetType: 'SNS', + }, + ], + })); + + expect(stack).to(haveResource('AWS::CodeStarNotifications::NotificationRule', { + Name: 'MyCodebuildProjectNotifyOnBuildFailedF680E310', + DetailType: 'FULL', + EventTypeIds: [ + 'codebuild-project-build-state-failed', + ], + Resource: { + 'Fn::GetAtt': [ + 'MyCodebuildProjectB0479580', + 'Arn', + ], + }, + Targets: [ + { + TargetAddress: { + Ref: 'MyTopic86869434', + }, + TargetType: 'SNS', + }, + ], + })); + + test.done(); + }, +}; diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package.json b/packages/@aws-cdk/aws-codepipeline-actions/package.json index 675ac2ba21c5c..b28c8a289c6a8 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package.json @@ -70,6 +70,7 @@ "devDependencies": { "@types/jest": "^26.0.23", "@aws-cdk/aws-cloudtrail": "0.0.0", + "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "@types/lodash": "^4.14.170", "cdk-build-tools": "0.0.0", @@ -108,6 +109,7 @@ "@aws-cdk/aws-codecommit": "0.0.0", "@aws-cdk/aws-codedeploy": "0.0.0", "@aws-cdk/aws-codepipeline": "0.0.0", + "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-ecr": "0.0.0", "@aws-cdk/aws-ecs": "0.0.0", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/pipeline-actions.test.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/pipeline-actions.test.ts index 1433ffc6e2860..2e94273f6924c 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/pipeline-actions.test.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/pipeline-actions.test.ts @@ -1,4 +1,5 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as notifications from '@aws-cdk/aws-codestarnotifications'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; @@ -343,6 +344,46 @@ class PipelineDouble extends cdk.Resource implements codepipeline.IPipeline { public onStateChange(_id: string, _options: events.OnEventOptions): events.Rule { throw new Error('Method not implemented.'); } + public notifyOn( + _id: string, + _target: notifications.INotificationRuleTarget, + _options: codepipeline.PipelineNotifyOnOptions, + ): notifications.NotificationRule { + throw new Error('Method not implemented.'); + } + public notifyOnExecutionStateChange( + _id: string, + _target: notifications.INotificationRuleTarget, + _options?: notifications.NotificationRuleOptions, + ): notifications.NotificationRule { + throw new Error('Method not implemented.'); + } + public notifyOnAnyStageStateChange( + _id: string, + _target: notifications.INotificationRuleTarget, + _options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule { + throw new Error('Method not implemented.'); + } + public notifyOnAnyActionStateChange( + _id: string, + _target: notifications.INotificationRuleTarget, + _options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule { + throw new Error('Method not implemented.'); + } + public notifyOnAnyManualApprovalStateChange( + _id: string, + _target: notifications.INotificationRuleTarget, + _options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule { + throw new Error('Method not implemented.'); + } + public bindAsNotificationRuleSource( + _scope: constructs.Construct, + ): notifications.NotificationRuleSourceConfig { + throw new Error('Method not implemented.'); + } } class FullAction { diff --git a/packages/@aws-cdk/aws-codepipeline/README.md b/packages/@aws-cdk/aws-codepipeline/README.md index 6a1731b614131..f3f61014cdaa4 100644 --- a/packages/@aws-cdk/aws-codepipeline/README.md +++ b/packages/@aws-cdk/aws-codepipeline/README.md @@ -190,7 +190,7 @@ place to serve as replication buckets, you can supply these at Pipeline definiti time using the `crossRegionReplicationBuckets` parameter. Example: ```ts -const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { /* ... */ }); +const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { // ... crossRegionReplicationBuckets: { @@ -339,3 +339,18 @@ myPipeline.onStateChange('MyPipelineStateChange', target); myStage.onStateChange('MyStageStateChange', target); myAction.onStateChange('MyActionStateChange', target); ``` + +## CodeStar Notifications + +To define CodeStar Notification rules for Pipelines, use one of the `notifyOnXxx()` methods. +They are very similar to `onXxx()` methods for CloudWatch events: + +```ts +const target = new chatbot.SlackChannelConfiguration(stack, 'MySlackChannel', { + slackChannelConfigurationName: 'YOUR_CHANNEL_NAME', + slackWorkspaceId: 'YOUR_SLACK_WORKSPACE_ID', + slackChannelId: 'YOUR_SLACK_CHANNEL_ID', +}); + +const rule = pipeline.notifyOnExecutionStateChange('NotifyOnExecutionStateChange', target); +``` diff --git a/packages/@aws-cdk/aws-codepipeline/lib/action.ts b/packages/@aws-cdk/aws-codepipeline/lib/action.ts index 2bf32ca5ee330..3ae4507d642a6 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/action.ts @@ -1,3 +1,4 @@ +import * as notifications from '@aws-cdk/aws-codestarnotifications'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; @@ -118,6 +119,18 @@ export interface ActionConfig { readonly configuration?: any; } +/** + * Additional options to pass to the notification rule. + */ +export interface PipelineNotifyOnOptions extends notifications.NotificationRuleOptions { + /** + * A list of event types associated with this notification rule for CodePipeline Pipeline. + * For a complete list of event types and IDs, see Notification concepts in the Developer Tools Console User Guide. + * @see https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#concepts-api + */ + readonly events: PipelineNotificationEvents[]; +} + /** * A Pipeline Action. * If you want to implement this interface, @@ -157,7 +170,7 @@ export interface IAction { * It extends {@link events.IRuleTarget}, * so this interface can be used as a Target for CloudWatch Events. */ -export interface IPipeline extends IResource { +export interface IPipeline extends IResource, notifications.INotificationRuleSource { /** * The name of the Pipeline. * @@ -188,6 +201,81 @@ export interface IPipeline extends IResource { * @param options Additional options to pass to the event rule. */ onStateChange(id: string, options?: events.OnEventOptions): events.Rule; + + /** + * Defines a CodeStar notification rule triggered when the pipeline + * events emitted by you specified, it very similar to `onEvent` API. + * + * You can also use the methods `notifyOnExecutionStateChange`, `notifyOnAnyStageStateChange`, + * `notifyOnAnyActionStateChange` and `notifyOnAnyManualApprovalStateChange` + * to define rules for these specific event emitted. + * + * @param id The id of the CodeStar notification rule + * @param target The target to register for the CodeStar Notifications destination. + * @param options Customization options for CodeStar notification rule + * @returns CodeStar notification rule associated with this build project. + */ + notifyOn( + id: string, + target: notifications.INotificationRuleTarget, + options: PipelineNotifyOnOptions, + ): notifications.INotificationRule; + + /** + * Define an notification rule triggered by the set of the "Pipeline execution" events emitted from this pipeline. + * @see https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#events-ref-pipeline + * + * @param id Identifier for this notification handler. + * @param target The target to register for the CodeStar Notifications destination. + * @param options Additional options to pass to the notification rule. + */ + notifyOnExecutionStateChange( + id: string, + target: notifications.INotificationRuleTarget, + options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule; + + /** + * Define an notification rule triggered by the set of the "Stage execution" events emitted from this pipeline. + * @see https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#events-ref-pipeline + * + * @param id Identifier for this notification handler. + * @param target The target to register for the CodeStar Notifications destination. + * @param options Additional options to pass to the notification rule. + */ + notifyOnAnyStageStateChange( + id: string, + target: notifications.INotificationRuleTarget, + options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule; + + /** + * Define an notification rule triggered by the set of the "Action execution" events emitted from this pipeline. + * @see https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#events-ref-pipeline + * + * @param id Identifier for this notification handler. + * @param target The target to register for the CodeStar Notifications destination. + * @param options Additional options to pass to the notification rule. + */ + notifyOnAnyActionStateChange( + id: string, + target: notifications.INotificationRuleTarget, + options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule; + + /** + * Define an notification rule triggered by the set of the "Manual approval" events emitted from this pipeline. + * @see https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#events-ref-pipeline + * + * @param id Identifier for this notification handler. + * @param target The target to register for the CodeStar Notifications destination. + * @param options Additional options to pass to the notification rule. + */ + notifyOnAnyManualApprovalStateChange( + id: string, + target: notifications.INotificationRuleTarget, + options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule; } /** @@ -379,3 +467,99 @@ export abstract class Action implements IAction { } } } + +/** + * The list of event types for AWS Codepipeline Pipeline + * @see https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#events-ref-pipeline + */ +export enum PipelineNotificationEvents { + /** + * Trigger notification when pipeline execution failed + */ + PIPELINE_EXECUTION_FAILED = 'codepipeline-pipeline-pipeline-execution-failed', + + /** + * Trigger notification when pipeline execution canceled + */ + PIPELINE_EXECUTION_CANCELED = 'codepipeline-pipeline-pipeline-execution-canceled', + + /** + * Trigger notification when pipeline execution started + */ + PIPELINE_EXECUTION_STARTED = 'codepipeline-pipeline-pipeline-execution-started', + + /** + * Trigger notification when pipeline execution resumed + */ + PIPELINE_EXECUTION_RESUMED = 'codepipeline-pipeline-pipeline-execution-resumed', + + /** + * Trigger notification when pipeline execution succeeded + */ + PIPELINE_EXECUTION_SUCCEEDED = 'codepipeline-pipeline-pipeline-execution-succeeded', + + /** + * Trigger notification when pipeline execution superseded + */ + PIPELINE_EXECUTION_SUPERSEDED = 'codepipeline-pipeline-pipeline-execution-superseded', + + /** + * Trigger notification when pipeline stage execution started + */ + STAGE_EXECUTION_STARTED = 'codepipeline-pipeline-stage-execution-started', + + /** + * Trigger notification when pipeline stage execution succeeded + */ + STAGE_EXECUTION_SUCCEEDED = 'codepipeline-pipeline-stage-execution-succeeded', + + /** + * Trigger notification when pipeline stage execution resumed + */ + STAGE_EXECUTION_RESUMED = 'codepipeline-pipeline-stage-execution-resumed', + + /** + * Trigger notification when pipeline stage execution canceled + */ + STAGE_EXECUTION_CANCELED = 'codepipeline-pipeline-stage-execution-canceled', + + /** + * Trigger notification when pipeline stage execution failed + */ + STAGE_EXECUTION_FAILED = 'codepipeline-pipeline-stage-execution-failed', + + /** + * Trigger notification when pipeline action execution succeeded + */ + ACTION_EXECUTION_SUCCEEDED = 'codepipeline-pipeline-action-execution-succeeded', + + /** + * Trigger notification when pipeline action execution failed + */ + ACTION_EXECUTION_FAILED = 'codepipeline-pipeline-action-execution-failed', + + /** + * Trigger notification when pipeline action execution canceled + */ + ACTION_EXECUTION_CANCELED = 'codepipeline-pipeline-action-execution-canceled', + + /** + * Trigger notification when pipeline action execution started + */ + ACTION_EXECUTION_STARTED = 'codepipeline-pipeline-action-execution-started', + + /** + * Trigger notification when pipeline manual approval failed + */ + MANUAL_APPROVAL_FAILED = 'codepipeline-pipeline-manual-approval-failed', + + /** + * Trigger notification when pipeline manual approval needed + */ + MANUAL_APPROVAL_NEEDED = 'codepipeline-pipeline-manual-approval-needed', + + /** + * Trigger notification when pipeline manual approval succeeded + */ + MANUAL_APPROVAL_SUCCEEDED = 'codepipeline-pipeline-manual-approval-succeeded', +} diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index 1b3ca9e0e6adc..c1e4df100aa23 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -1,3 +1,4 @@ +import * as notifications from '@aws-cdk/aws-codestarnotifications'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; @@ -7,7 +8,7 @@ import { IStackSynthesizer, Lazy, Names, PhysicalName, RemovalPolicy, Resource, Stack, Token, } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { ActionCategory, IAction, IPipeline, IStage } from './action'; +import { ActionCategory, IAction, IPipeline, IStage, PipelineNotificationEvents, PipelineNotifyOnOptions } from './action'; import { CfnPipeline } from './codepipeline.generated'; import { CrossRegionSupportConstruct, CrossRegionSupportStack } from './private/cross-region-support-stack'; import { FullActionDescriptor } from './private/full-action-descriptor'; @@ -162,6 +163,89 @@ abstract class PipelineBase extends Resource implements IPipeline { return rule; } + public bindAsNotificationRuleSource(_scope: Construct): notifications.NotificationRuleSourceConfig { + return { + sourceArn: this.pipelineArn, + }; + } + + public notifyOn( + id: string, + target: notifications.INotificationRuleTarget, + options: PipelineNotifyOnOptions, + ): notifications.INotificationRule { + return new notifications.NotificationRule(this, id, { + ...options, + source: this, + targets: [target], + }); + } + + public notifyOnExecutionStateChange( + id: string, + target: notifications.INotificationRuleTarget, + options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule { + return this.notifyOn(id, target, { + ...options, + events: [ + PipelineNotificationEvents.PIPELINE_EXECUTION_FAILED, + PipelineNotificationEvents.PIPELINE_EXECUTION_CANCELED, + PipelineNotificationEvents.PIPELINE_EXECUTION_STARTED, + PipelineNotificationEvents.PIPELINE_EXECUTION_RESUMED, + PipelineNotificationEvents.PIPELINE_EXECUTION_SUCCEEDED, + PipelineNotificationEvents.PIPELINE_EXECUTION_SUPERSEDED, + ], + }); + } + + public notifyOnAnyStageStateChange( + id: string, + target: notifications.INotificationRuleTarget, + options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule { + return this.notifyOn(id, target, { + ...options, + events: [ + PipelineNotificationEvents.STAGE_EXECUTION_CANCELED, + PipelineNotificationEvents.STAGE_EXECUTION_FAILED, + PipelineNotificationEvents.STAGE_EXECUTION_RESUMED, + PipelineNotificationEvents.STAGE_EXECUTION_STARTED, + PipelineNotificationEvents.STAGE_EXECUTION_SUCCEEDED, + ], + }); + } + + public notifyOnAnyActionStateChange( + id: string, + target: notifications.INotificationRuleTarget, + options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule { + return this.notifyOn(id, target, { + ...options, + events: [ + PipelineNotificationEvents.ACTION_EXECUTION_CANCELED, + PipelineNotificationEvents.ACTION_EXECUTION_FAILED, + PipelineNotificationEvents.ACTION_EXECUTION_STARTED, + PipelineNotificationEvents.ACTION_EXECUTION_SUCCEEDED, + ], + }); + } + + public notifyOnAnyManualApprovalStateChange( + id: string, + target: notifications.INotificationRuleTarget, + options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule { + return this.notifyOn(id, target, { + ...options, + events: [ + PipelineNotificationEvents.MANUAL_APPROVAL_FAILED, + PipelineNotificationEvents.MANUAL_APPROVAL_NEEDED, + PipelineNotificationEvents.MANUAL_APPROVAL_SUCCEEDED, + ], + }); + } } /** diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index 2a955d5dc665c..82c75be1eea46 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -90,6 +90,7 @@ "@aws-cdk/assert-internal": "0.0.0" }, "dependencies": { + "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", @@ -99,6 +100,7 @@ }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { + "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", diff --git a/packages/@aws-cdk/aws-codepipeline/test/notification-rule.test.ts b/packages/@aws-cdk/aws-codepipeline/test/notification-rule.test.ts new file mode 100644 index 0000000000000..46d32794dc3e1 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/test/notification-rule.test.ts @@ -0,0 +1,189 @@ +import '@aws-cdk/assert-internal/jest'; +import * as cdk from '@aws-cdk/core'; +import * as codepipeline from '../lib'; +import { FakeBuildAction } from './fake-build-action'; +import { FakeSourceAction } from './fake-source-action'; + +describe('pipeline with codestar notification integration', () => { + let stack: cdk.Stack; + let pipeline: codepipeline.Pipeline; + let sourceArtifact: codepipeline.Artifact; + beforeEach(() => { + stack = new cdk.Stack(); + sourceArtifact = new codepipeline.Artifact(); + pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { + stages: [ + { + stageName: 'Source', + actions: [new FakeSourceAction({ actionName: 'Fetch', output: sourceArtifact })], + }, + { + stageName: 'Build', + actions: [new FakeBuildAction({ actionName: 'Gcc', input: sourceArtifact })], + }, + ], + }); + }); + + test('On "Pipeline" execution events emitted notification rule', () => { + const target = { + bindAsNotificationRuleTarget: () => ({ + targetType: 'AWSChatbotSlack', + targetAddress: 'SlackID', + }), + }; + + pipeline.notifyOnExecutionStateChange('NotifyOnExecutionStateChange', target); + + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { + Name: 'PipelineNotifyOnExecutionStateChange9FE60973', + DetailType: 'FULL', + EventTypeIds: [ + 'codepipeline-pipeline-pipeline-execution-failed', + 'codepipeline-pipeline-pipeline-execution-canceled', + 'codepipeline-pipeline-pipeline-execution-started', + 'codepipeline-pipeline-pipeline-execution-resumed', + 'codepipeline-pipeline-pipeline-execution-succeeded', + 'codepipeline-pipeline-pipeline-execution-superseded', + ], + Resource: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':codepipeline:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':', + { Ref: 'PipelineC660917D' }, + ]], + }, + Targets: [ + { + TargetAddress: 'SlackID', + TargetType: 'AWSChatbotSlack', + }, + ], + }); + }); + + test('On any "Stage" execution events emitted notification rule in pipeline', () => { + const target = { + bindAsNotificationRuleTarget: () => ({ + targetType: 'AWSChatbotSlack', + targetAddress: 'SlackID', + }), + }; + + pipeline.notifyOnAnyStageStateChange('NotifyOnAnyStageStateChange', target); + + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { + Name: 'PipelineNotifyOnAnyStageStateChange05355CCD', + DetailType: 'FULL', + EventTypeIds: [ + 'codepipeline-pipeline-stage-execution-canceled', + 'codepipeline-pipeline-stage-execution-failed', + 'codepipeline-pipeline-stage-execution-resumed', + 'codepipeline-pipeline-stage-execution-started', + 'codepipeline-pipeline-stage-execution-succeeded', + ], + Resource: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':codepipeline:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':', + { Ref: 'PipelineC660917D' }, + ]], + }, + Targets: [ + { + TargetAddress: 'SlackID', + TargetType: 'AWSChatbotSlack', + }, + ], + }); + }); + + test('On any "Action" execution events emitted notification rule in pipeline', () => { + const target = { + bindAsNotificationRuleTarget: () => ({ + targetType: 'AWSChatbotSlack', + targetAddress: 'SlackID', + }), + }; + + pipeline.notifyOnAnyActionStateChange('NotifyOnAnyActionStateChange', target); + + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { + Name: 'PipelineNotifyOnAnyActionStateChange64D5B2AA', + DetailType: 'FULL', + EventTypeIds: [ + 'codepipeline-pipeline-action-execution-canceled', + 'codepipeline-pipeline-action-execution-failed', + 'codepipeline-pipeline-action-execution-started', + 'codepipeline-pipeline-action-execution-succeeded', + ], + Resource: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':codepipeline:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':', + { Ref: 'PipelineC660917D' }, + ]], + }, + Targets: [ + { + TargetAddress: 'SlackID', + TargetType: 'AWSChatbotSlack', + }, + ], + }); + }); + + test('On any "Manual approval" execution events emitted notification rule in pipeline', () => { + const target = { + bindAsNotificationRuleTarget: () => ({ + targetType: 'AWSChatbotSlack', + targetAddress: 'SlackID', + }), + }; + + pipeline.notifyOnAnyManualApprovalStateChange('NotifyOnAnyManualApprovalStateChange', target); + + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { + Name: 'PipelineNotifyOnAnyManualApprovalStateChangeE60778F7', + DetailType: 'FULL', + EventTypeIds: [ + 'codepipeline-pipeline-manual-approval-failed', + 'codepipeline-pipeline-manual-approval-needed', + 'codepipeline-pipeline-manual-approval-succeeded', + ], + Resource: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':codepipeline:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':', + { Ref: 'PipelineC660917D' }, + ]], + }, + Targets: [ + { + TargetAddress: 'SlackID', + TargetType: 'AWSChatbotSlack', + }, + ], + }); + }); +}); diff --git a/packages/@aws-cdk/aws-codestarnotifications/README.md b/packages/@aws-cdk/aws-codestarnotifications/README.md index cc63ebe8ecd28..b13a33089b01a 100644 --- a/packages/@aws-cdk/aws-codestarnotifications/README.md +++ b/packages/@aws-cdk/aws-codestarnotifications/README.md @@ -1,13 +1,11 @@ -# AWS::CodeStarNotifications Construct Library +# AWS CodeStarNotifications 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 +![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) --- @@ -15,6 +13,66 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. +## NotificationRule + +The `NotificationRule` construct defines an AWS CodeStarNotifications rule. +The rule specifies the events you want notifications about and the targets +(such as Amazon SNS topics or AWS Chatbot clients configured for Slack) +where you want to receive them. +Notification targets are objects that implement the `INotificationRuleTarget` +interface and notification source is object that implement the `INotificationRuleSource` interface. + +## Notification Targets + +This module includes classes that implement the `INotificationRuleTarget` interface for SNS and slack in AWS Chatbot. + +The following targets are supported: + +* `SNS`: specify event and notify to SNS topic. +* `AWS Chatbot`: specify event and notify to slack channel and only support `SlackChannelConfiguration`. + +## Examples + ```ts -import * as codestarnotifications from '@aws-cdk/aws-codestarnotifications'; +import * as notifications from '@aws-cdk/aws-codestarnotifications'; +import * as codebuild from '@aws-cdk/aws-codebuild'; +import * as sns from '@aws-cdk/aws-sns'; +import * as chatbot from '@aws-cdk/aws-chatbot'; + +const project = new codebuild.PipelineProject(stack, 'MyProject'); + +const topic = new sns.Topic(stack, 'MyTopic1'); + +const slack = new chatbot.SlackChannelConfiguration(stack, 'MySlackChannel', { + slackChannelConfigurationName: 'YOUR_CHANNEL_NAME', + slackWorkspaceId: 'YOUR_SLACK_WORKSPACE_ID', + slackChannelId: 'YOUR_SLACK_CHANNEL_ID', +}); + +const rule = new notifications.NotificationRule(stack, 'NotificationRule', { + source: project, + events: [ + 'codebuild-project-build-state-succeeded', + 'codebuild-project-build-state-failed', + ], + targets: [topic], +}); +rule.addTarget(slack); ``` + +## Notification Source + +This module includes classes that implement the `INotificationRuleSource` interface for AWS CodeBuild, +AWS CodePipeline and will support AWS CodeCommit, AWS CodeDeploy in future. + +The following sources are supported: + +* `AWS CodeBuild`: support codebuild project to trigger notification when event specified. +* `AWS CodePipeline`: support codepipeline to trigger notification when event specified. + +## Events + +For the complete list of supported event types for CodeBuild and CodePipeline, see: + +* [Events for notification rules on build projects](https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#events-ref-buildproject). +* [Events for notification rules on pipelines](https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#events-ref-pipeline). diff --git a/packages/@aws-cdk/aws-codestarnotifications/lib/index.ts b/packages/@aws-cdk/aws-codestarnotifications/lib/index.ts index 32e16c97f1900..86bb92180fc3a 100644 --- a/packages/@aws-cdk/aws-codestarnotifications/lib/index.ts +++ b/packages/@aws-cdk/aws-codestarnotifications/lib/index.ts @@ -1,2 +1,5 @@ // AWS::CodeStarNotifications CloudFormation Resources: export * from './codestarnotifications.generated'; +export * from './notification-rule'; +export * from './notification-rule-source'; +export * from './notification-rule-target'; diff --git a/packages/@aws-cdk/aws-codestarnotifications/lib/notification-rule-source.ts b/packages/@aws-cdk/aws-codestarnotifications/lib/notification-rule-source.ts new file mode 100644 index 0000000000000..08eef95380b38 --- /dev/null +++ b/packages/@aws-cdk/aws-codestarnotifications/lib/notification-rule-source.ts @@ -0,0 +1,22 @@ +import * as constructs from 'constructs'; + +/** + * Information about the Codebuild or CodePipeline associated with a notification source. + */ +export interface NotificationRuleSourceConfig { + /** + * The Amazon Resource Name (ARN) of the notification source. + */ + readonly sourceArn: string; +} + +/** + * Represents a notification source + * The source that allows CodeBuild and CodePipeline to associate with this rule. + */ +export interface INotificationRuleSource { + /** + * Returns a source configuration for notification rule. + */ + bindAsNotificationRuleSource(scope: constructs.Construct): NotificationRuleSourceConfig; +} diff --git a/packages/@aws-cdk/aws-codestarnotifications/lib/notification-rule-target.ts b/packages/@aws-cdk/aws-codestarnotifications/lib/notification-rule-target.ts new file mode 100644 index 0000000000000..7b7b27ebc0335 --- /dev/null +++ b/packages/@aws-cdk/aws-codestarnotifications/lib/notification-rule-target.ts @@ -0,0 +1,27 @@ +import * as constructs from 'constructs'; + +/** + * Information about the SNS topic or AWS Chatbot client associated with a notification target. + */ +export interface NotificationRuleTargetConfig { + /** + * The target type. Can be an Amazon SNS topic or AWS Chatbot client. + */ + readonly targetType: string; + + /** + * The Amazon Resource Name (ARN) of the Amazon SNS topic or AWS Chatbot client. + */ + readonly targetAddress: string; +} + +/** + * Represents a notification target + * That allows AWS Chatbot and SNS topic to associate with this rule target. + */ +export interface INotificationRuleTarget { + /** + * Returns a target configuration for notification rule. + */ + bindAsNotificationRuleTarget(scope: constructs.Construct): NotificationRuleTargetConfig; +} diff --git a/packages/@aws-cdk/aws-codestarnotifications/lib/notification-rule.ts b/packages/@aws-cdk/aws-codestarnotifications/lib/notification-rule.ts new file mode 100644 index 0000000000000..0796557e38a77 --- /dev/null +++ b/packages/@aws-cdk/aws-codestarnotifications/lib/notification-rule.ts @@ -0,0 +1,185 @@ +import { IResource, Resource, Names } from '@aws-cdk/core'; +import * as constructs from 'constructs'; +import { CfnNotificationRule } from './codestarnotifications.generated'; +import { INotificationRuleSource } from './notification-rule-source'; +import { INotificationRuleTarget, NotificationRuleTargetConfig } from './notification-rule-target'; + +/** + * The level of detail to include in the notifications for this resource. + */ +export enum DetailType { + /** + * BASIC will include only the contents of the event as it would appear in AWS CloudWatch + */ + BASIC = 'BASIC', + + /** + * FULL will include any supplemental information provided by AWS CodeStar Notifications and/or the service for the resource for which the notification is created. + */ + FULL = 'FULL', +} + +/** + * Standard set of options for `notifyOnXxx` codestar notification handler on construct + */ +export interface NotificationRuleOptions { + /** + * The name for the notification rule. + * Notification rule names must be unique in your AWS account. + * + * @default - generated from the `id` + */ + readonly notificationRuleName?: string; + + /** + * The status of the notification rule. + * If the enabled is set to DISABLED, notifications aren't sent for the notification rule. + * + * @default true + */ + readonly enabled?: boolean; + + /** + * The level of detail to include in the notifications for this resource. + * BASIC will include only the contents of the event as it would appear in AWS CloudWatch. + * FULL will include any supplemental information provided by AWS CodeStar Notifications and/or the service for the resource for which the notification is created. + * + * @default DetailType.FULL + */ + readonly detailType?: DetailType; +} + +/** + * Properties for a new notification rule + */ +export interface NotificationRuleProps extends NotificationRuleOptions { + /** + * A list of event types associated with this notification rule. + * For a complete list of event types and IDs, see Notification concepts in the Developer Tools Console User Guide. + * @see https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#concepts-api + */ + readonly events: string[]; + + /** + * The Amazon Resource Name (ARN) of the resource to associate with the notification rule. + * Currently, Supported sources include pipelines in AWS CodePipeline and build projects in AWS CodeBuild in this L2 constructor. + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codestarnotifications-notificationrule.html#cfn-codestarnotifications-notificationrule-resource + */ + readonly source: INotificationRuleSource; + + /** + * The targets to register for the notification destination. + * + * @default - No targets are added to the rule. Use `addTarget()` to add a target. + */ + readonly targets?: INotificationRuleTarget[]; +} + +/** + * Represents a notification rule + */ +export interface INotificationRule extends IResource { + + /** + * The ARN of the notification rule (i.e. arn:aws:codestar-notifications:::notificationrule/01234abcde) + * + * @attribute + */ + readonly notificationRuleArn: string; + + /** + * Adds target to notification rule + * + * @param target The SNS topic or AWS Chatbot Slack target + * @returns boolean - return true if it had any effect + */ + addTarget(target: INotificationRuleTarget): boolean; +} + +/** + * A new notification rule + * + * @resource AWS::CodeStarNotifications::NotificationRule + */ +export class NotificationRule extends Resource implements INotificationRule { + /** + * Import an existing notification rule provided an ARN + * @param scope The parent creating construct + * @param id The construct's name + * @param notificationRuleArn Notification rule ARN (i.e. arn:aws:codestar-notifications:::notificationrule/01234abcde) + */ + public static fromNotificationRuleArn(scope: constructs.Construct, id: string, notificationRuleArn: string): INotificationRule { + class Import extends Resource implements INotificationRule { + readonly notificationRuleArn = notificationRuleArn; + + public addTarget(_target: INotificationRuleTarget): boolean { + return false; + } + } + + return new Import(scope, id, { + environmentFromArn: notificationRuleArn, + }); + } + + /** + * @attribute + */ + public readonly notificationRuleArn: string; + + private readonly targets: NotificationRuleTargetConfig[] = []; + + private readonly events: string[] = []; + + constructor(scope: constructs.Construct, id: string, props: NotificationRuleProps) { + super(scope, id); + + const source = props.source.bindAsNotificationRuleSource(this); + + this.addEvents(props.events); + + const resource = new CfnNotificationRule(this, 'Resource', { + // It has a 64 characters limit for the name + name: props.notificationRuleName || Names.uniqueId(this).slice(-64), + detailType: props.detailType || DetailType.FULL, + targets: this.targets, + eventTypeIds: this.events, + resource: source.sourceArn, + status: props.enabled !== undefined + ? (props.enabled ? 'ENABLED' : 'DISABLED') + : undefined, + }); + + this.notificationRuleArn = resource.ref; + + props.targets?.forEach((target) => { + this.addTarget(target); + }); + } + + /** + * Adds target to notification rule + * @param target The SNS topic or AWS Chatbot Slack target + */ + public addTarget(target: INotificationRuleTarget): boolean { + this.targets.push(target.bindAsNotificationRuleTarget(this)); + return true; + } + + /** + * Adds events to notification rule + * + * @see https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#events-ref-pipeline + * @see https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#events-ref-buildproject + * @param events The list of event types for AWS Codebuild and AWS CodePipeline + */ + private addEvents(events: string[]): void { + events.forEach((event) => { + if (this.events.includes(event)) { + return; + } + + this.events.push(event); + }); + } +} diff --git a/packages/@aws-cdk/aws-codestarnotifications/package.json b/packages/@aws-cdk/aws-codestarnotifications/package.json index 3521ff2ee5f4a..8e96eef73d458 100644 --- a/packages/@aws-cdk/aws-codestarnotifications/package.json +++ b/packages/@aws-cdk/aws-codestarnotifications/package.json @@ -77,6 +77,7 @@ "devDependencies": { "@types/jest": "^26.0.23", "cdk-build-tools": "0.0.0", + "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", "pkglint": "0.0.0", "@aws-cdk/assert-internal": "0.0.0" @@ -92,8 +93,8 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, - "stability": "experimental", - "maturity": "cfn-only", + "stability": "stable", + "maturity": "stable", "awscdkio": { "announce": false }, diff --git a/packages/@aws-cdk/aws-codestarnotifications/test/codestarnotifications.test.ts b/packages/@aws-cdk/aws-codestarnotifications/test/codestarnotifications.test.ts deleted file mode 100644 index c4505ad966984..0000000000000 --- a/packages/@aws-cdk/aws-codestarnotifications/test/codestarnotifications.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import '@aws-cdk/assert-internal/jest'; -import {} from '../lib'; - -test('No tests are specified for this package', () => { - expect(true).toBe(true); -}); diff --git a/packages/@aws-cdk/aws-codestarnotifications/test/helpers.ts b/packages/@aws-cdk/aws-codestarnotifications/test/helpers.ts new file mode 100644 index 0000000000000..bf1d335c94d9f --- /dev/null +++ b/packages/@aws-cdk/aws-codestarnotifications/test/helpers.ts @@ -0,0 +1,45 @@ +import * as notifications from '../lib'; + +export class FakeCodeBuild implements notifications.INotificationRuleSource { + readonly projectArn = 'arn:aws:codebuild::1234567890:project/MyCodebuildProject'; + readonly projectName = 'test-project'; + + bindAsNotificationRuleSource(): notifications.NotificationRuleSourceConfig { + return { + sourceArn: this.projectArn, + }; + } +} + +export class FakeCodePipeline implements notifications.INotificationRuleSource { + readonly pipelineArn = 'arn:aws:codepipeline::1234567890:MyCodepipelineProject'; + readonly pipelineName = 'test-pipeline'; + + bindAsNotificationRuleSource(): notifications.NotificationRuleSourceConfig { + return { + sourceArn: this.pipelineArn, + }; + } +} + +export class FakeSnsTopicTarget implements notifications.INotificationRuleTarget { + readonly topicArn = 'arn:aws:sns::1234567890:MyTopic'; + + bindAsNotificationRuleTarget(): notifications.NotificationRuleTargetConfig { + return { + targetType: 'SNS', + targetAddress: this.topicArn, + }; + } +} + +export class FakeSlackTarget implements notifications.INotificationRuleTarget { + readonly slackChannelConfigurationArn = 'arn:aws:chatbot::1234567890:chat-configuration/slack-channel/MySlackChannel'; + + bindAsNotificationRuleTarget(): notifications.NotificationRuleTargetConfig { + return { + targetType: 'AWSChatbotSlack', + targetAddress: this.slackChannelConfigurationArn, + }; + } +} diff --git a/packages/@aws-cdk/aws-codestarnotifications/test/notification-rule.test.ts b/packages/@aws-cdk/aws-codestarnotifications/test/notification-rule.test.ts new file mode 100644 index 0000000000000..a155b3583a524 --- /dev/null +++ b/packages/@aws-cdk/aws-codestarnotifications/test/notification-rule.test.ts @@ -0,0 +1,218 @@ +import '@aws-cdk/assert-internal/jest'; +import * as cdk from '@aws-cdk/core'; +import * as notifications from '../lib'; +import { + FakeCodeBuild, + FakeCodePipeline, + FakeSlackTarget, + FakeSnsTopicTarget, +} from './helpers'; + +describe('NotificationRule', () => { + let stack: cdk.Stack; + + beforeEach(() => { + stack = new cdk.Stack(); + }); + + test('created new notification rule with source', () => { + const project = new FakeCodeBuild(); + + new notifications.NotificationRule(stack, 'MyNotificationRule', { + source: project, + events: ['codebuild-project-build-state-succeeded'], + }); + + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { + Resource: project.projectArn, + EventTypeIds: ['codebuild-project-build-state-succeeded'], + }); + }); + + test('created new notification rule with all parameters in constructor props', () => { + const project = new FakeCodeBuild(); + const slack = new FakeSlackTarget(); + + new notifications.NotificationRule(stack, 'MyNotificationRule', { + notificationRuleName: 'MyNotificationRule', + detailType: notifications.DetailType.FULL, + events: [ + 'codebuild-project-build-state-succeeded', + 'codebuild-project-build-state-failed', + ], + source: project, + targets: [slack], + }); + + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { + Name: 'MyNotificationRule', + DetailType: 'FULL', + EventTypeIds: [ + 'codebuild-project-build-state-succeeded', + 'codebuild-project-build-state-failed', + ], + Resource: project.projectArn, + Targets: [ + { + TargetAddress: slack.slackChannelConfigurationArn, + TargetType: 'AWSChatbotSlack', + }, + ], + }); + }); + + test('created new notification rule without name and will generate from the `id`', () => { + const project = new FakeCodeBuild(); + + new notifications.NotificationRule(stack, 'MyNotificationRuleGeneratedFromId', { + source: project, + events: ['codebuild-project-build-state-succeeded'], + }); + + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { + Name: 'MyNotificationRuleGeneratedFromId', + Resource: project.projectArn, + EventTypeIds: ['codebuild-project-build-state-succeeded'], + }); + }); + + test('generating name will cut if id length is over than 64 charts', () => { + const project = new FakeCodeBuild(); + + new notifications.NotificationRule(stack, 'MyNotificationRuleGeneratedFromIdIsToooooooooooooooooooooooooooooLong', { + source: project, + events: ['codebuild-project-build-state-succeeded'], + }); + + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { + Name: 'ificationRuleGeneratedFromIdIsToooooooooooooooooooooooooooooLong', + Resource: project.projectArn, + EventTypeIds: ['codebuild-project-build-state-succeeded'], + }); + }); + + test('created new notification rule without detailType', () => { + const project = new FakeCodeBuild(); + + new notifications.NotificationRule(stack, 'MyNotificationRule', { + notificationRuleName: 'MyNotificationRule', + source: project, + events: ['codebuild-project-build-state-succeeded'], + }); + + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { + Name: 'MyNotificationRule', + Resource: project.projectArn, + EventTypeIds: ['codebuild-project-build-state-succeeded'], + DetailType: 'FULL', + }); + }); + + test('created new notification rule with status DISABLED', () => { + const project = new FakeCodeBuild(); + + new notifications.NotificationRule(stack, 'MyNotificationRule', { + source: project, + events: ['codebuild-project-build-state-succeeded'], + enabled: false, + }); + + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { + Name: 'MyNotificationRule', + Resource: project.projectArn, + EventTypeIds: ['codebuild-project-build-state-succeeded'], + Status: 'DISABLED', + }); + }); + + test('created new notification rule with status ENABLED', () => { + const project = new FakeCodeBuild(); + + new notifications.NotificationRule(stack, 'MyNotificationRule', { + source: project, + events: ['codebuild-project-build-state-succeeded'], + enabled: true, + }); + + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { + Name: 'MyNotificationRule', + Resource: project.projectArn, + EventTypeIds: ['codebuild-project-build-state-succeeded'], + Status: 'ENABLED', + }); + }); + + test('notification added targets', () => { + const project = new FakeCodeBuild(); + const topic = new FakeSnsTopicTarget(); + const slack = new FakeSlackTarget(); + + const rule = new notifications.NotificationRule(stack, 'MyNotificationRule', { + source: project, + events: ['codebuild-project-build-state-succeeded'], + }); + + rule.addTarget(slack); + + expect(rule.addTarget(topic)).toEqual(true); + + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { + Resource: project.projectArn, + EventTypeIds: ['codebuild-project-build-state-succeeded'], + Targets: [ + { + TargetAddress: slack.slackChannelConfigurationArn, + TargetType: 'AWSChatbotSlack', + }, + { + TargetAddress: topic.topicArn, + TargetType: 'SNS', + }, + ], + }); + }); + + test('will not add if notification added duplicating event', () => { + const pipeline = new FakeCodePipeline(); + + new notifications.NotificationRule(stack, 'MyNotificationRule', { + source: pipeline, + events: [ + 'codepipeline-pipeline-pipeline-execution-succeeded', + 'codepipeline-pipeline-pipeline-execution-failed', + 'codepipeline-pipeline-pipeline-execution-succeeded', + 'codepipeline-pipeline-pipeline-execution-canceled', + ], + }); + + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { + Resource: pipeline.pipelineArn, + EventTypeIds: [ + 'codepipeline-pipeline-pipeline-execution-succeeded', + 'codepipeline-pipeline-pipeline-execution-failed', + 'codepipeline-pipeline-pipeline-execution-canceled', + ], + }); + }); +}); + +describe('NotificationRule from imported', () => { + let stack: cdk.Stack; + + beforeEach(() => { + stack = new cdk.Stack(); + }); + + test('from notification rule ARN', () => { + const imported = notifications.NotificationRule.fromNotificationRuleArn(stack, 'MyNotificationRule', + 'arn:aws:codestar-notifications::1234567890:notificationrule/1234567890abcdef'); + expect(imported.notificationRuleArn).toEqual('arn:aws:codestar-notifications::1234567890:notificationrule/1234567890abcdef'); + }); + + test('will not effect and return false when added targets if notification from imported', () => { + const imported = notifications.NotificationRule.fromNotificationRuleArn(stack, 'MyNotificationRule', + 'arn:aws:codestar-notifications::1234567890:notificationrule/1234567890abcdef'); + const slack = new FakeSlackTarget(); + expect(imported.addTarget(slack)).toEqual(false); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns/lib/topic-base.ts b/packages/@aws-cdk/aws-sns/lib/topic-base.ts index db262ef87e068..b867670474177 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic-base.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic-base.ts @@ -1,5 +1,7 @@ +import * as notifications from '@aws-cdk/aws-codestarnotifications'; import * as iam from '@aws-cdk/aws-iam'; import { IResource, Resource, Token } from '@aws-cdk/core'; +import * as constructs from 'constructs'; import { TopicPolicy } from './policy'; import { ITopicSubscription } from './subscriber'; import { Subscription } from './subscription'; @@ -11,7 +13,7 @@ import { Construct } from '@aws-cdk/core'; /** * Represents an SNS topic */ -export interface ITopic extends IResource { +export interface ITopic extends IResource, notifications.INotificationRuleTarget { /** * The ARN of the topic * @@ -124,6 +126,20 @@ export abstract class TopicBase extends Resource implements ITopic { }); } + /** + * Represents a notification target + * That allows SNS topic to associate with this rule target. + */ + public bindAsNotificationRuleTarget(_scope: constructs.Construct): notifications.NotificationRuleTargetConfig { + // SNS topic need to grant codestar-notifications service to publish + // @see https://docs.aws.amazon.com/dtconsole/latest/userguide/set-up-sns.html + this.grantPublish(new iam.ServicePrincipal('codestar-notifications.amazonaws.com')); + return { + targetType: 'SNS', + targetAddress: this.topicArn, + }; + } + private nextTokenId(scope: Construct) { let nextSuffix = 1; const re = /TokenSubscription:([\d]*)/gm; diff --git a/packages/@aws-cdk/aws-sns/package.json b/packages/@aws-cdk/aws-sns/package.json index afe1241b23e8b..09727cd171086 100644 --- a/packages/@aws-cdk/aws-sns/package.json +++ b/packages/@aws-cdk/aws-sns/package.json @@ -85,6 +85,7 @@ "@aws-cdk/assert-internal": "0.0.0" }, "dependencies": { + "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", @@ -95,6 +96,7 @@ }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { + "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-sns/test/test.sns.ts b/packages/@aws-cdk/aws-sns/test/test.sns.ts index 695166ace86a1..09cf051922e5e 100644 --- a/packages/@aws-cdk/aws-sns/test/test.sns.ts +++ b/packages/@aws-cdk/aws-sns/test/test.sns.ts @@ -1,4 +1,5 @@ import { expect, haveResource } from '@aws-cdk/assert-internal'; +import * as notifications from '@aws-cdk/aws-codestarnotifications'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as cdk from '@aws-cdk/core'; @@ -532,4 +533,38 @@ export = { test.throws(() => app.synth(), /A PolicyStatement used in a resource-based policy must specify at least one IAM principal/); test.done(); }, + + 'topic policy should be set if topic as a notifications rule target'(test: Test) { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'my-stack'); + const topic = new sns.Topic(stack, 'Topic'); + const rule = new notifications.NotificationRule(stack, 'MyNotificationRule', { + source: { + bindAsNotificationRuleSource: () => ({ + sourceArn: 'ARN', + }), + }, + events: ['codebuild-project-build-state-succeeded'], + }); + + rule.addTarget(topic); + + expect(stack).to(haveResource('AWS::SNS::TopicPolicy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + 'Sid': '0', + 'Action': 'sns:Publish', + 'Effect': 'Allow', + 'Principal': { 'Service': 'codestar-notifications.amazonaws.com' }, + 'Resource': { 'Ref': 'TopicBFC7AF6E' }, + }], + }, + Topics: [{ + Ref: 'TopicBFC7AF6E', + }], + })); + + test.done(); + }, }; From 203cc45b3bca3e7aa682cdf191fef71b3c9a8de1 Mon Sep 17 00:00:00 2001 From: Dillon Date: Tue, 15 Jun 2021 18:39:25 -0400 Subject: [PATCH 24/34] feat(servicecatalog): initial implementation of the Portfolio construct (#15099) This is the first minimal release of an L2 construct for a service catalog portfolio. The portfolio construct includes methods to give access to IAM Principals as well as share across accounts. In a future PR, functionality to associate with a Product and add constraints will be added. Testing done ------------------ * `yarn build && yarn lint` * `yarn rosetta:extract --strict` * `yarn test` * `yarn integ` Co-authored-by: Aidan Crank Co-authored-by: Jackie Wang ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-servicecatalog/README.md | 85 ++++++- .../@aws-cdk/aws-servicecatalog/lib/common.ts | 19 ++ .../@aws-cdk/aws-servicecatalog/lib/index.ts | 3 + .../aws-servicecatalog/lib/portfolio.ts | 212 +++++++++++++++++ .../aws-servicecatalog/lib/private/util.ts | 10 + .../lib/private/validation.ts | 20 ++ .../@aws-cdk/aws-servicecatalog/package.json | 11 +- .../rosetta/basic-portfolio.ts-fixture | 16 ++ .../rosetta/default.ts-fixture | 11 + .../test/integ.portfolio.expected.json | 87 +++++++ .../test/integ.portfolio.ts | 25 ++ .../aws-servicecatalog/test/portfolio.test.ts | 216 ++++++++++++++++++ .../test/servicecatalog.test.ts | 6 - 13 files changed, 713 insertions(+), 8 deletions(-) create mode 100644 packages/@aws-cdk/aws-servicecatalog/lib/common.ts create mode 100644 packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts create mode 100644 packages/@aws-cdk/aws-servicecatalog/lib/private/util.ts create mode 100644 packages/@aws-cdk/aws-servicecatalog/lib/private/validation.ts create mode 100644 packages/@aws-cdk/aws-servicecatalog/rosetta/basic-portfolio.ts-fixture create mode 100644 packages/@aws-cdk/aws-servicecatalog/rosetta/default.ts-fixture create mode 100644 packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json create mode 100644 packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts create mode 100644 packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts delete mode 100644 packages/@aws-cdk/aws-servicecatalog/test/servicecatalog.test.ts diff --git a/packages/@aws-cdk/aws-servicecatalog/README.md b/packages/@aws-cdk/aws-servicecatalog/README.md index b3f20916fcac4..fdf8a2f599cf6 100644 --- a/packages/@aws-cdk/aws-servicecatalog/README.md +++ b/packages/@aws-cdk/aws-servicecatalog/README.md @@ -9,8 +9,91 @@ > > [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. +> They are subject to non-backward compatible changes or removal in any future version. These are +> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be +> announced in the release notes. This means that while you may use them, you may need to update +> your source code when upgrading to a newer version of this package. + --- -This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. +[AWS Service Catalog](https://docs.aws.amazon.com/servicecatalog/latest/dg/what-is-service-catalog.html) +enables organizations to create and manage catalogs of products for their end users that are approved for use on AWS. + +## Table Of Contents + +- [Portfolio](#portfolio) + - [Granting access to a portfolio](#granting-access-to-a-portfolio) + - [Sharing a portfolio with another AWS account](#sharing-a-portfolio-with-another-aws-account) + +The `@aws-cdk/aws-servicecatalog` package contains resources that enable users to automate governance and management of their AWS resources at scale. + +```ts nofixture +import * as servicecatalog from '@aws-cdk/aws-servicecatalog'; +``` + +## Portfolio + +AWS Service Catalog portfolios allow admins to manage products that their end users have access to. +Using the CDK, a new portfolio can be created with the `Portfolio` construct: + +```ts +new servicecatalog.Portfolio(this, 'MyFirstPortfolio', { + displayName: 'MyFirstPortfolio', + providerName: 'MyTeam', +}); +``` + +You can also specify properties such as `description` and `acceptLanguage` +to help better catalog and manage your portfolios. + +```ts +new servicecatalog.Portfolio(this, 'MyFirstPortfolio', { + displayName: 'MyFirstPortfolio', + providerName: 'MyTeam', + description: 'Portfolio for a project', + acceptLanguage: servicecatalog.AcceptLanguage.EN, +}); +``` + +Read more at [Creating and Managing Portfolios](https://docs.aws.amazon.com/servicecatalog/latest/adminguide/catalogs_portfolios.html). + +A portfolio that has been created outside the stack can be imported into your CDK app. +Portfolios can be imported by their ARN via the `Portfolio.fromPortfolioArn()` API: + +```ts +const portfolio = servicecatalog.Portfolio.fromPortfolioArn(this, 'MyImportedPortfolio', + 'arn:aws:catalog:region:account-id:portfolio/port-abcdefghi'); +``` + +### Granting access to a portfolio + +You can manage end user access to a portfolio by granting permissions to `IAM` entities like a user, group, or role. +Once resources are deployed end users will be able to access them via the console or service catalog CLI. + +```ts fixture=basic-portfolio +import * as iam from '@aws-cdk/aws-iam'; + +const user = new iam.User(this, 'MyUser'); +portfolio.giveAccessToUser(user); + +const role = new iam.Role(this, 'MyRole', { + assumedBy: new iam.AccountRootPrincipal(), +}); +portfolio.giveAccessToRole(role); + +const group = new iam.Group(this, 'MyGroup'); +portfolio.giveAccessToGroup(group); +``` + +### Sharing a portfolio with another AWS account + +A portfolio can be programatically shared with other accounts so that specified users can also access it: + +```ts fixture=basic-portfolio +portfolio.shareWithAccount('012345678901'); +``` diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/common.ts b/packages/@aws-cdk/aws-servicecatalog/lib/common.ts new file mode 100644 index 0000000000000..f1382342626af --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/lib/common.ts @@ -0,0 +1,19 @@ +/** + * The language code. + */ +export enum AcceptLanguage { + /** + * English + */ + EN = 'en', + + /** + * Japanese + */ + JP = 'jp', + + /** + * Chinese + */ + ZH = 'zh' +} diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/index.ts b/packages/@aws-cdk/aws-servicecatalog/lib/index.ts index 821df334e56d7..04cf902c29069 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/index.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/index.ts @@ -1,2 +1,5 @@ +export * from './common'; +export * from './portfolio'; + // AWS::ServiceCatalog CloudFormation Resources: export * from './servicecatalog.generated'; diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts b/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts new file mode 100644 index 0000000000000..99c78de06e692 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts @@ -0,0 +1,212 @@ +import * as iam from '@aws-cdk/aws-iam'; +import { IResource, Names, Resource, Stack } from '@aws-cdk/core'; +import { AcceptLanguage } from './common'; +import { hashValues } from './private/util'; +import { InputValidator } from './private/validation'; +import { CfnPortfolio, CfnPortfolioPrincipalAssociation, CfnPortfolioShare } from './servicecatalog.generated'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from 'constructs'; + +/** + * Options for portfolio share. + */ +export interface PortfolioShareOptions { + /** + * Whether to share tagOptions as a part of the portfolio share + * @default - share not specified + */ + readonly shareTagOptions?: boolean; + + /** + * The accept language of the share + * @default - accept language not specified + */ + readonly acceptLanguage?: AcceptLanguage; +} + +/** + * A Service Catalog portfolio. + */ +export interface IPortfolio extends IResource { + /** + * The ARN of the portfolio. + * @attribute + */ + readonly portfolioArn: string; + + /** + * The ID of the portfolio. + * @attribute + */ + readonly portfolioId: string; + + /** + * Associate portfolio with an IAM Role. + * @param role an IAM role + */ + giveAccessToRole(role: iam.IRole): void; + + /** + * Associate portfolio with an IAM User. + * @param user an IAM user + */ + giveAccessToUser(user: iam.IUser): void; + + /** + * Associate portfolio with an IAM Group. + * @param group an IAM Group + */ + giveAccessToGroup(group: iam.IGroup): void; + + /** + * Initiate a portfolio share with another account. + * @param accountId AWS account to share portfolio with + * @param options Options for the initiate share + */ + shareWithAccount(accountId: string, options?: PortfolioShareOptions): void; +} + +abstract class PortfolioBase extends Resource implements IPortfolio { + public abstract readonly portfolioArn: string; + public abstract readonly portfolioId: string; + private readonly associatedPrincipals: Set = new Set(); + + public giveAccessToRole(role: iam.IRole): void { + this.associatePrincipal(role.roleArn, role.node.addr); + } + + public giveAccessToUser(user: iam.IUser): void { + this.associatePrincipal(user.userArn, user.node.addr); + } + + public giveAccessToGroup(group: iam.IGroup): void { + this.associatePrincipal(group.groupArn, group.node.addr); + } + + public shareWithAccount(accountId: string, options: PortfolioShareOptions = {}): void { + const hashId = this.generateUniqueHash(accountId); + new CfnPortfolioShare(this, `PortfolioShare${hashId}`, { + portfolioId: this.portfolioId, + accountId: accountId, + shareTagOptions: options.shareTagOptions, + acceptLanguage: options.acceptLanguage, + }); + } + + /** + * Associate a principal with the portfolio. + * If the principal is already associated, it will skip. + */ + private associatePrincipal(principalArn: string, principalId: string): void { + if (!this.associatedPrincipals.has(principalArn)) { + const hashId = this.generateUniqueHash(principalId); + new CfnPortfolioPrincipalAssociation(this, `PortolioPrincipalAssociation${hashId}`, { + portfolioId: this.portfolioId, + principalArn: principalArn, + principalType: 'IAM', + }); + this.associatedPrincipals.add(principalArn); + } + } + + /** + * Create a unique id based off the L1 CfnPortfolio or the arn of an imported portfolio. + */ + protected abstract generateUniqueHash(value: string): string; +} + +/** + * Properties for a Portfolio. + */ +export interface PortfolioProps { + /** + * The name of the portfolio. + */ + readonly displayName: string; + + /** + * The provider name. + */ + readonly providerName: string; + + /** + * The accept language. + * @default - No accept language provided + */ + readonly acceptLanguage?: AcceptLanguage; + + /** + * Description for portfolio. + * @default - No description provided + */ + readonly description?: string; +} + +/** + * A Service Catalog portfolio. + */ +export class Portfolio extends PortfolioBase { + /** + * Creates a Portfolio construct that represents an external portfolio. + * + * @param scope The parent creating construct (usually `this`). + * @param id The construct's name. + * @param portfolioArn the Amazon Resource Name of the existing portfolio. + */ + public static fromPortfolioArn(scope: Construct, id: string, portfolioArn: string): IPortfolio { + const arn = Stack.of(scope).parseArn(portfolioArn); + const portfolioId = arn.resourceName; + + if (!portfolioId) { + throw new Error('Missing required Portfolio ID from Portfolio ARN: ' + portfolioArn); + } + + class Import extends PortfolioBase { + public readonly portfolioArn = portfolioArn; + public readonly portfolioId = portfolioId!; + + protected generateUniqueHash(value: string): string { + return hashValues(this.portfolioArn, value); + } + } + + return new Import(scope, id, { + environmentFromArn: portfolioArn, + }); + } + + public readonly portfolioArn: string; + public readonly portfolioId: string; + private readonly portfolio: CfnPortfolio; + + constructor(scope: Construct, id: string, props: PortfolioProps) { + super(scope, id); + + this.validatePortfolioProps(props); + + this.portfolio = new CfnPortfolio(this, 'Resource', { + displayName: props.displayName, + providerName: props.providerName, + description: props.description, + acceptLanguage: props.acceptLanguage, + }); + this.portfolioId = this.portfolio.ref; + this.portfolioArn = Stack.of(this).formatArn({ + service: 'servicecatalog', + resource: 'portfolio', + resourceName: this.portfolioId, + }); + } + + protected generateUniqueHash(value: string): string { + return hashValues(Names.nodeUniqueId(this.portfolio.node), value); + } + + private validatePortfolioProps(props: PortfolioProps) { + InputValidator.validateLength(this.node.path, 'portfolio display name', 1, 100, props.displayName); + InputValidator.validateLength(this.node.path, 'portfolio provider name', 1, 50, props.providerName); + InputValidator.validateLength(this.node.path, 'portfolio description', 0, 2000, props.description); + } +} diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/private/util.ts b/packages/@aws-cdk/aws-servicecatalog/lib/private/util.ts new file mode 100644 index 0000000000000..7bec65be383a0 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/lib/private/util.ts @@ -0,0 +1,10 @@ +import * as crypto from 'crypto'; + +/** + * Generates a unique hash identfifer using SHA256 encryption algorithm + */ +export function hashValues(...ids: string[]): string { + const sha256 = crypto.createHash('sha256'); + ids.forEach(val => sha256.update(val)); + return sha256.digest('hex').slice(0, 12); +} diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/private/validation.ts b/packages/@aws-cdk/aws-servicecatalog/lib/private/validation.ts new file mode 100644 index 0000000000000..95a802561a2be --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/lib/private/validation.ts @@ -0,0 +1,20 @@ +/** + * Class to validate that inputs match requirements. + */ +export class InputValidator { + /** + * Validates length is between allowed min and max lengths. + */ + public static validateLength(resourceName: string, inputName: string, minLength: number, maxLength: number, inputString?: string): void { + if (inputString !== undefined && (inputString.length < minLength || inputString.length > maxLength)) { + throw new Error(`Invalid ${inputName} for resource ${resourceName}, must have length between ${minLength} and ${maxLength}, got: '${this.truncateString(inputString, 100)}'`); + } + } + + private static truncateString(string: string, maxLength: number): string { + if (string.length > maxLength) { + return string.substring(0, maxLength) + '[truncated]'; + } + return string; + } +} diff --git a/packages/@aws-cdk/aws-servicecatalog/package.json b/packages/@aws-cdk/aws-servicecatalog/package.json index cd93c1b7aab13..c7cbe70da6986 100644 --- a/packages/@aws-cdk/aws-servicecatalog/package.json +++ b/packages/@aws-cdk/aws-servicecatalog/package.json @@ -75,24 +75,33 @@ "devDependencies": { "@types/jest": "^26.0.23", "cdk-build-tools": "0.0.0", + "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", "pkglint": "0.0.0", "@aws-cdk/assert-internal": "0.0.0" }, "dependencies": { + "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.3.69" }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { + "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.3.69" }, "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, + "awslint": { + "exclude": [ + "resource-attribute:@aws-cdk/aws-servicecatalog.Portfolio.portfolioName", + "props-physical-name:@aws-cdk/aws-servicecatalog.PortfolioProps" + ] + }, + "maturity": "experimental", "stability": "experimental", - "maturity": "cfn-only", "awscdkio": { "announce": false }, diff --git a/packages/@aws-cdk/aws-servicecatalog/rosetta/basic-portfolio.ts-fixture b/packages/@aws-cdk/aws-servicecatalog/rosetta/basic-portfolio.ts-fixture new file mode 100644 index 0000000000000..d8925e6645aa7 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/rosetta/basic-portfolio.ts-fixture @@ -0,0 +1,16 @@ +// Fixture with packages imported, but nothing else +import { Construct, Stack } from '@aws-cdk/core'; +import * as servicecatalog from '@aws-cdk/aws-servicecatalog'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + const portfolio = new servicecatalog.Portfolio(this, "MyFirstPortfolio", { + displayName: "MyFirstPortfolio", + providerName: "MyTeam", + }); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-servicecatalog/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-servicecatalog/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..cda4dcaf5c01b --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct, Stack } from '@aws-cdk/core'; +import * as servicecatalog from '@aws-cdk/aws-servicecatalog'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json new file mode 100644 index 0000000000000..0409382efba03 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json @@ -0,0 +1,87 @@ +{ + "Resources": { + "TestRole6C9272DF": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TestGroupAF88660E": { + "Type": "AWS::IAM::Group" + }, + "TestPortfolio4AC794EB": { + "Type": "AWS::ServiceCatalog::Portfolio", + "Properties": { + "DisplayName": "TestPortfolio", + "ProviderName": "TestProvider", + "Description": "This is our Service Catalog Portfolio" + } + }, + "TestPortfolioPortolioPrincipalAssociation20e1afa20ac27E1A060D": { + "Type": "AWS::ServiceCatalog::PortfolioPrincipalAssociation", + "Properties": { + "PortfolioId": { + "Ref": "TestPortfolio4AC794EB" + }, + "PrincipalARN": { + "Fn::GetAtt": [ + "TestRole6C9272DF", + "Arn" + ] + }, + "PrincipalType": "IAM" + } + }, + "TestPortfolioPortolioPrincipalAssociation44a1ca1c23384D6E460B": { + "Type": "AWS::ServiceCatalog::PortfolioPrincipalAssociation", + "Properties": { + "PortfolioId": { + "Ref": "TestPortfolio4AC794EB" + }, + "PrincipalARN": { + "Fn::GetAtt": [ + "TestGroupAF88660E", + "Arn" + ] + }, + "PrincipalType": "IAM" + } + }, + "TestPortfolioPortfolioSharebf5b82f042508F035880": { + "Type": "AWS::ServiceCatalog::PortfolioShare", + "Properties": { + "AccountId": "123456789012", + "PortfolioId": { + "Ref": "TestPortfolio4AC794EB" + } + } + } + } + } + \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts new file mode 100644 index 0000000000000..b62523c59083f --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts @@ -0,0 +1,25 @@ +import * as iam from '@aws-cdk/aws-iam'; +import { App, Stack } from '@aws-cdk/core'; +import * as servicecatalog from '../lib'; + +const app = new App(); +const stack = new Stack(app, 'integ-servicecatalog-portfolio'); + +const role = new iam.Role(stack, 'TestRole', { + assumedBy: new iam.AccountRootPrincipal(), +}); + +const group = new iam.Group(stack, 'TestGroup'); + +const portfolio = new servicecatalog.Portfolio(stack, 'TestPortfolio', { + displayName: 'TestPortfolio', + providerName: 'TestProvider', + description: 'This is our Service Catalog Portfolio', +}); + +portfolio.giveAccessToRole(role); +portfolio.giveAccessToGroup(group); + +portfolio.shareWithAccount('123456789012'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts new file mode 100644 index 0000000000000..9115b232a7e6c --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts @@ -0,0 +1,216 @@ +import '@aws-cdk/assert-internal/jest'; +import { Stack, Tags } from '@aws-cdk/core'; +import * as iam from '@aws-cdk/aws-iam'; +import * as servicecatalog from '../lib'; + +describe('Portfolio', () => { + let stack: Stack; + + beforeEach(() => { + stack = new Stack(); + }); + + describe('portfolio creation and importing', () => { + test('default portfolio creation', () => { + new servicecatalog.Portfolio(stack, 'MyPortfolio', { + displayName: 'testPortfolio', + providerName: 'testProvider', + }); + + expect(stack).toMatchTemplate({ + Resources: { + MyPortfolio59CCA9C9: { + Type: 'AWS::ServiceCatalog::Portfolio', + Properties: { + DisplayName: 'testPortfolio', + ProviderName: 'testProvider', + }, + }, + }, + }); + }), + + test('portfolio with explicit acceptLanguage and description', () => { + new servicecatalog.Portfolio(stack, 'MyPortfolio', { + displayName: 'testPortfolio', + providerName: 'testProvider', + description: 'test portfolio description', + acceptLanguage: servicecatalog.AcceptLanguage.ZH, + }); + + expect(stack).toHaveResourceLike('AWS::ServiceCatalog::Portfolio', { + Description: 'test portfolio description', + AcceptLanguage: servicecatalog.AcceptLanguage.ZH, + }); + }), + + test('portfolio from arn', () => { + const portfolio = servicecatalog.Portfolio.fromPortfolioArn(stack, 'MyPortfolio', 'arn:aws:catalog:region:account-id:portfolio/port-djh8932wr'); + + expect(portfolio.portfolioId).toEqual('port-djh8932wr'); + }), + + test('fails portfolio from arn without resource name in arn', () => { + expect(() => { + servicecatalog.Portfolio.fromPortfolioArn(stack, 'MyPortfolio', 'arn:aws:catalog:region:account-id:portfolio'); + }).toThrowError(/Missing required Portfolio ID from Portfolio ARN/); + }), + + test('fails portfolio creation with short name', () => { + expect(() => { + new servicecatalog.Portfolio(stack, 'MyPortfolio', { + displayName: '', + providerName: 'testProvider', + }); + }).toThrowError(/Invalid portfolio display name for resource Default\/MyPortfolio/); + }), + + test('fails portfolio creation with long name', () => { + expect(() => { + new servicecatalog.Portfolio(stack, 'MyPortfolio', { + displayName: 'DisplayName', + providerName: 'testProvider', + description: 'A portfolio for some products'.repeat(1000), + }); + }).toThrowError(/Invalid portfolio description for resource Default\/MyPortfolio/); + }), + + test('fails portfolio creation with invalid provider name', () => { + expect(() => { + new servicecatalog.Portfolio(stack, 'MyPortfolio', { + displayName: 'testPortfolio', + providerName: '', + }); + }).toThrowError(/Invalid portfolio provider name for resource Default\/MyPortfolio/); + }), + + test('fails portfolio creation with invalid description length', () => { + const description = 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit'.repeat(50); + + expect(() => { + new servicecatalog.Portfolio(stack, 'MyPortfolio', { + displayName: 'testPortfolio', + providerName: 'testProvider', + description: description, + }); + }).toThrowError(/Invalid portfolio description for resource Default\/MyPortfolio/); + }); + }), + + describe('portfolio methods and associations', () => { + let portfolio: servicecatalog.Portfolio; + + beforeEach(() => { + portfolio = new servicecatalog.Portfolio(stack, 'MyPortfolio', { + displayName: 'testPortfolio', + providerName: 'testProvider', + }); + }); + + test('portfolio with tags', () => { + Tags.of(portfolio).add('myTestKey1', 'myTestKeyValue1'); + Tags.of(portfolio).add('myTestKey2', 'myTestKeyValue2'); + + expect(stack).toHaveResourceLike('AWS::ServiceCatalog::Portfolio', { + Tags: [ + { + Key: 'myTestKey1', + Value: 'myTestKeyValue1', + }, + { + Key: 'myTestKey2', + Value: 'myTestKeyValue2', + }, + ], + }); + }), + + test('portfolio share', () => { + const shareAccountId = '012345678901'; + + portfolio.shareWithAccount(shareAccountId); + + expect(stack).toHaveResourceLike('AWS::ServiceCatalog::PortfolioShare', { + AccountId: shareAccountId, + }); + }), + + test('portfolio share with share tagOptions', () => { + const shareAccountId = '012345678901'; + + portfolio.shareWithAccount(shareAccountId, { + shareTagOptions: true, + acceptLanguage: servicecatalog.AcceptLanguage.EN, + }); + + expect(stack).toHaveResourceLike('AWS::ServiceCatalog::PortfolioShare', { + AccountId: shareAccountId, + ShareTagOptions: true, + AcceptLanguage: 'en', + }); + }), + + test('portfolio share without share tagOptions', () => { + const shareAccountId = '012345678901'; + + portfolio.shareWithAccount(shareAccountId, { shareTagOptions: false }); + + expect(stack).toHaveResourceLike('AWS::ServiceCatalog::PortfolioShare', { + AccountId: shareAccountId, + ShareTagOptions: false, + }); + }), + + test('portfolio share without explicit share tagOptions', () => { + const shareAccountId = '012345678901'; + + portfolio.shareWithAccount(shareAccountId); + + expect(stack).toHaveResourceLike('AWS::ServiceCatalog::PortfolioShare', { + AccountId: shareAccountId, + }); + }), + + test('portfolio principal association with role type', () => { + const role = new iam.Role(stack, 'TestRole', { + assumedBy: new iam.AccountRootPrincipal(), + }); + + portfolio.giveAccessToRole(role); + + expect(stack).toHaveResourceLike('AWS::ServiceCatalog::PortfolioPrincipalAssociation', { + PrincipalARN: { 'Fn::GetAtt': ['TestRole6C9272DF', 'Arn'] }, + }); + }), + + test('portfolio principal association with user type', () => { + const user = new iam.User(stack, 'TestUser'); + + portfolio.giveAccessToUser(user); + + expect(stack).toHaveResourceLike('AWS::ServiceCatalog::PortfolioPrincipalAssociation', { + PrincipalARN: { 'Fn::GetAtt': ['TestUser6A619381', 'Arn'] }, + }); + }), + + test('portfolio principal association with group type', () => { + const group = new iam.Group(stack, 'TestGroup'); + + portfolio.giveAccessToGroup(group); + + expect(stack).toHaveResourceLike('AWS::ServiceCatalog::PortfolioPrincipalAssociation', { + PrincipalARN: { 'Fn::GetAtt': ['TestGroupAF88660E', 'Arn'] }, + }); + }), + + test('portfolio duplicate principle associations are idempotent', () => { + const role = new iam.Role(stack, 'TestRole', { + assumedBy: new iam.AccountRootPrincipal(), + }); + + // If this were not idempotent, the second call would produce an error for duplicate construct ID. + portfolio.giveAccessToRole(role); + portfolio.giveAccessToRole(role); + }); + }); +}); diff --git a/packages/@aws-cdk/aws-servicecatalog/test/servicecatalog.test.ts b/packages/@aws-cdk/aws-servicecatalog/test/servicecatalog.test.ts deleted file mode 100644 index c4505ad966984..0000000000000 --- a/packages/@aws-cdk/aws-servicecatalog/test/servicecatalog.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import '@aws-cdk/assert-internal/jest'; -import {} from '../lib'; - -test('No tests are specified for this package', () => { - expect(true).toBe(true); -}); From 03fca09b2367ecc0f9f016f0f1eb7dcb905820dc Mon Sep 17 00:00:00 2001 From: Mitchell Valine Date: Tue, 15 Jun 2021 18:25:52 -0700 Subject: [PATCH 25/34] revert: chore(bootstrap): enable s3 versioning (#14987) (#15137) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Object versioning on the bootstrap bucket breaks the bootstrapping integ tests. Investigating whether tests need to be changed to accommodate but for now reverting to unblock release. ``` ❌ BucketNotEmpty: The bucket you tried to delete is not empty. You must delete all versions in the bucket. FAIL ./bootstrapping.integtest.js (377.151 s) ``` Refs: 92cadef --- packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml index 813fcc5d4f063..fecf66b15aae7 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml @@ -178,8 +178,6 @@ Resources: IgnorePublicAcls: true RestrictPublicBuckets: true - Ref: AWS::NoValue - VersioningConfiguration: - Status: Enabled UpdateReplacePolicy: Retain DeletionPolicy: Retain StagingBucketPolicy: From cc7191e223ee3a19db3d46fd815236ca68bd36e4 Mon Sep 17 00:00:00 2001 From: Otavio Macedo Date: Wed, 16 Jun 2021 13:48:44 +0100 Subject: [PATCH 26/34] fix(cli): partition is not being resolved at missing value lookup (#15146) The partition component of the lookup role ARN, when present, was not being resolved. Normally, this resolution is done in the path that handles stacks and assets. The lookup, however, happens before that, so we have to replace the environment placeholders there as well. Fixes #15119 --- .../lib/api/cloudformation-deployments.ts | 41 ++++++++++--------- .../aws-cdk/lib/api/cxapp/cloud-executable.ts | 5 ++- .../aws-cdk/lib/context-providers/index.ts | 12 +++++- .../test/context-providers/generic.test.ts | 34 +++++++++++++++ packages/aws-cdk/test/util/mock-sdk.ts | 5 +++ 5 files changed, 74 insertions(+), 23 deletions(-) diff --git a/packages/aws-cdk/lib/api/cloudformation-deployments.ts b/packages/aws-cdk/lib/api/cloudformation-deployments.ts index 3dde581f0b514..84fac5cb15922 100644 --- a/packages/aws-cdk/lib/api/cloudformation-deployments.ts +++ b/packages/aws-cdk/lib/api/cloudformation-deployments.ts @@ -9,6 +9,25 @@ import { ToolkitInfo } from './toolkit-info'; import { CloudFormationStack, Template } from './util/cloudformation'; import { StackActivityProgress } from './util/cloudformation/stack-activity-monitor'; +/** + * Replace the {ACCOUNT} and {REGION} placeholders in all strings found in a complex object. + */ +export async function replaceEnvPlaceholders(object: A, env: cxapi.Environment, sdkProvider: SdkProvider): Promise { + return cxapi.EnvironmentPlaceholders.replaceAsync(object, { + accountId: () => Promise.resolve(env.account), + region: () => Promise.resolve(env.region), + partition: async () => { + // There's no good way to get the partition! + // We should have had it already, except we don't. + // + // Best we can do is ask the "base credentials" for this environment for their partition. Cross-partition + // AssumeRole'ing will never work anyway, so this answer won't be wrong (it will just be slow!) + return (await sdkProvider.baseCredentialsPartition(env, Mode.ForReading)) ?? 'aws'; + }, + }); +} + + export interface DeployStackOptions { /** * Stack to deploy @@ -223,12 +242,12 @@ export class CloudFormationDeployments { const resolvedEnvironment = await this.sdkProvider.resolveEnvironment(stack.environment); // Substitute any placeholders with information about the current environment - const arns = await this.replaceEnvPlaceholders({ + const arns = await replaceEnvPlaceholders({ assumeRoleArn: stack.assumeRoleArn, // Use the override if given, otherwise use the field from the stack cloudFormationRoleArn: roleArn ?? stack.cloudFormationExecutionRoleArn, - }, resolvedEnvironment); + }, resolvedEnvironment, this.sdkProvider); const stackSdk = await this.sdkProvider.forEnvironment(resolvedEnvironment, mode, { assumeRoleArn: arns.assumeRoleArn, @@ -241,24 +260,6 @@ export class CloudFormationDeployments { }; } - /** - * Replace the {ACCOUNT} and {REGION} placeholders in all strings found in a complex object. - */ - private async replaceEnvPlaceholders(object: A, env: cxapi.Environment): Promise { - return cxapi.EnvironmentPlaceholders.replaceAsync(object, { - accountId: () => Promise.resolve(env.account), - region: () => Promise.resolve(env.region), - partition: async () => { - // There's no good way to get the partition! - // We should have had it already, except we don't. - // - // Best we can do is ask the "base credentials" for this environment for their partition. Cross-partition - // AssumeRole'ing will never work anyway, so this answer won't be wrong (it will just be slow!) - return (await this.sdkProvider.baseCredentialsPartition(env, Mode.ForReading)) ?? 'aws'; - }, - }); - } - /** * Publish all asset manifests that are referenced by the given stack */ diff --git a/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts b/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts index 3a454177469f0..eeebec8e90f44 100644 --- a/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts +++ b/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts @@ -87,7 +87,10 @@ export class CloudExecutable { if (tryLookup) { debug('Some context information is missing. Fetching...'); - await contextproviders.provideContextValues(assembly.manifest.missing, this.props.configuration.context, this.props.sdkProvider); + await contextproviders.provideContextValues( + assembly.manifest.missing, + this.props.configuration.context, + this.props.sdkProvider); // Cache the new context to disk await this.props.configuration.saveContext(); diff --git a/packages/aws-cdk/lib/context-providers/index.ts b/packages/aws-cdk/lib/context-providers/index.ts index a87183d3b4e46..4e93905e4935c 100644 --- a/packages/aws-cdk/lib/context-providers/index.ts +++ b/packages/aws-cdk/lib/context-providers/index.ts @@ -1,13 +1,14 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import { SdkProvider } from '../api'; +import { replaceEnvPlaceholders } from '../api/cloudformation-deployments'; import { debug } from '../logging'; import { Context, TRANSIENT_CONTEXT_KEY } from '../settings'; import { AmiContextProviderPlugin } from './ami'; import { AZContextProviderPlugin } from './availability-zones'; import { EndpointServiceAZContextProviderPlugin } from './endpoint-service-availability-zones'; import { HostedZoneContextProviderPlugin } from './hosted-zones'; -import { LoadBalancerListenerContextProviderPlugin, LoadBalancerContextProviderPlugin } from './load-balancers'; +import { LoadBalancerContextProviderPlugin, LoadBalancerListenerContextProviderPlugin } from './load-balancers'; import { ContextProviderPlugin } from './provider'; import { SecurityGroupContextProviderPlugin } from './security-groups'; import { SSMContextProviderPlugin } from './ssm-parameters'; @@ -36,7 +37,14 @@ export async function provideContextValues( let value; try { - value = await provider.getValue(missingContext.props); + const environment = cxapi.EnvironmentUtils.make(missingContext.props.account, missingContext.props.region); + const resolvedEnvironment = await sdk.resolveEnvironment(environment); + + const arns = await replaceEnvPlaceholders({ + lookupRoleArn: missingContext.props.lookupRoleArn, + }, resolvedEnvironment, sdk); + + value = await provider.getValue({ ...missingContext.props, lookupRoleArn: arns.lookupRoleArn }); } catch (e) { // Set a specially formatted provider value which will be interpreted // as a lookup failure in the toolkit. diff --git a/packages/aws-cdk/test/context-providers/generic.test.ts b/packages/aws-cdk/test/context-providers/generic.test.ts index 35f8c85da83e6..66d837b2dab98 100644 --- a/packages/aws-cdk/test/context-providers/generic.test.ts +++ b/packages/aws-cdk/test/context-providers/generic.test.ts @@ -27,6 +27,40 @@ test('errors are reported into the context value', async () => { expect(context.get('asdf').$providerError).toBe('Something went wrong'); }); +test('lookup role ARN is resolved', async () => { + // GIVEN + contextproviders.registerContextProvider(TEST_PROVIDER, class { + public async getValue(args: {[key: string]: any}): Promise { + if (args.lookupRoleArn == null) { + throw new Error('No lookupRoleArn'); + } + + if (args.lookupRoleArn.includes('${AWS::Partition}')) { + throw new Error('Partition not resolved'); + } + + return 'some resolved value'; + } + }); + const context = new Context(); + + // WHEN + await contextproviders.provideContextValues([ + { + key: 'asdf', + props: { + account: '1234', + region: 'us-east-1', + lookupRoleArn: 'arn:${AWS::Partition}:iam::280619947791:role/cdk-hnb659fds-lookup-role-280619947791-us-east-1', + }, + provider: TEST_PROVIDER, + }, + ], context, mockSDK); + + // THEN - Value gets resolved + expect(context.get('asdf')).toEqual('some resolved value'); +}); + test('errors are marked transient', async () => { // GIVEN contextproviders.registerContextProvider(TEST_PROVIDER, class { diff --git a/packages/aws-cdk/test/util/mock-sdk.ts b/packages/aws-cdk/test/util/mock-sdk.ts index 30dbbd682a5a4..edb7be0445626 100644 --- a/packages/aws-cdk/test/util/mock-sdk.ts +++ b/packages/aws-cdk/test/util/mock-sdk.ts @@ -1,6 +1,7 @@ import * as cxapi from '@aws-cdk/cx-api'; import * as AWS from 'aws-sdk'; import { Account, ISDK, SDK, SdkProvider } from '../../lib/api/aws-auth'; +import { Mode } from '../../lib/api/aws-auth/credentials'; import { ToolkitInfo } from '../../lib/api/toolkit-info'; import { CloudFormationStack } from '../../lib/api/util/cloudformation'; @@ -43,6 +44,10 @@ export class MockSdkProvider extends SdkProvider { } } + async baseCredentialsPartition(_environment: cxapi.Environment, _mode: Mode): Promise { + return undefined; + } + public defaultAccount(): Promise { return Promise.resolve({ accountId: '123456789012', partition: 'aws' }); } From a418ea67c3481cf95209844df232e84c323b5bb8 Mon Sep 17 00:00:00 2001 From: maafk Date: Wed, 16 Jun 2021 09:30:37 -0400 Subject: [PATCH 27/34] feat(core): allow user to provide docker --security-opt when bundling (#14682) Allows user to pass `securityOpts` to [DockerRunOptions](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.DockerRunOptions.html) so that docker [security configurations](https://docs.docker.com/engine/reference/run/#security-configuration) can be set for developers working in restrictive dev environments. ---- *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/lib/bundling.ts | 11 ++++++ packages/@aws-cdk/core/test/bundling.test.ts | 37 ++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/packages/@aws-cdk/core/lib/bundling.ts b/packages/@aws-cdk/core/lib/bundling.ts index 232c986b8b212..81315ee94be0d 100644 --- a/packages/@aws-cdk/core/lib/bundling.ts +++ b/packages/@aws-cdk/core/lib/bundling.ts @@ -186,6 +186,9 @@ export class BundlingDockerImage { const dockerArgs: string[] = [ 'run', '--rm', + ...options.securityOpt + ? ['--security-opt', options.securityOpt] + : [], ...options.user ? ['-u', options.user] : [], @@ -405,6 +408,14 @@ export interface DockerRunOptions { * @default - root or image default */ readonly user?: string; + + /** + * [Security configuration](https://docs.docker.com/engine/reference/run/#security-configuration) + * when running the docker container. + * + * @default - no secutiy options + */ + readonly securityOpt?: string; } /** diff --git a/packages/@aws-cdk/core/test/bundling.test.ts b/packages/@aws-cdk/core/test/bundling.test.ts index 28f150f7b7d66..174bc15c7b115 100644 --- a/packages/@aws-cdk/core/test/bundling.test.ts +++ b/packages/@aws-cdk/core/test/bundling.test.ts @@ -342,4 +342,41 @@ nodeunitShim({ test.done(); }, + + 'adding user provided securit-opt'(test: Test) { + const spawnSyncStub = sinon.stub(child_process, 'spawnSync').returns({ + status: 0, + stderr: Buffer.from('stderr'), + stdout: Buffer.from('stdout'), + pid: 123, + output: ['stdout', 'stderr'], + signal: null, + }); + + const image = DockerImage.fromRegistry('alpine'); + image.run({ + command: ['cool', 'command'], + environment: { + VAR1: 'value1', + VAR2: 'value2', + }, + securityOpt: 'no-new-privileges', + volumes: [{ hostPath: '/host-path', containerPath: '/container-path' }], + workingDirectory: '/working-directory', + user: 'user:group', + }); + + test.ok(spawnSyncStub.calledWith('docker', [ + 'run', '--rm', + '--security-opt', 'no-new-privileges', + '-u', 'user:group', + '-v', '/host-path:/container-path:delegated', + '--env', 'VAR1=value1', + '--env', 'VAR2=value2', + '-w', '/working-directory', + 'alpine', + 'cool', 'command', + ], { stdio: ['ignore', process.stderr, 'inherit'] })); + test.done(); + }, }); From 5061a8d9c59bc7380290de93aa13e4d6e8119932 Mon Sep 17 00:00:00 2001 From: Meng Xin Zhu Date: Wed, 16 Jun 2021 22:11:05 +0800 Subject: [PATCH 28/34] fix(secretsmanager): support secrets rotation in partition 'aws-cn' (#14608) closes #13385 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .gitallowed | 7 +++ .../@aws-cdk/aws-docdb/test/cluster.test.ts | 8 +-- .../integ.cluster-rotation.lit.expected.json | 50 ++++++++++++---- .../integ.cluster-rotation.lit.expected.json | 50 ++++++++++++---- .../test/integ.instance.lit.expected.json | 32 +++++++++- .../aws-secretsmanager/lib/secret-rotation.ts | 58 ++++++++++++++++++- .../test/secret-rotation.test.ts | 8 ++- 7 files changed, 181 insertions(+), 32 deletions(-) diff --git a/.gitallowed b/.gitallowed index 2fa8726e1171d..43827f7ad99b7 100644 --- a/.gitallowed +++ b/.gitallowed @@ -23,3 +23,10 @@ account: '856666278305' account: '840364872350' account: '422531588944' account: '924023996002' + +# The account IDs of password rotation applications of Serverless Application Repository +# https://docs.aws.amazon.com/secretsmanager/latest/userguide/enable-rotation-rds.html +# partition aws +account: '297356227824' +# partition aws-cn +account: '193023089310' diff --git a/packages/@aws-cdk/aws-docdb/test/cluster.test.ts b/packages/@aws-cdk/aws-docdb/test/cluster.test.ts index 1dd057fa29653..beb564ed738e5 100644 --- a/packages/@aws-cdk/aws-docdb/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-docdb/test/cluster.test.ts @@ -541,8 +541,8 @@ describe('DatabaseCluster', () => { // THEN expectCDK(stack).to(haveResource('AWS::Serverless::Application', { Location: { - ApplicationId: 'arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerMongoDBRotationSingleUser', - SemanticVersion: '1.1.60', + ApplicationId: { 'Fn::FindInMap': ['DatabaseRotationSingleUserSARMapping9AEB3E55', { Ref: 'AWS::Partition' }, 'applicationId'] }, + SemanticVersion: { 'Fn::FindInMap': ['DatabaseRotationSingleUserSARMapping9AEB3E55', { Ref: 'AWS::Partition' }, 'semanticVersion'] }, }, Parameters: { endpoint: { @@ -653,8 +653,8 @@ describe('DatabaseCluster', () => { // THEN expectCDK(stack).to(haveResource('AWS::Serverless::Application', { Location: { - ApplicationId: 'arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerMongoDBRotationMultiUser', - SemanticVersion: '1.1.60', + ApplicationId: { 'Fn::FindInMap': ['DatabaseRotationSARMappingE46CFA92', { Ref: 'AWS::Partition' }, 'applicationId'] }, + SemanticVersion: { 'Fn::FindInMap': ['DatabaseRotationSARMappingE46CFA92', { Ref: 'AWS::Partition' }, 'semanticVersion'] }, }, Parameters: { endpoint: { diff --git a/packages/@aws-cdk/aws-docdb/test/integ.cluster-rotation.lit.expected.json b/packages/@aws-cdk/aws-docdb/test/integ.cluster-rotation.lit.expected.json index 13a549e5063de..6187414b9eb75 100644 --- a/packages/@aws-cdk/aws-docdb/test/integ.cluster-rotation.lit.expected.json +++ b/packages/@aws-cdk/aws-docdb/test/integ.cluster-rotation.lit.expected.json @@ -96,15 +96,15 @@ "VPCPublicSubnet1NATGatewayE0556630": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "Tags": [ { "Key": "Name", @@ -193,15 +193,15 @@ "VPCPublicSubnet2NATGateway3C070193": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet2EIP4947BC00", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" - }, "Tags": [ { "Key": "Name", @@ -290,15 +290,15 @@ "VPCPublicSubnet3NATGatewayD3048F5C": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet3EIPAD4BC883", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet3Subnet631C5E25" - }, "Tags": [ { "Key": "Name", @@ -747,8 +747,24 @@ "Type": "AWS::Serverless::Application", "Properties": { "Location": { - "ApplicationId": "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerMongoDBRotationSingleUser", - "SemanticVersion": "1.1.3" + "ApplicationId": { + "Fn::FindInMap": [ + "DatabaseRotationSingleUserSARMapping9AEB3E55", + { + "Ref": "AWS::Partition" + }, + "applicationId" + ] + }, + "SemanticVersion": { + "Fn::FindInMap": [ + "DatabaseRotationSingleUserSARMapping9AEB3E55", + { + "Ref": "AWS::Partition" + }, + "semanticVersion" + ] + } }, "Parameters": { "endpoint": { @@ -794,5 +810,17 @@ } } } + }, + "Mappings": { + "DatabaseRotationSingleUserSARMapping9AEB3E55": { + "aws": { + "applicationId": "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerMongoDBRotationSingleUser", + "semanticVersion": "1.1.60" + }, + "aws-cn": { + "applicationId": "arn:aws-cn:serverlessrepo:cn-north-1:193023089310:applications/SecretsManagerMongoDBRotationSingleUser", + "semanticVersion": "1.1.37" + } + } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json index 74a0642b875c8..6d8c335221156 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json @@ -96,15 +96,15 @@ "VPCPublicSubnet1NATGatewayE0556630": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "Tags": [ { "Key": "Name", @@ -193,15 +193,15 @@ "VPCPublicSubnet2NATGateway3C070193": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet2EIP4947BC00", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" - }, "Tags": [ { "Key": "Name", @@ -290,15 +290,15 @@ "VPCPublicSubnet3NATGatewayD3048F5C": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet3EIPAD4BC883", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet3Subnet631C5E25" - }, "Tags": [ { "Key": "Name", @@ -769,8 +769,24 @@ "Type": "AWS::Serverless::Application", "Properties": { "Location": { - "ApplicationId": "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSMySQLRotationSingleUser", - "SemanticVersion": "1.1.60" + "ApplicationId": { + "Fn::FindInMap": [ + "DatabaseRotationSingleUserSARMapping9AEB3E55", + { + "Ref": "AWS::Partition" + }, + "applicationId" + ] + }, + "SemanticVersion": { + "Fn::FindInMap": [ + "DatabaseRotationSingleUserSARMapping9AEB3E55", + { + "Ref": "AWS::Partition" + }, + "semanticVersion" + ] + } }, "Parameters": { "endpoint": { @@ -817,5 +833,17 @@ } } } + }, + "Mappings": { + "DatabaseRotationSingleUserSARMapping9AEB3E55": { + "aws": { + "applicationId": "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSMySQLRotationSingleUser", + "semanticVersion": "1.1.60" + }, + "aws-cn": { + "applicationId": "arn:aws-cn:serverlessrepo:cn-north-1:193023089310:applications/SecretsManagerRDSMySQLRotationSingleUser", + "semanticVersion": "1.1.37" + } + } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json index 0de11e4fd5c4f..421ad34dab71e 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json @@ -813,8 +813,24 @@ "Type": "AWS::Serverless::Application", "Properties": { "Location": { - "ApplicationId": "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSOracleRotationSingleUser", - "SemanticVersion": "1.1.60" + "ApplicationId": { + "Fn::FindInMap": [ + "InstanceRotationSingleUserSARMappingFEA0C86E", + { + "Ref": "AWS::Partition" + }, + "applicationId" + ] + }, + "SemanticVersion": { + "Fn::FindInMap": [ + "InstanceRotationSingleUserSARMappingFEA0C86E", + { + "Ref": "AWS::Partition" + }, + "semanticVersion" + ] + } }, "Parameters": { "endpoint": { @@ -1122,5 +1138,17 @@ "Type": "String", "Description": "Artifact hash for asset \"884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147\"" } + }, + "Mappings": { + "InstanceRotationSingleUserSARMappingFEA0C86E": { + "aws": { + "applicationId": "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSOracleRotationSingleUser", + "semanticVersion": "1.1.60" + }, + "aws-cn": { + "applicationId": "arn:aws-cn:serverlessrepo:cn-north-1:193023089310:applications/SecretsManagerRDSOracleRotationSingleUser", + "semanticVersion": "1.1.37" + } + } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts index 0d0ed6e75c348..fb52821c88729 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts @@ -1,7 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as lambda from '@aws-cdk/aws-lambda'; import * as serverless from '@aws-cdk/aws-sam'; -import { Duration, Names, Stack, Token } from '@aws-cdk/core'; +import { Duration, Names, Stack, Token, CfnMapping, Aws } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { ISecret } from './secret'; @@ -20,6 +20,7 @@ export interface SecretRotationApplicationOptions { */ readonly isMultiUser?: boolean; } + /** * A secret rotation serverless application. */ @@ -110,11 +111,15 @@ export class SecretRotationApplication { /** * The application identifier of the rotation application + * + * @deprecated only valid when deploying to the 'aws' partition. Use `applicationArnForPartition` instead. */ public readonly applicationId: string; /** * The semantic version of the rotation application + * + * @deprecated only valid when deploying to the 'aws' partition. Use `semanticVersionForPartition` instead. */ public readonly semanticVersion: string; @@ -123,11 +128,45 @@ export class SecretRotationApplication { */ public readonly isMultiUser?: boolean; + /** + * The application name of the rotation application + */ + private readonly applicationName: string; + constructor(applicationId: string, semanticVersion: string, options?: SecretRotationApplicationOptions) { this.applicationId = `arn:aws:serverlessrepo:us-east-1:297356227824:applications/${applicationId}`; this.semanticVersion = semanticVersion; + this.applicationName = applicationId; this.isMultiUser = options && options.isMultiUser; } + + /** + * Returns the application ARN for the current partition. + * Can be used in combination with a `CfnMapping` to automatically select the correct ARN based on the current partition. + */ + public applicationArnForPartition(partition: string) { + if (partition === 'aws') { + return this.applicationId; + } else if (partition === 'aws-cn') { + return `arn:aws-cn:serverlessrepo:cn-north-1:193023089310:applications/${this.applicationName}`; + } else { + throw new Error(`unsupported partition: ${partition}`); + } + } + + /** + * The semantic version of the app for the current partition. + * Can be used in combination with a `CfnMapping` to automatically select the correct version based on the current partition. + */ + public semanticVersionForPartition(partition: string) { + if (partition === 'aws') { + return this.semanticVersion; + } else if (partition === 'aws-cn') { + return '1.1.37'; + } else { + throw new Error(`unsupported partition: ${partition}`); + } + } } /** @@ -255,8 +294,23 @@ export class SecretRotation extends CoreConstruct { } } + const sarMapping = new CfnMapping(this, 'SARMapping', { + mapping: { + 'aws': { + applicationId: props.application.applicationArnForPartition('aws'), + semanticVersion: props.application.semanticVersionForPartition('aws'), + }, + 'aws-cn': { + applicationId: props.application.applicationArnForPartition('aws-cn'), + semanticVersion: props.application.semanticVersionForPartition('aws-cn'), + }, + }, + }); const application = new serverless.CfnApplication(this, 'Resource', { - location: props.application, + location: { + applicationId: sarMapping.findInMap(Aws.PARTITION, 'applicationId'), + semanticVersion: sarMapping.findInMap(Aws.PARTITION, 'semanticVersion'), + }, parameters, }); diff --git a/packages/@aws-cdk/aws-secretsmanager/test/secret-rotation.test.ts b/packages/@aws-cdk/aws-secretsmanager/test/secret-rotation.test.ts index e6e4943c2c74a..55c7388393619 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/secret-rotation.test.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/secret-rotation.test.ts @@ -74,8 +74,12 @@ test('secret rotation single user', () => { expect(stack).toHaveResource('AWS::Serverless::Application', { Location: { - ApplicationId: 'arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSMySQLRotationSingleUser', - SemanticVersion: '1.1.60', + ApplicationId: { + 'Fn::FindInMap': ['SecretRotationSARMappingC10A2F5D', { Ref: 'AWS::Partition' }, 'applicationId'], + }, + SemanticVersion: { + 'Fn::FindInMap': ['SecretRotationSARMappingC10A2F5D', { Ref: 'AWS::Partition' }, 'semanticVersion'], + }, }, Parameters: { endpoint: { From abc457e40396e5863ba460fd8a3bcce0da3ef385 Mon Sep 17 00:00:00 2001 From: AWS CDK Automation <43080478+aws-cdk-automation@users.noreply.github.com> Date: Wed, 16 Jun 2021 17:53:52 +0300 Subject: [PATCH 29/34] feat(cfnspec): cloudformation spec v39.1.0 (#15144) Co-authored-by: AWS CDK Team Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../monocdk/rosetta/basic-portfolio.ts-fixture | 16 ++++++++++++++++ packages/monocdk/rosetta/with-plan.ts-fixture | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 packages/monocdk/rosetta/basic-portfolio.ts-fixture create mode 100644 packages/monocdk/rosetta/with-plan.ts-fixture diff --git a/packages/monocdk/rosetta/basic-portfolio.ts-fixture b/packages/monocdk/rosetta/basic-portfolio.ts-fixture new file mode 100644 index 0000000000000..d8925e6645aa7 --- /dev/null +++ b/packages/monocdk/rosetta/basic-portfolio.ts-fixture @@ -0,0 +1,16 @@ +// Fixture with packages imported, but nothing else +import { Construct, Stack } from '@aws-cdk/core'; +import * as servicecatalog from '@aws-cdk/aws-servicecatalog'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + const portfolio = new servicecatalog.Portfolio(this, "MyFirstPortfolio", { + displayName: "MyFirstPortfolio", + providerName: "MyTeam", + }); + + /// here + } +} diff --git a/packages/monocdk/rosetta/with-plan.ts-fixture b/packages/monocdk/rosetta/with-plan.ts-fixture new file mode 100644 index 0000000000000..8dbfd6ac72c89 --- /dev/null +++ b/packages/monocdk/rosetta/with-plan.ts-fixture @@ -0,0 +1,16 @@ +// Fixture with packages imported, but nothing else +import { Duration, RemovalPolicy, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as backup from '@aws-cdk/aws-backup'; +import * as dynamodb from '@aws-cdk/aws-dynamodb'; +import * as events from '@aws-cdk/aws-events'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + const plan = backup.BackupPlan.dailyWeeklyMonthly5YearRetention(this, 'Plan'); + + /// here + } +} From ae98e88a71a57866a3cea31396d3014dda5605bd Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 16 Jun 2021 17:32:44 +0200 Subject: [PATCH 30/34] fix(cli): `cdk synth` too eager with validation in Pipelines (#15147) In #14613, we introduced validation so that `cdk synth` would guard against incomplete context lookups. In the past, stacks with missing context would have successfully completed synthesis, propagated down the pipeline and caused confusing error messages during deployment. The new behavior was to error out early in case there was missing context. This broke people who had resorted to resolving context in the pipeline using multiple `synth` commands in `for`-loop: this used to work because the `synths` would be incomplete but silently succeed, but with the new validation the very first `cdk synth` would start failing and the `for` loop would never complete. This PR adds a `--no-validation` flag to `cdk synth` to stop the additional validation, so the `for` loop can complete successfully. The same behavior can be controlled with an environment variable, by setting `CDK_VALIDATION=false`. Fixes #15130. --- packages/aws-cdk/bin/cdk.ts | 5 +-- packages/aws-cdk/lib/cdk-toolkit.ts | 12 ++++--- packages/aws-cdk/test/cdk-toolkit.test.ts | 32 ++++++++++++------- packages/aws-cdk/test/integ/cli/app/app.js | 21 ++++++++++++ .../aws-cdk/test/integ/cli/cli.integtest.ts | 19 +++++++++++ 5 files changed, 71 insertions(+), 18 deletions(-) diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index ec9b6b73ea009..b1873aba6c494 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -70,6 +70,7 @@ async function parseCommandLineArguments() { ) .command(['synthesize [STACKS..]', 'synth [STACKS..]'], 'Synthesizes and prints the CloudFormation template for this stack', yargs => yargs .option('exclusively', { type: 'boolean', alias: 'e', desc: 'Only synthesize requested stacks, don\'t include dependencies' }) + .option('validation', { type: 'boolean', desc: 'After synthesis, validate stacks with the "validateOnSynth" attribute set (can also be controlled with CDK_VALIDATION)', default: true }) .option('quiet', { type: 'boolean', alias: 'q', desc: 'Do not output CloudFormation Template to stdout', default: false })) .command('bootstrap [ENVIRONMENTS..]', 'Deploys the CDK toolkit stack into an AWS environment', yargs => yargs .option('bootstrap-bucket-name', { type: 'string', alias: ['b', 'toolkit-bucket-name'], desc: 'The name of the CDK toolkit bucket; bucket will be created and must not exist', default: undefined }) @@ -328,9 +329,9 @@ async function initCommandLine() { case 'synthesize': case 'synth': if (args.exclusively) { - return cli.synth(args.STACKS, args.exclusively, args.quiet); + return cli.synth(args.STACKS, args.exclusively, args.quiet, args.validation); } else { - return cli.synth(args.STACKS, true, args.quiet); + return cli.synth(args.STACKS, true, args.quiet, args.validation); } diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index 7bf1fc0499cb1..044bdb1e7aca4 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -296,8 +296,8 @@ export class CdkToolkit { * OUTPUT: If more than one stack ends up being selected, an output directory * should be supplied, where the templates will be written. */ - public async synth(stackNames: string[], exclusively: boolean, quiet: boolean): Promise { - const stacks = await this.selectStacksForDiff(stackNames, exclusively); + public async synth(stackNames: string[], exclusively: boolean, quiet: boolean, autoValidate?: boolean): Promise { + const stacks = await this.selectStacksForDiff(stackNames, exclusively, autoValidate); // if we have a single stack, print it to STDOUT if (stacks.stackCount === 1) { @@ -392,7 +392,7 @@ export class CdkToolkit { return stacks; } - private async selectStacksForDiff(stackNames: string[], exclusively?: boolean) { + private async selectStacksForDiff(stackNames: string[], exclusively?: boolean, autoValidate?: boolean) { const assembly = await this.assembly(); const selectedForDiff = await assembly.selectStacks({ patterns: stackNames }, { @@ -401,9 +401,11 @@ export class CdkToolkit { }); const allStacks = await this.selectStacksForList([]); - const flaggedStacks = allStacks.filter(art => art.validateOnSynth ?? false); + const autoValidateStacks = autoValidate + ? allStacks.filter(art => art.validateOnSynth ?? false) + : new StackCollection(assembly, []); - await this.validateStacks(selectedForDiff.concat(flaggedStacks)); + await this.validateStacks(selectedForDiff.concat(autoValidateStacks)); return selectedForDiff; } diff --git a/packages/aws-cdk/test/cdk-toolkit.test.ts b/packages/aws-cdk/test/cdk-toolkit.test.ts index 5a3760ab0b0c6..c2bd6c48206ca 100644 --- a/packages/aws-cdk/test/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cdk-toolkit.test.ts @@ -178,22 +178,32 @@ describe('synth', () => { process.env.STACKS_TO_VALIDATE = undefined; }); - test('stack has error and is flagged for validation', async() => { - cloudExecutable = new MockCloudExecutable({ - stacks: [ - MockStack.MOCK_STACK_A, - MockStack.MOCK_STACK_B, - ], - nestedAssemblies: [{ + describe('stack with error and flagged for validation', () => { + beforeEach(() => { + cloudExecutable = new MockCloudExecutable({ stacks: [ - { properties: { validateOnSynth: true }, ...MockStack.MOCK_STACK_WITH_ERROR }, + MockStack.MOCK_STACK_A, + MockStack.MOCK_STACK_B, ], - }], + nestedAssemblies: [{ + stacks: [ + { properties: { validateOnSynth: true }, ...MockStack.MOCK_STACK_WITH_ERROR }, + ], + }], + }); }); - const toolkit = defaultToolkitSetup(); + test('causes synth to fail if autoValidate=true', async() => { + const toolkit = defaultToolkitSetup(); + const autoValidate = true; + await expect(toolkit.synth([], false, true, autoValidate)).rejects.toBeDefined(); + }); - await expect(toolkit.synth([], false, true)).rejects.toBeDefined(); + test('causes synth to succeed if autoValidate=false', async() => { + const toolkit = defaultToolkitSetup(); + const autoValidate = false; + await expect(toolkit.synth([], false, true, autoValidate)).resolves.toBeUndefined(); + }); }); test('stack has error and was explicitly selected', async() => { diff --git a/packages/aws-cdk/test/integ/cli/app/app.js b/packages/aws-cdk/test/integ/cli/app/app.js index 43875f4fa0037..205d7014503dc 100644 --- a/packages/aws-cdk/test/integ/cli/app/app.js +++ b/packages/aws-cdk/test/integ/cli/app/app.js @@ -8,6 +8,7 @@ const lambda = require('@aws-cdk/aws-lambda'); const docker = require('@aws-cdk/aws-ecr-assets'); const core = require('@aws-cdk/core') const { StackWithNestedStack, StackWithNestedStackUsingParameters } = require('./nested-stack'); +const { Annotations } = require('@aws-cdk/core'); const stackPrefix = process.env.STACK_NAME_PREFIX; if (!stackPrefix) { @@ -136,7 +137,22 @@ class ProvidingStack extends cdk.Stack { } } +class StackWithError extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + this.topic = new sns.Topic(this, 'BogusTopic'); // Some filler + Annotations.of(this).addError('This is an error'); + } +} + +class StageWithError extends cdk.Stage { + constructor(parent, id, props) { + super(parent, id, props); + + new StackWithError(this, 'Stack'); + } +} class ConsumingStack extends cdk.Stack { constructor(parent, id, props) { @@ -335,6 +351,11 @@ switch (stackSet) { }); break; + case 'stage-with-errors': + const stage = new StageWithError(app, `${stackPrefix}-stage-with-errors`); + stage.synth({ validateOnSynthesis: true }); + break; + default: throw new Error(`Unrecognized INTEG_STACK_SET: '${stackSet}'`); } diff --git a/packages/aws-cdk/test/integ/cli/cli.integtest.ts b/packages/aws-cdk/test/integ/cli/cli.integtest.ts index eddce24e7a778..197c4150d4aae 100644 --- a/packages/aws-cdk/test/integ/cli/cli.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/cli.integtest.ts @@ -536,6 +536,25 @@ integTest('cdk ls', withDefaultFixture(async (fixture) => { } })); +integTest('synthing a stage with errors leads to failure', withDefaultFixture(async (fixture) => { + const output = await fixture.cdk(['synth'], { + allowErrExit: true, + modEnv: { + INTEG_STACK_SET: 'stage-with-errors', + }, + }); + + expect(output).toContain('This is an error'); +})); + +integTest('synthing a stage with errors can be suppressed', withDefaultFixture(async (fixture) => { + await fixture.cdk(['synth', '--no-validation'], { + modEnv: { + INTEG_STACK_SET: 'stage-with-errors', + }, + }); +})); + integTest('deploy stack without resource', withDefaultFixture(async (fixture) => { // Deploy the stack without resources await fixture.cdkDeploy('conditional-resource', { modEnv: { NO_RESOURCE: 'TRUE' } }); From a23aa63778a1fbb33596f402c0f769be3546b1a8 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 16 Jun 2021 17:27:58 +0100 Subject: [PATCH 31/34] feat(assertions): an initial version of the CDK 'assert' library, now experimentally available in all supported languages (#14952) Announcing 'assertions', the next revision of the CDK assert module. This module is built via jsii and hence is available in all CDK supported languages. Motivation and design: https://github.com/aws/aws-cdk-rfcs/blob/master/text/0328-polyglot-assert.md --- package.json | 34 +- packages/@aws-cdk/assertions/.eslintrc.js | 10 + packages/@aws-cdk/assertions/.gitignore | 19 + packages/@aws-cdk/assertions/.npmignore | 24 + packages/@aws-cdk/assertions/LICENSE | 201 ++++++++ packages/@aws-cdk/assertions/NOTICE | 465 ++++++++++++++++++ packages/@aws-cdk/assertions/README.md | 134 +++++ packages/@aws-cdk/assertions/jest.config.js | 11 + .../@aws-cdk/assertions/lib/assertions.ts | 92 ++++ packages/@aws-cdk/assertions/lib/index.ts | 2 + packages/@aws-cdk/assertions/lib/match.ts | 13 + packages/@aws-cdk/assertions/package.json | 113 +++++ .../assertions/test/assertions.test.ts | 138 ++++++ .../@aws-cdk/assertions/test/matchers.test.ts | 23 + packages/@aws-cdk/assertions/vendor-in.sh | 34 ++ packages/@aws-cdk/aws-efs/package.json | 2 +- .../aws-efs/test/access-point.test.ts | 8 +- .../aws-efs/test/efs-file-system.test.ts | 77 +-- packages/aws-cdk-lib/NOTICE | 435 ++++++++++++++++ packages/aws-cdk-lib/package.json | 11 + packages/decdk/package.json | 1 + packages/monocdk/NOTICE | 435 ++++++++++++++++ packages/monocdk/package.json | 11 + 23 files changed, 2248 insertions(+), 45 deletions(-) create mode 100644 packages/@aws-cdk/assertions/.eslintrc.js create mode 100644 packages/@aws-cdk/assertions/.gitignore create mode 100644 packages/@aws-cdk/assertions/.npmignore create mode 100644 packages/@aws-cdk/assertions/LICENSE create mode 100644 packages/@aws-cdk/assertions/NOTICE create mode 100644 packages/@aws-cdk/assertions/README.md create mode 100644 packages/@aws-cdk/assertions/jest.config.js create mode 100644 packages/@aws-cdk/assertions/lib/assertions.ts create mode 100644 packages/@aws-cdk/assertions/lib/index.ts create mode 100644 packages/@aws-cdk/assertions/lib/match.ts create mode 100644 packages/@aws-cdk/assertions/package.json create mode 100644 packages/@aws-cdk/assertions/test/assertions.test.ts create mode 100644 packages/@aws-cdk/assertions/test/matchers.test.ts create mode 100755 packages/@aws-cdk/assertions/vendor-in.sh diff --git a/package.json b/package.json index 718660cf8df49..62d30462466f0 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,16 @@ "nohoist": [ "**/jszip", "**/jszip/**", + "@aws-cdk/assertions/colors", + "@aws-cdk/assertions/colors/**", + "@aws-cdk/assertions/diff", + "@aws-cdk/assertions/diff/**", + "@aws-cdk/assertions/fast-deep-equal", + "@aws-cdk/assertions/fast-deep-equal/**", + "@aws-cdk/assertions/string-width", + "@aws-cdk/assertions/string-width/**", + "@aws-cdk/assertions/table", + "@aws-cdk/assertions/table/**", "@aws-cdk/aws-codebuild/yaml", "@aws-cdk/aws-codebuild/yaml/**", "@aws-cdk/aws-codepipeline-actions/case", @@ -81,6 +91,8 @@ "@aws-cdk/aws-ecr-assets/minimatch/**", "@aws-cdk/aws-eks/yaml", "@aws-cdk/aws-eks/yaml/**", + "@aws-cdk/aws-events-targets/aws-sdk", + "@aws-cdk/aws-events-targets/aws-sdk/**", "@aws-cdk/cloud-assembly-schema/jsonschema", "@aws-cdk/cloud-assembly-schema/jsonschema/**", "@aws-cdk/cloud-assembly-schema/semver", @@ -97,14 +109,18 @@ "@aws-cdk/core/minimatch/**", "@aws-cdk/cx-api/semver", "@aws-cdk/cx-api/semver/**", - "@aws-cdk/aws-events-targets/aws-sdk", - "@aws-cdk/aws-events-targets/aws-sdk/**", "@aws-cdk/yaml-cfn/yaml", "@aws-cdk/yaml-cfn/yaml/**", "aws-cdk-lib/@balena/dockerignore", "aws-cdk-lib/@balena/dockerignore/**", "aws-cdk-lib/case", "aws-cdk-lib/case/**", + "aws-cdk-lib/colors", + "aws-cdk-lib/colors/**", + "aws-cdk-lib/diff", + "aws-cdk-lib/diff/**", + "aws-cdk-lib/fast-deep-equal", + "aws-cdk-lib/fast-deep-equal/**", "aws-cdk-lib/fs-extra", "aws-cdk-lib/fs-extra/**", "aws-cdk-lib/ignore", @@ -117,12 +133,22 @@ "aws-cdk-lib/punycode/**", "aws-cdk-lib/semver", "aws-cdk-lib/semver/**", + "aws-cdk-lib/string-width", + "aws-cdk-lib/string-width/**", + "aws-cdk-lib/table", + "aws-cdk-lib/table/**", "aws-cdk-lib/yaml", "aws-cdk-lib/yaml/**", "monocdk/@balena/dockerignore", "monocdk/@balena/dockerignore/**", "monocdk/case", "monocdk/case/**", + "monocdk/colors", + "monocdk/colors/**", + "monocdk/diff", + "monocdk/diff/**", + "monocdk/fast-deep-equal", + "monocdk/fast-deep-equal/**", "monocdk/fs-extra", "monocdk/fs-extra/**", "monocdk/ignore", @@ -135,6 +161,10 @@ "monocdk/punycode/**", "monocdk/semver", "monocdk/semver/**", + "monocdk/string-width", + "monocdk/string-width/**", + "monocdk/table", + "monocdk/table/**", "monocdk/yaml", "monocdk/yaml/**" ] diff --git a/packages/@aws-cdk/assertions/.eslintrc.js b/packages/@aws-cdk/assertions/.eslintrc.js new file mode 100644 index 0000000000000..7d73af332f5d8 --- /dev/null +++ b/packages/@aws-cdk/assertions/.eslintrc.js @@ -0,0 +1,10 @@ +const baseConfig = require('cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = { + ...baseConfig, + ignorePatterns: [...baseConfig.ignorePatterns, 'lib/vendored/'], + rules: { + ...baseConfig.rules, + '@typescript-eslint/explicit-function-return-type': ['error'], + } +}; diff --git a/packages/@aws-cdk/assertions/.gitignore b/packages/@aws-cdk/assertions/.gitignore new file mode 100644 index 0000000000000..07fc66baa0c49 --- /dev/null +++ b/packages/@aws-cdk/assertions/.gitignore @@ -0,0 +1,19 @@ +lib/vendored/ + +*.d.ts +*.generated.ts +*.js +*.js.map +*.snk +.jsii +.LAST_BUILD +.LAST_PACKAGE +nyc.config.js +.nyc_output +coverage +dist +tsconfig.json +!.eslintrc.js +!jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/.npmignore b/packages/@aws-cdk/assertions/.npmignore new file mode 100644 index 0000000000000..24f98e142c79a --- /dev/null +++ b/packages/@aws-cdk/assertions/.npmignore @@ -0,0 +1,24 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +*.snk + +*.tsbuildinfo + +tsconfig.json +.eslintrc.js +jest.config.js + +# exclude cdk artifacts +**/cdk.out +junit.xml +test/ +!.jsii +!*.js \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/LICENSE b/packages/@aws-cdk/assertions/LICENSE new file mode 100644 index 0000000000000..28e4bdcec77ec --- /dev/null +++ b/packages/@aws-cdk/assertions/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/assertions/NOTICE b/packages/@aws-cdk/assertions/NOTICE new file mode 100644 index 0000000000000..4af990c1637a0 --- /dev/null +++ b/packages/@aws-cdk/assertions/NOTICE @@ -0,0 +1,465 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +------------------------------------------------------------------------------- + +The AWS CDK includes the following third-party software/licensing: + +** colors - https://www.npmjs.com/package/colors +Original Library + - Copyright (c) Marak Squires + +Additional Functionality + - Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------- + +** diff - https://www.npmjs.com/package/diff +Copyright (c) 2009-2015, Kevin Decker + +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Kevin Decker nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------- + +** fast-deep-equal - https://www.npmjs.com/package/fast-deep-equal +Copyright (c) 2017 Evgeny Poberezkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------- + +** string-width - https://www.npmjs.com/package/string-width +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** emoji-regex - https://www.npmjs.com/package/emoji-regex +Copyright Mathias Bynens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** is-fullwidth-code-point - https://www.npmjs.com/package/is-fullwidth-code-point +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** strip-ansi - https://www.npmjs.com/package/strip-ansi +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** ansi-regex - https://www.npmjs.com/package/ansi-regex +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** table - https://www.npmjs.com/package/table +Copyright (c) 2018, Gajus Kuizinas (http://gajus.com/) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Gajus Kuizinas (http://gajus.com/) nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ANUARY BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------- + +** ajv - https://www.npmjs.com/package/ajv +Copyright (c) 2015-2021 Evgeny Poberezkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------- + +** json-schema-traverse - https://www.npmjs.com/package/json-schema-traverse +Copyright (c) 2017 Evgeny Poberezkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------- + +** require-from-string - https://www.npmjs.com/package/require-from-string +Copyright (c) Vsevolod Strukchinsky (github.com/floatdrop) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------- + +** uri-js - https://www.npmjs.com/package/uri-js +Copyright 2011 Gary Court. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY GARY COURT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Gary Court. + +---------------- + +** punycode - https://www.npmjs.com/package/punycode +Copyright Mathias Bynens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** lodash.truncate - https://www.npmjs.com/package/lodash +Copyright JS Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. + +---------------- + +** lodash.clonedeep - https://www.npmjs.com/package/lodash +Copyright JS Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. + +---------------- + +** slice-ansi - https://www.npmjs.com/package/slice-ansi +Copyright (c) DC +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** ansi-styles - https://www.npmjs.com/package/ansi-styles +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** color-convert - https://www.npmjs.com/package/color-convert +Copyright (c) 2011-2016 Heather Arthur . +Copyright (c) 2016-2021 Josh Junon . + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** color-name - https://www.npmjs.com/package/color-name +Copyright (c) 2015 Dmitry Ivanov + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** astral-regex - https://www.npmjs.com/package/astral-regex +Copyright (c) Kevin Mårtensson (github.com/kevva) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/README.md b/packages/@aws-cdk/assertions/README.md new file mode 100644 index 0000000000000..f94f7808b2725 --- /dev/null +++ b/packages/@aws-cdk/assertions/README.md @@ -0,0 +1,134 @@ +# Assertions + + +--- + +![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. + +--- + + + +Functions for writing test asserting against CDK applications, with focus on CloudFormation templates. + +The `TemplateAssertions` class includes a set of methods for writing assertions against CloudFormation templates. Use one of the `TemplateAssertions.fromXxx()` static methods to create an instance of this class. + +To create `TemplateAssertions` from CDK stack, start off with: + +```ts +import { Stack } from '@aws-cdk/core'; +import { TemplateAssertions } from '@aws-cdk/assertions'; + +const stack = new Stack(...) +... +const assert = TemplateAssertions.fromStack(stack); +``` + +Alternatively, assertions can be run on an existing CloudFormation template - + +```ts +const template = fs.readFileSync('/path/to/template/file'); +const assert = TemplateAssertions.fromString(template); +``` + +## Full Template Match + +The simplest assertion would be to assert that the template matches a given +template. + +```ts +assert.templateMatches({ + Resources: { + Type: 'Foo::Bar', + Properties: { + Baz: 'Qux', + }, + }, +}); +``` + +## Counting Resources + +This module allows asserting the number of resources of a specific type found +in a template. + +```ts +assert.resourceCountIs('Foo::Bar', 2); +``` + +## Resource Matching + +Beyond resource counting, the module also allows asserting that a resource with +specific properties are present. + +The following code asserts that the `Properties` section of a resource of type +`Foo::Bar` contains the specified properties - + +```ts +assert.hasResourceProperties('Foo::Bar', { + Foo: 'Bar', + Baz: 5, + Qux: [ 'Waldo', 'Fred' ], +}); +``` + +The same method allows asserting the complete definition of the 'Resource' +which can be used to verify things other sections like `DependsOn`, `Metadata`, +`DeletionProperty`, etc. + +```ts +assert.hasResourceDefinition('Foo::Bar', { + Properties: { Foo: 'Bar' }, + DependsOn: [ 'Waldo', 'Fred' ], +}); +``` + +## Special Matchers + +The expectation provided to the `hasResourceXXX()` methods, besides carrying +literal values, as seen in the above examples, can also have special matchers +encoded. +They are available as part of the `Matchers` class and can be used as follows - + +```ts +assert.hasResourceProperties('Foo::Bar', { + Foo: 'Bar', + Baz: Match.absentProperty(), +}) +``` + +The list of available matchers are - + +* `absentProperty()`: Specifies that this key must not be present. + +## Strongly typed languages + +Some of the APIs documented above, such as `templateMatches()` and +`hasResourceProperties()` accept fluently an arbitrary JSON (like) structure +its parameter. +This fluency is available only in dynamically typed languages like javascript +and Python. + +For strongly typed languages, like Java, you can achieve similar fluency using +any popular JSON deserializer. The following Java example uses `Gson` - + +```java +// In Java, using text blocks and Gson +import com.google.gson.Gson; + +String json = """ + { + "Foo": "Bar", + "Baz": 5, + "Qux": [ "Waldo", "Fred" ], + } """; + +Map expected = new Gson().fromJson(json, Map.class); +assert.hasResourceProperties("Foo::Bar", expected); +``` diff --git a/packages/@aws-cdk/assertions/jest.config.js b/packages/@aws-cdk/assertions/jest.config.js new file mode 100644 index 0000000000000..e2cea817aefd7 --- /dev/null +++ b/packages/@aws-cdk/assertions/jest.config.js @@ -0,0 +1,11 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = { + ...baseConfig, + coverageThreshold: { + global: { + statements: 75, + branches: 60, + }, + }, + collectCoverageFrom: ['lib/**', '!lib/vendored/**'] +}; diff --git a/packages/@aws-cdk/assertions/lib/assertions.ts b/packages/@aws-cdk/assertions/lib/assertions.ts new file mode 100644 index 0000000000000..bdf9532878fc9 --- /dev/null +++ b/packages/@aws-cdk/assertions/lib/assertions.ts @@ -0,0 +1,92 @@ +import { Stack, Stage } from '@aws-cdk/core'; +import * as assert from './vendored/assert'; + +/** + * Suite of assertions that can be run on a CDK stack. + * Typically used, as part of unit tests, to validate that the rendered + * CloudFormation template has expected resources and properties. + */ +export class TemplateAssertions { + + /** + * Base your assertions on the CloudFormation template synthesized by a CDK `Stack`. + * @param stack the CDK Stack to run assertions on + */ + public static fromStack(stack: Stack): TemplateAssertions { + return new TemplateAssertions(toTemplate(stack)); + } + + /** + * Base your assertions from an existing CloudFormation template formatted as a + * nested set of records. + * @param template the CloudFormation template formatted as a nested set of records + */ + public static fromTemplate(template: { [key: string] : any }): TemplateAssertions { + return new TemplateAssertions(template); + } + + /** + * Base your assertions from an existing CloudFormation template formatted as a string. + * @param template the CloudFormation template in + */ + public static fromString(template: string): TemplateAssertions { + return new TemplateAssertions(JSON.parse(template)); + } + + private readonly inspector: assert.StackInspector; + + private constructor(template: any) { + this.inspector = new assert.StackInspector(template); + } + + /** + * Assert that the given number of resources of the given type exist in the + * template. + * @param type the resource type; ex: `AWS::S3::Bucket` + * @param count number of expected instances + */ + public resourceCountIs(type: string, count: number): void { + const assertion = assert.countResources(type, count); + assertion.assertOrThrow(this.inspector); + } + + /** + * Assert that a resource of the given type and properties exists in the + * CloudFormation template. + * @param type the resource type; ex: `AWS::S3::Bucket` + * @param props the 'Properties' section of the resource as should be expected in the template. + */ + public hasResourceProperties(type: string, props: any): void { + const assertion = assert.haveResource(type, props, assert.ResourcePart.Properties); + assertion.assertOrThrow(this.inspector); + } + + /** + * Assert that a resource of the given type and given definition exists in the + * CloudFormation template. + * @param type the resource type; ex: `AWS::S3::Bucket` + * @param props the entire defintion of the resource as should be expected in the template. + */ + public hasResourceDefinition(type: string, props: any): void { + const assertion = assert.haveResource(type, props, assert.ResourcePart.CompleteDefinition); + assertion.assertOrThrow(this.inspector); + } + + /** + * Assert that the CloudFormation template matches the given value + * @param expected the expected CloudFormation template as key-value pairs. + */ + public templateMatches(expected: {[key: string]: any}): void { + const assertion = assert.matchTemplate(expected); + assertion.assertOrThrow(this.inspector); + } +} + +function toTemplate(stack: Stack): any { + const root = stack.node.root; + if (!Stage.isStage(root)) { + throw new Error('unexpected: all stacks must be part of a Stage or an App'); + } + const assembly = root.synth(); + return assembly.getStackArtifact(stack.artifactId).template; +} \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/lib/index.ts b/packages/@aws-cdk/assertions/lib/index.ts new file mode 100644 index 0000000000000..af3c81322a81e --- /dev/null +++ b/packages/@aws-cdk/assertions/lib/index.ts @@ -0,0 +1,2 @@ +export * from './assertions'; +export * from './match'; \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/lib/match.ts b/packages/@aws-cdk/assertions/lib/match.ts new file mode 100644 index 0000000000000..11f01e1ca32d4 --- /dev/null +++ b/packages/@aws-cdk/assertions/lib/match.ts @@ -0,0 +1,13 @@ +import { ABSENT } from './vendored/assert'; + +/** + * Partial and special matching during template assertions + */ +export class Match { + /** + * Use this matcher in the place of a field's value, if the field must not be present. + */ + public static absentProperty(): string { + return ABSENT; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/package.json b/packages/@aws-cdk/assertions/package.json new file mode 100644 index 0000000000000..34c3937e7b774 --- /dev/null +++ b/packages/@aws-cdk/assertions/package.json @@ -0,0 +1,113 @@ +{ + "name": "@aws-cdk/assertions", + "version": "0.0.0", + "description": "An assertion library for use with CDK Apps", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "pkglint": "pkglint -f", + "package": "cdk-package", + "build+test+package": "yarn build+test && yarn package", + "build+test": "yarn build && yarn test", + "rosetta:extract": "yarn --silent jsii-rosetta extract", + "compat": "cdk-compat", + "build+extract": "yarn build && yarn rosetta:extract", + "build+test+extract": "yarn build+test && yarn rosetta:extract" + }, + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awscdk.assertions", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "cdk-assertions" + } + }, + "dotnet": { + "namespace": "Amazon.CDK.Assertions", + "packageId": "Amazon.CDK.Assertions", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-cdk.assertions", + "module": "aws_cdk.assertions", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] + } + }, + "projectReferences": true + }, + "cdk-build": { + "jest": true, + "pre": [ + "./vendor-in.sh" + ] + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/jest": "^26.0.23", + "cdk-build-tools": "0.0.0", + "constructs": "^3.3.69", + "jest": "^26.6.3", + "pkglint": "0.0.0", + "ts-jest": "^26.5.6" + }, + "dependencies": { + "@aws-cdk/cloud-assembly-schema": "0.0.0", + "@aws-cdk/core": "0.0.0", + "@aws-cdk/cx-api": "0.0.0", + "colors": "^1.4.0", + "constructs": "^3.3.69", + "diff": "^5.0.0", + "fast-deep-equal": "^3.1.3", + "string-width": "^4.2.2", + "table": "^6.7.1" + }, + "peerDependencies": { + "@aws-cdk/cloud-assembly-schema": "0.0.0", + "@aws-cdk/core": "0.0.0", + "@aws-cdk/cx-api": "0.0.0", + "constructs": "^3.3.69" + }, + "bundledDependencies": [ + "colors", + "diff", + "fast-deep-equal", + "string-width", + "table" + ], + "repository": { + "url": "https://github.com/aws/aws-cdk.git", + "type": "git", + "directory": "packages/@aws-cdk/assertions" + }, + "keywords": [ + "aws", + "cdk", + "assert" + ], + "homepage": "https://github.com/aws/aws-cdk", + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "experimental", + "maturity": "experimental", + "publishConfig": { + "tag": "latest" + }, + "awscdkio": { + "announce": false + } +} diff --git a/packages/@aws-cdk/assertions/test/assertions.test.ts b/packages/@aws-cdk/assertions/test/assertions.test.ts new file mode 100644 index 0000000000000..faa4b26b13f4f --- /dev/null +++ b/packages/@aws-cdk/assertions/test/assertions.test.ts @@ -0,0 +1,138 @@ +import { CfnResource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { TemplateAssertions } from '../lib'; + +describe('StackAssertions', () => { + describe('fromString', () => { + test('default', () => { + const assertions = TemplateAssertions.fromString(`{ + "Resources": { + "Foo": { + "Type": "Baz::Qux", + "Properties": { "Fred": "Waldo" } + } + } + }`); + assertions.resourceCountIs('Baz::Qux', 1); + }); + }); + + describe('fromStack', () => { + test('fails when root is not a stage', () => { + const c = new Construct(undefined as any, ''); + const stack = new Stack(c, 'MyStack'); + expect(() => TemplateAssertions.fromStack(stack)).toThrow(/must be part of a Stage or an App/); + }); + }); + + describe('resourceCountIs', () => { + test('resource exists', () => { + const stack = new Stack(); + new CfnResource(stack, 'Resource', { + type: 'Foo::Bar', + }); + + const inspect = TemplateAssertions.fromStack(stack); + inspect.resourceCountIs('Foo::Bar', 1); + + expect(() => inspect.resourceCountIs('Foo::Bar', 0)).toThrow(/has 1 resource of type Foo::Bar/); + expect(() => inspect.resourceCountIs('Foo::Bar', 2)).toThrow(/has 1 resource of type Foo::Bar/); + + expect(() => inspect.resourceCountIs('Foo::Baz', 1)).toThrow(/has 0 resource of type Foo::Baz/); + }); + + test('no resource', () => { + const stack = new Stack(); + + const inspect = TemplateAssertions.fromStack(stack); + inspect.resourceCountIs('Foo::Bar', 0); + + expect(() => inspect.resourceCountIs('Foo::Bar', 1)).toThrow(/has 0 resource of type Foo::Bar/); + }); + }); + + describe('hasResourceXXX', () => { + test('property matching', () => { + const stack = new Stack(); + new CfnResource(stack, 'Resource', { + type: 'Foo::Bar', + properties: { + baz: 'qux', + }, + }); + + const inspect = TemplateAssertions.fromStack(stack); + inspect.hasResourceProperties('Foo::Bar', { baz: 'qux' }); + + expect( + () => inspect.hasResourceProperties('Foo::Bar', { fred: 'waldo' }), + ).toThrow(/None .* matches resource 'Foo::Bar'/); + expect( + () => inspect.hasResourceProperties('Foo::Baz', {}), + ).toThrow(/None .* matches resource 'Foo::Baz'/); + }); + + test('no resource', () => { + const stack = new Stack(); + new CfnResource(stack, 'Resource', { + type: 'Foo::Bar', + }); + + const inspect = TemplateAssertions.fromStack(stack); + expect( + () => inspect.hasResourceProperties('Foo::Baz', {}), + ).toThrow(/None .* matches resource 'Foo::Baz'/); + }); + + test('complete definition', () => { + const stack = new Stack(); + const bar = new CfnResource(stack, 'Bar', { type: 'Foo::Bar', properties: { baz: 'qux' } }); + const baz = new CfnResource(stack, 'Baz', { type: 'Foo::Baz' }); + bar.node.addDependency(baz); + + const inspect = TemplateAssertions.fromStack(stack); + inspect.hasResourceDefinition('Foo::Bar', { + Properties: { baz: 'qux' }, + DependsOn: ['Baz'], + }); + }); + }); + + describe('templateMatches', () => { + test('matches', () => { + const stack = new Stack(); + new CfnResource(stack, 'Foo', { + type: 'Foo::Bar', + properties: { baz: 'qux' }, + }); + + const inspect = TemplateAssertions.fromStack(stack); + inspect.templateMatches({ + Resources: { + Foo: { + Type: 'Foo::Bar', + Properties: { baz: 'qux' }, + }, + }, + }); + }); + + test('fails', () => { + const stack = new Stack(); + new CfnResource(stack, 'Foo', { + type: 'Foo::Bar', + properties: { baz: 'qux' }, + }); + + const inspect = TemplateAssertions.fromStack(stack); + expect(() => inspect.templateMatches({ + Resources: { + Foo: { + Type: 'Foo::Bar', + Properties: { baz: 'waldo' }, + }, + }, + })).resolves; + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/test/matchers.test.ts b/packages/@aws-cdk/assertions/test/matchers.test.ts new file mode 100644 index 0000000000000..c61253d768a17 --- /dev/null +++ b/packages/@aws-cdk/assertions/test/matchers.test.ts @@ -0,0 +1,23 @@ +import { CfnResource, Stack } from '@aws-cdk/core'; +import { Match, TemplateAssertions } from '../lib'; + +describe('Matchers', () => { + test('absent', () => { + const stack = new Stack(); + new CfnResource(stack, 'Resource', { + type: 'Foo::Bar', + properties: { + baz: 'qux', + }, + }); + + const inspect = TemplateAssertions.fromStack(stack); + inspect.hasResourceProperties('Foo::Bar', { + fred: Match.absentProperty(), + }); + + expect(() => inspect.hasResourceProperties('Foo::Bar', { + baz: Match.absentProperty(), + })).toThrow(/None .* matches resource 'Foo::Bar'/); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/vendor-in.sh b/packages/@aws-cdk/assertions/vendor-in.sh new file mode 100755 index 0000000000000..699d121a0c323 --- /dev/null +++ b/packages/@aws-cdk/assertions/vendor-in.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +echo "⏳ Vendoring in modules..." + +scriptdir=$(cd $(dirname $0) && pwd) +cd $scriptdir +set -euo pipefail +dest="lib/vendored" +mkdir -p $dest + +# cfnspec +mkdir -p $dest/cfnspec +rsync -a --exclude '*.d.ts' --exclude '*.js' ../cfnspec/lib/ $dest/cfnspec/lib +rsync -a ../cfnspec/spec/ $dest/cfnspec/spec + +# cloudformation-diff +rsync -a --exclude '*.d.ts' --exclude '*.js' ../cloudformation-diff/lib/ $dest/cloudformation-diff +find $dest/cloudformation-diff -name '*.ts' -exec sed -i '' "s^'@aws-cdk/cfnspec'^'../../cfnspec/lib'^g" '{}' \; + +# assert-internal +rsync -a --exclude '*.d.ts' --exclude '*.js' ../assert-internal/lib/ $dest/assert +find $dest/assert -name '*.ts' -exec sed -i '' "s^'@aws-cdk/cloudformation-diff'^'../../cloudformation-diff'^g" '{}' \; + +# readme +cat > $dest/README.md < { // WHEN fileSystem.addAccessPoint('MyAccessPoint'); // THEN - expect(stack).toHaveResource('AWS::EFS::AccessPoint'); + TemplateAssertions.fromStack(stack).resourceCountIs('AWS::EFS::AccessPoint', 1); }); test('new AccessPoint correctly', () => { @@ -28,7 +28,7 @@ test('new AccessPoint correctly', () => { fileSystem, }); // THEN - expect(stack).toHaveResource('AWS::EFS::AccessPoint'); + TemplateAssertions.fromStack(stack).resourceCountIs('AWS::EFS::AccessPoint', 1); }); test('import an AccessPoint using fromAccessPointId', () => { @@ -143,7 +143,7 @@ test('custom access point is created correctly', () => { }); // THEN - expect(stack).toHaveResource('AWS::EFS::AccessPoint', { + TemplateAssertions.fromStack(stack).hasResourceProperties('AWS::EFS::AccessPoint', { FileSystemId: { Ref: 'EfsFileSystem37910666', }, diff --git a/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts b/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts index d98d0f4066c61..9141ba0c8de46 100644 --- a/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts +++ b/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts @@ -1,4 +1,4 @@ -import { ABSENT, expect as expectCDK, haveResource, ResourcePart, countResources } from '@aws-cdk/assert-internal'; +import { TemplateAssertions, Match } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import { App, RemovalPolicy, Size, Stack, Tags } from '@aws-cdk/core'; @@ -26,9 +26,9 @@ testFutureBehavior( vpc: customVpc, }); - expectCDK(customStack).to(haveResource('AWS::EFS::FileSystem', { + TemplateAssertions.fromStack(customStack).hasResourceProperties('AWS::EFS::FileSystem', { Encrypted: true, - })); + }); }); @@ -40,9 +40,9 @@ testLegacyBehavior('when @aws-cdk/aws-efs:defaultEncryptionAtRest is missing, en vpc: customVpc, }); - expectCDK(customStack).to(haveResource('AWS::EFS::FileSystem', { - Encrypted: ABSENT, - })); + TemplateAssertions.fromStack(customStack).hasResourceProperties('AWS::EFS::FileSystem', { + Encrypted: Match.absentProperty(), + }); }); @@ -52,12 +52,13 @@ test('default file system is created correctly', () => { vpc, }); // THEN - expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { + const assertions = TemplateAssertions.fromStack(stack); + assertions.hasResourceDefinition('AWS::EFS::FileSystem', { DeletionPolicy: 'Retain', UpdateReplacePolicy: 'Retain', - }, ResourcePart.CompleteDefinition)); - expectCDK(stack).to(haveResource('AWS::EFS::MountTarget')); - expectCDK(stack).to(haveResource('AWS::EC2::SecurityGroup')); + }); + assertions.resourceCountIs('AWS::EFS::MountTarget', 2); + assertions.resourceCountIs('AWS::EC2::SecurityGroup', 1); }); test('unencrypted file system is created correctly with default KMS', () => { @@ -67,9 +68,9 @@ test('unencrypted file system is created correctly with default KMS', () => { encrypted: false, }); // THEN - expectCDK(stack).notTo(haveResource('AWS::EFS::FileSystem', { - Encrypted: true, - })); + TemplateAssertions.fromStack(stack).hasResourceProperties('AWS::EFS::FileSystem', { + Encrypted: false, + }); }); test('encrypted file system is created correctly with default KMS', () => { @@ -79,9 +80,9 @@ test('encrypted file system is created correctly with default KMS', () => { encrypted: true, }); // THEN - expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { + TemplateAssertions.fromStack(stack).hasResourceProperties('AWS::EFS::FileSystem', { Encrypted: true, - })); + }); }); test('encrypted file system is created correctly with custom KMS', () => { @@ -101,7 +102,7 @@ test('encrypted file system is created correctly with custom KMS', () => { * in generated CDK, hence hardcoding the MD5 hash here for assertion. Assumption is that the path of the Key wont * change in this UT. Checked the unique id by generating the cloud formation stack. */ - expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { + TemplateAssertions.fromStack(stack).hasResourceProperties('AWS::EFS::FileSystem', { Encrypted: true, KmsKeyId: { 'Fn::GetAtt': [ @@ -109,7 +110,7 @@ test('encrypted file system is created correctly with custom KMS', () => { 'Arn', ], }, - })); + }); }); test('file system is created correctly with a life cycle property', () => { @@ -119,11 +120,11 @@ test('file system is created correctly with a life cycle property', () => { lifecyclePolicy: LifecyclePolicy.AFTER_7_DAYS, }); // THEN - expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { + TemplateAssertions.fromStack(stack).hasResourceProperties('AWS::EFS::FileSystem', { LifecyclePolicies: [{ TransitionToIA: 'AFTER_7_DAYS', }], - })); + }); }); test('file system is created correctly with performance mode', () => { @@ -133,9 +134,9 @@ test('file system is created correctly with performance mode', () => { performanceMode: PerformanceMode.MAX_IO, }); // THEN - expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { + TemplateAssertions.fromStack(stack).hasResourceProperties('AWS::EFS::FileSystem', { PerformanceMode: 'maxIO', - })); + }); }); test('file system is created correctly with bursting throughput mode', () => { @@ -145,9 +146,9 @@ test('file system is created correctly with bursting throughput mode', () => { throughputMode: ThroughputMode.BURSTING, }); // THEN - expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { + TemplateAssertions.fromStack(stack).hasResourceProperties('AWS::EFS::FileSystem', { ThroughputMode: 'bursting', - })); + }); }); test('Exception when throughput mode is set to PROVISIONED, but provisioned throughput is not set', () => { @@ -185,10 +186,10 @@ test('file system is created correctly with provisioned throughput mode', () => provisionedThroughputPerSecond: Size.mebibytes(5), }); // THEN - expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { + TemplateAssertions.fromStack(stack).hasResourceProperties('AWS::EFS::FileSystem', { ThroughputMode: 'provisioned', ProvisionedThroughputInMibps: 5, - })); + }); }); test('existing file system is imported correctly', () => { @@ -203,9 +204,9 @@ test('existing file system is imported correctly', () => { fs.connections.allowToAnyIpv4(ec2.Port.tcp(443)); // THEN - expectCDK(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', { + TemplateAssertions.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroupEgress', { GroupId: 'sg-123456789', - })); + }); }); test('support tags', () => { @@ -216,11 +217,11 @@ test('support tags', () => { Tags.of(fileSystem).add('Name', 'LookAtMeAndMyFancyTags'); // THEN - expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { + TemplateAssertions.fromStack(stack).hasResourceProperties('AWS::EFS::FileSystem', { FileSystemTags: [ { Key: 'Name', Value: 'LookAtMeAndMyFancyTags' }, ], - })); + }); }); test('file system is created correctly when given a name', () => { @@ -231,11 +232,11 @@ test('file system is created correctly when given a name', () => { }); // THEN - expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { + TemplateAssertions.fromStack(stack).hasResourceProperties('AWS::EFS::FileSystem', { FileSystemTags: [ { Key: 'Name', Value: 'MyNameableFileSystem' }, ], - })); + }); }); test('auto-named if none provided', () => { @@ -245,11 +246,11 @@ test('auto-named if none provided', () => { }); // THEN - expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { + TemplateAssertions.fromStack(stack).hasResourceProperties('AWS::EFS::FileSystem', { FileSystemTags: [ { Key: 'Name', Value: fileSystem.node.path }, ], - })); + }); }); test('removalPolicy is DESTROY', () => { @@ -257,10 +258,10 @@ test('removalPolicy is DESTROY', () => { new FileSystem(stack, 'EfsFileSystem', { vpc, removalPolicy: RemovalPolicy.DESTROY }); // THEN - expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { + TemplateAssertions.fromStack(stack).hasResourceDefinition('AWS::EFS::FileSystem', { DeletionPolicy: 'Delete', UpdateReplacePolicy: 'Delete', - }, ResourcePart.CompleteDefinition)); + }); }); test('can specify backup policy', () => { @@ -268,11 +269,11 @@ test('can specify backup policy', () => { new FileSystem(stack, 'EfsFileSystem', { vpc, enableAutomaticBackups: true }); // THEN - expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { + TemplateAssertions.fromStack(stack).hasResourceProperties('AWS::EFS::FileSystem', { BackupPolicy: { Status: 'ENABLED', }, - })); + }); }); test('can create when using a VPC with multiple subnets per availability zone', () => { @@ -286,5 +287,5 @@ test('can create when using a VPC with multiple subnets per availability zone', vpc: oneAzVpc, }); // make sure only one mount target is created. - expectCDK(stack).to(countResources('AWS::EFS::MountTarget', 1)); + TemplateAssertions.fromStack(stack).resourceCountIs('AWS::EFS::MountTarget', 1); }); diff --git a/packages/aws-cdk-lib/NOTICE b/packages/aws-cdk-lib/NOTICE index bd46bd848ec36..bf88ea022136c 100644 --- a/packages/aws-cdk-lib/NOTICE +++ b/packages/aws-cdk-lib/NOTICE @@ -372,3 +372,438 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------- + +** colors - https://www.npmjs.com/package/colors +Original Library + - Copyright (c) Marak Squires + +Additional Functionality + - Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------- + +** diff - https://www.npmjs.com/package/diff +Copyright (c) 2009-2015, Kevin Decker + +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Kevin Decker nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------- + +** fast-deep-equal - https://www.npmjs.com/package/fast-deep-equal +Copyright (c) 2017 Evgeny Poberezkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------- + +** string-width - https://www.npmjs.com/package/string-width +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** emoji-regex - https://www.npmjs.com/package/emoji-regex +Copyright Mathias Bynens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** is-fullwidth-code-point - https://www.npmjs.com/package/is-fullwidth-code-point +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** strip-ansi - https://www.npmjs.com/package/strip-ansi +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** ansi-regex - https://www.npmjs.com/package/ansi-regex +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** table - https://www.npmjs.com/package/table +Copyright (c) 2018, Gajus Kuizinas (http://gajus.com/) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Gajus Kuizinas (http://gajus.com/) nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ANUARY BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------- + +** ajv - https://www.npmjs.com/package/ajv +Copyright (c) 2015-2021 Evgeny Poberezkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------- + +** json-schema-traverse - https://www.npmjs.com/package/json-schema-traverse +Copyright (c) 2017 Evgeny Poberezkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------- + +** require-from-string - https://www.npmjs.com/package/require-from-string +Copyright (c) Vsevolod Strukchinsky (github.com/floatdrop) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------- + +** uri-js - https://www.npmjs.com/package/uri-js +Copyright 2011 Gary Court. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY GARY COURT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Gary Court. + +---------------- + +** lodash.truncate - https://www.npmjs.com/package/lodash +Copyright JS Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. + +---------------- + +** lodash.clonedeep - https://www.npmjs.com/package/lodash +Copyright JS Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. + +---------------- + +** slice-ansi - https://www.npmjs.com/package/slice-ansi +Copyright (c) DC +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** ansi-styles - https://www.npmjs.com/package/ansi-styles +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** color-convert - https://www.npmjs.com/package/color-convert +Copyright (c) 2011-2016 Heather Arthur . +Copyright (c) 2016-2021 Josh Junon . + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** color-name - https://www.npmjs.com/package/color-name +Copyright (c) 2015 Dmitry Ivanov + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** astral-regex - https://www.npmjs.com/package/astral-regex +Copyright (c) Kevin Mårtensson (github.com/kevva) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- \ No newline at end of file diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index 540f846976d90..05b8b779945bc 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -90,28 +90,39 @@ "bundledDependencies": [ "@balena/dockerignore", "case", + "colors", + "diff", + "fast-deep-equal", "fs-extra", "ignore", "jsonschema", "minimatch", "punycode", "semver", + "string-width", + "table", "yaml" ], "dependencies": { "@balena/dockerignore": "^1.0.2", "case": "1.6.3", + "colors": "^1.4.0", + "diff": "^5.0.0", + "fast-deep-equal": "^3.1.3", "fs-extra": "^9.1.0", "ignore": "^5.1.8", "jsonschema": "^1.4.0", "minimatch": "^3.0.4", "punycode": "^2.1.1", "semver": "^7.3.5", + "string-width": "^4.2.2", + "table": "^6.7.1", "yaml": "1.10.2" }, "devDependencies": { "@aws-cdk/alexa-ask": "0.0.0", "@aws-cdk/app-delivery": "0.0.0", + "@aws-cdk/assertions": "0.0.0", "@aws-cdk/assets": "0.0.0", "@aws-cdk/aws-accessanalyzer": "0.0.0", "@aws-cdk/aws-acmpca": "0.0.0", diff --git a/packages/decdk/package.json b/packages/decdk/package.json index d9896ef75e715..097705ddc1419 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -30,6 +30,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-cdk/alexa-ask": "0.0.0", + "@aws-cdk/assertions": "0.0.0", "@aws-cdk/assets": "0.0.0", "@aws-cdk/aws-accessanalyzer": "0.0.0", "@aws-cdk/aws-acmpca": "0.0.0", diff --git a/packages/monocdk/NOTICE b/packages/monocdk/NOTICE index bd46bd848ec36..bf88ea022136c 100644 --- a/packages/monocdk/NOTICE +++ b/packages/monocdk/NOTICE @@ -372,3 +372,438 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------- + +** colors - https://www.npmjs.com/package/colors +Original Library + - Copyright (c) Marak Squires + +Additional Functionality + - Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------- + +** diff - https://www.npmjs.com/package/diff +Copyright (c) 2009-2015, Kevin Decker + +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Kevin Decker nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------- + +** fast-deep-equal - https://www.npmjs.com/package/fast-deep-equal +Copyright (c) 2017 Evgeny Poberezkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------- + +** string-width - https://www.npmjs.com/package/string-width +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** emoji-regex - https://www.npmjs.com/package/emoji-regex +Copyright Mathias Bynens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** is-fullwidth-code-point - https://www.npmjs.com/package/is-fullwidth-code-point +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** strip-ansi - https://www.npmjs.com/package/strip-ansi +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** ansi-regex - https://www.npmjs.com/package/ansi-regex +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** table - https://www.npmjs.com/package/table +Copyright (c) 2018, Gajus Kuizinas (http://gajus.com/) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Gajus Kuizinas (http://gajus.com/) nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ANUARY BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------- + +** ajv - https://www.npmjs.com/package/ajv +Copyright (c) 2015-2021 Evgeny Poberezkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------- + +** json-schema-traverse - https://www.npmjs.com/package/json-schema-traverse +Copyright (c) 2017 Evgeny Poberezkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------- + +** require-from-string - https://www.npmjs.com/package/require-from-string +Copyright (c) Vsevolod Strukchinsky (github.com/floatdrop) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------- + +** uri-js - https://www.npmjs.com/package/uri-js +Copyright 2011 Gary Court. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY GARY COURT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Gary Court. + +---------------- + +** lodash.truncate - https://www.npmjs.com/package/lodash +Copyright JS Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. + +---------------- + +** lodash.clonedeep - https://www.npmjs.com/package/lodash +Copyright JS Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. + +---------------- + +** slice-ansi - https://www.npmjs.com/package/slice-ansi +Copyright (c) DC +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** ansi-styles - https://www.npmjs.com/package/ansi-styles +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** color-convert - https://www.npmjs.com/package/color-convert +Copyright (c) 2011-2016 Heather Arthur . +Copyright (c) 2016-2021 Josh Junon . + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** color-name - https://www.npmjs.com/package/color-name +Copyright (c) 2015 Dmitry Ivanov + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** astral-regex - https://www.npmjs.com/package/astral-regex +Copyright (c) Kevin Mårtensson (github.com/kevva) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- \ No newline at end of file diff --git a/packages/monocdk/package.json b/packages/monocdk/package.json index 8412449048e7d..0e27b2716c1b9 100644 --- a/packages/monocdk/package.json +++ b/packages/monocdk/package.json @@ -91,28 +91,39 @@ "bundledDependencies": [ "@balena/dockerignore", "case", + "colors", + "diff", + "fast-deep-equal", "fs-extra", "ignore", "jsonschema", "minimatch", "punycode", "semver", + "string-width", + "table", "yaml" ], "dependencies": { "@balena/dockerignore": "^1.0.2", "case": "1.6.3", + "colors": "^1.4.0", + "diff": "^5.0.0", + "fast-deep-equal": "^3.1.3", "fs-extra": "^9.1.0", "ignore": "^5.1.8", "jsonschema": "^1.4.0", "minimatch": "^3.0.4", "punycode": "^2.1.1", "semver": "^7.3.5", + "string-width": "^4.2.2", + "table": "^6.7.1", "yaml": "1.10.2" }, "devDependencies": { "@aws-cdk/alexa-ask": "0.0.0", "@aws-cdk/app-delivery": "0.0.0", + "@aws-cdk/assertions": "0.0.0", "@aws-cdk/assets": "0.0.0", "@aws-cdk/aws-accessanalyzer": "0.0.0", "@aws-cdk/aws-acmpca": "0.0.0", From abc9b379f94f162429b441204f292bdbb08f0a6e Mon Sep 17 00:00:00 2001 From: AWS CDK Automation <43080478+aws-cdk-automation@users.noreply.github.com> Date: Wed, 16 Jun 2021 20:27:08 +0300 Subject: [PATCH 32/34] chore: npm-check-updates && yarn upgrade (#15153) Ran npm-check-updates and yarn upgrade to keep the `yarn.lock` file up-to-date. --- package.json | 2 +- .../package.json | 2 +- .../aws-global-table-coordinator/package.json | 2 +- packages/@aws-cdk/aws-dynamodb/package.json | 2 +- .../@aws-cdk/aws-events-targets/package.json | 2 +- .../package.json | 2 +- .../@aws-cdk/aws-lambda-nodejs/package.json | 2 +- packages/@aws-cdk/aws-location/package.json | 2 +- packages/@aws-cdk/aws-logs/package.json | 2 +- .../@aws-cdk/custom-resources/package.json | 2 +- packages/aws-cdk/package.json | 6 +- packages/awslint/package.json | 4 +- tools/cdk-build-tools/package.json | 4 +- tools/eslint-plugin-cdk/package.json | 2 +- tools/pkglint/package.json | 4 +- tools/prlint/package.json | 2 +- yarn.lock | 671 ++++++++++-------- 17 files changed, 380 insertions(+), 333 deletions(-) diff --git a/package.json b/package.json index 62d30462466f0..d41454fa25423 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "conventional-changelog-cli": "^2.1.1", "fs-extra": "^9.1.0", "graceful-fs": "^4.2.6", - "jest-junit": "^12.1.0", + "jest-junit": "^12.2.0", "jsii-diff": "^1.30.0", "jsii-pacmak": "^1.30.0", "jsii-reflect": "^1.30.0", diff --git a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json index cbd5f76723186..2143497ac609d 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json +++ b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json @@ -33,7 +33,7 @@ "@types/sinon": "^9.0.11", "cdk-build-tools": "0.0.0", "aws-sdk": "^2.596.0", - "aws-sdk-mock": "^5.1.0", + "aws-sdk-mock": "^5.2.0", "eslint": "^7.28.0", "eslint-config-standard": "^14.1.1", "eslint-plugin-import": "^2.23.4", diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json index 18aea44e72667..526e836d80bd7 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json @@ -30,7 +30,7 @@ "license": "Apache-2.0", "devDependencies": { "aws-sdk": "^2.596.0", - "aws-sdk-mock": "^5.1.0", + "aws-sdk-mock": "^5.2.0", "eslint": "^7.28.0", "eslint-config-standard": "^14.1.1", "eslint-plugin-import": "^2.23.4", diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index 2e4d28cfc1c96..82e23dd8e6c86 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -77,7 +77,7 @@ "@types/jest": "^26.0.23", "@types/sinon": "^9.0.11", "aws-sdk": "^2.848.0", - "aws-sdk-mock": "^5.1.0", + "aws-sdk-mock": "^5.2.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index 4bf5bacea1d76..478812f94eb57 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -78,7 +78,7 @@ "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-batch": "0.0.0", "aws-sdk": "^2.848.0", - "aws-sdk-mock": "^5.1.0", + "aws-sdk-mock": "^5.2.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "jest": "^26.6.3", diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json b/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json index faa522a13a93a..06e55a7bb5b25 100644 --- a/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json @@ -73,7 +73,7 @@ "@types/jest": "^26.0.23", "@aws-cdk/assert-internal": "0.0.0", "aws-sdk": "^2.848.0", - "aws-sdk-mock": "^5.1.0", + "aws-sdk-mock": "^5.2.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "jest": "^26.6.3", diff --git a/packages/@aws-cdk/aws-lambda-nodejs/package.json b/packages/@aws-cdk/aws-lambda-nodejs/package.json index 884b5572b8d0c..04f9eca6babf7 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/package.json +++ b/packages/@aws-cdk/aws-lambda-nodejs/package.json @@ -69,7 +69,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "delay": "5.0.0", - "esbuild": "^0.12.8", + "esbuild": "^0.12.9", "pkglint": "0.0.0", "@aws-cdk/assert-internal": "0.0.0" }, diff --git a/packages/@aws-cdk/aws-location/package.json b/packages/@aws-cdk/aws-location/package.json index 14524818b5118..da398d6e16269 100644 --- a/packages/@aws-cdk/aws-location/package.json +++ b/packages/@aws-cdk/aws-location/package.json @@ -77,7 +77,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^26.0.22", + "@types/jest": "^26.0.23", "@aws-cdk/assert-internal": "0.0.0", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-logs/package.json b/packages/@aws-cdk/aws-logs/package.json index 5bf46fe85d1f8..425468e6a1743 100644 --- a/packages/@aws-cdk/aws-logs/package.json +++ b/packages/@aws-cdk/aws-logs/package.json @@ -76,7 +76,7 @@ "@types/aws-lambda": "^8.10.77", "@types/sinon": "^9.0.11", "aws-sdk": "^2.848.0", - "aws-sdk-mock": "^5.1.0", + "aws-sdk-mock": "^5.2.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index 9c5738b1a5def..aa599d3ca9a0c 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -81,7 +81,7 @@ "@types/fs-extra": "^8.1.1", "@types/sinon": "^9.0.11", "aws-sdk": "^2.848.0", - "aws-sdk-mock": "^5.1.0", + "aws-sdk-mock": "^5.2.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 0faa5548f0965..3abeba841617b 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -41,7 +41,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/core": "0.0.0", - "@octokit/rest": "^18.5.6", + "@octokit/rest": "^18.6.0", "@types/archiver": "^5.1.0", "@types/fs-extra": "^8.1.1", "@types/glob": "^7.1.3", @@ -56,10 +56,10 @@ "@types/uuid": "^8.3.0", "@types/wrap-ansi": "^3.0.0", "@types/yargs": "^15.0.13", - "aws-sdk-mock": "^5.1.0", + "aws-sdk-mock": "^5.2.0", "cdk-build-tools": "0.0.0", "jest": "^26.6.3", - "make-runnable": "^1.3.9", + "make-runnable": "^1.3.10", "mockery": "^2.1.0", "nock": "^13.1.0", "pkglint": "0.0.0", diff --git a/packages/awslint/package.json b/packages/awslint/package.json index 7712c8c2b9d50..bc62e89ec6f71 100644 --- a/packages/awslint/package.json +++ b/packages/awslint/package.json @@ -31,8 +31,8 @@ "@types/yargs": "^15.0.13", "pkglint": "0.0.0", "typescript": "~3.9.9", - "@typescript-eslint/eslint-plugin": "^4.26.1", - "@typescript-eslint/parser": "^4.26.1", + "@typescript-eslint/eslint-plugin": "^4.27.0", + "@typescript-eslint/parser": "^4.27.0", "eslint": "^7.28.0", "eslint-import-resolver-node": "^0.3.4", "eslint-import-resolver-typescript": "^2.4.0", diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index 20521aeb9009f..0d906487a92cd 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -42,8 +42,8 @@ "pkglint": "0.0.0" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^4.26.1", - "@typescript-eslint/parser": "^4.26.1", + "@typescript-eslint/eslint-plugin": "^4.27.0", + "@typescript-eslint/parser": "^4.27.0", "awslint": "0.0.0", "colors": "^1.4.0", "eslint": "^7.28.0", diff --git a/tools/eslint-plugin-cdk/package.json b/tools/eslint-plugin-cdk/package.json index 6ff23bf8454c4..6d67804baf495 100644 --- a/tools/eslint-plugin-cdk/package.json +++ b/tools/eslint-plugin-cdk/package.json @@ -24,7 +24,7 @@ "typescript": "~3.9.9" }, "dependencies": { - "@typescript-eslint/parser": "^4.26.1", + "@typescript-eslint/parser": "^4.27.0", "eslint": "^7.28.0", "fs-extra": "^9.1.0" }, diff --git a/tools/pkglint/package.json b/tools/pkglint/package.json index 6eea794a28378..ada2ae0bd7591 100644 --- a/tools/pkglint/package.json +++ b/tools/pkglint/package.json @@ -42,8 +42,8 @@ "@types/jest": "^26.0.23", "@types/semver": "^7.3.6", "@types/yargs": "^15.0.13", - "@typescript-eslint/eslint-plugin": "^4.26.1", - "@typescript-eslint/parser": "^4.26.1", + "@typescript-eslint/eslint-plugin": "^4.27.0", + "@typescript-eslint/parser": "^4.27.0", "eslint": "^7.28.0", "eslint-import-resolver-node": "^0.3.4", "eslint-import-resolver-typescript": "^2.4.0", diff --git a/tools/prlint/package.json b/tools/prlint/package.json index c45cde6336a3f..4aee420837b04 100644 --- a/tools/prlint/package.json +++ b/tools/prlint/package.json @@ -23,7 +23,7 @@ "@types/glob": "^7.1.3", "@types/jest": "^26.0.23", "jest": "^26.6.3", - "make-runnable": "^1.3.9", + "make-runnable": "^1.3.10", "typescript": "~3.9.9" }, "jest": { diff --git a/yarn.lock b/yarn.lock index 53f64291d4836..d55ad86cf466a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -30,32 +30,32 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" - integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" + integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== dependencies: - "@babel/highlight" "^7.12.13" + "@babel/highlight" "^7.14.5" -"@babel/compat-data@^7.14.4": - version "7.14.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.4.tgz#45720fe0cecf3fd42019e1d12cc3d27fadc98d58" - integrity sha512-i2wXrWQNkH6JplJQGn3Rd2I4Pij8GdHkXwHMxm+zV5YG/Jci+bCNrWZEWC4o+umiDkRrRs4dVzH3X4GP7vyjQQ== +"@babel/compat-data@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.5.tgz#8ef4c18e58e801c5c95d3c1c0f2874a2680fadea" + integrity sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w== "@babel/core@^7.1.0", "@babel/core@^7.7.5": - version "7.14.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.3.tgz#5395e30405f0776067fbd9cf0884f15bfb770a38" - integrity sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg== - dependencies: - "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.14.3" - "@babel/helper-compilation-targets" "^7.13.16" - "@babel/helper-module-transforms" "^7.14.2" - "@babel/helpers" "^7.14.0" - "@babel/parser" "^7.14.3" - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.14.2" - "@babel/types" "^7.14.2" + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.6.tgz#e0814ec1a950032ff16c13a2721de39a8416fcab" + integrity sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA== + dependencies: + "@babel/code-frame" "^7.14.5" + "@babel/generator" "^7.14.5" + "@babel/helper-compilation-targets" "^7.14.5" + "@babel/helper-module-transforms" "^7.14.5" + "@babel/helpers" "^7.14.6" + "@babel/parser" "^7.14.6" + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.14.5" + "@babel/types" "^7.14.5" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -63,137 +63,144 @@ semver "^6.3.0" source-map "^0.5.0" -"@babel/generator@^7.14.2", "@babel/generator@^7.14.3", "@babel/generator@^7.4.0": - version "7.14.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.3.tgz#0c2652d91f7bddab7cccc6ba8157e4f40dcedb91" - integrity sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA== +"@babel/generator@^7.14.5", "@babel/generator@^7.4.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.5.tgz#848d7b9f031caca9d0cd0af01b063f226f52d785" + integrity sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA== dependencies: - "@babel/types" "^7.14.2" + "@babel/types" "^7.14.5" jsesc "^2.5.1" source-map "^0.5.0" -"@babel/helper-compilation-targets@^7.13.16": - version "7.14.4" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.4.tgz#33ebd0ffc34248051ee2089350a929ab02f2a516" - integrity sha512-JgdzOYZ/qGaKTVkn5qEDV/SXAh8KcyUVkCoSWGN8T3bwrgd6m+/dJa2kVGi6RJYJgEYPBdZ84BZp9dUjNWkBaA== +"@babel/helper-compilation-targets@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz#7a99c5d0967911e972fe2c3411f7d5b498498ecf" + integrity sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw== dependencies: - "@babel/compat-data" "^7.14.4" - "@babel/helper-validator-option" "^7.12.17" + "@babel/compat-data" "^7.14.5" + "@babel/helper-validator-option" "^7.14.5" browserslist "^4.16.6" semver "^6.3.0" -"@babel/helper-function-name@^7.14.2": - version "7.14.2" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz#397688b590760b6ef7725b5f0860c82427ebaac2" - integrity sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ== - dependencies: - "@babel/helper-get-function-arity" "^7.12.13" - "@babel/template" "^7.12.13" - "@babel/types" "^7.14.2" - -"@babel/helper-get-function-arity@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz#bc63451d403a3b3082b97e1d8b3fe5bd4091e583" - integrity sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg== - dependencies: - "@babel/types" "^7.12.13" - -"@babel/helper-member-expression-to-functions@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz#dfe368f26d426a07299d8d6513821768216e6d72" - integrity sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw== - dependencies: - "@babel/types" "^7.13.12" - -"@babel/helper-module-imports@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz#c6a369a6f3621cb25da014078684da9196b61977" - integrity sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA== - dependencies: - "@babel/types" "^7.13.12" - -"@babel/helper-module-transforms@^7.14.2": - version "7.14.2" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz#ac1cc30ee47b945e3e0c4db12fa0c5389509dfe5" - integrity sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA== - dependencies: - "@babel/helper-module-imports" "^7.13.12" - "@babel/helper-replace-supers" "^7.13.12" - "@babel/helper-simple-access" "^7.13.12" - "@babel/helper-split-export-declaration" "^7.12.13" - "@babel/helper-validator-identifier" "^7.14.0" - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.14.2" - "@babel/types" "^7.14.2" - -"@babel/helper-optimise-call-expression@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz#5c02d171b4c8615b1e7163f888c1c81c30a2aaea" - integrity sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA== - dependencies: - "@babel/types" "^7.12.13" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.8.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af" - integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ== - -"@babel/helper-replace-supers@^7.13.12": - version "7.14.4" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.14.4.tgz#b2ab16875deecfff3ddfcd539bc315f72998d836" - integrity sha512-zZ7uHCWlxfEAAOVDYQpEf/uyi1dmeC7fX4nCf2iz9drnCwi1zvwXL3HwWWNXUQEJ1k23yVn3VbddiI9iJEXaTQ== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.13.12" - "@babel/helper-optimise-call-expression" "^7.12.13" - "@babel/traverse" "^7.14.2" - "@babel/types" "^7.14.4" - -"@babel/helper-simple-access@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz#dd6c538afb61819d205a012c31792a39c7a5eaf6" - integrity sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA== - dependencies: - "@babel/types" "^7.13.12" - -"@babel/helper-split-export-declaration@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz#e9430be00baf3e88b0e13e6f9d4eaf2136372b05" - integrity sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg== - dependencies: - "@babel/types" "^7.12.13" - -"@babel/helper-validator-identifier@^7.14.0": - version "7.14.0" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288" - integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A== - -"@babel/helper-validator-option@^7.12.17": - version "7.12.17" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831" - integrity sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw== - -"@babel/helpers@^7.14.0": - version "7.14.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.0.tgz#ea9b6be9478a13d6f961dbb5f36bf75e2f3b8f62" - integrity sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg== - dependencies: - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.14.0" - "@babel/types" "^7.14.0" - -"@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13": - version "7.14.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf" - integrity sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg== - dependencies: - "@babel/helper-validator-identifier" "^7.14.0" +"@babel/helper-function-name@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz#89e2c474972f15d8e233b52ee8c480e2cfcd50c4" + integrity sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ== + dependencies: + "@babel/helper-get-function-arity" "^7.14.5" + "@babel/template" "^7.14.5" + "@babel/types" "^7.14.5" + +"@babel/helper-get-function-arity@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz#25fbfa579b0937eee1f3b805ece4ce398c431815" + integrity sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-hoist-variables@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz#e0dd27c33a78e577d7c8884916a3e7ef1f7c7f8d" + integrity sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-member-expression-to-functions@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.5.tgz#d5c70e4ad13b402c95156c7a53568f504e2fb7b8" + integrity sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-module-imports@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz#6d1a44df6a38c957aa7c312da076429f11b422f3" + integrity sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-module-transforms@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz#7de42f10d789b423eb902ebd24031ca77cb1e10e" + integrity sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA== + dependencies: + "@babel/helper-module-imports" "^7.14.5" + "@babel/helper-replace-supers" "^7.14.5" + "@babel/helper-simple-access" "^7.14.5" + "@babel/helper-split-export-declaration" "^7.14.5" + "@babel/helper-validator-identifier" "^7.14.5" + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.14.5" + "@babel/types" "^7.14.5" + +"@babel/helper-optimise-call-expression@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz#f27395a8619e0665b3f0364cddb41c25d71b499c" + integrity sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.8.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" + integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== + +"@babel/helper-replace-supers@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz#0ecc0b03c41cd567b4024ea016134c28414abb94" + integrity sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.14.5" + "@babel/helper-optimise-call-expression" "^7.14.5" + "@babel/traverse" "^7.14.5" + "@babel/types" "^7.14.5" + +"@babel/helper-simple-access@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz#66ea85cf53ba0b4e588ba77fc813f53abcaa41c4" + integrity sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-split-export-declaration@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz#22b23a54ef51c2b7605d851930c1976dd0bc693a" + integrity sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-validator-identifier@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8" + integrity sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg== + +"@babel/helper-validator-option@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" + integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== + +"@babel/helpers@^7.14.6": + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.6.tgz#5b58306b95f1b47e2a0199434fa8658fa6c21635" + integrity sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA== + dependencies: + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.14.5" + "@babel/types" "^7.14.5" + +"@babel/highlight@^7.10.4", "@babel/highlight@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" + integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.5" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.14.2", "@babel/parser@^7.14.3", "@babel/parser@^7.4.3": - version "7.14.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.4.tgz#a5c560d6db6cd8e6ed342368dea8039232cbab18" - integrity sha512-ArliyUsWDUqEGfWcmzpGUzNfLxTdTp6WU4IuP6QFSp9gGfWS6boxFCkJSJ/L4+RG8z/FnIU3WxCk6hPL9SSWeA== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.5", "@babel/parser@^7.14.6", "@babel/parser@^7.4.3": + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.6.tgz#d85cc68ca3cac84eae384c06f032921f5227f4b2" + integrity sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -273,41 +280,42 @@ "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz#c5f0fa6e249f5b739727f923540cf7a806130178" - integrity sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/template@^7.12.13", "@babel/template@^7.3.3", "@babel/template@^7.4.0": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" - integrity sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA== - dependencies: - "@babel/code-frame" "^7.12.13" - "@babel/parser" "^7.12.13" - "@babel/types" "^7.12.13" - -"@babel/traverse@^7.1.0", "@babel/traverse@^7.14.0", "@babel/traverse@^7.14.2", "@babel/traverse@^7.4.3": - version "7.14.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.2.tgz#9201a8d912723a831c2679c7ebbf2fe1416d765b" - integrity sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA== - dependencies: - "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.14.2" - "@babel/helper-function-name" "^7.14.2" - "@babel/helper-split-export-declaration" "^7.12.13" - "@babel/parser" "^7.14.2" - "@babel/types" "^7.14.2" + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/template@^7.14.5", "@babel/template@^7.3.3", "@babel/template@^7.4.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4" + integrity sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g== + dependencies: + "@babel/code-frame" "^7.14.5" + "@babel/parser" "^7.14.5" + "@babel/types" "^7.14.5" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.14.5", "@babel/traverse@^7.4.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.5.tgz#c111b0f58afab4fea3d3385a406f692748c59870" + integrity sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg== + dependencies: + "@babel/code-frame" "^7.14.5" + "@babel/generator" "^7.14.5" + "@babel/helper-function-name" "^7.14.5" + "@babel/helper-hoist-variables" "^7.14.5" + "@babel/helper-split-export-declaration" "^7.14.5" + "@babel/parser" "^7.14.5" + "@babel/types" "^7.14.5" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.12.13", "@babel/types@^7.13.12", "@babel/types@^7.14.0", "@babel/types@^7.14.2", "@babel/types@^7.14.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.0": - version "7.14.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.4.tgz#bfd6980108168593b38b3eb48a24aa026b919bc0" - integrity sha512-lCj4aIs0xUefJFQnwwQv2Bxg7Omd6bgquZ6LGC+gGMh6/s5qDVfjuCMlDmYQ15SLsWHd9n+X3E75lKIhl5Lkiw== +"@babel/types@^7.0.0", "@babel/types@^7.14.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.5.tgz#3bb997ba829a2104cedb20689c4a5b8121d383ff" + integrity sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg== dependencies: - "@babel/helper-validator-identifier" "^7.14.0" + "@babel/helper-validator-identifier" "^7.14.5" to-fast-properties "^2.0.0" "@balena/dockerignore@^1.0.2": @@ -1294,41 +1302,41 @@ dependencies: "@octokit/types" "^6.0.3" -"@octokit/core@^3.2.3": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.4.0.tgz#b48aa27d755b339fe7550548b340dcc2b513b742" - integrity sha512-6/vlKPP8NF17cgYXqucdshWqmMZGXkuvtcrWCgU5NOI0Pl2GjlmZyWgBMrU8zJ3v2MJlM6++CiB45VKYmhiWWg== +"@octokit/core@^3.5.0": + version "3.5.1" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b" + integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw== dependencies: "@octokit/auth-token" "^2.4.4" "@octokit/graphql" "^4.5.8" - "@octokit/request" "^5.4.12" + "@octokit/request" "^5.6.0" "@octokit/request-error" "^2.0.5" "@octokit/types" "^6.0.3" before-after-hook "^2.2.0" universal-user-agent "^6.0.0" "@octokit/endpoint@^6.0.1": - version "6.0.11" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.11.tgz#082adc2aebca6dcefa1fb383f5efb3ed081949d1" - integrity sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ== + version "6.0.12" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658" + integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== dependencies: "@octokit/types" "^6.0.3" is-plain-object "^5.0.0" universal-user-agent "^6.0.0" "@octokit/graphql@^4.3.1", "@octokit/graphql@^4.5.8": - version "4.6.2" - resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.2.tgz#ec44abdfa87f2b9233282136ae33e4ba446a04e7" - integrity sha512-WmsIR1OzOr/3IqfG9JIczI8gMJUMzzyx5j0XXQ4YihHtKlQc+u35VpVoOXhlKAlaBntvry1WpAzPl/a+s3n89Q== + version "4.6.4" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.4.tgz#0c3f5bed440822182e972317122acb65d311a5ed" + integrity sha512-SWTdXsVheRmlotWNjKzPOb6Js6tjSqA2a8z9+glDJng0Aqjzti8MEWOtuT8ZSu6wHnci7LZNuarE87+WJBG4vg== dependencies: - "@octokit/request" "^5.3.0" + "@octokit/request" "^5.6.0" "@octokit/types" "^6.0.3" universal-user-agent "^6.0.0" -"@octokit/openapi-types@^7.2.3": - version "7.3.0" - resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-7.3.0.tgz#1d9ed79828513c57a95e6360b7c9b4749503e79d" - integrity sha512-o00X2FCLiEeXZkm1Ab5nvPUdVOlrpediwWZkpizUJ/xtZQsJ4FiQ2RB/dJEmb0Nk+NIz7zyDePcSCu/Y/0M3Ew== +"@octokit/openapi-types@^7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-7.3.2.tgz#065ce49b338043ec7f741316ce06afd4d459d944" + integrity sha512-oJhK/yhl9Gt430OrZOzAl2wJqR0No9445vmZ9Ey8GjUZUpwuu/vmEFP0TDhDXdpGDoxD6/EIFHJEcY8nHXpDTA== "@octokit/plugin-enterprise-rest@^6.0.1": version "6.0.1" @@ -1343,16 +1351,16 @@ "@octokit/types" "^2.0.1" "@octokit/plugin-paginate-rest@^2.6.2": - version "2.13.3" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.13.3.tgz#f0f1792230805108762d87906fb02d573b9e070a" - integrity sha512-46lptzM9lTeSmIBt/sVP/FLSTPGx6DCzAdSX3PfeJ3mTf4h9sGC26WpaQzMEq/Z44cOcmx8VsOhO+uEgE3cjYg== + version "2.13.5" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.13.5.tgz#e459f9b5dccbe0a53f039a355d5b80c0a2b0dc57" + integrity sha512-3WSAKBLa1RaR/7GG+LQR/tAZ9fp9H9waE9aPXallidyci9oZsfgsLn5M836d3LuDC6Fcym+2idRTBpssHZePVg== dependencies: - "@octokit/types" "^6.11.0" + "@octokit/types" "^6.13.0" "@octokit/plugin-request-log@^1.0.0", "@octokit/plugin-request-log@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.3.tgz#70a62be213e1edc04bb8897ee48c311482f9700d" - integrity sha512-4RFU4li238jMJAzLgAwkBAw+4Loile5haQMQr+uhFq27BmyJXcXSKvoQKqh0agsZEiUlW6iSv3FAgvmGkur7OQ== + version "1.0.4" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" + integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== "@octokit/plugin-rest-endpoint-methods@2.4.0": version "2.4.0" @@ -1379,22 +1387,22 @@ deprecation "^2.0.0" once "^1.4.0" -"@octokit/request-error@^2.0.0", "@octokit/request-error@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.5.tgz#72cc91edc870281ad583a42619256b380c600143" - integrity sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg== +"@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" + integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== dependencies: "@octokit/types" "^6.0.3" deprecation "^2.0.0" once "^1.4.0" -"@octokit/request@^5.2.0", "@octokit/request@^5.3.0", "@octokit/request@^5.4.12": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.5.0.tgz#6588c532255b8e71886cefa0d2b64b4ad73bf18c" - integrity sha512-jxbMLQdQ3heFMZUaTLSCqcKs2oAHEYh7SnLLXyxbZmlULExZ/RXai7QUWWFKowcGGPlCZuKTZg0gSKHWrfYEoQ== +"@octokit/request@^5.2.0", "@octokit/request@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.0.tgz#6084861b6e4fa21dc40c8e2a739ec5eff597e672" + integrity sha512-4cPp/N+NqmaGQwbh3vUsYqokQIzt7VjsgTYVXiwpUP2pxd5YiZB2XuTedbb0SPtv9XS7nzAKjAuQxmY8/aZkiA== dependencies: "@octokit/endpoint" "^6.0.1" - "@octokit/request-error" "^2.0.0" + "@octokit/request-error" "^2.1.0" "@octokit/types" "^6.16.1" is-plain-object "^5.0.0" node-fetch "^2.6.1" @@ -1422,12 +1430,12 @@ once "^1.4.0" universal-user-agent "^4.0.0" -"@octokit/rest@^18.1.0", "@octokit/rest@^18.5.6": - version "18.5.6" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.5.6.tgz#8c9a7c9329c7bbf478af20df78ddeab0d21f6d89" - integrity sha512-8HdG6ZjQdZytU6tCt8BQ2XLC7EJ5m4RrbyU/EARSkAM1/HP3ceOzMG/9atEfe17EDMer3IVdHWLedz2wDi73YQ== +"@octokit/rest@^18.1.0", "@octokit/rest@^18.6.0": + version "18.6.0" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.6.0.tgz#9a8457374c78c2773d3ab3f50aaffc62f3ed4f76" + integrity sha512-MdHuXHDJM7e5sUBe3K9tt7th0cs4csKU5Bb52LRi2oHAeIMrMZ4XqaTrEv660HoUPoM1iDlnj27Ab/Nh3MtwlA== dependencies: - "@octokit/core" "^3.2.3" + "@octokit/core" "^3.5.0" "@octokit/plugin-paginate-rest" "^2.6.2" "@octokit/plugin-request-log" "^1.0.2" "@octokit/plugin-rest-endpoint-methods" "5.3.1" @@ -1439,14 +1447,14 @@ dependencies: "@types/node" ">= 8" -"@octokit/types@^6.0.3", "@octokit/types@^6.11.0", "@octokit/types@^6.16.1", "@octokit/types@^6.16.2": - version "6.16.2" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.16.2.tgz#62242e0565a3eb99ca2fd376283fe78b4ea057b4" - integrity sha512-wWPSynU4oLy3i4KGyk+J1BLwRKyoeW2TwRHgwbDz17WtVFzSK2GOErGliruIx8c+MaYtHSYTx36DSmLNoNbtgA== +"@octokit/types@^6.0.3", "@octokit/types@^6.13.0", "@octokit/types@^6.16.1", "@octokit/types@^6.16.2": + version "6.16.4" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.16.4.tgz#d24f5e1bacd2fe96d61854b5bda0e88cf8288dfe" + integrity sha512-UxhWCdSzloULfUyamfOg4dJxV9B+XjgrIZscI0VCbp4eNrjmorGEw+4qdwcpTsu6DIrm9tQsFQS2pK5QkqQ04A== dependencies: - "@octokit/openapi-types" "^7.2.3" + "@octokit/openapi-types" "^7.3.2" -"@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1": +"@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1", "@sinonjs/commons@^1.8.3": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== @@ -1460,6 +1468,13 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@sinonjs/fake-timers@^7.0.4", "@sinonjs/fake-timers@^7.1.0": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5" + integrity sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@sinonjs/samsam@^5.3.1": version "5.3.1" resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.3.1.tgz#375a45fe6ed4e92fca2fb920e007c48232a6507f" @@ -1469,6 +1484,15 @@ lodash.get "^4.4.2" type-detect "^4.0.8" +"@sinonjs/samsam@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-6.0.2.tgz#a0117d823260f282c04bff5f8704bdc2ac6910bb" + integrity sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ== + dependencies: + "@sinonjs/commons" "^1.6.0" + lodash.get "^4.4.2" + type-detect "^4.0.8" + "@sinonjs/text-encoding@^0.7.1": version "0.7.1" resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" @@ -1675,9 +1699,9 @@ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/prettier@^2.0.0": - version "2.2.3" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.3.tgz#ef65165aea2924c9359205bf748865b8881753c0" - integrity sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.3.0.tgz#2e8332cc7363f887d32ec5496b207d26ba8052bb" + integrity sha512-hkc1DATxFLQo4VxPDpMH1gCkPpBbpOoJ/4nhuXw4n63/0R6bCpQECj4+K226UJ4JO/eJQz+1mC2I7JsWanAdQw== "@types/promptly@^3.0.1": version "3.0.1" @@ -1773,13 +1797,13 @@ resolved "https://registry.yarnpkg.com/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.4.tgz#445251eb00bd9c1e751f82c7c6bf4f714edfd464" integrity sha512-/emrKCfQMQmFCqRqqBJ0JueHBT06jBRM3e8OgnvDUcvuExONujIk2hFA5dNsN9Nt41ljGVDdChvCydATZ+KOZw== -"@typescript-eslint/eslint-plugin@^4.26.1": - version "4.26.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.26.1.tgz#b9c7313321cb837e2bf8bebe7acc2220659e67d3" - integrity sha512-aoIusj/8CR+xDWmZxARivZjbMBQTT9dImUtdZ8tVCVRXgBUuuZyM5Of5A9D9arQPxbi/0rlJLcuArclz/rCMJw== +"@typescript-eslint/eslint-plugin@^4.27.0": + version "4.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.27.0.tgz#0b7fc974e8bc9b2b5eb98ed51427b0be529b4ad0" + integrity sha512-DsLqxeUfLVNp3AO7PC3JyaddmEHTtI9qTSAs+RB6ja27QvIM0TA8Cizn1qcS6vOu+WDLFJzkwkgweiyFhssDdQ== dependencies: - "@typescript-eslint/experimental-utils" "4.26.1" - "@typescript-eslint/scope-manager" "4.26.1" + "@typescript-eslint/experimental-utils" "4.27.0" + "@typescript-eslint/scope-manager" "4.27.0" debug "^4.3.1" functional-red-black-tree "^1.0.1" lodash "^4.17.21" @@ -1787,60 +1811,60 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@4.26.1", "@typescript-eslint/experimental-utils@^4.0.1": - version "4.26.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.26.1.tgz#a35980a2390da9232aa206b27f620eab66e94142" - integrity sha512-sQHBugRhrXzRCs9PaGg6rowie4i8s/iD/DpTB+EXte8OMDfdCG5TvO73XlO9Wc/zi0uyN4qOmX9hIjQEyhnbmQ== +"@typescript-eslint/experimental-utils@4.27.0", "@typescript-eslint/experimental-utils@^4.0.1": + version "4.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.27.0.tgz#78192a616472d199f084eab8f10f962c0757cd1c" + integrity sha512-n5NlbnmzT2MXlyT+Y0Jf0gsmAQzCnQSWXKy4RGSXVStjDvS5we9IWbh7qRVKdGcxT0WYlgcCYUK/HRg7xFhvjQ== dependencies: "@types/json-schema" "^7.0.7" - "@typescript-eslint/scope-manager" "4.26.1" - "@typescript-eslint/types" "4.26.1" - "@typescript-eslint/typescript-estree" "4.26.1" + "@typescript-eslint/scope-manager" "4.27.0" + "@typescript-eslint/types" "4.27.0" + "@typescript-eslint/typescript-estree" "4.27.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/parser@^4.26.1": - version "4.26.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.26.1.tgz#cecfdd5eb7a5c13aabce1c1cfd7fbafb5a0f1e8e" - integrity sha512-q7F3zSo/nU6YJpPJvQveVlIIzx9/wu75lr6oDbDzoeIRWxpoc/HQ43G4rmMoCc5my/3uSj2VEpg/D83LYZF5HQ== +"@typescript-eslint/parser@^4.27.0": + version "4.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.27.0.tgz#85447e573364bce4c46c7f64abaa4985aadf5a94" + integrity sha512-XpbxL+M+gClmJcJ5kHnUpBGmlGdgNvy6cehgR6ufyxkEJMGP25tZKCaKyC0W/JVpuhU3VU1RBn7SYUPKSMqQvQ== dependencies: - "@typescript-eslint/scope-manager" "4.26.1" - "@typescript-eslint/types" "4.26.1" - "@typescript-eslint/typescript-estree" "4.26.1" + "@typescript-eslint/scope-manager" "4.27.0" + "@typescript-eslint/types" "4.27.0" + "@typescript-eslint/typescript-estree" "4.27.0" debug "^4.3.1" -"@typescript-eslint/scope-manager@4.26.1": - version "4.26.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.26.1.tgz#075a74a15ff33ee3a7ed33e5fce16ee86689f662" - integrity sha512-TW1X2p62FQ8Rlne+WEShyd7ac2LA6o27S9i131W4NwDSfyeVlQWhw8ylldNNS8JG6oJB9Ha9Xyc+IUcqipvheQ== +"@typescript-eslint/scope-manager@4.27.0": + version "4.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.27.0.tgz#b0b1de2b35aaf7f532e89c8e81d0fa298cae327d" + integrity sha512-DY73jK6SEH6UDdzc6maF19AHQJBFVRf6fgAXHPXCGEmpqD4vYgPEzqpFz1lf/daSbOcMpPPj9tyXXDPW2XReAw== dependencies: - "@typescript-eslint/types" "4.26.1" - "@typescript-eslint/visitor-keys" "4.26.1" + "@typescript-eslint/types" "4.27.0" + "@typescript-eslint/visitor-keys" "4.27.0" -"@typescript-eslint/types@4.26.1": - version "4.26.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.26.1.tgz#9e7c523f73c34b04a765e4167ca5650436ef1d38" - integrity sha512-STyMPxR3cS+LaNvS8yK15rb8Y0iL0tFXq0uyl6gY45glyI7w0CsyqyEXl/Fa0JlQy+pVANeK3sbwPneCbWE7yg== +"@typescript-eslint/types@4.27.0": + version "4.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.27.0.tgz#712b408519ed699baff69086bc59cd2fc13df8d8" + integrity sha512-I4ps3SCPFCKclRcvnsVA/7sWzh7naaM/b4pBO2hVxnM3wrU51Lveybdw5WoIktU/V4KfXrTt94V9b065b/0+wA== -"@typescript-eslint/typescript-estree@4.26.1": - version "4.26.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.26.1.tgz#b2ce2e789233d62283fae2c16baabd4f1dbc9633" - integrity sha512-l3ZXob+h0NQzz80lBGaykdScYaiEbFqznEs99uwzm8fPHhDjwaBFfQkjUC/slw6Sm7npFL8qrGEAMxcfBsBJUg== +"@typescript-eslint/typescript-estree@4.27.0": + version "4.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.27.0.tgz#189a7b9f1d0717d5cccdcc17247692dedf7a09da" + integrity sha512-KH03GUsUj41sRLLEy2JHstnezgpS5VNhrJouRdmh6yNdQ+yl8w5LrSwBkExM+jWwCJa7Ct2c8yl8NdtNRyQO6g== dependencies: - "@typescript-eslint/types" "4.26.1" - "@typescript-eslint/visitor-keys" "4.26.1" + "@typescript-eslint/types" "4.27.0" + "@typescript-eslint/visitor-keys" "4.27.0" debug "^4.3.1" globby "^11.0.3" is-glob "^4.0.1" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@4.26.1": - version "4.26.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.26.1.tgz#0d55ea735cb0d8903b198017d6d4f518fdaac546" - integrity sha512-IGouNSSd+6x/fHtYRyLOM6/C+QxMDzWlDtN41ea+flWuSF9g02iqcIlX8wM53JkfljoIjP0U+yp7SiTS1onEkw== +"@typescript-eslint/visitor-keys@4.27.0": + version "4.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.27.0.tgz#f56138b993ec822793e7ebcfac6ffdce0a60cb81" + integrity sha512-es0GRYNZp0ieckZ938cEANfEhsfHrzuLrePukLKtY3/KPXcq1Xd555Mno9/GOgXhKzn0QfkDLVgqWO3dGY80bg== dependencies: - "@typescript-eslint/types" "4.26.1" + "@typescript-eslint/types" "4.27.0" eslint-visitor-keys "^2.0.0" "@yarnpkg/lockfile@^1.1.0": @@ -1890,9 +1914,9 @@ acorn@^7.1.1, acorn@^7.4.0: integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.2.4: - version "8.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.3.0.tgz#1193f9b96c4e8232f00b11a9edff81b2c8b98b88" - integrity sha512-tqPKHZ5CaBJw0Xmy0ZZvLs1qTV+BNFSyvn77ASXkpBNfIRk8ev26fKrD9iLGwGA9zedPao52GSHzq8lyZG0NUw== + version "8.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.0.tgz#af53266e698d7cffa416714b503066a82221be60" + integrity sha512-ULr0LDaEqQrMFGyQ3bhJkLsbtrQ8QibAseGZeaSUiT/6zb9IvIkomWHJIvgvwad+hinRAgsI51JcWk2yvwyL+w== add-stream@^1.0.0: version "1.0.0" @@ -2222,19 +2246,19 @@ available-typed-arrays@^1.0.2: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz#9e0ae84ecff20caae6a94a1c3bc39b955649b7a9" integrity sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA== -aws-sdk-mock@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/aws-sdk-mock/-/aws-sdk-mock-5.1.0.tgz#6f2c0bd670d7f378c906a8dd806f812124db71aa" - integrity sha512-Wa5eCSo8HX0Snqb7FdBylaXMmfrAWoWZ+d7MFhiYsgHPvNvMEGjV945FF2qqE1U0Tolr1ALzik1fcwgaOhqUWQ== +aws-sdk-mock@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/aws-sdk-mock/-/aws-sdk-mock-5.2.0.tgz#e1d40ae287aa2e8bd80344381d2370448f846aac" + integrity sha512-1d2iU5s4bFtUw95CHVnTh/khsn3Vt90yJE5VlFz+8EsWqH2F9pwHXsdw2VJEjUc/2M84IUdGQO3E9LWs3Yya0Q== dependencies: - aws-sdk "^2.637.0" - sinon "^9.0.1" + aws-sdk "^2.928.0" + sinon "^11.1.1" traverse "^0.6.6" -aws-sdk@^2.596.0, aws-sdk@^2.637.0, aws-sdk@^2.848.0: - version "2.924.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.924.0.tgz#daefcd11729638d9c9279633a0cc5ba1c98fee64" - integrity sha512-EwJmZDNhEY1/hrihile8+EdrYrT5VKcLuL5F+OA9L+AYWxNou0i4fP36N5KFtMikkAGB31qhAuRDPcr132RnUw== +aws-sdk@^2.596.0, aws-sdk@^2.848.0, aws-sdk@^2.928.0: + version "2.929.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.929.0.tgz#fa0bfc0e97f0b38b2086f83eb1546d946b6215c9" + integrity sha512-rJ36UbkGhB8qhR4eH0D+TgNPA6wwwKh4885fN/v9uToNd8/Fz8HdgNLw9uy0QYOFOgqK99eWfpMGQyOR6DL+Bg== dependencies: buffer "4.9.2" events "1.1.1" @@ -2595,9 +2619,9 @@ camelcase@^6.0.0, camelcase@^6.2.0: integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== caniuse-lite@^1.0.30001219: - version "1.0.30001236" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001236.tgz#0a80de4cdf62e1770bb46a30d884fc8d633e3958" - integrity sha512-o0PRQSrSCGJKCPZcgMzl5fUaj5xHe8qA2m4QRvnyY4e1lITqoNkr7q/Oh1NcpGSy0Th97UZ35yoKcINPoq7YOQ== + version "1.0.30001237" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001237.tgz#4b7783661515b8e7151fc6376cfd97f0e427b9e5" + integrity sha512-pDHgRndit6p1NR2GhzMbQ6CkRrp4VKuSsqbcLeOQppYPKOYkKT/6ZvZDvKJUqcmtyWIAHuZq3SVS2vc1egCZzw== capture-exit@^2.0.0: version "2.0.0" @@ -2934,9 +2958,9 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= constructs@^3.3.69: - version "3.3.77" - resolved "https://registry.yarnpkg.com/constructs/-/constructs-3.3.77.tgz#7c2d8eb9758a9a0c1e7f194410a19a1f6e474008" - integrity sha512-A9xWepIkiED0baaEWxokUvRnPrxcAwoCsyIPDAJ3VqQ90juiIHQ0gkW9GX0nG6c+Go5l1dL5FZIFZBeayx+xIg== + version "3.3.82" + resolved "https://registry.yarnpkg.com/constructs/-/constructs-3.3.82.tgz#8ac46a925ffc98b52f5b7c8a041e3e8916418659" + integrity sha512-pOJAwmKf2yI8dF3fNK2akbl/xcq1xB3yhuu/ryDw7RESdrdUWbV7Bd013PI1brnXB3gNpUKsrKjXbnpfEZUrgA== conventional-changelog-angular@^5.0.12: version "5.0.12" @@ -3613,9 +3637,9 @@ ejs@^2.5.2: integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== electron-to-chromium@^1.3.723: - version "1.3.750" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.750.tgz#7e5ef6f478316b0bd656af5942fe502610e97eaf" - integrity sha512-Eqy9eHNepZxJXT+Pc5++zvEi5nQ6AGikwFYDCYwXUFBr+ynJ6pDG7MzZmwGYCIuXShLJM0n4bq+aoKDmvSGJ8A== + version "1.3.752" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.752.tgz#0728587f1b9b970ec9ffad932496429aef750d09" + integrity sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A== emittery@^0.7.1: version "0.7.2" @@ -3735,10 +3759,10 @@ es6-error@^4.0.1: resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -esbuild@^0.12.8: - version "0.12.8" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.8.tgz#ac90da77cb3bfbf49ab815200bcef7ffe1a3348f" - integrity sha512-sx/LwlP/SWTGsd9G4RlOPrXnIihAJ2xwBUmzoqe2nWwbXORMQWtAGNJNYLBJJqa3e9PWvVzxdrtyFZJcr7D87g== +esbuild@^0.12.9: + version "0.12.9" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.9.tgz#bed4e7087c286cd81d975631f77d47feb1660070" + integrity sha512-MWRhAbMOJ9RJygCrt778rz/qNYgA4ZVj6aXnNPxFjs7PmIpb0fuB9Gmg5uWrr6n++XKwwm/RmSz6RR5JL2Ocsw== escalade@^3.1.1: version "3.1.1" @@ -4706,9 +4730,9 @@ globals@^13.6.0, globals@^13.9.0: type-fest "^0.20.2" globby@^11.0.2, globby@^11.0.3: - version "11.0.3" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" - integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" @@ -5687,14 +5711,14 @@ jest-junit@^11.1.0: uuid "^3.3.3" xml "^1.0.1" -jest-junit@^12.1.0: - version "12.1.0" - resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-12.1.0.tgz#f27173529e7f8f10eac37beb30f8b9bc97e8f3c3" - integrity sha512-Z45INyzEAqTkCHX/hGCPgVFfZP3hQVgI68CgoEwkCiBuxETsPsniq5yPd8oxbMMHtDCpUlxXzoq7jY35dcuLKw== +jest-junit@^12.2.0: + version "12.2.0" + resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-12.2.0.tgz#cff7f9516e84f8e30f6bdea04cd84db6b095a376" + integrity sha512-ecGzF3KEQwLbMP5xMO7wqmgmyZlY/5yWDvgE/vFa+/uIT0KsU5nluf0D2fjIlOKB+tb6DiuSSpZuGpsmwbf7Fw== dependencies: mkdirp "^1.0.4" strip-ansi "^5.2.0" - uuid "^3.3.3" + uuid "^8.3.2" xml "^1.0.1" jest-leak-detector@^26.6.2: @@ -6606,10 +6630,10 @@ make-fetch-happen@^9.0.1: socks-proxy-agent "^5.0.0" ssri "^8.0.0" -make-runnable@^1.3.9: - version "1.3.9" - resolved "https://registry.yarnpkg.com/make-runnable/-/make-runnable-1.3.9.tgz#924bf6b28c4f1524391ec34ec1f14fc24aac3af8" - integrity sha512-yiAvZX8hAEXOcYP6ibvnjWcmxZVQ2aBZMQ2148IEPFga0ODr3htJfc+bdeUQfQCUuVe2lrY3VmajPAVSDZG8xw== +make-runnable@^1.3.10: + version "1.3.10" + resolved "https://registry.yarnpkg.com/make-runnable/-/make-runnable-1.3.10.tgz#c89f89e35ffd2dd88fd0ec1b06650e8357577c87" + integrity sha512-ec9hxTJip4ncG3TqZrkoR69oKdxFyJDq40A4sGNwGYVtl4Q10V4BhqnTGLUyJxQIxobhTqwxkgEFbGh77RmV7A== dependencies: bluebird "^3.5.0" yargs "^16.2.0" @@ -7040,6 +7064,17 @@ nise@^4.0.4: just-extend "^4.0.2" path-to-regexp "^1.7.0" +nise@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.0.tgz#713ef3ed138252daef20ec035ab62b7a28be645c" + integrity sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@sinonjs/fake-timers" "^7.0.4" + "@sinonjs/text-encoding" "^0.7.1" + just-extend "^4.0.2" + path-to-regexp "^1.7.0" + nock@^13.1.0: version "13.1.0" resolved "https://registry.yarnpkg.com/nock/-/nock-13.1.0.tgz#41c8ce8b35ab7d618c4cbf40de1d5bce319979ba" @@ -7216,9 +7251,9 @@ npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1: integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== npm-package-arg@^8.0.0, npm-package-arg@^8.0.1, npm-package-arg@^8.1.0, npm-package-arg@^8.1.2: - version "8.1.4" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.4.tgz#8001cdbc4363997b8ef6c6cf7aaf543c5805879d" - integrity sha512-xLokoCFqj/rPdr3LvcdDL6Kj6ipXGEDHD/QGpzwU6/pibYUOXmp5DBmg76yukFyx4ZDbrXNOTn+BPyd8TD4Jlw== + version "8.1.5" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.5.tgz#3369b2d5fe8fdc674baa7f1786514ddc15466e44" + integrity sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q== dependencies: hosted-git-info "^4.0.1" semver "^7.3.4" @@ -8392,9 +8427,9 @@ regexp.prototype.flags@^1.3.0: define-properties "^1.1.3" regexpp@^3.0.0, regexpp@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" - integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== release-zalgo@^1.0.0: version "1.0.0" @@ -8724,7 +8759,19 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== -sinon@^9.0.1, sinon@^9.2.4: +sinon@^11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-11.1.1.tgz#99a295a8b6f0fadbbb7e004076f3ae54fc6eab91" + integrity sha512-ZSSmlkSyhUWbkF01Z9tEbxZLF/5tRC9eojCdFh33gtQaP7ITQVaMWQHGuFM7Cuf/KEfihuh1tTl3/ABju3AQMg== + dependencies: + "@sinonjs/commons" "^1.8.3" + "@sinonjs/fake-timers" "^7.1.0" + "@sinonjs/samsam" "^6.0.2" + diff "^5.0.0" + nise "^5.1.0" + supports-color "^7.2.0" + +sinon@^9.2.4: version "9.2.4" resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.2.4.tgz#e55af4d3b174a4443a8762fa8421c2976683752b" integrity sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg== @@ -9206,7 +9253,7 @@ supports-color@^6.1.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0, supports-color@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -9617,9 +9664,9 @@ tslib@^1.8.1, tslib@^1.9.0: integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tslib@^2.0.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" - integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== + version "2.3.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" + integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== tsutils@^3.21.0: version "3.21.0" @@ -10157,9 +10204,9 @@ write-pkg@^4.0.0: write-json-file "^3.2.0" ws@^7.4.5: - version "7.4.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" - integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + version "7.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.0.tgz#0033bafea031fb9df041b2026fc72a571ca44691" + integrity sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw== xml-js@^1.6.11: version "1.6.11" From 7d218c22e5cbfeaf19b1573b537fc34dd07f7b22 Mon Sep 17 00:00:00 2001 From: Michael Brewer Date: Wed, 16 Jun 2021 11:31:02 -0700 Subject: [PATCH 33/34] feat(s3): notifications to existing buckets (#15158) An update custom resource changes to support adding notifications to an existing S3 bucket Resolves #2004 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../test/integ.s3.expected.json | 17 +- .../test/integ.notifications.expected.json | 96 ++++++- .../test/integ.notifications.ts | 3 + .../integ.bucket-notifications.expected.json | 30 ++- .../test/notifications.test.ts | 30 +++ ...teg.sns-bucket-notifications.expected.json | 17 +- .../integ.bucket-notifications.expected.json | 30 ++- packages/@aws-cdk/aws-s3/README.md | 9 + packages/@aws-cdk/aws-s3/lib/bucket.ts | 154 +++++++---- .../notifications-resource/lambda/index.py | 101 +++++++ .../notifications-resource-handler.ts | 112 ++------ .../notifications-resource.ts | 24 +- .../notifications-resource-handler/Dockerfile | 9 + .../notifications-resource-handler/test.sh | 26 ++ .../test_index.py | 246 ++++++++++++++++++ .../notifications-resource.lambda.test.ts | 8 + 16 files changed, 695 insertions(+), 217 deletions(-) create mode 100644 packages/@aws-cdk/aws-s3/lib/notifications-resource/lambda/index.py create mode 100644 packages/@aws-cdk/aws-s3/test/notifications-resource-handler/Dockerfile create mode 100755 packages/@aws-cdk/aws-s3/test/notifications-resource-handler/test.sh create mode 100644 packages/@aws-cdk/aws-s3/test/notifications-resource-handler/test_index.py create mode 100644 packages/@aws-cdk/aws-s3/test/notifications-resource.lambda.test.ts 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 96f5a3e6a63d7..7425814364cff 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 @@ -50,11 +50,6 @@ "FServiceRole3AC82EE1" ] }, - "B08E7C7AF": { - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "BNotificationsEB8DA980": { "Type": "Custom::S3BucketNotifications", "Properties": { @@ -91,12 +86,18 @@ } } ] - } + }, + "Managed": true }, "DependsOn": [ "BAllowBucketNotificationsTolambdaeventsources3F741608059EF9F709" ] }, + "B08E7C7AF": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "BAllowBucketNotificationsTolambdaeventsources3F741608059EF9F709": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -176,7 +177,7 @@ "Properties": { "Description": "AWS CloudFormation handler for \"Custom::S3BucketNotifications\" resources (@aws-cdk/aws-s3)", "Code": { - "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require('https');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require('url');\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration,\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse('FAILED', err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse('SUCCESS');\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // eslint-disable-next-line max-len\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error message as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: 'PUT',\n headers: {\n 'content-type': '',\n 'content-length': responseBody.length,\n },\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on('error', (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" + "ZipFile": "import boto3 # type: ignore\nimport json\nimport logging\nimport urllib.request\n\ns3 = boto3.client(\"s3\")\n\nCONFIGURATION_TYPES = [\"TopicConfigurations\", \"QueueConfigurations\", \"LambdaFunctionConfigurations\"]\n\ndef handler(event: dict, context):\n response_status = \"SUCCESS\"\n error_message = \"\"\n try:\n props = event[\"ResourceProperties\"]\n bucket = props[\"BucketName\"]\n notification_configuration = props[\"NotificationConfiguration\"]\n request_type = event[\"RequestType\"]\n managed = props.get('Managed', 'true').lower() == 'true'\n stack_id = event['StackId']\n\n if managed:\n config = handle_managed(request_type, notification_configuration)\n else:\n config = handle_unmanaged(bucket, stack_id, request_type, notification_configuration)\n\n put_bucket_notification_configuration(bucket, config)\n except Exception as e:\n logging.exception(\"Failed to put bucket notification configuration\")\n response_status = \"FAILED\"\n error_message = f\"Error: {str(e)}. \"\n finally:\n submit_response(event, context, response_status, error_message)\n\n\ndef handle_managed(request_type, notification_configuration):\n if request_type == 'Delete':\n return {}\n return notification_configuration\n\n\ndef handle_unmanaged(bucket, stack_id, request_type, notification_configuration):\n\n # find external notifications\n external_notifications = find_external_notifications(bucket, stack_id)\n\n # if delete, that's all we need\n if request_type == 'Delete':\n return external_notifications\n\n def with_id(notification):\n notification['Id'] = f\"{stack_id}-{hash(json.dumps(notification, sort_keys=True))}\"\n return notification\n\n # otherwise, merge external with incoming config and augment with id\n notifications = {}\n for t in CONFIGURATION_TYPES:\n external = external_notifications.get(t, [])\n incoming = [with_id(n) for n in notification_configuration.get(t, [])]\n notifications[t] = external + incoming\n return notifications\n\n\ndef find_external_notifications(bucket, stack_id):\n existing_notifications = get_bucket_notification_configuration(bucket)\n external_notifications = {}\n for t in CONFIGURATION_TYPES:\n # if the notification was created by us, we know what id to expect\n # so we can filter by it.\n external_notifications[t] = [n for n in existing_notifications.get(t, []) if not n['Id'].startswith(f\"{stack_id}-\")]\n\n return external_notifications\n\n\ndef get_bucket_notification_configuration(bucket):\n return s3.get_bucket_notification_configuration(Bucket=bucket)\n\n\ndef put_bucket_notification_configuration(bucket, notification_configuration):\n s3.put_bucket_notification_configuration(Bucket=bucket, NotificationConfiguration=notification_configuration)\n\n\ndef submit_response(event: dict, context, response_status: str, error_message: str):\n response_body = json.dumps(\n {\n \"Status\": response_status,\n \"Reason\": f\"{error_message}See the details in CloudWatch Log Stream: {context.log_stream_name}\",\n \"PhysicalResourceId\": event.get(\"PhysicalResourceId\") or event[\"LogicalResourceId\"],\n \"StackId\": event[\"StackId\"],\n \"RequestId\": event[\"RequestId\"],\n \"LogicalResourceId\": event[\"LogicalResourceId\"],\n \"NoEcho\": False,\n }\n ).encode(\"utf-8\")\n headers = {\"content-type\": \"\", \"content-length\": str(len(response_body))}\n try:\n req = urllib.request.Request(url=event[\"ResponseURL\"], headers=headers, data=response_body, method=\"PUT\")\n with urllib.request.urlopen(req) as response:\n print(response.read().decode(\"utf-8\"))\n print(\"Status code: \" + response.reason)\n except Exception as e:\n print(\"send(..) failed executing request.urlopen(..): \" + str(e))\n" }, "Handler": "index.handler", "Role": { @@ -185,7 +186,7 @@ "Arn" ] }, - "Runtime": "nodejs12.x", + "Runtime": "python3.8", "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 f8b1a350486f9..c24d991f31ed5 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 @@ -1,10 +1,5 @@ { "Resources": { - "Bucket83908E77": { - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "BucketNotifications8F2E257D": { "Type": "Custom::S3BucketNotifications", "Properties": { @@ -46,7 +41,8 @@ } } ] - } + }, + "Managed": true }, "DependsOn": [ "TopicPolicyA1747468", @@ -55,6 +51,11 @@ "Topic3DEAE47A7" ] }, + "Bucket83908E77": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "TopicBFC7AF6E": { "Type": "AWS::SNS::Topic" }, @@ -143,6 +144,36 @@ "Ref": "Topic3DEAE47A7" }, "Sid": "1" + }, + { + "Action": "sns:Publish", + "Condition": { + "ArnLike": { + "aws:SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "Bucket25524B414" + } + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "s3.amazonaws.com" + }, + "Resource": { + "Ref": "Topic3DEAE47A7" + }, + "Sid": "2" } ], "Version": "2012-10-17" @@ -194,6 +225,11 @@ "Action": "s3:PutBucketNotification", "Effect": "Allow", "Resource": "*" + }, + { + "Action": "s3:GetBucketNotification", + "Effect": "Allow", + "Resource": "*" } ], "Version": "2012-10-17" @@ -211,7 +247,7 @@ "Properties": { "Description": "AWS CloudFormation handler for \"Custom::S3BucketNotifications\" resources (@aws-cdk/aws-s3)", "Code": { - "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require('https');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require('url');\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration,\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse('FAILED', err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse('SUCCESS');\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // eslint-disable-next-line max-len\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error message as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: 'PUT',\n headers: {\n 'content-type': '',\n 'content-length': responseBody.length,\n },\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on('error', (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" + "ZipFile": "import boto3 # type: ignore\nimport json\nimport logging\nimport urllib.request\n\ns3 = boto3.client(\"s3\")\n\nCONFIGURATION_TYPES = [\"TopicConfigurations\", \"QueueConfigurations\", \"LambdaFunctionConfigurations\"]\n\ndef handler(event: dict, context):\n response_status = \"SUCCESS\"\n error_message = \"\"\n try:\n props = event[\"ResourceProperties\"]\n bucket = props[\"BucketName\"]\n notification_configuration = props[\"NotificationConfiguration\"]\n request_type = event[\"RequestType\"]\n managed = props.get('Managed', 'true').lower() == 'true'\n stack_id = event['StackId']\n\n if managed:\n config = handle_managed(request_type, notification_configuration)\n else:\n config = handle_unmanaged(bucket, stack_id, request_type, notification_configuration)\n\n put_bucket_notification_configuration(bucket, config)\n except Exception as e:\n logging.exception(\"Failed to put bucket notification configuration\")\n response_status = \"FAILED\"\n error_message = f\"Error: {str(e)}. \"\n finally:\n submit_response(event, context, response_status, error_message)\n\n\ndef handle_managed(request_type, notification_configuration):\n if request_type == 'Delete':\n return {}\n return notification_configuration\n\n\ndef handle_unmanaged(bucket, stack_id, request_type, notification_configuration):\n\n # find external notifications\n external_notifications = find_external_notifications(bucket, stack_id)\n\n # if delete, that's all we need\n if request_type == 'Delete':\n return external_notifications\n\n def with_id(notification):\n notification['Id'] = f\"{stack_id}-{hash(json.dumps(notification, sort_keys=True))}\"\n return notification\n\n # otherwise, merge external with incoming config and augment with id\n notifications = {}\n for t in CONFIGURATION_TYPES:\n external = external_notifications.get(t, [])\n incoming = [with_id(n) for n in notification_configuration.get(t, [])]\n notifications[t] = external + incoming\n return notifications\n\n\ndef find_external_notifications(bucket, stack_id):\n existing_notifications = get_bucket_notification_configuration(bucket)\n external_notifications = {}\n for t in CONFIGURATION_TYPES:\n # if the notification was created by us, we know what id to expect\n # so we can filter by it.\n external_notifications[t] = [n for n in existing_notifications.get(t, []) if not n['Id'].startswith(f\"{stack_id}-\")]\n\n return external_notifications\n\n\ndef get_bucket_notification_configuration(bucket):\n return s3.get_bucket_notification_configuration(Bucket=bucket)\n\n\ndef put_bucket_notification_configuration(bucket, notification_configuration):\n s3.put_bucket_notification_configuration(Bucket=bucket, NotificationConfiguration=notification_configuration)\n\n\ndef submit_response(event: dict, context, response_status: str, error_message: str):\n response_body = json.dumps(\n {\n \"Status\": response_status,\n \"Reason\": f\"{error_message}See the details in CloudWatch Log Stream: {context.log_stream_name}\",\n \"PhysicalResourceId\": event.get(\"PhysicalResourceId\") or event[\"LogicalResourceId\"],\n \"StackId\": event[\"StackId\"],\n \"RequestId\": event[\"RequestId\"],\n \"LogicalResourceId\": event[\"LogicalResourceId\"],\n \"NoEcho\": False,\n }\n ).encode(\"utf-8\")\n headers = {\"content-type\": \"\", \"content-length\": str(len(response_body))}\n try:\n req = urllib.request.Request(url=event[\"ResponseURL\"], headers=headers, data=response_body, method=\"PUT\")\n with urllib.request.urlopen(req) as response:\n print(response.read().decode(\"utf-8\"))\n print(\"Status code: \" + response.reason)\n except Exception as e:\n print(\"send(..) failed executing request.urlopen(..): \" + str(e))\n" }, "Handler": "index.handler", "Role": { @@ -220,7 +256,7 @@ "Arn" ] }, - "Runtime": "nodejs12.x", + "Runtime": "python3.8", "Timeout": 300 }, "DependsOn": [ @@ -228,11 +264,6 @@ "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC" ] }, - "Bucket25524B414": { - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "Bucket2NotificationsD9BA2A77": { "Type": "Custom::S3BucketNotifications", "Properties": { @@ -270,7 +301,44 @@ } } ] - } + }, + "Managed": true + }, + "DependsOn": [ + "Topic3Policy49BDDFBD", + "Topic3DEAE47A7" + ] + }, + "Bucket25524B414": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "Bucket3NotificationsAFEFF359": { + "Type": "Custom::S3BucketNotifications", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691", + "Arn" + ] + }, + "BucketName": { + "Ref": "Bucket25524B414" + }, + "NotificationConfiguration": { + "TopicConfigurations": [ + { + "Events": [ + "s3:ObjectCreated:Copy" + ], + "TopicArn": { + "Ref": "Topic3DEAE47A7" + } + } + ] + }, + "Managed": false }, "DependsOn": [ "Topic3Policy49BDDFBD", diff --git a/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.ts b/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.ts index 9a42537d44526..5e1f11d42a6ef 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.ts @@ -21,4 +21,7 @@ const bucket2 = new s3.Bucket(stack, 'Bucket2', { }); bucket2.addObjectRemovedNotification(new s3n.SnsDestination(topic3), { prefix: 'foo' }, { suffix: 'foo/bar' }); +const bucket3 = s3.Bucket.fromBucketName(stack, 'Bucket3', bucket2.bucketName); +bucket3.addEventNotification(s3.EventType.OBJECT_CREATED_COPY, new s3n.SnsDestination(topic3)); + app.synth(); 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 8eb330f53a91e..42d6d71d603bc 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 @@ -1,10 +1,5 @@ { "Resources": { - "MyBucketF68F3FF0": { - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "MyBucketNotifications46AC0CD2": { "Type": "Custom::S3BucketNotifications", "Properties": { @@ -41,12 +36,18 @@ } } ] - } + }, + "Managed": true }, "DependsOn": [ "MyBucketAllowBucketNotificationsTolambdabucketnotificationsMyFunction4086861C1BF13476" ] }, + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "MyBucketAllowBucketNotificationsTolambdabucketnotificationsMyFunction4086861C1BF13476": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -119,11 +120,6 @@ "MyFunctionServiceRole3C357FF2" ] }, - "YourBucketC6A57364": { - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "YourBucketNotifications8D39901A": { "Type": "Custom::S3BucketNotifications", "Properties": { @@ -150,12 +146,18 @@ } } ] - } + }, + "Managed": true }, "DependsOn": [ "YourBucketAllowBucketNotificationsTolambdabucketnotificationsMyFunction4086861C8FE2B89D" ] }, + "YourBucketC6A57364": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "YourBucketAllowBucketNotificationsTolambdabucketnotificationsMyFunction4086861C8FE2B89D": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -235,7 +237,7 @@ "Properties": { "Description": "AWS CloudFormation handler for \"Custom::S3BucketNotifications\" resources (@aws-cdk/aws-s3)", "Code": { - "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require('https');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require('url');\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration,\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse('FAILED', err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse('SUCCESS');\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // eslint-disable-next-line max-len\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error message as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: 'PUT',\n headers: {\n 'content-type': '',\n 'content-length': responseBody.length,\n },\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on('error', (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" + "ZipFile": "import boto3 # type: ignore\nimport json\nimport logging\nimport urllib.request\n\ns3 = boto3.client(\"s3\")\n\nCONFIGURATION_TYPES = [\"TopicConfigurations\", \"QueueConfigurations\", \"LambdaFunctionConfigurations\"]\n\ndef handler(event: dict, context):\n response_status = \"SUCCESS\"\n error_message = \"\"\n try:\n props = event[\"ResourceProperties\"]\n bucket = props[\"BucketName\"]\n notification_configuration = props[\"NotificationConfiguration\"]\n request_type = event[\"RequestType\"]\n managed = props.get('Managed', 'true').lower() == 'true'\n stack_id = event['StackId']\n\n if managed:\n config = handle_managed(request_type, notification_configuration)\n else:\n config = handle_unmanaged(bucket, stack_id, request_type, notification_configuration)\n\n put_bucket_notification_configuration(bucket, config)\n except Exception as e:\n logging.exception(\"Failed to put bucket notification configuration\")\n response_status = \"FAILED\"\n error_message = f\"Error: {str(e)}. \"\n finally:\n submit_response(event, context, response_status, error_message)\n\n\ndef handle_managed(request_type, notification_configuration):\n if request_type == 'Delete':\n return {}\n return notification_configuration\n\n\ndef handle_unmanaged(bucket, stack_id, request_type, notification_configuration):\n\n # find external notifications\n external_notifications = find_external_notifications(bucket, stack_id)\n\n # if delete, that's all we need\n if request_type == 'Delete':\n return external_notifications\n\n def with_id(notification):\n notification['Id'] = f\"{stack_id}-{hash(json.dumps(notification, sort_keys=True))}\"\n return notification\n\n # otherwise, merge external with incoming config and augment with id\n notifications = {}\n for t in CONFIGURATION_TYPES:\n external = external_notifications.get(t, [])\n incoming = [with_id(n) for n in notification_configuration.get(t, [])]\n notifications[t] = external + incoming\n return notifications\n\n\ndef find_external_notifications(bucket, stack_id):\n existing_notifications = get_bucket_notification_configuration(bucket)\n external_notifications = {}\n for t in CONFIGURATION_TYPES:\n # if the notification was created by us, we know what id to expect\n # so we can filter by it.\n external_notifications[t] = [n for n in existing_notifications.get(t, []) if not n['Id'].startswith(f\"{stack_id}-\")]\n\n return external_notifications\n\n\ndef get_bucket_notification_configuration(bucket):\n return s3.get_bucket_notification_configuration(Bucket=bucket)\n\n\ndef put_bucket_notification_configuration(bucket, notification_configuration):\n s3.put_bucket_notification_configuration(Bucket=bucket, NotificationConfiguration=notification_configuration)\n\n\ndef submit_response(event: dict, context, response_status: str, error_message: str):\n response_body = json.dumps(\n {\n \"Status\": response_status,\n \"Reason\": f\"{error_message}See the details in CloudWatch Log Stream: {context.log_stream_name}\",\n \"PhysicalResourceId\": event.get(\"PhysicalResourceId\") or event[\"LogicalResourceId\"],\n \"StackId\": event[\"StackId\"],\n \"RequestId\": event[\"RequestId\"],\n \"LogicalResourceId\": event[\"LogicalResourceId\"],\n \"NoEcho\": False,\n }\n ).encode(\"utf-8\")\n headers = {\"content-type\": \"\", \"content-length\": str(len(response_body))}\n try:\n req = urllib.request.Request(url=event[\"ResponseURL\"], headers=headers, data=response_body, method=\"PUT\")\n with urllib.request.urlopen(req) as response:\n print(response.read().decode(\"utf-8\"))\n print(\"Status code: \" + response.reason)\n except Exception as e:\n print(\"send(..) failed executing request.urlopen(..): \" + str(e))\n" }, "Handler": "index.handler", "Role": { @@ -244,7 +246,7 @@ "Arn" ] }, - "Runtime": "nodejs12.x", + "Runtime": "python3.8", "Timeout": 300 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts b/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts index 7c4894cc2866c..43922fb54cc5d 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts @@ -24,6 +24,35 @@ test('bucket without notifications', () => { }); }); +test('notifications can be added to imported buckets', () => { + + const stack = new cdk.Stack(); + + const bucket = s3.Bucket.fromBucketName(stack, 'MyBucket', 'mybucket'); + const topic = new sns.Topic(stack, 'MyTopic'); + + bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.SnsDestination(topic)); + + expect(stack).toHaveResource('Custom::S3BucketNotifications', { + ServiceToken: { 'Fn::GetAtt': ['BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691', 'Arn'] }, + BucketName: 'mybucket', + Managed: false, + NotificationConfiguration: { + 'TopicConfigurations': [ + { + 'Events': [ + 's3:ObjectCreated:*', + ], + 'TopicArn': { + 'Ref': 'MyTopic86869434', + }, + }, + ], + }, + }); + +}); + test('when notification are added, a custom resource is provisioned + a lambda handler for it', () => { const stack = new cdk.Stack(); @@ -296,6 +325,7 @@ test('a notification destination can specify a set of dependencies that must be Properties: { ServiceToken: { 'Fn::GetAtt': ['BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691', 'Arn'] }, BucketName: { Ref: 'Bucket83908E77' }, + Managed: true, NotificationConfiguration: { QueueConfigurations: [{ Events: ['s3:ObjectCreated:*'], QueueArn: 'arn' }] }, }, DependsOn: ['Dependent'], 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 2cd8f17706a09..74d8086ffe175 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 @@ -78,11 +78,6 @@ ] } }, - "MyBucketF68F3FF0": { - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "MyBucketNotifications46AC0CD2": { "Type": "Custom::S3BucketNotifications", "Properties": { @@ -128,7 +123,8 @@ } } ] - } + }, + "Managed": true }, "DependsOn": [ "ObjectCreatedTopicPolicyA938ECFC", @@ -137,6 +133,11 @@ "ObjectDeletedTopic2A914EC0" ] }, + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC": { "Type": "AWS::IAM::Role", "Properties": { @@ -194,7 +195,7 @@ "Properties": { "Description": "AWS CloudFormation handler for \"Custom::S3BucketNotifications\" resources (@aws-cdk/aws-s3)", "Code": { - "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require('https');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require('url');\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration,\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse('FAILED', err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse('SUCCESS');\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // eslint-disable-next-line max-len\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error message as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: 'PUT',\n headers: {\n 'content-type': '',\n 'content-length': responseBody.length,\n },\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on('error', (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" + "ZipFile": "import boto3 # type: ignore\nimport json\nimport logging\nimport urllib.request\n\ns3 = boto3.client(\"s3\")\n\nCONFIGURATION_TYPES = [\"TopicConfigurations\", \"QueueConfigurations\", \"LambdaFunctionConfigurations\"]\n\ndef handler(event: dict, context):\n response_status = \"SUCCESS\"\n error_message = \"\"\n try:\n props = event[\"ResourceProperties\"]\n bucket = props[\"BucketName\"]\n notification_configuration = props[\"NotificationConfiguration\"]\n request_type = event[\"RequestType\"]\n managed = props.get('Managed', 'true').lower() == 'true'\n stack_id = event['StackId']\n\n if managed:\n config = handle_managed(request_type, notification_configuration)\n else:\n config = handle_unmanaged(bucket, stack_id, request_type, notification_configuration)\n\n put_bucket_notification_configuration(bucket, config)\n except Exception as e:\n logging.exception(\"Failed to put bucket notification configuration\")\n response_status = \"FAILED\"\n error_message = f\"Error: {str(e)}. \"\n finally:\n submit_response(event, context, response_status, error_message)\n\n\ndef handle_managed(request_type, notification_configuration):\n if request_type == 'Delete':\n return {}\n return notification_configuration\n\n\ndef handle_unmanaged(bucket, stack_id, request_type, notification_configuration):\n\n # find external notifications\n external_notifications = find_external_notifications(bucket, stack_id)\n\n # if delete, that's all we need\n if request_type == 'Delete':\n return external_notifications\n\n def with_id(notification):\n notification['Id'] = f\"{stack_id}-{hash(json.dumps(notification, sort_keys=True))}\"\n return notification\n\n # otherwise, merge external with incoming config and augment with id\n notifications = {}\n for t in CONFIGURATION_TYPES:\n external = external_notifications.get(t, [])\n incoming = [with_id(n) for n in notification_configuration.get(t, [])]\n notifications[t] = external + incoming\n return notifications\n\n\ndef find_external_notifications(bucket, stack_id):\n existing_notifications = get_bucket_notification_configuration(bucket)\n external_notifications = {}\n for t in CONFIGURATION_TYPES:\n # if the notification was created by us, we know what id to expect\n # so we can filter by it.\n external_notifications[t] = [n for n in existing_notifications.get(t, []) if not n['Id'].startswith(f\"{stack_id}-\")]\n\n return external_notifications\n\n\ndef get_bucket_notification_configuration(bucket):\n return s3.get_bucket_notification_configuration(Bucket=bucket)\n\n\ndef put_bucket_notification_configuration(bucket, notification_configuration):\n s3.put_bucket_notification_configuration(Bucket=bucket, NotificationConfiguration=notification_configuration)\n\n\ndef submit_response(event: dict, context, response_status: str, error_message: str):\n response_body = json.dumps(\n {\n \"Status\": response_status,\n \"Reason\": f\"{error_message}See the details in CloudWatch Log Stream: {context.log_stream_name}\",\n \"PhysicalResourceId\": event.get(\"PhysicalResourceId\") or event[\"LogicalResourceId\"],\n \"StackId\": event[\"StackId\"],\n \"RequestId\": event[\"RequestId\"],\n \"LogicalResourceId\": event[\"LogicalResourceId\"],\n \"NoEcho\": False,\n }\n ).encode(\"utf-8\")\n headers = {\"content-type\": \"\", \"content-length\": str(len(response_body))}\n try:\n req = urllib.request.Request(url=event[\"ResponseURL\"], headers=headers, data=response_body, method=\"PUT\")\n with urllib.request.urlopen(req) as response:\n print(response.read().decode(\"utf-8\"))\n print(\"Status code: \" + response.reason)\n except Exception as e:\n print(\"send(..) failed executing request.urlopen(..): \" + str(e))\n" }, "Handler": "index.handler", "Role": { @@ -203,7 +204,7 @@ "Arn" ] }, - "Runtime": "nodejs12.x", + "Runtime": "python3.8", "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 0e8a6a56cfb6e..dee0ec6922ed7 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 @@ -1,10 +1,5 @@ { "Resources": { - "Bucket12520700A": { - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "Bucket1NotificationsBC5D9A45": { "Type": "Custom::S3BucketNotifications", "Properties": { @@ -42,7 +37,8 @@ } } ] - } + }, + "Managed": true }, "DependsOn": [ "EncryptedQueueKey6F4FD304", @@ -52,6 +48,11 @@ "MyQueueE6CA6235" ] }, + "Bucket12520700A": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "MyQueueE6CA6235": { "Type": "AWS::SQS::Queue", "UpdateReplacePolicy": "Delete", @@ -183,7 +184,7 @@ "Properties": { "Description": "AWS CloudFormation handler for \"Custom::S3BucketNotifications\" resources (@aws-cdk/aws-s3)", "Code": { - "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require('https');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require('url');\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration,\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse('FAILED', err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse('SUCCESS');\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // eslint-disable-next-line max-len\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error message as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: 'PUT',\n headers: {\n 'content-type': '',\n 'content-length': responseBody.length,\n },\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on('error', (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" + "ZipFile": "import boto3 # type: ignore\nimport json\nimport logging\nimport urllib.request\n\ns3 = boto3.client(\"s3\")\n\nCONFIGURATION_TYPES = [\"TopicConfigurations\", \"QueueConfigurations\", \"LambdaFunctionConfigurations\"]\n\ndef handler(event: dict, context):\n response_status = \"SUCCESS\"\n error_message = \"\"\n try:\n props = event[\"ResourceProperties\"]\n bucket = props[\"BucketName\"]\n notification_configuration = props[\"NotificationConfiguration\"]\n request_type = event[\"RequestType\"]\n managed = props.get('Managed', 'true').lower() == 'true'\n stack_id = event['StackId']\n\n if managed:\n config = handle_managed(request_type, notification_configuration)\n else:\n config = handle_unmanaged(bucket, stack_id, request_type, notification_configuration)\n\n put_bucket_notification_configuration(bucket, config)\n except Exception as e:\n logging.exception(\"Failed to put bucket notification configuration\")\n response_status = \"FAILED\"\n error_message = f\"Error: {str(e)}. \"\n finally:\n submit_response(event, context, response_status, error_message)\n\n\ndef handle_managed(request_type, notification_configuration):\n if request_type == 'Delete':\n return {}\n return notification_configuration\n\n\ndef handle_unmanaged(bucket, stack_id, request_type, notification_configuration):\n\n # find external notifications\n external_notifications = find_external_notifications(bucket, stack_id)\n\n # if delete, that's all we need\n if request_type == 'Delete':\n return external_notifications\n\n def with_id(notification):\n notification['Id'] = f\"{stack_id}-{hash(json.dumps(notification, sort_keys=True))}\"\n return notification\n\n # otherwise, merge external with incoming config and augment with id\n notifications = {}\n for t in CONFIGURATION_TYPES:\n external = external_notifications.get(t, [])\n incoming = [with_id(n) for n in notification_configuration.get(t, [])]\n notifications[t] = external + incoming\n return notifications\n\n\ndef find_external_notifications(bucket, stack_id):\n existing_notifications = get_bucket_notification_configuration(bucket)\n external_notifications = {}\n for t in CONFIGURATION_TYPES:\n # if the notification was created by us, we know what id to expect\n # so we can filter by it.\n external_notifications[t] = [n for n in existing_notifications.get(t, []) if not n['Id'].startswith(f\"{stack_id}-\")]\n\n return external_notifications\n\n\ndef get_bucket_notification_configuration(bucket):\n return s3.get_bucket_notification_configuration(Bucket=bucket)\n\n\ndef put_bucket_notification_configuration(bucket, notification_configuration):\n s3.put_bucket_notification_configuration(Bucket=bucket, NotificationConfiguration=notification_configuration)\n\n\ndef submit_response(event: dict, context, response_status: str, error_message: str):\n response_body = json.dumps(\n {\n \"Status\": response_status,\n \"Reason\": f\"{error_message}See the details in CloudWatch Log Stream: {context.log_stream_name}\",\n \"PhysicalResourceId\": event.get(\"PhysicalResourceId\") or event[\"LogicalResourceId\"],\n \"StackId\": event[\"StackId\"],\n \"RequestId\": event[\"RequestId\"],\n \"LogicalResourceId\": event[\"LogicalResourceId\"],\n \"NoEcho\": False,\n }\n ).encode(\"utf-8\")\n headers = {\"content-type\": \"\", \"content-length\": str(len(response_body))}\n try:\n req = urllib.request.Request(url=event[\"ResponseURL\"], headers=headers, data=response_body, method=\"PUT\")\n with urllib.request.urlopen(req) as response:\n print(response.read().decode(\"utf-8\"))\n print(\"Status code: \" + response.reason)\n except Exception as e:\n print(\"send(..) failed executing request.urlopen(..): \" + str(e))\n" }, "Handler": "index.handler", "Role": { @@ -192,7 +193,7 @@ "Arn" ] }, - "Runtime": "nodejs12.x", + "Runtime": "python3.8", "Timeout": 300 }, "DependsOn": [ @@ -200,11 +201,6 @@ "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC" ] }, - "Bucket25524B414": { - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "Bucket2NotificationsD9BA2A77": { "Type": "Custom::S3BucketNotifications", "Properties": { @@ -241,13 +237,19 @@ } } ] - } + }, + "Managed": true }, "DependsOn": [ "MyQueuePolicy6BBEDDAC", "MyQueueE6CA6235" ] }, + "Bucket25524B414": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "EncryptedQueueKey6F4FD304": { "Type": "AWS::KMS::Key", "Properties": { diff --git a/packages/@aws-cdk/aws-s3/README.md b/packages/@aws-cdk/aws-s3/README.md index 6a83cacd4bb92..6a266560f65e4 100644 --- a/packages/@aws-cdk/aws-s3/README.md +++ b/packages/@aws-cdk/aws-s3/README.md @@ -207,6 +207,15 @@ bucket.addEventNotification(s3.EventType.OBJECT_REMOVED, { prefix: 'foo/', suffix: '.jpg' }); ``` +Adding notifications on existing buckets: + +```ts +const bucket = Bucket.fromBucketAttributes(this, 'ImportedBucket', { + bucketArn: 'arn:aws:s3:::my-bucket' +}); +bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.SnsDestination(topic)); +``` + [S3 Bucket Notifications]: https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index b375c510a14d1..b4f549f5feef0 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -4,7 +4,7 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import { - Fn, IResource, Lazy, RemovalPolicy, Resource, Stack, Token, + Fn, IResource, Lazy, RemovalPolicy, Resource, ResourceProps, Stack, Token, CustomResource, CustomResourceProvider, CustomResourceProviderRuntime, FeatureFlags, } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; @@ -279,6 +279,47 @@ export interface IBucket extends IResource { * @param options Options for adding the rule */ onCloudTrailWriteObject(id: string, options?: OnCloudTrailBucketEventOptions): events.Rule; + + /** + * Adds a bucket notification event destination. + * @param event The event to trigger the notification + * @param dest The notification destination (Lambda, SNS Topic or SQS Queue) + * + * @param filters S3 object key filter rules to determine which objects + * trigger this event. Each filter must include a `prefix` and/or `suffix` + * that will be matched against the s3 object key. Refer to the S3 Developer Guide + * for details about allowed filter rules. + * + * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-filtering + * + * @example + * + * bucket.addEventNotification(EventType.OnObjectCreated, myLambda, 'home/myusername/*') + * + * @see + * https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html + */ + addEventNotification(event: EventType, dest: IBucketNotificationDestination, ...filters: NotificationKeyFilter[]): void; + + /** + * Subscribes a destination to receive notifications when an object is + * created in the bucket. This is identical to calling + * `onEvent(EventType.ObjectCreated)`. + * + * @param dest The notification destination (see onEvent) + * @param filters Filters (see onEvent) + */ + addObjectCreatedNotification(dest: IBucketNotificationDestination, ...filters: NotificationKeyFilter[]): void + + /** + * Subscribes a destination to receive notifications when an object is + * removed from the bucket. This is identical to calling + * `onEvent(EventType.ObjectRemoved)`. + * + * @param dest The notification destination (see onEvent) + * @param filters Filters (see onEvent) + */ + addObjectRemovedNotification(dest: IBucketNotificationDestination, ...filters: NotificationKeyFilter[]): void; } /** @@ -374,7 +415,7 @@ export interface BucketAttributes { * Bucket.import(this, 'MyImportedBucket', ref); * */ -abstract class BucketBase extends Resource implements IBucket { +export abstract class BucketBase extends Resource implements IBucket { public abstract readonly bucketArn: string; public abstract readonly bucketName: string; public abstract readonly bucketDomainName: string; @@ -412,6 +453,16 @@ abstract class BucketBase extends Resource implements IBucket { */ protected abstract disallowPublicAccess?: boolean; + private readonly notifications: BucketNotifications; + + constructor(scope: Construct, id: string, props: ResourceProps = {}) { + super(scope, id, props); + + // defines a BucketNotifications construct. Notice that an actual resource will only + // be added if there are notifications added, so we don't need to condition this. + this.notifications = new BucketNotifications(this, 'Notifications', { bucket: this }); + } + /** * Define a CloudWatch event that triggers when something happens to this repository * @@ -688,6 +739,53 @@ abstract class BucketBase extends Resource implements IBucket { }); } + /** + * Adds a bucket notification event destination. + * @param event The event to trigger the notification + * @param dest The notification destination (Lambda, SNS Topic or SQS Queue) + * + * @param filters S3 object key filter rules to determine which objects + * trigger this event. Each filter must include a `prefix` and/or `suffix` + * that will be matched against the s3 object key. Refer to the S3 Developer Guide + * for details about allowed filter rules. + * + * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-filtering + * + * @example + * + * bucket.addEventNotification(EventType.OnObjectCreated, myLambda, 'home/myusername/*') + * + * @see + * https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html + */ + public addEventNotification(event: EventType, dest: IBucketNotificationDestination, ...filters: NotificationKeyFilter[]) { + this.notifications.addNotification(event, dest, ...filters); + } + + /** + * Subscribes a destination to receive notifications when an object is + * created in the bucket. This is identical to calling + * `onEvent(EventType.ObjectCreated)`. + * + * @param dest The notification destination (see onEvent) + * @param filters Filters (see onEvent) + */ + public addObjectCreatedNotification(dest: IBucketNotificationDestination, ...filters: NotificationKeyFilter[]) { + return this.addEventNotification(EventType.OBJECT_CREATED, dest, ...filters); + } + + /** + * Subscribes a destination to receive notifications when an object is + * removed from the bucket. This is identical to calling + * `onEvent(EventType.ObjectRemoved)`. + * + * @param dest The notification destination (see onEvent) + * @param filters Filters (see onEvent) + */ + public addObjectRemovedNotification(dest: IBucketNotificationDestination, ...filters: NotificationKeyFilter[]) { + return this.addEventNotification(EventType.OBJECT_REMOVED, dest, ...filters); + } + private get writeActions(): string[] { return [ ...perms.BUCKET_DELETE_ACTIONS, @@ -1311,7 +1409,6 @@ export class Bucket extends BucketBase { private accessControl?: BucketAccessControl; private readonly lifecycleRules: LifecycleRule[] = []; private readonly versioned?: boolean; - private readonly notifications: BucketNotifications; private readonly metrics: BucketMetrics[] = []; private readonly cors: CorsRule[] = []; private readonly inventories: Inventory[] = []; @@ -1386,10 +1483,6 @@ export class Bucket extends BucketBase { // Add all lifecycle rules (props.lifecycleRules || []).forEach(this.addLifecycleRule.bind(this)); - // defines a BucketNotifications construct. Notice that an actual resource will only - // be added if there are notifications added, so we don't need to condition this. - this.notifications = new BucketNotifications(this, 'Notifications', { bucket: this }); - if (props.publicReadAccess) { this.grantPublicAccess(); } @@ -1436,53 +1529,6 @@ export class Bucket extends BucketBase { this.cors.push(rule); } - /** - * Adds a bucket notification event destination. - * @param event The event to trigger the notification - * @param dest The notification destination (Lambda, SNS Topic or SQS Queue) - * - * @param filters S3 object key filter rules to determine which objects - * trigger this event. Each filter must include a `prefix` and/or `suffix` - * that will be matched against the s3 object key. Refer to the S3 Developer Guide - * for details about allowed filter rules. - * - * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-filtering - * - * @example - * - * bucket.addEventNotification(EventType.OnObjectCreated, myLambda, 'home/myusername/*') - * - * @see - * https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html - */ - public addEventNotification(event: EventType, dest: IBucketNotificationDestination, ...filters: NotificationKeyFilter[]) { - this.notifications.addNotification(event, dest, ...filters); - } - - /** - * Subscribes a destination to receive notifications when an object is - * created in the bucket. This is identical to calling - * `onEvent(EventType.ObjectCreated)`. - * - * @param dest The notification destination (see onEvent) - * @param filters Filters (see onEvent) - */ - public addObjectCreatedNotification(dest: IBucketNotificationDestination, ...filters: NotificationKeyFilter[]) { - return this.addEventNotification(EventType.OBJECT_CREATED, dest, ...filters); - } - - /** - * Subscribes a destination to receive notifications when an object is - * removed from the bucket. This is identical to calling - * `onEvent(EventType.ObjectRemoved)`. - * - * @param dest The notification destination (see onEvent) - * @param filters Filters (see onEvent) - */ - public addObjectRemovedNotification(dest: IBucketNotificationDestination, ...filters: NotificationKeyFilter[]) { - return this.addEventNotification(EventType.OBJECT_REMOVED, dest, ...filters); - } - /** * Add an inventory configuration. * diff --git a/packages/@aws-cdk/aws-s3/lib/notifications-resource/lambda/index.py b/packages/@aws-cdk/aws-s3/lib/notifications-resource/lambda/index.py new file mode 100644 index 0000000000000..2551398d74958 --- /dev/null +++ b/packages/@aws-cdk/aws-s3/lib/notifications-resource/lambda/index.py @@ -0,0 +1,101 @@ +import boto3 # type: ignore +import json +import logging +import urllib.request + +s3 = boto3.client("s3") + +CONFIGURATION_TYPES = ["TopicConfigurations", "QueueConfigurations", "LambdaFunctionConfigurations"] + +def handler(event: dict, context): + response_status = "SUCCESS" + error_message = "" + try: + props = event["ResourceProperties"] + bucket = props["BucketName"] + notification_configuration = props["NotificationConfiguration"] + request_type = event["RequestType"] + managed = props.get('Managed', 'true').lower() == 'true' + stack_id = event['StackId'] + + if managed: + config = handle_managed(request_type, notification_configuration) + else: + config = handle_unmanaged(bucket, stack_id, request_type, notification_configuration) + + put_bucket_notification_configuration(bucket, config) + except Exception as e: + logging.exception("Failed to put bucket notification configuration") + response_status = "FAILED" + error_message = f"Error: {str(e)}. " + finally: + submit_response(event, context, response_status, error_message) + + +def handle_managed(request_type, notification_configuration): + if request_type == 'Delete': + return {} + return notification_configuration + + +def handle_unmanaged(bucket, stack_id, request_type, notification_configuration): + + # find external notifications + external_notifications = find_external_notifications(bucket, stack_id) + + # if delete, that's all we need + if request_type == 'Delete': + return external_notifications + + def with_id(notification): + notification['Id'] = f"{stack_id}-{hash(json.dumps(notification, sort_keys=True))}" + return notification + + # otherwise, merge external with incoming config and augment with id + notifications = {} + for t in CONFIGURATION_TYPES: + external = external_notifications.get(t, []) + incoming = [with_id(n) for n in notification_configuration.get(t, [])] + notifications[t] = external + incoming + return notifications + + +def find_external_notifications(bucket, stack_id): + existing_notifications = get_bucket_notification_configuration(bucket) + external_notifications = {} + for t in CONFIGURATION_TYPES: + # if the notification was created by us, we know what id to expect + # so we can filter by it. + external_notifications[t] = [n for n in existing_notifications.get(t, []) if not n['Id'].startswith(f"{stack_id}-")] + + return external_notifications + + +def get_bucket_notification_configuration(bucket): + return s3.get_bucket_notification_configuration(Bucket=bucket) + + +def put_bucket_notification_configuration(bucket, notification_configuration): + s3.put_bucket_notification_configuration(Bucket=bucket, NotificationConfiguration=notification_configuration) + + +def submit_response(event: dict, context, response_status: str, error_message: str): + response_body = json.dumps( + { + "Status": response_status, + "Reason": f"{error_message}See the details in CloudWatch Log Stream: {context.log_stream_name}", + "PhysicalResourceId": event.get("PhysicalResourceId") or event["LogicalResourceId"], + "StackId": event["StackId"], + "RequestId": event["RequestId"], + "LogicalResourceId": event["LogicalResourceId"], + "NoEcho": False, + } + ).encode("utf-8") + headers = {"content-type": "", "content-length": str(len(response_body))} + try: + req = urllib.request.Request(url=event["ResponseURL"], headers=headers, data=response_body, method="PUT") + with urllib.request.urlopen(req) as response: + print(response.read().decode("utf-8")) + print("Status code: " + response.reason) + except Exception as e: + print("send(..) failed executing request.urlopen(..): " + str(e)) 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 b013bc0ebd5b8..296bebe265374 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 @@ -1,3 +1,5 @@ +import * as fs from 'fs'; +import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; @@ -39,7 +41,7 @@ export class NotificationsResourceHandler extends Construct { lambda = new NotificationsResourceHandler(root, logicalId); } - return lambda.functionArn; + return lambda; } /** @@ -48,18 +50,22 @@ export class NotificationsResourceHandler extends Construct { */ public readonly functionArn: string; + /** + * The role of the handler's lambda function. + */ + public readonly role: iam.Role; + constructor(scope: Construct, id: string) { super(scope, id); - const role = new iam.Role(this, 'Role', { + this.role = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), ], }); - // handler allows to put bucket notification on s3 buckets. - role.addToPolicy(new iam.PolicyStatement({ + this.role.addToPolicy(new iam.PolicyStatement({ actions: ['s3:PutBucketNotification'], resources: ['*'], })); @@ -69,8 +75,7 @@ export class NotificationsResourceHandler extends Construct { public readonly tags: cdk.TagManager = new cdk.TagManager(cdk.TagType.STANDARD, resourceType); protected renderProperties(properties: any): { [key: string]: any } { - properties.Tags = cdk.listMapper( - cdk.cfnTagToCloudFormation)(this.tags.renderTags()); + properties.Tags = cdk.listMapper(cdk.cfnTagToCloudFormation)(this.tags.renderTags()); delete properties.tags; return properties; } @@ -79,102 +84,15 @@ export class NotificationsResourceHandler extends Construct { type: resourceType, properties: { Description: 'AWS CloudFormation handler for "Custom::S3BucketNotifications" resources (@aws-cdk/aws-s3)', - Code: { ZipFile: `exports.handler = ${handler.toString()};` }, + Code: { ZipFile: fs.readFileSync(path.join(__dirname, 'lambda/index.py'), 'utf8') }, Handler: 'index.handler', - Role: role.roleArn, - Runtime: 'nodejs12.x', + Role: this.role.roleArn, + Runtime: 'python3.8', Timeout: 300, }, }); - - resource.node.addDependency(role); + resource.node.addDependency(this.role); this.functionArn = resource.getAtt('Arn').toString(); } } - -/* eslint-disable no-console */ - -/** - * Lambda event handler for the custom resource. Bear in mind that we are going - * to .toString() this function and inline it as Lambda code. - * - * The function will issue a putBucketNotificationConfiguration request for the - * specified bucket. - */ -const handler = (event: any, context: any) => { - // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies - const s3 = new (require('aws-sdk').S3)(); - // eslint-disable-next-line @typescript-eslint/no-require-imports - const https = require('https'); - // eslint-disable-next-line @typescript-eslint/no-require-imports - const url = require('url'); - - log(JSON.stringify(event, undefined, 2)); - - const props = event.ResourceProperties; - - if (event.RequestType === 'Delete') { - props.NotificationConfiguration = { }; // this is how you clean out notifications - } - - const req = { - Bucket: props.BucketName, - NotificationConfiguration: props.NotificationConfiguration, - }; - - return s3.putBucketNotificationConfiguration(req, (err: any, data: any) => { - log({ err, data }); - if (err) { - return submitResponse('FAILED', err.message + `\nMore information in CloudWatch Log Stream: ${context.logStreamName}`); - } else { - return submitResponse('SUCCESS'); - } - }); - - function log(obj: any) { - console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj); - } - - // eslint-disable-next-line max-len - // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule - // to allow sending an error message as a reason. - function submitResponse(responseStatus: string, reason?: string) { - const responseBody = JSON.stringify({ - Status: responseStatus, - Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName, - PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId, - StackId: event.StackId, - RequestId: event.RequestId, - LogicalResourceId: event.LogicalResourceId, - NoEcho: false, - }); - - log({ responseBody }); - - const parsedUrl = url.parse(event.ResponseURL); - const options = { - hostname: parsedUrl.hostname, - port: 443, - path: parsedUrl.path, - method: 'PUT', - headers: { - 'content-type': '', - 'content-length': responseBody.length, - }, - }; - - const request = https.request(options, (r: any) => { - log({ statusCode: r.statusCode, statusMessage: r.statusMessage }); - context.done(); - }); - - request.on('error', (error: any) => { - log({ sendError: error }); - context.done(); - }); - - request.write(responseBody); - request.end(); - } -}; diff --git a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource.ts b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource.ts index 3c2194ddf7eb9..d5190f1a6a913 100644 --- a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource.ts +++ b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource.ts @@ -1,5 +1,6 @@ +import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; -import { Bucket, EventType, NotificationKeyFilter } from '../bucket'; +import { IBucket, EventType, NotificationKeyFilter, Bucket } from '../bucket'; import { BucketNotificationDestinationType, IBucketNotificationDestination } from '../destination'; import { NotificationsResourceHandler } from './notifications-resource-handler'; @@ -10,11 +11,8 @@ import { Construct } from '@aws-cdk/core'; interface NotificationsProps { /** * The bucket to manage notifications for. - * - * This cannot be an `IBucket` because the bucket maintains the 1:1 - * relationship with this resource. */ - bucket: Bucket; + bucket: IBucket; } /** @@ -37,7 +35,7 @@ export class BucketNotifications extends Construct { private readonly queueNotifications = new Array(); private readonly topicNotifications = new Array(); private resource?: cdk.CfnResource; - private readonly bucket: Bucket; + private readonly bucket: IBucket; constructor(scope: Construct, id: string, props: NotificationsProps) { super(scope, id); @@ -104,14 +102,24 @@ export class BucketNotifications extends Construct { */ private createResourceOnce() { if (!this.resource) { - const handlerArn = NotificationsResourceHandler.singleton(this); + const handler = NotificationsResourceHandler.singleton(this); + + const managed = this.bucket instanceof Bucket; + + if (!managed) { + handler.role.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:GetBucketNotification'], + resources: ['*'], + })); + } this.resource = new cdk.CfnResource(this, 'Resource', { type: 'Custom::S3BucketNotifications', properties: { - ServiceToken: handlerArn, + ServiceToken: handler.functionArn, BucketName: this.bucket.bucketName, NotificationConfiguration: cdk.Lazy.any({ produce: () => this.renderNotificationConfiguration() }), + Managed: managed, }, }); } diff --git a/packages/@aws-cdk/aws-s3/test/notifications-resource-handler/Dockerfile b/packages/@aws-cdk/aws-s3/test/notifications-resource-handler/Dockerfile new file mode 100644 index 0000000000000..916f369bcd353 --- /dev/null +++ b/packages/@aws-cdk/aws-s3/test/notifications-resource-handler/Dockerfile @@ -0,0 +1,9 @@ +FROM public.ecr.aws/lambda/python:3.8 + +ADD . /opt/lambda +WORKDIR /opt/lambda + +RUN pip3 install boto3==1.17.42 +RUN python3 test_index.py + +ENTRYPOINT [ "/bin/bash" ] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3/test/notifications-resource-handler/test.sh b/packages/@aws-cdk/aws-s3/test/notifications-resource-handler/test.sh new file mode 100755 index 0000000000000..661125fb4310c --- /dev/null +++ b/packages/@aws-cdk/aws-s3/test/notifications-resource-handler/test.sh @@ -0,0 +1,26 @@ +#!/bin/bash +#--------------------------------------------------------------------------------------------------- +# executes unit tests +# +# prepares a staging directory with the requirements +set -e +script_dir=$(cd $(dirname $0) && pwd) + +# prepare staging directory +staging=$(mktemp -d) +mkdir -p ${staging} +cd ${staging} + +# copy src and overlay with test +cp ${script_dir}/../../lib/notifications-resource/lambda/index.py $PWD +cp ${script_dir}/test_index.py $PWD +cp ${script_dir}/Dockerfile $PWD + +NOTIFICATIONS_RESOURCE_TEST_NO_DOCKER=${NOTIFICATIONS_RESOURCE_TEST_NO_DOCKER:-""} + +if [ -z ${NOTIFICATIONS_RESOURCE_TEST_NO_DOCKER} ]; then + # this will run our tests inside the right environment + docker build . +else + python test_index.py +fi diff --git a/packages/@aws-cdk/aws-s3/test/notifications-resource-handler/test_index.py b/packages/@aws-cdk/aws-s3/test/notifications-resource-handler/test_index.py new file mode 100644 index 0000000000000..9127677b02675 --- /dev/null +++ b/packages/@aws-cdk/aws-s3/test/notifications-resource-handler/test_index.py @@ -0,0 +1,246 @@ +from typing import Dict +import unittest +import uuid +import os +import sys +from unittest.mock import MagicMock, patch + +# boto needs a region to initialize the client +# even if no calls are executed. +os.environ["AWS_DEFAULT_REGION"] = "us-east-1" + +try: + # this is available only if executed with ./test.sh + import index +except ModuleNotFoundError as _: + print( + "Unable to import index. Use ./test.sh to run these tests. " + + 'If you want to avoid running them in docker, run "NOTIFICATIONS_RESOURCE_TEST_NO_DOCKER=true ./test.sh"' + ) + sys.exit(1) + +CONFIGURATION_TYPES = ["TopicConfigurations", "QueueConfigurations", "LambdaFunctionConfigurations"] + + +def make_event(request_type: str, managed: bool): + return { + "StackId": "StackId", + "RequestType": request_type, + "ResourceProperties": { + "Managed": str(managed), + "BucketName": "BucketName", + "NotificationConfiguration": make_notification_configuration(), + }, + } + + +def make_notification_configuration(id_prefix: str = None): + def make_id(): + return f"{id_prefix or ''}{str(uuid.uuid4())}" + + config = {} + for t in CONFIGURATION_TYPES: + config[t] = [{"Id": make_id()}] + return config + + +def make_empty_notification_configuration(): + config = {} + for t in CONFIGURATION_TYPES: + config[t] = [] + return config + + +def merge_notification_configurations(conf1: Dict, conf2: Dict): + notifications = {} + for t in CONFIGURATION_TYPES: + notifications[t] = conf1.get(t, []) + conf2.get(t, []) + return notifications + + +class ManagedBucketTest(unittest.TestCase): + @patch("index.put_bucket_notification_configuration") + @patch("index.submit_response") + def test_create(self, _, put: MagicMock): + + event = make_event("Create", True) + + index.handler(event, {}) + + put.assert_called_once_with( + event["ResourceProperties"]["BucketName"], + event["ResourceProperties"]["NotificationConfiguration"], + ) + + @patch("index.put_bucket_notification_configuration") + @patch("index.submit_response") + def test_update(self, _, put): + + event = make_event("Update", True) + + index.handler(event, {}) + + put.assert_called_once_with( + event["ResourceProperties"]["BucketName"], + event["ResourceProperties"]["NotificationConfiguration"], + ) + + @patch("index.put_bucket_notification_configuration") + @patch("index.submit_response") + def test_delete(self, _, put: MagicMock): + + event = make_event("Delete", True) + + index.handler(event, {}) + + put.assert_called_once_with(event["ResourceProperties"]["BucketName"], {}) + + +class UnmanagedCleanBucketTest(unittest.TestCase): + @patch("index.put_bucket_notification_configuration") + @patch("index.get_bucket_notification_configuration") + @patch("index.submit_response") + def test_create(self, _, get: MagicMock, put: MagicMock): + + get.return_value = {} + + event = make_event("Create", False) + + index.handler(event, {}) + + put.assert_called_once_with( + event["ResourceProperties"]["BucketName"], + event["ResourceProperties"]["NotificationConfiguration"], + ) + + @patch("index.put_bucket_notification_configuration") + @patch("index.get_bucket_notification_configuration") + @patch("index.submit_response") + def test_update(self, _, get: MagicMock, put: MagicMock): + + event = make_event("Update", False) + + # simulate a previous create operation + current_notifications = make_notification_configuration(f"{event['StackId']}-") + get.return_value = current_notifications + + index.handler(event, {}) + + put.assert_called_once_with( + event["ResourceProperties"]["BucketName"], + event["ResourceProperties"]["NotificationConfiguration"], + ) + + @patch("index.put_bucket_notification_configuration") + @patch("index.get_bucket_notification_configuration") + @patch("index.submit_response") + def test_delete(self, _, get: MagicMock, put: MagicMock): + + event = make_event("Delete", False) + + # simulate a previous create operation + current_notifications = make_notification_configuration(f"{event['StackId']}-") + get.return_value = current_notifications + + index.handler(event, {}) + + put.assert_called_once_with( + event["ResourceProperties"]["BucketName"], + make_empty_notification_configuration(), + ) + + +class UnmanagedDirtyBucketTest(unittest.TestCase): + @patch("index.put_bucket_notification_configuration") + @patch("index.get_bucket_notification_configuration") + @patch("index.submit_response") + def test_create(self, _, get: MagicMock, put: MagicMock): + + event = make_event("Create", False) + + # simulate external notifications + current_notifications = make_notification_configuration() + get.return_value = current_notifications + + index.handler(event, {}) + + put.assert_called_once_with( + event["ResourceProperties"]["BucketName"], + merge_notification_configurations( + current_notifications, + event["ResourceProperties"]["NotificationConfiguration"], + ), + ) + + @patch("index.put_bucket_notification_configuration") + @patch("index.get_bucket_notification_configuration") + @patch("index.submit_response") + def test_update(self, _, get: MagicMock, put: MagicMock): + + event = make_event("Update", False) + + # simulate external notifications + current_notifications = make_notification_configuration() + get.return_value = current_notifications + + index.handler(event, {}) + + put.assert_called_once_with( + event["ResourceProperties"]["BucketName"], + merge_notification_configurations( + current_notifications, + event["ResourceProperties"]["NotificationConfiguration"], + ), + ) + + @patch("index.put_bucket_notification_configuration") + @patch("index.get_bucket_notification_configuration") + @patch("index.submit_response") + def test_delete(self, _, get: MagicMock, put: MagicMock): + + event = make_event("Delete", False) + + # simulate external notifications + current_notifications = make_notification_configuration() + get.return_value = current_notifications + + index.handler(event, {}) + + put.assert_called_once_with( + event["ResourceProperties"]["BucketName"], + current_notifications, + ) + + +class CfnResponsesTest(unittest.TestCase): + @patch("index.put_bucket_notification_configuration") + @patch("index.handle_managed") + @patch("index.submit_response") + def test_success(self, submit: MagicMock, *_): + + event = make_event("Create", True) + + index.handler(event, {}) + + submit.assert_called_once() + self.assertEqual(submit.call_args_list[0][0][2], "SUCCESS") + + @patch("index.handle_managed") + @patch("index.submit_response") + def test_failure(self, submit: MagicMock, handle_managed: MagicMock): + + event = make_event("Create", True) + + def throw(*_): + raise RuntimeError("Intentional error for test") + + handle_managed.side_effect = throw + + index.handler(event, {}) + + submit.assert_called_once() + self.assertEqual(submit.call_args_list[0][0][2], "FAILED") + + +if __name__ == "__main__": + unittest.main() diff --git a/packages/@aws-cdk/aws-s3/test/notifications-resource.lambda.test.ts b/packages/@aws-cdk/aws-s3/test/notifications-resource.lambda.test.ts new file mode 100644 index 0000000000000..7a5e043993614 --- /dev/null +++ b/packages/@aws-cdk/aws-s3/test/notifications-resource.lambda.test.ts @@ -0,0 +1,8 @@ +import { spawnSync } from 'child_process'; +import * as path from 'path'; + +test('notifications handler', () => { + const testScript = path.join(__dirname, 'notifications-resource-handler', 'test.sh'); + const result = spawnSync(testScript, { stdio: 'inherit' }); + expect(result.status).toBe(0); +}); From dc3cf130d779c276569500bff54e44d4eb0c4763 Mon Sep 17 00:00:00 2001 From: Madeline Kusters <80541297+madeline-k@users.noreply.github.com> Date: Wed, 16 Jun 2021 13:51:59 -0700 Subject: [PATCH 34/34] feat(cloudwatch): use `string` instead of `any` for cloudwatch dimension values (#15097) This is an alternative solution to PR #14978 ---- *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/README.md | 4 +- .../@aws-cdk/aws-cloudwatch/lib/metric.ts | 28 ++++- .../aws-cloudwatch/test/test.metrics.ts | 111 +++++++++++++++++- 3 files changed, 133 insertions(+), 10 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudwatch/README.md b/packages/@aws-cdk/aws-cloudwatch/README.md index 943dc135fa775..ff1f1997489f4 100644 --- a/packages/@aws-cdk/aws-cloudwatch/README.md +++ b/packages/@aws-cdk/aws-cloudwatch/README.md @@ -38,7 +38,7 @@ const hostedZone = new route53.HostedZone(this, 'MyHostedZone', { zoneName: "exa const metric = new Metric({ namespace: 'AWS/Route53', metricName: 'DNSQueries', - dimensions: { + dimensionsMap: { HostedZoneId: hostedZone.hostedZoneId } }) @@ -53,7 +53,7 @@ you can instantiate a `Metric` object to represent it. For example: const metric = new Metric({ namespace: 'MyNamespace', metricName: 'MyMetric', - dimensions: { + dimensionsMap: { ProcessingStep: 'Download' } }); diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts index 3bb3d512c86d0..cf4051f74ffd4 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts @@ -12,6 +12,8 @@ import { Construct } from '@aws-cdk/core'; export type DimensionHash = {[dim: string]: any}; +export type DimensionsMap = { [dim: string]: string }; + /** * Options shared by most methods accepting metric options */ @@ -43,9 +45,18 @@ export interface CommonMetricOptions { * Dimensions of the metric * * @default - No dimensions. + * + * @deprecated Use 'dimensionsMap' instead. */ readonly dimensions?: DimensionHash; + /** + * Dimensions of the metric + * + * @default - No dimensions. + */ + readonly dimensionsMap?: DimensionsMap; + /** * Unit used to filter the metric stream * @@ -216,10 +227,7 @@ export class Metric implements IMetric { if (periodSec !== 1 && periodSec !== 5 && periodSec !== 10 && periodSec !== 30 && periodSec % 60 !== 0) { throw new Error(`'period' must be 1, 5, 10, 30, or a multiple of 60 seconds, received ${periodSec}`); } - if (props.dimensions) { - this.validateDimensions(props.dimensions); - } - this.dimensions = props.dimensions; + this.dimensions = this.validateDimensions(props.dimensionsMap ?? props.dimensions); this.namespace = props.namespace; this.metricName = props.metricName; // Try parsing, this will throw if it's not a valid stat @@ -249,12 +257,14 @@ export class Metric implements IMetric { // For these we're not going to do deep equality, misses some opportunity for optimization // but that's okay. && (props.dimensions === undefined) + && (props.dimensionsMap === undefined) && (props.period === undefined || props.period.toSeconds() === this.period.toSeconds())) { return this; } return new Metric({ dimensions: ifUndefined(props.dimensions, this.dimensions), + dimensionsMap: props.dimensionsMap, namespace: this.namespace, metricName: this.metricName, period: ifUndefined(props.period, this.period), @@ -398,7 +408,11 @@ export class Metric implements IMetric { return list; } - private validateDimensions(dims: DimensionHash): void { + private validateDimensions(dims?: DimensionHash): DimensionHash | undefined { + if (!dims) { + return dims; + } + var dimsArray = Object.keys(dims); if (dimsArray?.length > 10) { throw new Error(`The maximum number of dimensions is 10, received ${dimsArray.length}`); @@ -416,6 +430,8 @@ export class Metric implements IMetric { throw new Error(`Dimension value must be at least 1 and no more than 255 characters; received ${dims[key]}`); }; }); + + return dims; } } @@ -746,4 +762,4 @@ interface IModifiableMetric { function isModifiableMetric(m: any): m is IModifiableMetric { return typeof m === 'object' && m !== null && !!m.with; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cloudwatch/test/test.metrics.ts b/packages/@aws-cdk/aws-cloudwatch/test/test.metrics.ts index e1dc9230e1404..39f39d4476bc1 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/test.metrics.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/test.metrics.ts @@ -1,8 +1,8 @@ -import { expect, haveResource } from '@aws-cdk/assert-internal'; +import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; -import { Metric } from '../lib'; +import { Alarm, Metric } from '../lib'; export = { 'metric grant'(test: Test) { @@ -100,6 +100,24 @@ export = { test.done(); }, + 'cannot use long dimension values in dimensionsMap'(test: Test) { + const arr = new Array(256); + const invalidDimensionValue = arr.fill('A', 0).join(''); + + test.throws(() => { + new Metric({ + namespace: 'Test', + metricName: 'ACount', + period: cdk.Duration.minutes(10), + dimensionsMap: { + DimensionWithLongValue: invalidDimensionValue, + }, + }); + }, `Dimension value must be at least 1 and no more than 255 characters; received ${invalidDimensionValue}`); + + test.done(); + }, + 'throws error when there are more than 10 dimensions'(test: Test) { test.throws(() => { new Metric({ @@ -124,4 +142,93 @@ export = { test.done(); }, + + 'throws error when there are more than 10 dimensions in dimensionsMap'(test: Test) { + test.throws(() => { + new Metric({ + namespace: 'Test', + metricName: 'ACount', + period: cdk.Duration.minutes(10), + dimensionsMap: { + dimensionA: 'value1', + dimensionB: 'value2', + dimensionC: 'value3', + dimensionD: 'value4', + dimensionE: 'value5', + dimensionF: 'value6', + dimensionG: 'value7', + dimensionH: 'value8', + dimensionI: 'value9', + dimensionJ: 'value10', + dimensionK: 'value11', + }, + } ); + }, /The maximum number of dimensions is 10, received 11/); + + test.done(); + }, + + 'can create metric with dimensionsMap property'(test: Test) { + const stack = new cdk.Stack(); + const metric = new Metric({ + namespace: 'Test', + metricName: 'Metric', + dimensionsMap: { + dimensionA: 'value1', + dimensionB: 'value2', + }, + }); + + new Alarm(stack, 'Alarm', { + metric: metric, + threshold: 10, + evaluationPeriods: 1, + }); + + test.deepEqual(metric.dimensions, { + dimensionA: 'value1', + dimensionB: 'value2', + }); + expect(stack).to(haveResourceLike('AWS::CloudWatch::Alarm', { + Namespace: 'Test', + MetricName: 'Metric', + Dimensions: [ + { + Name: 'dimensionA', + Value: 'value1', + }, + { + Name: 'dimensionB', + Value: 'value2', + }, + ], + Threshold: 10, + EvaluationPeriods: 1, + })); + + test.done(); + }, + + '"with" with a different dimensions property'(test: Test) { + const dims = { + dimensionA: 'value1', + }; + + const metric = new Metric({ + namespace: 'Test', + metricName: 'Metric', + period: cdk.Duration.minutes(10), + dimensionsMap: dims, + }); + + const newDims = { + dimensionB: 'value2', + }; + + test.deepEqual(metric.with({ + dimensionsMap: newDims, + }).dimensions, newDims); + + test.done(); + }, };