diff --git a/.github/workflows/github-merit-badger.yml b/.github/workflows/github-merit-badger.yml index 1f1b2b14dbf13..29e2df649f483 100644 --- a/.github/workflows/github-merit-badger.yml +++ b/.github/workflows/github-merit-badger.yml @@ -10,11 +10,11 @@ jobs: permissions: pull-requests: write steps: - - uses: kaizencc/github-merit-badger@main + - uses: aws-github-ops/github-merit-badger@main id: merit-badger with: github-token: ${{ secrets.GITHUB_TOKEN }} badges: '[beginning-contributor,repeat-contributor,valued-contributor,admired-contributor,star-contributor,distinguished-contributor]' thresholds: '[0,3,6,13,25,50]' badge-type: 'achievement' - ignore-usernames: '[RomainMuller,rix0rrr,Jerry-AWS,MrArnoldPalmer,iliapolo,otaviomacedo,madeline-k,kaizencc,comcalvi,corymhall,peterwoodworth,ryparker,TheRealAmazonKendra,vinayak-kukreja,Naumel,mrgrain,aws-cdk-automation,dependabot,mergify]' + ignore-usernames: '[RomainMuller,rix0rrr,Jerry-AWS,MrArnoldPalmer,iliapolo,otaviomacedo,madeline-k,kaizencc,comcalvi,corymhall,peterwoodworth,ryparker,TheRealAmazonKendra,vinayak-kukreja,Naumel,mrgrain,pahud,cgarvis,aws-cdk-automation,dependabot[bot],mergify[bot]]' diff --git a/.github/workflows/pr-linter.yml b/.github/workflows/pr-linter.yml index 76b8f9a958f59..67b7bece9419c 100644 --- a/.github/workflows/pr-linter.yml +++ b/.github/workflows/pr-linter.yml @@ -3,7 +3,8 @@ name: PR Linter on: - pull_request: + # Necessary to have sufficient permissions to write to the PR + pull_request_target: types: - labeled - unlabeled @@ -29,5 +30,5 @@ jobs: - name: Validate uses: ./tools/@aws-cdk/prlint env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.PROJEN_GITHUB_TOKEN }} REPO_ROOT: ${{ github.workspace }} diff --git a/.mergify.yml b/.mergify.yml index c75f34e359160..1e5748e68ce04 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -10,7 +10,7 @@ pull_request_rules: label: add: [ contribution/core ] conditions: - - author~=^(RomainMuller|rix0rrr|Jerry-AWS|MrArnoldPalmer|iliapolo|uttarasridhar|otaviomacedo|madeline-k|kaizencc|comcalvi|corymhall|peterwoodworth|ryparker|TheRealAmazonKendra|yuth|vinayak-kukreja|Naumel|mrgrain)$ + - author~=^(RomainMuller|rix0rrr|Jerry-AWS|MrArnoldPalmer|iliapolo|uttarasridhar|otaviomacedo|madeline-k|kaizencc|comcalvi|corymhall|peterwoodworth|ryparker|TheRealAmazonKendra|yuth|vinayak-kukreja|Naumel|mrgrain|pahud|cgarvis)$ - -label~="contribution/core" - name: automatic merge actions: diff --git a/CHANGELOG.v2.alpha.md b/CHANGELOG.v2.alpha.md index 08548b9394b52..bd32608800e35 100644 --- a/CHANGELOG.v2.alpha.md +++ b/CHANGELOG.v2.alpha.md @@ -2,6 +2,14 @@ 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. +## [2.44.0-alpha.0](https://github.com/aws/aws-cdk/compare/v2.43.1-alpha.0...v2.44.0-alpha.0) (2022-09-28) + + +### Features + +* **integ-tests:** chain assertion api calls ([#22196](https://github.com/aws/aws-cdk/issues/22196)) ([530e07b](https://github.com/aws/aws-cdk/commit/530e07bdc87ab94bbd5ed28debac98400a8152cc)) +* **neptune:** introduce metric method to cluster and instance ([#21995](https://github.com/aws/aws-cdk/issues/21995)) ([02ed837](https://github.com/aws/aws-cdk/commit/02ed8371276d504ba9fe09687d45409ad7cca288)), closes [#20248](https://github.com/aws/aws-cdk/issues/20248) + ## [2.43.1-alpha.0](https://github.com/aws/aws-cdk/compare/v2.43.0-alpha.0...v2.43.1-alpha.0) (2022-09-23) ## [2.43.0-alpha.0](https://github.com/aws/aws-cdk/compare/v2.42.1-alpha.0...v2.43.0-alpha.0) (2022-09-21) diff --git a/CHANGELOG.v2.md b/CHANGELOG.v2.md index 2c926fc39721d..009aa8a98341f 100644 --- a/CHANGELOG.v2.md +++ b/CHANGELOG.v2.md @@ -2,6 +2,31 @@ 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. +## [2.44.0](https://github.com/aws/aws-cdk/compare/v2.43.1...v2.44.0) (2022-09-28) + + +### Features + +* **assets:** support drop-in docker replacements by setting `$CDK_DOCKER` ([#21838](https://github.com/aws/aws-cdk/issues/21838)) ([d52310e](https://github.com/aws/aws-cdk/commit/d52310ea2104dd1ed13761944d078ffce46a299f)), closes [40aws-cdk/core/lib/bundling.ts#L523](https://github.com/40aws-cdk/core/lib/bundling.ts/issues/L523) [#21836](https://github.com/aws/aws-cdk/issues/21836) +* **backup:** add copy actions to backup plan rules ([#22244](https://github.com/aws/aws-cdk/issues/22244)) ([d87a651](https://github.com/aws/aws-cdk/commit/d87a651608d23f3bfc3c178093d92b5bdda71084)), closes [#22173](https://github.com/aws/aws-cdk/issues/22173) +* **cfnspec:** cloudformation spec v89.0.0 ([#22232](https://github.com/aws/aws-cdk/issues/22232)) ([953d684](https://github.com/aws/aws-cdk/commit/953d6841fa3ed43258d0454e245cebcab6323e0d)) +* **cli:** `cdk deploy --method=direct` is faster ([#22079](https://github.com/aws/aws-cdk/issues/22079)) ([dd6ead4](https://github.com/aws/aws-cdk/commit/dd6ead447a80cdec3379a3ced2e04b7d15f9c55d)) +* **cloudwatch:** add gauge widget ([#22213](https://github.com/aws/aws-cdk/issues/22213)) ([d9f0e80](https://github.com/aws/aws-cdk/commit/d9f0e809d583d23cb83b4e2855574675a669c33f)), closes [#22136](https://github.com/aws/aws-cdk/issues/22136) +* **core:** 'postCliContext' property allows context that cannot be overridden by the CLI ([#21743](https://github.com/aws/aws-cdk/issues/21743)) ([a618096](https://github.com/aws/aws-cdk/commit/a618096432a27a808a0352ea186fe1e4db2911c4)) +* **dynamodb:** Changes how metricForOperation methods are used ([#22097](https://github.com/aws/aws-cdk/issues/22097)) ([fcb311d](https://github.com/aws/aws-cdk/commit/fcb311d615422b76f18b6be60dd466b315fcd6b0)), closes [#21963](https://github.com/aws/aws-cdk/issues/21963) +* **logs:** add dimensions to metric filter ([#21654](https://github.com/aws/aws-cdk/issues/21654)) ([f834a45](https://github.com/aws/aws-cdk/commit/f834a4537643b32131076111be0693c6f8f96b24)), closes [/github.com/aws/aws-cdk/issues/16999#issuecomment-1005172655](https://github.com/aws//github.com/aws/aws-cdk/issues/16999/issues/issuecomment-1005172655) [#16999](https://github.com/aws/aws-cdk/issues/16999) +* **pipelines:** allow disabling use of change sets ([#21619](https://github.com/aws/aws-cdk/issues/21619)) ([05723e7](https://github.com/aws/aws-cdk/commit/05723e74cc0e760f570c36ec02a70e8936287814)), closes [#20827](https://github.com/aws/aws-cdk/issues/20827) +* **s3-deployment:** extract flag to disable automatic unzipping ([#21805](https://github.com/aws/aws-cdk/issues/21805)) ([91898b5](https://github.com/aws/aws-cdk/commit/91898b51573c0bfd0f26ae7610feb6a400bc8159)), closes [#8065](https://github.com/aws/aws-cdk/issues/8065) + + +### Bug Fixes + +* **aws-elasticloadbalancingv2:** Validation for interval and timeout of application-target-group ([#22225](https://github.com/aws/aws-cdk/issues/22225)) ([6128e39](https://github.com/aws/aws-cdk/commit/6128e3908f4f6b6a1db66ebf7f77b6c966d1f9e7)) +* **cli:** SSO credentials do not work when using a proxy ([#22115](https://github.com/aws/aws-cdk/issues/22115)) ([c425e8c](https://github.com/aws/aws-cdk/commit/c425e8ca1a3d296eb6a7fd7e005d07c1eadd16aa)), closes [#21328](https://github.com/aws/aws-cdk/issues/21328) +* **elbv2:** Use correct format for parsing imported target group ARNs ([#22153](https://github.com/aws/aws-cdk/issues/22153)) ([4704d4c](https://github.com/aws/aws-cdk/commit/4704d4c4ac065634dbada3732193a6753369dd12)) +* **rds:** changing engine versions would fail to update on DBInstances that were part of a DBCluster ([#22185](https://github.com/aws/aws-cdk/issues/22185)) ([c070ace](https://github.com/aws/aws-cdk/commit/c070acea1b12ec4f73c7d2087c5408d7e38a90a3)), closes [#21758](https://github.com/aws/aws-cdk/issues/21758) [#22180](https://github.com/aws/aws-cdk/issues/22180) +* cannot use values that return an instance of a deprecated class for non TS / JS language ([#22204](https://github.com/aws/aws-cdk/issues/22204)) ([4cad2cf](https://github.com/aws/aws-cdk/commit/4cad2cf7e1ca41dedae6adc8866792e5f71b2123)) + ## [2.43.1](https://github.com/aws/aws-cdk/compare/v2.43.0...v2.43.1) (2022-09-23) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 000209bfa981c..ec8de994ef2b7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,6 +42,7 @@ let us know if it's not up-to-date (even better, submit a PR with your correcti - [Debugging](#debugging) - [Connecting the VS Code Debugger](#connecting-the-vs-code-debugger) - [Run a CDK unit test in the debugger](#run-a-cdk-unit-test-in-the-debugger) +- [Badges (Pilot Program)](#badges-pilot-program) - [Related Repositories](#related-repositories) ## Getting Started @@ -1116,6 +1117,24 @@ $ node --inspect-brk /path/to/aws-cdk/node_modules/.bin/jest -i -t 'TESTNAME' 3. On the `Run` pane of VSCode, select the run configuration **Attach to NodeJS** and click the button. +## Badges (Pilot Program) + +> CDK Merit Badges is a Pilot Program. The badges you get are experimental and may change. + +CDK Merit Badges is a program aimed at enhancing the CDK contributor experience. When you +submit new pull requests to the CDK repository, you will receive a merit badge that reflects +how many prior successful contributions you have to the repository. Right now, these badges +are just for fun and are meant as a small incentive to continued contributions to the CDK. + +The badges have the following meaning: + +- `beginning-contributor`: contributed between 0-2 PRs to the CDK +- `repeat-contributor`: contributed between 3-5 PRs to the CDK +- `valued-contributor`: contributed between 6-12 PRs to the CDK +- `admired-contributor`: contributed between 13-24 PRs to the CDK +- `star-contributor`: contributed between 25-49 PRs to the CDK +- `distinguished-contributor`: contributed 50+ PRs to the CDK + ## Related Repositories * [Samples](https://github.com/aws-samples/aws-cdk-examples): includes sample code in multiple languages diff --git a/packages/@aws-cdk/aws-amplify/package.json b/packages/@aws-cdk/aws-amplify/package.json index 93f42ccab64cc..f14005fc7c46f 100644 --- a/packages/@aws-cdk/aws-amplify/package.json +++ b/packages/@aws-cdk/aws-amplify/package.json @@ -88,7 +88,7 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", - "aws-sdk": "^2.848.0" + "aws-sdk": "^2.1211.0" }, "dependencies": { "@aws-cdk/aws-codebuild": "0.0.0", diff --git a/packages/@aws-cdk/aws-apigateway/package.json b/packages/@aws-cdk/aws-apigateway/package.json index 03ffea77f6e62..b6f3c28297160 100644 --- a/packages/@aws-cdk/aws-apigateway/package.json +++ b/packages/@aws-cdk/aws-apigateway/package.json @@ -84,7 +84,6 @@ "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/integ-runner": "0.0.0", - "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2" diff --git a/packages/@aws-cdk/aws-apprunner/lib/service.ts b/packages/@aws-cdk/aws-apprunner/lib/service.ts index cddaf2210a6de..d4d6865a81693 100644 --- a/packages/@aws-cdk/aws-apprunner/lib/service.ts +++ b/packages/@aws-cdk/aws-apprunner/lib/service.ts @@ -92,11 +92,32 @@ export class Memory { * The code runtimes */ export class Runtime { + + /** + * CORRETTO 8 + */ + public static readonly CORRETTO_8 = Runtime.of('CORRETTO_8') + + /** + * CORRETTO 11 + */ + public static readonly CORRETTO_11 = Runtime.of('CORRETTO_11') + /** * NodeJS 12 */ public static readonly NODEJS_12 = Runtime.of('NODEJS_12') + /** + * NodeJS 14 + */ + public static readonly NODEJS_14 = Runtime.of('NODEJS_14') + + /** + * NodeJS 16 + */ + public static readonly NODEJS_16 = Runtime.of('NODEJS_16') + /** * Python 3 */ diff --git a/packages/@aws-cdk/aws-backup/README.md b/packages/@aws-cdk/aws-backup/README.md index d15bc5d6b4e30..1bc6cec09476b 100644 --- a/packages/@aws-cdk/aws-backup/README.md +++ b/packages/@aws-cdk/aws-backup/README.md @@ -99,6 +99,21 @@ plan.addRule(new backup.BackupPlanRule({ })); ``` +Rules can also specify to copy recovery points to another Backup Vault using `copyActions`. Copied recovery points can +optionally have `moveToColdStorageAfter` and `deleteAfter` configured. + +```ts +declare const plan: backup.BackupPlan; +declare const secondaryVault: backup.BackupVault; +plan.addRule(new backup.BackupPlanRule({ + copyActions: [{ + destinationBackupVault: secondaryVault, + moveToColdStorageAfter: Duration.days(30), + deleteAfter: Duration.days(120), + }] +})); +``` + Ready-made rules are also available: ```ts diff --git a/packages/@aws-cdk/aws-backup/lib/plan.ts b/packages/@aws-cdk/aws-backup/lib/plan.ts index 632d823727b36..fd80dbb20d6b9 100644 --- a/packages/@aws-cdk/aws-backup/lib/plan.ts +++ b/packages/@aws-cdk/aws-backup/lib/plan.ts @@ -1,7 +1,7 @@ import { IResource, Lazy, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnBackupPlan } from './backup.generated'; -import { BackupPlanRule } from './rule'; +import { BackupPlanCopyActionProps, BackupPlanRule } from './rule'; import { BackupSelection, BackupSelectionOptions } from './selection'; import { BackupVault, IBackupVault } from './vault'; @@ -191,9 +191,20 @@ export class BackupPlan extends Resource implements IBackupPlan { startWindowMinutes: rule.props.startWindow?.toMinutes(), enableContinuousBackup: rule.props.enableContinuousBackup, targetBackupVault: vault.backupVaultName, + copyActions: rule.props.copyActions?.map(this.planCopyActions), }); } + private planCopyActions(props: BackupPlanCopyActionProps): CfnBackupPlan.CopyActionResourceTypeProperty { + return { + destinationBackupVaultArn: props.destinationBackupVault.backupVaultArn, + lifecycle: (props.deleteAfter || props.moveToColdStorageAfter) && { + deleteAfterDays: props.deleteAfter?.toDays(), + moveToColdStorageAfterDays: props.moveToColdStorageAfter?.toDays(), + }, + }; + } + /** * The backup vault where backups are stored if not defined at * the rule level diff --git a/packages/@aws-cdk/aws-backup/lib/rule.ts b/packages/@aws-cdk/aws-backup/lib/rule.ts index 295d2560911fc..a879fafd17d83 100644 --- a/packages/@aws-cdk/aws-backup/lib/rule.ts +++ b/packages/@aws-cdk/aws-backup/lib/rule.ts @@ -1,5 +1,5 @@ import * as events from '@aws-cdk/aws-events'; -import { Duration } from '@aws-cdk/core'; +import { Duration, Token } from '@aws-cdk/core'; import { IBackupVault } from './vault'; /** @@ -70,6 +70,38 @@ export interface BackupPlanRuleProps { * @default false */ readonly enableContinuousBackup?: boolean; + + /** + * Copy operations to perform on recovery points created by this rule + * + * @default - no copy actions + */ + readonly copyActions?: BackupPlanCopyActionProps[]; +} + +/** + * Properties for a BackupPlanCopyAction + */ +export interface BackupPlanCopyActionProps { + /** + * Destination Vault for recovery points to be copied into + */ + readonly destinationBackupVault: IBackupVault; + + /** + * Specifies the duration after creation that a copied recovery point is deleted from the destination vault. + * Must be at least 90 days greater than `moveToColdStorageAfter`, if specified. + * + * @default - recovery point is never deleted + */ + readonly deleteAfter?: Duration; + + /** + * Specifies the duration after creation that a copied recovery point is moved to cold storage. + * + * @default - recovery point is never moved to cold storage + */ + readonly moveToColdStorageAfter?: Duration; } /** @@ -185,6 +217,19 @@ export class BackupPlanRule { throw new Error(`'deleteAfter' must be between 1 and 35 days if 'enableContinuousBackup' is enabled, but got ${props.deleteAfter.toHumanString()}`); } + if (props.copyActions && props.copyActions.length > 0) { + props.copyActions.forEach(copyAction => { + if (copyAction.deleteAfter && !Token.isUnresolved(copyAction.deleteAfter) && + copyAction.moveToColdStorageAfter && !Token.isUnresolved(copyAction.moveToColdStorageAfter) && + copyAction.deleteAfter.toDays() < copyAction.moveToColdStorageAfter.toDays() + 90) { + throw new Error([ + '\'deleteAfter\' must at least 90 days later than corresponding \'moveToColdStorageAfter\'', + `received 'deleteAfter: ${copyAction.deleteAfter.toDays()}' and 'moveToColdStorageAfter: ${copyAction.moveToColdStorageAfter.toDays()}'`, + ].join('\n')); + } + }); + } + this.props = { ...props, deleteAfter, diff --git a/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/cdk-backup.assets.json b/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/cdk-backup.assets.json index 56aefc86c1e8e..3222720cf6f11 100644 --- a/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/cdk-backup.assets.json +++ b/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/cdk-backup.assets.json @@ -1,7 +1,7 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { - "8001d34381bcb57b7b2a8fb3ade1e27b0ea7c1819c1d3973537e2cb5aa604ce7": { + "14e034eeffbdd95a18b6c1a8c7a4876e1dfbedde51220bb1a196a337a6848c16": { "source": { "path": "cdk-backup.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "8001d34381bcb57b7b2a8fb3ade1e27b0ea7c1819c1d3973537e2cb5aa604ce7.json", + "objectKey": "14e034eeffbdd95a18b6c1a8c7a4876e1dfbedde51220bb1a196a337a6848c16.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/cdk-backup.template.json b/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/cdk-backup.template.json index 2c36c669d9a98..e2cf214d34d87 100644 --- a/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/cdk-backup.template.json +++ b/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/cdk-backup.template.json @@ -39,6 +39,17 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "SecondaryVault67665B5E": { + "Type": "AWS::Backup::BackupVault", + "Properties": { + "BackupVaultName": "cdkbackupSecondaryVaultA01C2A0E", + "LockConfiguration": { + "MinRetentionDays": 5 + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "PlanDAF4E53A": { "Type": "AWS::Backup::BackupPlan", "Properties": { @@ -84,6 +95,29 @@ "BackupVaultName" ] } + }, + { + "CopyActions": [ + { + "DestinationBackupVaultArn": { + "Fn::GetAtt": [ + "SecondaryVault67665B5E", + "BackupVaultArn" + ] + }, + "Lifecycle": { + "DeleteAfterDays": 120, + "MoveToColdStorageAfterDays": 30 + } + } + ], + "RuleName": "PlanRule3", + "TargetBackupVault": { + "Fn::GetAtt": [ + "Vault23237E5B", + "BackupVaultName" + ] + } } ] } diff --git a/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/integ.json b/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/integ.json index c432d2f16d1fa..dedd47fcf7e75 100644 --- a/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { "integ.backup": { "stacks": [ diff --git a/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/manifest.json index cce1a09cbac7b..b08a4b8df2d42 100644 --- a/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -23,7 +23,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/8001d34381bcb57b7b2a8fb3ade1e27b0ea7c1819c1d3973537e2cb5aa604ce7.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/14e034eeffbdd95a18b6c1a8c7a4876e1dfbedde51220bb1a196a337a6848c16.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -57,6 +57,12 @@ "data": "Vault23237E5B" } ], + "/cdk-backup/SecondaryVault/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "SecondaryVault67665B5E" + } + ], "/cdk-backup/Plan/Resource": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/tree.json b/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/tree.json index 37053efb47cae..2b8102553e225 100644 --- a/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-backup/test/backup.integ.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } }, "cdk-backup": { @@ -53,8 +53,8 @@ "id": "ScalingRole", "path": "cdk-backup/Table/ScalingRole", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } } }, @@ -102,6 +102,33 @@ "version": "0.0.0" } }, + "SecondaryVault": { + "id": "SecondaryVault", + "path": "cdk-backup/SecondaryVault", + "children": { + "Resource": { + "id": "Resource", + "path": "cdk-backup/SecondaryVault/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Backup::BackupVault", + "aws:cdk:cloudformation:props": { + "backupVaultName": "cdkbackupSecondaryVaultA01C2A0E", + "lockConfiguration": { + "minRetentionDays": 5 + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-backup.CfnBackupVault", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-backup.BackupVault", + "version": "0.0.0" + } + }, "Plan": { "id": "Plan", "path": "cdk-backup/Plan", @@ -154,6 +181,29 @@ "BackupVaultName" ] } + }, + { + "ruleName": "PlanRule3", + "targetBackupVault": { + "Fn::GetAtt": [ + "Vault23237E5B", + "BackupVaultName" + ] + }, + "copyActions": [ + { + "destinationBackupVaultArn": { + "Fn::GetAtt": [ + "SecondaryVault67665B5E", + "BackupVaultArn" + ] + }, + "lifecycle": { + "deleteAfterDays": 120, + "moveToColdStorageAfterDays": 30 + } + } + ] } ] } @@ -314,14 +364,14 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-backup/test/integ.backup.ts b/packages/@aws-cdk/aws-backup/test/integ.backup.ts index 57de1d51750fc..ef1bbff7251b2 100644 --- a/packages/@aws-cdk/aws-backup/test/integ.backup.ts +++ b/packages/@aws-cdk/aws-backup/test/integ.backup.ts @@ -25,6 +25,12 @@ class TestStack extends Stack { minRetention: Duration.days(5), }, }); + const secondaryVault = new backup.BackupVault(this, 'SecondaryVault', { + removalPolicy: RemovalPolicy.DESTROY, + lockConfiguration: { + minRetention: Duration.days(5), + }, + }); const plan = backup.BackupPlan.dailyWeeklyMonthly5YearRetention(this, 'Plan', vault); plan.addSelection('Selection', { @@ -33,6 +39,14 @@ class TestStack extends Stack { backup.BackupResource.fromTag('stage', 'prod'), // Resources that are tagged stage=prod ], }); + + plan.addRule(new backup.BackupPlanRule({ + copyActions: [{ + destinationBackupVault: secondaryVault, + moveToColdStorageAfter: Duration.days(30), + deleteAfter: Duration.days(120), + }], + })); } } diff --git a/packages/@aws-cdk/aws-backup/test/plan.test.ts b/packages/@aws-cdk/aws-backup/test/plan.test.ts index 77674a8223fb9..80f3ed4321427 100644 --- a/packages/@aws-cdk/aws-backup/test/plan.test.ts +++ b/packages/@aws-cdk/aws-backup/test/plan.test.ts @@ -286,6 +286,56 @@ test('automatically creates a new vault', () => { }); }); +test('create a plan and add rule to copy to a different vault', () => { + // GIVEN + const primaryVault = new BackupVault(stack, 'PrimaryVault'); + const secondaryVault = new BackupVault(stack, 'SecondaryVault'); + + // WHEN + new BackupPlan(stack, 'Plan', { + backupVault: primaryVault, + backupPlanRules: [ + new BackupPlanRule({ + copyActions: [{ + destinationBackupVault: secondaryVault, + deleteAfter: Duration.days(120), + moveToColdStorageAfter: Duration.days(30), + }], + }), + ], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Backup::BackupPlan', { + BackupPlan: { + BackupPlanName: 'Plan', + BackupPlanRule: [ + { + RuleName: 'PlanRule0', + TargetBackupVault: { + 'Fn::GetAtt': [ + 'PrimaryVault9BBEBB0D', + 'BackupVaultName', + ], + }, + CopyActions: [{ + DestinationBackupVaultArn: { + 'Fn::GetAtt': [ + 'SecondaryVault67665B5E', + 'BackupVaultArn', + ], + }, + Lifecycle: { + DeleteAfterDays: 120, + MoveToColdStorageAfterDays: 30, + }, + }], + }, + ], + }, + }); +}); + test('throws when deleteAfter is not greater than moveToColdStorageAfter', () => { expect(() => new BackupPlanRule({ deleteAfter: Duration.days(5), @@ -331,3 +381,23 @@ test('throws when deleteAfter is greater than 35 in combination with enableConti deleteAfter: Duration.days(36), })).toThrow(/'deleteAfter' must be between 1 and 35 days if 'enableContinuousBackup' is enabled, but got 36 days/); }); + +test('throws when deleteAfter is not greater than moveToColdStorageAfter in a copy action', () => { + expect(() => new BackupPlanRule({ + copyActions: [{ + destinationBackupVault: new BackupVault(stack, 'Vault'), + deleteAfter: Duration.days(5), + moveToColdStorageAfter: Duration.days(6), + }], + })).toThrow(/deleteAfter' must at least 90 days later than corresponding 'moveToColdStorageAfter'\nreceived 'deleteAfter: 5' and 'moveToColdStorageAfter: 6'/); +}); + +test('throws when deleteAfter is not greater than 90 days past moveToColdStorageAfter parameter in a copy action', () => { + expect(() => new BackupPlanRule({ + copyActions: [{ + destinationBackupVault: new BackupVault(stack, 'Vault'), + deleteAfter: Duration.days(45), + moveToColdStorageAfter: Duration.days(30), + }], + })).toThrow(/'deleteAfter' must at least 90 days later than corresponding 'moveToColdStorageAfter'\nreceived 'deleteAfter: 45' and 'moveToColdStorageAfter: 30'/); +}); diff --git a/packages/@aws-cdk/aws-cloudfront-origins/package.json b/packages/@aws-cdk/aws-cloudfront-origins/package.json index 143fca34ea916..030de64af9b5e 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/package.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/package.json @@ -85,7 +85,7 @@ "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", - "aws-sdk": "^2.848.0" + "aws-sdk": "^2.1211.0" }, "dependencies": { "@aws-cdk/aws-apigateway": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index 7268f2b59cb50..3eb58a20b10c7 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -87,7 +87,7 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", - "aws-sdk": "^2.848.0", + "aws-sdk": "^2.1211.0", "jest": "^27.5.1" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index 3363e15dafe52..17df9eddd0812 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/package.json @@ -87,7 +87,7 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", - "aws-sdk": "^2.848.0", + "aws-sdk": "^2.1211.0", "jest": "^27.5.1" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-cloudwatch/README.md b/packages/@aws-cdk/aws-cloudwatch/README.md index e92e0f09356c7..1f87ad8b169a1 100644 --- a/packages/@aws-cdk/aws-cloudwatch/README.md +++ b/packages/@aws-cdk/aws-cloudwatch/README.md @@ -399,6 +399,24 @@ dashboard.addWidgets(new cloudwatch.GraphWidget({ })); ``` +### Gauge widget + +Gauge graph requires the max and min value of the left Y axis, if no value is informed the limits will be from 0 to 100. + +```ts +declare const dashboard: cloudwatch.Dashboard; +declare const errorAlarm: cloudwatch.Alarm; +declare const gaugeMetric: cloudwatch.Metric; + +dashboard.addWidgets(new cloudwatch.GaugeWidget({ + metrics: [gaugeMetric], + leftYAxis: { + min: 0, + max: 1000, + } +})); +``` + ### Alarm widget An alarm widget shows the graph and the alarm line of a single alarm: diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts b/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts index 16bce048a7132..ebcf83afab423 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts @@ -139,6 +139,135 @@ export enum GraphWidgetView { PIE = 'pie', } +/** + * Properties for a GaugeWidget + */ +export interface GaugeWidgetProps extends MetricWidgetProps { + /** + * Metrics to display on left Y axis + * + * @default - No metrics + */ + readonly metrics?: IMetric[]; + + /** + * Annotations for the left Y axis + * + * @default - No annotations + */ + readonly annotations?: HorizontalAnnotation[]; + + /** + * Left Y axis + * + * @default - None + */ + readonly leftYAxis?: YAxisProps; + + /** + * Position of the legend + * + * @default - bottom + */ + readonly legendPosition?: LegendPosition; + + /** + * Whether the graph should show live data + * + * @default false + */ + readonly liveData?: boolean; + + /** + * Whether to show the value from the entire time range. Only applicable for Bar and Pie charts. + * + * If false, values will be from the most recent period of your chosen time range; + * if true, shows the value from the entire time range. + * + * @default false + */ + readonly setPeriodToTimeRange?: boolean; + + /** + * The default period for all metrics in this widget. + * The period is the length of time represented by one data point on the graph. + * This default can be overridden within each metric definition. + * + * @default cdk.Duration.seconds(300) + */ + readonly period?: cdk.Duration; + + /** + * The default statistic to be displayed for each metric. + * This default can be overridden within the definition of each individual metric + * + * @default - The statistic for each metric is used + */ + readonly statistic?: string; +} + +/** + * A dashboard gauge widget that displays metrics + */ +export class GaugeWidget extends ConcreteWidget { + + private readonly props: GaugeWidgetProps; + + private readonly metrics: IMetric[]; + + constructor(props: GaugeWidgetProps) { + super(props.width || 6, props.height || 6); + this.props = props; + this.metrics = props.metrics ?? []; + this.copyMetricWarnings(...this.metrics); + } + + /** + * Add another metric to the left Y axis of the GaugeWidget + * + * @param metric the metric to add + */ + public addMetric(metric: IMetric) { + this.metrics.push(metric); + this.copyMetricWarnings(metric); + } + + public toJson(): any[] { + const horizontalAnnotations = [ + ...(this.props.annotations || []).map(mapAnnotation('annotations')), + ]; + + const metrics = allMetricsGraphJson(this.metrics, []); + const leftYAxis = { + ...this.props.leftYAxis, + min: this.props.leftYAxis?.min ?? 0, + max: this.props.leftYAxis?.max ?? 100, + }; + return [{ + type: 'metric', + width: this.width, + height: this.height, + x: this.x, + y: this.y, + properties: { + view: 'gauge', + title: this.props.title, + region: this.props.region || cdk.Aws.REGION, + metrics: metrics.length > 0 ? metrics : undefined, + annotations: horizontalAnnotations.length > 0 ? { horizontal: horizontalAnnotations } : undefined, + yAxis: { + left: leftYAxis ?? undefined, + }, + legend: this.props.legendPosition !== undefined ? { position: this.props.legendPosition } : undefined, + liveData: this.props.liveData, + setPeriodToTimeRange: this.props.setPeriodToTimeRange, + period: this.props.period?.toSeconds(), + stat: this.props.statistic, + }, + }]; + } +} + /** * Properties for a GraphWidget */ diff --git a/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/GaugeAlarmIntegrationTestDefaultTestDeployAssertF43E2A2D.assets.json b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/GaugeAlarmIntegrationTestDefaultTestDeployAssertF43E2A2D.assets.json new file mode 100644 index 0000000000000..0fd619fc0de05 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/GaugeAlarmIntegrationTestDefaultTestDeployAssertF43E2A2D.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "GaugeAlarmIntegrationTestDefaultTestDeployAssertF43E2A2D.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/GaugeAlarmIntegrationTestDefaultTestDeployAssertF43E2A2D.template.json b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/GaugeAlarmIntegrationTestDefaultTestDeployAssertF43E2A2D.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/GaugeAlarmIntegrationTestDefaultTestDeployAssertF43E2A2D.template.json @@ -0,0 +1,36 @@ +{ + "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. [cdk:skip]" + } + }, + "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/aws-cloudwatch/test/gauge-alarm.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/gauge-alarm.assets.json b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/gauge-alarm.assets.json new file mode 100644 index 0000000000000..3d8f0b81624cf --- /dev/null +++ b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/gauge-alarm.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "8f02ef0ee3c900b85f9315a4a4807bd1e86f92b80d77cadc667b2b21b5b34e0b": { + "source": { + "path": "gauge-alarm.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "8f02ef0ee3c900b85f9315a4a4807bd1e86f92b80d77cadc667b2b21b5b34e0b.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/gauge-alarm.template.json b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/gauge-alarm.template.json new file mode 100644 index 0000000000000..d041fba598679 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/gauge-alarm.template.json @@ -0,0 +1,66 @@ +{ + "Resources": { + "queue": { + "Type": "AWS::SQS::Queue" + }, + "DashCCD7F836": { + "Type": "AWS::CloudWatch::Dashboard", + "Properties": { + "DashboardBody": { + "Fn::Join": [ + "", + [ + "{\"widgets\":[{\"type\":\"metric\",\"width\":24,\"height\":6,\"x\":0,\"y\":0,\"properties\":{\"view\":\"gauge\",\"region\":\"", + { + "Ref": "AWS::Region" + }, + "\",\"metrics\":[[\"AWS/SQS\",\"ApproximateNumberOfMessagesVisible\",\"QueueName\",\"", + { + "Fn::GetAtt": [ + "queue", + "QueueName" + ] + }, + "\"]],\"yAxis\":{\"left\":{\"max\":500,\"min\":0}}}}]}" + ] + ] + }, + "DashboardName": "MyCustomGaugeAlarm" + } + } + }, + "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. [cdk:skip]" + } + }, + "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/aws-cloudwatch/test/gauge-alarm.integ.snapshot/integ.json b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/integ.json new file mode 100644 index 0000000000000..bf239bc153d3d --- /dev/null +++ b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "GaugeAlarmIntegrationTest/DefaultTest": { + "stacks": [ + "gauge-alarm" + ], + "assertionStack": "GaugeAlarmIntegrationTest/DefaultTest/DeployAssert", + "assertionStackName": "GaugeAlarmIntegrationTestDefaultTestDeployAssertF43E2A2D" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..6589330e057d3 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/manifest.json @@ -0,0 +1,117 @@ +{ + "version": "21.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "gauge-alarm.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "gauge-alarm.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "gauge-alarm": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "gauge-alarm.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/8f02ef0ee3c900b85f9315a4a4807bd1e86f92b80d77cadc667b2b21b5b34e0b.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "gauge-alarm.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "gauge-alarm.assets" + ], + "metadata": { + "/gauge-alarm/queue": [ + { + "type": "aws:cdk:logicalId", + "data": "queue" + } + ], + "/gauge-alarm/Dash/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "DashCCD7F836" + } + ], + "/gauge-alarm/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/gauge-alarm/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "gauge-alarm" + }, + "GaugeAlarmIntegrationTestDefaultTestDeployAssertF43E2A2D.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "GaugeAlarmIntegrationTestDefaultTestDeployAssertF43E2A2D.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "GaugeAlarmIntegrationTestDefaultTestDeployAssertF43E2A2D": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "GaugeAlarmIntegrationTestDefaultTestDeployAssertF43E2A2D.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "GaugeAlarmIntegrationTestDefaultTestDeployAssertF43E2A2D.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "GaugeAlarmIntegrationTestDefaultTestDeployAssertF43E2A2D.assets" + ], + "metadata": { + "/GaugeAlarmIntegrationTest/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/GaugeAlarmIntegrationTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "GaugeAlarmIntegrationTest/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/tree.json b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/tree.json new file mode 100644 index 0000000000000..c5c5f23258bd5 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudwatch/test/gauge-alarm.integ.snapshot/tree.json @@ -0,0 +1,118 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "gauge-alarm": { + "id": "gauge-alarm", + "path": "gauge-alarm", + "children": { + "queue": { + "id": "queue", + "path": "gauge-alarm/queue", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Dash": { + "id": "Dash", + "path": "gauge-alarm/Dash", + "children": { + "Resource": { + "id": "Resource", + "path": "gauge-alarm/Dash/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudWatch::Dashboard", + "aws:cdk:cloudformation:props": { + "dashboardBody": { + "Fn::Join": [ + "", + [ + "{\"widgets\":[{\"type\":\"metric\",\"width\":24,\"height\":6,\"x\":0,\"y\":0,\"properties\":{\"view\":\"gauge\",\"region\":\"", + { + "Ref": "AWS::Region" + }, + "\",\"metrics\":[[\"AWS/SQS\",\"ApproximateNumberOfMessagesVisible\",\"QueueName\",\"", + { + "Fn::GetAtt": [ + "queue", + "QueueName" + ] + }, + "\"]],\"yAxis\":{\"left\":{\"max\":500,\"min\":0}}}}]}" + ] + ] + }, + "dashboardName": "MyCustomGaugeAlarm" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.CfnDashboard", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.Dashboard", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "GaugeAlarmIntegrationTest": { + "id": "GaugeAlarmIntegrationTest", + "path": "GaugeAlarmIntegrationTest", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "GaugeAlarmIntegrationTest/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "GaugeAlarmIntegrationTest/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "GaugeAlarmIntegrationTest/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudwatch/test/graphs.test.ts b/packages/@aws-cdk/aws-cloudwatch/test/graphs.test.ts index e459063e5936d..5f9d3f0d27842 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/graphs.test.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/graphs.test.ts @@ -1,5 +1,5 @@ import { Duration, Stack } from '@aws-cdk/core'; -import { Alarm, AlarmWidget, Color, GraphWidget, GraphWidgetView, LegendPosition, LogQueryWidget, Metric, Shading, SingleValueWidget, LogQueryVisualizationType, CustomWidget } from '../lib'; +import { Alarm, AlarmWidget, Color, GraphWidget, GraphWidgetView, LegendPosition, LogQueryWidget, Metric, Shading, SingleValueWidget, LogQueryVisualizationType, CustomWidget, GaugeWidget } from '../lib'; describe('Graphs', () => { test('add stacked property to graphs', () => { @@ -795,6 +795,34 @@ describe('Graphs', () => { }]); }); + test('add GaugeWidget', () => { + // GIVEN + const stack = new Stack(); + const widget = new GaugeWidget({ + metrics: [new Metric({ namespace: 'CDK', metricName: 'Test' })], + }); + + // THEN + expect(stack.resolve(widget.toJson())).toEqual([{ + type: 'metric', + width: 6, + height: 6, + properties: { + view: 'gauge', + region: { Ref: 'AWS::Region' }, + metrics: [ + ['CDK', 'Test'], + ], + yAxis: { + left: { + min: 0, + max: 100, + }, + }, + }, + }]); + }); + test('GraphWidget supports stat and period', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-cloudwatch/test/integ.gauge-alarm.ts b/packages/@aws-cdk/aws-cloudwatch/test/integ.gauge-alarm.ts new file mode 100644 index 0000000000000..4d782a8ed76b9 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudwatch/test/integ.gauge-alarm.ts @@ -0,0 +1,35 @@ +import * as cdk from '@aws-cdk/core'; +import * as integ from '@aws-cdk/integ-tests'; +import * as cloudwatch from '../lib'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'gauge-alarm'); + +const queue = new cdk.CfnResource(stack, 'queue', { type: 'AWS::SQS::Queue' }); + +const numberOfMessagesVisibleMetric = new cloudwatch.Metric({ + namespace: 'AWS/SQS', + metricName: 'ApproximateNumberOfMessagesVisible', + dimensionsMap: { QueueName: queue.getAtt('QueueName').toString() }, +}); + +const dashboard = new cloudwatch.Dashboard(stack, 'Dash', { + dashboardName: 'MyCustomGaugeAlarm', +}); +dashboard.addWidgets( + new cloudwatch.GaugeWidget({ + leftYAxis: { + max: 500, + min: 0, + }, + width: 24, + metrics: [numberOfMessagesVisibleMetric], + }), +); + +new integ.IntegTest(app, 'GaugeAlarmIntegrationTest', { + testCases: [stack], +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index af1404692a582..dedad72aa99fe 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -1925,6 +1925,16 @@ export class WindowsBuildImage implements IBuildImage { imageType: WindowsImageType.SERVER_2019, }); + /** + * The standard CodeBuild image `aws/codebuild/windows-base:2019-2.0`, which is + * based off Windows Server Core 2019. + */ + public static readonly WIN_SERVER_CORE_2019_BASE_2_0: IBuildImage = new WindowsBuildImage({ + imageId: 'aws/codebuild/windows-base:2019-2.0', + imagePullPrincipalType: ImagePullPrincipalType.CODEBUILD, + imageType: WindowsImageType.SERVER_2019, + }); + /** * @returns a Windows build image from a Docker Hub image. */ diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index b9c15e0b82297..bf7fc7931f72f 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -93,7 +93,7 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", - "aws-sdk": "^2.848.0", + "aws-sdk": "^2.1211.0", "jest": "^27.5.1" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-codebuild/test/project.test.ts b/packages/@aws-cdk/aws-codebuild/test/project.test.ts index 101ebd49b6f2d..b5e3fa61d7432 100644 --- a/packages/@aws-cdk/aws-codebuild/test/project.test.ts +++ b/packages/@aws-cdk/aws-codebuild/test/project.test.ts @@ -795,6 +795,7 @@ describe('Environment', () => { test.each([ ['Standard 6.0', codebuild.LinuxBuildImage.STANDARD_6_0, 'aws/codebuild/standard:6.0'], ['Amazon Linux 4.0', codebuild.LinuxBuildImage.AMAZON_LINUX_2_4, 'aws/codebuild/amazonlinux2-x86_64-standard:4.0'], + ['Windows Server Core 2019 2.0', codebuild.WindowsBuildImage.WIN_SERVER_CORE_2019_BASE_2_0, 'aws/codebuild/windows-base:2019-2.0'], ])('has build image for %s', (_, buildImage, expected) => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index e115ab261cf07..bde04cf3ebe28 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -93,7 +93,7 @@ "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", - "aws-sdk": "^2.848.0", + "aws-sdk": "^2.1211.0", "jest": "^27.5.1" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-dynamodb/README.md b/packages/@aws-cdk/aws-dynamodb/README.md index f9b5771953b14..bc7ca16e5b182 100644 --- a/packages/@aws-cdk/aws-dynamodb/README.md +++ b/packages/@aws-cdk/aws-dynamodb/README.md @@ -208,3 +208,26 @@ const table = new dynamodb.Table(this, 'Table', { kinesisStream: stream, }); ``` + +## Alarm metrics + +Alarms can be configured on the DynamoDB table to captured metric data + +```ts +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; + +const table = new dynamodb.Table(this, 'Table', { + partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, +}); + +const metric = table.metricThrottledRequestsForOperations({ + operations: [dynamodb.Operation.PUT_ITEM], + period: Duration.minutes(1), +}); + +new cloudwatch.Alarm(stack, 'Alarm', { + metric: metric, + evaluationPeriods: 1, + threshold: 1, +}); +``` diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index f518ea9a4914a..539761ba96032 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -36,6 +36,11 @@ export interface SystemErrorsForOperationsMetricOptions extends cloudwatch.Metri } +/** + * Options for configuring metrics that considers multiple operations. + */ +export interface OperationsMetricOptions extends SystemErrorsForOperationsMetricOptions {} + /** * Supported DynamoDB table operations. */ @@ -534,9 +539,18 @@ export interface ITable extends IResource { * * @param props properties of a metric * + * @deprecated use `metricThrottledRequestsForOperations` */ metricThrottledRequests(props?: cloudwatch.MetricOptions): cloudwatch.Metric; + /** + * Metric for throttled requests + * + * @param props properties of a metric + * + */ + metricThrottledRequestsForOperations(props?: OperationsMetricOptions): cloudwatch.IMetric; + /** * Metric for the successful request latency * @@ -869,18 +883,6 @@ abstract class TableBase extends Resource implements ITable { return this.metric('ThrottledRequests', { statistic: 'sum', ...props }); } - /** - * How many requests are throttled on this table, for the given operation - * - * Default: sum over 5 minutes - */ - public metricThrottledRequestsForOperation(operation: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return new cloudwatch.Metric({ - ...DynamoDBMetrics.throttledRequestsSum({ Operation: operation, TableName: this.tableName }), - ...props, - }).attachTo(this); - } - /** * Metric for the successful request latency this table. * @@ -904,6 +906,29 @@ abstract class TableBase extends Resource implements ITable { }).attachTo(this); } + /** + * How many requests are throttled on this table, for the given operation + * + * Default: sum over 5 minutes + */ + public metricThrottledRequestsForOperation(operation: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return new cloudwatch.Metric({ + ...DynamoDBMetrics.throttledRequestsSum({ Operation: operation, TableName: this.tableName }), + ...props, + }).attachTo(this); + } + + /** + * How many requests are throttled on this table. + * + * This will sum errors across all possible operations. + * Note that by default, each individual metric will be calculated as a sum over a period of 5 minutes. + * You can customize this by using the `statistic` and `period` properties. + */ + public metricThrottledRequestsForOperations(props?: OperationsMetricOptions): cloudwatch.IMetric { + return this.sumMetricsForOperations('ThrottledRequests', 'Sum of throttled requests across all operations', props); + } + /** * Metric for the system errors this table. * @@ -912,20 +937,30 @@ abstract class TableBase extends Resource implements ITable { * You can customize this by using the `statistic` and `period` properties. */ public metricSystemErrorsForOperations(props?: SystemErrorsForOperationsMetricOptions): cloudwatch.IMetric { + return this.sumMetricsForOperations('SystemErrors', 'Sum of errors across all operations', props); + } + /** + * Create a math expression for operations. + * + * @param metricName The metric name. + * @param expressionLabel Label for expression + * @param props operation list + */ + private sumMetricsForOperations(metricName: string, expressionLabel: string, props?: OperationsMetricOptions): cloudwatch.IMetric { if (props?.dimensions?.Operation) { throw new Error("The Operation dimension is not supported. Use the 'operations' property."); } const operations = props?.operations ?? Object.values(Operation); - const values = this.createMetricsForOperations('SystemErrors', operations, { statistic: 'sum', ...props }); + const values = this.createMetricsForOperations(metricName, operations, { statistic: 'sum', ...props }); const sum = new cloudwatch.MathExpression({ expression: `${Object.keys(values).join(' + ')}`, usingMetrics: { ...values }, color: props?.color, - label: 'Sum of errors across all operations', + label: expressionLabel, period: props?.period, }); diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index d3518666a91f8..06cc628f7d660 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -82,13 +82,14 @@ "devDependencies": { "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", - "@aws-cdk/integ-runner": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", + "@aws-cdk/integ-runner": "0.0.0", + "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/aws-lambda": "^8.10.104", "@types/jest": "^27.5.2", "@types/sinon": "^9.0.11", - "aws-sdk": "^2.848.0", + "aws-sdk": "^2.1211.0", "aws-sdk-mock": "5.6.0", "jest": "^27.5.1", "sinon": "^9.2.4", diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/alarm-metrics.assets.json b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/alarm-metrics.assets.json new file mode 100644 index 0000000000000..84cb2270266dc --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/alarm-metrics.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "fbae493d10d1cf974dda665d5d00077b8ac81e2d8edeec024da94102b7db2cc8": { + "source": { + "path": "alarm-metrics.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "fbae493d10d1cf974dda665d5d00077b8ac81e2d8edeec024da94102b7db2cc8.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/alarm-metrics.template.json b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/alarm-metrics.template.json new file mode 100644 index 0000000000000..c76f714107830 --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/alarm-metrics.template.json @@ -0,0 +1,187 @@ +{ + "Resources": { + "TableCD117FA1": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "metric", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "metric", + "AttributeType": "S" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "TableThrottleAlarm606592BC": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "Metrics": [ + { + "Expression": "putitem + scan", + "Id": "expr_1", + "Label": "Sum of throttled requests across all operations" + }, + { + "Id": "putitem", + "MetricStat": { + "Metric": { + "Dimensions": [ + { + "Name": "Operation", + "Value": "PutItem" + }, + { + "Name": "TableName", + "Value": { + "Ref": "TableCD117FA1" + } + } + ], + "MetricName": "ThrottledRequests", + "Namespace": "AWS/DynamoDB" + }, + "Period": 60, + "Stat": "Sum" + }, + "ReturnData": false + }, + { + "Id": "scan", + "MetricStat": { + "Metric": { + "Dimensions": [ + { + "Name": "Operation", + "Value": "Scan" + }, + { + "Name": "TableName", + "Value": { + "Ref": "TableCD117FA1" + } + } + ], + "MetricName": "ThrottledRequests", + "Namespace": "AWS/DynamoDB" + }, + "Period": 60, + "Stat": "Sum" + }, + "ReturnData": false + } + ], + "Threshold": 1 + } + }, + "TableErrorAlarm12A4E2F3": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "Metrics": [ + { + "Expression": "putitem + scan", + "Id": "expr_1", + "Label": "Sum of errors across all operations" + }, + { + "Id": "putitem", + "MetricStat": { + "Metric": { + "Dimensions": [ + { + "Name": "Operation", + "Value": "PutItem" + }, + { + "Name": "TableName", + "Value": { + "Ref": "TableCD117FA1" + } + } + ], + "MetricName": "SystemErrors", + "Namespace": "AWS/DynamoDB" + }, + "Period": 60, + "Stat": "Sum" + }, + "ReturnData": false + }, + { + "Id": "scan", + "MetricStat": { + "Metric": { + "Dimensions": [ + { + "Name": "Operation", + "Value": "Scan" + }, + { + "Name": "TableName", + "Value": { + "Ref": "TableCD117FA1" + } + } + ], + "MetricName": "SystemErrors", + "Namespace": "AWS/DynamoDB" + }, + "Period": 60, + "Stat": "Sum" + }, + "ReturnData": false + } + ], + "Threshold": 1 + } + } + }, + "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. [cdk:skip]" + } + }, + "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/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/alarmmetricsintegDefaultTestDeployAssert8721BBC0.assets.json b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/alarmmetricsintegDefaultTestDeployAssert8721BBC0.assets.json new file mode 100644 index 0000000000000..90682b0b0fe2b --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/alarmmetricsintegDefaultTestDeployAssert8721BBC0.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "alarmmetricsintegDefaultTestDeployAssert8721BBC0.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/alarmmetricsintegDefaultTestDeployAssert8721BBC0.template.json b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/alarmmetricsintegDefaultTestDeployAssert8721BBC0.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/alarmmetricsintegDefaultTestDeployAssert8721BBC0.template.json @@ -0,0 +1,36 @@ +{ + "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. [cdk:skip]" + } + }, + "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/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/integ.json b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/integ.json new file mode 100644 index 0000000000000..c32af717a0060 --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "alarm-metrics-integ/DefaultTest": { + "stacks": [ + "alarm-metrics" + ], + "assertionStack": "alarm-metrics-integ/DefaultTest/DeployAssert", + "assertionStackName": "alarmmetricsintegDefaultTestDeployAssert8721BBC0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..02db269fe97ae --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/manifest.json @@ -0,0 +1,123 @@ +{ + "version": "21.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "alarm-metrics.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "alarm-metrics.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "alarm-metrics": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "alarm-metrics.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/fbae493d10d1cf974dda665d5d00077b8ac81e2d8edeec024da94102b7db2cc8.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "alarm-metrics.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "alarm-metrics.assets" + ], + "metadata": { + "/alarm-metrics/Table/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TableCD117FA1" + } + ], + "/alarm-metrics/TableThrottleAlarm/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TableThrottleAlarm606592BC" + } + ], + "/alarm-metrics/TableErrorAlarm/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TableErrorAlarm12A4E2F3" + } + ], + "/alarm-metrics/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/alarm-metrics/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "alarm-metrics" + }, + "alarmmetricsintegDefaultTestDeployAssert8721BBC0.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "alarmmetricsintegDefaultTestDeployAssert8721BBC0.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "alarmmetricsintegDefaultTestDeployAssert8721BBC0": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "alarmmetricsintegDefaultTestDeployAssert8721BBC0.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "alarmmetricsintegDefaultTestDeployAssert8721BBC0.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "alarmmetricsintegDefaultTestDeployAssert8721BBC0.assets" + ], + "metadata": { + "/alarm-metrics-integ/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/alarm-metrics-integ/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "alarm-metrics-integ/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/tree.json b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/tree.json new file mode 100644 index 0000000000000..b79705862e78b --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.alarm-metrics.integ.snapshot/tree.json @@ -0,0 +1,276 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "alarm-metrics": { + "id": "alarm-metrics", + "path": "alarm-metrics", + "children": { + "Table": { + "id": "Table", + "path": "alarm-metrics/Table", + "children": { + "Resource": { + "id": "Resource", + "path": "alarm-metrics/Table/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::DynamoDB::Table", + "aws:cdk:cloudformation:props": { + "keySchema": [ + { + "attributeName": "metric", + "keyType": "HASH" + } + ], + "attributeDefinitions": [ + { + "attributeName": "metric", + "attributeType": "S" + } + ], + "provisionedThroughput": { + "readCapacityUnits": 5, + "writeCapacityUnits": 5 + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-dynamodb.CfnTable", + "version": "0.0.0" + } + }, + "ScalingRole": { + "id": "ScalingRole", + "path": "alarm-metrics/Table/ScalingRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-dynamodb.Table", + "version": "0.0.0" + } + }, + "TableThrottleAlarm": { + "id": "TableThrottleAlarm", + "path": "alarm-metrics/TableThrottleAlarm", + "children": { + "Resource": { + "id": "Resource", + "path": "alarm-metrics/TableThrottleAlarm/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudWatch::Alarm", + "aws:cdk:cloudformation:props": { + "comparisonOperator": "GreaterThanOrEqualToThreshold", + "evaluationPeriods": 1, + "metrics": [ + { + "expression": "putitem + scan", + "id": "expr_1", + "label": "Sum of throttled requests across all operations" + }, + { + "metricStat": { + "metric": { + "metricName": "ThrottledRequests", + "namespace": "AWS/DynamoDB", + "dimensions": [ + { + "name": "Operation", + "value": "PutItem" + }, + { + "name": "TableName", + "value": { + "Ref": "TableCD117FA1" + } + } + ] + }, + "period": 60, + "stat": "Sum" + }, + "id": "putitem", + "returnData": false + }, + { + "metricStat": { + "metric": { + "metricName": "ThrottledRequests", + "namespace": "AWS/DynamoDB", + "dimensions": [ + { + "name": "Operation", + "value": "Scan" + }, + { + "name": "TableName", + "value": { + "Ref": "TableCD117FA1" + } + } + ] + }, + "period": 60, + "stat": "Sum" + }, + "id": "scan", + "returnData": false + } + ], + "threshold": 1 + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.CfnAlarm", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.Alarm", + "version": "0.0.0" + } + }, + "TableErrorAlarm": { + "id": "TableErrorAlarm", + "path": "alarm-metrics/TableErrorAlarm", + "children": { + "Resource": { + "id": "Resource", + "path": "alarm-metrics/TableErrorAlarm/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudWatch::Alarm", + "aws:cdk:cloudformation:props": { + "comparisonOperator": "GreaterThanOrEqualToThreshold", + "evaluationPeriods": 1, + "metrics": [ + { + "expression": "putitem + scan", + "id": "expr_1", + "label": "Sum of errors across all operations" + }, + { + "metricStat": { + "metric": { + "metricName": "SystemErrors", + "namespace": "AWS/DynamoDB", + "dimensions": [ + { + "name": "Operation", + "value": "PutItem" + }, + { + "name": "TableName", + "value": { + "Ref": "TableCD117FA1" + } + } + ] + }, + "period": 60, + "stat": "Sum" + }, + "id": "putitem", + "returnData": false + }, + { + "metricStat": { + "metric": { + "metricName": "SystemErrors", + "namespace": "AWS/DynamoDB", + "dimensions": [ + { + "name": "Operation", + "value": "Scan" + }, + { + "name": "TableName", + "value": { + "Ref": "TableCD117FA1" + } + } + ] + }, + "period": 60, + "stat": "Sum" + }, + "id": "scan", + "returnData": false + } + ], + "threshold": 1 + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.CfnAlarm", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.Alarm", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "alarm-metrics-integ": { + "id": "alarm-metrics-integ", + "path": "alarm-metrics-integ", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "alarm-metrics-integ/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "alarm-metrics-integ/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "alarm-metrics-integ/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts index 72a0de9f1491c..35145edd0d57c 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts @@ -1,5 +1,6 @@ import { Annotations, Match, Template } from '@aws-cdk/assertions'; import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as kinesis from '@aws-cdk/aws-kinesis'; import * as kms from '@aws-cdk/aws-kms'; @@ -3009,6 +3010,96 @@ test('L1 inside L2 expects removalpolicy to have been set', () => { }).toThrow(/is a stateful resource type/); }); +test('System errors metrics', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'Stack'); + + // WHEN + const table = new Table(stack, 'Table', { + partitionKey: { name: 'metric', type: AttributeType.STRING }, + }); + const metricTableThrottled = table.metricSystemErrorsForOperations({ + operations: [Operation.SCAN], + period: Duration.minutes(1), + }); + new cloudwatch.Alarm(stack, 'TableErrorAlarm', { + metric: metricTableThrottled, + evaluationPeriods: 1, + threshold: 1, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { + Metrics: Match.arrayWith([ + Match.objectLike({ + Expression: 'scan', + }), + Match.objectLike({ + MetricStat: Match.objectLike({ + Metric: Match.objectLike({ + Dimensions: Match.arrayWith([ + Match.objectLike({ + Name: 'Operation', + }), + Match.objectLike({ + Name: 'TableName', + }), + ]), + MetricName: 'SystemErrors', + Namespace: 'AWS/DynamoDB', + }), + }), + }), + ]), + }); +}); + +test('Throttled requests metrics', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'Stack'); + + // WHEN + const table = new Table(stack, 'Table', { + partitionKey: { name: 'metric', type: AttributeType.STRING }, + }); + const metricTableThrottled = table.metricThrottledRequestsForOperations({ + operations: [Operation.PUT_ITEM], + period: Duration.minutes(1), + }); + new cloudwatch.Alarm(stack, 'TableThrottleAlarm', { + metric: metricTableThrottled, + evaluationPeriods: 1, + threshold: 1, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { + Metrics: Match.arrayWith([ + Match.objectLike({ + Expression: 'putitem', + }), + Match.objectLike({ + MetricStat: Match.objectLike({ + Metric: Match.objectLike({ + Dimensions: Match.arrayWith([ + Match.objectLike({ + Name: 'Operation', + }), + Match.objectLike({ + Name: 'TableName', + }), + ]), + MetricName: 'ThrottledRequests', + Namespace: 'AWS/DynamoDB', + }), + }), + }), + ]), + }); +}); + function testGrant(expectedActions: string[], invocation: (user: iam.IPrincipal, table: Table) => void) { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.alarm-metrics.ts b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.alarm-metrics.ts new file mode 100644 index 0000000000000..df7494b4b388a --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.alarm-metrics.ts @@ -0,0 +1,42 @@ +import { Alarm } from '@aws-cdk/aws-cloudwatch'; +import { App, Duration, Stack, StackProps } from '@aws-cdk/core'; +import { IntegTest } from '@aws-cdk/integ-tests'; +import { Construct } from 'constructs'; +import { AttributeType, Operation, Table } from '../lib'; + +export class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const table = new Table(this, 'Table', { + partitionKey: { name: 'metric', type: AttributeType.STRING }, + }); + const metricTableThrottled = table.metricThrottledRequestsForOperations({ + operations: [Operation.PUT_ITEM, Operation.SCAN], + period: Duration.minutes(1), + }); + new Alarm(this, 'TableThrottleAlarm', { + metric: metricTableThrottled, + evaluationPeriods: 1, + threshold: 1, + }); + const metricTableError = table.metricSystemErrorsForOperations({ + operations: [Operation.PUT_ITEM, Operation.SCAN], + period: Duration.minutes(1), + }); + new Alarm(this, 'TableErrorAlarm', { + metric: metricTableError, + evaluationPeriods: 1, + threshold: 1, + }); + } +} + +const app = new App(); +const stack = new TestStack(app, 'alarm-metrics'); + +new IntegTest(app, 'alarm-metrics-integ', { + testCases: [stack], +}); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index 8df78cf2aaef1..1b25fa8e2f742 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -322,6 +322,7 @@ export class InterfaceVpcEndpointAwsService implements IInterfaceVpcEndpointServ public static readonly LAMBDA = new InterfaceVpcEndpointAwsService('lambda'); public static readonly TRANSCRIBE = new InterfaceVpcEndpointAwsService('transcribe'); public static readonly XRAY = new InterfaceVpcEndpointAwsService('xray'); + public static readonly SECURITYHUB = new InterfaceVpcEndpointAwsService('securityhub'); /** * The name of the service. e.g. com.amazonaws.us-east-1.ecs diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index a6cdea1064707..5ed0d723801f8 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -323,6 +323,7 @@ "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.LAMBDA", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.TRANSCRIBE", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.XRAY", + "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.SECURITYHUB", "docs-public-apis:@aws-cdk/aws-ec2.Port.toString", "docs-public-apis:@aws-cdk/aws-ec2.PrivateSubnet.fromPrivateSubnetAttributes", "docs-public-apis:@aws-cdk/aws-ec2.PublicSubnet.fromPublicSubnetAttributes", 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 849948add5f13..c3764598114ad 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -149,6 +149,10 @@ export interface TaskDefinitionProps extends CommonTaskDefinitionProps { * * 4096 (4 vCPU) - Available memory values: Between 8192 (8 GB) and 30720 (30 GB) in increments of 1024 (1 GB) * + * 8192 (8 vCPU) - Available memory values: Between 16384 (16 GB) and 61440 (60 GB) in increments of 4096 (4 GB) + * + * 16384 (16 vCPU) - Available memory values: Between 32768 (32 GB) and 122880 (120 GB) in increments of 8192 (8 GB) + * * @default - CPU units are not specified. */ readonly cpu?: string; @@ -170,6 +174,10 @@ export interface TaskDefinitionProps extends CommonTaskDefinitionProps { * * Between 8192 (8 GB) and 30720 (30 GB) in increments of 1024 (1 GB) - Available cpu values: 4096 (4 vCPU) * + * Between 16384 (16 GB) and 61440 (60 GB) in increments of 4096 (4 GB) - Available cpu values: 8192 (8 vCPU) + * + * Between 32768 (32 GB) and 122880 (120 GB) in increments of 8192 (8 GB) - Available cpu values: 16384 (16 vCPU) + * * @default - Memory used by task is not specified. */ readonly memoryMiB?: string; diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts index c4e0831fbb4f7..5f5a8a99805e7 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts @@ -30,6 +30,10 @@ export interface FargateTaskDefinitionProps extends CommonTaskDefinitionProps { * * 4096 (4 vCPU) - Available memory values: Between 8192 (8 GB) and 30720 (30 GB) in increments of 1024 (1 GB) * + * 8192 (8 vCPU) - Available memory values: Between 16384 (16 GB) and 61440 (60 GB) in increments of 4096 (4 GB) + * + * 16384 (16 vCPU) - Available memory values: Between 32768 (32 GB) and 122880 (120 GB) in increments of 8192 (8 GB) + * * @default 256 */ readonly cpu?: number; @@ -48,6 +52,10 @@ export interface FargateTaskDefinitionProps extends CommonTaskDefinitionProps { * * Between 8192 (8 GB) and 30720 (30 GB) in increments of 1024 (1 GB) - Available cpu values: 4096 (4 vCPU) * + * Between 16384 (16 GB) and 61440 (60 GB) in increments of 4096 (4 GB) - Available cpu values: 8192 (8 vCPU) + * + * Between 32768 (32 GB) and 122880 (120 GB) in increments of 8192 (8 GB) - Available cpu values: 16384 (16 vCPU) + * * @default 512 */ readonly memoryLimitMiB?: number; diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index 62d76939194af..9774d2b3517e6 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -1160,6 +1160,23 @@ cluster.addHelmChart('test-chart', { }); ``` +Nested values passed to the `values` parameter should be provided as a nested dictionary: + +```ts +cluster.addHelmChart('ExternalSecretsOperator', { + chart: 'external-secrets', + release: 'external-secrets', + repository: 'https://charts.external-secrets.io', + namespace: 'external-secrets', + values: { + installCRDs: true, + webhook: { + port: 9443 + } + }, +}); +``` + ### OCI Charts OCI charts are also supported. diff --git a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts index 96bc0e6153846..4ac6e5f176cbd 100644 --- a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts +++ b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts @@ -51,6 +51,11 @@ export interface HelmChartOptions { /** * The values to be used by the chart. + * For nested values use a nested dictionary. For example: + * values: { + * installationCRDs: true, + * webhook: { port: 9443 } + * } * @default - No values are provided to the chart. */ readonly values?: {[key: string]: any}; diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index 579a67b6e7dcc..ee31ab3715eb3 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -90,8 +90,8 @@ "@types/jest": "^27.5.2", "@types/sinon": "^9.0.11", "@types/yaml": "1.9.6", - "aws-sdk": "^2.848.0", - "cdk8s": "^2.4.27", + "aws-sdk": "^2.1211.0", + "cdk8s": "^2.4.33", "cdk8s-plus-21": "^2.0.0-beta.12", "jest": "^27.5.1", "sinon": "^9.2.4" diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index 5f658168107bd..4599f289d892a 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -391,20 +391,19 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat ret.push('At least one of \'port\' or \'protocol\' is required for a non-Lambda TargetGroup'); } - if (this.healthCheck && this.healthCheck.protocol) { - - if (ALB_HEALTH_CHECK_PROTOCOLS.includes(this.healthCheck.protocol)) { - if (this.healthCheck.interval && this.healthCheck.timeout && - this.healthCheck.interval.toMilliseconds() <= this.healthCheck.timeout.toMilliseconds()) { - ret.push(`Healthcheck interval ${this.healthCheck.interval.toHumanString()} must be greater than the timeout ${this.healthCheck.timeout.toHumanString()}`); - } + if (this.healthCheck) { + if (this.healthCheck.interval && this.healthCheck.timeout && + this.healthCheck.interval.toMilliseconds() <= this.healthCheck.timeout.toMilliseconds()) { + ret.push(`Healthcheck interval ${this.healthCheck.interval.toHumanString()} must be greater than the timeout ${this.healthCheck.timeout.toHumanString()}`); } - if (!ALB_HEALTH_CHECK_PROTOCOLS.includes(this.healthCheck.protocol)) { - ret.push([ - `Health check protocol '${this.healthCheck.protocol}' is not supported. `, - `Must be one of [${ALB_HEALTH_CHECK_PROTOCOLS.join(', ')}]`, - ].join('')); + if (this.healthCheck.protocol) { + if (!ALB_HEALTH_CHECK_PROTOCOLS.includes(this.healthCheck.protocol)) { + ret.push([ + `Health check protocol '${this.healthCheck.protocol}' is not supported. `, + `Must be one of [${ALB_HEALTH_CHECK_PROTOCOLS.join(', ')}]`, + ].join('')); + } } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts index 72c4ce3969885..889565ffb0d93 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts @@ -30,7 +30,7 @@ export abstract class ImportedTargetGroupBase extends Construct implements ITarg super(scope, id); this.targetGroupArn = props.targetGroupArn; - this.targetGroupName = cdk.Stack.of(scope).splitArn(props.targetGroupArn, cdk.ArnFormat.SLASH_RESOURCE_SLASH_RESOURCE_NAME).resourceName!.split('/')[0]; + this.targetGroupName = cdk.Stack.of(scope).splitArn(props.targetGroupArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName!.split('/')[0]; this.loadBalancerArns = props.loadBalancerArns || cdk.Aws.NO_VALUE; } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts index 57a3aabe6a11b..c11ce5a524a7f 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts @@ -528,6 +528,27 @@ describe('tests', () => { }).toThrow('Healthcheck interval 1 minute must be greater than the timeout 2 minutes'); }); + test('Throws validation error, when `configureHealthCheck()`protocol is undefined and `interval` is smaller than `timeout`', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + const vpc = new ec2.Vpc(stack, 'VPC', {}); + const tg = new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { + vpc, + }); + + // WHEN + tg.configureHealthCheck({ + interval: cdk.Duration.seconds(60), + timeout: cdk.Duration.seconds(120), + }); + + // THEN + expect(() => { + app.synth(); + }).toThrow('Healthcheck interval 1 minute must be greater than the timeout 2 minute'); + }); + test('imported targetGroup has targetGroupName', () => { // GIVEN const app = new cdk.App(); @@ -541,4 +562,51 @@ describe('tests', () => { // THEN expect(importedTg.targetGroupName).toEqual('myAlbTargetGroup'); }); + + test('imported targetGroup with imported ARN has targetGroupName', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + + // WHEN + const importedTgArn = cdk.Fn.importValue('ImportTargetGroupArn'); + const importedTg = elbv2.ApplicationTargetGroup.fromTargetGroupAttributes(stack, 'importedTg', { + targetGroupArn: importedTgArn, + }); + new cdk.CfnOutput(stack, 'TargetGroupOutput', { + value: importedTg.targetGroupName, + }); + + // THEN + Template.fromStack(stack).hasOutput('TargetGroupOutput', { + Value: { + 'Fn::Select': [ + // myAlbTargetGroup + 1, + { + 'Fn::Split': [ + // [targetgroup, myAlbTargetGroup, 73e2d6bc24d8a067] + '/', + { + 'Fn::Select': [ + // targetgroup/myAlbTargetGroup/73e2d6bc24d8a067 + 5, + { + 'Fn::Split': [ + // [arn, aws, elasticloadbalancing, us-west-2, 123456789012, targetgroup/myAlbTargetGroup/73e2d6bc24d8a067] + ':', + { + // arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/myAlbTargetGroup/73e2d6bc24d8a067 + 'Fn::ImportValue': 'ImportTargetGroupArn', + }, + ], + }, + ], + }, + ], + }, + ], + }, + }); + }); }); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/target-group.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/target-group.test.ts index 4f634d82cfc00..6d94b4ee4d45e 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/target-group.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/target-group.test.ts @@ -635,4 +635,51 @@ describe('tests', () => { // THEN expect(importedTg.targetGroupName).toEqual('myNlbTargetGroup'); }); + + test('imported targetGroup with imported ARN has targetGroupName', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + + // WHEN + const importedTgArn = cdk.Fn.importValue('ImportTargetGroupArn'); + const importedTg = elbv2.ApplicationTargetGroup.fromTargetGroupAttributes(stack, 'importedTg', { + targetGroupArn: importedTgArn, + }); + new cdk.CfnOutput(stack, 'TargetGroupOutput', { + value: importedTg.targetGroupName, + }); + + // THEN + Template.fromStack(stack).hasOutput('TargetGroupOutput', { + Value: { + 'Fn::Select': [ + // myNlbTargetGroup + 1, + { + 'Fn::Split': [ + // [targetgroup, myNlbTargetGroup, 73e2d6bc24d8a067] + '/', + { + 'Fn::Select': [ + // targetgroup/myNlbTargetGroup/73e2d6bc24d8a067 + 5, + { + 'Fn::Split': [ + // [arn, aws, elasticloadbalancing, us-west-2, 123456789012, targetgroup/myNlbTargetGroup/73e2d6bc24d8a067] + ':', + { + // arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/myNlbTargetGroup/73e2d6bc24d8a067 + 'Fn::ImportValue': 'ImportTargetGroupArn', + }, + ], + }, + ], + }, + ], + }, + ], + }, + }); + }); }); diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index 4ea0fc4de7f0b..70c65b6b65abc 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -89,7 +89,7 @@ "@aws-cdk/integ-runner": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", - "aws-sdk": "^2.848.0", + "aws-sdk": "^2.1211.0", "aws-sdk-mock": "5.6.0", "jest": "^27.5.1" }, diff --git a/packages/@aws-cdk/aws-events-targets/test/logs/integ.log-group.ts b/packages/@aws-cdk/aws-events-targets/test/logs/integ.log-group.ts index 88004a8f44c5f..975e006283b07 100644 --- a/packages/@aws-cdk/aws-events-targets/test/logs/integ.log-group.ts +++ b/packages/@aws-cdk/aws-events-targets/test/logs/integ.log-group.ts @@ -2,8 +2,8 @@ import * as events from '@aws-cdk/aws-events'; import * as logs from '@aws-cdk/aws-logs'; import * as sqs from '@aws-cdk/aws-sqs'; import * as cdk from '@aws-cdk/core'; +import { IntegTest, ExpectedResult } from '@aws-cdk/integ-tests'; import * as targets from '../../lib'; -import { IntegTest, ExpectedResult, AssertionsProvider } from '@aws-cdk/integ-tests'; import { LogGroupTargetInput } from '../../lib'; const app = new cdk.App(); @@ -71,8 +71,7 @@ const putEvent = integ.assertions.awsApiCall('EventBridge', 'putEvents', { }, ], }); -const assertionProvider = putEvent.node.tryFindChild('SdkProvider') as AssertionsProvider; -assertionProvider.addPolicyStatementFromSdkCall('events', 'PutEvents'); +putEvent.provider.addPolicyStatementFromSdkCall('events', 'PutEvents'); const logEvents = integ.assertions.awsApiCall('CloudWatchLogs', 'filterLogEvents', { logGroupName: logGroup2.logGroupName, @@ -80,7 +79,7 @@ const logEvents = integ.assertions.awsApiCall('CloudWatchLogs', 'filterLogEvents limit: 1, }); -logEvents.node.addDependency(putEvent); +putEvent.next(logEvents); logEvents.assertAtPath('events.0.message', ExpectedResult.stringLikeRegexp(expectedValue)); diff --git a/packages/@aws-cdk/aws-events/package.json b/packages/@aws-cdk/aws-events/package.json index be6e389a3bc0e..072721b200824 100644 --- a/packages/@aws-cdk/aws-events/package.json +++ b/packages/@aws-cdk/aws-events/package.json @@ -87,8 +87,6 @@ "@aws-cdk/integ-runner": "0.0.0", "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", - "@aws-cdk/integ-runner": "0.0.0", - "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", "jest": "^27.5.1" diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json b/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json index 6039144f28c3c..945f8cd6ea48d 100644 --- a/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json @@ -81,7 +81,7 @@ "@aws-cdk/integ-runner": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", - "aws-sdk": "^2.848.0", + "aws-sdk": "^2.1211.0", "aws-sdk-mock": "5.6.0", "jest": "^27.5.1" }, diff --git a/packages/@aws-cdk/aws-lambda-nodejs/package.json b/packages/@aws-cdk/aws-lambda-nodejs/package.json index 7e0f97a8cec3c..4b9f3b9adf43c 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/package.json +++ b/packages/@aws-cdk/aws-lambda-nodejs/package.json @@ -79,7 +79,7 @@ "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", "delay": "5.0.0", - "esbuild": "^0.15.7" + "esbuild": "^0.15.8" }, "dependencies": { "@aws-cdk/aws-lambda": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda/test/integ.runtime.inlinecode.ts b/packages/@aws-cdk/aws-lambda/test/integ.runtime.inlinecode.ts index 25177767ca2b3..cc15712cc4d1e 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.runtime.inlinecode.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.runtime.inlinecode.ts @@ -15,13 +15,6 @@ const app = new App(); const stack = new Stack(app, 'aws-cdk-lambda-runtime-inlinecode'); -const node12xfn = new Function(stack, 'NODEJS_12_X', { - code: new InlineCode('exports.handler = async function(event) { return "success" }'), - handler: 'index.handler', - runtime: Runtime.NODEJS_12_X, -}); -new CfnOutput(stack, 'NODEJS_12_X-functionName', { value: node12xfn.functionName }); - const python37 = new Function(stack, 'PYTHON_3_7', { code: new InlineCode('def handler(event, context):\n return "success"'), handler: 'index.handler', diff --git a/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/aws-cdk-lambda-runtime-inlinecode.assets.json b/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/aws-cdk-lambda-runtime-inlinecode.assets.json index ab7c438558f12..4eb46a8ef9093 100644 --- a/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/aws-cdk-lambda-runtime-inlinecode.assets.json +++ b/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/aws-cdk-lambda-runtime-inlinecode.assets.json @@ -1,7 +1,7 @@ { "version": "21.0.0", "files": { - "fc0190b17e2248f645e6fb64b3fcc344774325a53c839f3e3d06cf7c1ba8b516": { + "33c56e02291eaea4afa147afc5d42ce6d80c4f482906b78fa47ddbe2582f19bc": { "source": { "path": "aws-cdk-lambda-runtime-inlinecode.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "fc0190b17e2248f645e6fb64b3fcc344774325a53c839f3e3d06cf7c1ba8b516.json", + "objectKey": "33c56e02291eaea4afa147afc5d42ce6d80c4f482906b78fa47ddbe2582f19bc.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/aws-cdk-lambda-runtime-inlinecode.template.json b/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/aws-cdk-lambda-runtime-inlinecode.template.json index f662b0219245b..373feab3c118f 100644 --- a/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/aws-cdk-lambda-runtime-inlinecode.template.json +++ b/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/aws-cdk-lambda-runtime-inlinecode.template.json @@ -1,55 +1,5 @@ { "Resources": { - "NODEJS12XServiceRole59E71436": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "NODEJS12X8B8075A4": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "exports.handler = async function(event) { return \"success\" }" - }, - "Role": { - "Fn::GetAtt": [ - "NODEJS12XServiceRole59E71436", - "Arn" - ] - }, - "Handler": "index.handler", - "Runtime": "nodejs12.x" - }, - "DependsOn": [ - "NODEJS12XServiceRole59E71436" - ] - }, "PYTHON37ServiceRoleDE7E561E": { "Type": "AWS::IAM::Role", "Properties": { @@ -302,11 +252,6 @@ } }, "Outputs": { - "NODEJS12XfunctionName": { - "Value": { - "Ref": "NODEJS12X8B8075A4" - } - }, "PYTHON37functionName": { "Value": { "Ref": "PYTHON37D3A10E04" diff --git a/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/manifest.json index 73c76fe8d3f9c..73f920768ee1a 100644 --- a/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/manifest.json @@ -23,7 +23,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/fc0190b17e2248f645e6fb64b3fcc344774325a53c839f3e3d06cf7c1ba8b516.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/33c56e02291eaea4afa147afc5d42ce6d80c4f482906b78fa47ddbe2582f19bc.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -39,24 +39,6 @@ "aws-cdk-lambda-runtime-inlinecode.assets" ], "metadata": { - "/aws-cdk-lambda-runtime-inlinecode/NODEJS_12_X/ServiceRole/Resource": [ - { - "type": "aws:cdk:logicalId", - "data": "NODEJS12XServiceRole59E71436" - } - ], - "/aws-cdk-lambda-runtime-inlinecode/NODEJS_12_X/Resource": [ - { - "type": "aws:cdk:logicalId", - "data": "NODEJS12X8B8075A4" - } - ], - "/aws-cdk-lambda-runtime-inlinecode/NODEJS_12_X-functionName": [ - { - "type": "aws:cdk:logicalId", - "data": "NODEJS12XfunctionName" - } - ], "/aws-cdk-lambda-runtime-inlinecode/PYTHON_3_7/ServiceRole/Resource": [ { "type": "aws:cdk:logicalId", @@ -158,6 +140,24 @@ "type": "aws:cdk:logicalId", "data": "CheckBootstrapVersion" } + ], + "NODEJS12XServiceRole59E71436": [ + { + "type": "aws:cdk:logicalId", + "data": "NODEJS12XServiceRole59E71436", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } + ], + "NODEJS12X8B8075A4": [ + { + "type": "aws:cdk:logicalId", + "data": "NODEJS12X8B8075A4", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } ] }, "displayName": "aws-cdk-lambda-runtime-inlinecode" diff --git a/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/tree.json b/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/tree.json index ba8003992ebeb..42a8a90058e96 100644 --- a/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-lambda/test/runtime.inlinecode.integ.snapshot/tree.json @@ -9,104 +9,13 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.108" } }, "aws-cdk-lambda-runtime-inlinecode": { "id": "aws-cdk-lambda-runtime-inlinecode", "path": "aws-cdk-lambda-runtime-inlinecode", "children": { - "NODEJS_12_X": { - "id": "NODEJS_12_X", - "path": "aws-cdk-lambda-runtime-inlinecode/NODEJS_12_X", - "children": { - "ServiceRole": { - "id": "ServiceRole", - "path": "aws-cdk-lambda-runtime-inlinecode/NODEJS_12_X/ServiceRole", - "children": { - "Resource": { - "id": "Resource", - "path": "aws-cdk-lambda-runtime-inlinecode/NODEJS_12_X/ServiceRole/Resource", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::IAM::Role", - "aws:cdk:cloudformation:props": { - "assumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "managedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-iam.CfnRole", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-iam.Role", - "version": "0.0.0" - } - }, - "Resource": { - "id": "Resource", - "path": "aws-cdk-lambda-runtime-inlinecode/NODEJS_12_X/Resource", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::Lambda::Function", - "aws:cdk:cloudformation:props": { - "code": { - "zipFile": "exports.handler = async function(event) { return \"success\" }" - }, - "role": { - "Fn::GetAtt": [ - "NODEJS12XServiceRole59E71436", - "Arn" - ] - }, - "handler": "index.handler", - "runtime": "nodejs12.x" - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-lambda.CfnFunction", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-lambda.Function", - "version": "0.0.0" - } - }, - "NODEJS_12_X-functionName": { - "id": "NODEJS_12_X-functionName", - "path": "aws-cdk-lambda-runtime-inlinecode/NODEJS_12_X-functionName", - "constructInfo": { - "fqn": "@aws-cdk/core.CfnOutput", - "version": "0.0.0" - } - }, "PYTHON_3_7": { "id": "PYTHON_3_7", "path": "aws-cdk-lambda-runtime-inlinecode/PYTHON_3_7", diff --git a/packages/@aws-cdk/aws-logs/README.md b/packages/@aws-cdk/aws-logs/README.md index e1cac6e3813ab..c10a095920c65 100644 --- a/packages/@aws-cdk/aws-logs/README.md +++ b/packages/@aws-cdk/aws-logs/README.md @@ -163,6 +163,9 @@ const mf = new logs.MetricFilter(this, 'MetricFilter', { metricName: 'Latency', filterPattern: logs.FilterPattern.exists('$.latency'), metricValue: '$.latency', + dimensions: { + ErrorCode: '$.errorCode', + } }); //expose a metric from the metric filter diff --git a/packages/@aws-cdk/aws-logs/lib/log-group.ts b/packages/@aws-cdk/aws-logs/lib/log-group.ts index 3776b0c28f83a..f13741077af6b 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-group.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-group.ts @@ -537,4 +537,12 @@ export interface MetricFilterOptions { * @default No metric emitted. */ readonly defaultValue?: number; + + /** + * The fields to use as dimensions for the metric. One metric filter can include as many as three dimensions. + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-logs-metricfilter-metrictransformation.html#cfn-logs-metricfilter-metrictransformation-dimensions + * @default - No dimensions attached to metrics. + */ + readonly dimensions?: Record; } diff --git a/packages/@aws-cdk/aws-logs/lib/metric-filter.ts b/packages/@aws-cdk/aws-logs/lib/metric-filter.ts index e9b076b55c8a3..69efc0059f123 100644 --- a/packages/@aws-cdk/aws-logs/lib/metric-filter.ts +++ b/packages/@aws-cdk/aws-logs/lib/metric-filter.ts @@ -28,6 +28,10 @@ export class MetricFilter extends Resource { this.metricName = props.metricName; this.metricNamespace = props.metricNamespace; + if (Object.keys(props.dimensions ?? {}).length > 3) { + throw new Error('MetricFilter only supports a maximum of 3 Dimensions'); + } + // It looks odd to map this object to a singleton list, but that's how // we're supposed to do it according to the docs. // @@ -44,6 +48,7 @@ export class MetricFilter extends Resource { metricName: props.metricName, metricValue: props.metricValue ?? '1', defaultValue: props.defaultValue, + dimensions: props.dimensions ? Object.entries(props.dimensions).map(([key, value]) => ({ key, value })) : undefined, }], }); } diff --git a/packages/@aws-cdk/aws-logs/package.json b/packages/@aws-cdk/aws-logs/package.json index 130e85cc553cd..d74bc2de6aa09 100644 --- a/packages/@aws-cdk/aws-logs/package.json +++ b/packages/@aws-cdk/aws-logs/package.json @@ -89,7 +89,7 @@ "@types/aws-lambda": "^8.10.104", "@types/jest": "^27.5.2", "@types/sinon": "^9.0.11", - "aws-sdk": "^2.848.0", + "aws-sdk": "^2.1211.0", "aws-sdk-mock": "5.6.0", "jest": "^27.5.1", "nock": "^13.2.9", diff --git a/packages/@aws-cdk/aws-logs/test/integ.metricfilter-dimensions.ts b/packages/@aws-cdk/aws-logs/test/integ.metricfilter-dimensions.ts new file mode 100644 index 0000000000000..542f481be5817 --- /dev/null +++ b/packages/@aws-cdk/aws-logs/test/integ.metricfilter-dimensions.ts @@ -0,0 +1,32 @@ +import { App, RemovalPolicy, Stack, StackProps } from '@aws-cdk/core'; +import { IntegTest } from '@aws-cdk/integ-tests'; +import { FilterPattern, LogGroup, MetricFilter } from '../lib'; + +class TestStack extends Stack { + constructor(scope: App, id: string, props?: StackProps) { + super(scope, id, props); + + const logGroup = new LogGroup(this, 'LogGroup', { + removalPolicy: RemovalPolicy.DESTROY, + }); + + new MetricFilter(this, 'MetricFilter', { + logGroup, + metricNamespace: 'MyApp', + metricName: 'Latency', + filterPattern: FilterPattern.exists('$.latency'), + metricValue: '$.latency', + dimensions: { + ErrorCode: '$.errorCode', + }, + }); + } +} + +const app = new App(); +const testCase = new TestStack(app, 'aws-cdk-metricfilter-dimensions-integ'); + +new IntegTest(app, 'metricfilter-dimensions', { + testCases: [testCase], +}); +app.synth(); diff --git a/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/aws-cdk-metricfilter-dimensions-integ.assets.json b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/aws-cdk-metricfilter-dimensions-integ.assets.json new file mode 100644 index 0000000000000..5be191e7f4c29 --- /dev/null +++ b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/aws-cdk-metricfilter-dimensions-integ.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "3d99811cf4d8b2d453d889e936569b925ead97bdb93a86d122b34d68818be01d": { + "source": { + "path": "aws-cdk-metricfilter-dimensions-integ.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "3d99811cf4d8b2d453d889e936569b925ead97bdb93a86d122b34d68818be01d.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/aws-cdk-metricfilter-dimensions-integ.template.json b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/aws-cdk-metricfilter-dimensions-integ.template.json new file mode 100644 index 0000000000000..b2b3588df8f3f --- /dev/null +++ b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/aws-cdk-metricfilter-dimensions-integ.template.json @@ -0,0 +1,68 @@ +{ + "Resources": { + "LogGroupF5B46931": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731 + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "MetricFilter1B93B6E5": { + "Type": "AWS::Logs::MetricFilter", + "Properties": { + "FilterPattern": "{ $.latency = \"*\" }", + "LogGroupName": { + "Ref": "LogGroupF5B46931" + }, + "MetricTransformations": [ + { + "Dimensions": [ + { + "Key": "ErrorCode", + "Value": "$.errorCode" + } + ], + "MetricName": "Latency", + "MetricNamespace": "MyApp", + "MetricValue": "$.latency" + } + ] + } + } + }, + "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. [cdk:skip]" + } + }, + "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/aws-logs/test/metricfilter-dimensions.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/integ.json b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/integ.json new file mode 100644 index 0000000000000..eff8bab92b5dc --- /dev/null +++ b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "metricfilter-dimensions/DefaultTest": { + "stacks": [ + "aws-cdk-metricfilter-dimensions-integ" + ], + "assertionStack": "metricfilter-dimensions/DefaultTest/DeployAssert", + "assertionStackName": "metricfilterdimensionsDefaultTestDeployAssertF7E39B09" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..906179f65fb53 --- /dev/null +++ b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/manifest.json @@ -0,0 +1,117 @@ +{ + "version": "21.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-cdk-metricfilter-dimensions-integ.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-cdk-metricfilter-dimensions-integ.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-cdk-metricfilter-dimensions-integ": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-cdk-metricfilter-dimensions-integ.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/3d99811cf4d8b2d453d889e936569b925ead97bdb93a86d122b34d68818be01d.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-cdk-metricfilter-dimensions-integ.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-cdk-metricfilter-dimensions-integ.assets" + ], + "metadata": { + "/aws-cdk-metricfilter-dimensions-integ/LogGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "LogGroupF5B46931" + } + ], + "/aws-cdk-metricfilter-dimensions-integ/MetricFilter/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MetricFilter1B93B6E5" + } + ], + "/aws-cdk-metricfilter-dimensions-integ/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-cdk-metricfilter-dimensions-integ/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-cdk-metricfilter-dimensions-integ" + }, + "metricfilterdimensionsDefaultTestDeployAssertF7E39B09.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "metricfilterdimensionsDefaultTestDeployAssertF7E39B09.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "metricfilterdimensionsDefaultTestDeployAssertF7E39B09": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "metricfilterdimensionsDefaultTestDeployAssertF7E39B09.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "metricfilterdimensionsDefaultTestDeployAssertF7E39B09.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "metricfilterdimensionsDefaultTestDeployAssertF7E39B09.assets" + ], + "metadata": { + "/metricfilter-dimensions/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/metricfilter-dimensions/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "metricfilter-dimensions/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/metricfilterdimensionsDefaultTestDeployAssertF7E39B09.assets.json b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/metricfilterdimensionsDefaultTestDeployAssertF7E39B09.assets.json new file mode 100644 index 0000000000000..9c8f92b52716e --- /dev/null +++ b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/metricfilterdimensionsDefaultTestDeployAssertF7E39B09.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "metricfilterdimensionsDefaultTestDeployAssertF7E39B09.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/metricfilterdimensionsDefaultTestDeployAssertF7E39B09.template.json b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/metricfilterdimensionsDefaultTestDeployAssertF7E39B09.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/metricfilterdimensionsDefaultTestDeployAssertF7E39B09.template.json @@ -0,0 +1,36 @@ +{ + "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. [cdk:skip]" + } + }, + "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/aws-logs/test/metricfilter-dimensions.integ.snapshot/tree.json b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/tree.json new file mode 100644 index 0000000000000..53e7571726531 --- /dev/null +++ b/packages/@aws-cdk/aws-logs/test/metricfilter-dimensions.integ.snapshot/tree.json @@ -0,0 +1,131 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.95" + } + }, + "aws-cdk-metricfilter-dimensions-integ": { + "id": "aws-cdk-metricfilter-dimensions-integ", + "path": "aws-cdk-metricfilter-dimensions-integ", + "children": { + "LogGroup": { + "id": "LogGroup", + "path": "aws-cdk-metricfilter-dimensions-integ/LogGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-metricfilter-dimensions-integ/LogGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Logs::LogGroup", + "aws:cdk:cloudformation:props": { + "retentionInDays": 731 + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-logs.CfnLogGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-logs.LogGroup", + "version": "0.0.0" + } + }, + "MetricFilter": { + "id": "MetricFilter", + "path": "aws-cdk-metricfilter-dimensions-integ/MetricFilter", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-metricfilter-dimensions-integ/MetricFilter/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Logs::MetricFilter", + "aws:cdk:cloudformation:props": { + "filterPattern": "{ $.latency = \"*\" }", + "logGroupName": { + "Ref": "LogGroupF5B46931" + }, + "metricTransformations": [ + { + "metricNamespace": "MyApp", + "metricName": "Latency", + "metricValue": "$.latency", + "dimensions": [ + { + "key": "ErrorCode", + "value": "$.errorCode" + } + ] + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-logs.CfnMetricFilter", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-logs.MetricFilter", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "metricfilter-dimensions": { + "id": "metricfilter-dimensions", + "path": "metricfilter-dimensions", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "metricfilter-dimensions/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "metricfilter-dimensions/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.95" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "metricfilter-dimensions/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-logs/test/metricfilter.lit.integ.snapshot/tree.json b/packages/@aws-cdk/aws-logs/test/metricfilter.lit.integ.snapshot/tree.json index 6a97a05861cfe..e24af4a1b21ce 100644 --- a/packages/@aws-cdk/aws-logs/test/metricfilter.lit.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-logs/test/metricfilter.lit.integ.snapshot/tree.json @@ -30,13 +30,13 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-logs.CfnLogGroup", + "fqn": "@aws-cdk/core.CfnResource", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-logs.LogGroup", + "fqn": "@aws-cdk/core.Resource", "version": "0.0.0" } }, @@ -64,13 +64,13 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-logs.CfnMetricFilter", + "fqn": "@aws-cdk/core.CfnResource", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-logs.MetricFilter", + "fqn": "@aws-cdk/core.Resource", "version": "0.0.0" } } diff --git a/packages/@aws-cdk/aws-logs/test/metricfilter.test.ts b/packages/@aws-cdk/aws-logs/test/metricfilter.test.ts index 6ab67899db2ea..88a925a488afc 100644 --- a/packages/@aws-cdk/aws-logs/test/metricfilter.test.ts +++ b/packages/@aws-cdk/aws-logs/test/metricfilter.test.ts @@ -30,6 +30,62 @@ describe('metric filter', () => { }); }); + test('with dimensions', () => { + // GIVEN + const stack = new Stack(); + const logGroup = new LogGroup(stack, 'LogGroup'); + + // WHEN + new MetricFilter(stack, 'Subscription', { + logGroup, + metricNamespace: 'AWS/Test', + metricName: 'Latency', + metricValue: '$.latency', + filterPattern: FilterPattern.exists('$.latency'), + dimensions: { + Foo: 'Bar', + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Logs::MetricFilter', { + MetricTransformations: [{ + MetricNamespace: 'AWS/Test', + MetricName: 'Latency', + MetricValue: '$.latency', + Dimensions: [ + { + Key: 'Foo', + Value: 'Bar', + }, + ], + }], + FilterPattern: '{ $.latency = "*" }', + LogGroupName: { Ref: 'LogGroupF5B46931' }, + }); + }); + + test('should throw with more than 3 dimensions', () => { + // GIVEN + const stack = new Stack(); + const logGroup = new LogGroup(stack, 'LogGroup'); + + // WHEN + expect(() => new MetricFilter(stack, 'Subscription', { + logGroup, + metricNamespace: 'AWS/Test', + metricName: 'Latency', + metricValue: '$.latency', + filterPattern: FilterPattern.exists('$.latency'), + dimensions: { + Foo: 'Bar', + Bar: 'Baz', + Baz: 'Qux', + Qux: 'Quux', + }, + })).toThrow(/MetricFilter only supports a maximum of 3 Dimensions/); + }); + test('metric filter exposes metric', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-neptune/README.md b/packages/@aws-cdk/aws-neptune/README.md index bb1c45dbcfd55..36433215ce57e 100644 --- a/packages/@aws-cdk/aws-neptune/README.md +++ b/packages/@aws-cdk/aws-neptune/README.md @@ -121,7 +121,7 @@ const cluster = new neptune.DatabaseCluster(this, 'Database', { }); ``` -Additionally it is also possible to add replicas using `DatabaseInstance` for an existing cluster. +Additionally, it is also possible to add replicas using `DatabaseInstance` for an existing cluster. ```ts fixture=with-cluster const replica1 = new neptune.DatabaseInstance(this, 'Instance', { @@ -143,3 +143,17 @@ new neptune.DatabaseCluster(this, 'Cluster', { autoMinorVersionUpgrade: true, }); ``` + +## Metrics + +Both `DatabaseCluster` and `DatabaseInstance` provide a `metric()` method to help with cluster-level and instance-level monitoring. + +```ts +declare const cluster: neptune.DatabaseCluster; +declare const instance: neptune.DatabaseInstance; + +cluster.metric('SparqlRequestsPerSec'); // cluster-level SparqlErrors metric +instance.metric('SparqlRequestsPerSec') // instance-level SparqlErrors metric +``` + +For more details on the available metrics, refer to https://docs.aws.amazon.com/neptune/latest/userguide/cw-metrics.html diff --git a/packages/@aws-cdk/aws-neptune/lib/cluster.ts b/packages/@aws-cdk/aws-neptune/lib/cluster.ts index f30bf46851f0f..f2e998332c304 100644 --- a/packages/@aws-cdk/aws-neptune/lib/cluster.ts +++ b/packages/@aws-cdk/aws-neptune/lib/cluster.ts @@ -1,3 +1,4 @@ +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; @@ -284,6 +285,14 @@ export interface IDatabaseCluster extends IResource, ec2.IConnectable { * Grant the given identity connection access to the database. */ grantConnect(grantee: iam.IGrantable): iam.Grant; + + /** + * Return the given named metric associated with this DatabaseCluster instance + * + * @see https://docs.aws.amazon.com/neptune/latest/userguide/cw-metrics.html + * @see https://docs.aws.amazon.com/neptune/latest/userguide/cw-dimensions.html + */ + metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric; } /** @@ -398,6 +407,17 @@ export abstract class DatabaseClusterBase extends Resource implements IDatabaseC public grantConnect(grantee: iam.IGrantable): iam.Grant { return this.grant(grantee, 'neptune-db:*'); } + + public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return new cloudwatch.Metric({ + namespace: 'AWS/Neptune', + dimensionsMap: { + DBClusterIdentifier: this.clusterIdentifier, + }, + metricName, + ...props, + }); + } } /** diff --git a/packages/@aws-cdk/aws-neptune/lib/instance.ts b/packages/@aws-cdk/aws-neptune/lib/instance.ts index b05003dffcb40..336606c6b9386 100644 --- a/packages/@aws-cdk/aws-neptune/lib/instance.ts +++ b/packages/@aws-cdk/aws-neptune/lib/instance.ts @@ -1,3 +1,4 @@ +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; @@ -165,6 +166,14 @@ export interface IDatabaseInstance extends cdk.IResource { * @attribute Port */ readonly dbInstanceEndpointPort: string; + + /** + * Return the given named metric associated with this database instance + * + * @see https://docs.aws.amazon.com/neptune/latest/userguide/cw-metrics.html + * @see https://docs.aws.amazon.com/neptune/latest/userguide/cw-dimensions.html + */ + metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric; } /** @@ -233,27 +242,64 @@ export interface DatabaseInstanceProps { } /** - * A database instance - * - * @resource AWS::Neptune::DBInstance + * A new or imported database instance. */ -export class DatabaseInstance extends cdk.Resource implements IDatabaseInstance { - +export abstract class DatabaseInstanceBase extends cdk.Resource implements IDatabaseInstance { /** * Import an existing database instance. */ public static fromDatabaseInstanceAttributes(scope: Construct, id: string, attrs: DatabaseInstanceAttributes): IDatabaseInstance { - class Import extends cdk.Resource implements IDatabaseInstance { + class Import extends DatabaseInstanceBase implements IDatabaseInstance { public readonly defaultPort = ec2.Port.tcp(attrs.port); public readonly instanceIdentifier = attrs.instanceIdentifier; public readonly dbInstanceEndpointAddress = attrs.instanceEndpointAddress; public readonly dbInstanceEndpointPort = attrs.port.toString(); public readonly instanceEndpoint = new Endpoint(attrs.instanceEndpointAddress, attrs.port); } - return new Import(scope, id); } + /** + * @inheritdoc + */ + public abstract readonly dbInstanceEndpointAddress: string; + + /** + * @inheritdoc + */ + public abstract readonly dbInstanceEndpointPort: string; + + /** + * @inheritdoc + */ + public abstract readonly instanceEndpoint: Endpoint; + + /** + * @inheritdoc + */ + public abstract readonly instanceIdentifier: string; + + /** + * @inheritdoc + */ + public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return new cloudwatch.Metric({ + namespace: 'AWS/Neptune', + dimensionsMap: { + DBInstanceIdentifier: this.instanceIdentifier, + }, + metricName, + ...props, + }); + } +} + +/** + * A database instance + * + * @resource AWS::Neptune::DBInstance + */ +export class DatabaseInstance extends DatabaseInstanceBase implements IDatabaseInstance { /** * The instance's database cluster diff --git a/packages/@aws-cdk/aws-neptune/package.json b/packages/@aws-cdk/aws-neptune/package.json index f1fc53edc893c..6b38c7760a3bf 100644 --- a/packages/@aws-cdk/aws-neptune/package.json +++ b/packages/@aws-cdk/aws-neptune/package.json @@ -90,6 +90,7 @@ "@types/jest": "^27.5.2" }, "dependencies": { + "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", @@ -97,6 +98,7 @@ "constructs": "^10.0.0" }, "peerDependencies": { + "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", diff --git a/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/ClusterTestDefaultTestDeployAssert6A1BBA9D.assets.json b/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/ClusterTestDefaultTestDeployAssert6A1BBA9D.assets.json new file mode 100644 index 0000000000000..fed1d52c49ba8 --- /dev/null +++ b/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/ClusterTestDefaultTestDeployAssert6A1BBA9D.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "ClusterTestDefaultTestDeployAssert6A1BBA9D.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/ClusterTestDefaultTestDeployAssert6A1BBA9D.template.json b/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/ClusterTestDefaultTestDeployAssert6A1BBA9D.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/ClusterTestDefaultTestDeployAssert6A1BBA9D.template.json @@ -0,0 +1,36 @@ +{ + "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. [cdk:skip]" + } + }, + "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/aws-neptune/test/cluster.integ.snapshot/aws-cdk-neptune-integ.assets.json b/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/aws-cdk-neptune-integ.assets.json index 406c273a3a088..8bbc76301c8f1 100644 --- a/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/aws-cdk-neptune-integ.assets.json +++ b/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/aws-cdk-neptune-integ.assets.json @@ -1,7 +1,7 @@ { "version": "21.0.0", "files": { - "315715ffe6004c7bd7c9874629785c10fd8f65a20d6995ea8eb20188dfb82b7d": { + "c3aa283b33e47bc3d0cb943f014017d1742247b6577982270570cb6cbf5a778c": { "source": { "path": "aws-cdk-neptune-integ.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "315715ffe6004c7bd7c9874629785c10fd8f65a20d6995ea8eb20188dfb82b7d.json", + "objectKey": "c3aa283b33e47bc3d0cb943f014017d1742247b6577982270570cb6cbf5a778c.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/aws-cdk-neptune-integ.template.json b/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/aws-cdk-neptune-integ.template.json index 67f5870712c2a..15290913fcbd9 100644 --- a/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/aws-cdk-neptune-integ.template.json +++ b/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/aws-cdk-neptune-integ.template.json @@ -538,6 +538,26 @@ ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" + }, + "Alarm7103F465": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "LessThanThreshold", + "EvaluationPeriods": 1, + "Dimensions": [ + { + "Name": "DBClusterIdentifier", + "Value": { + "Ref": "DatabaseB269D8BB" + } + } + ], + "MetricName": "SparqlRequestsPerSec", + "Namespace": "AWS/Neptune", + "Period": 300, + "Statistic": "Average", + "Threshold": 1 + } } }, "Parameters": { diff --git a/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/integ.json b/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/integ.json index 7f298dac51aa6..e2061d5ea4e11 100644 --- a/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/integ.json @@ -1,14 +1,12 @@ { "version": "21.0.0", "testCases": { - "integ.cluster": { + "ClusterTest/DefaultTest": { "stacks": [ "aws-cdk-neptune-integ" ], - "diffAssets": false, - "stackUpdateWorkflow": true + "assertionStack": "ClusterTest/DefaultTest/DeployAssert", + "assertionStackName": "ClusterTestDefaultTestDeployAssert6A1BBA9D" } - }, - "synthContext": {}, - "enableLookups": false + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/manifest.json index c9e9d7159e499..add010df1aabf 100644 --- a/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/manifest.json @@ -23,7 +23,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/315715ffe6004c7bd7c9874629785c10fd8f65a20d6995ea8eb20188dfb82b7d.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/c3aa283b33e47bc3d0cb943f014017d1742247b6577982270570cb6cbf5a778c.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -219,74 +219,73 @@ "data": "DatabaseInstance1844F58FD" } ], - "/aws-cdk-neptune-integ/BootstrapVersion": [ - { - "type": "aws:cdk:logicalId", - "data": "BootstrapVersion" - } - ], - "/aws-cdk-neptune-integ/CheckBootstrapVersion": [ - { - "type": "aws:cdk:logicalId", - "data": "CheckBootstrapVersion" - } - ], - "Params1200F93288": [ - { - "type": "aws:cdk:logicalId", - "data": "Params1200F93288", - "trace": [ - "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" - ] - } - ], - "Database12Subnets4179194B": [ + "/aws-cdk-neptune-integ/Alarm/Resource": [ { "type": "aws:cdk:logicalId", - "data": "Database12Subnets4179194B", - "trace": [ - "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" - ] + "data": "Alarm7103F465" } ], - "Database12SecurityGroup4F4302E8": [ + "/aws-cdk-neptune-integ/BootstrapVersion": [ { "type": "aws:cdk:logicalId", - "data": "Database12SecurityGroup4F4302E8", - "trace": [ - "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" - ] + "data": "BootstrapVersion" } ], - "Database12SecurityGroupfrom00000IndirectPort3A40EE2B": [ + "/aws-cdk-neptune-integ/CheckBootstrapVersion": [ { "type": "aws:cdk:logicalId", - "data": "Database12SecurityGroupfrom00000IndirectPort3A40EE2B", - "trace": [ - "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" - ] + "data": "CheckBootstrapVersion" } + ] + }, + "displayName": "aws-cdk-neptune-integ" + }, + "ClusterTestDefaultTestDeployAssert6A1BBA9D.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "ClusterTestDefaultTestDeployAssert6A1BBA9D.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "ClusterTestDefaultTestDeployAssert6A1BBA9D": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "ClusterTestDefaultTestDeployAssert6A1BBA9D.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "ClusterTestDefaultTestDeployAssert6A1BBA9D.assets" ], - "Database12D6A36FB9": [ + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "ClusterTestDefaultTestDeployAssert6A1BBA9D.assets" + ], + "metadata": { + "/ClusterTest/DefaultTest/DeployAssert/BootstrapVersion": [ { "type": "aws:cdk:logicalId", - "data": "Database12D6A36FB9", - "trace": [ - "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" - ] + "data": "BootstrapVersion" } ], - "Database12Instance10D9E6224": [ + "/ClusterTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [ { "type": "aws:cdk:logicalId", - "data": "Database12Instance10D9E6224", - "trace": [ - "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" - ] + "data": "CheckBootstrapVersion" } ] }, - "displayName": "aws-cdk-neptune-integ" + "displayName": "ClusterTest/DefaultTest/DeployAssert" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/tree.json b/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/tree.json index 0d2da0fe35492..1ca55bc53da8a 100644 --- a/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-neptune/test/cluster.integ.snapshot/tree.json @@ -900,12 +900,86 @@ "fqn": "@aws-cdk/aws-neptune.DatabaseCluster", "version": "0.0.0" } + }, + "Alarm": { + "id": "Alarm", + "path": "aws-cdk-neptune-integ/Alarm", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-neptune-integ/Alarm/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudWatch::Alarm", + "aws:cdk:cloudformation:props": { + "comparisonOperator": "GreaterThanThreshold", + "evaluationPeriods": 1, + "dimensions": [ + { + "name": "DBClusterIdentifier", + "value": { + "Ref": "DatabaseB269D8BB" + } + } + ], + "metricName": "SparqlErrors", + "namespace": "AWS/Neptune", + "period": 300, + "statistic": "Sum", + "threshold": 0 + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.CfnAlarm", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.Alarm", + "version": "0.0.0" + } } }, "constructInfo": { "fqn": "@aws-cdk/core.Stack", "version": "0.0.0" } + }, + "ClusterTest": { + "id": "ClusterTest", + "path": "ClusterTest", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "ClusterTest/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "ClusterTest/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "ClusterTest/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } } }, "constructInfo": { diff --git a/packages/@aws-cdk/aws-neptune/test/cluster.test.ts b/packages/@aws-cdk/aws-neptune/test/cluster.test.ts index e92386cfefcb0..b0b2873572eb8 100644 --- a/packages/@aws-cdk/aws-neptune/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-neptune/test/cluster.test.ts @@ -1,4 +1,5 @@ import { Match, Template } from '@aws-cdk/assertions'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; @@ -660,6 +661,46 @@ describe('DatabaseCluster', () => { }); + test('metric - constructs metric with correct namespace and dimension and inputs', () => { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new DatabaseCluster(stack, 'Cluster', { + vpc, + instanceType: InstanceType.R5_LARGE, + }); + + // WHEN + const metric = cluster.metric('SparqlRequestsPerSec'); + new cloudwatch.Alarm(stack, 'Alarm', { + evaluationPeriods: 1, + threshold: 1, + comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD, + metric: metric, + }); + + // THEN + expect(metric).toEqual(new cloudwatch.Metric({ + namespace: 'AWS/Neptune', + dimensionsMap: { + DBClusterIdentifier: cluster.clusterIdentifier, + }, + metricName: 'SparqlRequestsPerSec', + })); + Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { + Namespace: 'AWS/Neptune', + MetricName: 'SparqlRequestsPerSec', + Dimensions: [ + { + Name: 'DBClusterIdentifier', + Value: stack.resolve(cluster.clusterIdentifier), + }, + ], + ComparisonOperator: 'LessThanThreshold', + EvaluationPeriods: 1, + Threshold: 1, + }); + }); }); function testStack() { diff --git a/packages/@aws-cdk/aws-neptune/test/instance.test.ts b/packages/@aws-cdk/aws-neptune/test/instance.test.ts index ed83e1506496a..95e6e27d3f4ed 100644 --- a/packages/@aws-cdk/aws-neptune/test/instance.test.ts +++ b/packages/@aws-cdk/aws-neptune/test/instance.test.ts @@ -1,4 +1,5 @@ import { Template } from '@aws-cdk/assertions'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; @@ -140,6 +141,46 @@ describe('DatabaseInstance', () => { test('instance type from string throws if missing db prefix', () => { expect(() => { InstanceType.of('r5.xlarge');}).toThrowError(/instance type must start with 'db.'/); }); + + test('metric - constructs metric with correct namespace and dimension and inputs', () => { + // GIVEN + const stack = testStack(); + const instance = new DatabaseInstance(stack, 'Instance', { + cluster: stack.cluster, + instanceType: InstanceType.R5_LARGE, + }); + + // WHEN + const metric = instance.metric('SparqlRequestsPerSec'); + new cloudwatch.Alarm(stack, 'Alarm', { + evaluationPeriods: 1, + threshold: 1, + comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD, + metric: metric, + }); + + // THEN + expect(metric).toEqual(new cloudwatch.Metric({ + namespace: 'AWS/Neptune', + dimensionsMap: { + DBInstanceIdentifier: instance.instanceIdentifier, + }, + metricName: 'SparqlRequestsPerSec', + })); + Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { + Namespace: 'AWS/Neptune', + MetricName: 'SparqlRequestsPerSec', + Dimensions: [ + { + Name: 'DBInstanceIdentifier', + Value: stack.resolve(instance.instanceIdentifier), + }, + ], + ComparisonOperator: 'LessThanThreshold', + EvaluationPeriods: 1, + Threshold: 1, + }); + }); }); class TestStack extends cdk.Stack { @@ -160,6 +201,5 @@ class TestStack extends cdk.Stack { } function testStack() { - const stack = new TestStack(undefined, undefined, { env: { account: '12345', region: 'us-test-1' } }); - return stack; + return new TestStack(undefined, undefined, { env: { account: '12345', region: 'us-test-1' } }); } diff --git a/packages/@aws-cdk/aws-neptune/test/integ.cluster.ts b/packages/@aws-cdk/aws-neptune/test/integ.cluster.ts index 0d7014c049b3a..1b8d4156ef6f5 100644 --- a/packages/@aws-cdk/aws-neptune/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-neptune/test/integ.cluster.ts @@ -1,3 +1,4 @@ +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import * as cdk from '@aws-cdk/core'; @@ -43,6 +44,14 @@ const cluster = new DatabaseCluster(stack, 'Database', { cluster.connections.allowDefaultPortFromAnyIpv4('Open to the world'); +const metric = cluster.metric('SparqlRequestsPerSec'); +new cloudwatch.Alarm(stack, 'Alarm', { + evaluationPeriods: 1, + threshold: 1, + comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD, + metric: metric, +}); + new integ.IntegTest(app, 'ClusterTest', { testCases: [stack], }); diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 3eb555171b170..de35ba3df6f5f 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -890,7 +890,6 @@ function createInstances(cluster: DatabaseClusterNew, props: DatabaseClusterBase const instance = new CfnDBInstance(cluster, `Instance${instanceIndex}`, { // Link to cluster engine: props.engine.engineType, - engineVersion: props.engine.engineVersion?.fullVersion, dbClusterIdentifier: cluster.clusterIdentifier, dbInstanceIdentifier: instanceIdentifier, // Instance properties diff --git a/packages/@aws-cdk/aws-rds/lib/instance-engine.ts b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts index d3bd0f112d726..211bbfd154b31 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance-engine.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts @@ -934,6 +934,8 @@ export class PostgresEngineVersion { public static readonly VER_11_14 = PostgresEngineVersion.of('11.14', '11', { s3Import: true, s3Export: true }); /** Version "11.15". */ public static readonly VER_11_15 = PostgresEngineVersion.of('11.15', '11', { s3Import: true, s3Export: true }); + /** Version "11.16". */ + public static readonly VER_11_16 = PostgresEngineVersion.of('11.16', '11', { s3Import: true, s3Export: true }); /** Version "12" (only a major version, without a specific minor version). */ public static readonly VER_12 = PostgresEngineVersion.of('12', '12', { s3Import: true }); diff --git a/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/aws-cdk-rds-s3-mysql-8-integ.assets.json b/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/aws-cdk-rds-s3-mysql-8-integ.assets.json index f4e331845bafa..430b13a84ce1f 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/aws-cdk-rds-s3-mysql-8-integ.assets.json +++ b/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/aws-cdk-rds-s3-mysql-8-integ.assets.json @@ -1,7 +1,7 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { - "69d4b966c56ecebfa2378a3bf4ad6e2abe3ca2a900162197f7658ec8a258f509": { + "25234cf4a3c5d1399adfab4811db12cbab88f9459af99963277edcbbd40e815a": { "source": { "path": "aws-cdk-rds-s3-mysql-8-integ.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "69d4b966c56ecebfa2378a3bf4ad6e2abe3ca2a900162197f7658ec8a258f509.json", + "objectKey": "25234cf4a3c5d1399adfab4811db12cbab88f9459af99963277edcbbd40e815a.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/aws-cdk-rds-s3-mysql-8-integ.template.json b/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/aws-cdk-rds-s3-mysql-8-integ.template.json index b11711610c53f..2ac33a1ea212d 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/aws-cdk-rds-s3-mysql-8-integ.template.json +++ b/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/aws-cdk-rds-s3-mysql-8-integ.template.json @@ -557,8 +557,7 @@ "DBSubnetGroupName": { "Ref": "DatabaseSubnets56F17B9A" }, - "Engine": "aurora-mysql", - "EngineVersion": "8.0.mysql_aurora.3.01.0" + "Engine": "aurora-mysql" }, "DependsOn": [ "VPCPrivateSubnet1DefaultRouteAE1D6490", diff --git a/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/integ.json b/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/integ.json index a568e6fc706fa..8ffe8f32ed74b 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { "integ.cluster-s3.mysql-8": { "stacks": [ diff --git a/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/manifest.json index 3b8e9d9e3df9e..434c9f37b0cc9 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -23,7 +23,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/69d4b966c56ecebfa2378a3bf4ad6e2abe3ca2a900162197f7658ec8a258f509.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/25234cf4a3c5d1399adfab4811db12cbab88f9459af99963277edcbbd40e815a.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/tree.json b/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/tree.json index 454718d69084e..1b78db0ffdca6 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-rds/test/cluster-s3.mysql-8.integ.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } }, "aws-cdk-rds-s3-mysql-8-integ": { @@ -91,8 +91,8 @@ "id": "Acl", "path": "aws-cdk-rds-s3-mysql-8-integ/VPC/PublicSubnet1/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -258,8 +258,8 @@ "id": "Acl", "path": "aws-cdk-rds-s3-mysql-8-integ/VPC/PublicSubnet2/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -425,8 +425,8 @@ "id": "Acl", "path": "aws-cdk-rds-s3-mysql-8-integ/VPC/PrivateSubnet1/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -544,8 +544,8 @@ "id": "Acl", "path": "aws-cdk-rds-s3-mysql-8-integ/VPC/PrivateSubnet2/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -861,8 +861,8 @@ "id": "AuroraMySqlDatabaseClusterEngineDefaultParameterGroup", "path": "aws-cdk-rds-s3-mysql-8-integ/Database/AuroraMySqlDatabaseClusterEngineDefaultParameterGroup", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "ClusterParameterGroup": { @@ -953,8 +953,7 @@ "dbSubnetGroupName": { "Ref": "DatabaseSubnets56F17B9A" }, - "engine": "aurora-mysql", - "engineVersion": "8.0.mysql_aurora.3.01.0" + "engine": "aurora-mysql" } }, "constructInfo": { @@ -970,14 +969,14 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/cdk-integ-cluster-snapshot.assets.json b/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/cdk-integ-cluster-snapshot.assets.json index b8c141d51c1b5..bff378fc65a8d 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/cdk-integ-cluster-snapshot.assets.json +++ b/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/cdk-integ-cluster-snapshot.assets.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { "2e7ee01d9005281c0784e709cad69500591734343d1cb95da2fb4a3f5076aadd": { "source": { @@ -27,7 +27,7 @@ } } }, - "8ec192f0414bf86ff0d9b73ae3fe511c7d1a75d03f9d894cafa1e3b1d0d33f39": { + "d32f18fd62a56b01413ffc4d63a52dcec293772c9f178d8b459ba255481f9326": { "source": { "path": "cdk-integ-cluster-snapshot.template.json", "packaging": "file" @@ -35,7 +35,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "8ec192f0414bf86ff0d9b73ae3fe511c7d1a75d03f9d894cafa1e3b1d0d33f39.json", + "objectKey": "d32f18fd62a56b01413ffc4d63a52dcec293772c9f178d8b459ba255481f9326.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/cdk-integ-cluster-snapshot.template.json b/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/cdk-integ-cluster-snapshot.template.json index da2e9266169ef..366c5f002461c 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/cdk-integ-cluster-snapshot.template.json +++ b/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/cdk-integ-cluster-snapshot.template.json @@ -478,8 +478,7 @@ "DBSubnetGroupName": { "Ref": "ClusterSubnetsDCFA5CB7" }, - "Engine": "aurora-mysql", - "EngineVersion": "5.7.mysql_aurora.2.10.2" + "Engine": "aurora-mysql" }, "DependsOn": [ "VpcPrivateSubnet1DefaultRouteBE02A9ED", @@ -500,8 +499,7 @@ "DBSubnetGroupName": { "Ref": "ClusterSubnetsDCFA5CB7" }, - "Engine": "aurora-mysql", - "EngineVersion": "5.7.mysql_aurora.2.10.2" + "Engine": "aurora-mysql" }, "DependsOn": [ "VpcPrivateSubnet1DefaultRouteBE02A9ED", @@ -1542,8 +1540,7 @@ "DBSubnetGroupName": { "Ref": "FromSnapshotSubnets9ED4B8EE" }, - "Engine": "aurora-mysql", - "EngineVersion": "5.7.mysql_aurora.2.10.2" + "Engine": "aurora-mysql" }, "DependsOn": [ "VpcPrivateSubnet1DefaultRouteBE02A9ED", @@ -1564,8 +1561,7 @@ "DBSubnetGroupName": { "Ref": "FromSnapshotSubnets9ED4B8EE" }, - "Engine": "aurora-mysql", - "EngineVersion": "5.7.mysql_aurora.2.10.2" + "Engine": "aurora-mysql" }, "DependsOn": [ "VpcPrivateSubnet1DefaultRouteBE02A9ED", diff --git a/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/integ.json b/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/integ.json index 2e575b90eb401..329077c22006c 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { "integ.cluster-snapshot": { "stacks": [ diff --git a/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/manifest.json index cc4f2d49839ad..40957601b77fe 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -23,7 +23,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/8ec192f0414bf86ff0d9b73ae3fe511c7d1a75d03f9d894cafa1e3b1d0d33f39.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/d32f18fd62a56b01413ffc4d63a52dcec293772c9f178d8b459ba255481f9326.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/tree.json b/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/tree.json index 1104de32f857b..42da845702130 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-rds/test/cluster-snapshot.integ.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } }, "cdk-integ-cluster-snapshot": { @@ -91,8 +91,8 @@ "id": "Acl", "path": "cdk-integ-cluster-snapshot/Vpc/PublicSubnet1/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -258,8 +258,8 @@ "id": "Acl", "path": "cdk-integ-cluster-snapshot/Vpc/PublicSubnet2/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -377,8 +377,8 @@ "id": "Acl", "path": "cdk-integ-cluster-snapshot/Vpc/PrivateSubnet1/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -496,8 +496,8 @@ "id": "Acl", "path": "cdk-integ-cluster-snapshot/Vpc/PrivateSubnet2/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -685,8 +685,8 @@ "id": "AuroraMySqlDatabaseClusterEngineDefaultParameterGroup", "path": "cdk-integ-cluster-snapshot/Cluster/AuroraMySqlDatabaseClusterEngineDefaultParameterGroup", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "Secret": { @@ -824,8 +824,7 @@ "dbSubnetGroupName": { "Ref": "ClusterSubnetsDCFA5CB7" }, - "engine": "aurora-mysql", - "engineVersion": "5.7.mysql_aurora.2.10.2" + "engine": "aurora-mysql" } }, "constructInfo": { @@ -846,8 +845,7 @@ "dbSubnetGroupName": { "Ref": "ClusterSubnetsDCFA5CB7" }, - "engine": "aurora-mysql", - "engineVersion": "5.7.mysql_aurora.2.10.2" + "engine": "aurora-mysql" } }, "constructInfo": { @@ -1012,8 +1010,8 @@ "id": "Stage", "path": "cdk-integ-cluster-snapshot/Snapshoter/OnEventHandler/Code/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -1384,8 +1382,8 @@ "id": "Stage", "path": "cdk-integ-cluster-snapshot/Snapshoter/SnapshotProvider/framework-onEvent/Code/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -1597,8 +1595,8 @@ "id": "Stage", "path": "cdk-integ-cluster-snapshot/Snapshoter/SnapshotProvider/framework-isComplete/Code/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -1807,8 +1805,8 @@ "id": "Stage", "path": "cdk-integ-cluster-snapshot/Snapshoter/SnapshotProvider/framework-onTimeout/Code/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -2007,14 +2005,14 @@ "id": "Resource", "path": "cdk-integ-cluster-snapshot/Snapshoter/SnapshotProvider/waiter-state-machine/Resource", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } } }, @@ -2031,20 +2029,20 @@ "id": "Default", "path": "cdk-integ-cluster-snapshot/Snapshoter/Snapshot/Default", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } }, "FromSnapshot": { @@ -2160,8 +2158,8 @@ "id": "AuroraMySqlDatabaseClusterEngineDefaultParameterGroup", "path": "cdk-integ-cluster-snapshot/FromSnapshot/AuroraMySqlDatabaseClusterEngineDefaultParameterGroup", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "Secret": { @@ -2452,8 +2450,7 @@ "dbSubnetGroupName": { "Ref": "FromSnapshotSubnets9ED4B8EE" }, - "engine": "aurora-mysql", - "engineVersion": "5.7.mysql_aurora.2.10.2" + "engine": "aurora-mysql" } }, "constructInfo": { @@ -2474,8 +2471,7 @@ "dbSubnetGroupName": { "Ref": "FromSnapshotSubnets9ED4B8EE" }, - "engine": "aurora-mysql", - "engineVersion": "5.7.mysql_aurora.2.10.2" + "engine": "aurora-mysql" } }, "constructInfo": { @@ -2525,8 +2521,8 @@ "id": "SARMapping", "path": "cdk-integ-cluster-snapshot/FromSnapshot/RotationSingleUser/SARMapping", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnMapping", + "version": "0.0.0" } }, "Resource": { @@ -2625,20 +2621,20 @@ "id": "Service-principalMap", "path": "cdk-integ-cluster-snapshot/Service-principalMap", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnMapping", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/cluster.test.ts b/packages/@aws-cdk/aws-rds/test/cluster.test.ts index 35bd5af905f5c..397a3828b200a 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-rds/test/cluster.test.ts @@ -2599,6 +2599,22 @@ describe('cluster', () => { BacktrackWindow: 24 * 60 * 60, }); }); + + test('DB instances should not have engine version set when part of a cluster', () => { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.auroraPostgres({ version: AuroraPostgresEngineVersion.VER_14_3 }), + instanceProps: { vpc }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { + EngineVersion: Match.absent(), + }); + }); }); test.each([ diff --git a/packages/@aws-cdk/aws-redshift/package.json b/packages/@aws-cdk/aws-redshift/package.json index 017153eab6a0d..7d3a9b832f79e 100644 --- a/packages/@aws-cdk/aws-redshift/package.json +++ b/packages/@aws-cdk/aws-redshift/package.json @@ -87,7 +87,7 @@ "@aws-cdk/pkglint": "0.0.0", "@aws-cdk/integ-tests": "0.0.0", "@types/jest": "^27.5.2", - "aws-sdk": "^2.848.0", + "aws-sdk": "^2.1211.0", "jest": "^27.5.1" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 290d3bbfb8a1b..ddc18b7c5ef3d 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -87,7 +87,7 @@ "@aws-cdk/pkglint": "0.0.0", "@types/aws-lambda": "^8.10.104", "@types/jest": "^27.5.2", - "aws-sdk": "^2.848.0", + "aws-sdk": "^2.1211.0", "jest": "^27.5.1" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-s3-deployment/README.md b/packages/@aws-cdk/aws-s3-deployment/README.md index 7b38998b4945f..6da3234e82aa4 100644 --- a/packages/@aws-cdk/aws-s3-deployment/README.md +++ b/packages/@aws-cdk/aws-s3-deployment/README.md @@ -325,6 +325,28 @@ The value in `topic.topicArn` is a deploy-time value. It only gets resolved during deployment by placing a marker in the generated source file and substituting it when its deployed to the destination with the actual value. +## Keep Files Zipped + +By default, files are zipped, then extracted into the destination bucket. + +You can use the option `extract: false` to disable this behavior, in which case, files will remain in a zip file when deployed to S3. To reference the object keys, or filenames, which will be deployed to the bucket, you can use the `objectKeys` getter on the bucket deployment. + +```ts +import * as cdk from 'aws-cdk-lib'; + +declare const destinationBucket: s3.Bucket; + +const myBucketDeployment = new s3deploy.BucketDeployment(this, 'DeployMeWithoutExtractingFilesOnDestination', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], + destinationBucket, + extract: false, +}); + +new cdk.CfnOutput(this, 'ObjectKey', { + value: cdk.Fn.select(0, myBucketDeployment.objectKeys), +}); +``` + ## Notes - This library uses an AWS CloudFormation custom resource which is about 10MiB in diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts index 80e75dc71de25..209cc46e87f64 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts +++ b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts @@ -38,6 +38,13 @@ export interface BucketDeploymentProps { */ readonly destinationKeyPrefix?: string; + /** + * If this is set, the zip file will be synced to the destination S3 bucket and extracted. + * If false, the file will remain zipped in the destination bucket. + * @default true + */ + readonly extract?: boolean; + /** * If this is set, matching files or objects will be excluded from the deployment's sync * command. This can be used to exclude a file from being pruned in the destination bucket. @@ -339,6 +346,12 @@ export class BucketDeployment extends Construct { // the sources actually has markers. const hasMarkers = sources.some(source => source.markers); + // Markers are not replaced if zip sources are not extracted, so throw an error + // if extraction is not wanted and sources have markers. + if (hasMarkers && props.extract == false) { + throw new Error('Some sources are incompatible with extract=false; sources with deploy-time values (such as \'snsTopic.topicArn\') must be extracted.'); + } + const crUniqueId = `CustomResource${this.renderUniqueId(props.memoryLimit, props.ephemeralStorageSize, props.vpc)}`; this.cr = new cdk.CustomResource(this, crUniqueId, { serviceToken: handler.functionArn, @@ -350,6 +363,7 @@ export class BucketDeployment extends Construct { DestinationBucketName: props.destinationBucket.bucketName, DestinationBucketKeyPrefix: props.destinationKeyPrefix, RetainOnDelete: props.retainOnDelete, + Extract: props.extract, Prune: props.prune ?? true, Exclude: props.exclude, Include: props.include, @@ -434,6 +448,23 @@ export class BucketDeployment extends Construct { return this._deployedBucket; } + /** + * The object keys for the sources deployed to the S3 bucket. + * + * This returns a list of tokenized object keys for source files that are deployed to the bucket. + * + * This can be useful when using `BucketDeployment` with `extract` set to `false` and you need to reference + * the object key that resides in the bucket for that zip source file somewhere else in your CDK + * application, such as in a CFN output. + * + * For example, use `Fn.select(0, myBucketDeployment.objectKeys)` to reference the object key of the + * first source file in your bucket deployment. + */ + public get objectKeys(): string[] { + const objectKeys = cdk.Token.asList(this.cr.getAtt('SourceObjectKeys')); + return objectKeys; + } + private renderUniqueId(memoryLimit?: number, ephemeralStorageSize?: cdk.Size, vpc?: ec2.IVpc) { let uuid = ''; diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/lambda/index.py b/packages/@aws-cdk/aws-s3-deployment/lib/lambda/index.py index 877b78a8452ee..efa0b54bb19df 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/lambda/index.py +++ b/packages/@aws-cdk/aws-s3-deployment/lib/lambda/index.py @@ -49,6 +49,7 @@ def cfn_error(message=None): source_markers = props.get('SourceMarkers', None) dest_bucket_name = props['DestinationBucketName'] dest_bucket_prefix = props.get('DestinationBucketKeyPrefix', '') + extract = props.get('Extract', 'true') == 'true' retain_on_delete = props.get('RetainOnDelete', "true") == "true" distribution_id = props.get('DistributionId', '') user_metadata = props.get('UserMetadata', {}) @@ -113,14 +114,15 @@ def cfn_error(message=None): aws_command("s3", "rm", old_s3_dest, "--recursive") if request_type == "Update" or request_type == "Create": - s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, exclude, include, source_markers) + s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, exclude, include, source_markers, extract) if distribution_id: cloudfront_invalidate(distribution_id, distribution_paths) cfn_send(event, context, CFN_SUCCESS, physicalResourceId=physical_id, responseData={ # Passing through the ARN sequences dependencees on the deployment - 'DestinationBucketArn': props.get('DestinationBucketArn') + 'DestinationBucketArn': props.get('DestinationBucketArn'), + 'SourceObjectKeys': props.get('SourceObjectKeys'), }) except KeyError as e: cfn_error("invalid request. Missing key %s" % str(e)) @@ -130,7 +132,7 @@ def cfn_error(message=None): #--------------------------------------------------------------------------------------------------- # populate all files from s3_source_zips to a destination bucket -def s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, exclude, include, source_markers): +def s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, exclude, include, source_markers, extract): # list lengths are equal if len(s3_source_zips) != len(source_markers): raise Exception("'source_markers' and 's3_source_zips' must be the same length") @@ -154,12 +156,16 @@ def s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, ex s3_source_zip = s3_source_zips[i] markers = source_markers[i] - archive=os.path.join(workdir, str(uuid4())) - logger.info("archive: %s" % archive) - aws_command("s3", "cp", s3_source_zip, archive) - logger.info("| extracting archive to: %s\n" % contents_dir) - logger.info("| markers: %s" % markers) - extract_and_replace_markers(archive, contents_dir, markers) + if extract: + archive=os.path.join(workdir, str(uuid4())) + logger.info("archive: %s" % archive) + aws_command("s3", "cp", s3_source_zip, archive) + logger.info("| extracting archive to: %s\n" % contents_dir) + logger.info("| markers: %s" % markers) + extract_and_replace_markers(archive, contents_dir, markers) + else: + logger.info("| copying archive to: %s\n" % contents_dir) + aws_command("s3", "cp", s3_source_zip, contents_dir) # sync from "contents" to destination @@ -285,7 +291,7 @@ def extract_and_replace_markers(archive, contents_dir, markers): for file in zip.namelist(): file_path = os.path.join(contents_dir, file) if os.path.isdir(file_path): continue - replace_markers(file_path, markers) + replace_markers(file_path, markers) def replace_markers(filename, markers): # convert the dict of string markers to binary markers diff --git a/packages/@aws-cdk/aws-s3-deployment/package.json b/packages/@aws-cdk/aws-s3-deployment/package.json index 46aeaba8f479a..9d04bfef70a3f 100644 --- a/packages/@aws-cdk/aws-s3-deployment/package.json +++ b/packages/@aws-cdk/aws-s3-deployment/package.json @@ -104,6 +104,7 @@ "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", + "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/lambda-layer-awscli": "0.0.0", "case": "1.6.3", @@ -119,6 +120,7 @@ "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", + "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/lambda-layer-awscli": "0.0.0", "constructs": "^10.0.0" diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment-data.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment-data.integ.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment-data.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment-data.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment-data.integ.snapshot/integ.json b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment-data.integ.snapshot/integ.json index 74c1ad3617ee4..aa6d25c71cf09 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment-data.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment-data.integ.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { "integ.bucket-deployment-data": { "stacks": [ diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/asset.f98b78092dcdd31f5e6d47489beb5f804d4835ef86a8085d0a2053cb9ae711da/index.py b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/asset.6ddcf10002539818a9256eff3fb2b22aa09298d8f946e26ba121c175a600c44e/index.py similarity index 93% rename from packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/asset.f98b78092dcdd31f5e6d47489beb5f804d4835ef86a8085d0a2053cb9ae711da/index.py rename to packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/asset.6ddcf10002539818a9256eff3fb2b22aa09298d8f946e26ba121c175a600c44e/index.py index 877b78a8452ee..efa0b54bb19df 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/asset.f98b78092dcdd31f5e6d47489beb5f804d4835ef86a8085d0a2053cb9ae711da/index.py +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/asset.6ddcf10002539818a9256eff3fb2b22aa09298d8f946e26ba121c175a600c44e/index.py @@ -49,6 +49,7 @@ def cfn_error(message=None): source_markers = props.get('SourceMarkers', None) dest_bucket_name = props['DestinationBucketName'] dest_bucket_prefix = props.get('DestinationBucketKeyPrefix', '') + extract = props.get('Extract', 'true') == 'true' retain_on_delete = props.get('RetainOnDelete', "true") == "true" distribution_id = props.get('DistributionId', '') user_metadata = props.get('UserMetadata', {}) @@ -113,14 +114,15 @@ def cfn_error(message=None): aws_command("s3", "rm", old_s3_dest, "--recursive") if request_type == "Update" or request_type == "Create": - s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, exclude, include, source_markers) + s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, exclude, include, source_markers, extract) if distribution_id: cloudfront_invalidate(distribution_id, distribution_paths) cfn_send(event, context, CFN_SUCCESS, physicalResourceId=physical_id, responseData={ # Passing through the ARN sequences dependencees on the deployment - 'DestinationBucketArn': props.get('DestinationBucketArn') + 'DestinationBucketArn': props.get('DestinationBucketArn'), + 'SourceObjectKeys': props.get('SourceObjectKeys'), }) except KeyError as e: cfn_error("invalid request. Missing key %s" % str(e)) @@ -130,7 +132,7 @@ def cfn_error(message=None): #--------------------------------------------------------------------------------------------------- # populate all files from s3_source_zips to a destination bucket -def s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, exclude, include, source_markers): +def s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, exclude, include, source_markers, extract): # list lengths are equal if len(s3_source_zips) != len(source_markers): raise Exception("'source_markers' and 's3_source_zips' must be the same length") @@ -154,12 +156,16 @@ def s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, ex s3_source_zip = s3_source_zips[i] markers = source_markers[i] - archive=os.path.join(workdir, str(uuid4())) - logger.info("archive: %s" % archive) - aws_command("s3", "cp", s3_source_zip, archive) - logger.info("| extracting archive to: %s\n" % contents_dir) - logger.info("| markers: %s" % markers) - extract_and_replace_markers(archive, contents_dir, markers) + if extract: + archive=os.path.join(workdir, str(uuid4())) + logger.info("archive: %s" % archive) + aws_command("s3", "cp", s3_source_zip, archive) + logger.info("| extracting archive to: %s\n" % contents_dir) + logger.info("| markers: %s" % markers) + extract_and_replace_markers(archive, contents_dir, markers) + else: + logger.info("| copying archive to: %s\n" % contents_dir) + aws_command("s3", "cp", s3_source_zip, contents_dir) # sync from "contents" to destination @@ -285,7 +291,7 @@ def extract_and_replace_markers(archive, contents_dir, markers): for file in zip.namelist(): file_path = os.path.join(contents_dir, file) if os.path.isdir(file_path): continue - replace_markers(file_path, markers) + replace_markers(file_path, markers) def replace_markers(filename, markers): # convert the dict of string markers to binary markers diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/asset.84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.bundle/index.js b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/asset.84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.bundle/index.js new file mode 100644 index 0000000000000..ba956d47f51fe --- /dev/null +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/asset.84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.bundle/index.js @@ -0,0 +1,612 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// lib/assertions/providers/lambda-handler/index.ts +var lambda_handler_exports = {}; +__export(lambda_handler_exports, { + handler: () => handler +}); +module.exports = __toCommonJS(lambda_handler_exports); + +// ../assertions/lib/matcher.ts +var Matcher = class { + static isMatcher(x) { + return x && x instanceof Matcher; + } +}; +var MatchResult = class { + constructor(target) { + this.failures = []; + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.target = target; + } + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + recordFailure(failure) { + this.failures.push(failure); + return this; + } + hasFailed() { + return this.failures.length !== 0; + } + get failCount() { + return this.failures.length; + } + compose(id, inner) { + const innerF = inner.failures; + this.failures.push(...innerF.map((f) => { + return { path: [id, ...f.path], message: f.message, matcher: f.matcher }; + })); + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + toHumanStrings() { + return this.failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at ${r.path.join("")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + } + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } +}; + +// ../assertions/lib/private/matchers/absent.ts +var AbsentMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual !== void 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Received ${actual}, but key should be absent` + }); + } + return result; + } +}; + +// ../assertions/lib/private/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} + +// ../assertions/lib/match.ts +var Match = class { + static absent() { + return new AbsentMatch("absent"); + } + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + static not(pattern) { + return new NotMatch("not", pattern); + } + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + static anyValue() { + return new AnyMatch("anyValue"); + } + static stringLikeRegexp(pattern) { + return new StringLikeRegexpMatch("stringLikeRegexp", pattern); + } +}; +var LiteralMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partialObjects = options.partialObjects ?? false; + if (Matcher.isMatcher(this.pattern)) { + throw new Error("LiteralMatch cannot directly contain another matcher. Remove the top-level matcher or nest it more deeply."); + } + } + test(actual) { + if (Array.isArray(this.pattern)) { + return new ArrayMatch(this.name, this.pattern, { subsequence: false, partialObjects: this.partialObjects }).test(actual); + } + if (typeof this.pattern === "object") { + return new ObjectMatch(this.name, this.pattern, { partial: this.partialObjects }).test(actual); + } + const result = new MatchResult(actual); + if (typeof this.pattern !== typeof actual) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected type ${typeof this.pattern} but received ${getType(actual)}` + }); + return result; + } + if (actual !== this.pattern) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected ${this.pattern} but received ${actual}` + }); + } + return result; + } +}; +var ArrayMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.subsequence = options.subsequence ?? true; + this.partialObjects = options.partialObjects ?? false; + } + test(actual) { + if (!Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type array but received ${getType(actual)}` + }); + } + if (!this.subsequence && this.pattern.length !== actual.length) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected array of length ${this.pattern.length} but received ${actual.length}` + }); + } + let patternIdx = 0; + let actualIdx = 0; + const result = new MatchResult(actual); + while (patternIdx < this.pattern.length && actualIdx < actual.length) { + const patternElement = this.pattern[patternIdx]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const matcherName = matcher.name; + if (this.subsequence && (matcherName == "absent" || matcherName == "anyValue")) { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + if (!this.subsequence || !innerResult.hasFailed()) { + result.compose(`[${actualIdx}]`, innerResult); + patternIdx++; + actualIdx++; + } else { + actualIdx++; + } + } + for (; patternIdx < this.pattern.length; patternIdx++) { + const pattern = this.pattern[patternIdx]; + const element = Matcher.isMatcher(pattern) || typeof pattern === "object" ? " " : ` [${pattern}] `; + result.recordFailure({ + matcher: this, + path: [], + message: `Missing element${element}at pattern index ${patternIdx}` + }); + } + return result; + } +}; +var ObjectMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partial = options.partial ?? true; + } + test(actual) { + if (typeof actual !== "object" || Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type object but received ${getType(actual)}` + }); + } + const result = new MatchResult(actual); + if (!this.partial) { + for (const a of Object.keys(actual)) { + if (!(a in this.pattern)) { + result.recordFailure({ + matcher: this, + path: [`/${a}`], + message: "Unexpected key" + }); + } + } + } + for (const [patternKey, patternVal] of Object.entries(this.pattern)) { + if (!(patternKey in actual) && !(patternVal instanceof AbsentMatch)) { + result.recordFailure({ + matcher: this, + path: [`/${patternKey}`], + message: `Missing key '${patternKey}' among {${Object.keys(actual).join(",")}}` + }); + continue; + } + const matcher = Matcher.isMatcher(patternVal) ? patternVal : new LiteralMatch(this.name, patternVal, { partialObjects: this.partial }); + const inner = matcher.test(actual[patternKey]); + result.compose(`/${patternKey}`, inner); + } + return result; + } +}; +var SerializedJson = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + if (getType(actual) !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + return result; + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + result.recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + return result; + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + result.compose(`(${this.name})`, innerResult); + return result; + } +}; +var NotMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(actual); + const result = new MatchResult(actual); + if (innerResult.failCount === 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Found unexpected match: ${JSON.stringify(actual, void 0, 2)}` + }); + } + return result; + } +}; +var AnyMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual == null) { + result.recordFailure({ + matcher: this, + path: [], + message: "Expected a value but found none" + }); + } + return result; + } +}; +var StringLikeRegexpMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + const regex = new RegExp(this.pattern, "gm"); + if (typeof actual !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected a string, but got '${typeof actual}'` + }); + } + if (!regex.test(actual)) { + result.recordFailure({ + matcher: this, + path: [], + message: `String '${actual}' did not match pattern '${this.pattern}'` + }); + } + return result; + } +}; + +// lib/assertions/providers/lambda-handler/base.ts +var https = __toESM(require("https")); +var url = __toESM(require("url")); +var CustomResourceHandler = class { + constructor(event, context) { + this.event = event; + this.context = context; + this.timedOut = false; + this.timeout = setTimeout(async () => { + await this.respond({ + status: "FAILED", + reason: "Lambda Function Timeout", + data: this.context.logStreamName + }); + this.timedOut = true; + }, context.getRemainingTimeInMillis() - 1200); + this.event = event; + this.physicalResourceId = extractPhysicalResourceId(event); + } + async handle() { + try { + console.log(`Event: ${JSON.stringify({ ...this.event, ResponseURL: "..." })}`); + const response = await this.processEvent(this.event.ResourceProperties); + console.log(`Event output : ${JSON.stringify(response)}`); + await this.respond({ + status: "SUCCESS", + reason: "OK", + data: response + }); + } catch (e) { + console.log(e); + await this.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + } finally { + clearTimeout(this.timeout); + } + } + respond(response) { + if (this.timedOut) { + return; + } + const cfResponse = { + Status: response.status, + Reason: response.reason, + PhysicalResourceId: this.physicalResourceId, + StackId: this.event.StackId, + RequestId: this.event.RequestId, + LogicalResourceId: this.event.LogicalResourceId, + NoEcho: false, + Data: response.data + }; + const responseBody = JSON.stringify(cfResponse); + console.log("Responding to CloudFormation", responseBody); + const parsedUrl = url.parse(this.event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: "PUT", + headers: { "content-type": "", "content-length": responseBody.length } + }; + return new Promise((resolve, reject) => { + try { + const request2 = https.request(requestOptions, resolve); + request2.on("error", reject); + request2.write(responseBody); + request2.end(); + } catch (e) { + reject(e); + } + }); + } +}; +function extractPhysicalResourceId(event) { + switch (event.RequestType) { + case "Create": + return event.LogicalResourceId; + case "Update": + case "Delete": + return event.PhysicalResourceId; + } +} + +// lib/assertions/providers/lambda-handler/assertion.ts +var AssertionHandler = class extends CustomResourceHandler { + async processEvent(request2) { + let actual = decodeCall(request2.actual); + const expected = decodeCall(request2.expected); + let result; + const matcher = new MatchCreator(expected).getMatcher(); + console.log(`Testing equality between ${JSON.stringify(request2.actual)} and ${JSON.stringify(request2.expected)}`); + const matchResult = matcher.test(actual); + matchResult.finished(); + if (matchResult.hasFailed()) { + result = { + data: JSON.stringify({ + status: "fail", + message: [ + ...matchResult.toHumanStrings(), + JSON.stringify(matchResult.target, void 0, 2) + ].join("\n") + }) + }; + if (request2.failDeployment) { + throw new Error(result.data); + } + } else { + result = { + data: JSON.stringify({ + status: "success" + }) + }; + } + return result; + } +}; +var MatchCreator = class { + constructor(obj) { + this.parsedObj = { + matcher: obj + }; + } + getMatcher() { + try { + const final = JSON.parse(JSON.stringify(this.parsedObj), function(_k, v) { + const nested = Object.keys(v)[0]; + switch (nested) { + case "$ArrayWith": + return Match.arrayWith(v[nested]); + case "$ObjectLike": + return Match.objectLike(v[nested]); + case "$StringLike": + return Match.stringLikeRegexp(v[nested]); + default: + return v; + } + }); + if (Matcher.isMatcher(final.matcher)) { + return final.matcher; + } + return Match.exact(final.matcher); + } catch { + return Match.exact(this.parsedObj.matcher); + } + } +}; +function decodeCall(call) { + if (!call) { + return void 0; + } + try { + const parsed = JSON.parse(call); + return parsed; + } catch (e) { + return call; + } +} + +// lib/assertions/providers/lambda-handler/utils.ts +function decode(object) { + return JSON.parse(JSON.stringify(object), (_k, v) => { + switch (v) { + case "TRUE:BOOLEAN": + return true; + case "FALSE:BOOLEAN": + return false; + default: + return v; + } + }); +} + +// lib/assertions/providers/lambda-handler/sdk.ts +function flatten(object) { + return Object.assign( + {}, + ...function _flatten(child, path = []) { + return [].concat(...Object.keys(child).map((key) => { + const childKey = Buffer.isBuffer(child[key]) ? child[key].toString("utf8") : child[key]; + return typeof childKey === "object" && childKey !== null ? _flatten(childKey, path.concat([key])) : { [path.concat([key]).join(".")]: childKey }; + })); + }(object) + ); +} +var AwsApiCallHandler = class extends CustomResourceHandler { + async processEvent(request2) { + const AWS = require("aws-sdk"); + console.log(`AWS SDK VERSION: ${AWS.VERSION}`); + const service = new AWS[request2.service](); + const response = await service[request2.api](request2.parameters && decode(request2.parameters)).promise(); + console.log(`SDK response received ${JSON.stringify(response)}`); + delete response.ResponseMetadata; + const respond = { + apiCallResponse: response + }; + const flatData = { + ...flatten(respond) + }; + return request2.flattenResponse === "true" ? flatData : respond; + } +}; + +// lib/assertions/providers/lambda-handler/types.ts +var ASSERT_RESOURCE_TYPE = "Custom::DeployAssert@AssertEquals"; +var SDK_RESOURCE_TYPE_PREFIX = "Custom::DeployAssert@SdkCall"; + +// lib/assertions/providers/lambda-handler/index.ts +async function handler(event, context) { + const provider = createResourceHandler(event, context); + await provider.handle(); +} +function createResourceHandler(event, context) { + if (event.ResourceType.startsWith(SDK_RESOURCE_TYPE_PREFIX)) { + return new AwsApiCallHandler(event, context); + } + switch (event.ResourceType) { + case ASSERT_RESOURCE_TYPE: + return new AssertionHandler(event, context); + default: + throw new Error(`Unsupported resource type "${event.ResourceType}`); + } +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + handler +}); diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/asset.39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/asset.c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip similarity index 78% rename from packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/asset.39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip rename to packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/asset.c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip index d1ef73915c9a0..c1901359f405d 100644 Binary files a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/asset.39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip and b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/asset.c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip differ diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/integ.json b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/integ.json index d4b16af97e21a..39fc7f31c067f 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/integ.json @@ -1,11 +1,12 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { "integ-test-bucket-deployments/DefaultTest": { "stacks": [ "test-bucket-deployments-2" ], - "assertionStack": "integ-test-bucket-deployments/DefaultTest/DeployAssert" + "assertionStack": "integ-test-bucket-deployments/DefaultTest/DeployAssert", + "assertionStackName": "integtestbucketdeploymentsDefaultTestDeployAssertCF25A2DF" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/integtestbucketdeploymentsDefaultTestDeployAssertCF25A2DF.assets.json b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/integtestbucketdeploymentsDefaultTestDeployAssertCF25A2DF.assets.json index 3cd8cfe80d067..031ee31aac438 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/integtestbucketdeploymentsDefaultTestDeployAssertCF25A2DF.assets.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/integtestbucketdeploymentsDefaultTestDeployAssertCF25A2DF.assets.json @@ -1,7 +1,20 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { - "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7": { + "source": { + "path": "asset.84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.bundle", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "afd35caa55048830a313893994672278a347c493331a6c95789e51dc808f8ab7": { "source": { "path": "integtestbucketdeploymentsDefaultTestDeployAssertCF25A2DF.template.json", "packaging": "file" @@ -9,7 +22,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "objectKey": "afd35caa55048830a313893994672278a347c493331a6c95789e51dc808f8ab7.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/integtestbucketdeploymentsDefaultTestDeployAssertCF25A2DF.template.json b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/integtestbucketdeploymentsDefaultTestDeployAssertCF25A2DF.template.json index ad9d0fb73d1dd..c61d18b4db7ca 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/integtestbucketdeploymentsDefaultTestDeployAssertCF25A2DF.template.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/integtestbucketdeploymentsDefaultTestDeployAssertCF25A2DF.template.json @@ -1,4 +1,131 @@ { + "Resources": { + "AwsApiCallS3listObjects": { + "Type": "Custom::DeployAssert@SdkCallS3listObjects", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "S3", + "api": "listObjects", + "parameters": { + "Bucket": { + "Fn::ImportValue": "test-bucket-deployments-2:ExportsOutputRefDestination4368A3649E0CF338F" + }, + "MaxKeys": 1 + }, + "flattenResponse": "false", + "salt": "1663692910218" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AwsApiCallS3listObjectsAssertEqualsS3listObjects03999BE0": { + "Type": "Custom::DeployAssert@AssertEquals", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "actual": { + "Fn::GetAtt": [ + "AwsApiCallS3listObjects", + "apiCallResponse" + ] + }, + "expected": "{\"$ObjectLike\":{\"Contents\":[{\"Key\":\"fc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222e.zip\"}]}}", + "salt": "1663692910218" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "s3:ListObjects" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:ListBucket" + ], + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.zip" + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + } + }, + "Outputs": { + "AssertionResultsAssertEqualsS3listObjectsbf7bcf3cadf69373bbd3568d34e8bb76": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallS3listObjectsAssertEqualsS3listObjects03999BE0", + "data" + ] + } + } + }, "Parameters": { "BootstrapVersion": { "Type": "AWS::SSM::Parameter::Value", diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/manifest.json index 96c55c4f57df5..95cf53691ab08 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -23,7 +23,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/c31d9a77778dbea517260eab87b23f587990f9dffa9f880fc7a635c539128d61.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/0968a3358fd197c49e9f651d6ca44c21cb651331c3db04f74402b1cf3143bb5e.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -72,10 +72,7 @@ "/test-bucket-deployments-2/DeployMe/AwsCliLayer/Resource": [ { "type": "aws:cdk:logicalId", - "data": "DeployMeAwsCliLayer5F9219E9", - "trace": [ - "!!DESTRUCTIVE_CHANGES: WILL_REPLACE" - ] + "data": "DeployMeAwsCliLayer5F9219E9" } ], "/test-bucket-deployments-2/DeployMe/CustomResource/Default": [ @@ -243,10 +240,7 @@ "/test-bucket-deployments-2/DeployMeWithEfsStorage/AwsCliLayer/Resource": [ { "type": "aws:cdk:logicalId", - "data": "DeployMeWithEfsStorageAwsCliLayer1619A3EE", - "trace": [ - "!!DESTRUCTIVE_CHANGES: WILL_REPLACE" - ] + "data": "DeployMeWithEfsStorageAwsCliLayer1619A3EE" } ], "/test-bucket-deployments-2/DeployMeWithEfsStorage/CustomResource-c8e45d2d82aec23f89c7172e7e6f994ff3d9c444fa/Default": [ @@ -336,10 +330,7 @@ "/test-bucket-deployments-2/DeployWithPrefix/AwsCliLayer/Resource": [ { "type": "aws:cdk:logicalId", - "data": "DeployWithPrefixAwsCliLayerC9DDB597", - "trace": [ - "!!DESTRUCTIVE_CHANGES: WILL_REPLACE" - ] + "data": "DeployWithPrefixAwsCliLayerC9DDB597" } ], "/test-bucket-deployments-2/DeployWithPrefix/CustomResource/Default": [ @@ -369,10 +360,7 @@ "/test-bucket-deployments-2/DeployWithMetadata/AwsCliLayer/Resource": [ { "type": "aws:cdk:logicalId", - "data": "DeployWithMetadataAwsCliLayer2C774B41", - "trace": [ - "!!DESTRUCTIVE_CHANGES: WILL_REPLACE" - ] + "data": "DeployWithMetadataAwsCliLayer2C774B41" } ], "/test-bucket-deployments-2/DeployWithMetadata/CustomResource/Default": [ @@ -384,10 +372,7 @@ "/test-bucket-deployments-2/DeployMeWithoutDeletingFilesOnDestination/AwsCliLayer/Resource": [ { "type": "aws:cdk:logicalId", - "data": "DeployMeWithoutDeletingFilesOnDestinationAwsCliLayer4D54C41C", - "trace": [ - "!!DESTRUCTIVE_CHANGES: WILL_REPLACE" - ] + "data": "DeployMeWithoutDeletingFilesOnDestinationAwsCliLayer4D54C41C" } ], "/test-bucket-deployments-2/DeployMeWithoutDeletingFilesOnDestination/CustomResource/Default": [ @@ -399,10 +384,7 @@ "/test-bucket-deployments-2/DeployMeWithExcludedFilesOnDestination/AwsCliLayer/Resource": [ { "type": "aws:cdk:logicalId", - "data": "DeployMeWithExcludedFilesOnDestinationAwsCliLayer68F5E11D", - "trace": [ - "!!DESTRUCTIVE_CHANGES: WILL_REPLACE" - ] + "data": "DeployMeWithExcludedFilesOnDestinationAwsCliLayer68F5E11D" } ], "/test-bucket-deployments-2/DeployMeWithExcludedFilesOnDestination/CustomResource/Default": [ @@ -411,6 +393,48 @@ "data": "DeployMeWithExcludedFilesOnDestinationCustomResource48D69581" } ], + "/test-bucket-deployments-2/Destination4/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Destination4368A3649" + } + ], + "/test-bucket-deployments-2/Destination4/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Destination4Policy242B92D3" + } + ], + "/test-bucket-deployments-2/Destination4/AutoDeleteObjectsCustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "Destination4AutoDeleteObjectsCustomResource0ACF1B31" + } + ], + "/test-bucket-deployments-2/DeployMeWithoutExtractingFilesOnDestination/AwsCliLayer/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "DeployMeWithoutExtractingFilesOnDestinationAwsCliLayerC65F79D8" + } + ], + "/test-bucket-deployments-2/DeployMeWithoutExtractingFilesOnDestination/CustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "DeployMeWithoutExtractingFilesOnDestinationCustomResourceF1CAAF89" + } + ], + "/test-bucket-deployments-2/ObjectKey0": [ + { + "type": "aws:cdk:logicalId", + "data": "ObjectKey0" + } + ], + "/test-bucket-deployments-2/Exports/Output{\"Ref\":\"Destination4368A3649\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefDestination4368A3649E0CF338F" + } + ], "/test-bucket-deployments-2/BootstrapVersion": [ { "type": "aws:cdk:logicalId", @@ -442,7 +466,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/afd35caa55048830a313893994672278a347c493331a6c95789e51dc808f8ab7.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -455,9 +479,40 @@ } }, "dependencies": [ + "test-bucket-deployments-2", "integtestbucketdeploymentsDefaultTestDeployAssertCF25A2DF.assets" ], "metadata": { + "/integ-test-bucket-deployments/DefaultTest/DeployAssert/AwsApiCallS3listObjects/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallS3listObjects" + } + ], + "/integ-test-bucket-deployments/DefaultTest/DeployAssert/AwsApiCallS3listObjects/AssertEqualsS3listObjects/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallS3listObjectsAssertEqualsS3listObjects03999BE0" + } + ], + "/integ-test-bucket-deployments/DefaultTest/DeployAssert/AwsApiCallS3listObjects/AssertEqualsS3listObjects/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAssertEqualsS3listObjectsbf7bcf3cadf69373bbd3568d34e8bb76" + } + ], + "/integ-test-bucket-deployments/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/integ-test-bucket-deployments/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], "/integ-test-bucket-deployments/DefaultTest/DeployAssert/BootstrapVersion": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/test-bucket-deployments-2.assets.json b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/test-bucket-deployments-2.assets.json index 722803261cdc9..c6c41d715497e 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/test-bucket-deployments-2.assets.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/test-bucket-deployments-2.assets.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { "60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26": { "source": { @@ -14,28 +14,28 @@ } } }, - "39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080": { + "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc": { "source": { - "path": "asset.39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip", + "path": "asset.c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip", "packaging": "file" }, "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip", + "objectKey": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } }, - "f98b78092dcdd31f5e6d47489beb5f804d4835ef86a8085d0a2053cb9ae711da": { + "6ddcf10002539818a9256eff3fb2b22aa09298d8f946e26ba121c175a600c44e": { "source": { - "path": "asset.f98b78092dcdd31f5e6d47489beb5f804d4835ef86a8085d0a2053cb9ae711da", + "path": "asset.6ddcf10002539818a9256eff3fb2b22aa09298d8f946e26ba121c175a600c44e", "packaging": "zip" }, "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "f98b78092dcdd31f5e6d47489beb5f804d4835ef86a8085d0a2053cb9ae711da.zip", + "objectKey": "6ddcf10002539818a9256eff3fb2b22aa09298d8f946e26ba121c175a600c44e.zip", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } @@ -53,7 +53,7 @@ } } }, - "c31d9a77778dbea517260eab87b23f587990f9dffa9f880fc7a635c539128d61": { + "0968a3358fd197c49e9f651d6ca44c21cb651331c3db04f74402b1cf3143bb5e": { "source": { "path": "test-bucket-deployments-2.template.json", "packaging": "file" @@ -61,7 +61,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "c31d9a77778dbea517260eab87b23f587990f9dffa9f880fc7a635c539128d61.json", + "objectKey": "0968a3358fd197c49e9f651d6ca44c21cb651331c3db04f74402b1cf3143bb5e.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/test-bucket-deployments-2.template.json b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/test-bucket-deployments-2.template.json index 4ffbc2e243e33..710b469769e08 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/test-bucket-deployments-2.template.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/test-bucket-deployments-2.template.json @@ -167,7 +167,7 @@ "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "S3Key": "39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip" + "S3Key": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip" }, "Description": "/opt/awscli/aws" } @@ -302,6 +302,12 @@ "Arn" ] }, + { + "Fn::GetAtt": [ + "Destination4368A3649", + "Arn" + ] + }, { "Fn::GetAtt": [ "Destination920A3C57", @@ -336,6 +342,20 @@ ] ] }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Destination4368A3649", + "Arn" + ] + }, + "/*" + ] + ] + }, { "Fn::Join": [ "", @@ -370,7 +390,7 @@ "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "S3Key": "f98b78092dcdd31f5e6d47489beb5f804d4835ef86a8085d0a2053cb9ae711da.zip" + "S3Key": "6ddcf10002539818a9256eff3fb2b22aa09298d8f946e26ba121c175a600c44e.zip" }, "Role": { "Fn::GetAtt": [ @@ -790,7 +810,7 @@ "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "S3Key": "39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip" + "S3Key": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip" }, "Description": "/opt/awscli/aws" }, @@ -1234,7 +1254,7 @@ "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "S3Key": "f98b78092dcdd31f5e6d47489beb5f804d4835ef86a8085d0a2053cb9ae711da.zip" + "S3Key": "6ddcf10002539818a9256eff3fb2b22aa09298d8f946e26ba121c175a600c44e.zip" }, "Role": { "Fn::GetAtt": [ @@ -1408,7 +1428,7 @@ "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "S3Key": "39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip" + "S3Key": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip" }, "Description": "/opt/awscli/aws" } @@ -1534,7 +1554,7 @@ "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "S3Key": "39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip" + "S3Key": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip" }, "Description": "/opt/awscli/aws" } @@ -1581,7 +1601,7 @@ "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "S3Key": "39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip" + "S3Key": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip" }, "Description": "/opt/awscli/aws" } @@ -1619,7 +1639,7 @@ "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "S3Key": "39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip" + "S3Key": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip" }, "Description": "/opt/awscli/aws" } @@ -1652,6 +1672,155 @@ }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" + }, + "Destination4368A3649": { + "Type": "AWS::S3::Bucket", + "Properties": { + "Tags": [ + { + "Key": "aws-cdk:auto-delete-objects", + "Value": "true" + }, + { + "Key": "aws-cdk:cr-owned:0ac1b9c4", + "Value": "true" + } + ] + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "Destination4Policy242B92D3": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "Destination4368A3649" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "Destination4368A3649", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Destination4368A3649", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "Destination4AutoDeleteObjectsCustomResource0ACF1B31": { + "Type": "Custom::S3AutoDeleteObjects", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", + "Arn" + ] + }, + "BucketName": { + "Ref": "Destination4368A3649" + } + }, + "DependsOn": [ + "Destination4Policy242B92D3" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "DeployMeWithoutExtractingFilesOnDestinationAwsCliLayerC65F79D8": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip" + }, + "Description": "/opt/awscli/aws" + } + }, + "DeployMeWithoutExtractingFilesOnDestinationCustomResourceF1CAAF89": { + "Type": "Custom::CDKBucketDeployment", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536", + "Arn" + ] + }, + "SourceBucketNames": [ + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + } + ], + "SourceObjectKeys": [ + "fc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222e.zip" + ], + "DestinationBucketName": { + "Ref": "Destination4368A3649" + }, + "RetainOnDelete": false, + "Extract": false, + "Prune": true + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Outputs": { + "ObjectKey0": { + "Value": { + "Fn::Select": [ + 0, + { + "Fn::GetAtt": [ + "DeployMeWithoutExtractingFilesOnDestinationCustomResourceF1CAAF89", + "SourceObjectKeys" + ] + } + ] + } + }, + "ExportsOutputRefDestination4368A3649E0CF338F": { + "Value": { + "Ref": "Destination4368A3649" + }, + "Export": { + "Name": "test-bucket-deployments-2:ExportsOutputRefDestination4368A3649E0CF338F" + } } }, "Parameters": { diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/tree.json b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/tree.json index 49f7ab5b41a53..3c59b408944ae 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.integ.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } }, "test-bucket-deployments-2": { @@ -135,14 +135,14 @@ "id": "Default", "path": "test-bucket-deployments-2/Destination/AutoDeleteObjectsCustomResource/Default", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" } } }, @@ -159,30 +159,30 @@ "id": "Staging", "path": "test-bucket-deployments-2/Custom::S3AutoDeleteObjectsCustomResourceProvider/Staging", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "Role": { "id": "Role", "path": "test-bucket-deployments-2/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" } }, "Handler": { "id": "Handler", "path": "test-bucket-deployments-2/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CustomResourceProvider", + "version": "0.0.0" } }, "DeployMe": { @@ -201,8 +201,8 @@ "id": "Stage", "path": "test-bucket-deployments-2/DeployMe/AwsCliLayer/Code/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -229,7 +229,7 @@ "s3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "s3Key": "39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip" + "s3Key": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip" }, "description": "/opt/awscli/aws" } @@ -261,8 +261,8 @@ "id": "Stage", "path": "test-bucket-deployments-2/DeployMe/Asset1/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -287,14 +287,14 @@ "id": "Default", "path": "test-bucket-deployments-2/DeployMe/CustomResource/Default", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" } } }, @@ -430,6 +430,12 @@ "Arn" ] }, + { + "Fn::GetAtt": [ + "Destination4368A3649", + "Arn" + ] + }, { "Fn::GetAtt": [ "Destination920A3C57", @@ -464,6 +470,20 @@ ] ] }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Destination4368A3649", + "Arn" + ] + }, + "/*" + ] + ] + }, { "Fn::Join": [ "", @@ -516,8 +536,8 @@ "id": "Stage", "path": "test-bucket-deployments-2/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -544,7 +564,7 @@ "s3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "s3Key": "f98b78092dcdd31f5e6d47489beb5f804d4835ef86a8085d0a2053cb9ae711da.zip" + "s3Key": "6ddcf10002539818a9256eff3fb2b22aa09298d8f946e26ba121c175a600c44e.zip" }, "role": { "Fn::GetAtt": [ @@ -648,8 +668,8 @@ "id": "Acl", "path": "test-bucket-deployments-2/InlineVpc/PublicSubnet1/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -815,8 +835,8 @@ "id": "Acl", "path": "test-bucket-deployments-2/InlineVpc/PublicSubnet2/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -982,8 +1002,8 @@ "id": "Acl", "path": "test-bucket-deployments-2/InlineVpc/PrivateSubnet1/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -1101,8 +1121,8 @@ "id": "Acl", "path": "test-bucket-deployments-2/InlineVpc/PrivateSubnet2/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -1232,8 +1252,8 @@ "id": "Stage", "path": "test-bucket-deployments-2/DeployMeWithEfsStorage/AwsCliLayer/Code/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -1260,7 +1280,7 @@ "s3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "s3Key": "39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip" + "s3Key": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip" }, "description": "/opt/awscli/aws" } @@ -1292,8 +1312,8 @@ "id": "Stage", "path": "test-bucket-deployments-2/DeployMeWithEfsStorage/Asset1/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -1318,14 +1338,14 @@ "id": "Default", "path": "test-bucket-deployments-2/DeployMeWithEfsStorage/CustomResource-c8e45d2d82aec23f89c7172e7e6f994ff3d9c444fa/Default", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" } } }, @@ -1769,8 +1789,8 @@ "id": "Stage", "path": "test-bucket-deployments-2/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756Cc8e45d2d82aec23f89c7172e7e6f994ff3d9c444fa/Code/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -1831,7 +1851,7 @@ "s3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "s3Key": "f98b78092dcdd31f5e6d47489beb5f804d4835ef86a8085d0a2053cb9ae711da.zip" + "s3Key": "6ddcf10002539818a9256eff3fb2b22aa09298d8f946e26ba121c175a600c44e.zip" }, "role": { "Fn::GetAtt": [ @@ -2015,14 +2035,14 @@ "id": "Default", "path": "test-bucket-deployments-2/Destination2/AutoDeleteObjectsCustomResource/Default", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" } } }, @@ -2047,8 +2067,8 @@ "id": "Stage", "path": "test-bucket-deployments-2/DeployWithPrefix/AwsCliLayer/Code/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -2075,7 +2095,7 @@ "s3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "s3Key": "39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip" + "s3Key": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip" }, "description": "/opt/awscli/aws" } @@ -2107,8 +2127,8 @@ "id": "Stage", "path": "test-bucket-deployments-2/DeployWithPrefix/Asset1/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -2133,14 +2153,14 @@ "id": "Default", "path": "test-bucket-deployments-2/DeployWithPrefix/CustomResource/Default", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" } } }, @@ -2253,14 +2273,14 @@ "id": "Default", "path": "test-bucket-deployments-2/Destination3/AutoDeleteObjectsCustomResource/Default", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" } } }, @@ -2285,8 +2305,8 @@ "id": "Stage", "path": "test-bucket-deployments-2/DeployWithMetadata/AwsCliLayer/Code/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -2313,7 +2333,7 @@ "s3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "s3Key": "39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip" + "s3Key": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip" }, "description": "/opt/awscli/aws" } @@ -2345,8 +2365,8 @@ "id": "Stage", "path": "test-bucket-deployments-2/DeployWithMetadata/Asset1/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -2371,14 +2391,14 @@ "id": "Default", "path": "test-bucket-deployments-2/DeployWithMetadata/CustomResource/Default", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" } } }, @@ -2403,8 +2423,8 @@ "id": "Stage", "path": "test-bucket-deployments-2/DeployMeWithoutDeletingFilesOnDestination/AwsCliLayer/Code/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -2431,7 +2451,7 @@ "s3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "s3Key": "39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip" + "s3Key": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip" }, "description": "/opt/awscli/aws" } @@ -2463,8 +2483,8 @@ "id": "Stage", "path": "test-bucket-deployments-2/DeployMeWithoutDeletingFilesOnDestination/Asset1/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -2489,14 +2509,14 @@ "id": "Default", "path": "test-bucket-deployments-2/DeployMeWithoutDeletingFilesOnDestination/CustomResource/Default", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" } } }, @@ -2521,8 +2541,8 @@ "id": "Stage", "path": "test-bucket-deployments-2/DeployMeWithExcludedFilesOnDestination/AwsCliLayer/Code/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -2549,7 +2569,7 @@ "s3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "s3Key": "39ee629321f531fffd853b944b2d6f3fa7b5276431c9a4fd4dc681303ab15080.zip" + "s3Key": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip" }, "description": "/opt/awscli/aws" } @@ -2581,8 +2601,8 @@ "id": "Stage", "path": "test-bucket-deployments-2/DeployMeWithExcludedFilesOnDestination/Asset1/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -2607,14 +2627,252 @@ "id": "Default", "path": "test-bucket-deployments-2/DeployMeWithExcludedFilesOnDestination/CustomResource/Default", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3-deployment.BucketDeployment", + "version": "0.0.0" + } + }, + "Destination4": { + "id": "Destination4", + "path": "test-bucket-deployments-2/Destination4", + "children": { + "Resource": { + "id": "Resource", + "path": "test-bucket-deployments-2/Destination4/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "aws-cdk:auto-delete-objects", + "value": "true" + }, + { + "key": "aws-cdk:cr-owned:0ac1b9c4", + "value": "true" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucket", + "version": "0.0.0" + } + }, + "Policy": { + "id": "Policy", + "path": "test-bucket-deployments-2/Destination4/Policy", + "children": { + "Resource": { + "id": "Resource", + "path": "test-bucket-deployments-2/Destination4/Policy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::BucketPolicy", + "aws:cdk:cloudformation:props": { + "bucket": { + "Ref": "Destination4368A3649" + }, + "policyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "Destination4368A3649", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Destination4368A3649", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketPolicy", + "version": "0.0.0" + } + }, + "AutoDeleteObjectsCustomResource": { + "id": "AutoDeleteObjectsCustomResource", + "path": "test-bucket-deployments-2/Destination4/AutoDeleteObjectsCustomResource", + "children": { + "Default": { + "id": "Default", + "path": "test-bucket-deployments-2/Destination4/AutoDeleteObjectsCustomResource/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.Bucket", + "version": "0.0.0" + } + }, + "DeployMeWithoutExtractingFilesOnDestination": { + "id": "DeployMeWithoutExtractingFilesOnDestination", + "path": "test-bucket-deployments-2/DeployMeWithoutExtractingFilesOnDestination", + "children": { + "AwsCliLayer": { + "id": "AwsCliLayer", + "path": "test-bucket-deployments-2/DeployMeWithoutExtractingFilesOnDestination/AwsCliLayer", + "children": { + "Code": { + "id": "Code", + "path": "test-bucket-deployments-2/DeployMeWithoutExtractingFilesOnDestination/AwsCliLayer/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "test-bucket-deployments-2/DeployMeWithoutExtractingFilesOnDestination/AwsCliLayer/Code/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "test-bucket-deployments-2/DeployMeWithoutExtractingFilesOnDestination/AwsCliLayer/Code/AssetBucket", + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3-assets.Asset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "test-bucket-deployments-2/DeployMeWithoutExtractingFilesOnDestination/AwsCliLayer/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::LayerVersion", + "aws:cdk:cloudformation:props": { + "content": { + "s3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "s3Key": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip" + }, + "description": "/opt/awscli/aws" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.CfnLayerVersion", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/lambda-layer-awscli.AwsCliLayer", + "version": "0.0.0" + } + }, + "CustomResourceHandler": { + "id": "CustomResourceHandler", + "path": "test-bucket-deployments-2/DeployMeWithoutExtractingFilesOnDestination/CustomResourceHandler", + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.SingletonFunction", + "version": "0.0.0" + } + }, + "Asset1": { + "id": "Asset1", + "path": "test-bucket-deployments-2/DeployMeWithoutExtractingFilesOnDestination/Asset1", + "children": { + "Stage": { + "id": "Stage", + "path": "test-bucket-deployments-2/DeployMeWithoutExtractingFilesOnDestination/Asset1/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "test-bucket-deployments-2/DeployMeWithoutExtractingFilesOnDestination/Asset1/AssetBucket", + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3-assets.Asset", + "version": "0.0.0" + } + }, + "CustomResource": { + "id": "CustomResource", + "path": "test-bucket-deployments-2/DeployMeWithoutExtractingFilesOnDestination/CustomResource", + "children": { + "Default": { + "id": "Default", + "path": "test-bucket-deployments-2/DeployMeWithoutExtractingFilesOnDestination/CustomResource/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" } } }, @@ -2622,11 +2880,37 @@ "fqn": "@aws-cdk/aws-s3-deployment.BucketDeployment", "version": "0.0.0" } + }, + "ObjectKey0": { + "id": "ObjectKey0", + "path": "test-bucket-deployments-2/ObjectKey0", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "Exports": { + "id": "Exports", + "path": "test-bucket-deployments-2/Exports", + "children": { + "Output{\"Ref\":\"Destination4368A3649\"}": { + "id": "Output{\"Ref\":\"Destination4368A3649\"}", + "path": "test-bucket-deployments-2/Exports/Output{\"Ref\":\"Destination4368A3649\"}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" } }, "integ-test-bucket-deployments": { @@ -2642,15 +2926,151 @@ "path": "integ-test-bucket-deployments/DefaultTest/Default", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } }, "DeployAssert": { "id": "DeployAssert", "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert", + "children": { + "AwsApiCallS3listObjects": { + "id": "AwsApiCallS3listObjects", + "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert/AwsApiCallS3listObjects", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert/AwsApiCallS3listObjects/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert/AwsApiCallS3listObjects/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert/AwsApiCallS3listObjects/Default", + "children": { + "Default": { + "id": "Default", + "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert/AwsApiCallS3listObjects/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "AssertEqualsS3listObjects": { + "id": "AssertEqualsS3listObjects", + "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert/AwsApiCallS3listObjects/AssertEqualsS3listObjects", + "children": { + "AssertionProvider": { + "id": "AssertionProvider", + "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert/AwsApiCallS3listObjects/AssertEqualsS3listObjects/AssertionProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert/AwsApiCallS3listObjects/AssertEqualsS3listObjects/AssertionProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert/AwsApiCallS3listObjects/AssertEqualsS3listObjects/Default", + "children": { + "Default": { + "id": "Default", + "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert/AwsApiCallS3listObjects/AssertEqualsS3listObjects/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "AssertionResults": { + "id": "AssertionResults", + "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert/AwsApiCallS3listObjects/AssertEqualsS3listObjects/AssertionResults", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.EqualsAssertion", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AwsApiCall", + "version": "0.0.0" + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81": { + "id": "SingletonFunction1488541a7b23466481b69b4408076b81", + "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81", + "children": { + "Staging": { + "id": "Staging", + "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "integ-test-bucket-deployments/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" } } }, @@ -2667,8 +3087,8 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts index 098d5063ca631..c153cfc7ff64f 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts @@ -6,6 +6,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; +import * as sns from '@aws-cdk/aws-sns'; import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; @@ -943,6 +944,70 @@ test('deploy with multiple exclude and include filters', () => { }); }); +test('deploy without extracting files in destination', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + + // WHEN + new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website.zip'))], + destinationBucket: bucket, + extract: false, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('Custom::CDKBucketDeployment', { + Extract: false, + }); +}); + +test('deploy without extracting files in destination and get the object key', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + + // WHEN + const deployment = new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website.zip'))], + destinationBucket: bucket, + extract: false, + }); + + // Tests object key retrieval. + void(cdk.Fn.select(0, deployment.objectKeys)); + + // THEN + Template.fromStack(stack).hasResourceProperties('Custom::CDKBucketDeployment', { + Extract: false, + }); +}); + +test('given a source with markers and extract is false, BucketDeployment throws an error', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + const topic: sns.Topic = new sns.Topic(stack, 'SomeTopic1', {}); + + // WHEN + const file = s3deploy.Source.jsonData('MyJsonObject', { + 'config.json': { + Foo: { + Bar: topic.topicArn, // marker + }, + }, + }); + + // THEN + expect(() => { + new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [file], + destinationBucket: bucket, + extract: false, + }); + }).toThrow('Some sources are incompatible with extract=false; sources with deploy-time values (such as \'snsTopic.topicArn\') must be extracted.'); +}); + test('deployment allows vpc to be implicitly supplied to lambda', () => { // GIVEN diff --git a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.ts index 9c64d3e4205ee..491b98fb20a53 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.ts +++ b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.ts @@ -72,13 +72,48 @@ class TestBucketDeployment extends cdk.Stack { retainOnDelete: false, }); + const bucket4 = new s3.Bucket(this, 'Destination4', { + publicReadAccess: false, + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, // needed for integration test cleanup + }); + + const noExtractBucketDeployment = new s3deploy.BucketDeployment(this, 'DeployMeWithoutExtractingFilesOnDestination', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], + destinationBucket: bucket4, + extract: false, + retainOnDelete: false, + }); + + new cdk.CfnOutput(this, 'ObjectKey0', { + value: cdk.Fn.select(0, noExtractBucketDeployment.objectKeys), + }); } } const app = new cdk.App(); const testCase = new TestBucketDeployment(app, 'test-bucket-deployments-2'); -new integ.IntegTest(app, 'integ-test-bucket-deployments', { + +// Assert that DeployMeWithoutExtractingFilesOnDestination deploys a zip file to bucket4 +const bucket4 = testCase.node.findChild('Destination4') as s3.Bucket; +const integTest = new integ.IntegTest(app, 'integ-test-bucket-deployments', { testCases: [testCase], }); +const listObjectsCall = integTest.assertions.awsApiCall('S3', 'listObjects', { + Bucket: bucket4.bucketName, + MaxKeys: 1, +}); +listObjectsCall.provider.addToRolePolicy({ + Effect: 'Allow', + Action: ['s3:GetObject', 's3:ListBucket'], + Resource: ['*'], +}); +listObjectsCall.expect(integ.ExpectedResult.objectLike({ + Contents: [ + { + Key: 'fc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222e.zip', + }, + ], +})); app.synth(); diff --git a/packages/@aws-cdk/aws-s3-deployment/test/lambda/aws b/packages/@aws-cdk/aws-s3-deployment/test/lambda/aws index 5cfd9275eeb71..027a8b802ed59 100755 --- a/packages/@aws-cdk/aws-s3-deployment/test/lambda/aws +++ b/packages/@aws-cdk/aws-s3-deployment/test/lambda/aws @@ -15,8 +15,12 @@ import shutil scriptdir=os.path.dirname(os.path.realpath(__file__)) -# if "cp" is called with a local destination, copy a test zip file to the destination or -if sys.argv[2] == "cp" and not sys.argv[4].startswith("s3://"): +# if "cp" is called to contents, copy a test zip file to contents +if sys.argv[2] == "cp" and sys.argv[4].endswith("/contents"): + shutil.copy(os.path.join(scriptdir, 'test.zip'), sys.argv[4]) + sys.argv[4] = "/tmp/contents" +# else if "cp" is called with a local destination, copy a test zip file to the destination +elif sys.argv[2] == "cp" and not sys.argv[4].startswith("s3://"): shutil.copyfile(os.path.join(scriptdir, 'test.zip'), sys.argv[4]) sys.argv[4] = "archive.zip" diff --git a/packages/@aws-cdk/aws-s3-deployment/test/lambda/test.py b/packages/@aws-cdk/aws-s3-deployment/test/lambda/test.py index b999aa5a4e797..00a16385a0073 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/lambda/test.py +++ b/packages/@aws-cdk/aws-s3-deployment/test/lambda/test.py @@ -155,6 +155,34 @@ def test_update_include_exclude(self): ["s3", "sync", "--delete", "--exclude", "/sample/*", "--include", "/sample/*.json", "contents.zip", "s3:///"] ) + def test_create_no_extract_file(self): + invoke_handler("Create", { + "SourceBucketNames": [""], + "SourceObjectKeys": [""], + "DestinationBucketName": "", + "Extract": "false" + }) + + self.assertAwsCommands( + ["s3", "cp", "s3:///", "/tmp/contents"], + ["s3", "sync", "--delete", "contents.zip", "s3:///"] + ) + + def test_update_no_extract_file(self): + invoke_handler("Update", { + "SourceBucketNames": [""], + "SourceObjectKeys": [""], + "DestinationBucketName": "", + "Extract": "false" + }, old_resource_props={ + "DestinationBucketName": "", + }, physical_id="") + + self.assertAwsCommands( + ["s3", "cp", "s3:///", "/tmp/contents"], + ["s3", "sync", "--delete", "contents.zip", "s3:///"] + ) + def test_create_multiple_include_exclude(self): invoke_handler("Create", { "SourceBucketNames": [""], diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index 9dac4bc5cc743..04f5057ddb54f 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -87,7 +87,7 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", - "aws-sdk": "^2.848.0", + "aws-sdk": "^2.1211.0", "jest": "^27.5.1" }, "dependencies": { diff --git a/packages/@aws-cdk/cfnspec/CHANGELOG.md b/packages/@aws-cdk/cfnspec/CHANGELOG.md index 9e9427136047f..45cff09aed9cd 100644 --- a/packages/@aws-cdk/cfnspec/CHANGELOG.md +++ b/packages/@aws-cdk/cfnspec/CHANGELOG.md @@ -3,6 +3,32 @@ ## New Resource Types +## Attribute Changes + +* AWS::Redshift::ClusterSubnetGroup ClusterSubnetGroupName (__added__) + +## Property Changes + +* AWS::Glue::Job ExecutionClass (__added__) +* AWS::Glue::Job NonOverridableArguments (__added__) +* AWS::Glue::Trigger EventBatchingCondition (__added__) +* AWS::Glue::Trigger Type.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::Glue::Workflow MaxConcurrentRuns (__added__) +* AWS::Redshift::ClusterSubnetGroup SubnetIds.DuplicatesAllowed (__deleted__) +* AWS::Redshift::ClusterSubnetGroup Tags.DuplicatesAllowed (__deleted__) + +## Property Type Changes + +* AWS::Glue::Trigger.EventBatchingCondition (__added__) + + +# CloudFormation Resource Specification v89.0.0 + +## New Resource Types + + ## Attribute Changes * AWS::EC2::VPNConnection Documentation (__changed__) diff --git a/packages/@aws-cdk/cfnspec/spec-source/specification/000_cfn/000_official/000_AWS_Glue.json b/packages/@aws-cdk/cfnspec/spec-source/specification/000_cfn/000_official/000_AWS_Glue.json index a1cfe89917973..6a0dc8458ba5d 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/specification/000_cfn/000_official/000_AWS_Glue.json +++ b/packages/@aws-cdk/cfnspec/spec-source/specification/000_cfn/000_official/000_AWS_Glue.json @@ -1465,6 +1465,23 @@ } } }, + "AWS::Glue::Trigger.EventBatchingCondition": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-glue-trigger-eventbatchingcondition.html", + "Properties": { + "BatchSize": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-glue-trigger-eventbatchingcondition.html#cfn-glue-trigger-eventbatchingcondition-batchsize", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "BatchWindow": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-glue-trigger-eventbatchingcondition.html#cfn-glue-trigger-eventbatchingcondition-batchwindow", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Glue::Trigger.NotificationProperty": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-glue-trigger-notificationproperty.html", "Properties": { @@ -1790,6 +1807,12 @@ "Required": false, "UpdateType": "Mutable" }, + "ExecutionClass": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-job.html#cfn-glue-job-executionclass", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "ExecutionProperty": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-job.html#cfn-glue-job-executionproperty", "Required": false, @@ -1826,6 +1849,12 @@ "Required": false, "UpdateType": "Immutable" }, + "NonOverridableArguments": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-job.html#cfn-glue-job-nonoverridablearguments", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, "NotificationProperty": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-job.html#cfn-glue-job-notificationproperty", "Required": false, @@ -2174,6 +2203,12 @@ "Required": false, "UpdateType": "Mutable" }, + "EventBatchingCondition": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-trigger.html#cfn-glue-trigger-eventbatchingcondition", + "Required": false, + "Type": "EventBatchingCondition", + "UpdateType": "Mutable" + }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-trigger.html#cfn-glue-trigger-name", "PrimitiveType": "String", @@ -2208,7 +2243,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-trigger.html#cfn-glue-trigger-type", "PrimitiveType": "String", "Required": true, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "WorkflowName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-trigger.html#cfn-glue-trigger-workflowname", @@ -2233,6 +2268,12 @@ "Required": false, "UpdateType": "Mutable" }, + "MaxConcurrentRuns": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-workflow.html#cfn-glue-workflow-maxconcurrentruns", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-workflow.html#cfn-glue-workflow-name", "PrimitiveType": "String", diff --git a/packages/@aws-cdk/cfnspec/spec-source/specification/000_cfn/000_official/000_AWS_Redshift.json b/packages/@aws-cdk/cfnspec/spec-source/specification/000_cfn/000_official/000_AWS_Redshift.json index 60ab9bfb6744c..b23081964e3ec 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/specification/000_cfn/000_official/000_AWS_Redshift.json +++ b/packages/@aws-cdk/cfnspec/spec-source/specification/000_cfn/000_official/000_AWS_Redshift.json @@ -470,6 +470,11 @@ } }, "AWS::Redshift::ClusterSubnetGroup": { + "Attributes": { + "ClusterSubnetGroupName": { + "PrimitiveType": "String" + } + }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-clustersubnetgroup.html", "Properties": { "Description": { @@ -480,7 +485,6 @@ }, "SubnetIds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-clustersubnetgroup.html#cfn-redshift-clustersubnetgroup-subnetids", - "DuplicatesAllowed": true, "PrimitiveItemType": "String", "Required": true, "Type": "List", @@ -488,7 +492,6 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-clustersubnetgroup.html#cfn-redshift-clustersubnetgroup-tags", - "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, "Type": "List", diff --git a/packages/@aws-cdk/core/README.md b/packages/@aws-cdk/core/README.md index 54a0e38446481..dff275e6e263a 100644 --- a/packages/@aws-cdk/core/README.md +++ b/packages/@aws-cdk/core/README.md @@ -1085,4 +1085,74 @@ It's possible to synthesize the project with more Resources than the allowed (or Set the context key `@aws-cdk/core:stackResourceLimit` with the proper value, being 0 for disable the limit of resources. +## App Context + +[Context values](https://docs.aws.amazon.com/cdk/v2/guide/context.html) are key-value pairs that can be associated with an app, stack, or construct. +One common use case for context is to use it for enabling/disabling [feature flags](https://docs.aws.amazon.com/cdk/v2/guide/featureflags.html). There are several places +where context can be specified. They are listed below in the order they are evaluated (items at the +top take precedence over those below). + +- The `node.setContext()` method +- The `postCliContext` prop when you create an `App` +- The CLI via the `--context` CLI argument +- The `cdk.json` file via the `context` key: +- The `cdk.context.json` file: +- The `~/.cdk.json` file via the `context` key: +- The `context` prop when you create an `App` + +### Examples of setting context + +```ts +new App({ + context: { + '@aws-cdk/core:newStyleStackSynthesis': true, + }, +}); +``` + +```ts +const app = new App(); +app.node.setContext('@aws-cdk/core:newStyleStackSynthesis', true); +``` + +```ts +new App({ + postCliContext: { + '@aws-cdk/core:newStyleStackSynthesis': true, + }, +}); +``` + +```console +cdk synth --context @aws-cdk/core:newStyleStackSynthesis=true +``` + +_cdk.json_ + +```json +{ + "context": { + "@aws-cdk/core:newStyleStackSynthesis": true + } +} +``` + +_cdk.context.json_ + +```json +{ + "@aws-cdk/core:newStyleStackSynthesis": true +} +``` + +_~/.cdk.json_ + +```json +{ + "context": { + "@aws-cdk/core:newStyleStackSynthesis": true + } +} +``` + diff --git a/packages/@aws-cdk/core/lib/app.ts b/packages/@aws-cdk/core/lib/app.ts index 09ad1a3f81b79..5973445ab74b4 100644 --- a/packages/@aws-cdk/core/lib/app.ts +++ b/packages/@aws-cdk/core/lib/app.ts @@ -68,6 +68,37 @@ export interface AppProps { */ readonly context?: { [key: string]: any }; + /** + * Additional context values for the application. + * + * Context provided here has precedence over context set by: + * + * - The CLI via --context + * - The `context` key in `cdk.json` + * - The {@link AppProps.context} property + * + * This property is recommended over the {@link AppProps.context} property since you + * can make final decision over which context value to take in your app. + * + * Context can be read from any construct using `node.getContext(key)`. + * + * @example + * // context from the CLI and from `cdk.json` are stored in the + * // CDK_CONTEXT env variable + * const cliContext = JSON.parse(process.env.CDK_CONTEXT!); + * + * // determine whether to take the context passed in the CLI or not + * const determineValue = process.env.PROD ? cliContext.SOMEKEY : 'my-prod-value'; + * new App({ + * postCliContext: { + * SOMEKEY: determineValue, + * }, + * }); + * + * @default - no additional context + */ + readonly postCliContext?: { [key: string]: any }; + /** * Include construct tree metadata as part of the Cloud Assembly. * @@ -112,7 +143,7 @@ export class App extends Stage { Object.defineProperty(this, APP_SYMBOL, { value: true }); - this.loadContext(props.context); + this.loadContext(props.context, props.postCliContext); if (props.stackTraces === false) { this.node.setContext(cxapi.DISABLE_METADATA_STACK_TRACE, true); @@ -136,7 +167,7 @@ export class App extends Stage { } } - private loadContext(defaults: { [key: string]: string } = { }) { + private loadContext(defaults: { [key: string]: string } = { }, final: { [key: string]: string } = {}) { // prime with defaults passed through constructor for (const [k, v] of Object.entries(defaults)) { this.node.setContext(k, v); @@ -151,6 +182,11 @@ export class App extends Stage { for (const [k, v] of Object.entries(contextFromEnvironment)) { this.node.setContext(k, v); } + + // finalContext passed through constructor overwrites + for (const [k, v] of Object.entries(final)) { + this.node.setContext(k, v); + } } } diff --git a/packages/@aws-cdk/core/test/app.test.ts b/packages/@aws-cdk/core/test/app.test.ts index 0cf612d690f1a..d9ab8c0396f3d 100644 --- a/packages/@aws-cdk/core/test/app.test.ts +++ b/packages/@aws-cdk/core/test/app.test.ts @@ -126,6 +126,25 @@ describe('app', () => { expect(prog.node.tryGetContext('key2')).toEqual('val2'); }); + test('context passed through finalContext prop has precedence', () => { + process.env[cxapi.CONTEXT_ENV] = JSON.stringify({ + key1: 'val1', + key2: 'val2', + }); + const prog = new App({ + context: { + key1: 'val3', + key2: 'val4', + }, + postCliContext: { + key1: 'val5', + key2: 'val6', + }, + }); + expect(prog.node.tryGetContext('key1')).toEqual('val5'); + expect(prog.node.tryGetContext('key2')).toEqual('val6'); + }); + test('context from the command line can be used when creating the stack', () => { const output = synthStack('stack2', false, { ctx1: 'HELLO' }); diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index 8f45d7ea2d7ea..1af94d610e99b 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -93,7 +93,7 @@ "@types/fs-extra": "^8.1.2", "@types/jest": "^27.5.2", "@types/sinon": "^9.0.11", - "aws-sdk": "^2.848.0", + "aws-sdk": "^2.1211.0", "aws-sdk-mock": "5.6.0", "fs-extra": "^9.1.0", "nock": "^13.2.9", diff --git a/packages/@aws-cdk/integ-runner/THIRD_PARTY_LICENSES b/packages/@aws-cdk/integ-runner/THIRD_PARTY_LICENSES index 37495f5e64326..b8726a7138d0e 100644 --- a/packages/@aws-cdk/integ-runner/THIRD_PARTY_LICENSES +++ b/packages/@aws-cdk/integ-runner/THIRD_PARTY_LICENSES @@ -156,7 +156,7 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH RE ---------------- -** aws-sdk@2.1215.0 - https://www.npmjs.com/package/aws-sdk/v/2.1215.0 | Apache-2.0 +** aws-sdk@2.1219.0 - https://www.npmjs.com/package/aws-sdk/v/2.1219.0 | Apache-2.0 AWS SDK for JavaScript Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/integ-tests/README.md b/packages/@aws-cdk/integ-tests/README.md index 205e0699c88c9..7145e3c2d0ff4 100644 --- a/packages/@aws-cdk/integ-tests/README.md +++ b/packages/@aws-cdk/integ-tests/README.md @@ -182,7 +182,7 @@ There are two main scenarios in which assertions are created. - Part of an integration test using `integ-runner` In this case you would create an integration test using the `IntegTest` construct and then make assertions using the `assert` property. -You should **not** utilize the assertion constructs directly, but should instead use the `methods` on `IntegTest.assert`. +You should **not** utilize the assertion constructs directly, but should instead use the `methods` on `IntegTest.assertions`. ```ts declare const app: App; @@ -410,3 +410,21 @@ describe.expect(ExpectedResult.objectLike({ })); ``` +#### Chain ApiCalls + +Sometimes it may be necessary to chain API Calls. Since each API call is its own resource, all you +need to do is add a dependency between the calls. There is an helper method `next` that can be used. + +```ts +declare const integ: IntegTest; + +integ.assertions.awsApiCall('S3', 'putObject', { + Bucket: 'my-bucket', + Key: 'my-key', + Body: 'helloWorld', +}).next(integ.assertions.awsApiCall('S3', 'getObject', { + Bucket: 'my-bucket', + Key: 'my-key', +})); +``` + diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/api-call-base.ts b/packages/@aws-cdk/integ-tests/lib/assertions/api-call-base.ts new file mode 100644 index 0000000000000..08fd3885e61d8 --- /dev/null +++ b/packages/@aws-cdk/integ-tests/lib/assertions/api-call-base.ts @@ -0,0 +1,139 @@ +import { CustomResource, Reference } from '@aws-cdk/core'; +import { Construct, IConstruct } from 'constructs'; +import { ExpectedResult } from './common'; +import { AssertionsProvider } from './providers'; + +/** + * Represents an ApiCall + */ +export interface IApiCall extends IConstruct { + /** + * access the AssertionsProvider. This can be used to add additional IAM policies + * the the provider role policy + * + * @example + * declare const apiCall: AwsApiCall; + * apiCall.provider.addToRolePolicy({ + * Effect: 'Allow', + * Action: ['s3:GetObject'], + * Resource: ['*'], + * }); + */ + readonly provider: AssertionsProvider; + + /** + * Returns the value of an attribute of the custom resource of an arbitrary + * type. Attributes are returned from the custom resource provider through the + * `Data` map where the key is the attribute name. + * + * @param attributeName the name of the attribute + * @returns a token for `Fn::GetAtt`. Use `Token.asXxx` to encode the returned `Reference` as a specific type or + * use the convenience `getAttString` for string attributes. + */ + getAtt(attributeName: string): Reference; + + /** + * Returns the value of an attribute of the custom resource of type string. + * Attributes are returned from the custom resource provider through the + * `Data` map where the key is the attribute name. + * + * @param attributeName the name of the attribute + * @returns a token for `Fn::GetAtt` encoded as a string. + */ + getAttString(attributeName: string): string; + + /** + * Assert that the ExpectedResult is equal + * to the result of the AwsApiCall + * + * @example + * declare const integ: IntegTest; + * const invoke = integ.assertions.invokeFunction({ + * functionName: 'my-func', + * }); + * invoke.expect(ExpectedResult.objectLike({ Payload: 'OK' })); + */ + expect(expected: ExpectedResult): void; + + /** + * Assert that the ExpectedResult is equal + * to the result of the AwsApiCall at the given path. + * + * For example the SQS.receiveMessage api response would look + * like: + * + * If you wanted to assert the value of `Body` you could do + * + * @example + * const actual = { + * Messages: [{ + * MessageId: '', + * ReceiptHandle: '', + * MD5OfBody: '', + * Body: 'hello', + * Attributes: {}, + * MD5OfMessageAttributes: {}, + * MessageAttributes: {} + * }] + * }; + * + * + * declare const integ: IntegTest; + * const message = integ.assertions.awsApiCall('SQS', 'receiveMessage'); + * + * message.assertAtPath('Messages.0.Body', ExpectedResult.stringLikeRegexp('hello')); + */ + assertAtPath(path: string, expected: ExpectedResult): void; + + /** + * Allows you to chain IApiCalls. This adds an explicit dependency + * betweent the two resources. + * + * Returns the IApiCall provided as `next` + * + * @example + * declare const first: IApiCall; + * declare const second: IApiCall; + * + * first.next(second); + */ + next(next: IApiCall): IApiCall; +} + +/** + * Base class for an ApiCall + */ +export abstract class ApiCallBase extends Construct implements IApiCall { + protected abstract readonly apiCallResource: CustomResource; + protected expectedResult?: string; + protected flattenResponse: string = 'false'; + protected stateMachineArn?: string; + + public abstract readonly provider: AssertionsProvider; + + constructor(scope: Construct, id: string) { + super(scope, id); + + } + + public getAtt(attributeName: string): Reference { + this.flattenResponse = 'true'; + return this.apiCallResource.getAtt(`apiCallResponse.${attributeName}`); + } + + public getAttString(attributeName: string): string { + this.flattenResponse = 'true'; + return this.apiCallResource.getAttString(`apiCallResponse.${attributeName}`); + } + + public expect(expected: ExpectedResult): void { + this.expectedResult = expected.result; + } + + public abstract assertAtPath(path: string, expected: ExpectedResult): void; + + public next(next: IApiCall): IApiCall { + next.node.addDependency(this); + return next; + } +} diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/common.ts b/packages/@aws-cdk/integ-tests/lib/assertions/common.ts index 6daa9e510133c..13c1e50cddc08 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/common.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/common.ts @@ -1,5 +1,5 @@ import { CustomResource } from '@aws-cdk/core'; -import { IAwsApiCall } from './sdk'; +import { IApiCall } from './api-call-base'; /** * Represents the "actual" results to compare @@ -17,7 +17,7 @@ export abstract class ActualResult { /** * Get the actual results from a AwsApiCall */ - public static fromAwsApiCall(query: IAwsApiCall, attribute: string): ActualResult { + public static fromAwsApiCall(query: IApiCall, attribute: string): ActualResult { return { result: query.getAttString(attribute), }; diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/index.ts b/packages/@aws-cdk/integ-tests/lib/assertions/index.ts index 6622ddabcb560..b81ba41add625 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/index.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/index.ts @@ -4,3 +4,4 @@ export * from './assertions'; export * from './providers'; export * from './common'; export * from './match'; +export * from './api-call-base'; diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/private/deploy-assert.ts b/packages/@aws-cdk/integ-tests/lib/assertions/private/deploy-assert.ts index 068350c459aab..321082d7f8ce4 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/private/deploy-assert.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/private/deploy-assert.ts @@ -1,9 +1,10 @@ import { Stack } from '@aws-cdk/core'; import { Construct, IConstruct, Node } from 'constructs'; +import { IApiCall } from '../api-call-base'; import { EqualsAssertion } from '../assertions'; import { ExpectedResult, ActualResult } from '../common'; import { md5hash } from '../private/hash'; -import { AwsApiCall, LambdaInvokeFunction, IAwsApiCall, LambdaInvokeFunctionProps } from '../sdk'; +import { AwsApiCall, LambdaInvokeFunction, LambdaInvokeFunctionProps } from '../sdk'; import { IDeployAssert } from '../types'; @@ -49,7 +50,7 @@ export class DeployAssert extends Construct implements IDeployAssert { Object.defineProperty(this, DEPLOY_ASSERT_SYMBOL, { value: true }); } - public awsApiCall(service: string, api: string, parameters?: any): IAwsApiCall { + public awsApiCall(service: string, api: string, parameters?: any): IApiCall { return new AwsApiCall(this.scope, `AwsApiCall${service}${api}`, { api, service, @@ -57,7 +58,7 @@ export class DeployAssert extends Construct implements IDeployAssert { }); } - public invokeFunction(props: LambdaInvokeFunctionProps): IAwsApiCall { + public invokeFunction(props: LambdaInvokeFunctionProps): IApiCall { const hash = md5hash(this.scope.resolve(props)); return new LambdaInvokeFunction(this.scope, `LambdaInvoke${hash}`, props); } diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts b/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts index b79ecc4bce356..148ba0cc6a415 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts @@ -1,93 +1,10 @@ import { ArnFormat, CfnResource, CustomResource, Lazy, Reference, Stack } from '@aws-cdk/core'; -import { Construct, IConstruct } from 'constructs'; +import { Construct } from 'constructs'; +import { ApiCallBase } from './api-call-base'; import { EqualsAssertion } from './assertions'; import { ActualResult, ExpectedResult } from './common'; import { AssertionsProvider, SDK_RESOURCE_TYPE_PREFIX } from './providers'; -/** - * Interface for creating a custom resource that will perform - * an API call using the AWS SDK - */ -export interface IAwsApiCall extends IConstruct { - /** - * access the AssertionsProvider. This can be used to add additional IAM policies - * the the provider role policy - * - * @example - * declare const apiCall: AwsApiCall; - * apiCall.provider.addToRolePolicy({ - * Effect: 'Allow', - * Action: ['s3:GetObject'], - * Resource: ['*'], - * }); - */ - readonly provider: AssertionsProvider; - - /** - * Returns the value of an attribute of the custom resource of an arbitrary - * type. Attributes are returned from the custom resource provider through the - * `Data` map where the key is the attribute name. - * - * @param attributeName the name of the attribute - * @returns a token for `Fn::GetAtt`. Use `Token.asXxx` to encode the returned `Reference` as a specific type or - * use the convenience `getAttString` for string attributes. - */ - getAtt(attributeName: string): Reference; - - /** - * Returns the value of an attribute of the custom resource of type string. - * Attributes are returned from the custom resource provider through the - * `Data` map where the key is the attribute name. - * - * @param attributeName the name of the attribute - * @returns a token for `Fn::GetAtt` encoded as a string. - */ - getAttString(attributeName: string): string; - - /** - * Assert that the ExpectedResult is equal - * to the result of the AwsApiCall - * - * @example - * declare const integ: IntegTest; - * const invoke = integ.assertions.invokeFunction({ - * functionName: 'my-func', - * }); - * invoke.expect(ExpectedResult.objectLike({ Payload: 'OK' })); - */ - expect(expected: ExpectedResult): void; - - /** - * Assert that the ExpectedResult is equal - * to the result of the AwsApiCall at the given path. - * - * For example the SQS.receiveMessage api response would look - * like: - * - * If you wanted to assert the value of `Body` you could do - * - * @example - * const actual = { - * Messages: [{ - * MessageId: '', - * ReceiptHandle: '', - * MD5OfBody: '', - * Body: 'hello', - * Attributes: {}, - * MD5OfMessageAttributes: {}, - * MessageAttributes: {} - * }] - * }; - * - * - * declare const integ: IntegTest; - * const message = integ.assertions.awsApiCall('SQS', 'receiveMessage'); - * - * message.assertAtPath('Messages.0.Body', ExpectedResult.stringLikeRegexp('hello')); - */ - assertAtPath(path: string, expected: ExpectedResult): void; -} - /** * Options to perform an AWS JavaScript V2 API call */ @@ -119,9 +36,8 @@ export interface AwsApiCallProps extends AwsApiCallOptions { } * Construct that creates a custom resource that will perform * a query using the AWS SDK */ -export class AwsApiCall extends Construct implements IAwsApiCall { - private readonly sdkCallResource: CustomResource; - private flattenResponse: string = 'false'; +export class AwsApiCall extends ApiCallBase { + protected readonly apiCallResource: CustomResource; private readonly name: string; public readonly provider: AssertionsProvider; @@ -133,7 +49,7 @@ export class AwsApiCall extends Construct implements IAwsApiCall { this.provider.addPolicyStatementFromSdkCall(props.service, props.api); this.name = `${props.service}${props.api}`; - this.sdkCallResource = new CustomResource(this, 'Default', { + this.apiCallResource = new CustomResource(this, 'Default', { serviceToken: this.provider.serviceToken, properties: { service: props.service, @@ -146,23 +62,23 @@ export class AwsApiCall extends Construct implements IAwsApiCall { }); // Needed so that all the policies set up by the provider should be available before the custom resource is provisioned. - this.sdkCallResource.node.addDependency(this.provider); + this.apiCallResource.node.addDependency(this.provider); } public getAtt(attributeName: string): Reference { this.flattenResponse = 'true'; - return this.sdkCallResource.getAtt(`apiCallResponse.${attributeName}`); + return this.apiCallResource.getAtt(`apiCallResponse.${attributeName}`); } public getAttString(attributeName: string): string { this.flattenResponse = 'true'; - return this.sdkCallResource.getAttString(`apiCallResponse.${attributeName}`); + return this.apiCallResource.getAttString(`apiCallResponse.${attributeName}`); } public expect(expected: ExpectedResult): void { new EqualsAssertion(this, `AssertEquals${this.name}`, { expected, - actual: ActualResult.fromCustomResource(this.sdkCallResource, 'apiCallResponse'), + actual: ActualResult.fromCustomResource(this.apiCallResource, 'apiCallResponse'), }); } diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/types.ts b/packages/@aws-cdk/integ-tests/lib/assertions/types.ts index 7c5dd185aa058..468eaf54b28ec 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/types.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/types.ts @@ -1,5 +1,6 @@ +import { IApiCall } from './api-call-base'; import { ExpectedResult, ActualResult } from './common'; -import { IAwsApiCall, LambdaInvokeFunctionProps } from './sdk'; +import { LambdaInvokeFunctionProps } from './sdk'; /** * Interface that allows for registering a list of assertions @@ -26,7 +27,7 @@ export interface IDeployAssert { * Messages: [{ Body: 'hello' }], * })); */ - awsApiCall(service: string, api: string, parameters?: any): IAwsApiCall; + awsApiCall(service: string, api: string, parameters?: any): IApiCall; /** * Invoke a lambda function and return the response which can be asserted @@ -41,7 +42,7 @@ export interface IDeployAssert { * Payload: '200', * })); */ - invokeFunction(props: LambdaInvokeFunctionProps): IAwsApiCall; + invokeFunction(props: LambdaInvokeFunctionProps): IApiCall; /** * Assert that the ExpectedResult is equal diff --git a/packages/@aws-cdk/integ-tests/package.json b/packages/@aws-cdk/integ-tests/package.json index d72add060b19a..762647ff39582 100644 --- a/packages/@aws-cdk/integ-tests/package.json +++ b/packages/@aws-cdk/integ-tests/package.json @@ -61,18 +61,18 @@ }, "license": "Apache-2.0", "devDependencies": { + "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cx-api": "0.0.0", - "@aws-cdk/assertions": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/fs-extra": "^8.1.2", "@types/jest": "^27.5.2", "@types/node": "^14.18.29", + "aws-sdk": "^2.1211.0", + "aws-sdk-mock": "5.6.0", "jest": "^27.5.1", "nock": "^13.2.9", - "aws-sdk-mock": "5.6.0", - "sinon": "^9.2.4", - "aws-sdk": "^2.1093.0" + "sinon": "^9.2.4" }, "dependencies": { "@aws-cdk/cloud-assembly-schema": "0.0.0", diff --git a/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture b/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture index 7cf368abcf6db..168caf8aaf2af 100644 --- a/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture @@ -4,6 +4,7 @@ import { IntegTest, IntegTestCaseStack, AwsApiCall, + IApiCall, EqualsAssertion, ActualResult, ExpectedResult, diff --git a/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts b/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts index 7be64290b1b01..3a03487425c5c 100644 --- a/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts +++ b/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts @@ -22,6 +22,35 @@ describe('AwsApiCall', () => { }); }); + test('then', () => { + // GIVEN + const app = new App(); + const deplossert = new DeployAssert(app); + + // WHEN + const first = deplossert.awsApiCall('MyService', 'MyApi'); + first.next(deplossert.awsApiCall('MyOtherService', 'MyOtherApi')); + + // THEN + const template = Template.fromStack(deplossert.scope); + template.resourceCountIs('AWS::Lambda::Function', 1); + template.hasResourceProperties('Custom::DeployAssert@SdkCallMyServiceMyApi', { + service: 'MyService', + api: 'MyApi', + parameters: Match.absent(), + }); + template.hasResource('Custom::DeployAssert@SdkCallMyOtherServiceMyOtherApi', { + Properties: { + service: 'MyOtherService', + api: 'MyOtherApi', + parameters: Match.absent(), + }, + DependsOn: [ + 'AwsApiCallMyServiceMyApi', + ], + }); + }); + test('parameters', () => { // GIVEN const app = new App(); diff --git a/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/package-lock.json b/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/package-lock.json index 19220af66d40b..928ccb60c9cce 100644 --- a/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/package-lock.json +++ b/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/package-lock.json @@ -607,9 +607,9 @@ } }, "node_modules/vm2": { - "version": "3.9.8", - "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.8.tgz", - "integrity": "sha512-/1PYg/BwdKzMPo8maOZ0heT7DLI0DAFTm7YQaz/Lim9oIaFZsJs3EdtalvXuBfZwczNwsYhju75NW4d6E+4q+w==", + "version": "3.9.11", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.11.tgz", + "integrity": "sha512-PFG8iJRSjvvBdisowQ7iVF580DXb1uCIiGaXgm7tynMR1uTBlv7UJlB1zdv5KJ+Tmq1f0Upnj3fayoEOPpCBKg==", "dev": true, "dependencies": { "acorn": "^8.7.0", @@ -1106,9 +1106,9 @@ "dev": true }, "vm2": { - "version": "3.9.8", - "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.8.tgz", - "integrity": "sha512-/1PYg/BwdKzMPo8maOZ0heT7DLI0DAFTm7YQaz/Lim9oIaFZsJs3EdtalvXuBfZwczNwsYhju75NW4d6E+4q+w==", + "version": "3.9.11", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.11.tgz", + "integrity": "sha512-PFG8iJRSjvvBdisowQ7iVF580DXb1uCIiGaXgm7tynMR1uTBlv7UJlB1zdv5KJ+Tmq1f0Upnj3fayoEOPpCBKg==", "dev": true, "requires": { "acorn": "^8.7.0", diff --git a/packages/@aws-cdk/pipelines/README.md b/packages/@aws-cdk/pipelines/README.md index ed0142737175c..57e0b0b3ed15f 100644 --- a/packages/@aws-cdk/pipelines/README.md +++ b/packages/@aws-cdk/pipelines/README.md @@ -544,6 +544,32 @@ const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { }); ``` +#### Deploying without change sets + +Deployment is done by default with `CodePipeline` engine using change sets, +i.e. to first create a change set and then execute it. This allows you to inject +steps that inspect the change set and approve or reject it, but failed deployments +are not retryable and creation of the change set costs time. + +The creation of change sets can be switched off by setting `useChangeSets: false`: + +```ts +declare const synth: pipelines.ShellStep; + +class PipelineStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth, + + // Disable change set creation and make deployments in pipeline as single step + useChangeSets: false, + }); + } +} +``` + ### Validation Every `addStage()` and `addWave()` command takes additional options. As part of these options, @@ -867,11 +893,11 @@ because those values are passed in directly to the underlying `codepipeline.Pipe Docker can be used in 3 different places in the pipeline: -* If you are using Docker image assets in your application stages: Docker will +- If you are using Docker image assets in your application stages: Docker will run in the asset publishing projects. -* If you are using Docker image assets in your stack (for example as +- If you are using Docker image assets in your stack (for example as images for your CodeBuild projects): Docker will run in the self-mutate project. -* If you are using Docker to bundle file assets anywhere in your project (for +- If you are using Docker to bundle file assets anywhere in your project (for example, if you are using such construct libraries as `@aws-cdk/aws-lambda-nodejs`): Docker will run in the *synth* project. @@ -1069,26 +1095,26 @@ $ npx cdk bootstrap \ These command lines explained: -* `npx`: means to use the CDK CLI from the current NPM install. If you are using +- `npx`: means to use the CDK CLI from the current NPM install. If you are using a global install of the CDK CLI, leave this out. -* `--profile`: should indicate a profile with administrator privileges that has +- `--profile`: should indicate a profile with administrator privileges that has permissions to provision a pipeline in the indicated account. You can leave this flag out if either the AWS default credentials or the `AWS_*` environment variables confer these permissions. -* `--cloudformation-execution-policies`: ARN of the managed policy that future CDK +- `--cloudformation-execution-policies`: ARN of the managed policy that future CDK deployments should execute with. By default this is `AdministratorAccess`, but if you also specify the `--trust` flag to give another Account permissions to deploy into the current account, you must specify a value here. -* `--trust`: indicates which other account(s) should have permissions to deploy +- `--trust`: indicates which other account(s) should have permissions to deploy CDK applications into this account. In this case we indicate the Pipeline's account, but you could also use this for developer accounts (don't do that for production application accounts though!). -* `--trust-for-lookup`: gives a more limited set of permissions to the +- `--trust-for-lookup`: gives a more limited set of permissions to the trusted account, only allowing it to look up values such as availability zones, EC2 images and VPCs. `--trust-for-lookup` does not give permissions to modify anything in the account. Note that `--trust` implies `--trust-for-lookup`, so you don't need to specify the same acocunt twice. -* `aws://222222222222/us-east-2`: the account and region we're bootstrapping. +- `aws://222222222222/us-east-2`: the account and region we're bootstrapping. > Be aware that anyone who has access to the trusted Accounts **effectively has all > permissions conferred by the configured CloudFormation execution policies**, @@ -1126,10 +1152,10 @@ The "new" bootstrap stack (obtained by running `cdk bootstrap` with `CDK_NEW_BOOTSTRAP=1`) is slightly more elaborate than the "old" stack. It contains: -* An S3 bucket and ECR repository with predictable names, so that we can reference +- An S3 bucket and ECR repository with predictable names, so that we can reference assets in these storage locations *without* the use of CloudFormation template parameters. -* A set of roles with permissions to access these asset locations and to execute +- A set of roles with permissions to access these asset locations and to execute CloudFormation, assumable from whatever accounts you specify under `--trust`. It is possible and safe to migrate from the old bootstrap stack to the new @@ -1209,15 +1235,15 @@ very nature the library cannot take care of everything. We therefore expect you to mind the following: -* Maintain dependency hygiene and vet 3rd-party software you use. Any software you +- Maintain dependency hygiene and vet 3rd-party software you use. Any software you run on your build machine has the ability to change the infrastructure that gets deployed. Be careful with the software you depend on. -* Use dependency locking to prevent accidental upgrades! The default `CdkSynths` that +- Use dependency locking to prevent accidental upgrades! The default `CdkSynths` that come with CDK Pipelines will expect `package-lock.json` and `yarn.lock` to ensure your dependencies are the ones you expect. -* Credentials to production environments should be short-lived. After +- Credentials to production environments should be short-lived. After bootstrapping and the initial pipeline provisioning, there is no more need for developers to have access to any of the account credentials; all further changes can be deployed through git. Avoid the chances of credentials leaking @@ -1292,7 +1318,7 @@ use CDK Pipelines to build pipelines backed by other deployment engines. Here is a list of CDK Libraries that integrate CDK Pipelines with alternative deployment engines: -* GitHub Workflows: [`cdk-pipelines-github`](https://github.com/cdklabs/cdk-pipelines-github) +- GitHub Workflows: [`cdk-pipelines-github`](https://github.com/cdklabs/cdk-pipelines-github) ## Troubleshooting @@ -1329,11 +1355,11 @@ If you see this error during the **Synth** step, it means that CodeBuild is expecting to find a `cdk.out` directory in the root of your CodeBuild project, but the directory wasn't there. There are two common causes for this: -* `cdk synth` is not being executed: `cdk synth` used to be run +- `cdk synth` is not being executed: `cdk synth` used to be run implicitly for you, but you now have to explicitly include the command. For NPM-based projects, add `npx cdk synth` to the end of the `commands` property, for other languages add `npm install -g aws-cdk` and `cdk synth`. -* Your CDK project lives in a subdirectory: you added a `cd ` command +- Your CDK project lives in a subdirectory: you added a `cd ` command to the list of commands; don't forget to tell the `ScriptStep` about the different location of `cdk.out`, by passing `primaryOutputDirectory: '/cdk.out'`. @@ -1426,9 +1452,9 @@ all, and commit a file called `cdk.context.json` with the right lookup values in If you do want to do lookups in the pipeline, the cause is one of the following: -* The target environment has not been bootstrapped; OR -* The target environment has been bootstrapped without the right `--trust` relationship; OR -* The CodeBuild execution role does not have permissions to call `sts:AssumeRole`. +- The target environment has not been bootstrapped; OR +- The target environment has been bootstrapped without the right `--trust` relationship; OR +- The CodeBuild execution role does not have permissions to call `sts:AssumeRole`. See the section called **Context Lookups** for more information on using this feature. @@ -1452,8 +1478,8 @@ following: An "S3 Access Denied" error can have two causes: -* Asset hashes have changed, but self-mutation has been disabled in the pipeline. -* You have deleted and recreated the bootstrap stack, or changed its qualifier. +- Asset hashes have changed, but self-mutation has been disabled in the pipeline. +- You have deleted and recreated the bootstrap stack, or changed its qualifier. #### Self-mutation step has been removed @@ -1495,7 +1521,7 @@ The most automated way to solve the issue is to introduce a secondary bootstrap that the pipeline stack looks for, a change will be detected and the impacted policies and resources will be updated. A hypothetical recovery workflow would look something like this: -* First, for all impacted environments, create a secondary bootstrap stack: +- First, for all impacted environments, create a secondary bootstrap stack: ```sh $ env CDK_NEW_BOOTSTRAP=1 npx cdk bootstrap \ @@ -1504,7 +1530,7 @@ $ env CDK_NEW_BOOTSTRAP=1 npx cdk bootstrap \ aws://111111111111/us-east-1 ``` -* Update all impacted stacks in the pipeline to use this new qualifier. +- Update all impacted stacks in the pipeline to use this new qualifier. See https://docs.aws.amazon.com/cdk/latest/guide/bootstrapping.html for more info. ```ts @@ -1516,11 +1542,11 @@ new Stack(this, 'MyStack', { }); ``` -* Deploy the updated stacks. This will update the stacks to use the roles created in the new bootstrap stack. -* (Optional) Restore back to the original state: - * Revert the change made in step #2 above - * Re-deploy the pipeline to use the original qualifier. - * Delete the temporary bootstrap stack(s) +- Deploy the updated stacks. This will update the stacks to use the roles created in the new bootstrap stack. +- (Optional) Restore back to the original state: + - Revert the change made in step #2 above + - Re-deploy the pipeline to use the original qualifier. + - Delete the temporary bootstrap stack(s) ##### Manual Alternative @@ -1563,19 +1589,106 @@ We recommend you avoid specifying the `cliVersion` parameter at all. By default the pipeline will use the latest CLI version, which will support all cloud assembly versions. +## Using Drop-in Docker Replacements + +By default, the AWS CDK will build and publish Docker image assets using the +`docker` command. However, by specifying the `CDK_DOCKER` environment variable, +you can override the command that will be used to build and publish your +assets. + +In CDK Pipelines, the drop-in replacement for the `docker` command must be +included in the CodeBuild environment and configured for your pipeline. + +### Adding to the default CodeBuild image + +You can add a drop-in Docker replacement command to the default CodeBuild +environment by adding install-phase commands that encode how to install +your tooling and by adding the `CDK_DOCKER` environment variable to your +build environment. + +```ts +declare const source: pipelines.IFileSetProducer; // the repository source +declare const synthCommands: string[]; // Commands to synthesize your app +declare const installCommands: string[]; // Commands to install your toolchain + +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + // Standard CodePipeline properties... + synth: new pipelines.ShellStep('Synth', { + input: source, + commands: synthCommands, + }), + + // Configure CodeBuild to use a drop-in Docker replacement. + codeBuildDefaults: { + buildEnvironment: { + partialBuildSpec: Codebuild.BuildSpec.fromObject({ + phases: { + install: { + // Add the shell commands to install your drop-in Docker + // replacement to the CodeBuild enviromment. + commands: installCommands, + } + } + }), + environmentVariables: { + // Instruct the AWS CDK to use `drop-in-replacement` instead of + // `docker` when building / publishing docker images. + // e.g., `drop-in-replacement build . -f path/to/Dockerfile` + CDK_DOCKER: 'drop-in-replacement', + } + } + }, +}); +``` + +### Using a custom build image + +If you're using a custom build image in CodeBuild, you can override the +command the AWS CDK uses to build Docker images by providing `CDK_DOCKER` as +an `ENV` in your `Dockerfile` or by providing the environment variable in the +pipeline as shown below. + +```ts +declare const source: pipelines.IFileSetProducer; // the repository source +declare const synthCommands: string[]; // Commands to synthesize your app + +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + // Standard CodePipeline properties... + synth: new pipelines.ShellStep('Synth', { + input: source, + commands: synthCommands, + }), + + // Configure CodeBuild to use a drop-in Docker replacement. + codeBuildDefaults: { + buildEnvironment: { + // Provide a custom build image containing your toolchain and the + // pre-installed replacement for the `docker` command. + buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('your-docker-registry'), + environmentVariables: { + // If you haven't provided an `ENV` in your Dockerfile that overrides + // `CDK_DOCKER`, then you must provide the name of the command that + // the AWS CDK should run instead of `docker` here. + CDK_DOCKER: 'drop-in-replacement', + } + } + }, +}); +``` + ## Known Issues There are some usability issues that are caused by underlying technology, and cannot be remedied by CDK at this point. They are reproduced here for completeness. -* **Console links to other accounts will not work**: the AWS CodePipeline +- **Console links to other accounts will not work**: the AWS CodePipeline console will assume all links are relative to the current account. You will not be able to use the pipeline console to click through to a CloudFormation stack in a different account. -* **If a change set failed to apply the pipeline must restarted**: if a change +- **If a change set failed to apply the pipeline must restarted**: if a change set failed to apply, it cannot be retried. The pipeline must be restarted from the top by clicking **Release Change**. -* **A stack that failed to create must be deleted manually**: if a stack +- **A stack that failed to create must be deleted manually**: if a stack failed to create on the first attempt, you must delete it using the CloudFormation console before starting the pipeline again by clicking **Release Change**. diff --git a/packages/@aws-cdk/pipelines/lib/blueprint/stage-deployment.ts b/packages/@aws-cdk/pipelines/lib/blueprint/stage-deployment.ts index 651ccb3b46d53..0761f03b6a01b 100644 --- a/packages/@aws-cdk/pipelines/lib/blueprint/stage-deployment.ts +++ b/packages/@aws-cdk/pipelines/lib/blueprint/stage-deployment.ts @@ -117,6 +117,12 @@ export class StageDeployment { */ public readonly stackSteps: StackSteps[]; + /** + * Determine if all stacks in stage should be deployed with prepare + * step or not. + */ + public readonly prepareStep?: boolean; + private constructor( /** The stacks deployed in this stage */ public readonly stacks: StackDeployment[], props: StageDeploymentProps = {}) { diff --git a/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline.ts b/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline.ts index 8d06e9bb1fa80..8bc6165ea1077 100644 --- a/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline.ts +++ b/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline.ts @@ -214,6 +214,16 @@ export interface CodePipelineProps { * @default - A new role is created */ readonly role?: iam.IRole; + + /** + * Deploy every stack by creating a change set and executing it + * + * When enabled, creates a "Prepare" and "Execute" action for each stack. Disable + * to deploy the stack in one pipeline action. + * + * @default true + */ + readonly useChangeSets?: boolean; } /** @@ -299,6 +309,7 @@ export class CodePipeline extends PipelineBase { private artifacts = new ArtifactMap(); private _synthProject?: cb.IProject; private readonly selfMutation: boolean; + private readonly useChangeSets: boolean; private _myCxAsmRoot?: string; private readonly dockerCredentials: DockerCredential[]; private readonly cachedFnSub = new CachedFnSub(); @@ -325,6 +336,7 @@ export class CodePipeline extends PipelineBase { this.dockerCredentials = props.dockerCredentials ?? []; this.singlePublisherPerAssetType = !(props.publishAssetsInParallel ?? true); this.cliVersion = props.cliVersion ?? preferredCliVersion(); + this.useChangeSets = props.useChangeSets ?? true; } /** @@ -389,6 +401,7 @@ export class CodePipeline extends PipelineBase { const graphFromBp = new PipelineGraph(this, { selfMutation: this.selfMutation, singlePublisherPerAssetType: this.singlePublisherPerAssetType, + prepareStep: this.useChangeSets, }); this._cloudAssemblyFileSet = graphFromBp.cloudAssemblyFileSet; @@ -519,10 +532,15 @@ export class CodePipeline extends PipelineBase { return this.createChangeSetAction(node.data.stack); case 'execute': - return this.executeChangeSetAction(node.data.stack, node.data.captureOutputs); + return node.data.withoutChangeSet + ? this.executeDeploymentAction(node.data.stack, node.data.captureOutputs) + : this.executeChangeSetAction(node.data.stack, node.data.captureOutputs); case 'step': return this.actionFromStep(node, node.data.step); + + default: + throw new Error(`CodePipeline does not support graph nodes of type '${node.data?.type}'. You are probably using a feature this CDK Pipelines implementation does not support.`); } } @@ -630,6 +648,38 @@ export class CodePipeline extends PipelineBase { }; } + private executeDeploymentAction(stack: StackDeployment, captureOutputs: boolean): ICodePipelineActionFactory { + const templateArtifact = this.artifacts.toCodePipeline(this._cloudAssemblyFileSet!); + const templateConfigurationPath = this.writeTemplateConfiguration(stack); + + const region = stack.region !== Stack.of(this).region ? stack.region : undefined; + const account = stack.account !== Stack.of(this).account ? stack.account : undefined; + + const relativeTemplatePath = path.relative(this.myCxAsmRoot, stack.absoluteTemplatePath); + + return { + produceAction: (stage, options) => { + stage.addAction(new cpa.CloudFormationCreateUpdateStackAction({ + actionName: options.actionName, + runOrder: options.runOrder, + stackName: stack.stackName, + templatePath: templateArtifact.atPath(toPosixPath(relativeTemplatePath)), + adminPermissions: true, + role: this.roleFromPlaceholderArn(this.pipeline, region, account, stack.assumeRoleArn), + deploymentRole: this.roleFromPlaceholderArn(this.pipeline, region, account, stack.executionRoleArn), + region: region, + templateConfiguration: templateConfigurationPath + ? templateArtifact.atPath(toPosixPath(templateConfigurationPath)) + : undefined, + cfnCapabilities: [CfnCapabilities.NAMED_IAM, CfnCapabilities.AUTO_EXPAND], + variablesNamespace: captureOutputs ? stackVariableNamespace(stack) : undefined, + })); + + return { runOrdersConsumed: 1 }; + }, + }; + } + private selfMutateAction(): ICodePipelineActionFactory { const installSuffix = this.cliVersion ? `@${this.cliVersion}` : ''; diff --git a/packages/@aws-cdk/pipelines/lib/helpers-internal/pipeline-graph.ts b/packages/@aws-cdk/pipelines/lib/helpers-internal/pipeline-graph.ts index e26058b724b62..22692f509581a 100644 --- a/packages/@aws-cdk/pipelines/lib/helpers-internal/pipeline-graph.ts +++ b/packages/@aws-cdk/pipelines/lib/helpers-internal/pipeline-graph.ts @@ -88,7 +88,7 @@ export class PipelineGraph { if (props.selfMutation) { const stage: AGraph = Graph.of('UpdatePipeline', { type: 'group' }); this.graph.add(stage); - this.selfMutateNode = GraphNode.of('SelfMutate', { type: 'self-update' }); + this.selfMutateNode = aGraphNode('SelfMutate', { type: 'self-update' }); stage.add(this.selfMutateNode); this.selfMutateNode.dependOn(this.synthNode); @@ -134,11 +134,12 @@ export class PipelineGraph { for (const stack of stage.stacks) { const stackGraph: AGraph = Graph.of(this.simpleStackName(stack.stackName, stage.stageName), { type: 'stack-group', stack }); - const prepareNode: AGraphNode | undefined = this.prepareStep ? GraphNode.of('Prepare', { type: 'prepare', stack }) : undefined; - const deployNode: AGraphNode = GraphNode.of('Deploy', { + const prepareNode: AGraphNode | undefined = this.prepareStep ? aGraphNode('Prepare', { type: 'prepare', stack }) : undefined; + const deployNode: AGraphNode = aGraphNode('Deploy', { type: 'execute', stack, captureOutputs: this.queries.stackOutputsReferenced(stack).length > 0, + withoutChangeSet: prepareNode === undefined, }); retGraph.add(stackGraph); @@ -157,9 +158,9 @@ export class PipelineGraph { // add changeset steps at the stack level if (stack.changeSet.length > 0) { if (prepareNode) { - this.addChangeSet(stack.changeSet, prepareNode, deployNode, stackGraph); + this.addChangeSetNode(stack.changeSet, prepareNode, deployNode, stackGraph); } else { - throw new Error('Your pipeline engine does not support changeSet steps'); + throw new Error(`Cannot use \'changeSet\' steps for stack \'${stack.stackName}\': the pipeline does not support them or they have been disabled`); } } @@ -217,7 +218,7 @@ export class PipelineGraph { return retGraph; } - private addChangeSet(changeSet: Step[], prepareNode: AGraphNode, deployNode: AGraphNode, graph: AGraph) { + private addChangeSetNode(changeSet: Step[], prepareNode: AGraphNode, deployNode: AGraphNode, graph: AGraph) { for (const c of changeSet) { const changeSetNode = this.addAndRecurse(c, graph); changeSetNode?.dependOn(prepareNode); @@ -255,7 +256,7 @@ export class PipelineGraph { const previous = this.added.get(step); if (previous) { return previous; } - const node: AGraphNode = GraphNode.of(step.id, { type: 'step', step }); + const node: AGraphNode = aGraphNode(step.id, { type: 'step', step }); // If the step is a source step, change the parent to a special "Source" stage // (CodePipeline wants it that way) @@ -299,7 +300,7 @@ export class PipelineGraph { ? (this.singlePublisher ? 'FileAsset' : `FileAsset${++this._fileAssetCtr}`) : (this.singlePublisher ? 'DockerAsset' : `DockerAsset${++this._dockerAssetCtr}`); - assetNode = GraphNode.of(id, { type: 'publish-assets', assets: [] }); + assetNode = aGraphNode(id, { type: 'publish-assets', assets: [] }); assetsGraph.add(assetNode); assetNode.dependOn(this.lastPreparationNode); @@ -311,6 +312,7 @@ export class PipelineGraph { if (data?.type !== 'publish-assets') { throw new Error(`${assetNode} has the wrong data.type: ${data?.type}`); } + if (!data.assets.some(a => a.assetSelector === stackAsset.assetSelector)) { data.assets.push(stackAsset); } @@ -327,20 +329,48 @@ export class PipelineGraph { } type GraphAnnotation = - { readonly type: 'group' } + | { readonly type: 'group' } | { readonly type: 'stack-group'; readonly stack: StackDeployment } | { readonly type: 'publish-assets'; readonly assets: StackAsset[] } | { readonly type: 'step'; readonly step: Step; isBuildStep?: boolean } | { readonly type: 'self-update' } | { readonly type: 'prepare'; readonly stack: StackDeployment } - | { readonly type: 'execute'; readonly stack: StackDeployment; readonly captureOutputs: boolean } + | ExecuteAnnotation + // Explicitly disable exhaustiveness checking on GraphAnnotation. This forces all consumers to adding + // a 'default' clause which allows us to extend this list in the future. + // The code below looks weird, 'type' must be a non-enumerable type that is not assignable to 'string'. + | { readonly type: { error: 'you must add a default case to your switch' } } ; +interface ExecuteAnnotation { + readonly type: 'execute'; + /** + * The stack to deploy + */ + readonly stack: StackDeployment; + + /** + * Whether or not outputs should be captured + */ + readonly captureOutputs: boolean; + + /** + * If this is executing a change set, or should do a direct deployment + * + * @default false + */ + readonly withoutChangeSet?: boolean; +} + // Type aliases for the graph nodes tagged with our specific annotation type // (to save on generics in the code above). export type AGraphNode = GraphNode; export type AGraph = Graph; +function aGraphNode(id: string, x: GraphAnnotation): AGraphNode { + return GraphNode.of(id, x); +} + function stripPrefix(s: string, prefix: string) { return s.startsWith(prefix) ? s.slice(prefix.length) : s; } \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/package.json b/packages/@aws-cdk/pipelines/package.json index 8737aaf3b2008..286070734f9e0 100644 --- a/packages/@aws-cdk/pipelines/package.json +++ b/packages/@aws-cdk/pipelines/package.json @@ -51,7 +51,7 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", - "aws-sdk": "^2.848.0" + "aws-sdk": "^2.1211.0" }, "peerDependencies": { "@aws-cdk/aws-codebuild": "0.0.0", diff --git a/packages/@aws-cdk/pipelines/test/blueprint/helpers-internal/pipeline-graph.test.ts b/packages/@aws-cdk/pipelines/test/blueprint/helpers-internal/pipeline-graph.test.ts index 69391bf8b0594..747dcfcb91d77 100644 --- a/packages/@aws-cdk/pipelines/test/blueprint/helpers-internal/pipeline-graph.test.ts +++ b/packages/@aws-cdk/pipelines/test/blueprint/helpers-internal/pipeline-graph.test.ts @@ -288,7 +288,7 @@ describe('options for other engines', () => { // THEN expect(() => new PipelineGraph(blueprint, { prepareStep: false, - })).toThrow('Your pipeline engine does not support changeSet steps'); + })).toThrow(/Cannot use 'changeSet' steps/); }); }); diff --git a/packages/@aws-cdk/pipelines/test/cdk-integ.out.pipeline-with-asset-variables/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.ts b/packages/@aws-cdk/pipelines/test/cdk-integ.out.pipeline-with-asset-variables/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.ts new file mode 100644 index 0000000000000..2459d44ab1d18 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/cdk-integ.out.pipeline-with-asset-variables/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.ts @@ -0,0 +1,82 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { S3 } from 'aws-sdk'; + +const AUTO_DELETE_OBJECTS_TAG = 'aws-cdk:auto-delete-objects'; + +const s3 = new S3(); + +export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent) { + switch (event.RequestType) { + case 'Create': + return; + case 'Update': + return onUpdate(event); + case 'Delete': + return onDelete(event.ResourceProperties?.BucketName); + } +} + +async function onUpdate(event: AWSLambda.CloudFormationCustomResourceEvent) { + const updateEvent = event as AWSLambda.CloudFormationCustomResourceUpdateEvent; + const oldBucketName = updateEvent.OldResourceProperties?.BucketName; + const newBucketName = updateEvent.ResourceProperties?.BucketName; + const bucketNameHasChanged = newBucketName != null && oldBucketName != null && newBucketName !== oldBucketName; + + /* If the name of the bucket has changed, CloudFormation will try to delete the bucket + and create a new one with the new name. So we have to delete the contents of the + bucket so that this operation does not fail. */ + if (bucketNameHasChanged) { + return onDelete(oldBucketName); + } +} + +/** + * Recursively delete all items in the bucket + * + * @param bucketName the bucket name + */ +async function emptyBucket(bucketName: string) { + const listedObjects = await s3.listObjectVersions({ Bucket: bucketName }).promise(); + const contents = [...listedObjects.Versions ?? [], ...listedObjects.DeleteMarkers ?? []]; + if (contents.length === 0) { + return; + } + + const records = contents.map((record: any) => ({ Key: record.Key, VersionId: record.VersionId })); + await s3.deleteObjects({ Bucket: bucketName, Delete: { Objects: records } }).promise(); + + if (listedObjects?.IsTruncated) { + await emptyBucket(bucketName); + } +} + +async function onDelete(bucketName?: string) { + if (!bucketName) { + throw new Error('No BucketName was provided.'); + } + if (!await isBucketTaggedForDeletion(bucketName)) { + process.stdout.write(`Bucket does not have '${AUTO_DELETE_OBJECTS_TAG}' tag, skipping cleaning.\n`); + return; + } + try { + await emptyBucket(bucketName); + } catch (e) { + if (e.code !== 'NoSuchBucket') { + throw e; + } + // Bucket doesn't exist. Ignoring + } +} + +/** + * The bucket will only be tagged for deletion if it's being deleted in the same + * deployment as this Custom Resource. + * + * If the Custom Resource is every deleted before the bucket, it must be because + * `autoDeleteObjects` has been switched to false, in which case the tag would have + * been removed before we get to this Delete event. + */ +async function isBucketTaggedForDeletion(bucketName: string) { + const response = await s3.getBucketTagging({ Bucket: bucketName }).promise(); + return response.TagSet.some(tag => tag.Key === AUTO_DELETE_OBJECTS_TAG && tag.Value === 'true'); +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline.test.ts b/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline.test.ts index 5cc67f6debb7c..b0ecae2286d1a 100644 --- a/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline.test.ts +++ b/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline.test.ts @@ -237,6 +237,67 @@ test('CodePipeline supports use of existing role', () => { }); }); +describe('deployment of stack', () => { + test('is done with Prepare and Deploy step by default', () => { + const pipelineStack = new cdk.Stack(app, 'PipelineStack', { env: PIPELINE_ENV }); + const pipeline = new ModernTestGitHubNpmPipeline(pipelineStack, 'Cdk', { + crossAccountKeys: true, + }); + pipeline.addStage(new FileAssetApp(pipelineStack, 'App', {})); + + // THEN + const template = Template.fromStack(pipelineStack); + + // There should be Prepare step in piepline + template.hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ + Actions: Match.arrayWith([ + Match.objectLike({ + Configuration: Match.objectLike({ + ActionMode: 'CHANGE_SET_REPLACE', + }), + Name: 'Prepare', + }), + Match.objectLike({ + Configuration: Match.objectLike({ + ActionMode: 'CHANGE_SET_EXECUTE', + }), + Name: 'Deploy', + }), + ]), + Name: 'App', + }]), + }); + }); + + test('can be done with single step', () => { + const pipelineStack = new cdk.Stack(app, 'PipelineStack', { env: PIPELINE_ENV }); + const pipeline = new ModernTestGitHubNpmPipeline(pipelineStack, 'Cdk', { + crossAccountKeys: true, + useChangeSets: false, + }); + pipeline.addStage(new FileAssetApp(pipelineStack, 'App', {})); + + // THEN + const template = Template.fromStack(pipelineStack); + + // There should be Prepare step in piepline + template.hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ + Actions: Match.arrayWith([ + Match.objectLike({ + Configuration: Match.objectLike({ + ActionMode: 'CREATE_UPDATE', + }), + Name: 'Deploy', + }), + ]), + Name: 'App', + }]), + }); + }); +}); + interface ReuseCodePipelineStackProps extends cdk.StackProps { reuseCrossRegionSupportStacks?: boolean; } diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline-without-prepare.ts b/packages/@aws-cdk/pipelines/test/integ.pipeline-without-prepare.ts new file mode 100644 index 0000000000000..a48438ad2cbec --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline-without-prepare.ts @@ -0,0 +1,59 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +/// !cdk-integ VarablePipelineStack pragma:set-context:@aws-cdk/core:newStyleStackSynthesis=true +import * as s3 from '@aws-cdk/aws-s3'; +import { App, Stack, StackProps, RemovalPolicy, Stage, StageProps, DefaultStackSynthesizer } from '@aws-cdk/core'; +import * as integ from '@aws-cdk/integ-tests'; +import { Construct } from 'constructs'; +import * as pipelines from '../lib'; +import { PlainStackApp } from './testhelpers'; + +class MyStage extends Stage { + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + + const stack = new Stack(this, 'Stack', { + ...props, + synthesizer: new DefaultStackSynthesizer(), + }); + + new PlainStackApp(stack, 'MyApp'); + } +} + +class PipelineStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const sourceBucket = new s3.Bucket(this, 'SourceBucket', { + removalPolicy: RemovalPolicy.DESTROY, + autoDeleteObjects: true, + }); + const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.s3(sourceBucket, 'key'), + // input: pipelines.CodePipelineSource.gitHub('cdklabs/construct-hub-probe', 'main', { + // trigger: GitHubTrigger.POLL, + // }), + commands: ['mkdir cdk.out', 'touch cdk.out/dummy'], + }), + selfMutation: false, + useChangeSets: false, + }); + + pipeline.addStage(new MyStage(this, 'MyStage', {})); + } +} + +const app = new App({ + context: { + '@aws-cdk/core:newStyleStackSynthesis': '1', + }, +}); + +const stack = new PipelineStack(app, 'PreparelessPipelineStack'); + +new integ.IntegTest(app, 'PreparelessPipelineTest', { + testCases: [stack], +}); + +app.synth(); diff --git a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/PipelineStack.assets.json b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/PipelineStack.assets.json index 0799a1aa58659..ad85c4f0e29b6 100644 --- a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/PipelineStack.assets.json +++ b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/PipelineStack.assets.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { "339869b7efdb6a771fce40473ae99ce3e3174959597875b90007c83a87221f57": { "source": { diff --git a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/assembly-PipelineStack-Beta/PipelineStackBetaStack1E6541489.assets.json b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/assembly-PipelineStack-Beta/PipelineStackBetaStack1E6541489.assets.json index 09d77bb1e7823..4b182342ba90f 100644 --- a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/assembly-PipelineStack-Beta/PipelineStackBetaStack1E6541489.assets.json +++ b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/assembly-PipelineStack-Beta/PipelineStackBetaStack1E6541489.assets.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { "8289faf53c7da377bb2b90615999171adef5e1d8f6b88810e5fef75e6ca09ba5": { "source": { diff --git a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/assembly-PipelineStack-Beta/cdk.out b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/assembly-PipelineStack-Beta/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/assembly-PipelineStack-Beta/cdk.out +++ b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/assembly-PipelineStack-Beta/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/assembly-PipelineStack-Beta/manifest.json b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/assembly-PipelineStack-Beta/manifest.json index 14ab527bbc9bc..6dad8563dba5a 100644 --- a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/assembly-PipelineStack-Beta/manifest.json +++ b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/assembly-PipelineStack-Beta/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "PipelineStackBetaStack1E6541489.assets": { "type": "cdk:asset-manifest", diff --git a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/cdk.out b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/integ.json b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/integ.json index 66f5b01318d4f..00c9e8da3dbec 100644 --- a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/integ.json +++ b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { "integ.newpipeline-with-vpc": { "stacks": [ diff --git a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/manifest.json b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/manifest.json index 81748cc96388c..f265f1c8dae5d 100644 --- a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "assembly-PipelineStack-Beta": { "type": "cdk:cloud-assembly", diff --git a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/tree.json b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/tree.json index 8ce20f7a8d7dc..6683466c9909d 100644 --- a/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/tree.json +++ b/packages/@aws-cdk/pipelines/test/newpipeline-with-vpc.integ.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } }, "PipelineStack": { @@ -1298,13 +1298,13 @@ }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } }, "Build": { @@ -1742,13 +1742,13 @@ }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } }, "UpdatePipeline": { @@ -1760,13 +1760,13 @@ "path": "PipelineStack/Pipeline/Pipeline/UpdatePipeline/SelfMutate", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } }, "Assets": { @@ -1778,7 +1778,7 @@ "path": "PipelineStack/Pipeline/Pipeline/Assets/FileAsset1", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } }, "FileAsset2": { @@ -1786,13 +1786,13 @@ "path": "PipelineStack/Pipeline/Pipeline/Assets/FileAsset2", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } }, "Beta": { @@ -1804,7 +1804,7 @@ "path": "PipelineStack/Pipeline/Pipeline/Beta/Prepare", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } }, "Deploy": { @@ -1812,13 +1812,13 @@ "path": "PipelineStack/Pipeline/Pipeline/Beta/Deploy", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } }, "MutableRolearn:${AWS::Partition}:iam::${AWS::AccountId}:role--cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}": { @@ -1838,7 +1838,7 @@ "path": "PipelineStack/Pipeline/Pipeline/arn:${AWS::Partition}:iam::${AWS::AccountId}:role--cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}/8389e75f-0810-4838-bf64-d6f85a95cf83", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } } }, @@ -2449,7 +2449,7 @@ }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } }, "Assets": { @@ -2978,7 +2978,7 @@ }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.78" + "version": "10.1.92" } } }, diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/PreparelessPipelineStack.assets.json b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/PreparelessPipelineStack.assets.json new file mode 100644 index 0000000000000..0e07fad5fe474 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/PreparelessPipelineStack.assets.json @@ -0,0 +1,32 @@ +{ + "version": "21.0.0", + "files": { + "60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26": { + "source": { + "path": "asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "d48998cffedf7ada707ce9f74130f229eb47e4c549355b1031589d0c4d78d9ad": { + "source": { + "path": "PreparelessPipelineStack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "d48998cffedf7ada707ce9f74130f229eb47e4c549355b1031589d0c4d78d9ad.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/PreparelessPipelineStack.template.json b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/PreparelessPipelineStack.template.json new file mode 100644 index 0000000000000..66c500812ee7a --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/PreparelessPipelineStack.template.json @@ -0,0 +1,950 @@ +{ + "Resources": { + "SourceBucketDDD2130A": { + "Type": "AWS::S3::Bucket", + "Properties": { + "Tags": [ + { + "Key": "aws-cdk:auto-delete-objects", + "Value": "true" + } + ] + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "SourceBucketPolicy703DFBF9": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "SourceBucketDDD2130A" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "SourceBucketDDD2130A", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "SourceBucketDDD2130A", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "SourceBucketAutoDeleteObjectsCustomResourceC68FC040": { + "Type": "Custom::S3AutoDeleteObjects", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", + "Arn" + ] + }, + "BucketName": { + "Ref": "SourceBucketDDD2130A" + } + }, + "DependsOn": [ + "SourceBucketPolicy703DFBF9" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ] + } + }, + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Description": { + "Fn::Join": [ + "", + [ + "Lambda function for auto-deleting objects in ", + { + "Ref": "SourceBucketDDD2130A" + }, + " S3 bucket." + ] + ] + } + }, + "DependsOn": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092" + ] + }, + "PipelineArtifactsBucketAEA9A052": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "aws:kms" + } + } + ] + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "PipelineArtifactsBucketPolicyF53CCC52": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "PipelineArtifactsBucketAEA9A052" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":role/cdk-hnb659fds-deploy-role-", + { + "Ref": "AWS::AccountId" + }, + "-", + { + "Ref": "AWS::Region" + } + ] + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineRoleB27FAA37": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codepipeline.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineRoleDefaultPolicy7BDC1ABB": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineCodeBuildActionRole226DB0CB", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "PipelineSourceS3CodePipelineActionRole83895A58", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":role/cdk-hnb659fds-deploy-role-", + { + "Ref": "AWS::AccountId" + }, + "-", + { + "Ref": "AWS::Region" + } + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineRoleDefaultPolicy7BDC1ABB", + "Roles": [ + { + "Ref": "PipelineRoleB27FAA37" + } + ] + } + }, + "Pipeline9850B417": { + "Type": "AWS::CodePipeline::Pipeline", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "PipelineRoleB27FAA37", + "Arn" + ] + }, + "Stages": [ + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", + "Provider": "S3", + "Version": "1" + }, + "Configuration": { + "S3Bucket": { + "Ref": "SourceBucketDDD2130A" + }, + "S3ObjectKey": "key" + }, + "Name": { + "Ref": "SourceBucketDDD2130A" + }, + "OutputArtifacts": [ + { + "Name": "c8506b445957b8105ede7b68ebe35e9406d642cd0c_Source" + } + ], + "RoleArn": { + "Fn::GetAtt": [ + "PipelineSourceS3CodePipelineActionRole83895A58", + "Arn" + ] + }, + "RunOrder": 1 + } + ], + "Name": "Source" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1" + }, + "Configuration": { + "ProjectName": { + "Ref": "PipelineBuildSynthCdkBuildProject6BEFA8E6" + } + }, + "InputArtifacts": [ + { + "Name": "c8506b445957b8105ede7b68ebe35e9406d642cd0c_Source" + } + ], + "Name": "Synth", + "OutputArtifacts": [ + { + "Name": "Synth_Output" + } + ], + "RoleArn": { + "Fn::GetAtt": [ + "PipelineCodeBuildActionRole226DB0CB", + "Arn" + ] + }, + "RunOrder": 1 + } + ], + "Name": "Build" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "StackName": "MyStage-Stack", + "Capabilities": "CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND", + "RoleArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":role/cdk-hnb659fds-cfn-exec-role-", + { + "Ref": "AWS::AccountId" + }, + "-", + { + "Ref": "AWS::Region" + } + ] + ] + }, + "ActionMode": "CREATE_UPDATE", + "TemplatePath": "Synth_Output::assembly-PreparelessPipelineStack-MyStage/PreparelessPipelineStackMyStageStack3DC192E7.template.json" + }, + "InputArtifacts": [ + { + "Name": "Synth_Output" + } + ], + "Name": "Deploy", + "RoleArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":role/cdk-hnb659fds-deploy-role-", + { + "Ref": "AWS::AccountId" + }, + "-", + { + "Ref": "AWS::Region" + } + ] + ] + }, + "RunOrder": 1 + } + ], + "Name": "MyStage" + } + ], + "ArtifactStore": { + "Location": { + "Ref": "PipelineArtifactsBucketAEA9A052" + }, + "Type": "S3" + }, + "RestartExecutionOnUpdate": true + }, + "DependsOn": [ + "PipelineRoleDefaultPolicy7BDC1ABB", + "PipelineRoleB27FAA37" + ] + }, + "PipelineSourceS3CodePipelineActionRole83895A58": { + "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" + } + } + }, + "PipelineSourceS3CodePipelineActionRoleDefaultPolicyB176A07F": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "SourceBucketDDD2130A", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "SourceBucketDDD2130A", + "Arn" + ] + }, + "/key" + ] + ] + } + ] + }, + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineSourceS3CodePipelineActionRoleDefaultPolicyB176A07F", + "Roles": [ + { + "Ref": "PipelineSourceS3CodePipelineActionRole83895A58" + } + ] + } + }, + "PipelineBuildSynthCdkBuildProjectRole231EEA2A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codebuild.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineBuildSynthCdkBuildProjectRoleDefaultPolicyFB6C941C": { + "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": "PipelineBuildSynthCdkBuildProject6BEFA8E6" + }, + ":*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "PipelineBuildSynthCdkBuildProject6BEFA8E6" + } + ] + ] + } + ] + }, + { + "Action": [ + "codebuild:BatchPutCodeCoverages", + "codebuild:BatchPutTestCases", + "codebuild:CreateReport", + "codebuild:CreateReportGroup", + "codebuild:UpdateReport" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codebuild:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":report-group/", + { + "Ref": "PipelineBuildSynthCdkBuildProject6BEFA8E6" + }, + "-*" + ] + ] + } + }, + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineBuildSynthCdkBuildProjectRoleDefaultPolicyFB6C941C", + "Roles": [ + { + "Ref": "PipelineBuildSynthCdkBuildProjectRole231EEA2A" + } + ] + } + }, + "PipelineBuildSynthCdkBuildProject6BEFA8E6": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "CODEPIPELINE" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/standard:5.0", + "ImagePullCredentialsType": "CODEBUILD", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "PipelineBuildSynthCdkBuildProjectRole231EEA2A", + "Arn" + ] + }, + "Source": { + "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"mkdir cdk.out\",\n \"touch cdk.out/dummy\"\n ]\n }\n },\n \"artifacts\": {\n \"base-directory\": \"cdk.out\",\n \"files\": \"**/*\"\n }\n}", + "Type": "CODEPIPELINE" + }, + "Cache": { + "Type": "NO_CACHE" + }, + "Description": "Pipeline step PreparelessPipelineStack/Pipeline/Build/Synth", + "EncryptionKey": "alias/aws/s3" + } + }, + "PipelineCodeBuildActionRole226DB0CB": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Condition": { + "Bool": { + "aws:ViaAWSService": "codepipeline.amazonaws.com" + } + }, + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineCodeBuildActionRoleDefaultPolicy1D62A6FE": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "codebuild:BatchGetBuilds", + "codebuild:StartBuild", + "codebuild:StopBuild" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineBuildSynthCdkBuildProject6BEFA8E6", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineCodeBuildActionRoleDefaultPolicy1D62A6FE", + "Roles": [ + { + "Ref": "PipelineCodeBuildActionRole226DB0CB" + } + ] + } + } + }, + "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. [cdk:skip]" + } + }, + "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/pipeline-without-prepare.integ.snapshot/PreparelessPipelineTestDefaultTestDeployAssert7B7DD2C6.assets.json b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/PreparelessPipelineTestDefaultTestDeployAssert7B7DD2C6.assets.json new file mode 100644 index 0000000000000..770de88c374a0 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/PreparelessPipelineTestDefaultTestDeployAssert7B7DD2C6.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "PreparelessPipelineTestDefaultTestDeployAssert7B7DD2C6.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/PreparelessPipelineTestDefaultTestDeployAssert7B7DD2C6.template.json b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/PreparelessPipelineTestDefaultTestDeployAssert7B7DD2C6.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/PreparelessPipelineTestDefaultTestDeployAssert7B7DD2C6.template.json @@ -0,0 +1,36 @@ +{ + "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. [cdk:skip]" + } + }, + "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/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/PreparelessPipelineStackMyStageStack3DC192E7.assets.json b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/PreparelessPipelineStackMyStageStack3DC192E7.assets.json new file mode 100644 index 0000000000000..639913e637b14 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/PreparelessPipelineStackMyStageStack3DC192E7.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "PreparelessPipelineStackMyStageStack3DC192E7.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/PreparelessPipelineStackMyStageStack3DC192E7.template.json b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/PreparelessPipelineStackMyStageStack3DC192E7.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/PreparelessPipelineStackMyStageStack3DC192E7.template.json @@ -0,0 +1,36 @@ +{ + "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. [cdk:skip]" + } + }, + "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/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/assembly-PreparelessPipelineStack-MyStage-Stack-MyApp/PreparelessPipelineStackMyStageStackMyAppStack51FBCD39.assets.json b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/assembly-PreparelessPipelineStack-MyStage-Stack-MyApp/PreparelessPipelineStackMyStageStackMyAppStack51FBCD39.assets.json new file mode 100644 index 0000000000000..2be2327136882 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/assembly-PreparelessPipelineStack-MyStage-Stack-MyApp/PreparelessPipelineStackMyStageStackMyAppStack51FBCD39.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "1fd92c49dfa8050744f5e5169a7c3739923777fad154ba6b7275118191534ffc": { + "source": { + "path": "PreparelessPipelineStackMyStageStackMyAppStack51FBCD39.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "1fd92c49dfa8050744f5e5169a7c3739923777fad154ba6b7275118191534ffc.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/assembly-PreparelessPipelineStack-MyStage-Stack-MyApp/PreparelessPipelineStackMyStageStackMyAppStack51FBCD39.template.json b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/assembly-PreparelessPipelineStack-MyStage-Stack-MyApp/PreparelessPipelineStackMyStageStackMyAppStack51FBCD39.template.json new file mode 100644 index 0000000000000..fa27c2ed77a28 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/assembly-PreparelessPipelineStack-MyStage-Stack-MyApp/PreparelessPipelineStackMyStageStackMyAppStack51FBCD39.template.json @@ -0,0 +1,43 @@ +{ + "Resources": { + "Bucket83908E77": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + }, + "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. [cdk:skip]" + } + }, + "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/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/assembly-PreparelessPipelineStack-MyStage-Stack-MyApp/cdk.out b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/assembly-PreparelessPipelineStack-MyStage-Stack-MyApp/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/assembly-PreparelessPipelineStack-MyStage-Stack-MyApp/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/assembly-PreparelessPipelineStack-MyStage-Stack-MyApp/manifest.json b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/assembly-PreparelessPipelineStack-MyStage-Stack-MyApp/manifest.json new file mode 100644 index 0000000000000..f7bf8d40a960f --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/assembly-PreparelessPipelineStack-MyStage-Stack-MyApp/manifest.json @@ -0,0 +1,59 @@ +{ + "version": "21.0.0", + "artifacts": { + "PreparelessPipelineStackMyStageStackMyAppStack51FBCD39.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "PreparelessPipelineStackMyStageStackMyAppStack51FBCD39.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "PreparelessPipelineStackMyStageStackMyAppStack51FBCD39": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "PreparelessPipelineStackMyStageStackMyAppStack51FBCD39.template.json", + "validateOnSynth": true, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/1fd92c49dfa8050744f5e5169a7c3739923777fad154ba6b7275118191534ffc.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "PreparelessPipelineStackMyStageStackMyAppStack51FBCD39.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + }, + "stackName": "MyStage-MyApp-Stack" + }, + "dependencies": [ + "PreparelessPipelineStackMyStageStackMyAppStack51FBCD39.assets" + ], + "metadata": { + "/PreparelessPipelineStack/MyStage/Stack/MyApp/Stack/Bucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Bucket83908E77" + } + ], + "/PreparelessPipelineStack/MyStage/Stack/MyApp/Stack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/PreparelessPipelineStack/MyStage/Stack/MyApp/Stack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "PreparelessPipelineStack/MyStage/Stack/MyApp/Stack" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/cdk.out b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/manifest.json b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/manifest.json new file mode 100644 index 0000000000000..6887ae7fd2585 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/assembly-PreparelessPipelineStack-MyStage/manifest.json @@ -0,0 +1,60 @@ +{ + "version": "21.0.0", + "artifacts": { + "assembly-PreparelessPipelineStack-MyStage-Stack-MyApp": { + "type": "cdk:cloud-assembly", + "properties": { + "directoryName": "assembly-PreparelessPipelineStack-MyStage-Stack-MyApp", + "displayName": "PreparelessPipelineStack/MyStage/Stack/MyApp" + } + }, + "PreparelessPipelineStackMyStageStack3DC192E7.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "PreparelessPipelineStackMyStageStack3DC192E7.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "PreparelessPipelineStackMyStageStack3DC192E7": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "PreparelessPipelineStackMyStageStack3DC192E7.template.json", + "validateOnSynth": true, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "PreparelessPipelineStackMyStageStack3DC192E7.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + }, + "stackName": "MyStage-Stack" + }, + "dependencies": [ + "PreparelessPipelineStackMyStageStack3DC192E7.assets" + ], + "metadata": { + "/PreparelessPipelineStack/MyStage/Stack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/PreparelessPipelineStack/MyStage/Stack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "PreparelessPipelineStack/MyStage/Stack" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/__entrypoint__.js b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/__entrypoint__.js new file mode 100644 index 0000000000000..9df94382cc74e --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/__entrypoint__.js @@ -0,0 +1,118 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handler = exports.external = void 0; +const https = require("https"); +const url = require("url"); +// for unit tests +exports.external = { + sendHttpRequest: defaultSendHttpRequest, + log: defaultLog, + includeStackTraces: true, + userHandlerIndex: './index', +}; +const CREATE_FAILED_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::CREATE_FAILED'; +const MISSING_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID'; +async function handler(event, context) { + const sanitizedEvent = { ...event, ResponseURL: '...' }; + exports.external.log(JSON.stringify(sanitizedEvent, undefined, 2)); + // ignore DELETE event when the physical resource ID is the marker that + // indicates that this DELETE is a subsequent DELETE to a failed CREATE + // operation. + if (event.RequestType === 'Delete' && event.PhysicalResourceId === CREATE_FAILED_PHYSICAL_ID_MARKER) { + exports.external.log('ignoring DELETE event caused by a failed CREATE event'); + await submitResponse('SUCCESS', event); + return; + } + try { + // invoke the user handler. this is intentionally inside the try-catch to + // ensure that if there is an error it's reported as a failure to + // cloudformation (otherwise cfn waits). + // eslint-disable-next-line @typescript-eslint/no-require-imports + const userHandler = require(exports.external.userHandlerIndex).handler; + const result = await userHandler(sanitizedEvent, context); + // validate user response and create the combined event + const responseEvent = renderResponse(event, result); + // submit to cfn as success + await submitResponse('SUCCESS', responseEvent); + } + catch (e) { + const resp = { + ...event, + Reason: exports.external.includeStackTraces ? e.stack : e.message, + }; + if (!resp.PhysicalResourceId) { + // special case: if CREATE fails, which usually implies, we usually don't + // have a physical resource id. in this case, the subsequent DELETE + // operation does not have any meaning, and will likely fail as well. to + // address this, we use a marker so the provider framework can simply + // ignore the subsequent DELETE. + if (event.RequestType === 'Create') { + exports.external.log('CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored'); + resp.PhysicalResourceId = CREATE_FAILED_PHYSICAL_ID_MARKER; + } + else { + // otherwise, if PhysicalResourceId is not specified, something is + // terribly wrong because all other events should have an ID. + exports.external.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(event)}`); + } + } + // this is an actual error, fail the activity altogether and exist. + await submitResponse('FAILED', resp); + } +} +exports.handler = handler; +function renderResponse(cfnRequest, handlerResponse = {}) { + // if physical ID is not returned, we have some defaults for you based + // on the request type. + const physicalResourceId = handlerResponse.PhysicalResourceId ?? cfnRequest.PhysicalResourceId ?? cfnRequest.RequestId; + // if we are in DELETE and physical ID was changed, it's an error. + if (cfnRequest.RequestType === 'Delete' && physicalResourceId !== cfnRequest.PhysicalResourceId) { + throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${handlerResponse.PhysicalResourceId}" during deletion`); + } + // merge request event and result event (result prevails). + return { + ...cfnRequest, + ...handlerResponse, + PhysicalResourceId: physicalResourceId, + }; +} +async function submitResponse(status, event) { + const json = { + Status: status, + Reason: event.Reason ?? status, + StackId: event.StackId, + RequestId: event.RequestId, + PhysicalResourceId: event.PhysicalResourceId || MISSING_PHYSICAL_ID_MARKER, + LogicalResourceId: event.LogicalResourceId, + NoEcho: event.NoEcho, + Data: event.Data, + }; + exports.external.log('submit response to cloudformation', json); + const responseBody = JSON.stringify(json); + const parsedUrl = url.parse(event.ResponseURL); + const req = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { 'content-type': '', 'content-length': responseBody.length }, + }; + await exports.external.sendHttpRequest(req, responseBody); +} +async function defaultSendHttpRequest(options, responseBody) { + return new Promise((resolve, reject) => { + try { + const request = https.request(options, _ => resolve()); + request.on('error', reject); + request.write(responseBody); + request.end(); + } + catch (e) { + reject(e); + } + }); +} +function defaultLog(fmt, ...params) { + // eslint-disable-next-line no-console + console.log(fmt, ...params); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZWpzLWVudHJ5cG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJub2RlanMtZW50cnlwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwrQkFBK0I7QUFDL0IsMkJBQTJCO0FBRTNCLGlCQUFpQjtBQUNKLFFBQUEsUUFBUSxHQUFHO0lBQ3RCLGVBQWUsRUFBRSxzQkFBc0I7SUFDdkMsR0FBRyxFQUFFLFVBQVU7SUFDZixrQkFBa0IsRUFBRSxJQUFJO0lBQ3hCLGdCQUFnQixFQUFFLFNBQVM7Q0FDNUIsQ0FBQztBQUVGLE1BQU0sZ0NBQWdDLEdBQUcsd0RBQXdELENBQUM7QUFDbEcsTUFBTSwwQkFBMEIsR0FBRyw4REFBOEQsQ0FBQztBQVczRixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtELEVBQUUsT0FBMEI7SUFDMUcsTUFBTSxjQUFjLEdBQUcsRUFBRSxHQUFHLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDeEQsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFM0QsdUVBQXVFO0lBQ3ZFLHVFQUF1RTtJQUN2RSxhQUFhO0lBQ2IsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsa0JBQWtCLEtBQUssZ0NBQWdDLEVBQUU7UUFDbkcsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsdURBQXVELENBQUMsQ0FBQztRQUN0RSxNQUFNLGNBQWMsQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDdkMsT0FBTztLQUNSO0lBRUQsSUFBSTtRQUNGLHlFQUF5RTtRQUN6RSxpRUFBaUU7UUFDakUsd0NBQXdDO1FBQ3hDLGlFQUFpRTtRQUNqRSxNQUFNLFdBQVcsR0FBWSxPQUFPLENBQUMsZ0JBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUN4RSxNQUFNLE1BQU0sR0FBRyxNQUFNLFdBQVcsQ0FBQyxjQUFjLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFFMUQsdURBQXVEO1FBQ3ZELE1BQU0sYUFBYSxHQUFHLGNBQWMsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFcEQsMkJBQTJCO1FBQzNCLE1BQU0sY0FBYyxDQUFDLFNBQVMsRUFBRSxhQUFhLENBQUMsQ0FBQztLQUNoRDtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsTUFBTSxJQUFJLEdBQWE7WUFDckIsR0FBRyxLQUFLO1lBQ1IsTUFBTSxFQUFFLGdCQUFRLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPO1NBQzFELENBQUM7UUFFRixJQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFrQixFQUFFO1lBQzVCLHlFQUF5RTtZQUN6RSxtRUFBbUU7WUFDbkUsd0VBQXdFO1lBQ3hFLHFFQUFxRTtZQUNyRSxnQ0FBZ0M7WUFDaEMsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsRUFBRTtnQkFDbEMsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsNEdBQTRHLENBQUMsQ0FBQztnQkFDM0gsSUFBSSxDQUFDLGtCQUFrQixHQUFHLGdDQUFnQyxDQUFDO2FBQzVEO2lCQUFNO2dCQUNMLGtFQUFrRTtnQkFDbEUsNkRBQTZEO2dCQUM3RCxnQkFBUSxDQUFDLEdBQUcsQ0FBQyw2REFBNkQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7YUFDcEc7U0FDRjtRQUVELG1FQUFtRTtRQUNuRSxNQUFNLGNBQWMsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLENBQUM7S0FDdEM7QUFDSCxDQUFDO0FBbkRELDBCQW1EQztBQUVELFNBQVMsY0FBYyxDQUNyQixVQUF5RixFQUN6RixrQkFBMEMsRUFBRztJQUU3QyxzRUFBc0U7SUFDdEUsdUJBQXVCO0lBQ3ZCLE1BQU0sa0JBQWtCLEdBQUcsZUFBZSxDQUFDLGtCQUFrQixJQUFJLFVBQVUsQ0FBQyxrQkFBa0IsSUFBSSxVQUFVLENBQUMsU0FBUyxDQUFDO0lBRXZILGtFQUFrRTtJQUNsRSxJQUFJLFVBQVUsQ0FBQyxXQUFXLEtBQUssUUFBUSxJQUFJLGtCQUFrQixLQUFLLFVBQVUsQ0FBQyxrQkFBa0IsRUFBRTtRQUMvRixNQUFNLElBQUksS0FBSyxDQUFDLHdEQUF3RCxVQUFVLENBQUMsa0JBQWtCLFNBQVMsZUFBZSxDQUFDLGtCQUFrQixtQkFBbUIsQ0FBQyxDQUFDO0tBQ3RLO0lBRUQsMERBQTBEO0lBQzFELE9BQU87UUFDTCxHQUFHLFVBQVU7UUFDYixHQUFHLGVBQWU7UUFDbEIsa0JBQWtCLEVBQUUsa0JBQWtCO0tBQ3ZDLENBQUM7QUFDSixDQUFDO0FBRUQsS0FBSyxVQUFVLGNBQWMsQ0FBQyxNQUE0QixFQUFFLEtBQWU7SUFDekUsTUFBTSxJQUFJLEdBQW1EO1FBQzNELE1BQU0sRUFBRSxNQUFNO1FBQ2QsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLElBQUksTUFBTTtRQUM5QixPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU87UUFDdEIsU0FBUyxFQUFFLEtBQUssQ0FBQyxTQUFTO1FBQzFCLGtCQUFrQixFQUFFLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSwwQkFBMEI7UUFDMUUsaUJBQWlCLEVBQUUsS0FBSyxDQUFDLGlCQUFpQjtRQUMxQyxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU07UUFDcEIsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO0tBQ2pCLENBQUM7SUFFRixnQkFBUSxDQUFDLEdBQUcsQ0FBQyxtQ0FBbUMsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUV4RCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzFDLE1BQU0sU0FBUyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQy9DLE1BQU0sR0FBRyxHQUFHO1FBQ1YsUUFBUSxFQUFFLFNBQVMsQ0FBQyxRQUFRO1FBQzVCLElBQUksRUFBRSxTQUFTLENBQUMsSUFBSTtRQUNwQixNQUFNLEVBQUUsS0FBSztRQUNiLE9BQU8sRUFBRSxFQUFFLGNBQWMsRUFBRSxFQUFFLEVBQUUsZ0JBQWdCLEVBQUUsWUFBWSxDQUFDLE1BQU0sRUFBRTtLQUN2RSxDQUFDO0lBRUYsTUFBTSxnQkFBUSxDQUFDLGVBQWUsQ0FBQyxHQUFHLEVBQUUsWUFBWSxDQUFDLENBQUM7QUFDcEQsQ0FBQztBQUVELEtBQUssVUFBVSxzQkFBc0IsQ0FBQyxPQUE2QixFQUFFLFlBQW9CO0lBQ3ZGLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDckMsSUFBSTtZQUNGLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztZQUN2RCxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztZQUM1QixPQUFPLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxDQUFDO1lBQzVCLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQztTQUNmO1FBQUMsT0FBTyxDQUFDLEVBQUU7WUFDVixNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDWDtJQUNILENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVELFNBQVMsVUFBVSxDQUFDLEdBQVcsRUFBRSxHQUFHLE1BQWE7SUFDL0Msc0NBQXNDO0lBQ3RDLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLEdBQUcsTUFBTSxDQUFDLENBQUM7QUFDOUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIGh0dHBzIGZyb20gJ2h0dHBzJztcbmltcG9ydCAqIGFzIHVybCBmcm9tICd1cmwnO1xuXG4vLyBmb3IgdW5pdCB0ZXN0c1xuZXhwb3J0IGNvbnN0IGV4dGVybmFsID0ge1xuICBzZW5kSHR0cFJlcXVlc3Q6IGRlZmF1bHRTZW5kSHR0cFJlcXVlc3QsXG4gIGxvZzogZGVmYXVsdExvZyxcbiAgaW5jbHVkZVN0YWNrVHJhY2VzOiB0cnVlLFxuICB1c2VySGFuZGxlckluZGV4OiAnLi9pbmRleCcsXG59O1xuXG5jb25zdCBDUkVBVEVfRkFJTEVEX1BIWVNJQ0FMX0lEX01BUktFUiA9ICdBV1NDREs6OkN1c3RvbVJlc291cmNlUHJvdmlkZXJGcmFtZXdvcms6OkNSRUFURV9GQUlMRUQnO1xuY29uc3QgTUlTU0lOR19QSFlTSUNBTF9JRF9NQVJLRVIgPSAnQVdTQ0RLOjpDdXN0b21SZXNvdXJjZVByb3ZpZGVyRnJhbWV3b3JrOjpNSVNTSU5HX1BIWVNJQ0FMX0lEJztcblxuZXhwb3J0IHR5cGUgUmVzcG9uc2UgPSBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50ICYgSGFuZGxlclJlc3BvbnNlO1xuZXhwb3J0IHR5cGUgSGFuZGxlciA9IChldmVudDogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCwgY29udGV4dDogQVdTTGFtYmRhLkNvbnRleHQpID0+IFByb21pc2U8SGFuZGxlclJlc3BvbnNlIHwgdm9pZD47XG5leHBvcnQgdHlwZSBIYW5kbGVyUmVzcG9uc2UgPSB1bmRlZmluZWQgfCB7XG4gIERhdGE/OiBhbnk7XG4gIFBoeXNpY2FsUmVzb3VyY2VJZD86IHN0cmluZztcbiAgUmVhc29uPzogc3RyaW5nO1xuICBOb0VjaG8/OiBib29sZWFuO1xufTtcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGhhbmRsZXIoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQsIGNvbnRleHQ6IEFXU0xhbWJkYS5Db250ZXh0KSB7XG4gIGNvbnN0IHNhbml0aXplZEV2ZW50ID0geyAuLi5ldmVudCwgUmVzcG9uc2VVUkw6ICcuLi4nIH07XG4gIGV4dGVybmFsLmxvZyhKU09OLnN0cmluZ2lmeShzYW5pdGl6ZWRFdmVudCwgdW5kZWZpbmVkLCAyKSk7XG5cbiAgLy8gaWdub3JlIERFTEVURSBldmVudCB3aGVuIHRoZSBwaHlzaWNhbCByZXNvdXJjZSBJRCBpcyB0aGUgbWFya2VyIHRoYXRcbiAgLy8gaW5kaWNhdGVzIHRoYXQgdGhpcyBERUxFVEUgaXMgYSBzdWJzZXF1ZW50IERFTEVURSB0byBhIGZhaWxlZCBDUkVBVEVcbiAgLy8gb3BlcmF0aW9uLlxuICBpZiAoZXZlbnQuUmVxdWVzdFR5cGUgPT09ICdEZWxldGUnICYmIGV2ZW50LlBoeXNpY2FsUmVzb3VyY2VJZCA9PT0gQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVIpIHtcbiAgICBleHRlcm5hbC5sb2coJ2lnbm9yaW5nIERFTEVURSBldmVudCBjYXVzZWQgYnkgYSBmYWlsZWQgQ1JFQVRFIGV2ZW50Jyk7XG4gICAgYXdhaXQgc3VibWl0UmVzcG9uc2UoJ1NVQ0NFU1MnLCBldmVudCk7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgdHJ5IHtcbiAgICAvLyBpbnZva2UgdGhlIHVzZXIgaGFuZGxlci4gdGhpcyBpcyBpbnRlbnRpb25hbGx5IGluc2lkZSB0aGUgdHJ5LWNhdGNoIHRvXG4gICAgLy8gZW5zdXJlIHRoYXQgaWYgdGhlcmUgaXMgYW4gZXJyb3IgaXQncyByZXBvcnRlZCBhcyBhIGZhaWx1cmUgdG9cbiAgICAvLyBjbG91ZGZvcm1hdGlvbiAob3RoZXJ3aXNlIGNmbiB3YWl0cykuXG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1yZXF1aXJlLWltcG9ydHNcbiAgICBjb25zdCB1c2VySGFuZGxlcjogSGFuZGxlciA9IHJlcXVpcmUoZXh0ZXJuYWwudXNlckhhbmRsZXJJbmRleCkuaGFuZGxlcjtcbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB1c2VySGFuZGxlcihzYW5pdGl6ZWRFdmVudCwgY29udGV4dCk7XG5cbiAgICAvLyB2YWxpZGF0ZSB1c2VyIHJlc3BvbnNlIGFuZCBjcmVhdGUgdGhlIGNvbWJpbmVkIGV2ZW50XG4gICAgY29uc3QgcmVzcG9uc2VFdmVudCA9IHJlbmRlclJlc3BvbnNlKGV2ZW50LCByZXN1bHQpO1xuXG4gICAgLy8gc3VibWl0IHRvIGNmbiBhcyBzdWNjZXNzXG4gICAgYXdhaXQgc3VibWl0UmVzcG9uc2UoJ1NVQ0NFU1MnLCByZXNwb25zZUV2ZW50KTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGNvbnN0IHJlc3A6IFJlc3BvbnNlID0ge1xuICAgICAgLi4uZXZlbnQsXG4gICAgICBSZWFzb246IGV4dGVybmFsLmluY2x1ZGVTdGFja1RyYWNlcyA/IGUuc3RhY2sgOiBlLm1lc3NhZ2UsXG4gICAgfTtcblxuICAgIGlmICghcmVzcC5QaHlzaWNhbFJlc291cmNlSWQpIHtcbiAgICAgIC8vIHNwZWNpYWwgY2FzZTogaWYgQ1JFQVRFIGZhaWxzLCB3aGljaCB1c3VhbGx5IGltcGxpZXMsIHdlIHVzdWFsbHkgZG9uJ3RcbiAgICAgIC8vIGhhdmUgYSBwaHlzaWNhbCByZXNvdXJjZSBpZC4gaW4gdGhpcyBjYXNlLCB0aGUgc3Vic2VxdWVudCBERUxFVEVcbiAgICAgIC8vIG9wZXJhdGlvbiBkb2VzIG5vdCBoYXZlIGFueSBtZWFuaW5nLCBhbmQgd2lsbCBsaWtlbHkgZmFpbCBhcyB3ZWxsLiB0b1xuICAgICAgLy8gYWRkcmVzcyB0aGlzLCB3ZSB1c2UgYSBtYXJrZXIgc28gdGhlIHByb3ZpZGVyIGZyYW1ld29yayBjYW4gc2ltcGx5XG4gICAgICAvLyBpZ25vcmUgdGhlIHN1YnNlcXVlbnQgREVMRVRFLlxuICAgICAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnQ3JlYXRlJykge1xuICAgICAgICBleHRlcm5hbC5sb2coJ0NSRUFURSBmYWlsZWQsIHJlc3BvbmRpbmcgd2l0aCBhIG1hcmtlciBwaHlzaWNhbCByZXNvdXJjZSBpZCBzbyB0aGF0IHRoZSBzdWJzZXF1ZW50IERFTEVURSB3aWxsIGJlIGlnbm9yZWQnKTtcbiAgICAgICAgcmVzcC5QaHlzaWNhbFJlc291cmNlSWQgPSBDUkVBVEVfRkFJTEVEX1BIWVNJQ0FMX0lEX01BUktFUjtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIG90aGVyd2lzZSwgaWYgUGh5c2ljYWxSZXNvdXJjZUlkIGlzIG5vdCBzcGVjaWZpZWQsIHNvbWV0aGluZyBpc1xuICAgICAgICAvLyB0ZXJyaWJseSB3cm9uZyBiZWNhdXNlIGFsbCBvdGhlciBldmVudHMgc2hvdWxkIGhhdmUgYW4gSUQuXG4gICAgICAgIGV4dGVybmFsLmxvZyhgRVJST1I6IE1hbGZvcm1lZCBldmVudC4gXCJQaHlzaWNhbFJlc291cmNlSWRcIiBpcyByZXF1aXJlZDogJHtKU09OLnN0cmluZ2lmeShldmVudCl9YCk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gdGhpcyBpcyBhbiBhY3R1YWwgZXJyb3IsIGZhaWwgdGhlIGFjdGl2aXR5IGFsdG9nZXRoZXIgYW5kIGV4aXN0LlxuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdGQUlMRUQnLCByZXNwKTtcbiAgfVxufVxuXG5mdW5jdGlvbiByZW5kZXJSZXNwb25zZShcbiAgY2ZuUmVxdWVzdDogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCAmIHsgUGh5c2ljYWxSZXNvdXJjZUlkPzogc3RyaW5nIH0sXG4gIGhhbmRsZXJSZXNwb25zZTogdm9pZCB8IEhhbmRsZXJSZXNwb25zZSA9IHsgfSk6IFJlc3BvbnNlIHtcblxuICAvLyBpZiBwaHlzaWNhbCBJRCBpcyBub3QgcmV0dXJuZWQsIHdlIGhhdmUgc29tZSBkZWZhdWx0cyBmb3IgeW91IGJhc2VkXG4gIC8vIG9uIHRoZSByZXF1ZXN0IHR5cGUuXG4gIGNvbnN0IHBoeXNpY2FsUmVzb3VyY2VJZCA9IGhhbmRsZXJSZXNwb25zZS5QaHlzaWNhbFJlc291cmNlSWQgPz8gY2ZuUmVxdWVzdC5QaHlzaWNhbFJlc291cmNlSWQgPz8gY2ZuUmVxdWVzdC5SZXF1ZXN0SWQ7XG5cbiAgLy8gaWYgd2UgYXJlIGluIERFTEVURSBhbmQgcGh5c2ljYWwgSUQgd2FzIGNoYW5nZWQsIGl0J3MgYW4gZXJyb3IuXG4gIGlmIChjZm5SZXF1ZXN0LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJyAmJiBwaHlzaWNhbFJlc291cmNlSWQgIT09IGNmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBERUxFVEU6IGNhbm5vdCBjaGFuZ2UgdGhlIHBoeXNpY2FsIHJlc291cmNlIElEIGZyb20gXCIke2NmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkfVwiIHRvIFwiJHtoYW5kbGVyUmVzcG9uc2UuUGh5c2ljYWxSZXNvdXJjZUlkfVwiIGR1cmluZyBkZWxldGlvbmApO1xuICB9XG5cbiAgLy8gbWVyZ2UgcmVxdWVzdCBldmVudCBhbmQgcmVzdWx0IGV2ZW50IChyZXN1bHQgcHJldmFpbHMpLlxuICByZXR1cm4ge1xuICAgIC4uLmNmblJlcXVlc3QsXG4gICAgLi4uaGFuZGxlclJlc3BvbnNlLFxuICAgIFBoeXNpY2FsUmVzb3VyY2VJZDogcGh5c2ljYWxSZXNvdXJjZUlkLFxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzdWJtaXRSZXNwb25zZShzdGF0dXM6ICdTVUNDRVNTJyB8ICdGQUlMRUQnLCBldmVudDogUmVzcG9uc2UpIHtcbiAgY29uc3QganNvbjogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VSZXNwb25zZSA9IHtcbiAgICBTdGF0dXM6IHN0YXR1cyxcbiAgICBSZWFzb246IGV2ZW50LlJlYXNvbiA/PyBzdGF0dXMsXG4gICAgU3RhY2tJZDogZXZlbnQuU3RhY2tJZCxcbiAgICBSZXF1ZXN0SWQ6IGV2ZW50LlJlcXVlc3RJZCxcbiAgICBQaHlzaWNhbFJlc291cmNlSWQ6IGV2ZW50LlBoeXNpY2FsUmVzb3VyY2VJZCB8fCBNSVNTSU5HX1BIWVNJQ0FMX0lEX01BUktFUixcbiAgICBMb2dpY2FsUmVzb3VyY2VJZDogZXZlbnQuTG9naWNhbFJlc291cmNlSWQsXG4gICAgTm9FY2hvOiBldmVudC5Ob0VjaG8sXG4gICAgRGF0YTogZXZlbnQuRGF0YSxcbiAgfTtcblxuICBleHRlcm5hbC5sb2coJ3N1Ym1pdCByZXNwb25zZSB0byBjbG91ZGZvcm1hdGlvbicsIGpzb24pO1xuXG4gIGNvbnN0IHJlc3BvbnNlQm9keSA9IEpTT04uc3RyaW5naWZ5KGpzb24pO1xuICBjb25zdCBwYXJzZWRVcmwgPSB1cmwucGFyc2UoZXZlbnQuUmVzcG9uc2VVUkwpO1xuICBjb25zdCByZXEgPSB7XG4gICAgaG9zdG5hbWU6IHBhcnNlZFVybC5ob3N0bmFtZSxcbiAgICBwYXRoOiBwYXJzZWRVcmwucGF0aCxcbiAgICBtZXRob2Q6ICdQVVQnLFxuICAgIGhlYWRlcnM6IHsgJ2NvbnRlbnQtdHlwZSc6ICcnLCAnY29udGVudC1sZW5ndGgnOiByZXNwb25zZUJvZHkubGVuZ3RoIH0sXG4gIH07XG5cbiAgYXdhaXQgZXh0ZXJuYWwuc2VuZEh0dHBSZXF1ZXN0KHJlcSwgcmVzcG9uc2VCb2R5KTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZGVmYXVsdFNlbmRIdHRwUmVxdWVzdChvcHRpb25zOiBodHRwcy5SZXF1ZXN0T3B0aW9ucywgcmVzcG9uc2VCb2R5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVxdWVzdCA9IGh0dHBzLnJlcXVlc3Qob3B0aW9ucywgXyA9PiByZXNvbHZlKCkpO1xuICAgICAgcmVxdWVzdC5vbignZXJyb3InLCByZWplY3QpO1xuICAgICAgcmVxdWVzdC53cml0ZShyZXNwb25zZUJvZHkpO1xuICAgICAgcmVxdWVzdC5lbmQoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICByZWplY3QoZSk7XG4gICAgfVxuICB9KTtcbn1cblxuZnVuY3Rpb24gZGVmYXVsdExvZyhmbXQ6IHN0cmluZywgLi4ucGFyYW1zOiBhbnlbXSkge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICBjb25zb2xlLmxvZyhmbXQsIC4uLnBhcmFtcyk7XG59XG4iXX0= \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.d.ts b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.d.ts new file mode 100644 index 0000000000000..3554dc94d4617 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.d.ts @@ -0,0 +1 @@ +export declare function handler(event: AWSLambda.CloudFormationCustomResourceEvent): Promise; diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.js b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.js new file mode 100644 index 0000000000000..7ce4156d4ba41 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.js @@ -0,0 +1,78 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handler = void 0; +// eslint-disable-next-line import/no-extraneous-dependencies +const aws_sdk_1 = require("aws-sdk"); +const AUTO_DELETE_OBJECTS_TAG = 'aws-cdk:auto-delete-objects'; +const s3 = new aws_sdk_1.S3(); +async function handler(event) { + switch (event.RequestType) { + case 'Create': + return; + case 'Update': + return onUpdate(event); + case 'Delete': + return onDelete(event.ResourceProperties?.BucketName); + } +} +exports.handler = handler; +async function onUpdate(event) { + const updateEvent = event; + const oldBucketName = updateEvent.OldResourceProperties?.BucketName; + const newBucketName = updateEvent.ResourceProperties?.BucketName; + const bucketNameHasChanged = newBucketName != null && oldBucketName != null && newBucketName !== oldBucketName; + /* If the name of the bucket has changed, CloudFormation will try to delete the bucket + and create a new one with the new name. So we have to delete the contents of the + bucket so that this operation does not fail. */ + if (bucketNameHasChanged) { + return onDelete(oldBucketName); + } +} +/** + * Recursively delete all items in the bucket + * + * @param bucketName the bucket name + */ +async function emptyBucket(bucketName) { + const listedObjects = await s3.listObjectVersions({ Bucket: bucketName }).promise(); + const contents = [...listedObjects.Versions ?? [], ...listedObjects.DeleteMarkers ?? []]; + if (contents.length === 0) { + return; + } + const records = contents.map((record) => ({ Key: record.Key, VersionId: record.VersionId })); + await s3.deleteObjects({ Bucket: bucketName, Delete: { Objects: records } }).promise(); + if (listedObjects?.IsTruncated) { + await emptyBucket(bucketName); + } +} +async function onDelete(bucketName) { + if (!bucketName) { + throw new Error('No BucketName was provided.'); + } + if (!await isBucketTaggedForDeletion(bucketName)) { + process.stdout.write(`Bucket does not have '${AUTO_DELETE_OBJECTS_TAG}' tag, skipping cleaning.\n`); + return; + } + try { + await emptyBucket(bucketName); + } + catch (e) { + if (e.code !== 'NoSuchBucket') { + throw e; + } + // Bucket doesn't exist. Ignoring + } +} +/** + * The bucket will only be tagged for deletion if it's being deleted in the same + * deployment as this Custom Resource. + * + * If the Custom Resource is every deleted before the bucket, it must be because + * `autoDeleteObjects` has been switched to false, in which case the tag would have + * been removed before we get to this Delete event. + */ +async function isBucketTaggedForDeletion(bucketName) { + const response = await s3.getBucketTagging({ Bucket: bucketName }).promise(); + return response.TagSet.some(tag => tag.Key === AUTO_DELETE_OBJECTS_TAG && tag.Value === 'true'); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2REFBNkQ7QUFDN0QscUNBQTZCO0FBRTdCLE1BQU0sdUJBQXVCLEdBQUcsNkJBQTZCLENBQUM7QUFFOUQsTUFBTSxFQUFFLEdBQUcsSUFBSSxZQUFFLEVBQUUsQ0FBQztBQUViLEtBQUssVUFBVSxPQUFPLENBQUMsS0FBa0Q7SUFDOUUsUUFBUSxLQUFLLENBQUMsV0FBVyxFQUFFO1FBQ3pCLEtBQUssUUFBUTtZQUNYLE9BQU87UUFDVCxLQUFLLFFBQVE7WUFDWCxPQUFPLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN6QixLQUFLLFFBQVE7WUFDWCxPQUFPLFFBQVEsQ0FBQyxLQUFLLENBQUMsa0JBQWtCLEVBQUUsVUFBVSxDQUFDLENBQUM7S0FDekQ7QUFDSCxDQUFDO0FBVEQsMEJBU0M7QUFFRCxLQUFLLFVBQVUsUUFBUSxDQUFDLEtBQWtEO0lBQ3hFLE1BQU0sV0FBVyxHQUFHLEtBQTBELENBQUM7SUFDL0UsTUFBTSxhQUFhLEdBQUcsV0FBVyxDQUFDLHFCQUFxQixFQUFFLFVBQVUsQ0FBQztJQUNwRSxNQUFNLGFBQWEsR0FBRyxXQUFXLENBQUMsa0JBQWtCLEVBQUUsVUFBVSxDQUFDO0lBQ2pFLE1BQU0sb0JBQW9CLEdBQUcsYUFBYSxJQUFJLElBQUksSUFBSSxhQUFhLElBQUksSUFBSSxJQUFJLGFBQWEsS0FBSyxhQUFhLENBQUM7SUFFL0c7O3NEQUVrRDtJQUNsRCxJQUFJLG9CQUFvQixFQUFFO1FBQ3hCLE9BQU8sUUFBUSxDQUFDLGFBQWEsQ0FBQyxDQUFDO0tBQ2hDO0FBQ0gsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxLQUFLLFVBQVUsV0FBVyxDQUFDLFVBQWtCO0lBQzNDLE1BQU0sYUFBYSxHQUFHLE1BQU0sRUFBRSxDQUFDLGtCQUFrQixDQUFDLEVBQUUsTUFBTSxFQUFFLFVBQVUsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDcEYsTUFBTSxRQUFRLEdBQUcsQ0FBQyxHQUFHLGFBQWEsQ0FBQyxRQUFRLElBQUksRUFBRSxFQUFFLEdBQUcsYUFBYSxDQUFDLGFBQWEsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUN6RixJQUFJLFFBQVEsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO1FBQ3pCLE9BQU87S0FDUjtJQUVELE1BQU0sT0FBTyxHQUFHLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFXLEVBQUUsRUFBRSxDQUFDLENBQUMsRUFBRSxHQUFHLEVBQUUsTUFBTSxDQUFDLEdBQUcsRUFBRSxTQUFTLEVBQUUsTUFBTSxDQUFDLFNBQVMsRUFBRSxDQUFDLENBQUMsQ0FBQztJQUNsRyxNQUFNLEVBQUUsQ0FBQyxhQUFhLENBQUMsRUFBRSxNQUFNLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7SUFFdkYsSUFBSSxhQUFhLEVBQUUsV0FBVyxFQUFFO1FBQzlCLE1BQU0sV0FBVyxDQUFDLFVBQVUsQ0FBQyxDQUFDO0tBQy9CO0FBQ0gsQ0FBQztBQUVELEtBQUssVUFBVSxRQUFRLENBQUMsVUFBbUI7SUFDekMsSUFBSSxDQUFDLFVBQVUsRUFBRTtRQUNmLE1BQU0sSUFBSSxLQUFLLENBQUMsNkJBQTZCLENBQUMsQ0FBQztLQUNoRDtJQUNELElBQUksQ0FBQyxNQUFNLHlCQUF5QixDQUFDLFVBQVUsQ0FBQyxFQUFFO1FBQ2hELE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLHlCQUF5Qix1QkFBdUIsNkJBQTZCLENBQUMsQ0FBQztRQUNwRyxPQUFPO0tBQ1I7SUFDRCxJQUFJO1FBQ0YsTUFBTSxXQUFXLENBQUMsVUFBVSxDQUFDLENBQUM7S0FDL0I7SUFBQyxPQUFPLENBQUMsRUFBRTtRQUNWLElBQUksQ0FBQyxDQUFDLElBQUksS0FBSyxjQUFjLEVBQUU7WUFDN0IsTUFBTSxDQUFDLENBQUM7U0FDVDtRQUNELGlDQUFpQztLQUNsQztBQUNILENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsS0FBSyxVQUFVLHlCQUF5QixDQUFDLFVBQWtCO0lBQ3pELE1BQU0sUUFBUSxHQUFHLE1BQU0sRUFBRSxDQUFDLGdCQUFnQixDQUFDLEVBQUUsTUFBTSxFQUFFLFVBQVUsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDN0UsT0FBTyxRQUFRLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEtBQUssdUJBQXVCLElBQUksR0FBRyxDQUFDLEtBQUssS0FBSyxNQUFNLENBQUMsQ0FBQztBQUNsRyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGltcG9ydC9uby1leHRyYW5lb3VzLWRlcGVuZGVuY2llc1xuaW1wb3J0IHsgUzMgfSBmcm9tICdhd3Mtc2RrJztcblxuY29uc3QgQVVUT19ERUxFVEVfT0JKRUNUU19UQUcgPSAnYXdzLWNkazphdXRvLWRlbGV0ZS1vYmplY3RzJztcblxuY29uc3QgczMgPSBuZXcgUzMoKTtcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGhhbmRsZXIoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQpIHtcbiAgc3dpdGNoIChldmVudC5SZXF1ZXN0VHlwZSkge1xuICAgIGNhc2UgJ0NyZWF0ZSc6XG4gICAgICByZXR1cm47XG4gICAgY2FzZSAnVXBkYXRlJzpcbiAgICAgIHJldHVybiBvblVwZGF0ZShldmVudCk7XG4gICAgY2FzZSAnRGVsZXRlJzpcbiAgICAgIHJldHVybiBvbkRlbGV0ZShldmVudC5SZXNvdXJjZVByb3BlcnRpZXM/LkJ1Y2tldE5hbWUpO1xuICB9XG59XG5cbmFzeW5jIGZ1bmN0aW9uIG9uVXBkYXRlKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50KSB7XG4gIGNvbnN0IHVwZGF0ZUV2ZW50ID0gZXZlbnQgYXMgQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VVcGRhdGVFdmVudDtcbiAgY29uc3Qgb2xkQnVja2V0TmFtZSA9IHVwZGF0ZUV2ZW50Lk9sZFJlc291cmNlUHJvcGVydGllcz8uQnVja2V0TmFtZTtcbiAgY29uc3QgbmV3QnVja2V0TmFtZSA9IHVwZGF0ZUV2ZW50LlJlc291cmNlUHJvcGVydGllcz8uQnVja2V0TmFtZTtcbiAgY29uc3QgYnVja2V0TmFtZUhhc0NoYW5nZWQgPSBuZXdCdWNrZXROYW1lICE9IG51bGwgJiYgb2xkQnVja2V0TmFtZSAhPSBudWxsICYmIG5ld0J1Y2tldE5hbWUgIT09IG9sZEJ1Y2tldE5hbWU7XG5cbiAgLyogSWYgdGhlIG5hbWUgb2YgdGhlIGJ1Y2tldCBoYXMgY2hhbmdlZCwgQ2xvdWRGb3JtYXRpb24gd2lsbCB0cnkgdG8gZGVsZXRlIHRoZSBidWNrZXRcbiAgICAgYW5kIGNyZWF0ZSBhIG5ldyBvbmUgd2l0aCB0aGUgbmV3IG5hbWUuIFNvIHdlIGhhdmUgdG8gZGVsZXRlIHRoZSBjb250ZW50cyBvZiB0aGVcbiAgICAgYnVja2V0IHNvIHRoYXQgdGhpcyBvcGVyYXRpb24gZG9lcyBub3QgZmFpbC4gKi9cbiAgaWYgKGJ1Y2tldE5hbWVIYXNDaGFuZ2VkKSB7XG4gICAgcmV0dXJuIG9uRGVsZXRlKG9sZEJ1Y2tldE5hbWUpO1xuICB9XG59XG5cbi8qKlxuICogUmVjdXJzaXZlbHkgZGVsZXRlIGFsbCBpdGVtcyBpbiB0aGUgYnVja2V0XG4gKlxuICogQHBhcmFtIGJ1Y2tldE5hbWUgdGhlIGJ1Y2tldCBuYW1lXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGVtcHR5QnVja2V0KGJ1Y2tldE5hbWU6IHN0cmluZykge1xuICBjb25zdCBsaXN0ZWRPYmplY3RzID0gYXdhaXQgczMubGlzdE9iamVjdFZlcnNpb25zKHsgQnVja2V0OiBidWNrZXROYW1lIH0pLnByb21pc2UoKTtcbiAgY29uc3QgY29udGVudHMgPSBbLi4ubGlzdGVkT2JqZWN0cy5WZXJzaW9ucyA/PyBbXSwgLi4ubGlzdGVkT2JqZWN0cy5EZWxldGVNYXJrZXJzID8/IFtdXTtcbiAgaWYgKGNvbnRlbnRzLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybjtcbiAgfVxuXG4gIGNvbnN0IHJlY29yZHMgPSBjb250ZW50cy5tYXAoKHJlY29yZDogYW55KSA9PiAoeyBLZXk6IHJlY29yZC5LZXksIFZlcnNpb25JZDogcmVjb3JkLlZlcnNpb25JZCB9KSk7XG4gIGF3YWl0IHMzLmRlbGV0ZU9iamVjdHMoeyBCdWNrZXQ6IGJ1Y2tldE5hbWUsIERlbGV0ZTogeyBPYmplY3RzOiByZWNvcmRzIH0gfSkucHJvbWlzZSgpO1xuXG4gIGlmIChsaXN0ZWRPYmplY3RzPy5Jc1RydW5jYXRlZCkge1xuICAgIGF3YWl0IGVtcHR5QnVja2V0KGJ1Y2tldE5hbWUpO1xuICB9XG59XG5cbmFzeW5jIGZ1bmN0aW9uIG9uRGVsZXRlKGJ1Y2tldE5hbWU/OiBzdHJpbmcpIHtcbiAgaWYgKCFidWNrZXROYW1lKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdObyBCdWNrZXROYW1lIHdhcyBwcm92aWRlZC4nKTtcbiAgfVxuICBpZiAoIWF3YWl0IGlzQnVja2V0VGFnZ2VkRm9yRGVsZXRpb24oYnVja2V0TmFtZSkpIHtcbiAgICBwcm9jZXNzLnN0ZG91dC53cml0ZShgQnVja2V0IGRvZXMgbm90IGhhdmUgJyR7QVVUT19ERUxFVEVfT0JKRUNUU19UQUd9JyB0YWcsIHNraXBwaW5nIGNsZWFuaW5nLlxcbmApO1xuICAgIHJldHVybjtcbiAgfVxuICB0cnkge1xuICAgIGF3YWl0IGVtcHR5QnVja2V0KGJ1Y2tldE5hbWUpO1xuICB9IGNhdGNoIChlKSB7XG4gICAgaWYgKGUuY29kZSAhPT0gJ05vU3VjaEJ1Y2tldCcpIHtcbiAgICAgIHRocm93IGU7XG4gICAgfVxuICAgIC8vIEJ1Y2tldCBkb2Vzbid0IGV4aXN0LiBJZ25vcmluZ1xuICB9XG59XG5cbi8qKlxuICogVGhlIGJ1Y2tldCB3aWxsIG9ubHkgYmUgdGFnZ2VkIGZvciBkZWxldGlvbiBpZiBpdCdzIGJlaW5nIGRlbGV0ZWQgaW4gdGhlIHNhbWVcbiAqIGRlcGxveW1lbnQgYXMgdGhpcyBDdXN0b20gUmVzb3VyY2UuXG4gKlxuICogSWYgdGhlIEN1c3RvbSBSZXNvdXJjZSBpcyBldmVyeSBkZWxldGVkIGJlZm9yZSB0aGUgYnVja2V0LCBpdCBtdXN0IGJlIGJlY2F1c2VcbiAqIGBhdXRvRGVsZXRlT2JqZWN0c2AgaGFzIGJlZW4gc3dpdGNoZWQgdG8gZmFsc2UsIGluIHdoaWNoIGNhc2UgdGhlIHRhZyB3b3VsZCBoYXZlXG4gKiBiZWVuIHJlbW92ZWQgYmVmb3JlIHdlIGdldCB0byB0aGlzIERlbGV0ZSBldmVudC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gaXNCdWNrZXRUYWdnZWRGb3JEZWxldGlvbihidWNrZXROYW1lOiBzdHJpbmcpIHtcbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBzMy5nZXRCdWNrZXRUYWdnaW5nKHsgQnVja2V0OiBidWNrZXROYW1lIH0pLnByb21pc2UoKTtcbiAgcmV0dXJuIHJlc3BvbnNlLlRhZ1NldC5zb21lKHRhZyA9PiB0YWcuS2V5ID09PSBBVVRPX0RFTEVURV9PQkpFQ1RTX1RBRyAmJiB0YWcuVmFsdWUgPT09ICd0cnVlJyk7XG59Il19 \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.ts b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.ts new file mode 100644 index 0000000000000..2459d44ab1d18 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.ts @@ -0,0 +1,82 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { S3 } from 'aws-sdk'; + +const AUTO_DELETE_OBJECTS_TAG = 'aws-cdk:auto-delete-objects'; + +const s3 = new S3(); + +export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent) { + switch (event.RequestType) { + case 'Create': + return; + case 'Update': + return onUpdate(event); + case 'Delete': + return onDelete(event.ResourceProperties?.BucketName); + } +} + +async function onUpdate(event: AWSLambda.CloudFormationCustomResourceEvent) { + const updateEvent = event as AWSLambda.CloudFormationCustomResourceUpdateEvent; + const oldBucketName = updateEvent.OldResourceProperties?.BucketName; + const newBucketName = updateEvent.ResourceProperties?.BucketName; + const bucketNameHasChanged = newBucketName != null && oldBucketName != null && newBucketName !== oldBucketName; + + /* If the name of the bucket has changed, CloudFormation will try to delete the bucket + and create a new one with the new name. So we have to delete the contents of the + bucket so that this operation does not fail. */ + if (bucketNameHasChanged) { + return onDelete(oldBucketName); + } +} + +/** + * Recursively delete all items in the bucket + * + * @param bucketName the bucket name + */ +async function emptyBucket(bucketName: string) { + const listedObjects = await s3.listObjectVersions({ Bucket: bucketName }).promise(); + const contents = [...listedObjects.Versions ?? [], ...listedObjects.DeleteMarkers ?? []]; + if (contents.length === 0) { + return; + } + + const records = contents.map((record: any) => ({ Key: record.Key, VersionId: record.VersionId })); + await s3.deleteObjects({ Bucket: bucketName, Delete: { Objects: records } }).promise(); + + if (listedObjects?.IsTruncated) { + await emptyBucket(bucketName); + } +} + +async function onDelete(bucketName?: string) { + if (!bucketName) { + throw new Error('No BucketName was provided.'); + } + if (!await isBucketTaggedForDeletion(bucketName)) { + process.stdout.write(`Bucket does not have '${AUTO_DELETE_OBJECTS_TAG}' tag, skipping cleaning.\n`); + return; + } + try { + await emptyBucket(bucketName); + } catch (e) { + if (e.code !== 'NoSuchBucket') { + throw e; + } + // Bucket doesn't exist. Ignoring + } +} + +/** + * The bucket will only be tagged for deletion if it's being deleted in the same + * deployment as this Custom Resource. + * + * If the Custom Resource is every deleted before the bucket, it must be because + * `autoDeleteObjects` has been switched to false, in which case the tag would have + * been removed before we get to this Delete event. + */ +async function isBucketTaggedForDeletion(bucketName: string) { + const response = await s3.getBucketTagging({ Bucket: bucketName }).promise(); + return response.TagSet.some(tag => tag.Key === AUTO_DELETE_OBJECTS_TAG && tag.Value === 'true'); +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/cdk.out b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/integ.json b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/integ.json new file mode 100644 index 0000000000000..240c501c9dba6 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "PreparelessPipelineTest/DefaultTest": { + "stacks": [ + "PreparelessPipelineStack" + ], + "assertionStack": "PreparelessPipelineTest/DefaultTest/DeployAssert", + "assertionStackName": "PreparelessPipelineTestDefaultTestDeployAssert7B7DD2C6" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/manifest.json b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..595a5fc4aa045 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/manifest.json @@ -0,0 +1,214 @@ +{ + "version": "21.0.0", + "artifacts": { + "assembly-PreparelessPipelineStack-MyStage": { + "type": "cdk:cloud-assembly", + "properties": { + "directoryName": "assembly-PreparelessPipelineStack-MyStage", + "displayName": "PreparelessPipelineStack/MyStage" + } + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "PreparelessPipelineStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "PreparelessPipelineStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "PreparelessPipelineStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "PreparelessPipelineStack.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/d48998cffedf7ada707ce9f74130f229eb47e4c549355b1031589d0c4d78d9ad.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "PreparelessPipelineStack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "PreparelessPipelineStack.assets" + ], + "metadata": { + "/PreparelessPipelineStack/SourceBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "SourceBucketDDD2130A" + } + ], + "/PreparelessPipelineStack/SourceBucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "SourceBucketPolicy703DFBF9" + } + ], + "/PreparelessPipelineStack/SourceBucket/AutoDeleteObjectsCustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "SourceBucketAutoDeleteObjectsCustomResourceC68FC040" + } + ], + "/PreparelessPipelineStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092" + } + ], + "/PreparelessPipelineStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F" + } + ], + "/PreparelessPipelineStack/Pipeline/Pipeline/ArtifactsBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineArtifactsBucketAEA9A052" + } + ], + "/PreparelessPipelineStack/Pipeline/Pipeline/ArtifactsBucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineArtifactsBucketPolicyF53CCC52" + } + ], + "/PreparelessPipelineStack/Pipeline/Pipeline/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineRoleB27FAA37" + } + ], + "/PreparelessPipelineStack/Pipeline/Pipeline/Role/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineRoleDefaultPolicy7BDC1ABB" + } + ], + "/PreparelessPipelineStack/Pipeline/Pipeline/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Pipeline9850B417" + } + ], + "/PreparelessPipelineStack/Pipeline/Pipeline/Source/S3/CodePipelineActionRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineSourceS3CodePipelineActionRole83895A58" + } + ], + "/PreparelessPipelineStack/Pipeline/Pipeline/Source/S3/CodePipelineActionRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineSourceS3CodePipelineActionRoleDefaultPolicyB176A07F" + } + ], + "/PreparelessPipelineStack/Pipeline/Pipeline/Build/Synth/CdkBuildProject/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineBuildSynthCdkBuildProjectRole231EEA2A" + } + ], + "/PreparelessPipelineStack/Pipeline/Pipeline/Build/Synth/CdkBuildProject/Role/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineBuildSynthCdkBuildProjectRoleDefaultPolicyFB6C941C" + } + ], + "/PreparelessPipelineStack/Pipeline/Pipeline/Build/Synth/CdkBuildProject/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineBuildSynthCdkBuildProject6BEFA8E6" + } + ], + "/PreparelessPipelineStack/Pipeline/CodeBuildActionRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineCodeBuildActionRole226DB0CB" + } + ], + "/PreparelessPipelineStack/Pipeline/CodeBuildActionRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineCodeBuildActionRoleDefaultPolicy1D62A6FE" + } + ], + "/PreparelessPipelineStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/PreparelessPipelineStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "PreparelessPipelineStack" + }, + "PreparelessPipelineTestDefaultTestDeployAssert7B7DD2C6.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "PreparelessPipelineTestDefaultTestDeployAssert7B7DD2C6.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "PreparelessPipelineTestDefaultTestDeployAssert7B7DD2C6": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "PreparelessPipelineTestDefaultTestDeployAssert7B7DD2C6.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "PreparelessPipelineTestDefaultTestDeployAssert7B7DD2C6.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "PreparelessPipelineTestDefaultTestDeployAssert7B7DD2C6.assets" + ], + "metadata": { + "/PreparelessPipelineTest/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/PreparelessPipelineTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "PreparelessPipelineTest/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/tree.json b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/tree.json new file mode 100644 index 0000000000000..05ba185d3d1dd --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-without-prepare.integ.snapshot/tree.json @@ -0,0 +1,1402 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + }, + "PreparelessPipelineStack": { + "id": "PreparelessPipelineStack", + "path": "PreparelessPipelineStack", + "children": { + "SourceBucket": { + "id": "SourceBucket", + "path": "PreparelessPipelineStack/SourceBucket", + "children": { + "Resource": { + "id": "Resource", + "path": "PreparelessPipelineStack/SourceBucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "aws-cdk:auto-delete-objects", + "value": "true" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucket", + "version": "0.0.0" + } + }, + "Policy": { + "id": "Policy", + "path": "PreparelessPipelineStack/SourceBucket/Policy", + "children": { + "Resource": { + "id": "Resource", + "path": "PreparelessPipelineStack/SourceBucket/Policy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::BucketPolicy", + "aws:cdk:cloudformation:props": { + "bucket": { + "Ref": "SourceBucketDDD2130A" + }, + "policyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "SourceBucketDDD2130A", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "SourceBucketDDD2130A", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketPolicy", + "version": "0.0.0" + } + }, + "AutoDeleteObjectsCustomResource": { + "id": "AutoDeleteObjectsCustomResource", + "path": "PreparelessPipelineStack/SourceBucket/AutoDeleteObjectsCustomResource", + "children": { + "Default": { + "id": "Default", + "path": "PreparelessPipelineStack/SourceBucket/AutoDeleteObjectsCustomResource/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.Bucket", + "version": "0.0.0" + } + }, + "Custom::S3AutoDeleteObjectsCustomResourceProvider": { + "id": "Custom::S3AutoDeleteObjectsCustomResourceProvider", + "path": "PreparelessPipelineStack/Custom::S3AutoDeleteObjectsCustomResourceProvider", + "children": { + "Staging": { + "id": "Staging", + "path": "PreparelessPipelineStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "PreparelessPipelineStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "PreparelessPipelineStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResourceProvider", + "version": "0.0.0" + } + }, + "Pipeline": { + "id": "Pipeline", + "path": "PreparelessPipelineStack/Pipeline", + "children": { + "Pipeline": { + "id": "Pipeline", + "path": "PreparelessPipelineStack/Pipeline/Pipeline", + "children": { + "ArtifactsBucket": { + "id": "ArtifactsBucket", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/ArtifactsBucket", + "children": { + "Resource": { + "id": "Resource", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/ArtifactsBucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "bucketEncryption": { + "serverSideEncryptionConfiguration": [ + { + "serverSideEncryptionByDefault": { + "sseAlgorithm": "aws:kms" + } + } + ] + }, + "publicAccessBlockConfiguration": { + "blockPublicAcls": true, + "blockPublicPolicy": true, + "ignorePublicAcls": true, + "restrictPublicBuckets": true + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucket", + "version": "0.0.0" + } + }, + "Policy": { + "id": "Policy", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/ArtifactsBucket/Policy", + "children": { + "Resource": { + "id": "Resource", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/ArtifactsBucket/Policy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::BucketPolicy", + "aws:cdk:cloudformation:props": { + "bucket": { + "Ref": "PipelineArtifactsBucketAEA9A052" + }, + "policyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":role/cdk-hnb659fds-deploy-role-", + { + "Ref": "AWS::AccountId" + }, + "-", + { + "Ref": "AWS::Region" + } + ] + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.Bucket", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Role", + "children": { + "Resource": { + "id": "Resource", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codepipeline.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Role/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Role/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineCodeBuildActionRole226DB0CB", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "PipelineSourceS3CodePipelineActionRole83895A58", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":role/cdk-hnb659fds-deploy-role-", + { + "Ref": "AWS::AccountId" + }, + "-", + { + "Ref": "AWS::Region" + } + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "policyName": "PipelineRoleDefaultPolicy7BDC1ABB", + "roles": [ + { + "Ref": "PipelineRoleB27FAA37" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CodePipeline::Pipeline", + "aws:cdk:cloudformation:props": { + "roleArn": { + "Fn::GetAtt": [ + "PipelineRoleB27FAA37", + "Arn" + ] + }, + "stages": [ + { + "name": "Source", + "actions": [ + { + "name": { + "Ref": "SourceBucketDDD2130A" + }, + "outputArtifacts": [ + { + "name": "c8506b445957b8105ede7b68ebe35e9406d642cd0c_Source" + } + ], + "actionTypeId": { + "category": "Source", + "version": "1", + "owner": "AWS", + "provider": "S3" + }, + "configuration": { + "S3Bucket": { + "Ref": "SourceBucketDDD2130A" + }, + "S3ObjectKey": "key" + }, + "runOrder": 1, + "roleArn": { + "Fn::GetAtt": [ + "PipelineSourceS3CodePipelineActionRole83895A58", + "Arn" + ] + } + } + ] + }, + { + "name": "Build", + "actions": [ + { + "name": "Synth", + "inputArtifacts": [ + { + "name": "c8506b445957b8105ede7b68ebe35e9406d642cd0c_Source" + } + ], + "outputArtifacts": [ + { + "name": "Synth_Output" + } + ], + "actionTypeId": { + "category": "Build", + "version": "1", + "owner": "AWS", + "provider": "CodeBuild" + }, + "configuration": { + "ProjectName": { + "Ref": "PipelineBuildSynthCdkBuildProject6BEFA8E6" + } + }, + "runOrder": 1, + "roleArn": { + "Fn::GetAtt": [ + "PipelineCodeBuildActionRole226DB0CB", + "Arn" + ] + } + } + ] + }, + { + "name": "MyStage", + "actions": [ + { + "name": "Deploy", + "inputArtifacts": [ + { + "name": "Synth_Output" + } + ], + "actionTypeId": { + "category": "Deploy", + "version": "1", + "owner": "AWS", + "provider": "CloudFormation" + }, + "configuration": { + "StackName": "MyStage-Stack", + "Capabilities": "CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND", + "RoleArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":role/cdk-hnb659fds-cfn-exec-role-", + { + "Ref": "AWS::AccountId" + }, + "-", + { + "Ref": "AWS::Region" + } + ] + ] + }, + "ActionMode": "CREATE_UPDATE", + "TemplatePath": "Synth_Output::assembly-PreparelessPipelineStack-MyStage/PreparelessPipelineStackMyStageStack3DC192E7.template.json" + }, + "runOrder": 1, + "roleArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":role/cdk-hnb659fds-deploy-role-", + { + "Ref": "AWS::AccountId" + }, + "-", + { + "Ref": "AWS::Region" + } + ] + ] + } + } + ] + } + ], + "artifactStore": { + "type": "S3", + "location": { + "Ref": "PipelineArtifactsBucketAEA9A052" + } + }, + "restartExecutionOnUpdate": true + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codepipeline.CfnPipeline", + "version": "0.0.0" + } + }, + "Source": { + "id": "Source", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Source", + "children": { + "S3": { + "id": "S3", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Source/S3", + "children": { + "CodePipelineActionRole": { + "id": "CodePipelineActionRole", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Source/S3/CodePipelineActionRole", + "children": { + "Resource": { + "id": "Resource", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Source/S3/CodePipelineActionRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Source/S3/CodePipelineActionRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Source/S3/CodePipelineActionRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "SourceBucketDDD2130A", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "SourceBucketDDD2130A", + "Arn" + ] + }, + "/key" + ] + ] + } + ] + }, + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "policyName": "PipelineSourceS3CodePipelineActionRoleDefaultPolicyB176A07F", + "roles": [ + { + "Ref": "PipelineSourceS3CodePipelineActionRole83895A58" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + }, + "Build": { + "id": "Build", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Build", + "children": { + "Synth": { + "id": "Synth", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Build/Synth", + "children": { + "CdkBuildProject": { + "id": "CdkBuildProject", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Build/Synth/CdkBuildProject", + "children": { + "Role": { + "id": "Role", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Build/Synth/CdkBuildProject/Role", + "children": { + "Resource": { + "id": "Resource", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Build/Synth/CdkBuildProject/Role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codebuild.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Build/Synth/CdkBuildProject/Role/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Build/Synth/CdkBuildProject/Role/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "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": "PipelineBuildSynthCdkBuildProject6BEFA8E6" + }, + ":*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "PipelineBuildSynthCdkBuildProject6BEFA8E6" + } + ] + ] + } + ] + }, + { + "Action": [ + "codebuild:BatchPutCodeCoverages", + "codebuild:BatchPutTestCases", + "codebuild:CreateReport", + "codebuild:CreateReportGroup", + "codebuild:UpdateReport" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codebuild:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":report-group/", + { + "Ref": "PipelineBuildSynthCdkBuildProject6BEFA8E6" + }, + "-*" + ] + ] + } + }, + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "policyName": "PipelineBuildSynthCdkBuildProjectRoleDefaultPolicyFB6C941C", + "roles": [ + { + "Ref": "PipelineBuildSynthCdkBuildProjectRole231EEA2A" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/Build/Synth/CdkBuildProject/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CodeBuild::Project", + "aws:cdk:cloudformation:props": { + "artifacts": { + "type": "CODEPIPELINE" + }, + "environment": { + "type": "LINUX_CONTAINER", + "image": "aws/codebuild/standard:5.0", + "imagePullCredentialsType": "CODEBUILD", + "privilegedMode": false, + "computeType": "BUILD_GENERAL1_SMALL" + }, + "serviceRole": { + "Fn::GetAtt": [ + "PipelineBuildSynthCdkBuildProjectRole231EEA2A", + "Arn" + ] + }, + "source": { + "type": "CODEPIPELINE", + "buildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"mkdir cdk.out\",\n \"touch cdk.out/dummy\"\n ]\n }\n },\n \"artifacts\": {\n \"base-directory\": \"cdk.out\",\n \"files\": \"**/*\"\n }\n}" + }, + "cache": { + "type": "NO_CACHE" + }, + "description": "Pipeline step PreparelessPipelineStack/Pipeline/Build/Synth", + "encryptionKey": "alias/aws/s3" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codebuild.CfnProject", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codebuild.PipelineProject", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + }, + "MyStage": { + "id": "MyStage", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/MyStage", + "children": { + "Deploy": { + "id": "Deploy", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/MyStage/Deploy", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + }, + "MutableRolearn:${AWS::Partition}:iam::${AWS::AccountId}:role--cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}": { + "id": "MutableRolearn:${AWS::Partition}:iam::${AWS::AccountId}:role--cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/MutableRolearn:${AWS::Partition}:iam::${AWS::AccountId}:role--cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "arn:${AWS::Partition}:iam::${AWS::AccountId}:role--cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}": { + "id": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role--cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/arn:${AWS::Partition}:iam::${AWS::AccountId}:role--cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "children": { + "8389e75f-0810-4838-bf64-d6f85a95cf83": { + "id": "8389e75f-0810-4838-bf64-d6f85a95cf83", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/arn:${AWS::Partition}:iam::${AWS::AccountId}:role--cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}/8389e75f-0810-4838-bf64-d6f85a95cf83", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "MutableRolearn:${AWS::Partition}:iam::${AWS::AccountId}:role--cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}": { + "id": "MutableRolearn:${AWS::Partition}:iam::${AWS::AccountId}:role--cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/MutableRolearn:${AWS::Partition}:iam::${AWS::AccountId}:role--cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "arn:${AWS::Partition}:iam::${AWS::AccountId}:role--cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}": { + "id": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role--cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "path": "PreparelessPipelineStack/Pipeline/Pipeline/arn:${AWS::Partition}:iam::${AWS::AccountId}:role--cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codepipeline.Pipeline", + "version": "0.0.0" + } + }, + "CodeBuildActionRole": { + "id": "CodeBuildActionRole", + "path": "PreparelessPipelineStack/Pipeline/CodeBuildActionRole", + "children": { + "Resource": { + "id": "Resource", + "path": "PreparelessPipelineStack/Pipeline/CodeBuildActionRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Condition": { + "Bool": { + "aws:ViaAWSService": "codepipeline.amazonaws.com" + } + }, + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "PreparelessPipelineStack/Pipeline/CodeBuildActionRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "PreparelessPipelineStack/Pipeline/CodeBuildActionRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "codebuild:BatchGetBuilds", + "codebuild:StartBuild", + "codebuild:StopBuild" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineBuildSynthCdkBuildProject6BEFA8E6", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "PipelineCodeBuildActionRoleDefaultPolicy1D62A6FE", + "roles": [ + { + "Ref": "PipelineCodeBuildActionRole226DB0CB" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/pipelines.CodePipeline", + "version": "0.0.0" + } + }, + "MyStage": { + "id": "MyStage", + "path": "PreparelessPipelineStack/MyStage", + "children": { + "Stack": { + "id": "Stack", + "path": "PreparelessPipelineStack/MyStage/Stack", + "children": { + "MyApp": { + "id": "MyApp", + "path": "PreparelessPipelineStack/MyStage/Stack/MyApp", + "children": { + "Stack": { + "id": "Stack", + "path": "PreparelessPipelineStack/MyStage/Stack/MyApp/Stack", + "children": { + "Bucket": { + "id": "Bucket", + "path": "PreparelessPipelineStack/MyStage/Stack/MyApp/Stack/Bucket", + "children": { + "Resource": { + "id": "Resource", + "path": "PreparelessPipelineStack/MyStage/Stack/MyApp/Stack/Bucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucket", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.Bucket", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "PreparelessPipelineStack/MyStage/Stack/MyApp/Stack/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "PreparelessPipelineStack/MyStage/Stack/MyApp/Stack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stage", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "PreparelessPipelineStack/MyStage/Stack/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "PreparelessPipelineStack/MyStage/Stack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stage", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "PreparelessPipelineTest": { + "id": "PreparelessPipelineTest", + "path": "PreparelessPipelineTest", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "PreparelessPipelineTest/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "PreparelessPipelineTest/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "PreparelessPipelineTest/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/testhelpers/test-app.ts b/packages/@aws-cdk/pipelines/test/testhelpers/test-app.ts index 24e29b2625929..fca2cd26f04d4 100644 --- a/packages/@aws-cdk/pipelines/test/testhelpers/test-app.ts +++ b/packages/@aws-cdk/pipelines/test/testhelpers/test-app.ts @@ -223,4 +223,10 @@ export class PlainStackApp extends Stage { } } - +export class MultiStackApp extends Stage { + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + new BucketStack(this, 'Stack1'); + new BucketStack(this, 'Stack2'); + } +} diff --git a/packages/@aws-cdk/triggers/package.json b/packages/@aws-cdk/triggers/package.json index 3bb54b26788ed..e3d732a28e934 100644 --- a/packages/@aws-cdk/triggers/package.json +++ b/packages/@aws-cdk/triggers/package.json @@ -77,7 +77,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/integ-runner": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", - "aws-sdk": "^2.848.0", + "aws-sdk": "^2.1211.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", "jest": "^27.5.1" diff --git a/packages/aws-cdk-lib/README.md b/packages/aws-cdk-lib/README.md index 63be77ed01bbb..2cda63fee8efe 100644 --- a/packages/aws-cdk-lib/README.md +++ b/packages/aws-cdk-lib/README.md @@ -1116,4 +1116,74 @@ It's possible to synthesize the project with more Resources than the allowed (or Set the context key `@aws-cdk/core:stackResourceLimit` with the proper value, being 0 for disable the limit of resources. +## App Context + +[Context values](https://docs.aws.amazon.com/cdk/v2/guide/context.html) are key-value pairs that can be associated with an app, stack, or construct. +One common use case for context is to use it for enabling/disabling [feature flags](https://docs.aws.amazon.com/cdk/v2/guide/featureflags.html). There are several places +where context can be specified. They are listed below in the order they are evaluated (items at the +top take precedence over those below). + +- The `node.setContext()` method +- The `postCliContext` prop when you create an `App` +- The CLI via the `--context` CLI argument +- The `cdk.json` file via the `context` key: +- The `cdk.context.json` file: +- The `~/.cdk.json` file via the `context` key: +- The `context` prop when you create an `App` + +### Examples of setting context + +```ts +new App({ + context: { + '@aws-cdk/core:newStyleStackSynthesis': true, + }, +}); +``` + +```ts +const app = new App(); +app.node.setContext('@aws-cdk/core:newStyleStackSynthesis', true); +``` + +```ts +new App({ + postCliContext: { + '@aws-cdk/core:newStyleStackSynthesis': true, + }, +}); +``` + +```console +cdk synth --context @aws-cdk/core:newStyleStackSynthesis=true +``` + +_cdk.json_ + +```json +{ + "context": { + "@aws-cdk/core:newStyleStackSynthesis": true + } +} +``` + +_cdk.context.json_ + +```json +{ + "@aws-cdk/core:newStyleStackSynthesis": true +} +``` + +_~/.cdk.json_ + +```json +{ + "context": { + "@aws-cdk/core:newStyleStackSynthesis": true + } +} +``` + diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index 9b4c336db9c08..5ded6c593fb1f 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -364,7 +364,7 @@ "@types/fs-extra": "^8.1.2", "@types/node": "^14.18.29", "constructs": "^10.0.0", - "esbuild": "^0.15.7", + "esbuild": "^0.15.8", "fs-extra": "^9.1.0", "ts-node": "^9.1.1", "typescript": "~3.8.3" diff --git a/packages/aws-cdk/README.md b/packages/aws-cdk/README.md index d6e29a2e3e5dc..4b4a03656fe9d 100644 --- a/packages/aws-cdk/README.md +++ b/packages/aws-cdk/README.md @@ -334,19 +334,38 @@ When `cdk deploy` is executed, deployment events will include the complete histo The `progress` key can also be specified as a user setting (`~/.cdk.json`) -#### Externally Executable CloudFormation Change Sets +#### CloudFormation Change Sets vs direct stack updates -For more control over when stack changes are deployed, the CDK can generate a -CloudFormation change set but not execute it. The default name of the generated -change set is *cdk-deploy-change-set*, and a previous change set with that -name will be overwritten. The change set will always be created, even if it -is empty. A name can also be given to the change set to make it easier to later -execute. +By default CDK will create a CloudFormation change with the changes that will +be deployed, and then executes it. This behavior can be controlled with the +`--method` parameter: + +- `--method=change-set` (default): create and execute the change set. +- `--method=prepare-change-set`: create teh change set but don't execute it. + This is useful if you have external tools that will inspect the change set or + you have an approval process for change sets. +- `--method=direct`: do not create a change set but apply the change immediately. + This is typically a bit faster than creating a change set, but it loses + the progress information. + +To have deploy faster without using change sets: ```console -$ cdk deploy --no-execute --change-set-name MyChangeSetName +$ cdk deploy --method=direct ``` +If a change set is created, it will be called *cdk-deploy-change-set*, and a +previous change set with that name will be overwritten. The change set will +always be created, even if it is empty. A name can also be given to the change +set to make it easier to later execute: + +```console +$ cdk deploy --method=prepare-change-set --change-set-name MyChangeSetName +``` + +For more control over when stack changes are deployed, the CDK can generate a +CloudFormation change set but not execute it. + #### Hotswap deployments for faster development You can pass the `--hotswap` flag to the `deploy` command: diff --git a/packages/aws-cdk/THIRD_PARTY_LICENSES b/packages/aws-cdk/THIRD_PARTY_LICENSES index 1bc62f44d98f3..e01cf25bb95c8 100644 --- a/packages/aws-cdk/THIRD_PARTY_LICENSES +++ b/packages/aws-cdk/THIRD_PARTY_LICENSES @@ -268,7 +268,7 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH RE ---------------- -** aws-sdk@2.1215.0 - https://www.npmjs.com/package/aws-sdk/v/2.1215.0 | Apache-2.0 +** aws-sdk@2.1219.0 - https://www.npmjs.com/package/aws-sdk/v/2.1219.0 | Apache-2.0 AWS SDK for JavaScript Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts b/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts index d7fe6cf902f5b..6999c892779cf 100644 --- a/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts +++ b/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts @@ -41,7 +41,7 @@ export class AwsCliCompatible { // we use that to the exclusion of everything else (note: this does not apply // to AWS_PROFILE, environment credentials still take precedence over AWS_PROFILE) if (options.profile) { - return new AWS.CredentialProviderChain(iniFileCredentialFactories(options.profile)); + return new AWS.CredentialProviderChain(iniFileCredentialFactories(options.profile, options.httpOptions)); } const implicitProfile = process.env.AWS_PROFILE || process.env.AWS_DEFAULT_PROFILE || 'default'; @@ -49,7 +49,7 @@ export class AwsCliCompatible { const sources = [ () => new AWS.EnvironmentCredentials('AWS'), () => new AWS.EnvironmentCredentials('AMAZON'), - ...iniFileCredentialFactories(implicitProfile), + ...iniFileCredentialFactories(implicitProfile, options.httpOptions), ]; if (options.containerCreds ?? hasEcsCredentials()) { @@ -75,10 +75,13 @@ export class AwsCliCompatible { }); } - function iniFileCredentialFactories(theProfile: string) { + function iniFileCredentialFactories(theProfile: string, theHttpOptions?: AWS.HTTPOptions) { return [ () => profileCredentials(theProfile), - () => new AWS.SsoCredentials({ profile: theProfile }), + () => new AWS.SsoCredentials({ + profile: theProfile, + httpOptions: theHttpOptions, + }), () => new AWS.ProcessCredentials({ profile: theProfile }), ]; } diff --git a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts index df20bf0f62b63..1d93d1db89eb1 100644 --- a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts +++ b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts @@ -104,7 +104,7 @@ export class BootstrapStack { force: options.force, roleArn: options.roleArn, tags: options.tags, - execute: options.execute, + deploymentMethod: { method: 'change-set', execute: options.execute }, parameters, usePreviousParameters: true, // Obviously we can't need a bootstrap stack to deploy a bootstrap stack diff --git a/packages/aws-cdk/lib/api/cloudformation-deployments.ts b/packages/aws-cdk/lib/api/cloudformation-deployments.ts index 6ca75c57bf5af..e23c3be1e8747 100644 --- a/packages/aws-cdk/lib/api/cloudformation-deployments.ts +++ b/packages/aws-cdk/lib/api/cloudformation-deployments.ts @@ -6,7 +6,7 @@ import { buildAssets, publishAssets } from '../util/asset-publishing'; import { Mode } from './aws-auth/credentials'; import { ISDK } from './aws-auth/sdk'; import { SdkProvider } from './aws-auth/sdk-provider'; -import { deployStack, DeployStackResult, destroyStack, makeBodyParameterAndUpload } from './deploy-stack'; +import { deployStack, DeployStackResult, destroyStack, makeBodyParameterAndUpload, DeploymentMethod } from './deploy-stack'; import { loadCurrentTemplateWithNestedStacks, loadCurrentTemplate } from './nested-stack-helpers'; import { ToolkitInfo } from './toolkit-info'; import { CloudFormationStack, Template, ResourcesToImport, ResourceIdentifierSummaries } from './util/cloudformation'; @@ -104,79 +104,89 @@ export interface DeployStackOptions { /** * Stack to deploy */ - stack: cxapi.CloudFormationStackArtifact; + readonly stack: cxapi.CloudFormationStackArtifact; /** * Execution role for the deployment (pass through to CloudFormation) * * @default - Current role */ - roleArn?: string; + readonly roleArn?: string; /** * Topic ARNs to send a message when deployment finishes (pass through to CloudFormation) * * @default - No notifications */ - notificationArns?: string[]; + readonly notificationArns?: string[]; /** * Override name under which stack will be deployed * * @default - Use artifact default */ - deployName?: string; + readonly deployName?: string; /** * Don't show stack deployment events, just wait * * @default false */ - quiet?: boolean; + readonly quiet?: boolean; /** * Name of the toolkit stack, if not the default name * * @default 'CDKToolkit' */ - toolkitStackName?: string; + readonly toolkitStackName?: string; /** * List of asset IDs which should NOT be built or uploaded * * @default - Build all assets */ - reuseAssets?: string[]; + readonly reuseAssets?: string[]; /** * Stack tags (pass through to CloudFormation) */ - tags?: Tag[]; + readonly tags?: Tag[]; /** * Stage the change set but don't execute it * - * @default - false + * @default - true + * @deprecated Use 'deploymentMethod' instead */ - execute?: boolean; + readonly execute?: boolean; /** * Optional name to use for the CloudFormation change set. * If not provided, a name will be generated automatically. + * + * @deprecated Use 'deploymentMethod' instead */ - changeSetName?: string; + readonly changeSetName?: string; + + /** + * Select the deployment method (direct or using a change set) + * + * @default - Change set with default options + */ + readonly deploymentMethod?: DeploymentMethod; /** * Force deployment, even if the deployed template is identical to the one we are about to deploy. * @default false deployment will be skipped if the template is identical */ - force?: boolean; + readonly force?: boolean; /** * Extra parameters for CloudFormation * @default - no additional parameters will be passed to the template */ - parameters?: { [name: string]: string | undefined }; + readonly parameters?: { [name: string]: string | undefined }; /** * Use previous values for unspecified parameters @@ -185,7 +195,7 @@ export interface DeployStackOptions { * * @default true */ - usePreviousParameters?: boolean; + readonly usePreviousParameters?: boolean; /** * Display mode for stack deployment progress. @@ -193,7 +203,7 @@ export interface DeployStackOptions { * @default - StackActivityProgress.Bar - stack events will be displayed for * the resource currently being deployed. */ - progress?: StackActivityProgress; + readonly progress?: StackActivityProgress; /** * Whether we are on a CI system @@ -371,6 +381,18 @@ export class CloudFormationDeployments { } public async deployStack(options: DeployStackOptions): Promise { + let deploymentMethod = options.deploymentMethod; + if (options.changeSetName || options.execute !== undefined) { + if (deploymentMethod) { + throw new Error('You cannot supply both \'deploymentMethod\' and \'changeSetName/execute\'. Supply one or the other.'); + } + deploymentMethod = { + method: 'change-set', + changeSetName: options.changeSetName, + execute: options.execute, + }; + } + const { stackSdk, resolvedEnvironment, cloudFormationRoleArn } = await this.prepareSdkFor(options.stack, options.roleArn); const toolkitInfo = await ToolkitInfo.lookup(resolvedEnvironment, stackSdk, options.toolkitStackName); @@ -401,8 +423,7 @@ export class CloudFormationDeployments { reuseAssets: options.reuseAssets, toolkitInfo, tags: options.tags, - execute: options.execute, - changeSetName: options.changeSetName, + deploymentMethod, force: options.force, parameters: options.parameters, usePreviousParameters: options.usePreviousParameters, diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index a9ac408527db7..23e16d14b989d 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -1,4 +1,5 @@ import * as cxapi from '@aws-cdk/cx-api'; +import type { CloudFormation } from 'aws-sdk'; import * as chalk from 'chalk'; import * as fs from 'fs-extra'; import * as uuid from 'uuid'; @@ -35,7 +36,7 @@ export interface DeployStackOptions { /** * The stack to be deployed */ - stack: cxapi.CloudFormationStackArtifact; + readonly stack: cxapi.CloudFormationStackArtifact; /** * The environment to deploy this stack in @@ -43,7 +44,7 @@ export interface DeployStackOptions { * The environment on the stack artifact may be unresolved, this one * must be resolved. */ - resolvedEnvironment: cxapi.Environment; + readonly resolvedEnvironment: cxapi.Environment; /** * The SDK to use for deploying the stack @@ -51,7 +52,7 @@ export interface DeployStackOptions { * Should have been initialized with the correct role with which * stack operations should be performed. */ - sdk: ISDK; + readonly sdk: ISDK; /** * SDK provider (seeded with default credentials) @@ -65,67 +66,61 @@ export interface DeployStackOptions { * - Publish legacy assets. * - Upload large CloudFormation templates to the staging bucket. */ - sdkProvider: SdkProvider; + readonly sdkProvider: SdkProvider; /** * Information about the bootstrap stack found in the target environment */ - toolkitInfo: ToolkitInfo; + readonly toolkitInfo: ToolkitInfo; /** * Role to pass to CloudFormation to execute the change set * * @default - Role specified on stack, otherwise current */ - roleArn?: string; + readonly roleArn?: string; /** * Notification ARNs to pass to CloudFormation to notify when the change set has completed * * @default - No notifications */ - notificationArns?: string[]; + readonly notificationArns?: string[]; /** * Name to deploy the stack under * * @default - Name from assembly */ - deployName?: string; + readonly deployName?: string; /** * Quiet or verbose deployment * * @default false */ - quiet?: boolean; + readonly quiet?: boolean; /** * List of asset IDs which shouldn't be built * * @default - Build all assets */ - reuseAssets?: string[]; + readonly reuseAssets?: string[]; /** * Tags to pass to CloudFormation to add to stack * * @default - No tags */ - tags?: Tag[]; + readonly tags?: Tag[]; /** - * Whether to execute the changeset or leave it in review. + * What deployment method to use * - * @default true - */ - execute?: boolean; - - /** - * Optional name to use for the CloudFormation change set. - * If not provided, a name will be generated automatically. + * @default - Change set with defaults */ - changeSetName?: string; + readonly deploymentMethod?: DeploymentMethod; /** * The collection of extra parameters @@ -136,7 +131,7 @@ export interface DeployStackOptions { * * @default - no additional parameters will be passed to the template */ - parameters?: { [name: string]: string | undefined }; + readonly parameters?: { [name: string]: string | undefined }; /** * Use previous values for unspecified parameters @@ -145,7 +140,7 @@ export interface DeployStackOptions { * * @default false */ - usePreviousParameters?: boolean; + readonly usePreviousParameters?: boolean; /** * Display mode for stack deployment progress. @@ -153,13 +148,13 @@ export interface DeployStackOptions { * @default StackActivityProgress.Bar stack events will be displayed for * the resource currently being deployed. */ - progress?: StackActivityProgress; + readonly progress?: StackActivityProgress; /** * Deploy even if the deployed template is identical to the one we are about to deploy. * @default false */ - force?: boolean; + readonly force?: boolean; /** * Whether we are on a CI system @@ -205,6 +200,32 @@ export interface DeployStackOptions { readonly overrideTemplate?: any; } +export type DeploymentMethod = + | DirectDeploymentMethod + | ChangeSetDeploymentMethod + ; + +export interface DirectDeploymentMethod { + readonly method: 'direct'; +} + +export interface ChangeSetDeploymentMethod { + readonly method: 'change-set'; + + /** + * Whether to execute the changeset or leave it in review. + * + * @default true + */ + readonly execute?: boolean; + + /** + * Optional name to use for the CloudFormation change set. + * If not provided, a name will be generated automatically. + */ + readonly changeSetName?: string; +} + const LARGE_TEMPLATE_SIZE_KB = 50; export async function deployStack(options: DeployStackOptions): Promise { @@ -283,116 +304,232 @@ export async function deployStack(options: DeployStackOptions): Promise { - // if we got here, and hotswap is enabled, that means changes couldn't be hotswapped, - // and we had to fall back on a full deployment. Note that fact in our User-Agent - if (options.hotswap) { - options.sdk.appendCustomUserAgent('cdk-hotswap/fallback'); + +type CommonPrepareOptions = + & keyof CloudFormation.CreateStackInput + & keyof CloudFormation.UpdateStackInput + & keyof CloudFormation.CreateChangeSetInput; +type CommonExecuteOptions = + & keyof CloudFormation.CreateStackInput + & keyof CloudFormation.UpdateStackInput + & keyof CloudFormation.ExecuteChangeSetInput; + +/** + * This class shares state and functionality between the different full deployment modes + */ +class FullCloudFormationDeployment { + private readonly cfn: ReturnType; + private readonly stackName: string; + private readonly update: boolean; + private readonly verb: string; + private readonly uuid: string; + + constructor( + private readonly options: DeployStackOptions, + private readonly cloudFormationStack: CloudFormationStack, + private readonly stackArtifact: cxapi.CloudFormationStackArtifact, + private readonly stackParams: ParameterValues, + private readonly bodyParameter: TemplateBodyParameter, + ) { + this.cfn = options.sdk.cloudFormation(); + this.stackName = options.deployName ?? stackArtifact.stackName; + + this.update = cloudFormationStack.exists && cloudFormationStack.stackStatus.name !== 'REVIEW_IN_PROGRESS'; + this.verb = this.update ? 'update' : 'create'; + this.uuid = uuid.v4(); } - const cfn = options.sdk.cloudFormation(); - const deployName = options.deployName ?? stackArtifact.stackName; - - const changeSetName = options.changeSetName ?? 'cdk-deploy-change-set'; - if (cloudFormationStack.exists) { - //Delete any existing change sets generated by CDK since change set names must be unique. - //The delete request is successful as long as the stack exists (even if the change set does not exist). - debug(`Removing existing change set with name ${changeSetName} if it exists`); - await cfn.deleteChangeSet({ StackName: deployName, ChangeSetName: changeSetName }).promise(); + public async performDeployment(): Promise { + const deploymentMethod = this.options.deploymentMethod ?? { method: 'change-set' }; + + if (deploymentMethod.method === 'direct' && this.options.resourcesToImport) { + throw new Error('Importing resources requires a changeset deployment'); + } + + switch (deploymentMethod.method) { + case 'change-set': + return this.changeSetDeployment(deploymentMethod); + + case 'direct': + return this.directDeployment(); + } + } + + private async changeSetDeployment(deploymentMethod: ChangeSetDeploymentMethod): Promise { + const changeSetName = deploymentMethod.changeSetName ?? 'cdk-deploy-change-set'; + const execute = deploymentMethod.execute ?? true; + const changeSetDescription = await this.createChangeSet(changeSetName, execute); + await this.updateTerminationProtection(); + + if (changeSetHasNoChanges(changeSetDescription)) { + debug('No changes are to be performed on %s.', this.stackName); + if (execute) { + debug('Deleting empty change set %s', changeSetDescription.ChangeSetId); + await this.cfn.deleteChangeSet({ StackName: this.stackName, ChangeSetName: changeSetName }).promise(); + } + return { noOp: true, outputs: this.cloudFormationStack.outputs, stackArn: changeSetDescription.StackId! }; + } + + if (!execute) { + print('Changeset %s created and waiting in review for manual execution (--no-execute)', changeSetDescription.ChangeSetId); + return { noOp: false, outputs: this.cloudFormationStack.outputs, stackArn: changeSetDescription.StackId! }; + } + + return this.executeChangeSet(changeSetDescription); } - const update = cloudFormationStack.exists && cloudFormationStack.stackStatus.name !== 'REVIEW_IN_PROGRESS'; - - debug(`Attempting to create ChangeSet with name ${changeSetName} to ${update ? 'update' : 'create'} stack ${deployName}`); - print('%s: creating CloudFormation changeset...', chalk.bold(deployName)); - const executionId = uuid.v4(); - const changeSet = await cfn.createChangeSet({ - StackName: deployName, - ChangeSetName: changeSetName, - ChangeSetType: options.resourcesToImport ? 'IMPORT' : update ? 'UPDATE' : 'CREATE', - ResourcesToImport: options.resourcesToImport, - Description: `CDK Changeset for execution ${executionId}`, - TemplateBody: bodyParameter.TemplateBody, - TemplateURL: bodyParameter.TemplateURL, - Parameters: stackParams.apiParameters, - RoleARN: options.roleArn, - NotificationARNs: options.notificationArns, - Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'], - Tags: options.tags, - }).promise(); - - const execute = options.execute ?? true; - - debug('Initiated creation of changeset: %s; waiting for it to finish creating...', changeSet.Id); - // Fetching all pages if we'll execute, so we can have the correct change count when monitoring. - const changeSetDescription = await waitForChangeSet(cfn, deployName, changeSetName, { fetchAll: execute }); - - // Update termination protection only if it has changed. - const terminationProtection = stackArtifact.terminationProtection ?? false; - if (!!cloudFormationStack.terminationProtection !== terminationProtection) { - debug('Updating termination protection from %s to %s for stack %s', cloudFormationStack.terminationProtection, terminationProtection, deployName); - await cfn.updateTerminationProtection({ - StackName: deployName, - EnableTerminationProtection: terminationProtection, + private async createChangeSet(changeSetName: string, willExecute: boolean) { + await this.cleanupOldChangeset(changeSetName); + + debug(`Attempting to create ChangeSet with name ${changeSetName} to ${this.verb} stack ${this.stackName}`); + print('%s: creating CloudFormation changeset...', chalk.bold(this.stackName)); + const changeSet = await this.cfn.createChangeSet({ + StackName: this.stackName, + ChangeSetName: changeSetName, + ChangeSetType: this.options.resourcesToImport ? 'IMPORT' : this.update ? 'UPDATE' : 'CREATE', + ResourcesToImport: this.options.resourcesToImport, + Description: `CDK Changeset for execution ${this.uuid}`, + ClientToken: `create${this.uuid}`, + ...this.commonPrepareOptions(), }).promise(); - debug('Termination protection updated to %s for stack %s', terminationProtection, deployName); + + debug('Initiated creation of changeset: %s; waiting for it to finish creating...', changeSet.Id); + // Fetching all pages if we'll execute, so we can have the correct change count when monitoring. + return waitForChangeSet(this.cfn, this.stackName, changeSetName, { fetchAll: willExecute }); + } + + private async executeChangeSet(changeSet: CloudFormation.DescribeChangeSetOutput): Promise { + debug('Initiating execution of changeset %s on stack %s', changeSet.ChangeSetId, this.stackName); + + await this.cfn.executeChangeSet({ + StackName: this.stackName, + ChangeSetName: changeSet.ChangeSetName!, + ClientRequestToken: `exec${this.uuid}`, + ...this.commonExecuteOptions(), + }).promise(); + + debug('Execution of changeset %s on stack %s has started; waiting for the update to complete...', changeSet.ChangeSetId, this.stackName); + + // +1 for the extra event emitted from updates. + const changeSetLength: number = (changeSet.Changes ?? []).length + (this.update ? 1 : 0); + return this.monitorDeployment(changeSet.CreationTime!, changeSetLength); + } + + private async cleanupOldChangeset(changeSetName: string) { + if (this.cloudFormationStack.exists) { + // Delete any existing change sets generated by CDK since change set names must be unique. + // The delete request is successful as long as the stack exists (even if the change set does not exist). + debug(`Removing existing change set with name ${changeSetName} if it exists`); + await this.cfn.deleteChangeSet({ StackName: this.stackName, ChangeSetName: changeSetName }).promise(); + } } - if (changeSetHasNoChanges(changeSetDescription)) { - debug('No changes are to be performed on %s.', deployName); - if (options.execute) { - debug('Deleting empty change set %s', changeSet.Id); - await cfn.deleteChangeSet({ StackName: deployName, ChangeSetName: changeSetName }).promise(); + private async updateTerminationProtection() { + // Update termination protection only if it has changed. + const terminationProtection = this.stackArtifact.terminationProtection ?? false; + if (!!this.cloudFormationStack.terminationProtection !== terminationProtection) { + debug('Updating termination protection from %s to %s for stack %s', this.cloudFormationStack.terminationProtection, terminationProtection, this.stackName); + await this.cfn.updateTerminationProtection({ + StackName: this.stackName, + EnableTerminationProtection: terminationProtection, + }).promise(); + debug('Termination protection updated to %s for stack %s', terminationProtection, this.stackName); } - return { noOp: true, outputs: cloudFormationStack.outputs, stackArn: changeSet.StackId! }; } - if (execute) { - debug('Initiating execution of changeset %s on stack %s', changeSet.Id, deployName); + private async directDeployment(): Promise { + print('%s: %s stack...', chalk.bold(this.stackName), this.update ? 'updating' : 'creating'); - const shouldDisableRollback = options.rollback === false; - // Do a bit of contortions to only pass the `DisableRollback` flag if it's true. That way, - // CloudFormation won't balk at the unrecognized option in regions where the feature is not available yet. - const disableRollback = shouldDisableRollback ? { DisableRollback: true } : undefined; + const startTime = new Date(); - await cfn.executeChangeSet({ StackName: deployName, ChangeSetName: changeSetName, ...disableRollback }).promise(); + if (this.update) { + await this.cfn.updateStack({ + StackName: this.stackName, + ClientRequestToken: `update${this.uuid}`, + ...this.commonPrepareOptions(), + ...this.commonExecuteOptions(), + }).promise(); - // eslint-disable-next-line max-len - const changeSetLength: number = (changeSetDescription.Changes ?? []).length; - const monitor = options.quiet ? undefined : StackActivityMonitor.withDefaultPrinter(cfn, deployName, stackArtifact, { - // +1 for the extra event emitted from updates. - resourcesTotal: cloudFormationStack.exists ? changeSetLength + 1 : changeSetLength, - progress: options.progress, - changeSetCreationTime: changeSetDescription.CreationTime, - ci: options.ci, + const ret = await this.monitorDeployment(startTime, undefined); + await this.updateTerminationProtection(); + return ret; + } else { + // Take advantage of the fact that we can set termination protection during create + const terminationProtection = this.stackArtifact.terminationProtection ?? false; + + await this.cfn.createStack({ + StackName: this.stackName, + ClientRequestToken: `create${this.uuid}`, + ...terminationProtection ? { EnableTerminationProtection: true } : undefined, + ...this.commonPrepareOptions(), + ...this.commonExecuteOptions(), + }).promise(); + + return this.monitorDeployment(startTime, undefined); + } + } + + private async monitorDeployment(startTime: Date, expectedChanges: number | undefined): Promise { + const monitor = this.options.quiet ? undefined : StackActivityMonitor.withDefaultPrinter(this.cfn, this.stackName, this.stackArtifact, { + resourcesTotal: expectedChanges, + progress: this.options.progress, + changeSetCreationTime: startTime, + ci: this.options.ci, }).start(); - debug('Execution of changeset %s on stack %s has started; waiting for the update to complete...', changeSet.Id, deployName); + + let finalState = this.cloudFormationStack; try { - const finalStack = await waitForStackDeploy(cfn, deployName); + const successStack = await waitForStackDeploy(this.cfn, this.stackName); // This shouldn't really happen, but catch it anyway. You never know. - if (!finalStack) { throw new Error('Stack deploy failed (the stack disappeared while we were deploying it)'); } - cloudFormationStack = finalStack; + if (!successStack) { throw new Error('Stack deploy failed (the stack disappeared while we were deploying it)'); } + finalState = successStack; } catch (e) { throw new Error(suffixWithErrors(e.message, monitor?.errors)); } finally { await monitor?.stop(); } - debug('Stack %s has completed updating', deployName); - } else { - print('Changeset %s created and waiting in review for manual execution (--no-execute)', changeSet.Id); + debug('Stack %s has completed updating', this.stackName); + return { noOp: false, outputs: finalState.outputs, stackArn: finalState.stackId }; } - return { noOp: false, outputs: cloudFormationStack.outputs, stackArn: changeSet.StackId! }; + /** + * Return the options that are shared between CreateStack, UpdateStack and CreateChangeSet + */ + private commonPrepareOptions(): Partial> { + return { + Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'], + NotificationARNs: this.options.notificationArns, + Parameters: this.stackParams.apiParameters, + RoleARN: this.options.roleArn, + TemplateBody: this.bodyParameter.TemplateBody, + TemplateURL: this.bodyParameter.TemplateURL, + Tags: this.options.tags, + }; + } + + /** + * Return the options that are shared between UpdateStack and CreateChangeSet + * + * Be careful not to add in keys for options that aren't used, as the features may not have been + * deployed everywhere yet. + */ + private commonExecuteOptions(): Partial> { + const shouldDisableRollback = this.options.rollback === false; + + return { + StackName: this.stackName, + ...shouldDisableRollback ? { DisableRollback: true } : undefined, + }; + } } /** @@ -550,7 +687,7 @@ async function canSkipDeploy( } // Creating changeset only (default true), never skip - if (deployStackOptions.execute === false) { + if (deployStackOptions.deploymentMethod?.method === 'change-set' && deployStackOptions.deploymentMethod.execute === false) { debug(`${deployName}: --no-execute, always creating change set`); return false; } diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index 884e39bf001c0..77f718746e1be 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -6,6 +6,7 @@ import * as chokidar from 'chokidar'; import * as fs from 'fs-extra'; import * as promptly from 'promptly'; import { environmentsFromDescriptors, globEnvironmentsFromStacks, looksLikeGlob } from '../lib/api/cxapp/environments'; +import { DeploymentMethod } from './api'; import { SdkProvider } from './api/aws-auth'; import { Bootstrapper, BootstrapEnvironmentOptions } from './api/bootstrap'; import { CloudFormationDeployments } from './api/cloudformation-deployments'; @@ -247,6 +248,7 @@ export class CdkToolkit { tags, execute: options.execute, changeSetName: options.changeSetName, + deploymentMethod: options.deploymentMethod, force: options.force, parameters: Object.assign({}, parameterMap['*'], parameterMap[stack.stackName]), usePreviousParameters: options.usePreviousParameters, @@ -462,8 +464,11 @@ export class CdkToolkit { roleArn: options.roleArn, toolkitStackName: options.toolkitStackName, tags, - execute: options.execute, - changeSetName: options.changeSetName, + deploymentMethod: { + method: 'change-set', + changeSetName: options.changeSetName, + execute: options.execute, + }, usePreviousParameters: true, progress: options.progress, rollback: options.rollback, @@ -860,16 +865,25 @@ interface CfnDeployOptions { /** * Optional name to use for the CloudFormation change set. * If not provided, a name will be generated automatically. + * + * @deprecated Use 'deploymentMethod' instead */ changeSetName?: string; /** * Whether to execute the ChangeSet * Not providing `execute` parameter will result in execution of ChangeSet + * * @default true + * @deprecated Use 'deploymentMethod' instead */ execute?: boolean; + /** + * Deployment method + */ + readonly deploymentMethod?: DeploymentMethod; + /** * Display mode for stack deployment progress. * diff --git a/packages/aws-cdk/lib/cli.ts b/packages/aws-cdk/lib/cli.ts index b9cf877c7873b..5716a29ed5e64 100644 --- a/packages/aws-cdk/lib/cli.ts +++ b/packages/aws-cdk/lib/cli.ts @@ -23,6 +23,7 @@ import { data, debug, error, print, setLogLevel, setCI } from '../lib/logging'; import { displayNotices, refreshNotices } from '../lib/notices'; import { Command, Configuration, Settings } from '../lib/settings'; import * as version from '../lib/version'; +import { DeploymentMethod } from './api'; // https://github.com/yargs/yargs/issues/1929 // https://github.com/evanw/esbuild/issues/1492 @@ -112,8 +113,15 @@ async function parseCommandLineArguments() { .option('notification-arns', { type: 'array', desc: 'ARNs of SNS topics that CloudFormation will notify with stack related events', nargs: 1, requiresArg: true }) // @deprecated(v2) -- tags are part of the Cloud Assembly and tags specified here will be overwritten on the next deployment .option('tags', { type: 'array', alias: 't', desc: 'Tags to add to the stack (KEY=VALUE), overrides tags from Cloud Assembly (deprecated)', nargs: 1, requiresArg: true }) - .option('execute', { type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)', default: true }) - .option('change-set-name', { type: 'string', desc: 'Name of the CloudFormation change set to create' }) + .option('execute', { type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet) (deprecated)', deprecated: true }) + .option('change-set-name', { type: 'string', desc: 'Name of the CloudFormation change set to create (only if method is not direct)' }) + .options('method', { + alias: 'm', + type: 'string', + choices: ['direct', 'change-set', 'prepare-change-set'], + requiresArg: true, + desc: 'How to perform the deployment. Direct is a bit faster but lacks progress information', + }) .option('force', { alias: 'f', type: 'boolean', desc: 'Always deploy stack even if templates are identical', default: false }) .option('parameters', { type: 'array', desc: 'Additional parameters passed to CloudFormation at deploy time (STACK:KEY=VALUE)', nargs: 1, requiresArg: true, default: {} }) .option('outputs-file', { type: 'string', alias: 'O', desc: 'Path to file where stack outputs will be written as JSON', requiresArg: true }) @@ -461,6 +469,30 @@ async function initCommandLine() { parameterMap[keyValue[0]] = keyValue.slice(1).join('='); } } + + if (args.execute !== undefined && args.method !== undefined) { + throw new Error('Can not supply both --[no-]execute and --method at the same time'); + } + + let deploymentMethod: DeploymentMethod | undefined; + switch (args.method) { + case 'direct': + if (args.changeSetName) { + throw new Error('--change-set-name cannot be used with method=direct'); + } + deploymentMethod = { method: 'direct' }; + break; + case 'change-set': + deploymentMethod = { method: 'change-set', execute: true, changeSetName: args.changeSetName }; + break; + case 'prepare-change-set': + deploymentMethod = { method: 'change-set', execute: false, changeSetName: args.changeSetName }; + break; + case undefined: + deploymentMethod = { method: 'change-set', execute: args.execute ?? true, changeSetName: args.changeSetName }; + break; + } + return cli.deploy({ selector, exclusively: args.exclusively, @@ -470,8 +502,7 @@ async function initCommandLine() { requireApproval: configuration.settings.get(['requireApproval']), reuseAssets: args['build-exclude'], tags: configuration.settings.get(['tags']), - execute: args.execute, - changeSetName: args.changeSetName, + deploymentMethod, force: args.force, parameters: parameterMap, usePreviousParameters: args['previous-parameters'], diff --git a/packages/aws-cdk/lib/logging.ts b/packages/aws-cdk/lib/logging.ts index 8e5a611e37231..240fe7b256fdd 100644 --- a/packages/aws-cdk/lib/logging.ts +++ b/packages/aws-cdk/lib/logging.ts @@ -7,16 +7,28 @@ const { stdout, stderr } = process; type WritableFactory = () => Writable; -const logger = (stream: Writable | WritableFactory, styles?: StyleFn[]) => (fmt: string, ...args: any[]) => { - let str = util.format(fmt, ...args); +const logger = (stream: Writable | WritableFactory, styles?: StyleFn[], timestamp?: boolean) => (fmt: string, ...args: any[]) => { + const ts = timestamp ? `[${formatTime(new Date())}] ` : ''; + + let str = ts + util.format(fmt, ...args); if (styles && styles.length) { str = styles.reduce((a, style) => style(a), str); } + const realStream = typeof stream === 'function' ? stream() : stream; realStream.write(str + '\n'); }; +function formatTime(d: Date) { + return `${lpad(d.getHours(), 2)}:${lpad(d.getMinutes(), 2)}:${lpad(d.getSeconds(), 2)}`; + + function lpad(x: any, w: number) { + const s = `${x}`; + return '0'.repeat(Math.max(w - s.length, 0)) + s; + } +} + export enum LogLevel { /** Not verbose at all */ DEFAULT = 0, @@ -43,7 +55,7 @@ export function increaseVerbosity() { } const stream = () => CI ? stdout : stderr; -const _debug = logger(stream, [chalk.gray]); +const _debug = logger(stream, [chalk.gray], true); export const trace = (fmt: string, ...args: any) => logLevel >= LogLevel.TRACE && _debug(fmt, ...args); export const debug = (fmt: string, ...args: any[]) => logLevel >= LogLevel.DEBUG && _debug(fmt, ...args); diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 2865fd762751d..dcd2f52dd01df 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -97,7 +97,7 @@ "@aws-cdk/region-info": "0.0.0", "@jsii/check-node": "1.68.0", "archiver": "^5.3.1", - "aws-sdk": "^2.1093.0", + "aws-sdk": "^2.1211.0", "camelcase": "^6.3.0", "cdk-assets": "0.0.0", "chokidar": "^3.5.3", diff --git a/packages/aws-cdk/test/api/deploy-stack.test.ts b/packages/aws-cdk/test/api/deploy-stack.test.ts index a8642e3d598a4..2ff4d691983be 100644 --- a/packages/aws-cdk/test/api/deploy-stack.test.ts +++ b/packages/aws-cdk/test/api/deploy-stack.test.ts @@ -58,6 +58,8 @@ beforeEach(() => { })), createChangeSet: jest.fn((_o) => ({})), deleteChangeSet: jest.fn((_o) => ({})), + updateStack: jest.fn((_o) => ({})), + createStack: jest.fn((_o) => ({})), describeChangeSet: jest.fn((_o) => ({ Status: 'CREATE_COMPLETE', Changes: [], @@ -97,6 +99,31 @@ test("calls tryHotswapDeployment() if 'hotswap' is true", async () => { expect(sdk.appendCustomUserAgent).toHaveBeenCalledWith('cdk-hotswap/fallback'); }); +test('call CreateStack when method=direct and the stack doesnt exist yet', async () => { + // WHEN + await deployStack({ + ...standardDeployStackArguments(), + deploymentMethod: { method: 'direct' }, + }); + + // THEN + expect(cfnMocks.createStack).toHaveBeenCalled(); +}); + +test('call UpdateStack when method=direct and the stack exists already', async () => { + // WHEN + givenStackExists(); + + await deployStack({ + ...standardDeployStackArguments(), + deploymentMethod: { method: 'direct' }, + force: true, + }); + + // THEN + expect(cfnMocks.updateStack).toHaveBeenCalled(); +}); + test("does not call tryHotswapDeployment() if 'hotswap' is false", async () => { // WHEN await deployStack({ @@ -539,7 +566,7 @@ test('not executed and no error if --no-execute is given', async () => { // WHEN await deployStack({ ...standardDeployStackArguments(), - execute: false, + deploymentMethod: { method: 'change-set', execute: false }, }); // THEN @@ -558,7 +585,7 @@ test('empty change set is deleted if --execute is given', async () => { // WHEN await deployStack({ ...standardDeployStackArguments(), - execute: true, + deploymentMethod: { method: 'change-set', execute: true }, force: true, // Necessary to bypass "skip deploy" }); @@ -582,7 +609,7 @@ test('empty change set is not deleted if --no-execute is given', async () => { // WHEN await deployStack({ ...standardDeployStackArguments(), - execute: false, + deploymentMethod: { method: 'change-set', execute: false }, }); // THEN @@ -644,7 +671,7 @@ test('changeset is created when stack exists in REVIEW_IN_PROGRESS status', asyn // WHEN await deployStack({ ...standardDeployStackArguments(), - execute: false, + deploymentMethod: { method: 'change-set', execute: false }, }); // THEN @@ -669,7 +696,7 @@ test('changeset is updated when stack exists in CREATE_COMPLETE status', async ( // WHEN await deployStack({ ...standardDeployStackArguments(), - execute: false, + deploymentMethod: { method: 'change-set', execute: false }, }); // THEN diff --git a/packages/aws-cdk/test/integ/cli/cli.integtest.ts b/packages/aws-cdk/test/integ/cli/cli.integtest.ts index 89477861966d1..bac932e23ea4b 100644 --- a/packages/aws-cdk/test/integ/cli/cli.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/cli.integtest.ts @@ -206,6 +206,19 @@ integTest('deploy', withDefaultFixture(async (fixture) => { expect(response.StackResources?.length).toEqual(2); })); +integTest('deploy --method=direct', withDefaultFixture(async (fixture) => { + const stackArn = await fixture.cdkDeploy('test-2', { + options: ['--method=direct'], + captureStderr: false, + }); + + // verify the number of resources in the stack + const response = await fixture.aws.cloudFormation('describeStackResources', { + StackName: stackArn, + }); + expect(response.StackResources?.length).toBeGreaterThan(0); +})); + integTest('deploy all', withDefaultFixture(async (fixture) => { const arns = await fixture.cdkDeploy('test-*', { captureStderr: false }); diff --git a/packages/cdk-assets/README.md b/packages/cdk-assets/README.md index 60379e343a330..1635efa339bbe 100644 --- a/packages/cdk-assets/README.md +++ b/packages/cdk-assets/README.md @@ -186,3 +186,10 @@ If the credentials file is present, `docker` will be configured to use the `docker-credential-cdk-assets` credential helper for each of the domains listed in the file. This helper will assume the role provided (if present), and then fetch the login credentials from either SecretsManager or ECR. + +## Using Drop-in Docker Replacements + +By default, the AWS CDK will build and publish Docker image assets using the +`docker` command. However, by specifying the `CDK_DOCKER` environment variable, +you can override the command that will be used to build and publish your +assets. diff --git a/packages/cdk-assets/lib/private/docker.ts b/packages/cdk-assets/lib/private/docker.ts index 999d44de7fb4c..6c0a302c19eb2 100644 --- a/packages/cdk-assets/lib/private/docker.ts +++ b/packages/cdk-assets/lib/private/docker.ts @@ -131,7 +131,7 @@ export class Docker { const pathToCdkAssets = path.resolve(__dirname, '..', '..', 'bin'); try { - await shell(['docker', ...configArgs, ...args], { + await shell([getDockerCmd(), ...configArgs, ...args], { logger: this.logger, ...options, env: { @@ -208,6 +208,10 @@ export class DockerFactory { } } +function getDockerCmd(): string { + return process.env.CDK_DOCKER ?? 'docker'; +} + function flatten(x: string[][]) { return Array.prototype.concat([], ...x); } diff --git a/packages/cdk-assets/package.json b/packages/cdk-assets/package.json index b05136e5d93c6..77dd3bd010629 100644 --- a/packages/cdk-assets/package.json +++ b/packages/cdk-assets/package.json @@ -47,7 +47,7 @@ "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "archiver": "^5.3.1", - "aws-sdk": "^2.1093.0", + "aws-sdk": "^2.1211.0", "glob": "^7.2.3", "mime": "^2.6.0", "yargs": "^16.2.0" diff --git a/packages/cdk-assets/test/docker-images.test.ts b/packages/cdk-assets/test/docker-images.test.ts index 19662fff8f055..1b0d896743f86 100644 --- a/packages/cdk-assets/test/docker-images.test.ts +++ b/packages/cdk-assets/test/docker-images.test.ts @@ -13,6 +13,7 @@ let aws: ReturnType; const absoluteDockerPath = '/simple/cdk.out/dockerdir'; beforeEach(() => { jest.resetAllMocks(); + delete(process.env.CDK_DOCKER); // By default, assume no externally-configured credentials. jest.spyOn(dockercreds, 'cdkCredentialsConfig').mockReturnValue(undefined); @@ -497,3 +498,28 @@ test('publishing only', async () => { expectAllSpawns(); expect(true).toBeTruthy(); // Expect no exception, satisfy linter }); + +test('overriding the docker command', async () => { + process.env.CDK_DOCKER = 'custom'; + + const pub = new AssetPublishing(AssetManifest.fromPath('/simple/cdk.out'), { aws, throwOnError: false }); + + aws.mockEcr.describeImages = mockedApiFailure('ImageNotFoundException', 'File does not exist'); + aws.mockEcr.getAuthorizationToken = mockedApiResult({ + authorizationData: [ + { authorizationToken: 'dXNlcjpwYXNz', proxyEndpoint: 'https://proxy.com/' }, + ], + }); + + const expectAllSpawns = mockSpawn( + { commandLine: ['custom', 'login', '--username', 'user', '--password-stdin', 'https://proxy.com/'] }, + { commandLine: ['custom', 'inspect', 'cdkasset-theasset'] }, + { commandLine: ['custom', 'tag', 'cdkasset-theasset', '12345.amazonaws.com/repo:abcdef'] }, + { commandLine: ['custom', 'push', '12345.amazonaws.com/repo:abcdef'] }, + ); + + await pub.publish(); + + expectAllSpawns(); + expect(true).toBeTruthy(); // Expect no exception, satisfy linter +}); diff --git a/tools/@aws-cdk/node-bundle/package.json b/tools/@aws-cdk/node-bundle/package.json index a649e13419ca5..4596f19173688 100644 --- a/tools/@aws-cdk/node-bundle/package.json +++ b/tools/@aws-cdk/node-bundle/package.json @@ -40,13 +40,13 @@ "jest-junit": "^13", "json-schema": "^0.4.0", "npm-check-updates": "^12", - "projen": "^0.62.6", + "projen": "^0.62.13", "standard-version": "^9", "ts-jest": "^27", "typescript": "^4.5.5" }, "dependencies": { - "esbuild": "^0.15.7", + "esbuild": "^0.15.8", "fs-extra": "^10.1.0", "license-checker": "^25.0.1", "madge": "^5.0.1", diff --git a/tools/@aws-cdk/prlint/index.ts b/tools/@aws-cdk/prlint/index.ts index 3519d02d2643f..46b41908a7a4b 100644 --- a/tools/@aws-cdk/prlint/index.ts +++ b/tools/@aws-cdk/prlint/index.ts @@ -1,10 +1,11 @@ import * as core from '@actions/core'; import * as github from '@actions/github'; +import { Octokit } from '@octokit/rest'; import * as linter from './lint'; async function run() { const token: string = process.env.GITHUB_TOKEN!; - const client = github.getOctokit(token).rest.pulls; + const client = new Octokit({ auth: token }); const prLinter = new linter.PullRequestLinter({ client, diff --git a/tools/@aws-cdk/prlint/lint.ts b/tools/@aws-cdk/prlint/lint.ts index 37c124235030c..425be04d6f8e4 100644 --- a/tools/@aws-cdk/prlint/lint.ts +++ b/tools/@aws-cdk/prlint/lint.ts @@ -1,4 +1,5 @@ import * as path from 'path'; +import { Octokit } from '@octokit/rest'; import { breakingModules } from './parser'; import { findModulePath, moduleStability } from './module'; @@ -9,7 +10,23 @@ enum Exemption { README = 'pr-linter/exempt-readme', TEST = 'pr-linter/exempt-test', INTEG_TEST = 'pr-linter/exempt-integ-test', - BREAKING_CHANGE = 'pr-linter/exempt-breaking-change' + BREAKING_CHANGE = 'pr-linter/exempt-breaking-change', + CLI_INTEG_TESTED = 'pr-linter/cli-integ-tested', +} + +export interface GitHubPr { + readonly number: number; + readonly title: string; + readonly body: string | null; + readonly labels: GitHubLabel[]; +} + +export interface GitHubLabel { + readonly name: string; +} + +export interface GitHubFile { + readonly filename: string; } class LinterError extends Error { @@ -25,6 +42,15 @@ class LinterError extends Error { * Some tests may return multiple failures. */ class TestResult { + /** + * Create a test result from a potential failure + */ + public static fromFailure(failureCondition: boolean, failureMessage: string): TestResult { + const ret = new TestResult(); + ret.assessFailure(failureCondition, failureMessage); + return ret; + } + public errorMessages: string[] = []; /** @@ -44,7 +70,7 @@ class TestResult { * Represents a single test. */ interface Test { - test: (pr: any, files: any[]) => TestResult; + test: (pr: GitHubPr, files: GitHubFile[]) => TestResult; } /** @@ -55,7 +81,7 @@ interface ValidateRuleSetOptions { /** * The function to test for exemption from the rules in testRuleSet. */ - exemption?: (pr: any) => boolean; + exemption?: (pr: GitHubPr) => boolean; /** * The log message printed if the exemption is granted. @@ -75,7 +101,7 @@ interface ValidateRuleSetOptions { class ValidationCollector { public errors: string[] = []; - constructor(private pr: any, private files: string[]) { } + constructor(private pr: GitHubPr, private files: GitHubFile[]) { } /** * Checks for exemption criteria and then validates against the ruleset when not exempt to it. @@ -107,7 +133,7 @@ export interface PullRequestLinterProps { /** * GitHub client scoped to pull requests. Imported via @actions/github. */ - readonly client: any; + readonly client: Octokit; /** * Repository owner. @@ -130,7 +156,7 @@ export interface PullRequestLinterProps { * in the body of the review, and dismiss any previous reviews upon changes to the pull request. */ export class PullRequestLinter { - private readonly client: any; + private readonly client: Octokit; private readonly prParams: { owner: string, repo: string, pull_number: number }; @@ -143,10 +169,10 @@ export class PullRequestLinter { * Dismisses previous reviews by aws-cdk-automation when changes have been made to the pull request. */ private async dismissPreviousPRLinterReviews(): Promise { - const reviews = await this.client.listReviews(this.prParams); + const reviews = await this.client.pulls.listReviews(this.prParams); reviews.data.forEach(async (review: any) => { - if (review.user?.login === 'github-actions[bot]' && review.state !== 'DISMISSED') { - await this.client.dismissReview({ + if (review.user?.login === 'aws-cdk-automation' && review.state !== 'DISMISSED') { + await this.client.pulls.dismissReview({ ...this.prParams, review_id: review.id, message: 'Pull Request updated. Dissmissing previous PRLinter Review.', @@ -161,7 +187,7 @@ export class PullRequestLinter { */ private async communicateResult(failureReasons: string[]): Promise { const body = `The Pull Request Linter fails with the following errors:${this.formatErrors(failureReasons)}PRs must pass status checks before we can provide a meaningful review.`; - await this.client.createReview({ ...this.prParams, body, event: 'REQUEST_CHANGES', }); + await this.client.pulls.createReview({ ...this.prParams, body, event: 'REQUEST_CHANGES', }); throw new LinterError(body); } @@ -173,10 +199,10 @@ export class PullRequestLinter { const number = this.props.number; console.log(`⌛ Fetching PR number ${number}`); - const pr = (await this.client.get(this.prParams)).data; + const pr = (await this.client.pulls.get(this.prParams)).data; console.log(`⌛ Fetching files for PR number ${number}`); - const files = (await this.client.listFiles(this.prParams)).data; + const files = (await this.client.pulls.listFiles(this.prParams)).data; console.log("⌛ Validating..."); @@ -214,6 +240,11 @@ export class PullRequestLinter { testRuleSet: [ { test: assertStability } ] }); + validationCollector.validateRuleSet({ + exemption: (pr) => hasLabel(pr, Exemption.CLI_INTEG_TESTED), + testRuleSet: [ { test: noCliChanges } ], + }); + await this.dismissPreviousPRLinterReviews(); validationCollector.isValid() ? console.log("✅ Success") : await this.communicateResult(validationCollector.errors); } @@ -221,85 +252,87 @@ export class PullRequestLinter { private formatErrors(errors: string[]) { return `\n\n\t❌ ${errors.join('\n\t❌ ')}\n\n`; }; + } -function isPkgCfnspec(pr: any): boolean { +function isPkgCfnspec(pr: GitHubPr): boolean { return pr.title.indexOf("(cfnspec)") > -1; } -function isFeature(pr: any): boolean { +function isFeature(pr: GitHubPr): boolean { return pr.title.startsWith("feat") } -function isFix(pr: any): boolean { +function isFix(pr: GitHubPr): boolean { return pr.title.startsWith("fix") } -function testChanged(files: any[]): boolean { +function testChanged(files: GitHubFile[]): boolean { return files.filter(f => f.filename.toLowerCase().includes("test")).length != 0; } -function integTestChanged(files: any[]): boolean { +function integTestChanged(files: GitHubFile[]): boolean { return files.filter(f => f.filename.toLowerCase().match(/integ.*.ts$/)).length != 0; } -function integTestSnapshotChanged(files: any[]): boolean { +function integTestSnapshotChanged(files: GitHubFile[]): boolean { return files.filter(f => f.filename.toLowerCase().includes("integ.snapshot")).length != 0; } -function readmeChanged(files: any[]): boolean { +function readmeChanged(files: GitHubFile[]): boolean { return files.filter(f => path.basename(f.filename) == "README.md").length != 0; } -function featureContainsReadme(pr: any, files: any[]): TestResult { +function featureContainsReadme(pr: GitHubPr, files: GitHubFile[]): TestResult { const result = new TestResult(); result.assessFailure(isFeature(pr) && !readmeChanged(files) && !isPkgCfnspec(pr), 'Features must contain a change to a README file.'); return result; }; -function featureContainsTest(pr: any, files: any[]): TestResult { +function featureContainsTest(pr: GitHubPr, files: GitHubFile[]): TestResult { const result = new TestResult(); result.assessFailure(isFeature(pr) && !testChanged(files), 'Features must contain a change to a test file.'); return result; }; -function fixContainsTest(pr: any, files: any[]): TestResult { +function fixContainsTest(pr: GitHubPr, files: GitHubFile[]): TestResult { const result = new TestResult(); result.assessFailure(isFix(pr) && !testChanged(files), 'Fixes must contain a change to a test file.'); return result; }; -function featureContainsIntegTest(pr: any, files: any[]): TestResult { +function featureContainsIntegTest(pr: GitHubPr, files: GitHubFile[]): TestResult { const result = new TestResult(); result.assessFailure(isFeature(pr) && (!integTestChanged(files) || !integTestSnapshotChanged(files)), 'Features must contain a change to an integration test file and the resulting snapshot.'); return result; }; -function fixContainsIntegTest(pr: any, files: any[]): TestResult { +function fixContainsIntegTest(pr: GitHubPr, files: GitHubFile[]): TestResult { const result = new TestResult(); result.assessFailure(isFix(pr) && (!integTestChanged(files) || !integTestSnapshotChanged(files)), 'Fixes must contain a change to an integration test file and the resulting snapshot.'); return result; }; -function shouldExemptReadme(pr: any): boolean { + +function shouldExemptReadme(pr: GitHubPr): boolean { return hasLabel(pr, Exemption.README); }; -function shouldExemptTest(pr: any): boolean { +function shouldExemptTest(pr: GitHubPr): boolean { return hasLabel(pr, Exemption.TEST); }; -function shouldExemptIntegTest(pr: any): boolean { +function shouldExemptIntegTest(pr: GitHubPr): boolean { return hasLabel(pr, Exemption.INTEG_TEST); }; -function shouldExemptBreakingChange(pr: any): boolean { +function shouldExemptBreakingChange(pr: GitHubPr): boolean { return hasLabel(pr, Exemption.BREAKING_CHANGE); }; -function hasLabel(pr: any, labelName: string): boolean { +function hasLabel(pr: GitHubPr, labelName: string): boolean { return pr.labels.some(function (l: any) { return l.name === labelName; }) @@ -312,12 +345,12 @@ function hasLabel(pr: any, labelName: string): boolean { * to be said note, but got misspelled as "BREAKING CHANGES:" or * "BREAKING CHANGES(module):" */ - function validateBreakingChangeFormat(pr: any, _files: any[]): TestResult { + function validateBreakingChangeFormat(pr: GitHubPr, _files: GitHubFile[]): TestResult { const title = pr.title; const body = pr.body; const result = new TestResult(); const re = /^BREAKING.*$/m; - const m = re.exec(body); + const m = re.exec(body ?? ''); if (m) { result.assessFailure(!m[0].startsWith('BREAKING CHANGE: '), `Breaking changes should be indicated by starting a line with 'BREAKING CHANGE: ', variations are not allowed. (found: '${m[0]}').`); result.assessFailure(m[0].slice('BREAKING CHANGE:'.length).trim().length === 0, `The description of the first breaking change should immediately follow the 'BREAKING CHANGE: ' clause.`); @@ -330,25 +363,35 @@ function hasLabel(pr: any, labelName: string): boolean { /** * Check that the PR title has the correct prefix. */ - function validateTitlePrefix(title: string): TestResult { + function validateTitlePrefix(pr: GitHubPr): TestResult { const result = new TestResult(); - const titleRe = /^(feat|fix|build|chore|ci|docs|style|refactor|perf|test)(\([\w-]+\)){0,1}: /; - const m = titleRe.exec(title); - if (m) { - result.assessFailure(m[0] !== undefined, "The title of this pull request must specify the module name that the first breaking change should be associated to."); - } + const titleRe = /^(feat|fix|build|chore|ci|docs|style|refactor|perf|test)(\([\w_-]+\))?: /; + const m = titleRe.exec(pr.title); + result.assessFailure( + !m, + "The title of this pull request does not follow the Conventional Commits format, see https://www.conventionalcommits.org/."); return result; }; -function assertStability(pr: any, _files: any[]): TestResult { +function assertStability(pr: GitHubPr, _files: GitHubFile[]): TestResult { const title = pr.title; const body = pr.body; const result = new TestResult(); - const breakingStable = breakingModules(title, body).filter(mod => 'stable' === moduleStability(findModulePath(mod))); + const breakingStable = breakingModules(title, body ?? '').filter(mod => 'stable' === moduleStability(findModulePath(mod))); result.assessFailure(breakingStable.length > 0, `Breaking changes in stable modules [${breakingStable.join(', ')}] is disallowed.`); return result; }; +function noCliChanges(pr: GitHubPr, files: GitHubFile[]): TestResult { + const branch = `pull/${pr.number}/head`; + + const cliCodeChanged = files.some(f => f.filename.toLowerCase().includes('packages/aws-cdk/lib/') && f.filename.endsWith('.ts')); + + return TestResult.fromFailure( + cliCodeChanged, + `CLI code has changed. A maintainer must run the code through the testing pipeline (git fetch origin ${branch} && git push -f origin FETCH_HEAD:test-main-pipeline), then add the '${Exemption.CLI_INTEG_TESTED}' label when the pipeline succeeds.`); +} + require('make-runnable/custom')({ printOutputFrame: false }); diff --git a/tools/@aws-cdk/prlint/test/lint.test.ts b/tools/@aws-cdk/prlint/test/lint.test.ts index 703108868cc77..5cd298eb65a49 100644 --- a/tools/@aws-cdk/prlint/test/lint.test.ts +++ b/tools/@aws-cdk/prlint/test/lint.test.ts @@ -14,6 +14,7 @@ let mockCreateReview: (errorMessage: string) => Promise; describe('breaking changes format', () => { test('disallow variations to "BREAKING CHANGE:"', async () => { const issue = { + number: 1, title: 'chore: some title', body: 'BREAKING CHANGES:', labels: [{ name: 'pr-linter/exempt-test' }, { name: 'pr-linter/exempt-readme' }] @@ -24,6 +25,7 @@ describe('breaking changes format', () => { test('the first breaking change should immediately follow "BREAKING CHANGE:"', async () => { const issue = { + number: 1, title: 'chore(cdk-build-tools): some title', body: `BREAKING CHANGE:\x20 * **module:** another change`, @@ -35,6 +37,7 @@ describe('breaking changes format', () => { test('invalid title', async () => { const issue = { + number: 1, title: 'chore(): some title', body: 'BREAKING CHANGE: this breaking change', labels: [{ name: 'pr-linter/exempt-test' }, { name: 'pr-linter/exempt-readme' }] @@ -45,6 +48,7 @@ describe('breaking changes format', () => { test('valid title', async () => { const issue = { + number: 1, title: 'chore(cdk-build-tools): some title', body: 'BREAKING CHANGE: this breaking change', labels: [{ name: 'pr-linter/exempt-test' }, { name: 'pr-linter/exempt-readme' }] @@ -57,6 +61,7 @@ describe('breaking changes format', () => { describe('ban breaking changes in stable modules', () => { test('breaking change in stable module', async () => { const issue = { + number: 1, title: 'chore(s3): some title', body: 'BREAKING CHANGE: this breaking change', labels: [{ name: 'pr-linter/exempt-test' }, { name: 'pr-linter/exempt-readme' }] @@ -67,6 +72,7 @@ describe('ban breaking changes in stable modules', () => { test('breaking changes multiple in stable modules', async () => { const issue = { + number: 1, title: 'chore(lambda): some title', body: ` BREAKING CHANGE: this breaking change @@ -81,6 +87,7 @@ describe('ban breaking changes in stable modules', () => { test('unless exempt-breaking-change label added', async () => { const issue = { + number: 1, title: 'chore(lambda): some title', body: ` BREAKING CHANGE: this breaking change @@ -94,6 +101,7 @@ describe('ban breaking changes in stable modules', () => { test('with additional "closes" footer', async () => { const issue = { + number: 1, title: 'chore(s3): some title', body: ` description of the commit @@ -112,6 +120,7 @@ describe('ban breaking changes in stable modules', () => { describe('integration tests required on features', () => { test('integ files changed', async () => { const issue = { + number: 1, title: 'feat(s3): some title', body: ` description of the commit @@ -137,6 +146,7 @@ describe('integration tests required on features', () => { test('integ files not changed in feat', async () => { const issue = { + number: 1, title: 'feat(s3): some title', body: ` description of the commit @@ -166,6 +176,7 @@ describe('integration tests required on features', () => { test('integ snapshots not changed in feat', async () => { const issue = { + number: 1, title: 'feat(s3): some title', body: ` description of the commit @@ -195,6 +206,7 @@ describe('integration tests required on features', () => { test('integ files not changed in fix', async () => { const issue = { + number: 1, title: 'fix(s3): some title', body: ` description of the commit @@ -224,6 +236,7 @@ describe('integration tests required on features', () => { test('integ snapshots not changed in fix', async () => { const issue = { + number: 1, title: 'fix(s3): some title', body: ` description of the commit @@ -253,6 +266,7 @@ describe('integration tests required on features', () => { test('integ files not changed, pr exempt', async () => { const issue = { + number: 1, title: 'feat(s3): some title', body: ` description of the commit @@ -275,6 +289,7 @@ describe('integration tests required on features', () => { test('integ files not changed, not a feature', async () => { const issue = { + number: 1, title: 'chore(s3): some title', body: ` description of the commit @@ -288,23 +303,49 @@ describe('integration tests required on features', () => { filename: 'some-test.test.ts' }, { - filename: 'README.md' + filename: 'readme.md' } ]; - const prLinter = configureMock(issue, files); - expect(await prLinter.validate()).resolves; + const prlinter = configureMock(issue, files); + expect(await prlinter.validate()).resolves; + }); + + describe('CLI file changed', () => { + const labels: linter.GitHubLabel[] = []; + const issue = { + number: 23, + title: 'chore(cli): change the help or something', + body: ` + description of the commit + closes #123456789 + `, + labels, + }; + const files = [ { filename: 'packages/aws-cdk/lib/cdk-toolkit.ts' } ]; + + test('no label throws error', async () => { + const prLinter = configureMock(issue, files); + await expect(prLinter.validate()).rejects.toThrow(/CLI code has changed. A maintainer must/); + }); + + test('with label no error', async () => { + labels.push({ name: 'pr-linter/cli-integ-tested' }); + const prLinter = configureMock(issue, files); + await prLinter.validate(); + // THEN: no exception + }); }); }); -function configureMock(issue: any, prFiles: any[] | undefined): linter.PullRequestLinter { - const client = { +function configureMock(pr: linter.GitHubPr, prFiles?: linter.GitHubFile[]): linter.PullRequestLinter { + const pullsClient = { get(_props: { _owner: string, _repo: string, _pull_number: number }) { - return { data: issue }; + return { data: pr }; }, listFiles(_props: { _owner: string, _repo: string, _pull_number: number }) { - return { data: prFiles }; + return { data: prFiles ?? [] }; }, createReview(errorMessage: string) { @@ -314,7 +355,7 @@ function configureMock(issue: any, prFiles: any[] | undefined): linter.PullReque }, listReviews(_props: { _owner: string, _repo: string, _pull_number: number }) { - return { data: [{ id: 1111122222, user: { login: 'github-actions[bot]' }, state: 'CHANGES_REQUESTED' }] }; + return { data: [{ id: 1111122222, user: { login: 'aws-cdk-automation' }, state: 'CHANGES_REQUESTED' }] }; }, dismissReview() {}, @@ -324,6 +365,10 @@ function configureMock(issue: any, prFiles: any[] | undefined): linter.PullReque owner: 'aws', repo: 'aws-cdk', number: 1000, - client + + // hax hax + client: { + pulls: pullsClient as any, + } as any, }) } diff --git a/version.v2.json b/version.v2.json index 7d3475748c006..d48962f88e81c 100644 --- a/version.v2.json +++ b/version.v2.json @@ -1,4 +1,4 @@ { - "version": "2.43.1", - "alphaVersion": "2.43.1-alpha.0" + "version": "2.44.0", + "alphaVersion": "2.44.0-alpha.0" } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 498dd71f123a1..a1aa3e8496b03 100644 --- a/yarn.lock +++ b/yarn.lock @@ -49,25 +49,25 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.0.tgz#2a592fd89bacb1fcde68de31bee4f2f2dacb0e86" - integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw== +"@babel/compat-data@^7.19.1": + version "7.19.1" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.1.tgz#72d647b4ff6a4f82878d184613353af1dd0290f9" + integrity sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg== "@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.7.5", "@babel/core@^7.8.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.19.0.tgz#d2f5f4f2033c00de8096be3c9f45772563e150c3" - integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ== + version "7.19.1" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.19.1.tgz#c8fa615c5e88e272564ace3d42fbc8b17bfeb22b" + integrity sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" "@babel/generator" "^7.19.0" - "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-compilation-targets" "^7.19.1" "@babel/helper-module-transforms" "^7.19.0" "@babel/helpers" "^7.19.0" - "@babel/parser" "^7.19.0" + "@babel/parser" "^7.19.1" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" + "@babel/traverse" "^7.19.1" "@babel/types" "^7.19.0" convert-source-map "^1.7.0" debug "^4.1.0" @@ -84,14 +84,14 @@ "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@babel/helper-compilation-targets@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz#537ec8339d53e806ed422f1e06c8f17d55b96bb0" - integrity sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA== +"@babel/helper-compilation-targets@^7.19.1": + version "7.19.1" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz#7f630911d83b408b76fe584831c98e5395d7a17c" + integrity sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg== dependencies: - "@babel/compat-data" "^7.19.0" + "@babel/compat-data" "^7.19.1" "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.20.2" + browserslist "^4.21.3" semver "^6.3.0" "@babel/helper-environment-visitor@^7.18.9": @@ -160,9 +160,9 @@ integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== "@babel/helper-validator-identifier@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" - integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + version "7.19.1" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== "@babel/helper-validator-option@^7.18.6": version "7.18.6" @@ -187,10 +187,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c" - integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.1": + version "7.19.1" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz#6f6d6c2e621aad19a92544cc217ed13f1aac5b4c" + integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -292,10 +292,10 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/traverse@^7.19.0", "@babel/traverse@^7.7.2": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.0.tgz#eb9c561c7360005c592cc645abafe0c3c4548eed" - integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA== +"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.7.2": + version "7.19.1" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz#0fafe100a8c2a603b4718b1d9bf2568d1d193347" + integrity sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA== dependencies: "@babel/code-frame" "^7.18.6" "@babel/generator" "^7.19.0" @@ -303,7 +303,7 @@ "@babel/helper-function-name" "^7.19.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.19.0" + "@babel/parser" "^7.19.1" "@babel/types" "^7.19.0" debug "^4.1.0" globals "^11.1.0" @@ -334,10 +334,17 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@esbuild/linux-loong64@0.15.7": - version "0.15.7" - resolved "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.7.tgz#1ec4af4a16c554cbd402cc557ccdd874e3f7be53" - integrity sha512-IKznSJOsVUuyt7cDzzSZyqBEcZe+7WlBqTVXiF1OXP/4Nm387ToaXZ0fyLwI1iBlI/bzpxVq411QE2/Bt2XWWw== +"@esbuild/android-arm@0.15.8": + version "0.15.8" + resolved "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.8.tgz#52b094c98e415ec72fab39827c12f2051ac9c550" + integrity sha512-CyEWALmn+no/lbgbAJsbuuhT8s2J19EJGHkeyAwjbFJMrj80KJ9zuYsoAvidPTU7BgBf87r/sgae8Tw0dbOc4Q== + dependencies: + esbuild-wasm "0.15.8" + +"@esbuild/linux-loong64@0.15.8": + version "0.15.8" + resolved "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.8.tgz#d64575fc46bf4eb689352aa9f8a139271b6e1647" + integrity sha512-pE5RQsOTSERCtfZdfCT25wzo7dfhOSlhAXcsZmuvRYhendOv7djcdvtINdnDp2DAjP17WXlBB4nBO6sHLczmsg== "@eslint/eslintrc@^0.4.3": version "0.4.3" @@ -2094,15 +2101,14 @@ tsutils "^3.21.0" "@typescript-eslint/eslint-plugin@^5": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.37.0.tgz#5ccdd5d9004120f28fc6e717fb4b5c9bddcfbc04" - integrity sha512-Fde6W0IafXktz1UlnhGkrrmnnGpAo1kyX7dnyHHVrmwJOn72Oqm3eYtddrpOwwel2W8PAK9F3pIL5S+lfoM0og== + version "5.38.0" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.0.tgz#ac919a199548861012e8c1fb2ec4899ac2bc22ae" + integrity sha512-GgHi/GNuUbTOeoJiEANi0oI6fF3gBQc3bGFYj40nnAPCbhrtEDf2rjBmefFadweBmO1Du1YovHeDP2h5JLhtTQ== dependencies: - "@typescript-eslint/scope-manager" "5.37.0" - "@typescript-eslint/type-utils" "5.37.0" - "@typescript-eslint/utils" "5.37.0" + "@typescript-eslint/scope-manager" "5.38.0" + "@typescript-eslint/type-utils" "5.38.0" + "@typescript-eslint/utils" "5.38.0" debug "^4.3.4" - functional-red-black-tree "^1.0.1" ignore "^5.2.0" regexpp "^3.2.0" semver "^7.3.7" @@ -2131,13 +2137,13 @@ debug "^4.3.1" "@typescript-eslint/parser@^5": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.37.0.tgz#c382077973f3a4ede7453fb14cadcad3970cbf3b" - integrity sha512-01VzI/ipYKuaG5PkE5+qyJ6m02fVALmMPY3Qq5BHflDx3y4VobbLdHQkSMg9VPRS4KdNt4oYTMaomFoHonBGAw== + version "5.38.0" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.38.0.tgz#5a59a1ff41a7b43aacd1bb2db54f6bf1c02b2ff8" + integrity sha512-/F63giJGLDr0ms1Cr8utDAxP2SPiglaD6V+pCOcG35P2jCqdfR7uuEhz1GIC3oy4hkUF8xA1XSXmd9hOh/a5EA== dependencies: - "@typescript-eslint/scope-manager" "5.37.0" - "@typescript-eslint/types" "5.37.0" - "@typescript-eslint/typescript-estree" "5.37.0" + "@typescript-eslint/scope-manager" "5.38.0" + "@typescript-eslint/types" "5.38.0" + "@typescript-eslint/typescript-estree" "5.38.0" debug "^4.3.4" "@typescript-eslint/scope-manager@4.33.0": @@ -2148,21 +2154,21 @@ "@typescript-eslint/types" "4.33.0" "@typescript-eslint/visitor-keys" "4.33.0" -"@typescript-eslint/scope-manager@5.37.0": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.37.0.tgz#044980e4f1516a774a418dafe701a483a6c9f9ca" - integrity sha512-F67MqrmSXGd/eZnujjtkPgBQzgespu/iCZ+54Ok9X5tALb9L2v3G+QBSoWkXG0p3lcTJsL+iXz5eLUEdSiJU9Q== +"@typescript-eslint/scope-manager@5.38.0": + version "5.38.0" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.38.0.tgz#8f0927024b6b24e28671352c93b393a810ab4553" + integrity sha512-ByhHIuNyKD9giwkkLqzezZ9y5bALW8VNY6xXcP+VxoH4JBDKjU5WNnsiD4HJdglHECdV+lyaxhvQjTUbRboiTA== dependencies: - "@typescript-eslint/types" "5.37.0" - "@typescript-eslint/visitor-keys" "5.37.0" + "@typescript-eslint/types" "5.38.0" + "@typescript-eslint/visitor-keys" "5.38.0" -"@typescript-eslint/type-utils@5.37.0": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.37.0.tgz#43ed2f567ada49d7e33a6e4b6f9babd060445fe5" - integrity sha512-BSx/O0Z0SXOF5tY0bNTBcDEKz2Ec20GVYvq/H/XNKiUorUFilH7NPbFUuiiyzWaSdN3PA8JV0OvYx0gH/5aFAQ== +"@typescript-eslint/type-utils@5.38.0": + version "5.38.0" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.38.0.tgz#c8b7f681da825fcfc66ff2b63d70693880496876" + integrity sha512-iZq5USgybUcj/lfnbuelJ0j3K9dbs1I3RICAJY9NZZpDgBYXmuUlYQGzftpQA9wC8cKgtS6DASTvF3HrXwwozA== dependencies: - "@typescript-eslint/typescript-estree" "5.37.0" - "@typescript-eslint/utils" "5.37.0" + "@typescript-eslint/typescript-estree" "5.38.0" + "@typescript-eslint/utils" "5.38.0" debug "^4.3.4" tsutils "^3.21.0" @@ -2171,10 +2177,10 @@ resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== -"@typescript-eslint/types@5.37.0": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.37.0.tgz#09e4870a5f3af7af3f84e08d792644a87d232261" - integrity sha512-3frIJiTa5+tCb2iqR/bf7XwU20lnU05r/sgPJnRpwvfZaqCJBrl8Q/mw9vr3NrNdB/XtVyMA0eppRMMBqdJ1bA== +"@typescript-eslint/types@5.38.0": + version "5.38.0" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.38.0.tgz#8cd15825e4874354e31800dcac321d07548b8a5f" + integrity sha512-HHu4yMjJ7i3Cb+8NUuRCdOGu2VMkfmKyIJsOr9PfkBVYLYrtMCK/Ap50Rpov+iKpxDTfnqvDbuPLgBE5FwUNfA== "@typescript-eslint/typescript-estree@4.33.0", "@typescript-eslint/typescript-estree@^4.33.0": version "4.33.0" @@ -2189,28 +2195,28 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@5.37.0": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.37.0.tgz#956dcf5c98363bcb97bdd5463a0a86072ff79355" - integrity sha512-JkFoFIt/cx59iqEDSgIGnQpCTRv96MQnXCYvJi7QhBC24uyuzbD8wVbajMB1b9x4I0octYFJ3OwjAwNqk1AjDA== +"@typescript-eslint/typescript-estree@5.38.0": + version "5.38.0" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.0.tgz#89f86b2279815c6fb7f57d68cf9b813f0dc25d98" + integrity sha512-6P0RuphkR+UuV7Avv7MU3hFoWaGcrgOdi8eTe1NwhMp2/GjUJoODBTRWzlHpZh6lFOaPmSvgxGlROa0Sg5Zbyg== dependencies: - "@typescript-eslint/types" "5.37.0" - "@typescript-eslint/visitor-keys" "5.37.0" + "@typescript-eslint/types" "5.38.0" + "@typescript-eslint/visitor-keys" "5.38.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.37.0": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.37.0.tgz#7784cb8e91390c4f90ccaffd24a0cf9874df81b2" - integrity sha512-jUEJoQrWbZhmikbcWSMDuUSxEE7ID2W/QCV/uz10WtQqfOuKZUqFGjqLJ+qhDd17rjgp+QJPqTdPIBWwoob2NQ== +"@typescript-eslint/utils@5.38.0": + version "5.38.0" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.38.0.tgz#5b31f4896471818153790700eb02ac869a1543f4" + integrity sha512-6sdeYaBgk9Fh7N2unEXGz+D+som2QCQGPAf1SxrkEr+Z32gMreQ0rparXTNGRRfYUWk/JzbGdcM8NSSd6oqnTA== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.37.0" - "@typescript-eslint/types" "5.37.0" - "@typescript-eslint/typescript-estree" "5.37.0" + "@typescript-eslint/scope-manager" "5.38.0" + "@typescript-eslint/types" "5.38.0" + "@typescript-eslint/typescript-estree" "5.38.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" @@ -2222,12 +2228,12 @@ "@typescript-eslint/types" "4.33.0" eslint-visitor-keys "^2.0.0" -"@typescript-eslint/visitor-keys@5.37.0": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.37.0.tgz#7b72dd343295ea11e89b624995abc7103c554eee" - integrity sha512-Hp7rT4cENBPIzMwrlehLW/28EVCOcE9U1Z1BQTc8EA8v5qpr7GRGuG+U58V5tTY48zvUOA3KHvw3rA8tY9fbdA== +"@typescript-eslint/visitor-keys@5.38.0": + version "5.38.0" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.0.tgz#60591ca3bf78aa12b25002c0993d067c00887e34" + integrity sha512-MxnrdIyArnTi+XyFLR+kt/uNAcdOnmT+879os7qDRI+EYySR4crXJq9BXPfRzzLGq0wgxkwidrCJ9WCAoacm1w== dependencies: - "@typescript-eslint/types" "5.37.0" + "@typescript-eslint/types" "5.38.0" eslint-visitor-keys "^3.3.0" "@xmldom/xmldom@^0.8.2": @@ -2620,10 +2626,10 @@ aws-sdk-mock@5.6.0: sinon "^11.1.1" traverse "^0.6.6" -aws-sdk@^2.1093.0, aws-sdk@^2.596.0, aws-sdk@^2.848.0, aws-sdk@^2.928.0: - version "2.1215.0" - resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1215.0.tgz#dafc339c2f9039a8f9de30d863a8665716df2ec0" - integrity sha512-btOexIY0O2F+HhjytToaYuub2HEdLqccZSM8rbT3nrbXo7U4k4Gqi6SbMGi2a+vEpj8lY8dAuMR2lvvVs4Ib9Q== +aws-sdk@^2.1211.0, aws-sdk@^2.596.0, aws-sdk@^2.928.0: + version "2.1219.0" + resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1219.0.tgz#96d056fc4ebfd8417308f18a34f127dbaafc022e" + integrity sha512-KOGA0E3wZ/Zom1VDAd4ttsaq2LAVECXdHUs/i8OyJkuR3vSvmKQa/BOH4baIBNt4VMS062FhPA29UtT1YPTlwQ== dependencies: buffer "4.9.2" events "1.1.1" @@ -2797,15 +2803,15 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.20.2: - version "4.21.3" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a" - integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== +browserslist@^4.21.3: + version "4.21.4" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" + integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== dependencies: - caniuse-lite "^1.0.30001370" - electron-to-chromium "^1.4.202" + caniuse-lite "^1.0.30001400" + electron-to-chromium "^1.4.251" node-releases "^2.0.6" - update-browserslist-db "^1.0.5" + update-browserslist-db "^1.0.9" bs-logger@0.x: version "0.2.6" @@ -2978,10 +2984,10 @@ camelcase@^6.2.0, camelcase@^6.3.0: resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001370: - version "1.0.30001399" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001399.tgz#1bf994ca375d7f33f8d01ce03b7d5139e8587873" - integrity sha512-4vQ90tMKS+FkvuVWS5/QY1+d805ODxZiKFzsU8o/RsVJz49ZSRR8EjykLJbqhzdPgadbX6wB538wOzle3JniRA== +caniuse-lite@^1.0.30001400: + version "1.0.30001409" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001409.tgz#6135da9dcab34cd9761d9cdb12a68e6740c5e96e" + integrity sha512-V0mnJ5dwarmhYv8/MzhJ//aW68UpvnQBXv8lJ2QUsvn2pHcmAuNtu8hQEDz37XnA1iE+lRR9CIfGWWpgJ5QedQ== case@1.6.3, case@^1.6.3: version "1.6.3" @@ -3013,10 +3019,10 @@ cdk8s-plus-21@^2.0.0-beta.12: minimatch "^3.1.2" safe-stable-stringify "*" -cdk8s@^2.4.27: - version "2.4.27" - resolved "https://registry.npmjs.org/cdk8s/-/cdk8s-2.4.27.tgz#fd995b4b8ec9885fa86f6ea4f8b97b38d58abfcc" - integrity sha512-jcgk5myyjWyy8F7o9GEEswSQcvRPyXLTIDEkYqSTclZALAO8nCh9Q4Rtq6RsTH5dNKbY/TC57yoezz0TgkGQIg== +cdk8s@^2.4.33: + version "2.4.33" + resolved "https://registry.npmjs.org/cdk8s/-/cdk8s-2.4.33.tgz#b9474ded4cf2f31a5d300d417ea5ffdaaf666fed" + integrity sha512-kwmY2EENMZoHfRjiixaOqYWz2ksZ5t6wlZ66H8xzetC3LXjKsn6rixFHMzZNADFiofeQYjkiy8OvE5xg5uJlSQ== dependencies: fast-json-patch "^3.1.1" follow-redirects "^1.15.2" @@ -3360,9 +3366,9 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control- integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== constructs@^10.0.0: - version "10.1.102" - resolved "https://registry.npmjs.org/constructs/-/constructs-10.1.102.tgz#20dc622f5bd409f715b1a2d41d6c85b191bd4945" - integrity sha512-wMAb6AqfsFNgPeQd46teyP49nc2ZFivfo3sMc3Py4Jts7Y/k9/qI9D2W+b5hKpRA6aVq8xEodeRLZghqpkX/vA== + version "10.1.108" + resolved "https://registry.npmjs.org/constructs/-/constructs-10.1.108.tgz#6417608a01c29ec4b2197e7fb1c30a89aa036008" + integrity sha512-WfVXhurLliVGxqeDeVIT1dZmgwcpQpcCFzeN6BIClAjYGl7ax+lyslaqqJPqJT+XfPuYkCZT2i9Uc3gtySZJCA== conventional-changelog-angular@^5.0.12: version "5.0.13" @@ -3736,9 +3742,9 @@ decamelize@^5.0.1: integrity sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA== decimal.js@^10.2.1: - version "10.4.0" - resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz#97a7448873b01e92e5ff9117d89a7bca8e63e0fe" - integrity sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg== + version "10.4.1" + resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.1.tgz#be75eeac4a2281aace80c1a8753587c27ef053e7" + integrity sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw== decode-uri-component@^0.2.0: version "0.2.0" @@ -4074,10 +4080,10 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -electron-to-chromium@^1.4.202: - version "1.4.249" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.249.tgz#49c34336c742ee65453dbddf4c84355e59b96e2c" - integrity sha512-GMCxR3p2HQvIw47A599crTKYZprqihoBL4lDSAUmr7IYekXFK5t/WgEBrGJDCa2HWIZFQEkGuMqPCi05ceYqPQ== +electron-to-chromium@^1.4.251: + version "1.4.256" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.256.tgz#c735032f412505e8e0482f147a8ff10cfca45bf4" + integrity sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw== emittery@^0.8.1: version "0.8.1" @@ -4241,132 +4247,140 @@ es6-weak-map@^2.0.3: es6-iterator "^2.0.3" es6-symbol "^3.1.1" -esbuild-android-64@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.7.tgz#a521604d8c4c6befc7affedc897df8ccde189bea" - integrity sha512-p7rCvdsldhxQr3YHxptf1Jcd86dlhvc3EQmQJaZzzuAxefO9PvcI0GLOa5nCWem1AJ8iMRu9w0r5TG8pHmbi9w== - -esbuild-android-arm64@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.7.tgz#307b81f1088bf1e81dfe5f3d1d63a2d2a2e3e68e" - integrity sha512-L775l9ynJT7rVqRM5vo+9w5g2ysbOCfsdLV4CWanTZ1k/9Jb3IYlQ06VCI1edhcosTYJRECQFJa3eAvkx72eyQ== - -esbuild-darwin-64@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.7.tgz#270117b0c4ec6bcbc5cf3a297a7d11954f007e11" - integrity sha512-KGPt3r1c9ww009t2xLB6Vk0YyNOXh7hbjZ3EecHoVDxgtbUlYstMPDaReimKe6eOEfyY4hBEEeTvKwPsiH5WZg== - -esbuild-darwin-arm64@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.7.tgz#97851eacd11dacb7719713602e3319e16202fc77" - integrity sha512-kBIHvtVqbSGajN88lYMnR3aIleH3ABZLLFLxwL2stiuIGAjGlQW741NxVTpUHQXUmPzxi6POqc9npkXa8AcSZQ== - -esbuild-freebsd-64@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.7.tgz#1de15ffaf5ae916aa925800aa6d02579960dd8c4" - integrity sha512-hESZB91qDLV5MEwNxzMxPfbjAhOmtfsr9Wnuci7pY6TtEh4UDuevmGmkUIjX/b+e/k4tcNBMf7SRQ2mdNuK/HQ== - -esbuild-freebsd-arm64@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.7.tgz#0f160dbf5c9a31a1d8dd87acbbcb1a04b7031594" - integrity sha512-dLFR0ChH5t+b3J8w0fVKGvtwSLWCv7GYT2Y2jFGulF1L5HftQLzVGN+6pi1SivuiVSmTh28FwUhi9PwQicXI6Q== - -esbuild-linux-32@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.7.tgz#422eb853370a5e40bdce8b39525380de11ccadec" - integrity sha512-v3gT/LsONGUZcjbt2swrMjwxo32NJzk+7sAgtxhGx1+ZmOFaTRXBAi1PPfgpeo/J//Un2jIKm/I+qqeo4caJvg== - -esbuild-linux-64@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.7.tgz#f89c468453bb3194b14f19dc32e0b99612e81d2b" - integrity sha512-LxXEfLAKwOVmm1yecpMmWERBshl+Kv5YJ/1KnyAr6HRHFW8cxOEsEfisD3sVl/RvHyW//lhYUVSuy9jGEfIRAQ== - -esbuild-linux-arm64@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.7.tgz#68a79d6eb5e032efb9168a0f340ccfd33d6350a1" - integrity sha512-P3cfhudpzWDkglutWgXcT2S7Ft7o2e3YDMrP1n0z2dlbUZghUkKCyaWw0zhp4KxEEzt/E7lmrtRu/pGWnwb9vw== - -esbuild-linux-arm@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.7.tgz#2b7c784d0b3339878013dfa82bf5eaf82c7ce7d3" - integrity sha512-JKgAHtMR5f75wJTeuNQbyznZZa+pjiUHV7sRZp42UNdyXC6TiUYMW/8z8yIBAr2Fpad8hM1royZKQisqPABPvQ== - -esbuild-linux-mips64le@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.7.tgz#bb8330a50b14aa84673816cb63cc6c8b9beb62cc" - integrity sha512-T7XKuxl0VpeFLCJXub6U+iybiqh0kM/bWOTb4qcPyDDwNVhLUiPcGdG2/0S7F93czUZOKP57YiLV8YQewgLHKw== - -esbuild-linux-ppc64le@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.7.tgz#52544e7fa992811eb996674090d0bc41f067a14b" - integrity sha512-6mGuC19WpFN7NYbecMIJjeQgvDb5aMuvyk0PDYBJrqAEMkTwg3Z98kEKuCm6THHRnrgsdr7bp4SruSAxEM4eJw== - -esbuild-linux-riscv64@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.7.tgz#a43ae60697992b957e454cbb622f7ee5297e8159" - integrity sha512-uUJsezbswAYo/X7OU/P+PuL/EI9WzxsEQXDekfwpQ23uGiooxqoLFAPmXPcRAt941vjlY9jtITEEikWMBr+F/g== - -esbuild-linux-s390x@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.7.tgz#8c76a125dd10a84c166294d77416caaf5e1c7b64" - integrity sha512-+tO+xOyTNMc34rXlSxK7aCwJgvQyffqEM5MMdNDEeMU3ss0S6wKvbBOQfgd5jRPblfwJ6b+bKiz0g5nABpY0QQ== - -esbuild-netbsd-64@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.7.tgz#19b2e75449d7d9c32b5d8a222bac2f1e0c3b08fd" - integrity sha512-yVc4Wz+Pu3cP5hzm5kIygNPrjar/v5WCSoRmIjCPWfBVJkZNb5brEGKUlf+0Y759D48BCWa0WHrWXaNy0DULTQ== - -esbuild-openbsd-64@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.7.tgz#1357b2bf72fd037d9150e751420a1fe4c8618ad7" - integrity sha512-GsimbwC4FSR4lN3wf8XmTQ+r8/0YSQo21rWDL0XFFhLHKlzEA4SsT1Tl8bPYu00IU6UWSJ+b3fG/8SB69rcuEQ== - -esbuild-sunos-64@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.7.tgz#87ab2c604592a9c3c763e72969da0d72bcde91d2" - integrity sha512-8CDI1aL/ts0mDGbWzjEOGKXnU7p3rDzggHSBtVryQzkSOsjCHRVe0iFYUuhczlxU1R3LN/E7HgUO4NXzGGP/Ag== - -esbuild-windows-32@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.7.tgz#c81e688c0457665a8d463a669e5bf60870323e99" - integrity sha512-cOnKXUEPS8EGCzRSFa1x6NQjGhGsFlVgjhqGEbLTPsA7x4RRYiy2RKoArNUU4iR2vHmzqS5Gr84MEumO/wxYKA== - -esbuild-windows-64@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.7.tgz#2421d1ae34b0561a9d6767346b381961266c4eff" - integrity sha512-7MI08Ec2sTIDv+zH6StNBKO+2hGUYIT42GmFyW6MBBWWtJhTcQLinKS6ldIN1d52MXIbiJ6nXyCJ+LpL4jBm3Q== - -esbuild-windows-arm64@0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.7.tgz#7d5e9e060a7b454cb2f57f84a3f3c23c8f30b7d2" - integrity sha512-R06nmqBlWjKHddhRJYlqDd3Fabx9LFdKcjoOy08YLimwmsswlFBJV4rXzZCxz/b7ZJXvrZgj8DDv1ewE9+StMw== - -esbuild@^0.15.7: - version "0.15.7" - resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.15.7.tgz#8a1f1aff58671a3199dd24df95314122fc1ddee8" - integrity sha512-7V8tzllIbAQV1M4QoE52ImKu8hT/NLGlGXkiDsbEU5PS6K8Mn09ZnYoS+dcmHxOS9CRsV4IRAMdT3I67IyUNXw== +esbuild-android-64@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.8.tgz#625863e705d4ed32a3b4c0b997dbf9454d50a455" + integrity sha512-bVh8FIKOolF7/d4AMzt7xHlL0Ljr+mYKSHI39TJWDkybVWHdn6+4ODL3xZGHOxPpdRpitemXA1WwMKYBsw8dGw== + dependencies: + esbuild-wasm "0.15.8" + +esbuild-android-arm64@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.8.tgz#cd62afe08652ac146014386d3adbe7a9d33db1b0" + integrity sha512-ReAMDAHuo0H1h9LxRabI6gwYPn8k6WiUeyxuMvx17yTrJO+SCnIfNc/TSPFvDwtK9MiyiKG/2dBYHouT/M0BXQ== + +esbuild-darwin-64@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.8.tgz#eb668dc973165f85aefecdca8aa60231acb2f705" + integrity sha512-KaKcGfJ+yto7Fo5gAj3xwxHMd1fBIKatpCHK8znTJLVv+9+NN2/tIPBqA4w5rBwjX0UqXDeIE2v1xJP+nGEXgA== + +esbuild-darwin-arm64@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.8.tgz#91c110daa46074fdfc18f411247ca0d1228aacc3" + integrity sha512-8tjEaBgAKnXCkP7bhEJmEqdG9HEV6oLkF36BrMzpfW2rgaw0c48Zrxe+9RlfeGvs6gDF4w+agXyTjikzsS3izw== + +esbuild-freebsd-64@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.8.tgz#22270945a9bf9107c340eb73922e122bbe84f8ad" + integrity sha512-jaxcsGHYzn2L0/lffON2WfH4Nc+d/EwozVTP5K2v016zxMb5UQMhLoJzvLgBqHT1SG0B/mO+a+THnJCMVg15zw== + +esbuild-freebsd-arm64@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.8.tgz#0efe2741fbcaa2cfd31b9f94bd3ca7385b68c469" + integrity sha512-2xp2UlljMvX8HExtcg7VHaeQk8OBU0CSl1j18B5CcZmSDkLF9p3utuMXIopG3a08fr9Hv+Dz6+seSXUow/G51w== + +esbuild-linux-32@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.8.tgz#6fc98659105da5c0d1fedfce3b7b9fa24ebee0d4" + integrity sha512-9u1E54BRz1FQMl86iaHK146+4ID2KYNxL3trLZT4QLLx3M7Q9n4lGG3lrzqUatGR2cKy8c33b0iaCzsItZWkFg== + +esbuild-linux-64@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.8.tgz#8e738c926d145cdd4e9bcb2febc96d89dc27dc09" + integrity sha512-4HxrsN9eUzJXdVGMTYA5Xler82FuZUu21bXKN42zcLHHNKCAMPUzD62I+GwDhsdgUBAUj0tRXDdsQHgaP6v0HA== + +esbuild-linux-arm64@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.8.tgz#a12675e5a56e8ef08dea49da8eed51a87b0e60d6" + integrity sha512-1OCm7Aq0tEJT70PbxmHSGYDLYP8DKH8r4Nk7/XbVzWaduo9beCjGBB+tGZIHK6DdTQ3h00/4Tb/70YMH/bOtKg== + +esbuild-linux-arm@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.8.tgz#6424da1e8a3ece78681ebee4a70477b40c36ab35" + integrity sha512-7DVBU9SFjX4+vBwt8tHsUCbE6Vvl6y6FQWHAgyw1lybC5gULqn/WnjHYHN2/LJaZRsDBvxWT4msEgwLGq1Wd3Q== + +esbuild-linux-mips64le@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.8.tgz#5b39a16272cb4eaaad1f24938c057b19fb5a0ee5" + integrity sha512-yeFoNPVFPEzZvFYBfUQNG2TjGRaCyV1E27OcOg4LOtnGrxb2wA+mkW3luckyv1CEyd00mpAg7UdHx8nlx3ghgA== + +esbuild-linux-ppc64le@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.8.tgz#98ea8cfae8227180b45b2d952b2cbb072900944f" + integrity sha512-CEyMMUUNabXibw8OSNmBXhOIGhnjNVl5Lpseiuf00iKN0V47oqDrbo4dsHz1wH62m49AR8iG8wpDlTqfYgKbtg== + +esbuild-linux-riscv64@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.8.tgz#6334607025eb449d8dd402d7810721dc15a6210f" + integrity sha512-OCGSOaspMUjexSCU8ZiA0UnV/NiRU+s2vIfEcAQWQ6u32R+2luyfh/4ZaY6jFbylJE07Esc/yRvb9Q5fXuClXA== + +esbuild-linux-s390x@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.8.tgz#874f1a3507c32cce1d2ce0d2f28ac1496c094eab" + integrity sha512-RHdpdfxRTSrZXZJlFSLazFU4YwXLB5Rgf6Zr5rffqSsO4y9JybgtKO38bFwxZNlDXliYISXN/YROKrG9s7mZQA== + +esbuild-netbsd-64@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.8.tgz#2e03d87ed811400d5d1fa8c7629b9fd97a574231" + integrity sha512-VolFFRatBH09T5QMWhiohAWCOien1R1Uz9K0BRVVTBgBaVBt7eArsXTKxVhUgRf2vwu2c2SXkuP0r7HLG0eozw== + +esbuild-openbsd-64@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.8.tgz#8fdbc6399563ac61ff546449e2226a2b1477216c" + integrity sha512-HTAPlg+n4kUeE/isQxlCfsOz0xJGNoT5LJ9oYZWFKABfVf4Ycu7Zlf5ITgOnrdheTkz8JeL/gISIOCFAoOXrSA== + +esbuild-sunos-64@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.8.tgz#db657b5c09c0c0161d67ddafca1b710a2e7ce96b" + integrity sha512-qMP/jR/FzcIOwKj+W+Lb+8Cfr8GZHbHUJxAPi7DUhNZMQ/6y7sOgRzlOSpRrbbUntrRZh0MqOyDhJ3Gpo6L1QA== + +esbuild-wasm@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.15.8.tgz#60fb8c5dc1a5538421857a2fa5fbb9eab908dcbb" + integrity sha512-Y7uCl5RNO4URjlemjdx++ukVHEMt5s5AfMWYUnMiK4Sry+pPCvQIctzXq6r6FKCyGKjX6/NGMCqR2OX6aLxj0w== + +esbuild-windows-32@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.8.tgz#bbb9fe20a8b6bba4428642cacf45a0fb7b2f3783" + integrity sha512-RKR1QHh4iWzjUhkP8Yqi75PPz/KS+b8zw3wUrzw6oAkj+iU5Qtyj61ZDaSG3Qf2vc6hTIUiPqVTqBH0NpXFNwg== + +esbuild-windows-64@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.8.tgz#cedee65505209c8d371d7228b60785c08f43e04d" + integrity sha512-ag9ptYrsizgsR+PQE8QKeMqnosLvAMonQREpLw4evA4FFgOBMLEat/dY/9txbpozTw9eEOYyD3a4cE9yTu20FA== + +esbuild-windows-arm64@0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.8.tgz#1d75235290bf23a111e6c0b03febd324af115cb1" + integrity sha512-dbpAb0VyPaUs9mgw65KRfQ9rqiWCHpNzrJusoPu+LpEoswosjt/tFxN7cd2l68AT4qWdBkzAjDLRon7uqMeWcg== + +esbuild@^0.15.8: + version "0.15.8" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.15.8.tgz#75daa25d03f6dd9cc9355030eba2b93555b42cd4" + integrity sha512-Remsk2dmr1Ia65sU+QasE6svJbsHe62lzR+CnjpUvbZ+uSYo1SitiOWPRfZQkCu82YWZBBKXiD/j0i//XWMZ+Q== optionalDependencies: - "@esbuild/linux-loong64" "0.15.7" - esbuild-android-64 "0.15.7" - esbuild-android-arm64 "0.15.7" - esbuild-darwin-64 "0.15.7" - esbuild-darwin-arm64 "0.15.7" - esbuild-freebsd-64 "0.15.7" - esbuild-freebsd-arm64 "0.15.7" - esbuild-linux-32 "0.15.7" - esbuild-linux-64 "0.15.7" - esbuild-linux-arm "0.15.7" - esbuild-linux-arm64 "0.15.7" - esbuild-linux-mips64le "0.15.7" - esbuild-linux-ppc64le "0.15.7" - esbuild-linux-riscv64 "0.15.7" - esbuild-linux-s390x "0.15.7" - esbuild-netbsd-64 "0.15.7" - esbuild-openbsd-64 "0.15.7" - esbuild-sunos-64 "0.15.7" - esbuild-windows-32 "0.15.7" - esbuild-windows-64 "0.15.7" - esbuild-windows-arm64 "0.15.7" + "@esbuild/android-arm" "0.15.8" + "@esbuild/linux-loong64" "0.15.8" + esbuild-android-64 "0.15.8" + esbuild-android-arm64 "0.15.8" + esbuild-darwin-64 "0.15.8" + esbuild-darwin-arm64 "0.15.8" + esbuild-freebsd-64 "0.15.8" + esbuild-freebsd-arm64 "0.15.8" + esbuild-linux-32 "0.15.8" + esbuild-linux-64 "0.15.8" + esbuild-linux-arm "0.15.8" + esbuild-linux-arm64 "0.15.8" + esbuild-linux-mips64le "0.15.8" + esbuild-linux-ppc64le "0.15.8" + esbuild-linux-riscv64 "0.15.8" + esbuild-linux-s390x "0.15.8" + esbuild-netbsd-64 "0.15.8" + esbuild-openbsd-64 "0.15.8" + esbuild-sunos-64 "0.15.8" + esbuild-windows-32 "0.15.8" + esbuild-windows-64 "0.15.8" + esbuild-windows-arm64 "0.15.8" escalade@^3.1.1: version "3.1.1" @@ -5834,9 +5848,9 @@ is-buffer@~1.1.6: integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.4: - version "1.2.5" - resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.5.tgz#6123e0b1fef5d7591514b371bb018204892f1a2b" - integrity sha512-ZIWRujF6MvYGkEuHMYtFRkL2wAtFw89EHfKlXrkPkjQZZRWeh9L1q3SV13NIfHnqxugjLvAOkEHx9mb1zcMnEw== + version "1.2.6" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.6.tgz#fd6170b0b8c7e2cc73de342ef8284a2202023c44" + integrity sha512-krO72EO2NptOGAX2KYyqbP9vYMlNAXdB53rq6f8LXY6RY7JdSR/3BD6wLUlPHSAesmY9vstNrjvqGaCiRK/91Q== is-ci@^2.0.0: version "2.0.0" @@ -8916,10 +8930,10 @@ progress@^2.0.0, progress@^2.0.3: resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -projen@^0.62.6: - version "0.62.6" - resolved "https://registry.npmjs.org/projen/-/projen-0.62.6.tgz#55d9185b4d308fd5d8e25ae2a28c316de0713755" - integrity sha512-kVV3JDOf4qo5uEezTvZRPvfdTvVes0Us+wQOwJrx+FBUJZEjbvB70/st0G5Unf1qD/AA0q2xZ3Z7o4gR03X/tQ== +projen@^0.62.13: + version "0.62.13" + resolved "https://registry.npmjs.org/projen/-/projen-0.62.13.tgz#e54c3775974b38b325b641c774848429ceb49ff1" + integrity sha512-Z6k5K3GNjYY6YSDeG7HITEMVTthonAYJAN8SgOckul4GKA4FzC8g1mCdxqnN6Dj5aCWQt5SK5xM+3cxqVmAsKA== dependencies: "@iarna/toml" "^2.2.5" case "^1.6.3" @@ -9555,9 +9569,9 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== safe-stable-stringify@*, safe-stable-stringify@^2.2.0: - version "2.3.1" - resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz#ab67cbe1fe7d40603ca641c5e765cb942d04fc73" - integrity sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg== + version "2.4.0" + resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.0.tgz#95fadb1bcf8057a1363e11052122f5da36a69215" + integrity sha512-eehKHKpab6E741ud7ZIMcXhKcP6TSIezPkNZhy5U8xC6+VvrRdUA2tMgxGxaGl4cz7c2Ew5+mg5+wNB16KQqrA== "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" @@ -10620,9 +10634,9 @@ uc.micro@^1.0.1, uc.micro@^1.0.5: integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== uglify-js@^3.1.4: - version "3.17.0" - resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.0.tgz#55bd6e9d19ce5eef0d5ad17cd1f587d85b180a85" - integrity sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg== + version "3.17.1" + resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.1.tgz#1258a2a488147a8266b3034499ce6959978ba7f4" + integrity sha512-+juFBsLLw7AqMaqJ0GFvlsGZwdQfI2ooKQB39PSBgMnMakcFosi9O8jCwE+2/2nMNcc0z63r9mwjoDG8zr+q0Q== uid-number@0.0.6: version "0.0.6" @@ -10714,7 +10728,7 @@ upath@^2.0.1: resolved "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== -update-browserslist-db@^1.0.5: +update-browserslist-db@^1.0.9: version "1.0.9" resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz#2924d3927367a38d5c555413a7ce138fc95fcb18" integrity sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==