diff --git a/CHANGELOG.v2.alpha.md b/CHANGELOG.v2.alpha.md index 7aed20c525d1b..4cc0a991fd01f 100644 --- a/CHANGELOG.v2.alpha.md +++ b/CHANGELOG.v2.alpha.md @@ -2,6 +2,8 @@ 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.80.0-alpha.0](https://github.com/aws/aws-cdk/compare/v2.79.1-alpha.0...v2.80.0-alpha.0) (2023-05-19) + ## [2.79.1-alpha.0](https://github.com/aws/aws-cdk/compare/v2.79.0-alpha.0...v2.79.1-alpha.0) (2023-05-11) ## [2.79.0-alpha.0](https://github.com/aws/aws-cdk/compare/v2.78.0-alpha.0...v2.79.0-alpha.0) (2023-05-10) diff --git a/CHANGELOG.v2.md b/CHANGELOG.v2.md index 25804a11a91a1..8824bca8a2bbc 100644 --- a/CHANGELOG.v2.md +++ b/CHANGELOG.v2.md @@ -2,6 +2,32 @@ 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.80.0](https://github.com/aws/aws-cdk/compare/v2.79.1...v2.80.0) (2023-05-19) + + +### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES + +* **eks:** A masters role is no longer provisioned by default. Use the `mastersRole` property to explicitly pass a role that needs cluster access. In addition, the creation role no longer allows any identity (with the appropriate `sts:AssumeRole` permissions) to assume it. + +### Features + +* **apigateway:** add grantExecute to API Methods ([#25630](https://github.com/aws/aws-cdk/issues/25630)) ([ecb59fd](https://github.com/aws/aws-cdk/commit/ecb59fda50078e29d579b7b0ee82600f553aec75)) +* **appmesh:** access log format support for app mesh ([#25229](https://github.com/aws/aws-cdk/issues/25229)) ([c4b00be](https://github.com/aws/aws-cdk/commit/c4b00bee9a2ada024c8d838ba083549bc69889f8)) +* **appsync:** Add Private API support when creating a GraphqlApi ([#25569](https://github.com/aws/aws-cdk/issues/25569)) ([d7e263d](https://github.com/aws/aws-cdk/commit/d7e263d5d175f5f189f3ea3d1a5501b975a26281)) +* **cfnspec:** cloudformation spec v122.0.0 ([#25555](https://github.com/aws/aws-cdk/issues/25555)) ([5ccc569](https://github.com/aws/aws-cdk/commit/5ccc56975c323ea19fd0917def51184e13f440d9)) +* **cli:** assets can now depend on stacks ([#25536](https://github.com/aws/aws-cdk/issues/25536)) ([25d5d60](https://github.com/aws/aws-cdk/commit/25d5d60fd0ed852b1817d749b65c68d5279b38a3)) +* **cli:** logging can be corked ([#25644](https://github.com/aws/aws-cdk/issues/25644)) ([0643020](https://github.com/aws/aws-cdk/commit/064302007e902a1521ccc6948a5691cd777afc15)), closes [#25536](https://github.com/aws/aws-cdk/issues/25536) +* **codepipeline-actions:** add KMSEncryptionKeyARN for S3DeployAction ([#24536](https://github.com/aws/aws-cdk/issues/24536)) ([b60876f](https://github.com/aws/aws-cdk/commit/b60876f7bd973f88e965c7e6204ced11c55c55a3)), closes [#24535](https://github.com/aws/aws-cdk/issues/24535) +* **eks:** alb controller include versions 2.4.2 - 2.5.1 ([#25330](https://github.com/aws/aws-cdk/issues/25330)) ([83c4c36](https://github.com/aws/aws-cdk/commit/83c4c36e56917be248bdee1bc11516982d50b17a)), closes [#25307](https://github.com/aws/aws-cdk/issues/25307) +* **msk:** Kafka version 3.4.0 ([#25557](https://github.com/aws/aws-cdk/issues/25557)) ([6317518](https://github.com/aws/aws-cdk/commit/6317518e5d68e5659237b676668fd69bfbd2f42f)), closes [#25522](https://github.com/aws/aws-cdk/issues/25522) +* **scheduler:** schedule expression construct ([#25422](https://github.com/aws/aws-cdk/issues/25422)) ([97a698e](https://github.com/aws/aws-cdk/commit/97a698ee9e1e47ffb4af5d7d06cd309ddd3a2732)) + + +### Bug Fixes + +* **bootstrap:** bootstrap doesn't work in non-aws partitions anymore (revert security hub finding fix) ([#25540](https://github.com/aws/aws-cdk/issues/25540)) ([8854739](https://github.com/aws/aws-cdk/commit/8854739a6b4cdd33dc0da3b76b634b5ab151437b)), closes [/github.com/aws/aws-cdk/issues/19380#issuecomment-1512009270](https://github.com/aws//github.com/aws/aws-cdk/issues/19380/issues/issuecomment-1512009270) [#25272](https://github.com/aws/aws-cdk/issues/25272) [#25273](https://github.com/aws/aws-cdk/issues/25273) [#25507](https://github.com/aws/aws-cdk/issues/25507) +* **eks:** overly permissive trust policies ([#25473](https://github.com/aws/aws-cdk/issues/25473)) ([51f0193](https://github.com/aws/aws-cdk/commit/51f0193bf34cca8254743561a1176e3ca5d83a74)). We would like to thank @twelvemo and @stefreak for reporting this issue. + ## [2.79.1](https://github.com/aws/aws-cdk/compare/v2.79.0...v2.79.1) (2023-05-11) diff --git a/package.json b/package.json index b215aed051477..2c534bb241a92 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@types/node": "18.11.19", "@types/prettier": "2.6.0", "@yarnpkg/lockfile": "^1.1.0", - "cdk-generate-synthetic-examples": "^0.1.243", + "cdk-generate-synthetic-examples": "^0.1.244", "conventional-changelog-cli": "^2.2.2", "fs-extra": "^9.1.0", "graceful-fs": "^4.2.11", diff --git a/packages/@aws-cdk-testing/cli-integ/package.json b/packages/@aws-cdk-testing/cli-integ/package.json index 0af574aa32b87..9263b44267546 100644 --- a/packages/@aws-cdk-testing/cli-integ/package.json +++ b/packages/@aws-cdk-testing/cli-integ/package.json @@ -39,7 +39,7 @@ }, "dependencies": { "@octokit/rest": "^18.12.0", - "aws-sdk": "^2.1378.0", + "aws-sdk": "^2.1379.0", "axios": "^0.27.2", "fs-extra": "^9.1.0", "glob": "^7.2.3", diff --git a/packages/@aws-cdk-testing/framework-integ/package.json b/packages/@aws-cdk-testing/framework-integ/package.json index 61d2b12bdf5bd..7abd7a4468ec1 100644 --- a/packages/@aws-cdk-testing/framework-integ/package.json +++ b/packages/@aws-cdk-testing/framework-integ/package.json @@ -37,12 +37,12 @@ }, "dependencies": { "@aws-cdk/integ-tests-alpha": "0.0.0", - "@aws-cdk/lambda-layer-kubectl-v24": "^2.0.194", + "@aws-cdk/lambda-layer-kubectl-v24": "^2.0.195", "aws-cdk-lib": "0.0.0", - "aws-sdk": "^2.1378.0", + "aws-sdk": "^2.1379.0", "aws-sdk-mock": "5.6.0", - "cdk8s": "^2.7.65", - "cdk8s-plus-24": "2.7.30", + "cdk8s": "^2.7.68", + "cdk8s-plus-24": "2.7.31", "constructs": "^10.0.0" }, "repository": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/GrantExecute.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/GrantExecute.assets.json new file mode 100644 index 0000000000000..0b10c220d7fac --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/GrantExecute.assets.json @@ -0,0 +1,19 @@ +{ + "version": "31.0.0", + "files": { + "454874b4674a38a5eb7ede0bc79fe77dcbf5062acaeb3b4424c7919758ae5191": { + "source": { + "path": "GrantExecute.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "454874b4674a38a5eb7ede0bc79fe77dcbf5062acaeb3b4424c7919758ae5191.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-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/GrantExecute.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/GrantExecute.template.json new file mode 100644 index 0000000000000..186a0e205a3f9 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/GrantExecute.template.json @@ -0,0 +1,179 @@ +{ + "Resources": { + "user2C2B57AE": { + "Type": "AWS::IAM::User" + }, + "userDefaultPolicy083DF682": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "testapiD6451F70" + }, + "/", + { + "Ref": "testapiDeploymentStageprod5C9E92A4" + }, + "/GET/pets" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "userDefaultPolicy083DF682", + "Users": [ + { + "Ref": "user2C2B57AE" + } + ] + } + }, + "testapiD6451F70": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Name": "test-api" + } + }, + "testapiDeployment356D2C358af14d7f8fefbad1c57a65ea01cc6136": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "testapiD6451F70" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "testapipetsGET25A78130", + "testapipets981F319E" + ] + }, + "testapiDeploymentStageprod5C9E92A4": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "testapiD6451F70" + }, + "DeploymentId": { + "Ref": "testapiDeployment356D2C358af14d7f8fefbad1c57a65ea01cc6136" + }, + "StageName": "prod" + } + }, + "testapipets981F319E": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "testapiD6451F70", + "RootResourceId" + ] + }, + "PathPart": "pets", + "RestApiId": { + "Ref": "testapiD6451F70" + } + } + }, + "testapipetsGET25A78130": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "GET", + "ResourceId": { + "Ref": "testapipets981F319E" + }, + "RestApiId": { + "Ref": "testapiD6451F70" + }, + "AuthorizationType": "NONE", + "Integration": { + "Type": "MOCK" + } + } + } + }, + "Outputs": { + "testapiEndpoint4AE34D29": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "testapiD6451F70" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "testapiDeploymentStageprod5C9E92A4" + }, + "/" + ] + ] + } + } + }, + "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-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/GrantExecuteTestDefaultTestDeployAssertA66B6F20.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/GrantExecuteTestDefaultTestDeployAssertA66B6F20.assets.json new file mode 100644 index 0000000000000..316bd7d581d68 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/GrantExecuteTestDefaultTestDeployAssertA66B6F20.assets.json @@ -0,0 +1,19 @@ +{ + "version": "31.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "GrantExecuteTestDefaultTestDeployAssertA66B6F20.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-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/GrantExecuteTestDefaultTestDeployAssertA66B6F20.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/GrantExecuteTestDefaultTestDeployAssertA66B6F20.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/GrantExecuteTestDefaultTestDeployAssertA66B6F20.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-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/cdk.out new file mode 100644 index 0000000000000..7925065efbcc4 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"31.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/integ.json new file mode 100644 index 0000000000000..3806bee2a2e3c --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "31.0.0", + "testCases": { + "GrantExecuteTest/DefaultTest": { + "stacks": [ + "GrantExecute" + ], + "assertionStack": "GrantExecuteTest/DefaultTest/DeployAssert", + "assertionStackName": "GrantExecuteTestDefaultTestDeployAssertA66B6F20" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/manifest.json new file mode 100644 index 0000000000000..06817f21c13a9 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/manifest.json @@ -0,0 +1,153 @@ +{ + "version": "31.0.0", + "artifacts": { + "GrantExecute.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "GrantExecute.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "GrantExecute": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "GrantExecute.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}/454874b4674a38a5eb7ede0bc79fe77dcbf5062acaeb3b4424c7919758ae5191.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "GrantExecute.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": [ + "GrantExecute.assets" + ], + "metadata": { + "/GrantExecute/user/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "user2C2B57AE" + } + ], + "/GrantExecute/user/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "userDefaultPolicy083DF682" + } + ], + "/GrantExecute/test-api/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "testapiD6451F70" + } + ], + "/GrantExecute/test-api/Deployment/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "testapiDeployment356D2C358af14d7f8fefbad1c57a65ea01cc6136" + } + ], + "/GrantExecute/test-api/DeploymentStage.prod/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "testapiDeploymentStageprod5C9E92A4" + } + ], + "/GrantExecute/test-api/Endpoint": [ + { + "type": "aws:cdk:logicalId", + "data": "testapiEndpoint4AE34D29" + } + ], + "/GrantExecute/test-api/Default/pets/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "testapipets981F319E" + } + ], + "/GrantExecute/test-api/Default/pets/GET/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "testapipetsGET25A78130" + } + ], + "/GrantExecute/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/GrantExecute/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "GrantExecute" + }, + "GrantExecuteTestDefaultTestDeployAssertA66B6F20.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "GrantExecuteTestDefaultTestDeployAssertA66B6F20.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "GrantExecuteTestDefaultTestDeployAssertA66B6F20": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "GrantExecuteTestDefaultTestDeployAssertA66B6F20.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": [ + "GrantExecuteTestDefaultTestDeployAssertA66B6F20.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": [ + "GrantExecuteTestDefaultTestDeployAssertA66B6F20.assets" + ], + "metadata": { + "/GrantExecuteTest/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/GrantExecuteTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "GrantExecuteTest/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/tree.json new file mode 100644 index 0000000000000..07fd5007601fd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.js.snapshot/tree.json @@ -0,0 +1,355 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "GrantExecute": { + "id": "GrantExecute", + "path": "GrantExecute", + "children": { + "user": { + "id": "user", + "path": "GrantExecute/user", + "children": { + "Resource": { + "id": "Resource", + "path": "GrantExecute/user/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::User", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnUser", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "GrantExecute/user/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "GrantExecute/user/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "testapiD6451F70" + }, + "/", + { + "Ref": "testapiDeploymentStageprod5C9E92A4" + }, + "/GET/pets" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "userDefaultPolicy083DF682", + "users": [ + { + "Ref": "user2C2B57AE" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.User", + "version": "0.0.0" + } + }, + "test-api": { + "id": "test-api", + "path": "GrantExecute/test-api", + "children": { + "Resource": { + "id": "Resource", + "path": "GrantExecute/test-api/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::RestApi", + "aws:cdk:cloudformation:props": { + "name": "test-api" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigateway.CfnRestApi", + "version": "0.0.0" + } + }, + "Deployment": { + "id": "Deployment", + "path": "GrantExecute/test-api/Deployment", + "children": { + "Resource": { + "id": "Resource", + "path": "GrantExecute/test-api/Deployment/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Deployment", + "aws:cdk:cloudformation:props": { + "restApiId": { + "Ref": "testapiD6451F70" + }, + "description": "Automatically created by the RestApi construct" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigateway.CfnDeployment", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigateway.Deployment", + "version": "0.0.0" + } + }, + "DeploymentStage.prod": { + "id": "DeploymentStage.prod", + "path": "GrantExecute/test-api/DeploymentStage.prod", + "children": { + "Resource": { + "id": "Resource", + "path": "GrantExecute/test-api/DeploymentStage.prod/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Stage", + "aws:cdk:cloudformation:props": { + "restApiId": { + "Ref": "testapiD6451F70" + }, + "deploymentId": { + "Ref": "testapiDeployment356D2C358af14d7f8fefbad1c57a65ea01cc6136" + }, + "stageName": "prod" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigateway.CfnStage", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigateway.Stage", + "version": "0.0.0" + } + }, + "Endpoint": { + "id": "Endpoint", + "path": "GrantExecute/test-api/Endpoint", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnOutput", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "GrantExecute/test-api/Default", + "children": { + "pets": { + "id": "pets", + "path": "GrantExecute/test-api/Default/pets", + "children": { + "Resource": { + "id": "Resource", + "path": "GrantExecute/test-api/Default/pets/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Resource", + "aws:cdk:cloudformation:props": { + "parentId": { + "Fn::GetAtt": [ + "testapiD6451F70", + "RootResourceId" + ] + }, + "pathPart": "pets", + "restApiId": { + "Ref": "testapiD6451F70" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigateway.CfnResource", + "version": "0.0.0" + } + }, + "GET": { + "id": "GET", + "path": "GrantExecute/test-api/Default/pets/GET", + "children": { + "Resource": { + "id": "Resource", + "path": "GrantExecute/test-api/Default/pets/GET/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Method", + "aws:cdk:cloudformation:props": { + "httpMethod": "GET", + "resourceId": { + "Ref": "testapipets981F319E" + }, + "restApiId": { + "Ref": "testapiD6451F70" + }, + "authorizationType": "NONE", + "integration": { + "type": "MOCK" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigateway.CfnMethod", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigateway.Method", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigateway.Resource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigateway.ResourceBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigateway.RestApi", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "GrantExecute/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "GrantExecute/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "GrantExecuteTest": { + "id": "GrantExecuteTest", + "path": "GrantExecuteTest", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "GrantExecuteTest/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "GrantExecuteTest/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.25" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "GrantExecuteTest/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "GrantExecuteTest/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "GrantExecuteTest/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.25" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.ts new file mode 100644 index 0000000000000..9960bd3fde900 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigateway/test/integ.method-grant-execute.ts @@ -0,0 +1,19 @@ +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as cdk from 'aws-cdk-lib'; +import * as integ from '@aws-cdk/integ-tests-alpha'; +import * as apigw from 'aws-cdk-lib/aws-apigateway'; + + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'GrantExecute'); + +const user = new iam.User(stack, 'user'); +const api = new apigw.RestApi(stack, 'test-api'); +const method = api.root.addResource('pets').addMethod('GET'); +method.grantExecute(user); + +new integ.IntegTest(app, 'GrantExecuteTest', { + testCases: [stack], +}); + +app.synth(); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/aws-cdk-codepipeline-s3-deploy.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/aws-cdk-codepipeline-s3-deploy.assets.json index 2d274f8b21777..2d87ab096ad10 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/aws-cdk-codepipeline-s3-deploy.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/aws-cdk-codepipeline-s3-deploy.assets.json @@ -14,7 +14,7 @@ } } }, - "c77c225bf996813c66f962ac8da785aa5fa677d3c2a632c3743e4075e07a194e": { + "0e8ab65ec77f46df122d00ad20da666bb3461c6aee65675b4a7a64b8b284c5a9": { "source": { "path": "aws-cdk-codepipeline-s3-deploy.template.json", "packaging": "file" @@ -22,7 +22,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "c77c225bf996813c66f962ac8da785aa5fa677d3c2a632c3743e4075e07a194e.json", + "objectKey": "0e8ab65ec77f46df122d00ad20da666bb3461c6aee65675b4a7a64b8b284c5a9.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/aws-cdk-codepipeline-s3-deploy.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/aws-cdk-codepipeline-s3-deploy.template.json index a6713b413e740..a87bf0d5e0dc4 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/aws-cdk-codepipeline-s3-deploy.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/aws-cdk-codepipeline-s3-deploy.template.json @@ -1,8 +1,59 @@ { "Resources": { + "EnvVarEncryptKey1A7CABDB": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "Description": "sample key" + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, "PipelineBucketB967BD35": { "Type": "AWS::S3::Bucket", "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "EnvVarEncryptKey1A7CABDB", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + }, "Tags": [ { "Key": "aws-cdk:auto-delete-objects", @@ -369,6 +420,22 @@ } ] }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EnvVarEncryptKey1A7CABDB", + "Arn" + ] + } + }, { "Action": "sts:AssumeRole", "Effect": "Allow", @@ -462,7 +529,13 @@ "Extract": "false", "ObjectKey": "key", "CannedACL": "private", - "CacheControl": "public, max-age=43200" + "CacheControl": "public, max-age=43200", + "KMSEncryptionKeyARN": { + "Fn::GetAtt": [ + "EnvVarEncryptKey1A7CABDB", + "Arn" + ] + } }, "InputArtifacts": [ { @@ -515,6 +588,15 @@ } ], "ArtifactStore": { + "EncryptionKey": { + "Id": { + "Fn::GetAtt": [ + "EnvVarEncryptKey1A7CABDB", + "Arn" + ] + }, + "Type": "KMS" + }, "Location": { "Ref": "PipelineBucketB967BD35" }, @@ -599,6 +681,22 @@ } ] }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EnvVarEncryptKey1A7CABDB", + "Arn" + ] + } + }, { "Action": [ "s3:Abort*", @@ -765,6 +863,22 @@ ] } ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EnvVarEncryptKey1A7CABDB", + "Arn" + ] + } } ], "Version": "2012-10-17" @@ -877,6 +991,19 @@ ] } ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EnvVarEncryptKey1A7CABDB", + "Arn" + ] + } } ], "Version": "2012-10-17" @@ -994,6 +1121,14 @@ } }, "Outputs": { + "ExportsOutputRefDeployBucket67E2C076D8DEC04D": { + "Value": { + "Ref": "DeployBucket67E2C076" + }, + "Export": { + "Name": "aws-cdk-codepipeline-s3-deploy:ExportsOutputRefDeployBucket67E2C076D8DEC04D" + } + }, "ExportsOutputRefPipelineBucketB967BD35BAE6E881": { "Value": { "Ref": "PipelineBucketB967BD35" @@ -1009,14 +1144,6 @@ "Export": { "Name": "aws-cdk-codepipeline-s3-deploy:ExportsOutputRefPipelineC660917DEB540586" } - }, - "ExportsOutputRefDeployBucket67E2C076D8DEC04D": { - "Value": { - "Ref": "DeployBucket67E2C076" - }, - "Export": { - "Name": "aws-cdk-codepipeline-s3-deploy:ExportsOutputRefDeployBucket67E2C076D8DEC04D" - } } }, "Parameters": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/manifest.json index 9e262633dd598..01c97429e50e0 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/manifest.json @@ -17,7 +17,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}/c77c225bf996813c66f962ac8da785aa5fa677d3c2a632c3743e4075e07a194e.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/0e8ab65ec77f46df122d00ad20da666bb3461c6aee65675b4a7a64b8b284c5a9.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -33,6 +33,12 @@ "aws-cdk-codepipeline-s3-deploy.assets" ], "metadata": { + "/aws-cdk-codepipeline-s3-deploy/EnvVarEncryptKey/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "EnvVarEncryptKey1A7CABDB" + } + ], "/aws-cdk-codepipeline-s3-deploy/PipelineBucket/Resource": [ { "type": "aws:cdk:logicalId", @@ -159,22 +165,22 @@ "data": "PipelineDisabledDisabledDeployActionCodePipelineActionRoleDefaultPolicyB1AF629C" } ], - "/aws-cdk-codepipeline-s3-deploy/Exports/Output{\"Ref\":\"PipelineBucketB967BD35\"}": [ + "/aws-cdk-codepipeline-s3-deploy/Exports/Output{\"Ref\":\"DeployBucket67E2C076\"}": [ { "type": "aws:cdk:logicalId", - "data": "ExportsOutputRefPipelineBucketB967BD35BAE6E881" + "data": "ExportsOutputRefDeployBucket67E2C076D8DEC04D" } ], - "/aws-cdk-codepipeline-s3-deploy/Exports/Output{\"Ref\":\"PipelineC660917D\"}": [ + "/aws-cdk-codepipeline-s3-deploy/Exports/Output{\"Ref\":\"PipelineBucketB967BD35\"}": [ { "type": "aws:cdk:logicalId", - "data": "ExportsOutputRefPipelineC660917DEB540586" + "data": "ExportsOutputRefPipelineBucketB967BD35BAE6E881" } ], - "/aws-cdk-codepipeline-s3-deploy/Exports/Output{\"Ref\":\"DeployBucket67E2C076\"}": [ + "/aws-cdk-codepipeline-s3-deploy/Exports/Output{\"Ref\":\"PipelineC660917D\"}": [ { "type": "aws:cdk:logicalId", - "data": "ExportsOutputRefDeployBucket67E2C076D8DEC04D" + "data": "ExportsOutputRefPipelineC660917DEB540586" } ], "/aws-cdk-codepipeline-s3-deploy/BootstrapVersion": [ @@ -208,7 +214,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}/2a0db37afe84ae5c439012506dfdee1493ab05d9cc40f507fa44ff0ed8d2dfab.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/a5e87b4a3b1576f59ec7c5aeb8238a7899b624959515db8b64d69c9b7111fb75.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -225,10 +231,10 @@ "s3deploytestDefaultTestDeployAssert6BC61647.assets" ], "metadata": { - "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObject/Default/Default": [ + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject132afe15f6b0866b1b0b18d4081f0330/Default/Default": [ { "type": "aws:cdk:logicalId", - "data": "AwsApiCallS3putObject" + "data": "AwsApiCallS3getObject132afe15f6b0866b1b0b18d4081f0330" } ], "/s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ @@ -243,40 +249,46 @@ "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" } ], - "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/Default/Default": [ + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObjecte1b51fae535275287a7fd0b537ad2b3d/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallS3putObjecte1b51fae535275287a7fd0b537ad2b3d" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/Default/Default": [ { "type": "aws:cdk:logicalId", - "data": "AwsApiCallCodePipelinegetPipelineState" + "data": "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e" } ], - "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/IsCompleteProvider/Invoke": [ + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/WaitFor/IsCompleteProvider/Invoke": [ { "type": "aws:cdk:logicalId", - "data": "AwsApiCallCodePipelinegetPipelineStateWaitForIsCompleteProviderInvokeB83E9F2C" + "data": "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForIsCompleteProviderInvoke821ABA06" } ], - "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/TimeoutProvider/Invoke": [ + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/WaitFor/TimeoutProvider/Invoke": [ { "type": "aws:cdk:logicalId", - "data": "AwsApiCallCodePipelinegetPipelineStateWaitForTimeoutProviderInvoke96D2C126" + "data": "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForTimeoutProviderInvoke2F043504" } ], - "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/Role": [ + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/WaitFor/Role": [ { "type": "aws:cdk:logicalId", - "data": "AwsApiCallCodePipelinegetPipelineStateWaitForRoleDF2D0D47" + "data": "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForRole44AD3905" } ], - "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/Resource": [ + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/WaitFor/Resource": [ { "type": "aws:cdk:logicalId", - "data": "AwsApiCallCodePipelinegetPipelineStateWaitFor68BABF78" + "data": "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForC3FB32C5" } ], - "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/AssertionResults": [ + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/AssertionResults": [ { "type": "aws:cdk:logicalId", - "data": "AssertionResultsAwsApiCallCodePipelinegetPipelineState" + "data": "AssertionResultsAwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e" } ], "/s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Role": [ @@ -303,12 +315,6 @@ "data": "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA" } ], - "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject/Default/Default": [ - { - "type": "aws:cdk:logicalId", - "data": "AwsApiCallS3getObject" - } - ], "/s3-deploy-test/DefaultTest/DeployAssert/BootstrapVersion": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.assets.json index 3447c7d92314b..c23aca437f00e 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.assets.json @@ -14,7 +14,7 @@ } } }, - "2a0db37afe84ae5c439012506dfdee1493ab05d9cc40f507fa44ff0ed8d2dfab": { + "a5e87b4a3b1576f59ec7c5aeb8238a7899b624959515db8b64d69c9b7111fb75": { "source": { "path": "s3deploytestDefaultTestDeployAssert6BC61647.template.json", "packaging": "file" @@ -22,7 +22,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "2a0db37afe84ae5c439012506dfdee1493ab05d9cc40f507fa44ff0ed8d2dfab.json", + "objectKey": "a5e87b4a3b1576f59ec7c5aeb8238a7899b624959515db8b64d69c9b7111fb75.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.template.json index 48401a56fe0a3..3e6c3e21fba3e 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.template.json @@ -1,7 +1,7 @@ { "Resources": { - "AwsApiCallS3putObject": { - "Type": "Custom::DeployAssert@SdkCallS3putObject", + "AwsApiCallS3getObject132afe15f6b0866b1b0b18d4081f0330": { + "Type": "Custom::DeployAssert@SdkCallS3getObject", "Properties": { "ServiceToken": { "Fn::GetAtt": [ @@ -10,17 +10,24 @@ ] }, "service": "S3", - "api": "putObject", + "api": "getObject", "parameters": { "Bucket": { - "Fn::ImportValue": "aws-cdk-codepipeline-s3-deploy:ExportsOutputRefPipelineBucketB967BD35BAE6E881" + "Fn::ImportValue": "aws-cdk-codepipeline-s3-deploy:ExportsOutputRefDeployBucket67E2C076D8DEC04D" }, - "Key": "key", - "Body": "HelloWorld" + "Key": "key" }, "flattenResponse": "false", - "salt": "1682378113734" + "salt": "1684406090555" }, + "DependsOn": [ + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e", + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForIsCompleteProviderInvoke821ABA06", + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForC3FB32C5", + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForRole44AD3905", + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForTimeoutProviderInvoke2F043504", + "AwsApiCallS3putObjecte1b51fae535275287a7fd0b537ad2b3d" + ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, @@ -52,7 +59,7 @@ "Statement": [ { "Action": [ - "s3:PutObject" + "s3:GetObject" ], "Effect": "Allow", "Resource": [ @@ -60,8 +67,17 @@ ] }, { + "Effect": "Allow", "Action": [ - "codepipeline:GetPipelineState" + "kms:Decrypt" + ], + "Resource": [ + "*" + ] + }, + { + "Action": [ + "s3:PutObject" ], "Effect": "Allow", "Resource": [ @@ -69,8 +85,17 @@ ] }, { + "Effect": "Allow", "Action": [ - "states:StartExecution" + "kms:GenerateDataKey" + ], + "Resource": [ + "*" + ] + }, + { + "Action": [ + "codepipeline:GetPipelineState" ], "Effect": "Allow", "Resource": [ @@ -79,7 +104,7 @@ }, { "Action": [ - "s3:GetObject" + "states:StartExecution" ], "Effect": "Allow", "Resource": [ @@ -112,7 +137,31 @@ } } }, - "AwsApiCallCodePipelinegetPipelineState": { + "AwsApiCallS3putObjecte1b51fae535275287a7fd0b537ad2b3d": { + "Type": "Custom::DeployAssert@SdkCallS3putObject", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "S3", + "api": "putObject", + "parameters": { + "Bucket": { + "Fn::ImportValue": "aws-cdk-codepipeline-s3-deploy:ExportsOutputRefPipelineBucketB967BD35BAE6E881" + }, + "Key": "key", + "Body": "HelloWorld" + }, + "flattenResponse": "false", + "salt": "1684406090555" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e": { "Type": "Custom::DeployAssert@SdkCallCodePipelinegetPipelineState", "Properties": { "ServiceToken": { @@ -125,7 +174,7 @@ "api": "getPipelineState", "expected": "{\"$ObjectLike\":{\"stageStates\":{\"$ArrayWith\":[{\"$ObjectLike\":{\"stageName\":\"Deploy\",\"latestExecution\":{\"$ObjectLike\":{\"status\":\"Succeeded\"}}}}]}}}", "stateMachineArn": { - "Ref": "AwsApiCallCodePipelinegetPipelineStateWaitFor68BABF78" + "Ref": "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForC3FB32C5" }, "parameters": { "name": { @@ -133,12 +182,12 @@ } }, "flattenResponse": "false", - "salt": "1682378113735" + "salt": "1684406090556" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "AwsApiCallCodePipelinegetPipelineStateWaitForIsCompleteProviderInvokeB83E9F2C": { + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForIsCompleteProviderInvoke821ABA06": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", @@ -150,13 +199,13 @@ }, "Principal": { "Fn::GetAtt": [ - "AwsApiCallCodePipelinegetPipelineStateWaitForRoleDF2D0D47", + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForRole44AD3905", "Arn" ] } } }, - "AwsApiCallCodePipelinegetPipelineStateWaitForTimeoutProviderInvoke96D2C126": { + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForTimeoutProviderInvoke2F043504": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", @@ -168,13 +217,13 @@ }, "Principal": { "Fn::GetAtt": [ - "AwsApiCallCodePipelinegetPipelineStateWaitForRoleDF2D0D47", + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForRole44AD3905", "Arn" ] } } }, - "AwsApiCallCodePipelinegetPipelineStateWaitForRoleDF2D0D47": { + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForRole44AD3905": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -219,7 +268,7 @@ ] } }, - "AwsApiCallCodePipelinegetPipelineStateWaitFor68BABF78": { + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForC3FB32C5": { "Type": "AWS::StepFunctions::StateMachine", "Properties": { "DefinitionString": { @@ -246,13 +295,13 @@ }, "RoleArn": { "Fn::GetAtt": [ - "AwsApiCallCodePipelinegetPipelineStateWaitForRoleDF2D0D47", + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForRole44AD3905", "Arn" ] } }, "DependsOn": [ - "AwsApiCallCodePipelinegetPipelineStateWaitForRoleDF2D0D47" + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225eWaitForRole44AD3905" ] }, "SingletonFunction76b3e830a873425f8453eddd85c86925Role918961BB": { @@ -357,44 +406,13 @@ ] } } - }, - "AwsApiCallS3getObject": { - "Type": "Custom::DeployAssert@SdkCallS3getObject", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", - "Arn" - ] - }, - "service": "S3", - "api": "getObject", - "parameters": { - "Bucket": { - "Fn::ImportValue": "aws-cdk-codepipeline-s3-deploy:ExportsOutputRefDeployBucket67E2C076D8DEC04D" - }, - "Key": "key" - }, - "flattenResponse": "false", - "salt": "1682378113736" - }, - "DependsOn": [ - "AwsApiCallCodePipelinegetPipelineState", - "AwsApiCallCodePipelinegetPipelineStateWaitForIsCompleteProviderInvokeB83E9F2C", - "AwsApiCallCodePipelinegetPipelineStateWaitFor68BABF78", - "AwsApiCallCodePipelinegetPipelineStateWaitForRoleDF2D0D47", - "AwsApiCallCodePipelinegetPipelineStateWaitForTimeoutProviderInvoke96D2C126", - "AwsApiCallS3putObject" - ], - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" } }, "Outputs": { - "AssertionResultsAwsApiCallCodePipelinegetPipelineState": { + "AssertionResultsAwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e": { "Value": { "Fn::GetAtt": [ - "AwsApiCallCodePipelinegetPipelineState", + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e", "assertion" ] } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/tree.json index 3bb28177996c2..325da7459a733 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.js.snapshot/tree.json @@ -8,6 +8,58 @@ "id": "aws-cdk-codepipeline-s3-deploy", "path": "aws-cdk-codepipeline-s3-deploy", "children": { + "EnvVarEncryptKey": { + "id": "EnvVarEncryptKey", + "path": "aws-cdk-codepipeline-s3-deploy/EnvVarEncryptKey", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codepipeline-s3-deploy/EnvVarEncryptKey/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::KMS::Key", + "aws:cdk:cloudformation:props": { + "keyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "description": "sample key" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_kms.CfnKey", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_kms.Key", + "version": "0.0.0" + } + }, "PipelineBucket": { "id": "PipelineBucket", "path": "aws-cdk-codepipeline-s3-deploy/PipelineBucket", @@ -18,6 +70,21 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::S3::Bucket", "aws:cdk:cloudformation:props": { + "bucketEncryption": { + "serverSideEncryptionConfiguration": [ + { + "serverSideEncryptionByDefault": { + "sseAlgorithm": "aws:kms", + "kmsMasterKeyId": { + "Fn::GetAtt": [ + "EnvVarEncryptKey1A7CABDB", + "Arn" + ] + } + } + } + ] + }, "tags": [ { "key": "aws-cdk:auto-delete-objects", @@ -491,6 +558,22 @@ } ] }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EnvVarEncryptKey1A7CABDB", + "Arn" + ] + } + }, { "Action": "sts:AssumeRole", "Effect": "Allow", @@ -611,7 +694,13 @@ "Extract": "false", "ObjectKey": "key", "CannedACL": "private", - "CacheControl": "public, max-age=43200" + "CacheControl": "public, max-age=43200", + "KMSEncryptionKeyARN": { + "Fn::GetAtt": [ + "EnvVarEncryptKey1A7CABDB", + "Arn" + ] + } }, "runOrder": 1, "roleArn": { @@ -660,6 +749,15 @@ "type": "S3", "location": { "Ref": "PipelineBucketB967BD35" + }, + "encryptionKey": { + "type": "KMS", + "id": { + "Fn::GetAtt": [ + "EnvVarEncryptKey1A7CABDB", + "Arn" + ] + } } }, "disableInboundStageTransitions": [ @@ -777,6 +875,22 @@ } ] }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EnvVarEncryptKey1A7CABDB", + "Arn" + ] + } + }, { "Action": [ "s3:Abort*", @@ -842,13 +956,13 @@ }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.25" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.25" } }, "Deploy": { @@ -1007,6 +1121,22 @@ ] } ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EnvVarEncryptKey1A7CABDB", + "Arn" + ] + } } ], "Version": "2012-10-17" @@ -1039,13 +1169,13 @@ }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.25" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.25" } }, "Disabled": { @@ -1183,6 +1313,19 @@ ] } ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EnvVarEncryptKey1A7CABDB", + "Arn" + ] + } } ], "Version": "2012-10-17" @@ -1215,13 +1358,13 @@ }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.25" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.25" } } }, @@ -1234,6 +1377,14 @@ "id": "Exports", "path": "aws-cdk-codepipeline-s3-deploy/Exports", "children": { + "Output{\"Ref\":\"DeployBucket67E2C076\"}": { + "id": "Output{\"Ref\":\"DeployBucket67E2C076\"}", + "path": "aws-cdk-codepipeline-s3-deploy/Exports/Output{\"Ref\":\"DeployBucket67E2C076\"}", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnOutput", + "version": "0.0.0" + } + }, "Output{\"Ref\":\"PipelineBucketB967BD35\"}": { "id": "Output{\"Ref\":\"PipelineBucketB967BD35\"}", "path": "aws-cdk-codepipeline-s3-deploy/Exports/Output{\"Ref\":\"PipelineBucketB967BD35\"}", @@ -1249,19 +1400,11 @@ "fqn": "aws-cdk-lib.CfnOutput", "version": "0.0.0" } - }, - "Output{\"Ref\":\"DeployBucket67E2C076\"}": { - "id": "Output{\"Ref\":\"DeployBucket67E2C076\"}", - "path": "aws-cdk-codepipeline-s3-deploy/Exports/Output{\"Ref\":\"DeployBucket67E2C076\"}", - "constructInfo": { - "fqn": "aws-cdk-lib.CfnOutput", - "version": "0.0.0" - } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.25" } }, "BootstrapVersion": { @@ -1299,27 +1442,27 @@ "path": "s3-deploy-test/DefaultTest/Default", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.25" } }, "DeployAssert": { "id": "DeployAssert", "path": "s3-deploy-test/DefaultTest/DeployAssert", "children": { - "AwsApiCallS3putObject": { - "id": "AwsApiCallS3putObject", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObject", + "AwsApiCallS3getObject132afe15f6b0866b1b0b18d4081f0330": { + "id": "AwsApiCallS3getObject132afe15f6b0866b1b0b18d4081f0330", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject132afe15f6b0866b1b0b18d4081f0330", "children": { "SdkProvider": { "id": "SdkProvider", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObject/SdkProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject132afe15f6b0866b1b0b18d4081f0330/SdkProvider", "children": { "AssertionsProvider": { "id": "AssertionsProvider", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObject/SdkProvider/AssertionsProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject132afe15f6b0866b1b0b18d4081f0330/SdkProvider/AssertionsProvider", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.25" } } }, @@ -1330,11 +1473,11 @@ }, "Default": { "id": "Default", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObject/Default", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject132afe15f6b0866b1b0b18d4081f0330/Default", "children": { "Default": { "id": "Default", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObject/Default/Default", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject132afe15f6b0866b1b0b18d4081f0330/Default/Default", "constructInfo": { "fqn": "aws-cdk-lib.CfnResource", "version": "0.0.0" @@ -1383,23 +1526,69 @@ }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.25" + } + }, + "AwsApiCallS3putObjecte1b51fae535275287a7fd0b537ad2b3d": { + "id": "AwsApiCallS3putObjecte1b51fae535275287a7fd0b537ad2b3d", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObjecte1b51fae535275287a7fd0b537ad2b3d", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObjecte1b51fae535275287a7fd0b537ad2b3d/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObjecte1b51fae535275287a7fd0b537ad2b3d/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.25" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObjecte1b51fae535275287a7fd0b537ad2b3d/Default", + "children": { + "Default": { + "id": "Default", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObjecte1b51fae535275287a7fd0b537ad2b3d/Default/Default", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.CustomResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.AwsApiCall", + "version": "0.0.0" } }, - "AwsApiCallCodePipelinegetPipelineState": { - "id": "AwsApiCallCodePipelinegetPipelineState", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState", + "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e": { + "id": "AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e", "children": { "SdkProvider": { "id": "SdkProvider", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/SdkProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/SdkProvider", "children": { "AssertionsProvider": { "id": "AssertionsProvider", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/SdkProvider/AssertionsProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/SdkProvider/AssertionsProvider", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.25" } } }, @@ -1410,11 +1599,11 @@ }, "Default": { "id": "Default", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/Default", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/Default", "children": { "Default": { "id": "Default", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/Default/Default", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/Default/Default", "constructInfo": { "fqn": "aws-cdk-lib.CfnResource", "version": "0.0.0" @@ -1428,23 +1617,23 @@ }, "WaitFor": { "id": "WaitFor", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/WaitFor", "children": { "IsCompleteProvider": { "id": "IsCompleteProvider", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/IsCompleteProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/WaitFor/IsCompleteProvider", "children": { "AssertionsProvider": { "id": "AssertionsProvider", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/IsCompleteProvider/AssertionsProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/WaitFor/IsCompleteProvider/AssertionsProvider", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.25" } }, "Invoke": { "id": "Invoke", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/IsCompleteProvider/Invoke", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/WaitFor/IsCompleteProvider/Invoke", "constructInfo": { "fqn": "aws-cdk-lib.CfnResource", "version": "0.0.0" @@ -1458,19 +1647,19 @@ }, "TimeoutProvider": { "id": "TimeoutProvider", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/TimeoutProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/WaitFor/TimeoutProvider", "children": { "AssertionsProvider": { "id": "AssertionsProvider", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/TimeoutProvider/AssertionsProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/WaitFor/TimeoutProvider/AssertionsProvider", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.25" } }, "Invoke": { "id": "Invoke", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/TimeoutProvider/Invoke", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/WaitFor/TimeoutProvider/Invoke", "constructInfo": { "fqn": "aws-cdk-lib.CfnResource", "version": "0.0.0" @@ -1484,7 +1673,7 @@ }, "Role": { "id": "Role", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/Role", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/WaitFor/Role", "constructInfo": { "fqn": "aws-cdk-lib.CfnResource", "version": "0.0.0" @@ -1492,7 +1681,7 @@ }, "Resource": { "id": "Resource", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/Resource", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/WaitFor/Resource", "constructInfo": { "fqn": "aws-cdk-lib.CfnResource", "version": "0.0.0" @@ -1506,7 +1695,7 @@ }, "AssertionResults": { "id": "AssertionResults", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/AssertionResults", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState57ac6eaf015feec14cf48d22e7e8225e/AssertionResults", "constructInfo": { "fqn": "aws-cdk-lib.CfnOutput", "version": "0.0.0" @@ -1549,7 +1738,7 @@ }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.25" } }, "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a": { @@ -1583,53 +1772,7 @@ }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" - } - }, - "AwsApiCallS3getObject": { - "id": "AwsApiCallS3getObject", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject", - "children": { - "SdkProvider": { - "id": "SdkProvider", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject/SdkProvider", - "children": { - "AssertionsProvider": { - "id": "AssertionsProvider", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject/SdkProvider/AssertionsProvider", - "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.270" - } - } - }, - "constructInfo": { - "fqn": "@aws-cdk/integ-tests-alpha.AssertionsProvider", - "version": "0.0.0" - } - }, - "Default": { - "id": "Default", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject/Default", - "children": { - "Default": { - "id": "Default", - "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject/Default/Default", - "constructInfo": { - "fqn": "aws-cdk-lib.CfnResource", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.CustomResource", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "@aws-cdk/integ-tests-alpha.AwsApiCall", - "version": "0.0.0" + "version": "10.2.25" } }, "BootstrapVersion": { @@ -1671,7 +1814,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.25" } } }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.ts index 6ef696607bc8b..879d96e92071f 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.ts @@ -1,4 +1,5 @@ import * as codepipeline from 'aws-cdk-lib/aws-codepipeline'; +import * as kms from 'aws-cdk-lib/aws-kms'; import * as s3 from 'aws-cdk-lib/aws-s3'; import * as cdk from 'aws-cdk-lib'; import { Duration } from 'aws-cdk-lib'; @@ -9,10 +10,15 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-s3-deploy'); +const key: kms.IKey = new kms.Key(stack, 'EnvVarEncryptKey', { + description: 'sample key', +}); + const bucket = new s3.Bucket(stack, 'PipelineBucket', { versioned: true, removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, + encryptionKey: key, }); const sourceOutput = new codepipeline.Artifact('SourceArtifact'); const sourceAction = new cpactions.S3SourceAction({ @@ -53,6 +59,7 @@ const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { cpactions.CacheControl.setPublic(), cpactions.CacheControl.maxAge(cdk.Duration.hours(12)), ], + encryptionKey: key, }), ], }, @@ -74,11 +81,31 @@ const integ = new IntegTest(app, 's3-deploy-test', { testCases: [stack], }); -integ.assertions.awsApiCall('S3', 'putObject', { +const getObjectCall = integ.assertions.awsApiCall('S3', 'getObject', { + Bucket: deployBucket.bucketName, + Key: 'key', +}); + +getObjectCall.provider.addToRolePolicy({ + Effect: 'Allow', + Action: ['kms:Decrypt'], + Resource: ['*'], +}); + + +const putObjectCall = integ.assertions.awsApiCall('S3', 'putObject', { Bucket: bucket.bucketName, Key: 'key', Body: 'HelloWorld', -}).next( +}); + +putObjectCall.provider.addToRolePolicy({ + Effect: 'Allow', + Action: ['kms:GenerateDataKey'], + Resource: ['*'], +}); + +putObjectCall.next( integ.assertions.awsApiCall('CodePipeline', 'getPipelineState', { name: pipeline.pipelineName, }).expect(ExpectedResult.objectLike({ @@ -92,12 +119,7 @@ integ.assertions.awsApiCall('S3', 'putObject', { ]), })).waitForAssertions({ totalTimeout: Duration.minutes(5), - }).next( - integ.assertions.awsApiCall('S3', 'getObject', { - Bucket: deployBucket.bucketName, - Key: 'key', - }), - ), + }).next(getObjectCall), ); app.synth(); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-eks/test/sdk-call-integ-test-docker-app/app/package.json b/packages/@aws-cdk-testing/framework-integ/test/aws-eks/test/sdk-call-integ-test-docker-app/app/package.json index 2b9d7370b7b0c..7931e4aa1f62d 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-eks/test/sdk-call-integ-test-docker-app/app/package.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-eks/test/sdk-call-integ-test-docker-app/app/package.json @@ -2,6 +2,6 @@ "name": "eks-service-account-sdk-call-integ-test", "private": "true", "dependencies": { - "aws-sdk": "^2.1378.0" + "aws-sdk": "^2.1379.0" } } diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/.eslintrc.js b/packages/@aws-cdk/app-staging-synthesizer-alpha/.eslintrc.js new file mode 100644 index 0000000000000..c6b0adb2216b1 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/.eslintrc.js @@ -0,0 +1,4 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc'); +baseConfig.ignorePatterns.push('resources/**/*'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/.gitignore b/packages/@aws-cdk/app-staging-synthesizer-alpha/.gitignore new file mode 100644 index 0000000000000..1272e8254630e --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/.gitignore @@ -0,0 +1,22 @@ +*.js +*.d.ts +tsconfig.json +*.generated.ts +*.js.map +dist +coverage +.nyc_output +.jsii + +.LAST_BUILD +nyc.config.js +.LAST_PACKAGE +*.snk +!.eslintrc.js + +junit.xml +!jest.config.js +!**/*.snapshot/**/asset.*/*.js +!**/*.snapshot/**/asset.*/*.d.ts + +!**/*.snapshot/**/asset.*/** diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/.npmignore b/packages/@aws-cdk/app-staging-synthesizer-alpha/.npmignore new file mode 100644 index 0000000000000..773d1bc0f120e --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/.npmignore @@ -0,0 +1,14 @@ + +.LAST_BUILD +*.snk +junit.xml +*.ts +!*.d.ts +!*.js +!*.lit.ts +coverage +.nyc_output +*.tgz +.eslintrc.js +# exclude cdk artifacts +**/cdk.out \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/LICENSE b/packages/@aws-cdk/app-staging-synthesizer-alpha/LICENSE new file mode 100644 index 0000000000000..9b722c65c5481 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/NOTICE b/packages/@aws-cdk/app-staging-synthesizer-alpha/NOTICE new file mode 100644 index 0000000000000..a27b7dd317649 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/README.md b/packages/@aws-cdk/app-staging-synthesizer-alpha/README.md new file mode 100644 index 0000000000000..9d9c9e372f7a0 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/README.md @@ -0,0 +1,389 @@ +# App Staging Synthesizer + + +--- + +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. +> They are subject to non-backward compatible changes or removal in any future version. These are +> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be +> announced in the release notes. This means that while you may use them, you may need to update +> your source code when upgrading to a newer version of this package. + +--- + + + +This library includes constructs aimed at replacing the current model of bootstrapping and providing +greater control of the bootstrap experience to the CDK user. The important constructs in this library +are as follows: + +- the `IStagingResources` interface: a framework for an app-level bootstrap stack that handles + file assets and docker assets. +- the `DefaultStagingStack`, which is a works-out-of-the-box implementation of the `IStagingResources` + interface. +- the `AppStagingSynthesizer`, a new CDK synthesizer that will synthesize CDK applications with + the staging resources provided. + +> Currently this module does not support CDK Pipelines. You must deploy CDK Apps using this +> synthesizer via `cdk deploy`. + +To get started, update your CDK App with a new `defaultStackSynthesizer`: + +```ts +const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: 'my-app-id', // put a unique id here + }), +}); +``` + +This will introduce a `DefaultStagingStack` in your CDK App and staging assets of your App +will live in the resources from that stack rather than the CDK Bootstrap stack. + +If you are migrating from a different version of synthesis your updated CDK App will target +the resources in the `DefaultStagingStack` and no longer be tied to the bootstrapped resources +in your account. + +## Bootstrap Model + +Our current bootstrap model looks like this, when you run `cdk bootstrap aws:///` : + +```text +┌───────────────────────────────────┐┌────────────────────────┐┌────────────────────────┐ +│ ││ ││ │ +│ ││ ││ │ +│ ┌───────────────┐ ││ ┌──────────────┐ ││ ┌──────────────┐ │ +│ │Bootstrap Stack│ ││ │ CDK App 1 │ ││ │ CDK App 2 │ │ +│ └───────────────┘ ││ └──────────────┘ ││ └──────────────┘ │ +│ ││ ││ │ +│ ││ ││ │ +│ ┌───────────────────────────┐ ││ ┌────────────┐ ││ │ +│ │IAM Role for CFN execution │ ││┌────│ S3 Asset │ ││ │ +│ │ IAM Role for lookup │ │││ └────────────┘ ││ │ +│ │ IAM Role for deployment │ │││ ││ │ +│ └───────────────────────────┘ │││ ││ ┌─────────────┐ │ +│ │││ ┌──────────┼┼─────│ S3 Asset │ │ +│ │││ │ ││ └─────────────┘ │ +│ ┌───────────────────────────────┐ │││ │ ││ │ +│ │ IAM Role for File Publishing │ │││ │ ││ │ +│ │ IAM Role for Image Publishing │ │││ │ ││ │ +│ └───────────────────────────────┘ │││ │ ││ │ +│ │││ │ ││ │ +│ ┌─────────────────────────────┐ │││ │ ││ │ +│ │S3 Bucket for Staging Assets │ │││ │ ││ │ +│ │ KMS Key encryption │◀─┼┼┴────────────┘ ││ ┌────────────┐ │ +│ └─────────────────────────────┘ ││ ┌──────────┼┼───── │ ECR Asset │ │ +│ ││ │ ││ └────────────┘ │ +│ ││ │ ││ │ +│┌─────────────────────────────────┐││ │ ││ │ +││ECR Repository for Staging Assets◀┼┼─────────────┘ ││ │ +│└─────────────────────────────────┘││ ││ │ +│ ││ ││ │ +│ ││ ││ │ +│ ││ ││ │ +│ ││ ││ │ +│ ││ ││ │ +│ ││ ││ │ +└───────────────────────────────────┘└────────────────────────┘└────────────────────────┘ +``` + +Your CDK Application utilizes these resources when deploying. For example, if you have a file asset, +it gets uploaded to the S3 Staging Bucket using the File Publishing Role when you run `cdk deploy`. + +This library introduces an alternate model to bootstrapping, by splitting out essential CloudFormation IAM roles +and staging resources. There will still be a Bootstrap Stack, but this will only contain IAM roles necessary for +CloudFormation deployment. Each CDK App will instead be in charge of its own staging resources, including the +S3 Bucket, ECR Repositories, and associated IAM roles. It works like this: + +The Staging Stack will contain, on a per-need basis, + +- 1 S3 Bucket with KMS encryption for all file assets in the CDK App. +- An ECR Repository _per_ image (and its revisions). +- IAM roles with access to the Bucket and Repositories. + +```text +┌─────────────────────────────┐┌───────────────────────────────────────┐┌───────────────────────────────────────┐ +│ ││ ││ │ +│ ┌───────────────┐ ││ ┌──────────────┐ ││ ┌──────────────┐ │ +│ │Bootstrap Stack│ ││ │ CDK App 1 │ ││ │ CDK App 2 │ │ +│ └───────────────┘ ││ └──────────────┘ ││ └──────────────┘ │ +│ ││┌──────────────────┐ ││┌──────────────────┐ │ +│ │││ ┌──────────────┐ │ │││ ┌──────────────┐ │ │ +│ │││ │Staging Stack │ │ │││ │Staging Stack │ │ │ +│ │││ └──────────────┘ │ │││ └──────────────┘ │ │ +│ │││ │ │││ │ │ +│ │││ │ │││ │ │ +│ │││┌────────────────┐│ ┌────────────┐│││┌────────────────┐│ ┌────────────┐│ +│ ││││ IAM Role for ││ ┌───│ S3 Asset │││││ IAM Role for ││ ┌───│ S3 Asset ││ +│ ││││File Publishing ││ │ └────────────┘││││File Publishing ││ │ └────────────┘│ +│ │││└────────────────┘│ │ ││││ IAM Role for ││ │ │ +│ │││ │ │ ││││Image Publishing││ │ │ +│┌───────────────────────────┐│││ │ │ │││└────────────────┘│ │ │ +││IAM Role for CFN execution ││││ │ │ │││ │ │ │ +││ IAM Role for lookup ││││ │ │ │││ │ │ │ +││ IAM Role for deployment ││││┌────────────────┐│ │ │││┌────────────────┐│ │ │ +│└───────────────────────────┘││││ S3 Bucket for ││ │ ││││ S3 Bucket for ││ │ │ +│ ││││ Staging Assets │◀─┘ ││││ Staging Assets │◀─┘ │ +│ │││└────────────────┘│ │││└────────────────┘│ ┌───────────┐│ +│ │││ │ │││ │ ┌───│ ECR Asset ││ +│ │││ │ │││┌────────────────┐│ │ └───────────┘│ +│ │││ │ ││││ ECR Repository ││ │ │ +│ │││ │ ││││ for Staging │◀──┘ │ +│ │││ │ ││││ Assets ││ │ +│ │││ │ │││└────────────────┘│ │ +│ │││ │ │││ │ │ +│ │││ │ │││ │ │ +│ │││ │ │││ │ │ +│ │││ │ │││ │ │ +│ │││ │ │││ │ │ +│ ││└──────────────────┘ ││└──────────────────┘ │ +└─────────────────────────────┘└───────────────────────────────────────┘└───────────────────────────────────────┘ +``` + +This allows staging resources to be created when needed next to the CDK App. It has the following +benefits: + +- Resources between separate CDK Apps are separated so they can be cleaned up and lifecycle +controlled individually. +- Users have a familiar way to customize staging resources in the CDK Application. + +> As this library is `experimental`, the accompanying Bootstrap Stack is not yet implemented. To use this +> library right now, you must reuse roles that have been traditionally bootstrapped. + +## Using the Default Staging Stack per Environment + +The most common use case will be to use the built-in default resources. In this scenario, the +synthesizer will create a new Staging Stack in each environment the CDK App is deployed to store +its staging resources. To use this kind of synthesizer, use `AppStagingSynthesizer.defaultResources()`. + +```ts +const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: 'my-app-id', + }), +}); +``` + +Every CDK App that uses the `DefaultStagingStack` must include an `appId`. This should +be an identifier unique to the app and is used to differentiate staging resources associated +with the app. + +### Default Staging Stack + +The Default Staging Stack includes all the staging resources necessary for CDK Assets. The below example +is of a CDK App using the `AppStagingSynthesizer` and creating a file asset for the Lambda Function +source code. As part of the `DefaultStagingStack`, an S3 bucket and IAM role will be created that will be +used to upload the asset to S3. + +```ts +const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ appId: 'my-app-id' }), +}); + +const stack = new Stack(app, 'my-stack'); + +new lambda.Function(stack, 'lambda', { + code: lambda.AssetCode.fromAsset(path.join(__dirname, 'assets')), + handler: 'index.handler', + runtime: lambda.Runtime.PYTHON_3_9, +}); + +app.synth(); +``` + +### Custom Roles + +You can customize some or all of the roles you'd like to use in the synthesizer as well, +if all you need is to supply custom roles (and not change anything else in the `DefaultStagingStack`): + +```ts +const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: 'my-app-id', + deploymentIdentities: DeploymentIdentities.specifyRoles({ + cloudFormationExecutionRole: BootstrapRole.fromRoleArn('arn:aws:iam::123456789012:role/Execute'), + deploymentRole: BootstrapRole.fromRoleArn('arn:aws:iam::123456789012:role/Deploy'), + lookupRole: BootstrapRole.fromRoleArn('arn:aws:iam::123456789012:role/Lookup'), + }), + }), +}); +``` + +Or, you can ask to use the CLI credentials that exist at deploy-time. +These credentials must have the ability to perform CloudFormation calls, +lookup resources in your account, and perform CloudFormation deployment. +For a full list of what is necessary, see `LookupRole`, `DeploymentActionRole`, +and `CloudFormationExecutionRole` in the +[bootstrap template](https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml). + +```ts +const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: 'my-app-id', + deploymentIdentities: DeploymentIdentities.cliCredentials(), + }), +}); +``` + +The default staging stack will create roles to publish to the S3 bucket and ECR repositories, +assumable by the deployment role. You can also specify an existing IAM role for the +`fileAssetPublishingRole` or `imageAssetPublishingRole`: + +```ts +const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: 'my-app-id', + fileAssetPublishingRole: BootstrapRole.fromRoleArn('arn:aws:iam::123456789012:role/S3Access'), + imageAssetPublishingRole: BootstrapRole.fromRoleArn('arn:aws:iam::123456789012:role/ECRAccess'), + }), +}); +``` + +### Deploy Time S3 Assets + +There are two types of assets: + +- Assets used only during deployment. These are used to hand off a large piece of data to another +service, that will make a private copy of that data. After deployment, the asset is only necessary for +a potential future rollback. +- Assets accessed throughout the running life time of the application. + +Examples of assets that are only used at deploy time are CloudFormation Templates and Lambda Code +bundles. Examples of assets accessed throughout the life time of the application are script files +downloaded to run in a CodeBuild Project, or on EC2 instance startup. ECR images are always application +life-time assets. S3 deploy time assets are stored with a `deploy-time/` prefix, and a lifecycle rule will collect them after a configurable number of days. + +Lambda assets are by default marked as deploy time assets: + +```ts +declare const stack: Stack; +new lambda.Function(stack, 'lambda', { + code: lambda.AssetCode.fromAsset(path.join(__dirname, 'assets')), // lambda marks deployTime = true + handler: 'index.handler', + runtime: lambda.Runtime.PYTHON_3_9, +}); +``` + +Or, if you want to create your own deploy time asset: + +```ts +import { Asset } from 'aws-cdk-lib/aws-s3-assets'; + +declare const stack: Stack; +const asset = new Asset(stack, 'deploy-time-asset', { + deployTime: true, + path: path.join(__dirname, './deploy-time-asset'), +}); +``` + +By default, we store deploy time assets for 30 days, but you can change this number by specifying +`deployTimeFileAssetLifetime`. The number you specify here is how long you will be able to roll back +to a previous version of an application just by doing a CloudFormation deployment with the old +template, without rebuilding and republishing assets. + +```ts +const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: 'my-app-id', + deployTimeFileAssetLifetime: Duration.days(100), + }), +}); +``` + +### Lifecycle Rules on ECR Repositories + +By default, we store a maximum of 3 revisions of a particular docker image asset. This allows +for smooth faciliation of rollback scenarios where we may reference previous versions of an +image. When more than 3 revisions of an asset exist in the ECR repository, the oldest one is +purged. + +To change the number of revisions stored, use `imageAssetVersionCount`: + +```ts +const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: 'my-app-id', + imageAssetVersionCount: 10, + }), +}); +``` + +## Using a Custom Staging Stack per Environment + +If you want to customize some behavior that is not configurable via properties, +you can implement your own class that implements `IStagingResources`. To get a head start, +you can subclass `DefaultStagingStack`. + +```ts +interface CustomStagingStackOptions extends DefaultStagingStackOptions {} + +class CustomStagingStack extends DefaultStagingStack { +} +``` + +Or you can roll your own staging resources from scratch, as long as it implements `IStagingResources`. + +```ts +interface CustomStagingStackProps extends StackProps {} + +class CustomStagingStack extends Stack implements IStagingResources { + public constructor(scope: Construct, id: string, props: CustomStagingStackProps) { + super(scope, id, props); + } + + public addFile(asset: FileAssetSource): FileStagingLocation { + return { + bucketName: 'myBucket', + assumeRoleArn: 'myArn', + dependencyStack: this, + }; + } + + public addDockerImage(asset: DockerImageAssetSource): ImageStagingLocation { + return { + repoName: 'myRepo', + assumeRoleArn: 'myArn', + dependencyStack: this, + }; + } +} +``` + +Using your custom staging resources means implementing a `CustomFactory` class and calling the +`AppStagingSynthesizer.customFactory()` static method. This has the benefit of providing a +custom Staging Stack that can be created in every environment the CDK App is deployed to. + +```ts fixture=with-custom-staging +class CustomFactory implements IStagingResourcesFactory { + public obtainStagingResources(stack: Stack, context: ObtainStagingResourcesContext) { + const myApp = App.of(stack); + + return new CustomStagingStack(myApp!, `CustomStagingStack-${context.environmentString}`, {}); + } +} + +const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.customFactory({ + factory: new CustomFactory(), + oncePerEnv: true, // by default + }), +}); +``` + +## Using an Existing Staging Stack + +Use `AppStagingSynthesizer.customResources()` to supply an existing stack as the Staging Stack. +Make sure that the custom stack you provide implements `IStagingResources`. + +```ts fixture=with-custom-staging +const resourceApp = new App(); +const resources = new CustomStagingStack(resourceApp, 'CustomStagingStack', {}); + +const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.customResources({ + resources, + }), +}); +``` diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/adr/resource-names.md b/packages/@aws-cdk/app-staging-synthesizer-alpha/adr/resource-names.md new file mode 100644 index 0000000000000..f718afc5ca0e6 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/adr/resource-names.md @@ -0,0 +1,30 @@ +# Staging Stack Resource Names + +The Staging Stack can produce the following types of resources, depending on what is needed for the app: + +- iam role (file publishing role and asset publishing role) +- s3 bucket (one per app) +- ecr repository (one per image asset family) + +These resources need to be named unique to their scope to avoid CloudFormation errors when trying to create +a resource with an existing name. The resource specific limitations are as follows: + +- iam role names: must be unique to their account +- s3 bucket names: must be globally unique +- ecr repository names: must be unique to their account/region + +The attributes we can use to name our resources are as follows: + +- account number (i.e. `123456789012`) +- region name (i.e. `us-east-1`) +- app id (a user-specified id that should be unique to the app) +- image id (a user-specified id added on image assets) + +This information can be distilled into the following table, which shows what identifiers are necessary to +make each resource name unique: + +| Resource | Account | Region | App Id | Image Id | +| --------- | ------- | ------ | ------ | -------- | +| iam roles | | ✔️ | ✔️ | | +| s3 bucket | ✔️ | ✔️ | ✔️ ️️ | | +| ecr repos | | | ✔️ | ✔️ | diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/jest.config.js b/packages/@aws-cdk/app-staging-synthesizer-alpha/jest.config.js new file mode 100644 index 0000000000000..87e3ed1d7117c --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/jest.config.js @@ -0,0 +1,4 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/jest.config'); +module.exports = { + ...baseConfig, +}; diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/app-staging-synthesizer.ts b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/app-staging-synthesizer.ts new file mode 100644 index 0000000000000..21f7290d19f4b --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/app-staging-synthesizer.ts @@ -0,0 +1,372 @@ +import { + AssetManifestBuilder, + BOOTSTRAP_QUALIFIER_CONTEXT, + DockerImageAssetLocation, + DockerImageAssetSource, + FileAssetLocation, + FileAssetSource, + IBoundStackSynthesizer as IBoundAppStagingSynthesizer, + IReusableStackSynthesizer, + ISynthesisSession, + Stack, + StackSynthesizer, + Token, +} from 'aws-cdk-lib'; +import { StringSpecializer, translateCfnTokenToAssetToken } from 'aws-cdk-lib/core/lib/helpers-internal'; +import { BootstrapRole, BootstrapRoles, DeploymentIdentities } from './bootstrap-roles'; +import { DefaultStagingStack, DefaultStagingStackOptions } from './default-staging-stack'; +import { PerEnvironmentStagingFactory as PerEnvironmentStagingFactory } from './per-env-staging-factory'; +import { AppScopedGlobal } from './private/app-global'; +import { validateNoTokens } from './private/no-tokens'; +import { IStagingResources, IStagingResourcesFactory, ObtainStagingResourcesContext } from './staging-stack'; + +const AGNOSTIC_STACKS = new AppScopedGlobal(() => new Set()); +const ENV_AWARE_STACKS = new AppScopedGlobal(() => new Set()); + +/** + * Options that apply to all AppStagingSynthesizer variants + */ +export interface AppStagingSynthesizerOptions { + /** + * What roles to use to deploy applications + * + * These are the roles that have permissions to interact with CloudFormation + * on your behalf. By default these are the standard bootstrapped CDK roles, + * but you can customize them or turn them off and use the CLI credentials + * to deploy. + * + * @default - The standard bootstrapped CDK roles + */ + readonly deploymentIdentities?: DeploymentIdentities; + + /** + * Qualifier to disambiguate multiple bootstrapped environments in the same account + * + * This qualifier is only used to reference bootstrapped resources. It will not + * be used in the creation of app-specific staging resources: `appId` is used for that + * instead. + * + * @default - Value of context key '@aws-cdk/core:bootstrapQualifier' if set, otherwise `DEFAULT_QUALIFIER` + */ + readonly bootstrapQualifier?: string; +} + +/** + * Properties for stackPerEnv static method + */ +export interface DefaultResourcesOptions extends AppStagingSynthesizerOptions, DefaultStagingStackOptions {} + +/** + * Properties for customFactory static method + */ +export interface CustomFactoryOptions extends AppStagingSynthesizerOptions { + /** + * The factory that will be used to return staging resources for each stack + */ + readonly factory: IStagingResourcesFactory; + + /** + * Reuse the answer from the factory for stacks in the same environment + * + * @default true + */ + readonly oncePerEnv?: boolean; +} + +/** + * Properties for customResources static method + */ +export interface CustomResourcesOptions extends AppStagingSynthesizerOptions { + /** + * Use these exact staging resources for every stack that this synthesizer is used for + */ + readonly resources: IStagingResources; +} + +/** + * Internal properties for AppStagingSynthesizer + */ +interface AppStagingSynthesizerProps extends AppStagingSynthesizerOptions { + /** + * A factory method that creates an IStagingStack when given the stack the + * synthesizer is binding. + */ + readonly factory: IStagingResourcesFactory; +} + +/** + * App Staging Synthesizer + */ +export class AppStagingSynthesizer extends StackSynthesizer implements IReusableStackSynthesizer { + /** + * Default ARN qualifier + */ + public static readonly DEFAULT_QUALIFIER = 'hnb659fds'; + + /** + * Default CloudFormation role ARN. + */ + public static readonly DEFAULT_CLOUDFORMATION_ROLE_ARN = 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-${Qualifier}-cfn-exec-role-${AWS::AccountId}-${AWS::Region}'; + + /** + * Default deploy role ARN. + */ + public static readonly DEFAULT_DEPLOY_ROLE_ARN = 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-${Qualifier}-deploy-role-${AWS::AccountId}-${AWS::Region}'; + + /** + * Default lookup role ARN for missing values. + */ + public static readonly DEFAULT_LOOKUP_ROLE_ARN = 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-${Qualifier}-lookup-role-${AWS::AccountId}-${AWS::Region}'; + + /** + * Use the Default Staging Resources, creating a single stack per environment this app is deployed in + */ + public static defaultResources(options: DefaultResourcesOptions) { + validateNoTokens(options, 'AppStagingSynthesizer'); + + return AppStagingSynthesizer.customFactory({ + factory: DefaultStagingStack.factory(options), + deploymentIdentities: options.deploymentIdentities, + bootstrapQualifier: options.bootstrapQualifier, + oncePerEnv: true, + }); + } + + /** + * Use these exact staging resources for every stack that this synthesizer is used for + */ + public static customResources(options: CustomResourcesOptions) { + return AppStagingSynthesizer.customFactory({ + deploymentIdentities: options.deploymentIdentities, + bootstrapQualifier: options.bootstrapQualifier, + oncePerEnv: false, + factory: { + obtainStagingResources() { + return options.resources; + }, + }, + }); + } + + /** + * Supply your own stagingStackFactory method for creating an IStagingStack when + * a stack is bound to the synthesizer. + * + * By default, `oncePerEnv = true`, which means that a new instance of the IStagingStack + * will be created in new environments. Set `oncePerEnv = false` to turn off that behavior. + */ + public static customFactory(options: CustomFactoryOptions) { + const oncePerEnv = options.oncePerEnv ?? true; + const factory = oncePerEnv ? new PerEnvironmentStagingFactory(options.factory) : options.factory; + + return new AppStagingSynthesizer({ + factory, + bootstrapQualifier: options.bootstrapQualifier, + deploymentIdentities: options.deploymentIdentities, + }); + } + + private readonly roles: Required; + + private constructor(private readonly props: AppStagingSynthesizerProps) { + super(); + + this.roles = { + deploymentRole: props.deploymentIdentities?.roles.deploymentRole ?? + BootstrapRole.fromRoleArn(AppStagingSynthesizer.DEFAULT_DEPLOY_ROLE_ARN), + cloudFormationExecutionRole: props.deploymentIdentities?.roles.cloudFormationExecutionRole ?? + BootstrapRole.fromRoleArn(AppStagingSynthesizer.DEFAULT_CLOUDFORMATION_ROLE_ARN), + lookupRole: this.props.deploymentIdentities?.roles.lookupRole ?? + BootstrapRole.fromRoleArn(AppStagingSynthesizer.DEFAULT_LOOKUP_ROLE_ARN), + }; + } + + /** + * Returns a version of the synthesizer bound to a stack. + */ + public reusableBind(stack: Stack): IBoundAppStagingSynthesizer { + this.checkEnvironmentGnosticism(stack); + const qualifier = this.props.bootstrapQualifier ?? + stack.node.tryGetContext(BOOTSTRAP_QUALIFIER_CONTEXT) ?? + AppStagingSynthesizer.DEFAULT_QUALIFIER; + const spec = new StringSpecializer(stack, qualifier); + + const deployRole = this.roles.deploymentRole._specialize(spec); + + const context: ObtainStagingResourcesContext = { + environmentString: [ + Token.isUnresolved(stack.account) ? 'ACCOUNT' : stack.account, + Token.isUnresolved(stack.region) ? 'REGION' : stack.region, + ].join('-'), + deployRoleArn: deployRole._arnForCloudFormation(), + qualifier, + }; + + return new BoundAppStagingSynthesizer(stack, { + stagingResources: this.props.factory.obtainStagingResources(stack, context), + deployRole, + cloudFormationExecutionRole: this.roles.cloudFormationExecutionRole._specialize(spec), + lookupRole: this.roles.lookupRole._specialize(spec), + qualifier, + }); + } + + /** + * Implemented for legacy purposes; this will never be called. + */ + public bind(_stack: Stack) { + throw new Error('This is a legacy API, call reusableBind instead'); + } + + /** + * Implemented for legacy purposes; this will never be called. + */ + public synthesize(_session: ISynthesisSession): void { + throw new Error('This is a legacy API, call reusableBind instead'); + } + + /** + * Implemented for legacy purposes; this will never be called. + */ + public addFileAsset(_asset: FileAssetSource): FileAssetLocation { + throw new Error('This is a legacy API, call reusableBind instead'); + } + + /** + * Implemented for legacy purposes; this will never be called. + */ + public addDockerImageAsset(_asset: DockerImageAssetSource): DockerImageAssetLocation { + throw new Error('This is a legacy API, call reusableBind instead'); + } + + /** + * Check that we're only being used for exclusively gnostic or agnostic stacks. + * + * We can think about whether to loosen this requirement later. + */ + private checkEnvironmentGnosticism(stack: Stack) { + const isAgnostic = Token.isUnresolved(stack.account) || Token.isUnresolved(stack.region); + const agnosticStacks = AGNOSTIC_STACKS.for(stack); + const envAwareStacks = ENV_AWARE_STACKS.for(stack); + + (isAgnostic ? agnosticStacks : envAwareStacks).add(stack); + if (agnosticStacks.size > 0 && envAwareStacks.size > 0) { + + const describeStacks = (xs: Set) => Array.from(xs).map(s => s.node.path).join(', '); + + throw new Error([ + 'It is not safe to use AppStagingSynthesizer for both environment-agnostic and environment-aware stacks at the same time.', + 'Please either specify environments for all stacks or no stacks in the CDK App.', + `Stacks with environment: ${describeStacks(agnosticStacks)}.`, + `Stacks without environment: ${describeStacks(envAwareStacks)}.`, + ].join(' ')); + } + } +} + +/** + * Internal properties for BoundAppStagingSynthesizer + */ +interface BoundAppStagingSynthesizerProps { + /** + * The bootstrap qualifier + */ + readonly qualifier: string; + + /** + * The resources we end up using for this synthesizer + */ + readonly stagingResources: IStagingResources; + + /** + * The deploy role + */ + readonly deployRole: BootstrapRole; + + /** + * CloudFormation Execution Role + */ + readonly cloudFormationExecutionRole: BootstrapRole; + + /** + * Lookup Role + */ + readonly lookupRole: BootstrapRole; +} + +class BoundAppStagingSynthesizer extends StackSynthesizer implements IBoundAppStagingSynthesizer { + private readonly stagingStack: IStagingResources; + private readonly assetManifest = new AssetManifestBuilder(); + private readonly qualifier: string; + private readonly dependencyStacks: Set = new Set(); + + constructor(stack: Stack, private readonly props: BoundAppStagingSynthesizerProps) { + super(); + super.bind(stack); + + this.qualifier = props.qualifier; + this.stagingStack = props.stagingResources; + } + /** + * The qualifier used to bootstrap this stack + */ + public get bootstrapQualifier(): string | undefined { + // Not sure why we need this. + return this.qualifier; + } + + public synthesize(session: ISynthesisSession): void { + const templateAssetSource = this.synthesizeTemplate(session, this.props.lookupRole?._arnForCloudAssembly()); + const templateAsset = this.addFileAsset(templateAssetSource); + + const dependencies = Array.from(this.dependencyStacks).flatMap((d) => d.artifactId); + const assetManifestId = this.assetManifest.emitManifest(this.boundStack, session, {}, dependencies); + + const lookupRoleArn = this.props.lookupRole?._arnForCloudAssembly(); + + this.emitArtifact(session, { + assumeRoleArn: this.props.deployRole?._arnForCloudAssembly(), + additionalDependencies: [assetManifestId], + stackTemplateAssetObjectUrl: templateAsset.s3ObjectUrlWithPlaceholders, + cloudFormationExecutionRoleArn: this.props.cloudFormationExecutionRole?._arnForCloudAssembly(), + lookupRole: lookupRoleArn ? { arn: lookupRoleArn } : undefined, + }); + } + + /** + * Add a file asset to the manifest. + */ + public addFileAsset(asset: FileAssetSource): FileAssetLocation { + const { bucketName, assumeRoleArn, prefix, dependencyStack } = this.stagingStack.addFile(asset); + const location = this.assetManifest.defaultAddFileAsset(this.boundStack, asset, { + bucketName: translateCfnTokenToAssetToken(bucketName), + bucketPrefix: prefix, + role: assumeRoleArn ? { assumeRoleArn: translateCfnTokenToAssetToken(assumeRoleArn) } : undefined, + }); + + if (dependencyStack) { + this.boundStack.addDependency(dependencyStack, 'stack depends on the staging stack for staging resources'); + this.dependencyStacks.add(dependencyStack); + } + + return this.cloudFormationLocationFromFileAsset(location); + } + + /** + * Add a docker image asset to the manifest. + */ + public addDockerImageAsset(asset: DockerImageAssetSource): DockerImageAssetLocation { + const { repoName, assumeRoleArn, dependencyStack } = this.stagingStack.addDockerImage(asset); + const location = this.assetManifest.defaultAddDockerImageAsset(this.boundStack, asset, { + repositoryName: translateCfnTokenToAssetToken(repoName), + role: assumeRoleArn ? { assumeRoleArn: translateCfnTokenToAssetToken(assumeRoleArn) } : undefined, + }); + + if (dependencyStack) { + this.boundStack.addDependency(dependencyStack, 'stack depends on the staging stack for staging resources'); + this.dependencyStacks.add(dependencyStack); + } + + return this.cloudFormationLocationFromDockerImageAsset(location); + } +} diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/bootstrap-roles.ts b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/bootstrap-roles.ts new file mode 100644 index 0000000000000..a5454ca51d021 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/bootstrap-roles.ts @@ -0,0 +1,130 @@ +import { StringSpecializer, translateAssetTokenToCfnToken, translateCfnTokenToAssetToken } from 'aws-cdk-lib/core/lib/helpers-internal'; + +/** + * Bootstrapped role specifier. These roles must exist already. + * This class does not create new IAM Roles. + */ +export class BootstrapRole { + /** + * Use the currently assumed role/credentials + */ + public static cliCredentials() { + return new BootstrapRole(BootstrapRole.CLI_CREDS); + } + + /** + * Specify an existing IAM Role to assume + */ + public static fromRoleArn(arn: string) { + StringSpecializer.validateNoTokens(arn, 'BootstrapRole ARN'); + return new BootstrapRole(arn); + } + + private static CLI_CREDS = 'cli-credentials'; + + private constructor(private readonly roleArn: string) {} + + /** + * Whether or not this is object was created using BootstrapRole.cliCredentials() + */ + public isCliCredentials() { + return this.roleArn === BootstrapRole.CLI_CREDS; + } + + /** + * @internal + */ + public _arnForCloudFormation() { + return this.isCliCredentials() ? undefined : translateAssetTokenToCfnToken(this.roleArn); + } + + /** + * @internal + */ + public _arnForCloudAssembly() { + return this.isCliCredentials() ? undefined : translateCfnTokenToAssetToken(this.roleArn); + } + + /** + * @internal + */ + public _specialize(spec: StringSpecializer) { + return new BootstrapRole(spec.specialize(this.roleArn)); + } +} + +/** + * Deployment identities are the class of roles to be assumed by the CDK + * when deploying the App. + */ +export class DeploymentIdentities { + /** + * Use CLI credentials for all deployment identities. + */ + public static cliCredentials(): DeploymentIdentities { + return new DeploymentIdentities({ + cloudFormationExecutionRole: BootstrapRole.cliCredentials(), + deploymentRole: BootstrapRole.cliCredentials(), + lookupRole: BootstrapRole.cliCredentials(), + }); + } + + /** + * Specify your own roles for all deployment identities. These roles + * must already exist. + */ + public static specifyRoles(roles: BootstrapRoles): DeploymentIdentities { + return new DeploymentIdentities(roles); + } + + private constructor( + /** roles that are bootstrapped to your account. */ + public readonly roles: BootstrapRoles, + ) {} +} + +/** + * Roles that are bootstrapped to your account. + */ +export interface BootstrapRoles { + /** + * CloudFormation Execution Role + * + * @default - use bootstrapped role + */ + readonly cloudFormationExecutionRole?: BootstrapRole; + + /** + * Deployment Action Role + * + * @default - use boostrapped role + */ + readonly deploymentRole?: BootstrapRole; + + /** + * Lookup Role + * + * @default - use bootstrapped role + */ + readonly lookupRole?: BootstrapRole; +} + +/** + * Roles that are included in the Staging Stack + * (for access to Staging Resources) + */ +export interface StagingRoles { + /** + * File Asset Publishing Role + * + * @default - staging stack creates a file asset publishing role + */ + readonly fileAssetPublishingRole?: BootstrapRole; + + /** + * Docker Asset Publishing Role + * + * @default - staging stack creates a docker asset publishing role + */ + readonly dockerAssetPublishingRole?: BootstrapRole; +} diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/default-staging-stack.ts b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/default-staging-stack.ts new file mode 100644 index 0000000000000..468323e0ec2c4 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/default-staging-stack.ts @@ -0,0 +1,425 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { + App, + ArnFormat, + BootstraplessSynthesizer, + DockerImageAssetSource, + Duration, + FileAssetSource, + ISynthesisSession, + RemovalPolicy, + Stack, + StackProps, +} from 'aws-cdk-lib'; +import * as ecr from 'aws-cdk-lib/aws-ecr'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import { StringSpecializer } from 'aws-cdk-lib/core/lib/helpers-internal'; +import { BootstrapRole } from './bootstrap-roles'; +import { FileStagingLocation, IStagingResources, IStagingResourcesFactory, ImageStagingLocation } from './staging-stack'; + +export const DEPLOY_TIME_PREFIX = 'deploy-time/'; + +/** + * User configurable options to the DefaultStagingStack. + */ +export interface DefaultStagingStackOptions { + /** + * A unique identifier for the application that the staging stack belongs to. + * + * This identifier will be used in the name of staging resources + * created for this application, and should be unique across CDK apps. + * + * The identifier should include lowercase characters and dashes ('-') only + * and have a maximum of 20 characters. + */ + readonly appId: string; + + /** + * Explicit name for the staging bucket + * + * @default - a well-known name unique to this app/env. + */ + readonly stagingBucketName?: string; + + /** + * Pass in an existing role to be used as the file publishing role. + * + * @default - a new role will be created + */ + readonly fileAssetPublishingRole?: BootstrapRole; + + /** + * Pass in an existing role to be used as the image publishing role. + * + * @default - a new role will be created + */ + readonly imageAssetPublishingRole?: BootstrapRole; + + /** + * The lifetime for deploy time file assets. + * + * Assets that are only necessary at deployment time (for instance, + * CloudFormation templates and Lambda source code bundles) will be + * automatically deleted after this many days. Assets that may be + * read from the staging bucket during your application's run time + * will not be deleted. + * + * Set this to the length of time you wish to be able to roll back to + * previous versions of your application without having to do a new + * `cdk synth` and re-upload of assets. + * + * @default - Duration.days(30) + */ + readonly deployTimeFileAssetLifetime?: Duration; + + /** + * The maximum number of image versions to store in a repository. + * + * Previous versions of an image can be stored for rollback purposes. + * Once a repository has more than 3 image versions stored, the oldest + * version will be discarded. This allows for sensible garbage collection + * while maintaining a few previous versions for rollback scenarios. + * + * @default - up to 3 versions stored + */ + readonly imageAssetVersionCount?: number; +} + +/** + * Default Staging Stack Properties + */ +export interface DefaultStagingStackProps extends DefaultStagingStackOptions, StackProps { + /** + * The ARN of the deploy action role, if given + * + * This role will need permissions to read from to the staging resources. + * + * @default - The CLI credentials are assumed, no additional permissions are granted. + */ + readonly deployRoleArn?: string; + + /** + * The qualifier used to specialize strings + * + * Shouldn't be necessary but who knows what people might do. + */ + readonly qualifier: string; +} + +/** + * A default Staging Stack that implements IStagingResources. + */ +export class DefaultStagingStack extends Stack implements IStagingResources { + /** + * Return a factory that will create DefaultStagingStacks + */ + public static factory(options: DefaultStagingStackOptions): IStagingResourcesFactory { + const appId = options.appId.toLocaleLowerCase().replace(/[^a-z0-9-]/g, '-').slice(0, 20); + return { + obtainStagingResources(stack, context) { + const app = App.of(stack); + if (!App.isApp(app)) { + throw new Error(`Stack ${stack.stackName} must be part of an App`); + } + + const stackId = `StagingStack-${appId}-${context.environmentString}`; + return new DefaultStagingStack(app, stackId, { + ...options, + + // Does not need to contain environment because stack names are unique inside an env anyway + stackName: `StagingStack-${appId}`, + env: { + account: stack.account, + region: stack.region, + }, + appId, + qualifier: context.qualifier, + deployRoleArn: context.deployRoleArn, + }); + }, + }; + } + + /** + * Default asset publishing role name for file (S3) assets. + */ + private get fileRoleName() { + // This role name can be a maximum of 64 letters. The reason why + // we slice the appId and not the entire name is because this.region + // can be a token and we don't want to accidentally cut it off. + return `cdk-${this.appId}-file-role-${this.region}`; + } + + /** + * Default asset publishing role name for docker (ECR) assets. + */ + private get imageRoleName() { + // This role name can be a maximum of 64 letters. The reason why + // we slice the appId and not the entire name is because this.region + // can be a token and we don't want to accidentally cut it off. + return `cdk-${this.appId}-image-role-${this.region}`; + } + + /** + * The app-scoped, evironment-keyed staging bucket. + */ + public readonly stagingBucket?: s3.Bucket; + + /** + * The app-scoped, environment-keyed ecr repositories associated with this app. + */ + public readonly stagingRepos: Record; + + /** + * The stack to add dependencies to. + */ + public readonly dependencyStack: Stack; + + private readonly appId: string; + private readonly stagingBucketName?: string; + + /** + * File publish role ARN in asset manifest format + */ + private readonly providedFileRole?: BootstrapRole; + private fileRole?: iam.IRole; + private fileRoleManifestArn?: string; + + /** + * Image publishing role ARN in asset manifest format + */ + private readonly providedImageRole?: BootstrapRole; + private imageRole?: iam.IRole; + private didImageRole = false; + private imageRoleManifestArn?: string; + + private readonly deployRoleArn?: string; + + constructor(scope: App, id: string, private readonly props: DefaultStagingStackProps) { + super(scope, id, { + ...props, + synthesizer: new BootstraplessSynthesizer(), + }); + + this.appId = this.validateAppId(props.appId); + this.dependencyStack = this; + + this.deployRoleArn = props.deployRoleArn; + this.stagingBucketName = props.stagingBucketName; + const specializer = new StringSpecializer(this, props.qualifier); + + this.providedFileRole = props.fileAssetPublishingRole?._specialize(specializer); + this.providedImageRole = props.imageAssetPublishingRole?._specialize(specializer); + this.stagingRepos = {}; + } + + private validateAppId(id: string) { + const errors = []; + if (id.length > 20) { + errors.push(`appId expected no more than 20 characters but got ${id.length} characters.`); + } + if (id !== id.toLocaleLowerCase()) { + errors.push('appId only accepts lowercase characters.'); + } + if (!/^[a-z0-9-]*$/.test(id)) { + errors.push('appId expects only letters, numbers, and dashes (\'-\')'); + } + + if (errors.length > 0) { + throw new Error([ + `appId ${id} has errors:`, + ...errors, + ].join('\n')); + } + return id; + } + + private ensureFileRole() { + if (this.providedFileRole) { + // Override + this.fileRoleManifestArn = this.providedFileRole._arnForCloudAssembly(); + const cfnArn = this.providedFileRole._arnForCloudFormation(); + this.fileRole = cfnArn ? iam.Role.fromRoleArn(this, 'CdkFileRole', cfnArn) : undefined; + return; + } + + const roleName = this.fileRoleName; + this.fileRole = new iam.Role(this, 'CdkFileRole', { + roleName, + assumedBy: new iam.AccountPrincipal(this.account), + }); + + this.fileRoleManifestArn = Stack.of(this).formatArn({ + partition: '${AWS::Partition}', + region: '', // iam is global + service: 'iam', + resource: 'role', + resourceName: roleName, + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, + }); + } + + private ensureImageRole() { + // It may end up setting imageRole to undefined, but at least we tried + if (this.didImageRole) { + return; + } + this.didImageRole = true; + + if (this.providedImageRole) { + // Override + this.imageRoleManifestArn = this.providedImageRole._arnForCloudAssembly(); + const cfnArn = this.providedImageRole._arnForCloudFormation(); + this.imageRole = cfnArn ? iam.Role.fromRoleArn(this, 'CdkImageRole', cfnArn) : undefined; + return; + } + + const roleName = this.imageRoleName; + this.imageRole = new iam.Role(this, 'CdkImageRole', { + roleName, + assumedBy: new iam.AccountPrincipal(this.account), + }); + this.imageRoleManifestArn = Stack.of(this).formatArn({ + partition: '${AWS::Partition}', + region: '', // iam is global + service: 'iam', + resource: 'role', + resourceName: roleName, + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, + }); + } + + private createBucketKey(): kms.IKey { + return new kms.Key(this, 'BucketKey', { + alias: `alias/cdk-${this.appId}-staging`, + admins: [new iam.AccountPrincipal(this.account)], + }); + } + + private getCreateBucket() { + const stagingBucketName = this.stagingBucketName ?? `cdk-${this.appId}-staging-${this.account}-${this.region}`; + const bucketId = 'CdkStagingBucket'; + const createdBucket = this.node.tryFindChild(bucketId) as s3.Bucket; + if (createdBucket) { + return stagingBucketName; + } + + this.ensureFileRole(); + const key = this.createBucketKey(); + + // Create the bucket once the dependencies have been created + const bucket = new s3.Bucket(this, bucketId, { + bucketName: stagingBucketName, + removalPolicy: RemovalPolicy.RETAIN, + encryption: s3.BucketEncryption.KMS, + encryptionKey: key, + + // Many AWS account safety checkers will complain when buckets aren't versioned + versioned: true, + // Many AWS account safety checkers will complain when SSL isn't enforced + enforceSSL: true, + }); + + if (this.fileRole) { + bucket.grantReadWrite(this.fileRole); + } + + if (this.deployRoleArn) { + bucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: [ + 's3:GetObject*', + 's3:GetBucket*', + 's3:List*', + ], + resources: [bucket.bucketArn, bucket.arnForObjects('*')], + principals: [new iam.ArnPrincipal(this.deployRoleArn)], + })); + } + + // Objects should never be overwritten, but let's make sure we have a lifecycle policy + // for it anyway. + bucket.addLifecycleRule({ + noncurrentVersionExpiration: Duration.days(365), + }); + + bucket.addLifecycleRule({ + prefix: DEPLOY_TIME_PREFIX, + expiration: this.props.deployTimeFileAssetLifetime ?? Duration.days(30), + }); + + return stagingBucketName; + } + + /** + * Returns the well-known name of the repo + */ + private getCreateRepo(asset: DockerImageAssetSource): string { + if (!asset.assetName) { + throw new Error('Assets synthesized with AppScopedStagingSynthesizer must include an \'assetName\' in the asset source definition.'); + } + + // Create image publishing role if it doesn't exist + this.ensureImageRole(); + + const repoName = generateRepoName(`${this.appId}/${asset.assetName}`); + if (this.stagingRepos[asset.assetName] === undefined) { + this.stagingRepos[asset.assetName] = new ecr.Repository(this, repoName, { + repositoryName: repoName, + lifecycleRules: [{ + description: 'Garbage collect old image versions and keep the specified number of latest versions', + maxImageCount: this.props.imageAssetVersionCount ?? 3, + }], + }); + if (this.imageRole) { + this.stagingRepos[asset.assetName].grantPullPush(this.imageRole); + this.stagingRepos[asset.assetName].grantRead(this.imageRole); + } + } + return repoName; + + function generateRepoName(name: string): string { + return name.toLocaleLowerCase().replace('.', '-'); + } + } + + public addFile(asset: FileAssetSource): FileStagingLocation { + // Has side effects so must go first + const bucketName = this.getCreateBucket(); + + return { + bucketName, + assumeRoleArn: this.fileRoleManifestArn, + prefix: asset.deployTime ? DEPLOY_TIME_PREFIX : undefined, + dependencyStack: this, + }; + } + + public addDockerImage(asset: DockerImageAssetSource): ImageStagingLocation { + // Has side effects so must go first + const repoName = this.getCreateRepo(asset); + + return { + repoName, + assumeRoleArn: this.imageRoleManifestArn, + dependencyStack: this, + }; + } + + /** + * Synthesizes the cloudformation template into a cloud assembly. + * @internal + */ + public _synthesizeTemplate(session: ISynthesisSession, lookupRoleArn?: string | undefined): void { + super._synthesizeTemplate(session, lookupRoleArn); + + const builder = session.assembly; + const outPath = path.join(builder.outdir, this.templateFile); + const size = fs.statSync(outPath).size; + if (size > 51200) { + throw new Error(`Staging resource template cannot be greater than 51200 bytes, but got ${size} bytes`); + } + } +} diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/index.ts b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/index.ts new file mode 100644 index 0000000000000..2a5055670e09d --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/index.ts @@ -0,0 +1,4 @@ +export * from './default-staging-stack'; +export * from './app-staging-synthesizer'; +export * from './bootstrap-roles'; +export * from './staging-stack'; diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/per-env-staging-factory.ts b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/per-env-staging-factory.ts new file mode 100644 index 0000000000000..3d3c8fd5c50b2 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/per-env-staging-factory.ts @@ -0,0 +1,32 @@ +import { Stack } from 'aws-cdk-lib'; +import { AppScopedGlobal } from './private/app-global'; +import { IStagingResources, IStagingResourcesFactory, ObtainStagingResourcesContext } from './staging-stack'; + +/** + * Per-environment cache + * + * This is a global because we might have multiple instances of this class + * in the app, but we want to cache across all of them. + */ +const ENVIRONMENT_CACHE = new AppScopedGlobal(() => new Map()); + +/** + * Wraps another IStagingResources factory, and caches the result on a per-environment basis. + */ +export class PerEnvironmentStagingFactory implements IStagingResourcesFactory { + constructor(private readonly wrapped: IStagingResourcesFactory) { } + + public obtainStagingResources(stack: Stack, context: ObtainStagingResourcesContext): IStagingResources { + const cacheKey = context.environmentString; + + const cache = ENVIRONMENT_CACHE.for(stack); + const existing = cache.get(cacheKey); + if (existing) { + return existing; + } + + const result = this.wrapped.obtainStagingResources(stack, context); + cache.set(cacheKey, result); + return result; + } +} diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/private/app-global.ts b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/private/app-global.ts new file mode 100644 index 0000000000000..2cd36d0264a5b --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/private/app-global.ts @@ -0,0 +1,33 @@ +import { App } from 'aws-cdk-lib'; +import { IConstruct } from 'constructs'; + +/** + * Hold an App-wide global variable + * + * This is a replacement for a `static` variable, but does the right thing in case people + * instantiate multiple Apps in the same process space (for example, in unit tests or + * people using `cli-lib` in advanced configurations). + * + * This class assumes that the global you're going to be storing is a mutable object. + */ +export class AppScopedGlobal { + private readonly map = new WeakMap(); + + constructor(private readonly factory: () => A) { + } + + public for(ctr: IConstruct): A { + const app = App.of(ctr); + if (!App.isApp(app)) { + throw new Error(`Construct ${ctr.node.path} must be part of an App`); + } + + const existing = this.map.get(app); + if (existing) { + return existing; + } + const instance = this.factory(); + this.map.set(app, instance); + return instance; + } +} diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/private/no-tokens.ts b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/private/no-tokens.ts new file mode 100644 index 0000000000000..befb88fc8f4de --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/private/no-tokens.ts @@ -0,0 +1,9 @@ +import { StringSpecializer } from 'aws-cdk-lib/core/lib/helpers-internal'; + +export function validateNoTokens(props: A, context: string) { + for (const [key, value] of Object.entries(props)) { + if (typeof value === 'string') { + StringSpecializer.validateNoTokens(value, `${context} property '${key}'`); + } + } +} diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/staging-stack.ts b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/staging-stack.ts new file mode 100644 index 0000000000000..9cc24dbb3bce5 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/lib/staging-stack.ts @@ -0,0 +1,120 @@ +import { DockerImageAssetSource, FileAssetSource, Stack } from 'aws-cdk-lib'; +import { IConstruct } from 'constructs'; + +/** + * Information returned by the Staging Stack for each file asset. + */ +export interface FileStagingLocation { + /** + * The name of the staging bucket + */ + readonly bucketName: string; + + /** + * A prefix to add to the keys + * + * @default '' + */ + readonly prefix?: string; + + /** + * The ARN to assume to write files to this bucket + * + * @default - Don't assume a role + */ + readonly assumeRoleArn?: string; + + /** + * The stack that creates this bucket (leads to dependencies on it) + * + * @default - Don't add dependencies + */ + readonly dependencyStack?: Stack; +} + +/** + * Information returned by the Staging Stack for each image asset + */ +export interface ImageStagingLocation { + /** + * The name of the staging repository + */ + readonly repoName: string; + + /** + * The arn to assume to write files to this repository + * + * @default - Don't assume a role + */ + readonly assumeRoleArn?: string; + + /** + * The stack that creates this repository (leads to dependencies on it) + * + * @default - Don't add dependencies + */ + readonly dependencyStack?: Stack; +} + +/** + * Staging Resource interface. + */ +export interface IStagingResources extends IConstruct { + /** + * Return staging resource information for a file asset. + */ + addFile(asset: FileAssetSource): FileStagingLocation; + + /** + * Return staging resource information for a docker asset. + */ + addDockerImage(asset: DockerImageAssetSource): ImageStagingLocation; +} + +/** + * Staging Resource Factory interface. + * + * The function included in this class will be called by the synthesizer + * to create or reference an IStagingResources construct that has the necessary + * staging resources for the stack. + */ +export interface IStagingResourcesFactory { + /** + * Return an object that will manage staging resources for the given stack + * + * This is called whenever the the `AppStagingSynthesizer` binds to a specific + * stack, and allows selecting where the staging resources go. + * + * This method can choose to either create a new construct (perhaps a stack) + * and return it, or reference an existing construct. + * + * @param stack - stack to return an appropriate IStagingStack for + */ + obtainStagingResources(stack: Stack, context: ObtainStagingResourcesContext): IStagingResources; +} + +/** + * Context parameters for the 'obtainStagingResources' function + */ +export interface ObtainStagingResourcesContext { + /** + * A unique string describing the environment that is guaranteed not to have tokens in it + */ + readonly environmentString: string; + + /** + * The ARN of the deploy action role, if given + * + * This role will need permissions to read from to the staging resources. + * + * @default - Deploy role ARN is unknown + */ + readonly deployRoleArn?: string; + + /** + * The qualifier passed to the synthesizer + * + * The staging stack shouldn't need this, but it might. + */ + readonly qualifier: string; +} diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/package.json b/packages/@aws-cdk/app-staging-synthesizer-alpha/package.json new file mode 100644 index 0000000000000..7b5e5e9ef5dee --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/package.json @@ -0,0 +1,101 @@ +{ + "name": "@aws-cdk/app-staging-synthesizer-alpha", + "private": true, + "version": "0.0.0", + "description": "Cdk synthesizer for with app-scoped staging stack", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, + "targets": { + "java": { + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "cdk-app-staging-synthesizer-alpha" + }, + "package": "software.amazon.awscdk.app.staging.synthesizer.alpha" + }, + "python": { + "distName": "aws-cdk.app-staging-synthesizer-alpha", + "module": "aws_cdk.app_staging_synthesizer_alpha", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 2" + ] + }, + "dotnet": { + "namespace": "Amazon.CDK.App.Staging.Synthesizer.Alpha", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/main/logo/default-256-dark.png" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/app-staging-synthesizer-alpha" + }, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "integ-runner", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "build+test": "yarn build && yarn test", + "build+test+package": "yarn build+test && yarn package", + "compat": "cdk-compat", + "rosetta:extract": "yarn --silent jsii-rosetta extract", + "build+extract": "yarn build && yarn rosetta:extract", + "build+test+extract": "yarn build+test && yarn rosetta:extract" + }, + "keywords": [ + "aws", + "cdk" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "homepage": "https://github.com/aws/aws-cdk", + "engines": { + "node": ">= 14.15.0" + }, + "stability": "experimental", + "maturity": "experimental", + "awscdkio": { + "announce": false + }, + "cdk-build": { + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } + }, + "dependencies": { + "aws-cdk-lib": "0.0.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk-lib": "0.0.0", + "@aws-cdk/integ-runner": "0.0.0", + "@aws-cdk/integ-tests-alpha": "0.0.0", + "constructs": "^10.0.0", + "@aws-cdk/cdk-build-tools": "0.0.0", + "@aws-cdk/pkglint": "0.0.0" + }, + "peerDependencies": { + "aws-cdk-lib": "0.0.0", + "constructs": "^10.0.0" + } +} diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/rosetta/default.ts-fixture b/packages/@aws-cdk/app-staging-synthesizer-alpha/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..150cd4c706021 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/rosetta/default.ts-fixture @@ -0,0 +1,22 @@ +// Fixture with packages imported, but nothing else +import { App, Stack, StackProps, Duration, DockerImageAssetSource, FileAssetSource } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import { + AppStagingSynthesizer, + BootstrapRole, + DefaultStagingStack, + DefaultStagingStackOptions, + IStagingResources, + FileStagingLocation, + ImageStagingLocation, + DeploymentIdentities, +} from '@aws-cdk/app-staging-synthesizer-alpha'; +import * as path from 'path'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + /// here + } +} diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/rosetta/with-custom-staging.ts-fixture b/packages/@aws-cdk/app-staging-synthesizer-alpha/rosetta/with-custom-staging.ts-fixture new file mode 100644 index 0000000000000..981f76ecbbac1 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/rosetta/with-custom-staging.ts-fixture @@ -0,0 +1,44 @@ +// Fixture with packages imported, but nothing else +import { App, Stack, StackProps, DockerImageAssetSource, FileAssetSource } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import { + AppStagingSynthesizer, + DefaultStagingStack, + BootstrapRole, + FileStagingLocation, + ImageStagingLocation, + ObtainStagingResourcesContext, + IStagingResourcesFactory, + IStagingResources, +} from '@aws-cdk/app-staging-synthesizer-alpha'; + +interface CustomStagingStackProps extends StackProps {} + +class CustomStagingStack extends Stack implements IStagingResources { + public constructor(scope: Construct, id: string, props: CustomStagingStackProps) { + super(scope, id, props); + } + + public addFile(asset: FileAssetSource): FileStagingLocation { + return { + bucketName: 'myBucket', + assumeRoleArn: 'myArn', + dependencyStack: this, + }; + } + + public addDockerImage(asset: DockerImageAssetSource): ImageStagingLocation { + return { + repoName: 'myRepo', + assumeRoleArn: 'myArn', + dependencyStack: this, + }; + } +} + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + /// here + } +} diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/app-staging-synthesizer.test.ts b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/app-staging-synthesizer.test.ts new file mode 100644 index 0000000000000..d6a47ba76c65e --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/app-staging-synthesizer.test.ts @@ -0,0 +1,493 @@ +import * as fs from 'fs'; +import { App, Stack, CfnResource, FileAssetPackaging, Token, Lazy, Duration } from 'aws-cdk-lib'; +import { Match, Template } from 'aws-cdk-lib/assertions'; +import * as cxschema from 'aws-cdk-lib/cloud-assembly-schema'; +import { CloudAssembly } from 'aws-cdk-lib/cx-api'; +import { evaluateCFN } from './evaluate-cfn'; +import { APP_ID, CFN_CONTEXT, isAssetManifest, last } from './util'; +import { AppStagingSynthesizer, DEPLOY_TIME_PREFIX } from '../lib'; + +describe(AppStagingSynthesizer, () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ appId: APP_ID }), + }); + stack = new Stack(app, 'Stack', { + env: { + account: '000000000000', + region: 'us-east-1', + }, + }); + }); + + test('stack template is in asset manifest', () => { + // GIVEN + new CfnResource(stack, 'Resource', { + type: 'Some::Resource', + }); + + // WHEN + const asm = app.synth(); + + // THEN -- the S3 url is advertised on the stack artifact + const stackArtifact = asm.getStackArtifact('Stack'); + + const templateObjectKey = `${DEPLOY_TIME_PREFIX}${last(stackArtifact.stackTemplateAssetObjectUrl?.split('/'))}`; + expect(stackArtifact.stackTemplateAssetObjectUrl).toEqual(`s3://cdk-${APP_ID}-staging-000000000000-us-east-1/${templateObjectKey}`); + + // THEN - the template is in the asset manifest + const manifestArtifact = asm.artifacts.filter(isAssetManifest)[0]; + expect(manifestArtifact).toBeDefined(); + const manifest: cxschema.AssetManifest = JSON.parse(fs.readFileSync(manifestArtifact.file, { encoding: 'utf-8' })); + + const firstFile = (manifest.files ? manifest.files[Object.keys(manifest.files)[0]] : undefined) ?? {}; + + expect(firstFile).toEqual({ + source: { path: 'Stack.template.json', packaging: 'file' }, + destinations: { + '000000000000-us-east-1': { + bucketName: `cdk-${APP_ID}-staging-000000000000-us-east-1`, + objectKey: templateObjectKey, + region: 'us-east-1', + assumeRoleArn: `arn:\${AWS::Partition}:iam::000000000000:role/cdk-${APP_ID}-file-role-us-east-1`, + }, + }, + }); + }); + + test('stack template is in the asset manifest - environment tokens', () => { + const app2 = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ appId: APP_ID }), + }); + const accountToken = Token.asString('111111111111'); + const regionToken = Token.asString('us-east-2'); + const stack2 = new Stack(app2, 'Stack2', { + env: { + account: accountToken, + region: regionToken, + }, + }); + + // GIVEN + new CfnResource(stack2, 'Resource', { + type: 'Some::Resource', + }); + + // WHEN + const asm = app2.synth(); + + // THEN -- the S3 url is advertised on the stack artifact + const stackArtifact = asm.getStackArtifact('Stack2'); + + const templateObjectKey = `${DEPLOY_TIME_PREFIX}${last(stackArtifact.stackTemplateAssetObjectUrl?.split('/'))}`; + expect(stackArtifact.stackTemplateAssetObjectUrl).toEqual(`s3://cdk-${APP_ID}-staging-${accountToken}-${regionToken}/${templateObjectKey}`); + + // THEN - the template is in the asset manifest + const manifestArtifact = asm.artifacts.filter(isAssetManifest)[0]; + expect(manifestArtifact).toBeDefined(); + const manifest: cxschema.AssetManifest = JSON.parse(fs.readFileSync(manifestArtifact.file, { encoding: 'utf-8' })); + + const firstFile = (manifest.files ? manifest.files[Object.keys(manifest.files)[0]] : undefined) ?? {}; + + expect(firstFile).toEqual({ + source: { path: 'Stack2.template.json', packaging: 'file' }, + destinations: { + '111111111111-us-east-2': { + bucketName: `cdk-${APP_ID}-staging-111111111111-us-east-2`, + objectKey: templateObjectKey, + region: 'us-east-2', + assumeRoleArn: `arn:\${AWS::Partition}:iam::111111111111:role/cdk-${APP_ID}-file-role-us-east-2`, + }, + }, + }); + }); + + test('stack depends on staging stack', () => { + // WHEN + stack.synthesizer.addFileAsset({ + fileName: __filename, + packaging: FileAssetPackaging.FILE, + sourceHash: 'abcdef', + }); + + // THEN - we have a stack dependency on the staging stack + expect(stack.dependencies.length).toEqual(1); + const depStack = stack.dependencies[0]; + expect(depStack.stackName).toEqual(`StagingStack-${APP_ID}`); + }); + + test('add file asset', () => { + // WHEN + const location = stack.synthesizer.addFileAsset({ + fileName: __filename, + packaging: FileAssetPackaging.FILE, + sourceHash: 'abcdef', + }); + + // THEN - we have a fixed asset location + expect(evalCFN(location.bucketName)).toEqual(`cdk-${APP_ID}-staging-000000000000-us-east-1`); + expect(evalCFN(location.httpUrl)).toEqual(`https://s3.us-east-1.domain.aws/cdk-${APP_ID}-staging-000000000000-us-east-1/abcdef.js`); + + // THEN - object key contains source hash somewhere + expect(location.objectKey.indexOf('abcdef')).toBeGreaterThan(-1); + }); + + test('file asset depends on staging stack', () => { + // WHEN + stack.synthesizer.addFileAsset({ + fileName: __filename, + packaging: FileAssetPackaging.FILE, + sourceHash: 'abcdef', + }); + + const asm = app.synth(); + + // THEN - the template is in the asset manifest + const manifestArtifact = asm.artifacts.filter(isAssetManifest)[0]; + expect(manifestArtifact).toBeDefined(); + expect(manifestArtifact.manifest.dependencies).toEqual([`StagingStack-${APP_ID}-000000000000-us-east-1`]); + }); + + test('adding multiple files only creates one bucket', () => { + // WHEN + const location1 = stack.synthesizer.addFileAsset({ + fileName: __filename, + packaging: FileAssetPackaging.FILE, + sourceHash: 'abcdef', + }); + const location2 = stack.synthesizer.addFileAsset({ + fileName: __filename, + packaging: FileAssetPackaging.FILE, + sourceHash: 'zyxwvu', + }); + + // THEN - assets have the same location + expect(evalCFN(location1.bucketName)).toEqual(evalCFN(location2.bucketName)); + }); + + describe('deploy time assets', () => { + test('have the \'deploy-time/\' prefix', () => { + // WHEN + const location = stack.synthesizer.addFileAsset({ + fileName: __filename, + packaging: FileAssetPackaging.FILE, + sourceHash: 'abcdef', + deployTime: true, + }); + + // THEN - asset has bucket prefix + expect(evalCFN(location.objectKey)).toEqual(`${DEPLOY_TIME_PREFIX}abcdef.js`); + }); + + test('do not get specified bucketPrefix', () => { + // GIVEN + app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ appId: APP_ID }), + }); + stack = new Stack(app, 'Stack', { + env: { + account: '000000000000', + region: 'us-west-2', + }, + }); + + // WHEN + const location = stack.synthesizer.addFileAsset({ + fileName: __filename, + packaging: FileAssetPackaging.FILE, + sourceHash: 'abcdef', + deployTime: true, + }); + + // THEN - asset has bucket prefix + expect(evalCFN(location.objectKey)).toEqual(`${DEPLOY_TIME_PREFIX}abcdef.js`); + }); + + test('have s3 bucket has lifecycle rule by default', () => { + // GIVEN + new CfnResource(stack, 'Resource', { + type: 'Some::Resource', + }); + + // WHEN + const asm = app.synth(); + + // THEN + Template.fromJSON(getStagingResourceStack(asm).template).hasResourceProperties('AWS::S3::Bucket', { + LifecycleConfiguration: { + Rules: Match.arrayWith([{ + ExpirationInDays: 30, + Prefix: DEPLOY_TIME_PREFIX, + Status: 'Enabled', + }]), + }, + }); + }); + + test('can have customized lifecycle rules', () => { + // GIVEN + app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: APP_ID, + deployTimeFileAssetLifetime: Duration.days(1), + }), + }); + stack = new Stack(app, 'Stack', { + env: { + account: '000000000000', + region: 'us-west-2', + }, + }); + new CfnResource(stack, 'Resource', { + type: 'Some::Resource', + }); + + // WHEN + const asm = app.synth(); + + // THEN + const stagingStackArtifact = asm.getStackArtifact(`StagingStack-${APP_ID}-000000000000-us-west-2`); + + Template.fromJSON(stagingStackArtifact.template).hasResourceProperties('AWS::S3::Bucket', { + LifecycleConfiguration: { + Rules: Match.arrayWith([{ + ExpirationInDays: 1, + Prefix: DEPLOY_TIME_PREFIX, + Status: 'Enabled', + }]), + }, + }); + }); + }); + + test('bucket has policy referring to deploymentrolearn', () => { + new CfnResource(stack, 'Resource', { + type: 'Some::Resource', + }); + + // WHEN + const asm = app.synth(); + + // THEN + const stagingStackArtifact = asm.getStackArtifact(`StagingStack-${APP_ID}-000000000000-us-east-1`); + + Template.fromJSON(stagingStackArtifact.template).hasResourceProperties('AWS::S3::BucketPolicy', { + PolicyDocument: { + Statement: Match.arrayWith([ + Match.objectLike({ + Effect: 'Allow', + Principal: { + AWS: Match.anyValue(), + }, + Action: [ + 's3:GetObject*', + 's3:GetBucket*', + 's3:List*', + ], + }), + ]), + }, + }); + }); + + test('add docker image asset', () => { + // WHEN + const assetName = 'abcdef'; + const location = stack.synthesizer.addDockerImageAsset({ + directoryName: '.', + sourceHash: 'abcdef', + assetName, + }); + + // THEN - we have a fixed asset location + const repo = `${APP_ID}/${assetName}`; + expect(evalCFN(location.repositoryName)).toEqual(repo); + expect(evalCFN(location.imageUri)).toEqual(`000000000000.dkr.ecr.us-east-1.domain.aws/${repo}:abcdef`); + }); + + test('throws with docker image asset without assetName', () => { + expect(() => stack.synthesizer.addDockerImageAsset({ + directoryName: '.', + sourceHash: 'abcdef', + })).toThrowError('Assets synthesized with AppScopedStagingSynthesizer must include an \'assetName\' in the asset source definition.'); + }); + + test('docker image asset depends on staging stack', () => { + // WHEN + const assetName = 'abcdef'; + stack.synthesizer.addDockerImageAsset({ + directoryName: '.', + sourceHash: 'abcdef', + assetName, + }); + + const asm = app.synth(); + + // THEN - the template is in the asset manifest + const manifestArtifact = asm.artifacts.filter(isAssetManifest)[0]; + expect(manifestArtifact).toBeDefined(); + expect(manifestArtifact.manifest.dependencies).toEqual([`StagingStack-${APP_ID}-000000000000-us-east-1`]); + }); + + test('docker image assets with different assetName have separate repos', () => { + // WHEN + const location1 = stack.synthesizer.addDockerImageAsset({ + directoryName: '.', + sourceHash: 'abcdef', + assetName: 'firstAsset', + }); + + const location2 = stack.synthesizer.addDockerImageAsset({ + directoryName: './hello', + sourceHash: 'abcdef', + assetName: 'secondAsset', + }); + + // THEN - images have different asset locations + expect(evalCFN(location1.repositoryName)).not.toEqual(evalCFN(location2.repositoryName)); + }); + + test('docker image assets with same assetName live in same repos', () => { + // WHEN + const assetName = 'abcdef'; + const location1 = stack.synthesizer.addDockerImageAsset({ + directoryName: '.', + sourceHash: 'abcdef', + assetName, + }); + + const location2 = stack.synthesizer.addDockerImageAsset({ + directoryName: './hello', + sourceHash: 'abcdefg', + assetName, + }); + + // THEN - images share same ecr repo + expect(evalCFN(location1.repositoryName)).toEqual(`${APP_ID}/${assetName}`); + expect(evalCFN(location1.repositoryName)).toEqual(evalCFN(location2.repositoryName)); + }); + + test('docker image repositories have lifecycle rule - default', () => { + // GIVEN + const assetName = 'abcdef'; + stack.synthesizer.addDockerImageAsset({ + directoryName: '.', + sourceHash: 'abcdef', + assetName, + }); + + // WHEN + const asm = app.synth(); + + // THEN + Template.fromJSON(getStagingResourceStack(asm).template).hasResourceProperties('AWS::ECR::Repository', { + LifecyclePolicy: { + LifecyclePolicyText: Match.serializedJson({ + rules: Match.arrayWith([ + Match.objectLike({ + selection: Match.objectLike({ + countType: 'imageCountMoreThan', + countNumber: 3, + }), + }), + ]), + }), + }, + RepositoryName: `${APP_ID}/${assetName}`, + }); + }); + + test('docker image repositories have lifecycle rule - specified', () => { + // GIVEN + app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: APP_ID, + imageAssetVersionCount: 1, + }), + }); + stack = new Stack(app, 'Stack', { + env: { + account: '000000000000', + region: 'us-east-1', + }, + }); + + const assetName = 'abcdef'; + stack.synthesizer.addDockerImageAsset({ + directoryName: '.', + sourceHash: 'abcdef', + assetName, + }); + + // WHEN + const asm = app.synth(); + + // THEN + Template.fromJSON(getStagingResourceStack(asm).template).hasResourceProperties('AWS::ECR::Repository', { + LifecyclePolicy: { + LifecyclePolicyText: Match.serializedJson({ + rules: Match.arrayWith([ + Match.objectLike({ + selection: Match.objectLike({ + countType: 'imageCountMoreThan', + countNumber: 1, + }), + }), + ]), + }), + }, + RepositoryName: `${APP_ID}/${assetName}`, + }); + }); + + describe('environment specifics', () => { + test('throws if App includes env-agnostic and specific env stacks', () => { + // GIVEN - App with Stack with specific environment + + // THEN - Expect environment agnostic stack to fail + expect(() => new Stack(app, 'NoEnvStack')).toThrowError(/It is not safe to use AppStagingSynthesizer/); + }); + }); + + test('throws if synthesizer props have tokens', () => { + expect(() => new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: Lazy.string({ produce: () => 'appId' }), + }), + })).toThrowError(/AppStagingSynthesizer property 'appId' may not contain tokens;/); + }); + + test('throws when staging resource stack is too large', () => { + // WHEN + const assetName = 'abcdef'; + for (let i = 0; i < 100; i++) { + stack.synthesizer.addDockerImageAsset({ + directoryName: '.', + sourceHash: 'abcdef', + assetName: assetName + i, + }); + } + + // THEN + expect(() => app.synth()).toThrowError(/Staging resource template cannot be greater than 51200 bytes/); + }); + + /** + * Evaluate a possibly string-containing value the same way CFN would do + * + * (Be invariant to the specific Fn::Sub or Fn::Join we would output) + */ + function evalCFN(value: any) { + return evaluateCFN(stack.resolve(value), CFN_CONTEXT); + } + + /** + * Return the staging resource stack that is generated as part of the assembly + */ + function getStagingResourceStack(asm: CloudAssembly) { + return asm.getStackArtifact(`StagingStack-${APP_ID}-000000000000-us-east-1`); + } +}); diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/assets/Dockerfile b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/assets/Dockerfile new file mode 100644 index 0000000000000..4a015204a5983 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/assets/Dockerfile @@ -0,0 +1,2 @@ +FROM public.ecr.aws/lambda/python:3.10 +CMD echo hello world \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/assets/index.py b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/assets/index.py new file mode 100644 index 0000000000000..ed0f110e2e61e --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/assets/index.py @@ -0,0 +1 @@ +print('hello') \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/bootstrap-roles.test.ts b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/bootstrap-roles.test.ts new file mode 100644 index 0000000000000..a46e1807f8c97 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/bootstrap-roles.test.ts @@ -0,0 +1,189 @@ +import * as fs from 'fs'; +import { App, Stack, CfnResource } from 'aws-cdk-lib'; +import * as cxschema from 'aws-cdk-lib/cloud-assembly-schema'; +import { APP_ID, isAssetManifest } from './util'; +import { AppStagingSynthesizer, BootstrapRole, DeploymentIdentities } from '../lib'; + +const CLOUDFORMATION_EXECUTION_ROLE = 'cloudformation-execution-role'; +const DEPLOY_ACTION_ROLE = 'deploy-action-role'; +const LOOKUP_ROLE = 'lookup-role'; + +describe('Boostrap Roles', () => { + test('default bootstrap role name is always under 64 characters', () => { + // GIVEN + const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: 'super long app id that needs to be cut', + }), + }); + const stack = new Stack(app, 'Stack', { + env: { + account: '000000000000', + region: 'us-east-1', + }, + }); + new CfnResource(stack, 'Resource', { + type: 'Some::Resource', + }); + + // WHEN + const asm = app.synth(); + + // THEN + const manifestArtifact = asm.artifacts.filter(isAssetManifest)[0]; + expect(manifestArtifact).toBeDefined(); + const manifest: cxschema.AssetManifest = JSON.parse(fs.readFileSync(manifestArtifact.file, { encoding: 'utf-8' })); + const firstFile: any = (manifest.files ? manifest.files[Object.keys(manifest.files)[0]] : undefined) ?? {}; + expect(firstFile.destinations['000000000000-us-east-1'].assumeRoleArn).toEqual('arn:${AWS::Partition}:iam::000000000000:role/cdk-super-long-app-id-th-file-role-us-east-1'); + }); + + test('can supply existing arns for bootstrapped roles', () => { + // GIVEN + const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: APP_ID, + deploymentIdentities: DeploymentIdentities.specifyRoles({ + cloudFormationExecutionRole: BootstrapRole.fromRoleArn(CLOUDFORMATION_EXECUTION_ROLE), + lookupRole: BootstrapRole.fromRoleArn(LOOKUP_ROLE), + deploymentRole: BootstrapRole.fromRoleArn(DEPLOY_ACTION_ROLE), + }), + }), + }); + const stack = new Stack(app, 'Stack', { + env: { + account: '000000000000', + region: 'us-east-1', + }, + }); + new CfnResource(stack, 'Resource', { + type: 'Some::Resource', + }); + + // WHEN + const asm = app.synth(); + + // THEN + const stackArtifact = asm.getStackArtifact('Stack'); + + // Bootstrapped roles are as advertised + expect(stackArtifact.cloudFormationExecutionRoleArn).toEqual(CLOUDFORMATION_EXECUTION_ROLE); + expect(stackArtifact.lookupRole).toEqual({ arn: LOOKUP_ROLE }); + expect(stackArtifact.assumeRoleArn).toEqual(DEPLOY_ACTION_ROLE); + }); + + test('can supply existing arn for bucket staging role', () => { + // GIVEN + const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: APP_ID, + fileAssetPublishingRole: BootstrapRole.fromRoleArn('arn:aws:iam::123456789012:role/S3Access'), + }), + }); + const stack = new Stack(app, 'Stack', { + env: { + account: '000000000000', + region: 'us-east-1', + }, + }); + new CfnResource(stack, 'Resource', { + type: 'Some::Resource', + }); + + // WHEN + const asm = app.synth(); + + // THEN + // Staging role is as advertised + const manifestArtifact = asm.artifacts.filter(isAssetManifest)[0]; + expect(manifestArtifact).toBeDefined(); + const manifest: cxschema.AssetManifest = JSON.parse(fs.readFileSync(manifestArtifact.file, { encoding: 'utf-8' })); + const firstFile: any = (manifest.files ? manifest.files[Object.keys(manifest.files)[0]] : undefined) ?? {}; + expect(firstFile.destinations['000000000000-us-east-1'].assumeRoleArn).toEqual('arn:aws:iam::123456789012:role/S3Access'); + }); + + test('can provide existing arn for image staging role', () => { + // GIVEN + const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: APP_ID, + imageAssetPublishingRole: BootstrapRole.fromRoleArn('arn:aws:iam::123456789012:role/ECRAccess'), + }), + }); + const stack = new Stack(app, 'Stack', { + env: { + account: '000000000000', + region: 'us-east-1', + }, + }); + stack.synthesizer.addDockerImageAsset({ + directoryName: '.', + sourceHash: 'abcdef', + assetName: 'myDockerAsset', + }); + + // WHEN + const asm = app.synth(); + + // THEN + // Image role is as advertised + const manifestArtifact = asm.artifacts.filter(isAssetManifest)[0]; + expect(manifestArtifact).toBeDefined(); + const manifest: cxschema.AssetManifest = JSON.parse(fs.readFileSync(manifestArtifact.file, { encoding: 'utf-8' })); + const firstFile: any = (manifest.dockerImages ? manifest.dockerImages[Object.keys(manifest.dockerImages)[0]] : undefined) ?? {}; + expect(firstFile.destinations['000000000000-us-east-1'].assumeRoleArn).toEqual('arn:aws:iam::123456789012:role/ECRAccess'); + }); + + test('bootstrap roles can be specified as current cli credentials instead', () => { + // GIVEN + const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: APP_ID, + deploymentIdentities: DeploymentIdentities.cliCredentials(), + }), + }); + const stack = new Stack(app, 'Stack', { + env: { + account: '000000000000', + region: 'us-east-1', + }, + }); + new CfnResource(stack, 'Resource', { + type: 'Some::Resource', + }); + + // WHEN + const asm = app.synth(); + + // THEN + const stackArtifact = asm.getStackArtifact('Stack'); + + // Bootstrapped roles are undefined, which means current credentials are used + expect(stackArtifact.cloudFormationExecutionRoleArn).toBeUndefined(); + expect(stackArtifact.lookupRole).toBeUndefined(); + expect(stackArtifact.assumeRoleArn).toBeUndefined(); + }); + + test('qualifier is resolved in the synthesizer', () => { + const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + bootstrapQualifier: 'abcdef', + appId: APP_ID, + }), + }); + new Stack(app, 'Stack', { + env: { + account: '000000000000', + region: 'us-east-1', + }, + }); + + // WHEN + const asm = app.synth(); + + // THEN + const stackArtifact = asm.getStackArtifact('Stack'); + + // Bootstrapped role's asset manifest tokens are resolved, where possible + expect(stackArtifact.cloudFormationExecutionRoleArn).toEqual('arn:${AWS::Partition}:iam::000000000000:role/cdk-abcdef-cfn-exec-role-000000000000-us-east-1'); + }); +}); diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/default-staging-stack.test.ts b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/default-staging-stack.test.ts new file mode 100644 index 0000000000000..d711195d4ca25 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/default-staging-stack.test.ts @@ -0,0 +1,44 @@ +import { App } from 'aws-cdk-lib'; +import { DefaultStagingStack } from '../lib'; + +describe('default staging stack', () => { + describe('appId fails', () => { + test('when appId > 20 characters', () => { + const app = new App(); + expect(() => new DefaultStagingStack(app, 'stack', { + appId: 'a'.repeat(21), + qualifier: 'qualifier', + })).toThrowError(/appId expected no more than 20 characters but got 21 characters./); + }); + + test('when uppercase characters are used', () => { + const app = new App(); + expect(() => new DefaultStagingStack(app, 'stack', { + appId: 'ABCDEF', + qualifier: 'qualifier', + })).toThrowError(/appId only accepts lowercase characters./); + }); + + test('when symbols are used', () => { + const app = new App(); + expect(() => new DefaultStagingStack(app, 'stack', { + appId: 'ca$h', + qualifier: 'qualifier', + })).toThrowError(/appId expects only letters, numbers, and dashes \('-'\)/); + }); + + test('when multiple rules broken at once', () => { + const app = new App(); + const appId = 'AB&C'.repeat(10); + expect(() => new DefaultStagingStack(app, 'stack', { + appId, + qualifier: 'qualifier', + })).toThrowError([ + `appId ${appId} has errors:`, + 'appId expected no more than 20 characters but got 40 characters.', + 'appId only accepts lowercase characters.', + 'appId expects only letters, numbers, and dashes (\'-\')', + ].join('\n')); + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/evaluate-cfn.ts b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/evaluate-cfn.ts new file mode 100644 index 0000000000000..917ffb6646195 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/evaluate-cfn.ts @@ -0,0 +1,114 @@ +/** + * Simple function to evaluate CloudFormation intrinsics. + * + * Note that this function is not production quality, it exists to support tests. + */ +export function evaluateCFN(object: any, context: {[key: string]: string} = {}): any { + const intrinsicFns: any = { + 'Fn::Join'(separator: string, args: string[]) { + if (typeof separator !== 'string') { + // CFN does not support expressions here! + throw new Error('\'separator\' argument of { Fn::Join } must be a string literal'); + } + return evaluate(args).map(evaluate).join(separator); + }, + + 'Fn::Split'(separator: string, args: any) { + if (typeof separator !== 'string') { + // CFN does not support expressions here! + throw new Error('\'separator\' argument of { Fn::Split } must be a string literal'); + } + return evaluate(args).split(separator); + }, + + 'Fn::Select'(index: number, args: any) { + return evaluate(args).map(evaluate)[index]; + }, + + 'Ref'(logicalId: string) { + if (!(logicalId in context)) { + throw new Error(`Trying to evaluate Ref of '${logicalId}' but not in context!`); + } + return context[logicalId]; + }, + + 'Fn::GetAtt'(logicalId: string, attributeName: string) { + const key = `${logicalId}.${attributeName}`; + if (!(key in context)) { + throw new Error(`Trying to evaluate Fn::GetAtt of '${logicalId}.${attributeName}' but not in context!`); + } + return context[key]; + }, + + 'Fn::Sub'(template: string, explicitPlaceholders?: Record) { + const placeholders = explicitPlaceholders ? evaluate(explicitPlaceholders) : context; + + if (typeof template !== 'string') { + throw new Error('The first argument to {Fn::Sub} must be a string literal (cannot be the result of an expression)'); + } + + return template.replace(/\$\{([a-zA-Z0-9.:-]*)\}/g, (_: string, key: string) => { + if (key in placeholders) { return placeholders[key]; } + throw new Error(`Unknown placeholder in Fn::Sub: ${key}`); + }); + }, + }; + + return evaluate(object); + + function evaluate(obj: any): any { + if (Array.isArray(obj)) { + return obj.map(evaluate); + } + + if (typeof obj === 'object') { + const intrinsic = parseIntrinsic(obj); + if (intrinsic) { + return evaluateIntrinsic(intrinsic); + } + + const ret: {[key: string]: any} = {}; + for (const key of Object.keys(obj)) { + ret[key] = evaluate(obj[key]); + } + return ret; + } + + return obj; + } + + function evaluateIntrinsic(intrinsic: Intrinsic) { + if (!(intrinsic.name in intrinsicFns)) { + throw new Error(`Intrinsic ${intrinsic.name} not supported here`); + } + + const argsAsArray = Array.isArray(intrinsic.args) ? intrinsic.args : [intrinsic.args]; + + return intrinsicFns[intrinsic.name].apply(intrinsicFns, argsAsArray); + } +} + +interface Intrinsic { + readonly name: string; + readonly args: any; +} + +function parseIntrinsic(x: any): Intrinsic | undefined { + if (typeof x !== 'object' || x === null) { return undefined; } + const keys = Object.keys(x); + if (keys.length === 1 && (isNameOfCloudFormationIntrinsic(keys[0]) || keys[0] === 'Ref')) { + return { + name: keys[0], + args: x[keys[0]], + }; + } + return undefined; +} + +function isNameOfCloudFormationIntrinsic(name: string): boolean { + if (!name.startsWith('Fn::')) { + return false; + } + // these are 'fake' intrinsics, only usable inside the parameter overrides of a CFN CodePipeline Action + return name !== 'Fn::GetArtifactAtt' && name !== 'Fn::GetParam'; +} \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/StagingStack-default-resources-ACCOUNT-REGION.template.json b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/StagingStack-default-resources-ACCOUNT-REGION.template.json new file mode 100644 index 0000000000000..fae319dc47641 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/StagingStack-default-resources-ACCOUNT-REGION.template.json @@ -0,0 +1,472 @@ +{ + "Resources": { + "CdkFileRoleE26CEABA": { + "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" + }, + "RoleName": { + "Fn::Join": [ + "", + [ + "cdk-default-resources-file-role-", + { + "Ref": "AWS::Region" + } + ] + ] + } + } + }, + "CdkFileRoleDefaultPolicy621C7E5B": { + "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": [ + "CdkStagingBucket1636058C", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CdkStagingBucket1636058C", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "BucketKey7092080A", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CdkFileRoleDefaultPolicy621C7E5B", + "Roles": [ + { + "Ref": "CdkFileRoleE26CEABA" + } + ] + } + }, + "BucketKey7092080A": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:CancelKeyDeletion", + "kms:Create*", + "kms:Delete*", + "kms:Describe*", + "kms:Disable*", + "kms:Enable*", + "kms:Get*", + "kms:List*", + "kms:Put*", + "kms:Revoke*", + "kms:ScheduleKeyDeletion", + "kms:TagResource", + "kms:UntagResource", + "kms:Update*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "BucketKeyAlias69A0886F": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/cdk-default-resources-staging", + "TargetKeyId": { + "Fn::GetAtt": [ + "BucketKey7092080A", + "Arn" + ] + } + } + }, + "CdkStagingBucket1636058C": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "BucketKey7092080A", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + }, + "BucketName": { + "Fn::Join": [ + "", + [ + "cdk-default-resources-staging-", + { + "Ref": "AWS::AccountId" + }, + "-", + { + "Ref": "AWS::Region" + } + ] + ] + }, + "LifecycleConfiguration": { + "Rules": [ + { + "NoncurrentVersionExpiration": { + "NoncurrentDays": 365 + }, + "Status": "Enabled" + }, + { + "ExpirationInDays": 30, + "Prefix": "deploy-time/", + "Status": "Enabled" + } + ] + }, + "VersioningConfiguration": { + "Status": "Enabled" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "CdkStagingBucketPolicy42BD1F92": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "CdkStagingBucket1636058C" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "CdkStagingBucket1636058C", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CdkStagingBucket1636058C", + "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": [ + "CdkStagingBucket1636058C", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CdkStagingBucket1636058C", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "CdkImageRoleF1394AC3": { + "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" + }, + "RoleName": { + "Fn::Join": [ + "", + [ + "cdk-default-resources-image-role-", + { + "Ref": "AWS::Region" + } + ] + ] + } + } + }, + "CdkImageRoleDefaultPolicy4A1572DE": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:BatchGetImage", + "ecr:CompleteLayerUpload", + "ecr:DescribeImages", + "ecr:DescribeRepositories", + "ecr:GetDownloadUrlForLayer", + "ecr:InitiateLayerUpload", + "ecr:PutImage", + "ecr:UploadLayerPart" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "defaultresourcesecrasset2FBE6B8A9", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "defaultresourcesecrasset9191BD6E", + "Arn" + ] + } + ] + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CdkImageRoleDefaultPolicy4A1572DE", + "Roles": [ + { + "Ref": "CdkImageRoleF1394AC3" + } + ] + } + }, + "defaultresourcesecrasset9191BD6E": { + "Type": "AWS::ECR::Repository", + "Properties": { + "LifecyclePolicy": { + "LifecyclePolicyText": "{\"rules\":[{\"rulePriority\":1,\"description\":\"Garbage collect old image versions and keep the specified number of latest versions\",\"selection\":{\"tagStatus\":\"any\",\"countType\":\"imageCountMoreThan\",\"countNumber\":3},\"action\":{\"type\":\"expire\"}}]}" + }, + "RepositoryName": "default-resources/ecr-asset" + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "defaultresourcesecrasset2FBE6B8A9": { + "Type": "AWS::ECR::Repository", + "Properties": { + "LifecyclePolicy": { + "LifecyclePolicyText": "{\"rules\":[{\"rulePriority\":1,\"description\":\"Garbage collect old image versions and keep the specified number of latest versions\",\"selection\":{\"tagStatus\":\"any\",\"countType\":\"imageCountMoreThan\",\"countNumber\":3},\"action\":{\"type\":\"expire\"}}]}" + }, + "RepositoryName": "default-resources/ecr-asset-2" + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/asset.16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622/Dockerfile b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/asset.16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622/Dockerfile new file mode 100644 index 0000000000000..4a015204a5983 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/asset.16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622/Dockerfile @@ -0,0 +1,2 @@ +FROM public.ecr.aws/lambda/python:3.10 +CMD echo hello world \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/asset.16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622/index.py b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/asset.16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622/index.py new file mode 100644 index 0000000000000..ed0f110e2e61e --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/asset.16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622/index.py @@ -0,0 +1 @@ +print('hello') \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/asset.68539effc3f7ad46fff9765606c2a01b7f7965833643ab37e62799f19a37f650/Dockerfile b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/asset.68539effc3f7ad46fff9765606c2a01b7f7965833643ab37e62799f19a37f650/Dockerfile new file mode 100644 index 0000000000000..4a015204a5983 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/asset.68539effc3f7ad46fff9765606c2a01b7f7965833643ab37e62799f19a37f650/Dockerfile @@ -0,0 +1,2 @@ +FROM public.ecr.aws/lambda/python:3.10 +CMD echo hello world \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/asset.68539effc3f7ad46fff9765606c2a01b7f7965833643ab37e62799f19a37f650/index.py b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/asset.68539effc3f7ad46fff9765606c2a01b7f7965833643ab37e62799f19a37f650/index.py new file mode 100644 index 0000000000000..ed0f110e2e61e --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/asset.68539effc3f7ad46fff9765606c2a01b7f7965833643ab37e62799f19a37f650/index.py @@ -0,0 +1 @@ +print('hello') \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/cdk.out b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/cdk.out new file mode 100644 index 0000000000000..7925065efbcc4 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"31.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/integ.json b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/integ.json new file mode 100644 index 0000000000000..9eeaef0dfe700 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "31.0.0", + "testCases": { + "integ-tests/DefaultTest": { + "stacks": [ + "synthesize-default-resources" + ], + "assertionStack": "integ-tests/DefaultTest/DeployAssert", + "assertionStackName": "integtestsDefaultTestDeployAssert44C8D370" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/integtestsDefaultTestDeployAssert44C8D370.assets.json b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/integtestsDefaultTestDeployAssert44C8D370.assets.json new file mode 100644 index 0000000000000..7526fee9ff76c --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/integtestsDefaultTestDeployAssert44C8D370.assets.json @@ -0,0 +1,19 @@ +{ + "version": "31.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "integtestsDefaultTestDeployAssert44C8D370.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/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/integtestsDefaultTestDeployAssert44C8D370.template.json b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/integtestsDefaultTestDeployAssert44C8D370.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/integtestsDefaultTestDeployAssert44C8D370.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/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/manifest.json b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/manifest.json new file mode 100644 index 0000000000000..e9ac382233e75 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/manifest.json @@ -0,0 +1,213 @@ +{ + "version": "31.0.0", + "artifacts": { + "synthesize-default-resources.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "synthesize-default-resources.assets.json" + }, + "dependencies": [ + "StagingStack-default-resources-ACCOUNT-REGION" + ] + }, + "synthesize-default-resources": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "synthesize-default-resources.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "additionalDependencies": [ + "synthesize-default-resources.assets" + ], + "stackTemplateAssetObjectUrl": "s3://cdk-default-resources-staging-${AWS::AccountId}-${AWS::Region}/deploy-time/e21d11bec65be920861a56a86066cc88a0241d5cbe8324d0692ca982420e4cb0.json", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}" + } + }, + "dependencies": [ + "StagingStack-default-resources-ACCOUNT-REGION", + "synthesize-default-resources.assets" + ], + "metadata": { + "/synthesize-default-resources/lambda-s3/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "lambdas3ServiceRoleC9EDE33A" + } + ], + "/synthesize-default-resources/lambda-s3/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "lambdas342CE2BBD" + } + ], + "/synthesize-default-resources/lambda-ecr-1/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "lambdaecr1ServiceRoleA6BBC49F" + } + ], + "/synthesize-default-resources/lambda-ecr-1/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "lambdaecr1B33A3D15" + } + ], + "/synthesize-default-resources/lambda-ecr-1-copy/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "lambdaecr1copyServiceRole2A9FAF5F" + } + ], + "/synthesize-default-resources/lambda-ecr-1-copy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "lambdaecr1copyD39CDE9B" + } + ], + "/synthesize-default-resources/lambda-ecr-2/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "lambdaecr2ServiceRole2EA363D2" + } + ], + "/synthesize-default-resources/lambda-ecr-2/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "lambdaecr2615DAF68" + } + ] + }, + "displayName": "synthesize-default-resources" + }, + "StagingStack-default-resources-ACCOUNT-REGION": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "StagingStack-default-resources-ACCOUNT-REGION.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}", + "stackName": "StagingStack-default-resources" + }, + "metadata": { + "/StagingStack-default-resources-ACCOUNT-REGION/CdkFileRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CdkFileRoleE26CEABA" + } + ], + "/StagingStack-default-resources-ACCOUNT-REGION/CdkFileRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CdkFileRoleDefaultPolicy621C7E5B" + } + ], + "/StagingStack-default-resources-ACCOUNT-REGION/BucketKey/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BucketKey7092080A" + } + ], + "/StagingStack-default-resources-ACCOUNT-REGION/BucketKey/Alias/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BucketKeyAlias69A0886F" + } + ], + "/StagingStack-default-resources-ACCOUNT-REGION/CdkStagingBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CdkStagingBucket1636058C" + } + ], + "/StagingStack-default-resources-ACCOUNT-REGION/CdkStagingBucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CdkStagingBucketPolicy42BD1F92" + } + ], + "/StagingStack-default-resources-ACCOUNT-REGION/CdkImageRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CdkImageRoleF1394AC3" + } + ], + "/StagingStack-default-resources-ACCOUNT-REGION/CdkImageRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CdkImageRoleDefaultPolicy4A1572DE" + } + ], + "/StagingStack-default-resources-ACCOUNT-REGION/default-resources--ecr-asset/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "defaultresourcesecrasset9191BD6E" + } + ], + "/StagingStack-default-resources-ACCOUNT-REGION/default-resources--ecr-asset-2/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "defaultresourcesecrasset2FBE6B8A9" + } + ] + }, + "displayName": "StagingStack-default-resources-ACCOUNT-REGION" + }, + "integtestsDefaultTestDeployAssert44C8D370.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integtestsDefaultTestDeployAssert44C8D370.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integtestsDefaultTestDeployAssert44C8D370": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integtestsDefaultTestDeployAssert44C8D370.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": [ + "integtestsDefaultTestDeployAssert44C8D370.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": [ + "integtestsDefaultTestDeployAssert44C8D370.assets" + ], + "metadata": { + "/integ-tests/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-tests/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-tests/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/synthesize-default-resources.assets.json b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/synthesize-default-resources.assets.json new file mode 100644 index 0000000000000..c17a6ccdaa514 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/synthesize-default-resources.assets.json @@ -0,0 +1,57 @@ +{ + "version": "31.0.0", + "files": { + "68539effc3f7ad46fff9765606c2a01b7f7965833643ab37e62799f19a37f650": { + "source": { + "path": "asset.68539effc3f7ad46fff9765606c2a01b7f7965833643ab37e62799f19a37f650", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-default-resources-staging-${AWS::AccountId}-${AWS::Region}", + "objectKey": "68539effc3f7ad46fff9765606c2a01b7f7965833643ab37e62799f19a37f650.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-default-resources-file-role-${AWS::Region}" + } + } + }, + "e21d11bec65be920861a56a86066cc88a0241d5cbe8324d0692ca982420e4cb0": { + "source": { + "path": "synthesize-default-resources.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-default-resources-staging-${AWS::AccountId}-${AWS::Region}", + "objectKey": "deploy-time/e21d11bec65be920861a56a86066cc88a0241d5cbe8324d0692ca982420e4cb0.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-default-resources-file-role-${AWS::Region}" + } + } + } + }, + "dockerImages": { + "ecr-asset-16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622": { + "source": { + "directory": "asset.16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622" + }, + "destinations": { + "current_account-current_region": { + "repositoryName": "default-resources/ecr-asset", + "imageTag": "16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-default-resources-image-role-${AWS::Region}" + } + } + }, + "ecr-asset-2-16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622": { + "source": { + "directory": "asset.16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622" + }, + "destinations": { + "current_account-current_region": { + "repositoryName": "default-resources/ecr-asset-2", + "imageTag": "16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-default-resources-image-role-${AWS::Region}" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/synthesize-default-resources.template.json b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/synthesize-default-resources.template.json new file mode 100644 index 0000000000000..05ac9636afd0b --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/synthesize-default-resources.template.json @@ -0,0 +1,210 @@ +{ + "Resources": { + "lambdas3ServiceRoleC9EDE33A": { + "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" + ] + ] + } + ] + } + }, + "lambdas342CE2BBD": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-default-resources-staging-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "68539effc3f7ad46fff9765606c2a01b7f7965833643ab37e62799f19a37f650.zip" + }, + "Role": { + "Fn::GetAtt": [ + "lambdas3ServiceRoleC9EDE33A", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "python3.10" + }, + "DependsOn": [ + "lambdas3ServiceRoleC9EDE33A" + ] + }, + "lambdaecr1ServiceRoleA6BBC49F": { + "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" + ] + ] + } + ] + } + }, + "lambdaecr1B33A3D15": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": { + "Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/default-resources/ecr-asset:16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622" + } + }, + "Role": { + "Fn::GetAtt": [ + "lambdaecr1ServiceRoleA6BBC49F", + "Arn" + ] + }, + "PackageType": "Image" + }, + "DependsOn": [ + "lambdaecr1ServiceRoleA6BBC49F" + ] + }, + "lambdaecr1copyServiceRole2A9FAF5F": { + "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" + ] + ] + } + ] + } + }, + "lambdaecr1copyD39CDE9B": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": { + "Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/default-resources/ecr-asset:16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622" + } + }, + "Role": { + "Fn::GetAtt": [ + "lambdaecr1copyServiceRole2A9FAF5F", + "Arn" + ] + }, + "PackageType": "Image" + }, + "DependsOn": [ + "lambdaecr1copyServiceRole2A9FAF5F" + ] + }, + "lambdaecr2ServiceRole2EA363D2": { + "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" + ] + ] + } + ] + } + }, + "lambdaecr2615DAF68": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": { + "Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/default-resources/ecr-asset-2:16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622" + } + }, + "Role": { + "Fn::GetAtt": [ + "lambdaecr2ServiceRole2EA363D2", + "Arn" + ] + }, + "PackageType": "Image" + }, + "DependsOn": [ + "lambdaecr2ServiceRole2EA363D2" + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/tree.json b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/tree.json new file mode 100644 index 0000000000000..4a76ae37e2e0d --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.js.snapshot/tree.json @@ -0,0 +1,1225 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "synthesize-default-resources": { + "id": "synthesize-default-resources", + "path": "synthesize-default-resources", + "children": { + "lambda-s3": { + "id": "lambda-s3", + "path": "synthesize-default-resources/lambda-s3", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "synthesize-default-resources/lambda-s3/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "synthesize-default-resources/lambda-s3/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "synthesize-default-resources/lambda-s3/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-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "Code": { + "id": "Code", + "path": "synthesize-default-resources/lambda-s3/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "synthesize-default-resources/lambda-s3/Code/Stage", + "constructInfo": { + "fqn": "aws-cdk-lib.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "synthesize-default-resources/lambda-s3/Code/AssetBucket", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3_assets.Asset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "synthesize-default-resources/lambda-s3/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "s3Bucket": { + "Fn::Sub": "cdk-default-resources-staging-${AWS::AccountId}-${AWS::Region}" + }, + "s3Key": "68539effc3f7ad46fff9765606c2a01b7f7965833643ab37e62799f19a37f650.zip" + }, + "role": { + "Fn::GetAtt": [ + "lambdas3ServiceRoleC9EDE33A", + "Arn" + ] + }, + "handler": "index.handler", + "runtime": "python3.10" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.CfnFunction", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.Function", + "version": "0.0.0" + } + }, + "lambda-ecr-1": { + "id": "lambda-ecr-1", + "path": "synthesize-default-resources/lambda-ecr-1", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "synthesize-default-resources/lambda-ecr-1/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "synthesize-default-resources/lambda-ecr-1/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "synthesize-default-resources/lambda-ecr-1/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-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "AssetImage": { + "id": "AssetImage", + "path": "synthesize-default-resources/lambda-ecr-1/AssetImage", + "children": { + "Staging": { + "id": "Staging", + "path": "synthesize-default-resources/lambda-ecr-1/AssetImage/Staging", + "constructInfo": { + "fqn": "aws-cdk-lib.AssetStaging", + "version": "0.0.0" + } + }, + "Repository": { + "id": "Repository", + "path": "synthesize-default-resources/lambda-ecr-1/AssetImage/Repository", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecr.RepositoryBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecr_assets.DockerImageAsset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "synthesize-default-resources/lambda-ecr-1/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "imageUri": { + "Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/default-resources/ecr-asset:16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622" + } + }, + "role": { + "Fn::GetAtt": [ + "lambdaecr1ServiceRoleA6BBC49F", + "Arn" + ] + }, + "packageType": "Image" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.CfnFunction", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.Function", + "version": "0.0.0" + } + }, + "lambda-ecr-1-copy": { + "id": "lambda-ecr-1-copy", + "path": "synthesize-default-resources/lambda-ecr-1-copy", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "synthesize-default-resources/lambda-ecr-1-copy/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "synthesize-default-resources/lambda-ecr-1-copy/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "synthesize-default-resources/lambda-ecr-1-copy/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-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "AssetImage": { + "id": "AssetImage", + "path": "synthesize-default-resources/lambda-ecr-1-copy/AssetImage", + "children": { + "Staging": { + "id": "Staging", + "path": "synthesize-default-resources/lambda-ecr-1-copy/AssetImage/Staging", + "constructInfo": { + "fqn": "aws-cdk-lib.AssetStaging", + "version": "0.0.0" + } + }, + "Repository": { + "id": "Repository", + "path": "synthesize-default-resources/lambda-ecr-1-copy/AssetImage/Repository", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecr.RepositoryBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecr_assets.DockerImageAsset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "synthesize-default-resources/lambda-ecr-1-copy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "imageUri": { + "Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/default-resources/ecr-asset:16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622" + } + }, + "role": { + "Fn::GetAtt": [ + "lambdaecr1copyServiceRole2A9FAF5F", + "Arn" + ] + }, + "packageType": "Image" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.CfnFunction", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.Function", + "version": "0.0.0" + } + }, + "lambda-ecr-2": { + "id": "lambda-ecr-2", + "path": "synthesize-default-resources/lambda-ecr-2", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "synthesize-default-resources/lambda-ecr-2/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "synthesize-default-resources/lambda-ecr-2/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "synthesize-default-resources/lambda-ecr-2/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-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "AssetImage": { + "id": "AssetImage", + "path": "synthesize-default-resources/lambda-ecr-2/AssetImage", + "children": { + "Staging": { + "id": "Staging", + "path": "synthesize-default-resources/lambda-ecr-2/AssetImage/Staging", + "constructInfo": { + "fqn": "aws-cdk-lib.AssetStaging", + "version": "0.0.0" + } + }, + "Repository": { + "id": "Repository", + "path": "synthesize-default-resources/lambda-ecr-2/AssetImage/Repository", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecr.RepositoryBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecr_assets.DockerImageAsset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "synthesize-default-resources/lambda-ecr-2/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "imageUri": { + "Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/default-resources/ecr-asset-2:16624c2a162b07c5cc0e2c59c484f638bac238ca558ccbdc2aa0e0535df3e622" + } + }, + "role": { + "Fn::GetAtt": [ + "lambdaecr2ServiceRole2EA363D2", + "Arn" + ] + }, + "packageType": "Image" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.CfnFunction", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.Function", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "StagingStack-default-resources-ACCOUNT-REGION": { + "id": "StagingStack-default-resources-ACCOUNT-REGION", + "path": "StagingStack-default-resources-ACCOUNT-REGION", + "children": { + "CdkFileRole": { + "id": "CdkFileRole", + "path": "StagingStack-default-resources-ACCOUNT-REGION/CdkFileRole", + "children": { + "ImportCdkFileRole": { + "id": "ImportCdkFileRole", + "path": "StagingStack-default-resources-ACCOUNT-REGION/CdkFileRole/ImportCdkFileRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "StagingStack-default-resources-ACCOUNT-REGION/CdkFileRole/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" + }, + "roleName": { + "Fn::Join": [ + "", + [ + "cdk-default-resources-file-role-", + { + "Ref": "AWS::Region" + } + ] + ] + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "StagingStack-default-resources-ACCOUNT-REGION/CdkFileRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "StagingStack-default-resources-ACCOUNT-REGION/CdkFileRole/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": [ + "CdkStagingBucket1636058C", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CdkStagingBucket1636058C", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "BucketKey7092080A", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "CdkFileRoleDefaultPolicy621C7E5B", + "roles": [ + { + "Ref": "CdkFileRoleE26CEABA" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "BucketKey": { + "id": "BucketKey", + "path": "StagingStack-default-resources-ACCOUNT-REGION/BucketKey", + "children": { + "Resource": { + "id": "Resource", + "path": "StagingStack-default-resources-ACCOUNT-REGION/BucketKey/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::KMS::Key", + "aws:cdk:cloudformation:props": { + "keyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:CancelKeyDeletion", + "kms:Create*", + "kms:Delete*", + "kms:Describe*", + "kms:Disable*", + "kms:Enable*", + "kms:Get*", + "kms:List*", + "kms:Put*", + "kms:Revoke*", + "kms:ScheduleKeyDeletion", + "kms:TagResource", + "kms:UntagResource", + "kms:Update*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_kms.CfnKey", + "version": "0.0.0" + } + }, + "Alias": { + "id": "Alias", + "path": "StagingStack-default-resources-ACCOUNT-REGION/BucketKey/Alias", + "children": { + "Resource": { + "id": "Resource", + "path": "StagingStack-default-resources-ACCOUNT-REGION/BucketKey/Alias/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::KMS::Alias", + "aws:cdk:cloudformation:props": { + "aliasName": "alias/cdk-default-resources-staging", + "targetKeyId": { + "Fn::GetAtt": [ + "BucketKey7092080A", + "Arn" + ] + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_kms.CfnAlias", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_kms.Alias", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_kms.Key", + "version": "0.0.0" + } + }, + "CdkStagingBucket": { + "id": "CdkStagingBucket", + "path": "StagingStack-default-resources-ACCOUNT-REGION/CdkStagingBucket", + "children": { + "Resource": { + "id": "Resource", + "path": "StagingStack-default-resources-ACCOUNT-REGION/CdkStagingBucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "bucketEncryption": { + "serverSideEncryptionConfiguration": [ + { + "serverSideEncryptionByDefault": { + "sseAlgorithm": "aws:kms", + "kmsMasterKeyId": { + "Fn::GetAtt": [ + "BucketKey7092080A", + "Arn" + ] + } + } + } + ] + }, + "bucketName": { + "Fn::Join": [ + "", + [ + "cdk-default-resources-staging-", + { + "Ref": "AWS::AccountId" + }, + "-", + { + "Ref": "AWS::Region" + } + ] + ] + }, + "lifecycleConfiguration": { + "rules": [ + { + "noncurrentVersionExpiration": { + "noncurrentDays": 365 + }, + "status": "Enabled" + }, + { + "expirationInDays": 30, + "prefix": "deploy-time/", + "status": "Enabled" + } + ] + }, + "versioningConfiguration": { + "status": "Enabled" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.CfnBucket", + "version": "0.0.0" + } + }, + "Policy": { + "id": "Policy", + "path": "StagingStack-default-resources-ACCOUNT-REGION/CdkStagingBucket/Policy", + "children": { + "Resource": { + "id": "Resource", + "path": "StagingStack-default-resources-ACCOUNT-REGION/CdkStagingBucket/Policy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::BucketPolicy", + "aws:cdk:cloudformation:props": { + "bucket": { + "Ref": "CdkStagingBucket1636058C" + }, + "policyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "CdkStagingBucket1636058C", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CdkStagingBucket1636058C", + "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": [ + "CdkStagingBucket1636058C", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CdkStagingBucket1636058C", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.CfnBucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.BucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.Bucket", + "version": "0.0.0" + } + }, + "CdkImageRole": { + "id": "CdkImageRole", + "path": "StagingStack-default-resources-ACCOUNT-REGION/CdkImageRole", + "children": { + "ImportCdkImageRole": { + "id": "ImportCdkImageRole", + "path": "StagingStack-default-resources-ACCOUNT-REGION/CdkImageRole/ImportCdkImageRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "StagingStack-default-resources-ACCOUNT-REGION/CdkImageRole/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" + }, + "roleName": { + "Fn::Join": [ + "", + [ + "cdk-default-resources-image-role-", + { + "Ref": "AWS::Region" + } + ] + ] + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "StagingStack-default-resources-ACCOUNT-REGION/CdkImageRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "StagingStack-default-resources-ACCOUNT-REGION/CdkImageRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:BatchGetImage", + "ecr:CompleteLayerUpload", + "ecr:DescribeImages", + "ecr:DescribeRepositories", + "ecr:GetDownloadUrlForLayer", + "ecr:InitiateLayerUpload", + "ecr:PutImage", + "ecr:UploadLayerPart" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "defaultresourcesecrasset2FBE6B8A9", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "defaultresourcesecrasset9191BD6E", + "Arn" + ] + } + ] + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "policyName": "CdkImageRoleDefaultPolicy4A1572DE", + "roles": [ + { + "Ref": "CdkImageRoleF1394AC3" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "default-resources--ecr-asset": { + "id": "default-resources--ecr-asset", + "path": "StagingStack-default-resources-ACCOUNT-REGION/default-resources--ecr-asset", + "children": { + "Resource": { + "id": "Resource", + "path": "StagingStack-default-resources-ACCOUNT-REGION/default-resources--ecr-asset/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECR::Repository", + "aws:cdk:cloudformation:props": { + "lifecyclePolicy": { + "lifecyclePolicyText": "{\"rules\":[{\"rulePriority\":1,\"description\":\"Garbage collect old image versions and keep the specified number of latest versions\",\"selection\":{\"tagStatus\":\"any\",\"countType\":\"imageCountMoreThan\",\"countNumber\":3},\"action\":{\"type\":\"expire\"}}]}" + }, + "repositoryName": "default-resources/ecr-asset" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecr.CfnRepository", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecr.Repository", + "version": "0.0.0" + } + }, + "default-resources--ecr-asset-2": { + "id": "default-resources--ecr-asset-2", + "path": "StagingStack-default-resources-ACCOUNT-REGION/default-resources--ecr-asset-2", + "children": { + "Resource": { + "id": "Resource", + "path": "StagingStack-default-resources-ACCOUNT-REGION/default-resources--ecr-asset-2/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECR::Repository", + "aws:cdk:cloudformation:props": { + "lifecyclePolicy": { + "lifecyclePolicyText": "{\"rules\":[{\"rulePriority\":1,\"description\":\"Garbage collect old image versions and keep the specified number of latest versions\",\"selection\":{\"tagStatus\":\"any\",\"countType\":\"imageCountMoreThan\",\"countNumber\":3},\"action\":{\"type\":\"expire\"}}]}" + }, + "repositoryName": "default-resources/ecr-asset-2" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecr.CfnRepository", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecr.Repository", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/app-staging-synthesizer-alpha.DefaultStagingStack", + "version": "0.0.0" + } + }, + "integ-tests": { + "id": "integ-tests", + "path": "integ-tests", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "integ-tests/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "integ-tests/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "integ-tests/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-tests/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-tests/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.ts b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.ts new file mode 100644 index 0000000000000..e8f9aa1e27682 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/integ.synth-default-resources.ts @@ -0,0 +1,51 @@ +import * as path from 'path'; +import * as integ from '@aws-cdk/integ-tests-alpha'; +import { App, Stack } from 'aws-cdk-lib'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import { AppStagingSynthesizer } from '../lib'; + +const app = new App(); + +const stack = new Stack(app, 'synthesize-default-resources', { + synthesizer: AppStagingSynthesizer.defaultResources({ + appId: 'default-resources', + }), +}); + +new lambda.Function(stack, 'lambda-s3', { + code: lambda.AssetCode.fromAsset(path.join(__dirname, 'assets')), + handler: 'index.handler', + runtime: lambda.Runtime.PYTHON_3_10, +}); + +new lambda.Function(stack, 'lambda-ecr-1', { + code: lambda.EcrImageCode.fromAssetImage(path.join(__dirname, 'assets'), { + assetName: 'ecr-asset', + }), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, +}); + +// This lambda will share the same published asset as lambda-ecr-1 +new lambda.Function(stack, 'lambda-ecr-1-copy', { + code: lambda.EcrImageCode.fromAssetImage(path.join(__dirname, 'assets'), { + assetName: 'ecr-asset', + }), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, +}); + +// This lambda will use a different published asset as lambda-ecr-1 +new lambda.Function(stack, 'lambda-ecr-2', { + code: lambda.EcrImageCode.fromAssetImage(path.join(__dirname, 'assets'), { + assetName: 'ecr-asset-2', + }), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, +}); + +new integ.IntegTest(app, 'integ-tests', { + testCases: [stack], +}); + +app.synth(); diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/per-env-staging-factory.test.ts b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/per-env-staging-factory.test.ts new file mode 100644 index 0000000000000..a5001aaa9f2f4 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/per-env-staging-factory.test.ts @@ -0,0 +1,79 @@ +import { App, Stack } from 'aws-cdk-lib'; +import { APP_ID } from './util'; +import { AppStagingSynthesizer } from '../lib'; + +describe('per environment cache', () => { + test('same app, same env', () => { + // GIVEN + const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: APP_ID, + }), + }); + new Stack(app, 'Stack1', { + env: { + account: '000000000000', + region: 'us-east-1', + }, + }); + new Stack(app, 'Stack2', { + env: { + account: '000000000000', + region: 'us-east-1', + }, + }); + + // THEN + // stacks share the same staging resources + const asm = app.synth(); + expect(asm.stacks.length).toEqual(3); + const stagingResources = asm.stacks.filter((s) => s.displayName.startsWith('StagingStack')); + expect(stagingResources.length).toEqual(1); + }); + + test('same app, different envs', () => { + // GIVEN + const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: APP_ID, + }), + }); + new Stack(app, 'Stack1', { + env: { + account: '000000000000', + region: 'us-east-1', + }, + }); + new Stack(app, 'Stack2', { + env: { + account: '000000000000', + region: 'us-west-2', + }, + }); + + // THEN + // separate stacks for staging resources + const asm = app.synth(); + expect(asm.stacks.length).toEqual(4); + const stagingResources = asm.stacks.filter((s) => s.displayName.startsWith('StagingStack')); + expect(stagingResources.length).toEqual(2); + }); + + test('apps must be gnostic', () => { + // GIVEN + const app = new App({ + defaultStackSynthesizer: AppStagingSynthesizer.defaultResources({ + appId: APP_ID, + }), + }); + new Stack(app, 'Stack1', { + env: { + account: '000000000000', + region: 'us-east-1', + }, + }); + + // THEN + expect(() => new Stack(app, 'Stack2')).toThrowError(/It is not safe to use AppStagingSynthesizer for both environment-agnostic and environment-aware stacks at the same time./); + }); +}); diff --git a/packages/@aws-cdk/app-staging-synthesizer-alpha/test/util.ts b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/util.ts new file mode 100644 index 0000000000000..b90fbbe98b7e7 --- /dev/null +++ b/packages/@aws-cdk/app-staging-synthesizer-alpha/test/util.ts @@ -0,0 +1,16 @@ +import * as cxapi from 'aws-cdk-lib/cx-api'; + +export const CFN_CONTEXT = { + 'AWS::Region': 'the_region', + 'AWS::AccountId': 'the_account', + 'AWS::URLSuffix': 'domain.aws', +}; +export const APP_ID = 'appid'; + +export function isAssetManifest(x: cxapi.CloudArtifact): x is cxapi.AssetManifestArtifact { + return x instanceof cxapi.AssetManifestArtifact; +} + +export function last(xs?: A[]): A | undefined { + return xs ? xs[xs.length - 1] : undefined; +} diff --git a/packages/@aws-cdk/aws-amplify-alpha/package.json b/packages/@aws-cdk/aws-amplify-alpha/package.json index de00c7d3fe145..85ec256ca5452 100644 --- a/packages/@aws-cdk/aws-amplify-alpha/package.json +++ b/packages/@aws-cdk/aws-amplify-alpha/package.json @@ -88,7 +88,7 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^29.5.1", - "aws-sdk": "^2.1378.0", + "aws-sdk": "^2.1379.0", "aws-cdk-lib": "0.0.0", "constructs": "^10.0.0" }, diff --git a/packages/@aws-cdk/aws-redshift-alpha/package.json b/packages/@aws-cdk/aws-redshift-alpha/package.json index 0d173ecc69482..21bf2401b4558 100644 --- a/packages/@aws-cdk/aws-redshift-alpha/package.json +++ b/packages/@aws-cdk/aws-redshift-alpha/package.json @@ -86,7 +86,7 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^29.5.1", - "aws-sdk": "^2.1378.0", + "aws-sdk": "^2.1379.0", "aws-cdk-lib": "0.0.0", "constructs": "^10.0.0", "@aws-cdk/integ-tests-alpha": "0.0.0" diff --git a/packages/@aws-cdk/aws-sagemaker-alpha/package.json b/packages/@aws-cdk/aws-sagemaker-alpha/package.json index ea2adcda19154..9c5338a44f351 100644 --- a/packages/@aws-cdk/aws-sagemaker-alpha/package.json +++ b/packages/@aws-cdk/aws-sagemaker-alpha/package.json @@ -87,7 +87,7 @@ "@aws-cdk/integ-runner": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^29.5.1", - "aws-sdk": "^2.1378.0", + "aws-sdk": "^2.1379.0", "jest": "^29.5.0", "aws-cdk-lib": "0.0.0", "constructs": "^10.0.0", diff --git a/packages/@aws-cdk/aws-scheduler-alpha/.eslintrc.js b/packages/@aws-cdk/aws-scheduler-alpha/.eslintrc.js new file mode 100644 index 0000000000000..2a2c7498774d0 --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/.eslintrc.js @@ -0,0 +1,6 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; + +baseConfig.rules['import/no-extraneous-dependencies'] = ['error', { devDependencies: true, peerDependencies: true }]; + +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-scheduler-alpha/.gitignore b/packages/@aws-cdk/aws-scheduler-alpha/.gitignore new file mode 100644 index 0000000000000..4b4c1e6d4716a --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/.gitignore @@ -0,0 +1,24 @@ +*.d.ts +*.generated.ts +*.js +*.js.map +*.snk +.jsii +.jsii.gz +.LAST_BUILD +.LAST_PACKAGE +nyc.config.js +.nyc_output +.vscode +.types-compat +coverage +dist +tsconfig.json +!.eslintrc.js +!jest.config.js + +junit.xml +!**/*.snapshot/**/asset.*/*.js +!**/*.snapshot/**/asset.*/*.d.ts + +!**/*.snapshot/**/asset.*/** diff --git a/packages/@aws-cdk/aws-scheduler-alpha/.npmignore b/packages/@aws-cdk/aws-scheduler-alpha/.npmignore new file mode 100644 index 0000000000000..39a19f1bc4c14 --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/.npmignore @@ -0,0 +1,39 @@ +# The basics +*.ts +*.tgz +*.snk +!*.d.ts +!*.js +test/ +**/test/** + +# Coverage +coverage +.nyc_output +.nycrc + +# Build gear +build-tools +dist +scripts +.LAST_BUILD +.LAST_PACKAGE + +tsconfig.json +*.tsbuildinfo + +!.jsii +!.jsii.gz +.eslintrc.js + +# exclude cdk artifacts +**/cdk.out +junit.xml + +!*.lit.ts + +# exclude source maps as they only work locally +*.map + +# Nested node_modules +aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/node_modules diff --git a/packages/@aws-cdk/aws-scheduler-alpha/LICENSE b/packages/@aws-cdk/aws-scheduler-alpha/LICENSE new file mode 100644 index 0000000000000..9b722c65c5481 --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/aws-scheduler-alpha/NOTICE b/packages/@aws-cdk/aws-scheduler-alpha/NOTICE new file mode 100644 index 0000000000000..a27b7dd317649 --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-scheduler-alpha/README.md b/packages/@aws-cdk/aws-scheduler-alpha/README.md new file mode 100644 index 0000000000000..171cecdf66854 --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/README.md @@ -0,0 +1,138 @@ +# Amazon EventBridge Scheduler Construct Library + + +--- + +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. +> They are subject to non-backward compatible changes or removal in any future version. These are +> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be +> announced in the release notes. This means that while you may use them, you may need to update +> your source code when upgrading to a newer version of this package. + +--- + + + +[Amazon EventBridge Scheduler](https://aws.amazon.com/blogs/compute/introducing-amazon-eventbridge-scheduler/) is a feature from Amazon EventBridge +that allows you to create, run, and manage scheduled tasks at scale. With EventBridge Scheduler, you can schedule one-time or recurrently tens +of millions of tasks across many AWS services without provisioning or managing underlying infrastructure. + +1. **Schedule**: A schedule is the main resource you create, configure, and manage using Amazon EventBridge Scheduler. Every schedule has a schedule expression that determines when, and with what frequency, the schedule runs. EventBridge Scheduler supports three types of schedules: rate, cron, and one-time schedules. When you create a schedule, you configure a target for the schedule to invoke. +2. **Targets**: A target is an API operation that EventBridge Scheduler calls on your behalf every time your schedule runs. EventBridge Scheduler +supports two types of targets: templated targets and universal targets. Templated targets invoke common API operations across a core groups of +services. For example, EventBridge Scheduler supports templated targets for invoking AWS Lambda Function or starting execution of Step Function state +machine. For API operations that are not supported by templated targets you can use customizeable universal targets. Universal targets support calling +more than 6,000 API operations across over 270 AWS services. +3. **Schedule Group**: A schedule group is an Amazon EventBridge Scheduler resource that you use to organize your schedules. Your AWS account comes +with a default scheduler group. A new schedule will always be added to a scheduling group. If you do not provide a scheduling group to add to, it +will be added to the default scheduling group. You can create up to 500 schedule groups in your AWS account. Groups can be used to organize the +schedules logically, access the schedule metrics and manage permissions at group granularity (see details below). Scheduling groups support tagging: +with EventBridge Scheduler, you apply tags to schedule groups, not to individual schedules to organize your resources. + +This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. It allows you to define Event Bridge Schedules. + +> This module is in active development. Some features may not be implemented yet. + +## Defining a schedule + +TODO: Schedule is not yet implemented. See section in [L2 Event Bridge Scheduler RFC](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0474-event-bridge-scheduler-l2.md) + +### Schedule Expressions + +You can choose from three schedule types when configuring your schedule: rate-based, cron-based, and one-time schedules. + +Both rate-based and cron-based schedules are recurring schedules. You can configure each recurring schedule type using a schedule expression. For +cron-based schedule you can specify a time zone in which EventBridge Scheduler evaluates the expression. + + +> ScheduleExpression should be used together with class Schedule, which is not yet implemented. + +[comment]: <> (TODO: Switch to `ts` once Schedule is implemented) + +```text +const rateBasedSchedule = new Schedule(this, 'Schedule', { + scheduleExpression: ScheduleExpression.rate(Duration.minutes(10)), + target, + description: 'This is a test rate-based schedule', +}); + +const cronBasedSchedule = new Schedule(this, 'Schedule', { + scheduleExpression: ScheduleExpression.cron({ + minute: '0', + hour: '23', + day: '20', + month: '11', + timeZone: TimeZone.AMERICA_NEW_YORK, + }), + target, + description: 'This is a test cron-based schedule that will run at 11:00 PM, on day 20 of the month, only in November in New York timezone', +}); +``` + +A one-time schedule is a schedule that invokes a target only once. You configure a one-time schedule when by specifying the time of the day, date, +and time zone in which EventBridge Scheduler evaluates the schedule. + +[comment]: <> (TODO: Switch to `ts` once Schedule is implemented) + +```text +const oneTimeSchedule = new Schedule(this, 'Schedule', { + scheduleExpression: ScheduleExpression.at( + new Date(2022, 10, 20, 19, 20, 23), + TimeZone.AMERICA_NEW_YORK, + ), + target, + description: 'This is a one-time schedule in New York timezone', +}); +``` + +### Grouping Schedules + +TODO: Group is not yet implemented. See section in [L2 Event Bridge Scheduler RFC](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0474-event-bridge-scheduler-l2.md) + +## Scheduler Targets + +TODO: Scheduler Targets Module is not yet implemented. See section in [L2 Event Bridge Scheduler RFC](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0474-event-bridge-scheduler-l2.md) + +### Input + +TODO: Target Input is not yet implemented. See section in [L2 Event Bridge Scheduler RFC](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0474-event-bridge-scheduler-l2.md) + + +### Specifying Execution Role + +TODO: Not yet implemented. See section in [L2 Event Bridge Scheduler RFC](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0474-event-bridge-scheduler-l2.md) + +### Cross-account and cross-region targets + +Executing cross-account and cross-region targets are not supported yet. + +### Specifying Encryption key + +TODO: Not yet implemented. See section in [L2 Event Bridge Scheduler RFC](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0474-event-bridge-scheduler-l2.md) + +## Error-handling + +TODO: Not yet implemented. See section in [L2 Event Bridge Scheduler RFC](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0474-event-bridge-scheduler-l2.md) + +## Overriding Target Properties + +TODO: Not yet implemented. See section in [L2 Event Bridge Scheduler RFC](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0474-event-bridge-scheduler-l2.md) + + +## Monitoring + +You can monitor Amazon EventBridge Scheduler using CloudWatch, which collects raw data +and processes it into readable, near real-time metrics. EventBridge Scheduler emits +a set of metrics for all schedules, and an additional set of metrics for schedules that +have an associated dead-letter queue (DLQ). If you configure a DLQ for your schedule, +EventBridge Scheduler publishes additional metrics when your schedule exhausts its retry policy. + +### Metrics for all schedules + +TODO: Not yet implemented. See section in [L2 Event Bridge Scheduler RFC](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0474-event-bridge-scheduler-l2.md) + +### Metrics for a Group + +TODO: Not yet implemented. See section in [L2 Event Bridge Scheduler RFC](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0474-event-bridge-scheduler-l2.md) diff --git a/packages/@aws-cdk/aws-scheduler-alpha/jest.config.js b/packages/@aws-cdk/aws-scheduler-alpha/jest.config.js new file mode 100644 index 0000000000000..7ea6abe8036b2 --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/jest.config.js @@ -0,0 +1,10 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/jest.config'); +module.exports = { + ...baseConfig, + coverageThreshold: { + global: { + ...baseConfig.coverageThreshold.global, + branches: 60, + }, + }, + };; diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/index.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/index.ts new file mode 100644 index 0000000000000..c00ab258ae963 --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/index.ts @@ -0,0 +1 @@ +export * from './schedule-expression'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule-expression.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule-expression.ts new file mode 100644 index 0000000000000..89aa1f9205dae --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule-expression.ts @@ -0,0 +1,95 @@ +import { Duration, TimeZone } from 'aws-cdk-lib'; +import * as events from 'aws-cdk-lib/aws-events'; + +/** + * ScheduleExpression for EventBridge Schedule + * + * You can choose from three schedule types when configuring your schedule: rate-based, cron-based, and one-time schedules. + * Both rate-based and cron-based schedules are recurring schedules. + * + * @see https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html + */ +export abstract class ScheduleExpression { + /** + * Construct a one-time schedule from a date. + * + * @param date The date and time to use. The millisecond part will be ignored. + * @param timeZone The time zone to use for interpreting the date. Default: - UTC + */ + public static at(date: Date, timeZone?: TimeZone): ScheduleExpression { + try { + const literal = date.toISOString().split('.')[0]; + return new LiteralScheduleExpression(`at(${literal})`, timeZone ?? TimeZone.ETC_UTC); + } catch (e) { + if (e instanceof RangeError) { + throw new Error('Invalid date'); + } + throw e; + } + } + + /** + * Construct a schedule from a literal schedule expression + * @param expression The expression to use. Must be in a format that EventBridge will recognize + * @param timeZone The time zone to use for interpreting the expression. Default: - UTC + */ + public static expression(expression: string, timeZone?: TimeZone): ScheduleExpression { + return new LiteralScheduleExpression(expression, timeZone ?? TimeZone.ETC_UTC); + } + + /** + * Construct a recurring schedule from an interval and a time unit + * + * Rates may be defined with any unit of time, but when converted into minutes, the duration must be a positive whole number of minutes. + */ + public static rate(duration: Duration): ScheduleExpression { + const schedule = events.Schedule.rate(duration); + return new LiteralScheduleExpression(schedule.expressionString); + } + + /** + * Create a recurring schedule from a set of cron fields and time zone. + */ + public static cron(options: CronOptionsWithTimezone): ScheduleExpression { + const { timeZone, ...cronOptions } = options; + const schedule = events.Schedule.cron(cronOptions); + return new LiteralScheduleExpression(schedule.expressionString, timeZone); + } + + /** + * Retrieve the expression for this schedule + */ + public abstract readonly expressionString: string; + + /** + * Retrieve the expression for this schedule + */ + public abstract readonly timeZone?: TimeZone; + + protected constructor() {} +} + +/** + * Options to configure a cron expression + * + * All fields are strings so you can use complex expressions. Absence of + * a field implies '*' or '?', whichever one is appropriate. + * + * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html#cron-expressions + */ +export interface CronOptionsWithTimezone extends events.CronOptions { + /** + * The timezone to run the schedule in + * + * @default - TimeZone.ETC_UTC + */ + readonly timeZone?: TimeZone; +} + +const DEFAULT_TIMEZONE = TimeZone.ETC_UTC; + +class LiteralScheduleExpression extends ScheduleExpression { + constructor(public readonly expressionString: string, public readonly timeZone: TimeZone = DEFAULT_TIMEZONE) { + super(); + } +} diff --git a/packages/@aws-cdk/aws-scheduler-alpha/package.json b/packages/@aws-cdk/aws-scheduler-alpha/package.json new file mode 100644 index 0000000000000..0dd7f971c0784 --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/package.json @@ -0,0 +1,119 @@ +{ + "name": "@aws-cdk/aws-scheduler-alpha", + "version": "0.0.0", + "description": "The CDK Construct Library for Amazon Scheduler", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awscdk.services.scheduler.alpha", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "scheduler-alpha" + } + }, + "dotnet": { + "namespace": "Amazon.CDK.AWS.Scheduler.Alpha", + "packageId": "Amazon.CDK.AWS.Scheduler.Alpha", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/main/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-cdk.aws-scheduler-alpha", + "module": "aws_cdk.aws_scheduler_alpha", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 2" + ] + }, + "go": { + "moduleName": "github.com/aws/aws-cdk-go", + "packageName": "awscdkscheduleralpha" + } + }, + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-scheduler-alpha" + }, + "homepage": "https://github.com/aws/aws-cdk", + "scripts": { + "build": "cdk-build", + "integ": "integ-runner", + "lint": "cdk-lint", + "package": "cdk-package", + "awslint": "cdk-awslint", + "pkglint": "pkglint -f", + "test": "cdk-test", + "watch": "cdk-watch", + "compat": "cdk-compat", + "build+test": "yarn build && yarn test", + "build+test+package": "yarn build+test && yarn package", + "rosetta:extract": "yarn --silent jsii-rosetta extract", + "build+extract": "yarn build && yarn rosetta:extract", + "build+test+extract": "yarn build+test && yarn rosetta:extract" + }, + "cdk-build": { + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "aws-scheduler" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/cdk-build-tools": "0.0.0", + "@aws-cdk/integ-runner": "0.0.0", + "@aws-cdk/cfn2ts": "0.0.0", + "@aws-cdk/pkglint": "0.0.0", + "@types/jest": "^29.5.1", + "aws-cdk-lib": "0.0.0", + "constructs": "^10.0.0" + }, + "dependencies": {}, + "peerDependencies": { + "aws-cdk-lib": "^0.0.0", + "constructs": "^10.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "stability": "experimental", + "maturity": "experimental", + "awscdkio": { + "announce": false + }, + "publishConfig": { + "tag": "latest" + }, + "awslint": { + "exclude": [ + "*:*" + ] + }, + "pkglint": { + "exclude": [ + "naming/package-matches-directory", + "assert/assert-dependency" + ] + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-scheduler-alpha/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-scheduler-alpha/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..71131d04c63a3 --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/rosetta/default.ts-fixture @@ -0,0 +1,17 @@ +// Fixture with packages imported, but nothing else +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'; +import { App, Stack, TimeZone, Duration } from 'aws-cdk-lib'; +import { ScheduleExpression } from '@aws-cdk/aws-scheduler-alpha'; + +class Fixture extends cdk.Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + /// here + } +} diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/schedule-expression.test.ts b/packages/@aws-cdk/aws-scheduler-alpha/test/schedule-expression.test.ts new file mode 100644 index 0000000000000..06fdb794ca600 --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/schedule-expression.test.ts @@ -0,0 +1,164 @@ +import { Duration, Lazy, Stack, TimeZone } from 'aws-cdk-lib'; +import { ScheduleExpression } from '../lib'; + +describe('schedule expression', () => { + test('cron expressions day and dow are mutex: given weekday', () => { + // Run every 10 minutes Monday through Friday + expect('cron(0/10 * ? * MON-FRI *)').toEqual(ScheduleExpression.cron({ + minute: '0/10', + weekDay: 'MON-FRI', + }).expressionString); + }); + + test('cron expressions day and dow are mutex: given month day', () => { + // Run at 8:00 am (UTC) every 1st day of the month + expect('cron(0 8 1 * ? *)').toEqual(ScheduleExpression.cron({ + minute: '0', + hour: '8', + day: '1', + }).expressionString); + }); + + test('cron expressions day and dow are mutex: given neither', () => { + // Run at 10:00 am (UTC) every day + expect('cron(0 10 * * ? *)').toEqual(ScheduleExpression.cron({ + minute: '0', + hour: '10', + }).expressionString); + }); + + test('cron expressions saves timezone', () => { + expect(TimeZone.EUROPE_LONDON).toEqual(ScheduleExpression.cron( + { + minute: '0', + hour: '10', + timeZone: TimeZone.EUROPE_LONDON, + }).timeZone); + }); + + test('cron expressions timezone is UTC if not specified', () => { + expect(TimeZone.ETC_UTC).toEqual(ScheduleExpression.cron( + { + minute: '0', + hour: '10', + }).timeZone); + }); + + test('rate cannot be 0', () => { + expect(() => { + ScheduleExpression.rate(Duration.days(0)); + }).toThrow(/Duration cannot be 0/); + }); + + test('rate cannot be negative', () => { + expect(() => { + ScheduleExpression.rate(Duration.minutes(-2)); + }).toThrow(/Duration amounts cannot be negative/); + }); + + test('rate can be from a token', () => { + const stack = new Stack(); + const lazyDuration = Duration.minutes(Lazy.number({ produce: () => 5 })); + const rate = ScheduleExpression.rate(lazyDuration); + expect('rate(5 minutes)').toEqual(stack.resolve(rate).expressionString); + }); + + test('rate can be in minutes', () => { + expect('rate(10 minutes)').toEqual( + ScheduleExpression.rate(Duration.minutes(10)) + .expressionString); + }); + + test('rate can be in days', () => { + expect('rate(10 days)').toEqual( + ScheduleExpression.rate(Duration.days(10)) + .expressionString); + }); + + test('rate can be in hours', () => { + expect('rate(10 hours)').toEqual( + ScheduleExpression.rate(Duration.hours(10)) + .expressionString); + }); + + test('rate can be in seconds', () => { + expect('rate(2 minutes)').toEqual( + ScheduleExpression.rate(Duration.seconds(120)) + .expressionString); + }); + + test('rate must not be in seconds when specified as a token', () => { + expect(() => { + ScheduleExpression.rate(Duration.seconds(Lazy.number({ produce: () => 5 }))); + }).toThrow(/Allowed units for scheduling/); + }); + + test('one-time expression string has expected date', () => { + const x = ScheduleExpression.at(new Date(2022, 10, 20, 19, 20, 23)); + expect(x.expressionString).toEqual('at(2022-11-20T19:20:23)'); + }); + + test('one-time expression time zone is UTC if not provided', () => { + const x = ScheduleExpression.at(new Date(2022, 10, 20, 19, 20, 23)); + expect(x.timeZone).toEqual(TimeZone.ETC_UTC); + }); + + test('one-time expression has expected time zone if provided', () => { + const x = ScheduleExpression.at(new Date(2022, 10, 20, 19, 20, 23), TimeZone.EUROPE_LONDON); + expect(x.expressionString).toEqual('at(2022-11-20T19:20:23)'); + expect(x.timeZone).toEqual(TimeZone.EUROPE_LONDON); + }); + + test('one-time expression milliseconds ignored', () => { + const x = ScheduleExpression.at(new Date(Date.UTC(2022, 10, 20, 19, 20, 23, 111))); + expect(x.expressionString).toEqual('at(2022-11-20T19:20:23)'); + }); + + test('one-time expression with invalid date throws', () => { + expect(() => ScheduleExpression.at(new Date('13-20-1969'))).toThrowError('Invalid date'); + }); +}); + +describe('fractional minutes checks', () => { + test('rate cannot be a fractional amount of minutes (defined with seconds)', () => { + expect(() => { + ScheduleExpression.rate(Duration.seconds(150)); + }).toThrow(/cannot be converted into a whole number of/); + }); + + test('rate cannot be a fractional amount of minutes (defined with minutes)', () => { + expect(()=> { + ScheduleExpression.rate(Duration.minutes(5/3)); + }).toThrow(/must be a whole number of/); + }); + + test('rate cannot be a fractional amount of minutes (defined with hours)', () => { + expect(()=> { + ScheduleExpression.rate(Duration.hours(1.03)); + }).toThrow(/cannot be converted into a whole number of/); + }); + + test('rate cannot be less than 1 minute (defined with seconds)', () => { + expect(() => { + ScheduleExpression.rate(Duration.seconds(30)); + }).toThrow(/'30 seconds' cannot be converted into a whole number of minutes./); + }); + + test('rate cannot be less than 1 minute (defined with minutes as fractions)', () => { + expect(() => { + ScheduleExpression.rate(Duration.minutes(1/2)); + }).toThrow(/must be a whole number of/); + }); + + test('rate cannot be less than 1 minute (defined with minutes as decimals)', () => { + expect(() => { + ScheduleExpression.rate(Duration.minutes(0.25)); + }).toThrow(/must be a whole number of/); + }); + + test('rate can be in minutes', () => { + expect('rate(10 minutes)').toEqual( + ScheduleExpression.rate(Duration.minutes(10)) + .expressionString); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-scheduler-alpha/tsconfig.dev.json b/packages/@aws-cdk/aws-scheduler-alpha/tsconfig.dev.json new file mode 100644 index 0000000000000..4470bb29bf6da --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/tsconfig.dev.json @@ -0,0 +1,38 @@ +{ + "$": "Config file for ts-node", + "ts-node": { + "preferTsExts": true + }, + "compilerOptions": { + "alwaysStrict": true, + "experimentalDecorators": true, + "incremental": true, + "lib": [ + "es2020" + ], + "module": "CommonJS", + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "stripInternal": false, + "target": "ES2020", + "composite": false, + "tsBuildInfoFile": "tsconfig.dev.tsbuildinfo" + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules", + ".types-compat", + "**/*.d.ts" + ] +} diff --git a/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json b/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json index 58a13cadf9785..46239f9a73d9c 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json +++ b/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json @@ -24504,7 +24504,7 @@ "Enable": "Specifies whether the detector is to be enabled on creation.", "Features": "A list of features that will be configured for the detector.", "FindingPublishingFrequency": "Specifies how frequently updated findings are exported.", - "Tags": "The tags to be added to a new detector resource. Each tag consists of a key and an optional value, both of which you define.\n\nFor more information, see [Tag](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html) ." + "Tags": "Specifies tags added to a new detector resource. Each tag consists of a key and an optional value, both of which you define.\n\nCurrently, support is available only for creating and deleting a tag. No support exists for updating the tags.\n\nFor more information, see [Tag](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html) ." } }, "AWS::GuardDuty::Detector.CFNDataSourceConfigurations": { @@ -28428,11 +28428,11 @@ "IsAbstract": "A boolean value that specifies whether the component type is abstract.", "IsSchemaInitialized": "A boolean value that specifies whether the component type has a schema initializer and that the schema initializer has run.", "Ref": "`Ref` returns the workspace Id and the ComponentType Id.", - "Status": "", - "Status.Error": "", - "Status.Error.Code": "", - "Status.Error.Message": "", - "Status.State": "", + "Status": "The component type status.", + "Status.Error": "The component type error.", + "Status.Error.Code": "component type error code.", + "Status.Error.Message": "The component type error message.", + "Status.State": "The component type state.", "UpdateDateTime": "The component type the update time." }, "description": "Use the `AWS::IoTTwinMaker::ComponentType` resource to declare a component type.", @@ -28484,10 +28484,10 @@ }, "AWS::IoTTwinMaker::ComponentType.Error": { "attributes": {}, - "description": "", + "description": "The component type error.", "properties": { - "Code": "", - "Message": "" + "Code": "The component type error code.", + "Message": "The component type error message." } }, "AWS::IoTTwinMaker::ComponentType.Function": { @@ -28537,18 +28537,18 @@ }, "AWS::IoTTwinMaker::ComponentType.RelationshipValue": { "attributes": {}, - "description": "", + "description": "The component type relationship value.", "properties": { - "TargetComponentName": "", - "TargetEntityId": "" + "TargetComponentName": "The target component name.", + "TargetEntityId": "The target entity Id." } }, "AWS::IoTTwinMaker::ComponentType.Status": { "attributes": {}, - "description": "", + "description": "The component type status.", "properties": { - "Error": "", - "State": "" + "Error": "The component type error.", + "State": "The component type status state." } }, "AWS::IoTTwinMaker::Entity": { @@ -28557,11 +28557,11 @@ "CreationDateTime": "The date and time the entity was created.", "HasChildEntities": "A boolean value that specifies whether the entity has child entities or not.", "Ref": "`Ref` returns the workspace Id and the entity Id.", - "Status": "", - "Status.Error": "", - "Status.Error.Code": "", - "Status.Error.Message": "", - "Status.State": "", + "Status": "The entity status.", + "Status.Error": "The error.", + "Status.Error.Code": "The error code.", + "Status.Error.Message": "The error message.", + "Status.State": "The state ofthe entity, component type, or workspace.", "UpdateDateTime": "The date and time when the component type was last updated." }, "description": "Use the `AWS::IoTTwinMaker::Entity` resource to declare an entity.", @@ -28590,13 +28590,13 @@ }, "AWS::IoTTwinMaker::Entity.DataType": { "attributes": {}, - "description": "", + "description": "The entity data type.", "properties": { - "AllowedValues": "", - "NestedType": "", - "Relationship": "", - "Type": "", - "UnitOfMeasure": "" + "AllowedValues": "The allowed values.", + "NestedType": "The nested type.", + "Relationship": "The relationship.", + "Type": "The entity type.", + "UnitOfMeasure": "The unit of measure." } }, "AWS::IoTTwinMaker::Entity.DataValue": { @@ -28616,26 +28616,26 @@ }, "AWS::IoTTwinMaker::Entity.Definition": { "attributes": {}, - "description": "", + "description": "The entity definition.", "properties": { - "Configuration": "", - "DataType": "", - "DefaultValue": "", - "IsExternalId": "", - "IsFinal": "", - "IsImported": "", - "IsInherited": "", - "IsRequiredInEntity": "", - "IsStoredExternally": "", - "IsTimeSeries": "" + "Configuration": "The configuration.", + "DataType": "The data type", + "DefaultValue": "The default value.", + "IsExternalId": "Displays if the entity has a external Id.", + "IsFinal": "Displays if the entity is final.", + "IsImported": "Displays if the entity is imported.", + "IsInherited": "Displays if the entity is inherited.", + "IsRequiredInEntity": "Displays if the entity is a required entity.", + "IsStoredExternally": "Displays if the entity is tored externally.", + "IsTimeSeries": "Displays if the entity" } }, "AWS::IoTTwinMaker::Entity.Error": { "attributes": {}, - "description": "", + "description": "The entity error.", "properties": { - "Code": "", - "Message": "" + "Code": "The entity error code.", + "Message": "The entity error message." } }, "AWS::IoTTwinMaker::Entity.Property": { @@ -28656,18 +28656,18 @@ }, "AWS::IoTTwinMaker::Entity.Relationship": { "attributes": {}, - "description": "", + "description": "The entity relationship.", "properties": { - "RelationshipType": "", - "TargetComponentTypeId": "" + "RelationshipType": "The relationship type.", + "TargetComponentTypeId": "the component type Id target." } }, "AWS::IoTTwinMaker::Entity.RelationshipValue": { "attributes": {}, - "description": "", + "description": "The entity relationship.", "properties": { - "TargetComponentName": "", - "TargetEntityId": "" + "TargetComponentName": "The target component name.", + "TargetEntityId": "The target entity Id." } }, "AWS::IoTTwinMaker::Entity.Status": { @@ -28682,7 +28682,7 @@ "attributes": { "Arn": "The scene ARN.", "CreationDateTime": "The date and time when the scene was created.", - "GeneratedSceneMetadata": "", + "GeneratedSceneMetadata": "The generated scene metadata.", "Ref": "`Ref` returns the workspace Id and the sence Id.", "UpdateDateTime": "The scene the update time." }, @@ -28692,7 +28692,7 @@ "ContentLocation": "The relative path that specifies the location of the content definition file.", "Description": "The description of this scene.", "SceneId": "The scene ID.", - "SceneMetadata": "", + "SceneMetadata": "The scene metadata.", "Tags": "The ComponentType tags.", "WorkspaceId": "The ID of the workspace." } @@ -31232,11 +31232,11 @@ "properties": { "ColumnNames": "An array of UTF-8 strings. A list of column names.", "ColumnWildcard": "A wildcard with exclusions. You must specify either a `ColumnNames` list or the `ColumnWildCard` .", - "DatabaseName": "UTF-8 string, not less than 1 or more than 255 bytes long, matching the [single-line string pattern](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-aws-lake-formation-api-common.html#aws-glue-api-regex-oneLine) .\n\nA database in the Data Catalog .", - "Name": "UTF-8 string, not less than 1 or more than 255 bytes long, matching the [single-line string pattern](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-aws-lake-formation-api-common.html#aws-glue-api-regex-oneLine) .\n\nThe name given by the user to the data filter cell.", + "DatabaseName": "UTF-8 string, not less than 1 or more than 255 bytes long, matching the [single-line string pattern](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-aws-lake-formation-api-common.html) .\n\nA database in the Data Catalog .", + "Name": "UTF-8 string, not less than 1 or more than 255 bytes long, matching the [single-line string pattern](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-aws-lake-formation-api-common.html) .\n\nThe name given by the user to the data filter cell.", "RowFilter": "A PartiQL predicate.", - "TableCatalogId": "Catalog id string, not less than 1 or more than 255 bytes long, matching the [single-line string pattern](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-aws-lake-formation-api-common.html#aws-glue-api-regex-oneLine) .\n\nThe ID of the catalog to which the table belongs.", - "TableName": "UTF-8 string, not less than 1 or more than 255 bytes long, matching the [single-line string pattern](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-aws-lake-formation-api-common.html#aws-glue-api-regex-oneLine) .\n\nA table in the database." + "TableCatalogId": "Catalog id string, not less than 1 or more than 255 bytes long, matching the [single-line string pattern](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-aws-lake-formation-api-common.html) .\n\nThe ID of the catalog to which the table belongs.", + "TableName": "UTF-8 string, not less than 1 or more than 255 bytes long, matching the [single-line string pattern](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-aws-lake-formation-api-common.html) .\n\nA table in the database." } }, "AWS::LakeFormation::DataCellsFilter.ColumnWildcard": { @@ -31256,15 +31256,15 @@ }, "AWS::LakeFormation::DataLakeSettings": { "attributes": {}, - "description": "The `AWS::LakeFormation::DataLakeSettings` resource is an AWS Lake Formation resource type that manages the data lake settings for your account. Note that the CloudFormation template only supports updating the `Admins` list. It does not support updating the [CreateDatabaseDefaultPermissions](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-aws-lake-formation-api-settings.html#aws-lake-formation-api-aws-lake-formation-api-settings-DataLakeSettings) or [CreateTableDefaultPermissions](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-aws-lake-formation-api-settings.html#aws-lake-formation-api-aws-lake-formation-api-settings-DataLakeSettings) . Those permissions can only be edited in the DataLakeSettings resource via the API.", + "description": "The `AWS::LakeFormation::DataLakeSettings` resource is an AWS Lake Formation resource type that manages the data lake settings for your account.", "properties": { "Admins": "A list of AWS Lake Formation principals.", - "AllowExternalDataFiltering": "", - "AuthorizedSessionTagValueList": "", - "CreateDatabaseDefaultPermissions": "", - "CreateTableDefaultPermissions": "", - "ExternalDataFilteringAllowList": "", - "Parameters": "", + "AllowExternalDataFiltering": "Whether to allow Amazon EMR clusters or other third-party query engines to access data managed by Lake Formation .\n\nIf set to true, you allow Amazon EMR clusters or other third-party engines to access data in Amazon S3 locations that are registered with Lake Formation .\n\nIf false or null, no third-party query engines will be able to access data in Amazon S3 locations that are registered with Lake Formation.\n\nFor more information, see [External data filtering setting](https://docs.aws.amazon.com/lake-formation/latest/dg/initial-LF-setup.html#external-data-filter) .", + "AuthorizedSessionTagValueList": "Lake Formation relies on a privileged process secured by Amazon EMR or the third party integrator to tag the user's role while assuming it. Lake Formation will publish the acceptable key-value pair, for example key = \"LakeFormationTrustedCaller\" and value = \"TRUE\" and the third party integrator must properly tag the temporary security credentials that will be used to call Lake Formation 's administrative API operations.", + "CreateDatabaseDefaultPermissions": "Specifies whether access control on a newly created database is managed by Lake Formation permissions or exclusively by IAM permissions.\n\nA null value indicates that the access is controlled by Lake Formation permissions. `ALL` permissions assigned to `IAM_ALLOWED_PRINCIPALS` group indicates that the user's IAM permissions determine the access to the database. This is referred to as the setting \"Use only IAM access control,\" and is to support backward compatibility with the AWS Glue permission model implemented by IAM permissions.\n\nThe only permitted values are an empty array or an array that contains a single JSON object that grants `ALL` to `IAM_ALLOWED_PRINCIPALS` .\n\nFor more information, see [Changing the default security settings for your data lake](https://docs.aws.amazon.com/lake-formation/latest/dg/change-settings.html) .", + "CreateTableDefaultPermissions": "Specifies whether access control on a newly created table is managed by Lake Formation permissions or exclusively by IAM permissions.\n\nA null value indicates that the access is controlled by Lake Formation permissions. `ALL` permissions assigned to `IAM_ALLOWED_PRINCIPALS` group indicate that the user's IAM permissions determine the access to the table. This is referred to as the setting \"Use only IAM access control,\" and is to support the backward compatibility with the AWS Glue permission model implemented by IAM permissions.\n\nThe only permitted values are an empty array or an array that contains a single JSON object that grants `ALL` permissions to `IAM_ALLOWED_PRINCIPALS` .\n\nFor more information, see [Changing the default security settings for your data lake](https://docs.aws.amazon.com/lake-formation/latest/dg/change-settings.html) .", + "ExternalDataFilteringAllowList": "A list of the account IDs of AWS accounts with Amazon EMR clusters or third-party engines that are allwed to perform data filtering.", + "Parameters": "A key-value map that provides an additional configuration on your data lake. `CrossAccountVersion` is the key you can configure in the `Parameters` field. Accepted values for the `CrossAccountVersion` key are 1, 2, and 3.", "TrustedResourceOwners": "An array of UTF-8 strings.\n\nA list of the resource-owning account IDs that the caller's account can use to share their user access details (user ARNs). The user ARNs can be logged in the resource owner's CloudTrail log. You may want to specify this property when you are in a high-trust boundary, such as the same team or company." } }, @@ -31275,12 +31275,12 @@ }, "AWS::LakeFormation::DataLakeSettings.CreateDatabaseDefaultPermissions": { "attributes": {}, - "description": "", + "description": "Specifies whether access control on a newly created database is managed by Lake Formation permissions or exclusively by IAM permissions.\n\nA null value indicates that the access is controlled by Lake Formation permissions. A value that assigns `ALL` to `IAM_ALLOWED_PRINCIPALS` indicates access control by IAM permissions. This is referred to as the setting \"Use only IAM access control,\" and is for backward compatibility with the AWS Glue permission model implemented by IAM permissions.\n\nThe only permitted values are an empty array or an array that contains a single JSON object that grants `ALL` to `IAM_ALLOWED_PRINCIPALS` .\n\nFor more information, see [Changing the default security settings for your data lake](https://docs.aws.amazon.com/lake-formation/latest/dg/change-settings.html) .", "properties": {} }, "AWS::LakeFormation::DataLakeSettings.CreateTableDefaultPermissions": { "attributes": {}, - "description": "", + "description": "Specifies whether access control on a newly created table is managed by Lake Formation permissions or exclusively by IAM permissions.\n\nA null value indicates that the access is controlled by Lake Formation permissions. A value that assigns `ALL` to `IAM_ALLOWED_PRINCIPALS` indicates access control by IAM permissions. This is referred to as the setting \"Use only IAM access control,\" and is for backward compatibility with the AWS Glue permission model implemented by IAM permissions.\n\nThe only permitted values are an empty array or an array that contains a single JSON object that grants `ALL` to `IAM_ALLOWED_PRINCIPALS` .\n\nFor more information, see [Changing the Default Security Settings for Your Data Lake](https://docs.aws.amazon.com/lake-formation/latest/dg/change-settings.html) .", "properties": {} }, "AWS::LakeFormation::DataLakeSettings.DataLakePrincipal": { @@ -31292,20 +31292,20 @@ }, "AWS::LakeFormation::DataLakeSettings.ExternalDataFilteringAllowList": { "attributes": {}, - "description": "", + "description": "A list of the account IDs of AWS accounts with Amazon EMR clusters that are allowed to perform data filtering.", "properties": {} }, "AWS::LakeFormation::DataLakeSettings.Permissions": { "attributes": {}, - "description": "", + "description": "Permissions granted to a principal.", "properties": {} }, "AWS::LakeFormation::DataLakeSettings.PrincipalPermissions": { "attributes": {}, - "description": "", + "description": "Permissions granted to a principal.", "properties": { - "Permissions": "", - "Principal": "" + "Permissions": "The permissions that are granted to the principal.", + "Principal": "The principal who is granted permissions." } }, "AWS::LakeFormation::Permissions": { @@ -31502,12 +31502,12 @@ }, "AWS::LakeFormation::Resource": { "attributes": {}, - "description": "The `AWS::LakeFormation::Resource` represents the data ( buckets and folders) that is being registered with AWS Lake Formation . During a stack operation, AWS CloudFormation calls the AWS Lake Formation [`RegisterResource`](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-credential-vending.html#aws-lake-formation-api-credential-vending-RegisterResource) API operation to register the resource. To remove a `Resource` type, AWS CloudFormation calls the AWS Lake Formation [`DeregisterResource`](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-credential-vending.html#aws-lake-formation-api-credential-vending-DeregisterResource) API operation.\n\n> `AWS::LakeFormation::Resource` is a legacy resource that doesn't support the `UPDATE` operation. Changes to the resource will require an explicit deletion and recreation to apply new properties.", + "description": "The `AWS::LakeFormation::Resource` represents the data ( buckets and folders) that is being registered with AWS Lake Formation . During a stack operation, AWS CloudFormation calls the AWS Lake Formation [`RegisterResource`](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-credential-vending.html#aws-lake-formation-api-credential-vending-RegisterResource) API operation to register the resource. To remove a `Resource` type, AWS CloudFormation calls the AWS Lake Formation [`DeregisterResource`](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-credential-vending.html#aws-lake-formation-api-credential-vending-DeregisterResource) API operation.", "properties": { "ResourceArn": "The Amazon Resource Name (ARN) of the resource.", "RoleArn": "The IAM role that registered a resource.", "UseServiceLinkedRole": "Designates a trusted caller, an IAM principal, by registering this caller with the Data Catalog .", - "WithFederation": "" + "WithFederation": "Allows Lake Formation to assume a role to access tables in a federated database." } }, "AWS::LakeFormation::Tag": { @@ -31516,8 +31516,8 @@ }, "description": "The `AWS::LakeFormation::Tag` resource represents an LF-tag, which consists of a key and one or more possible values for the key. During a stack operation, AWS CloudFormation calls the AWS Lake Formation `CreateLFTag` API to create a tag, and `UpdateLFTag` API to update a tag resource, and a `DeleteLFTag` to delete it.", "properties": { - "CatalogId": "Catalog id string, not less than 1 or more than 255 bytes long, matching the [single-line string pattern](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-aws-lake-formation-api-common.html#aws-glue-api-regex-oneLine) .\n\nThe identifier for the Data Catalog . By default, the account ID. The Data Catalog is the persistent metadata store. It contains database definitions, table definitions, and other control information to manage your AWS Lake Formation environment.", - "TagKey": "UTF-8 string, not less than 1 or more than 255 bytes long, matching the [single-line string pattern](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-aws-lake-formation-api-common.html#aws-glue-api-regex-oneLine) .\n\nThe key-name for the LF-tag.", + "CatalogId": "Catalog id string, not less than 1 or more than 255 bytes long, matching the [single-line string pattern](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-aws-lake-formation-api-common.html) .\n\nThe identifier for the Data Catalog . By default, the account ID. The Data Catalog is the persistent metadata store. It contains database definitions, table definitions, and other control information to manage your AWS Lake Formation environment.", + "TagKey": "UTF-8 string, not less than 1 or more than 255 bytes long, matching the [single-line string pattern](https://docs.aws.amazon.com/lake-formation/latest/dg/aws-lake-formation-api-aws-lake-formation-api-common.html) .\n\nThe key-name for the LF-tag.", "TagValues": "An array of UTF-8 strings, not less than 1 or more than 50 strings.\n\nA list of possible values of the corresponding `TagKey` of an LF-tag key-value pair." } }, @@ -55765,7 +55765,7 @@ "AppArn": "The Amazon Resource Name (ARN) of the app.", "Ref": "The returned Amazon Resource Name (ARN) for the app." }, - "description": "Creates an AWS Resilience Hub application. An AWS Resilience Hub application is a collection of AWS resources structured to prevent and recover AWS application disruptions. To describe an AWS Resilience Hub application, you provide an application name, resources from one or more AWS CloudFormation stacks, AWS Resource Groups , Terraform state files, AppRegistry applications, and an appropriate resiliency policy. For more information about the number of resources supported per application, see [Service Quotas](https://docs.aws.amazon.com//general/latest/gr/resiliencehub.html#limits_resiliencehub) .\n\nAfter you create an AWS Resilience Hub application, you publish it so that you can run a resiliency assessment on it. You can then use recommendations from the assessment to improve resiliency by running another assessment, comparing results, and then iterating the process until you achieve your goals for recovery time objective (RTO) and recovery point objective (RPO).", + "description": "Creates an AWS Resilience Hub application. An AWS Resilience Hub application is a collection of AWS resources structured to prevent and recover AWS application disruptions. To describe a AWS Resilience Hub application, you provide an application name, resources from one or more AWS CloudFormation stacks, AWS Resource Groups , Terraform state files, AppRegistry applications, and an appropriate resiliency policy. In addition, you can also add resources that are located on Amazon Elastic Kubernetes Service ( Amazon EKS ) clusters as optional resources. For more information about the number of resources supported per application, see [Service quotas](https://docs.aws.amazon.com/general/latest/gr/resiliencehub.html#limits_resiliencehub) .\n\nAfter you create an AWS Resilience Hub application, you publish it so that you can run a resiliency assessment on it. You can then use recommendations from the assessment to improve resiliency by running another assessment, comparing results, and then iterating the process until you achieve your goals for recovery time objective (RTO) and recovery point objective (RPO).", "properties": { "AppAssessmentSchedule": "Assessment execution schedule with 'Daily' or 'Disabled' values.", "AppTemplateBody": "A JSON string that provides information about your application structure. To learn more about the `appTemplateBody` template, see the sample template provided in the *Examples* section.\n\nThe `appTemplateBody` JSON string has the following structure:\n\n- *`resources`*\n\nThe list of logical resources that needs to be included in the AWS Resilience Hub application.\n\nType: Array\n\n> Don't add the resources that you want to exclude. \n\nEach `resources` array item includes the following fields:\n\n- *`logicalResourceId`*\n\nThe logical identifier of the resource.\n\nType: Object\n\nEach `logicalResourceId` object includes the following fields:\n\n- `identifier`\n\nThe identifier of the resource.\n\nType: String\n- `logicalStackName`\n\nThe name of the AWS CloudFormation stack this resource belongs to.\n\nType: String\n- `resourceGroupName`\n\nThe name of the resource group this resource belongs to.\n\nType: String\n- `terraformSourceName`\n\nThe name of the Terraform S3 state file this resource belongs to.\n\nType: String\n- `eksSourceName`\n\nThe name of the Amazon Elastic Kubernetes Service cluster and namespace this resource belongs to.\n\n> This parameter accepts values in \"eks-cluster/namespace\" format. \n\nType: String\n- *`type`*\n\nThe type of resource.\n\nType: string\n- *`name`*\n\nThe name of the resource.\n\nType: String\n- `additionalInfo`\n\nAdditional configuration parameters for an AWS Resilience Hub application. If you want to implement `additionalInfo` through the AWS Resilience Hub console rather than using an API call, see [Configure the application configuration parameters](https://docs.aws.amazon.com//resilience-hub/latest/userguide/app-config-param.html) .\n\n> Currently, this parameter accepts a key-value mapping (in a string format) of only one failover region and one associated account.\n> \n> Key: `\"failover-regions\"`\n> \n> Value: `\"[{\"region\":\"\", \"accounts\":[{\"id\":\"\"}]}]\"`\n- *`appComponents`*\n\nThe list of Application Components (AppComponent) that this resource belongs to. If an AppComponent is not part of the AWS Resilience Hub application, it will be added.\n\nType: Array\n\nEach `appComponents` array item includes the following fields:\n\n- `name`\n\nThe name of the AppComponent.\n\nType: String\n- `type`\n\nThe type of AppComponent. For more information about the types of AppComponent, see [Grouping resources in an AppComponent](https://docs.aws.amazon.com/resilience-hub/latest/userguide/AppComponent.grouping.html) .\n\nType: String\n- `resourceNames`\n\nThe list of included resources that are assigned to the AppComponent.\n\nType: Array of strings\n- `additionalInfo`\n\nAdditional configuration parameters for an AWS Resilience Hub application. If you want to implement `additionalInfo` through the AWS Resilience Hub console rather than using an API call, see [Configure the application configuration parameters](https://docs.aws.amazon.com//resilience-hub/latest/userguide/app-config-param.html) .\n\n> Currently, this parameter accepts a key-value mapping (in a string format) of only one failover region and one associated account.\n> \n> Key: `\"failover-regions\"`\n> \n> Value: `\"[{\"region\":\"\", \"accounts\":[{\"id\":\"\"}]}]\"`\n- *`excludedResources`*\n\nThe list of logical resource identifiers to be excluded from the application.\n\nType: Array\n\n> Don't add the resources that you want to include. \n\nEach `excludedResources` array item includes the following fields:\n\n- *`logicalResourceIds`*\n\nThe logical identifier of the resource.\n\nType: Object\n\n> You can configure only one of the following fields:\n> \n> - `logicalStackName`\n> - `resourceGroupName`\n> - `terraformSourceName`\n> - `eksSourceName` \n\nEach `logicalResourceIds` object includes the following fields:\n\n- `identifier`\n\nThe identifier of the resource.\n\nType: String\n- `logicalStackName`\n\nThe name of the AWS CloudFormation stack this resource belongs to.\n\nType: String\n- `resourceGroupName`\n\nThe name of the resource group this resource belongs to.\n\nType: String\n- `terraformSourceName`\n\nThe name of the Terraform S3 state file this resource belongs to.\n\nType: String\n- `eksSourceName`\n\nThe name of the Amazon Elastic Kubernetes Service cluster and namespace this resource belongs to.\n\n> This parameter accepts values in \"eks-cluster/namespace\" format. \n\nType: String\n- *`version`*\n\nThe AWS Resilience Hub application version.\n- `additionalInfo`\n\nAdditional configuration parameters for an AWS Resilience Hub application. If you want to implement `additionalInfo` through the AWS Resilience Hub console rather than using an API call, see [Configure the application configuration parameters](https://docs.aws.amazon.com//resilience-hub/latest/userguide/app-config-param.html) .\n\n> Currently, this parameter accepts a key-value mapping (in a string format) of only one failover region and one associated account.\n> \n> Key: `\"failover-regions\"`\n> \n> Value: `\"[{\"region\":\"\", \"accounts\":[{\"id\":\"\"}]}]\"`", @@ -64067,7 +64067,7 @@ "PositionalConstraint": "The area within the portion of the web request that you want AWS WAF to search for `SearchString` . Valid values include the following:\n\n*CONTAINS*\n\nThe specified part of the web request must include the value of `SearchString` , but the location doesn't matter.\n\n*CONTAINS_WORD*\n\nThe specified part of the web request must include the value of `SearchString` , and `SearchString` must contain only alphanumeric characters or underscore (A-Z, a-z, 0-9, or _). In addition, `SearchString` must be a word, which means that both of the following are true:\n\n- `SearchString` is at the beginning of the specified part of the web request or is preceded by a character other than an alphanumeric character or underscore (_). Examples include the value of a header and `;BadBot` .\n- `SearchString` is at the end of the specified part of the web request or is followed by a character other than an alphanumeric character or underscore (_), for example, `BadBot;` and `-BadBot;` .\n\n*EXACTLY*\n\nThe value of the specified part of the web request must exactly match the value of `SearchString` .\n\n*STARTS_WITH*\n\nThe value of `SearchString` must appear at the beginning of the specified part of the web request.\n\n*ENDS_WITH*\n\nThe value of `SearchString` must appear at the end of the specified part of the web request.", "SearchString": "A string value that you want AWS WAF to search for. AWS WAF searches only in the part of web requests that you designate for inspection in `FieldToMatch` . The maximum length of the value is 200 bytes. For alphabetic characters A-Z and a-z, the value is case sensitive.\n\nDon't encode this string. Provide the value that you want AWS WAF to search for. AWS CloudFormation automatically base64 encodes the value for you.\n\nFor example, suppose the value of `Type` is `HEADER` and the value of `Data` is `User-Agent` . If you want to search the `User-Agent` header for the value `BadBot` , you provide the string `BadBot` in the value of `SearchString` .\n\nYou must specify either `SearchString` or `SearchStringBase64` in a `ByteMatchStatement` .", "SearchStringBase64": "String to search for in a web request component, base64-encoded. If you don't want to encode the string, specify the unencoded value in `SearchString` instead.\n\nYou must specify either `SearchString` or `SearchStringBase64` in a `ByteMatchStatement` .", - "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. If you specify one or more transformations in a rule statement, AWS WAF performs all transformations on the content of the request component identified by `FieldToMatch` , starting from the lowest priority setting, before inspecting the content for a match." + "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. Text transformations are used in rule match statements, to transform the `FieldToMatch` request component before inspecting it, and they're used in rate-based rule statements, to transform request components before using them as custom aggregation keys. If you specify one or more transformations to apply, AWS WAF performs all transformations on the specified content, starting from the lowest priority setting, and then uses the component contents." } }, "AWS::WAFv2::RuleGroup.CaptchaAction": { @@ -64285,11 +64285,11 @@ }, "AWS::WAFv2::RuleGroup.RateBasedStatement": { "attributes": {}, - "description": "A rate-based rule tracks the rate of requests for each originating IP address, and triggers the rule action when the rate exceeds a limit that you specify on the number of requests in any 5-minute time span. You can use this to put a temporary block on requests from an IP address that is sending excessive requests.\n\nAWS WAF tracks and manages web requests separately for each instance of a rate-based rule that you use. For example, if you provide the same rate-based rule settings in two web ACLs, each of the two rule statements represents a separate instance of the rate-based rule and gets its own tracking and management by AWS WAF . If you define a rate-based rule inside a rule group, and then use that rule group in multiple places, each use creates a separate instance of the rate-based rule that gets its own tracking and management by AWS WAF .\n\nWhen the rule action triggers, AWS WAF blocks additional requests from the IP address until the request rate falls below the limit.\n\nYou can optionally nest another statement inside the rate-based statement, to narrow the scope of the rule so that it only counts requests that match the nested statement. For example, based on recent requests that you have seen from an attacker, you might create a rate-based rule with a nested AND rule statement that contains the following nested statements:\n\n- An IP match statement with an IP set that specifies the address 192.0.2.44.\n- A string match statement that searches in the User-Agent header for the string BadBot.\n\nIn this rate-based rule, you also define a rate limit. For this example, the rate limit is 1,000. Requests that meet the criteria of both of the nested statements are counted. If the count exceeds 1,000 requests per five minutes, the rule action triggers. Requests that do not meet the criteria of both of the nested statements are not counted towards the rate limit and are not affected by this rule.\n\nYou cannot nest a `RateBasedStatement` inside another statement, for example inside a `NotStatement` or `OrStatement` . You can define a `RateBasedStatement` inside a web ACL and inside a rule group.", + "description": "A rate-based rule counts incoming requests and rate limits requests when they are coming at too fast a rate. The rule categorizes requests according to your aggregation criteria, collects them into aggregation instances, and counts and rate limits the requests for each instance.\n\nYou can specify individual aggregation keys, like IP address or HTTP method. You can also specify aggregation key combinations, like IP address and HTTP method, or HTTP method, query argument, and cookie.\n\nEach unique set of values for the aggregation keys that you specify is a separate aggregation instance, with the value from each key contributing to the aggregation instance definition.\n\nFor example, assume the rule evaluates web requests with the following IP address and HTTP method values:\n\n- IP address 10.1.1.1, HTTP method POST\n- IP address 10.1.1.1, HTTP method GET\n- IP address 127.0.0.0, HTTP method POST\n- IP address 10.1.1.1, HTTP method GET\n\nThe rule would create different aggregation instances according to your aggregation criteria, for example:\n\n- If the aggregation criteria is just the IP address, then each individual address is an aggregation instance, and AWS WAF counts requests separately for each. The aggregation instances and request counts for our example would be the following:\n\n- IP address 10.1.1.1: count 3\n- IP address 127.0.0.0: count 1\n- If the aggregation criteria is HTTP method, then each individual HTTP method is an aggregation instance. The aggregation instances and request counts for our example would be the following:\n\n- HTTP method POST: count 2\n- HTTP method GET: count 2\n- If the aggregation criteria is IP address and HTTP method, then each IP address and each HTTP method would contribute to the combined aggregation instance. The aggregation instances and request counts for our example would be the following:\n\n- IP address 10.1.1.1, HTTP method POST: count 1\n- IP address 10.1.1.1, HTTP method GET: count 2\n- IP address 127.0.0.0, HTTP method POST: count 1\n\nFor any n-tuple of aggregation keys, each unique combination of values for the keys defines a separate aggregation instance, which AWS WAF counts and rate-limits individually.\n\nYou can optionally nest another statement inside the rate-based statement, to narrow the scope of the rule so that it only counts and rate limits requests that match the nested statement. You can use this nested scope-down statement in conjunction with your aggregation key specifications or you can just count and rate limit all requests that match the scope-down statement, without additional aggregation. When you choose to just manage all requests that match a scope-down statement, the aggregation instance is singular for the rule.\n\nYou cannot nest a `RateBasedStatement` inside another statement, for example inside a `NotStatement` or `OrStatement` . You can define a `RateBasedStatement` inside a web ACL and inside a rule group.\n\nFor additional information about the options, see [Rate limiting web requests using rate-based rules](https://docs.aws.amazon.com/waf/latest/developerguide/waf-rate-based-rules.html) in the *AWS WAF Developer Guide* .\n\nIf you only aggregate on the individual IP address or forwarded IP address, you can retrieve the list of IP addresses that AWS WAF is currently rate limiting for a rule through the API call `GetRateBasedStatementManagedKeys` . This option is not available for other aggregation configurations.\n\nAWS WAF tracks and manages web requests separately for each instance of a rate-based rule that you use. For example, if you provide the same rate-based rule settings in two web ACLs, each of the two rule statements represents a separate instance of the rate-based rule and gets its own tracking and management by AWS WAF . If you define a rate-based rule inside a rule group, and then use that rule group in multiple places, each use creates a separate instance of the rate-based rule that gets its own tracking and management by AWS WAF .", "properties": { - "AggregateKeyType": "Setting that indicates how to aggregate the request counts. The options are the following:\n\n- IP - Aggregate the request counts on the IP address from the web request origin.\n- FORWARDED_IP - Aggregate the request counts on the first IP address in an HTTP header. If you use this, configure the `ForwardedIPConfig` , to specify the header to use.", - "ForwardedIPConfig": "The configuration for inspecting IP addresses in an HTTP header that you specify, instead of using the IP address that's reported by the web request origin. Commonly, this is the X-Forwarded-For (XFF) header, but you can specify any header name.\n\n> If the specified header isn't present in the request, AWS WAF doesn't apply the rule to the web request at all. \n\nThis is required if `AggregateKeyType` is set to `FORWARDED_IP` .", - "Limit": "The limit on requests per 5-minute period for a single originating IP address. If the statement includes a `ScopeDownStatement` , this limit is applied only to the requests that match the statement.", + "AggregateKeyType": "Setting that indicates how to aggregate the request counts.\n\n> Web requests that are missing any of the components specified in the aggregation keys are omitted from the rate-based rule evaluation and handling. \n\n- `CONSTANT` - Count and limit the requests that match the rate-based rule's scope-down statement. With this option, the counted requests aren't further aggregated. The scope-down statement is the only specification used. When the count of all requests that satisfy the scope-down statement goes over the limit, AWS WAF applies the rule action to all requests that satisfy the scope-down statement.\n\nWith this option, you must configure the `ScopeDownStatement` property.\n- `CUSTOM_KEYS` - Aggregate the request counts using one or more web request components as the aggregate keys.\n\nWith this option, you must specify the aggregate keys in the `CustomKeys` property.\n\nTo aggregate on only the IP address or only the forwarded IP address, don't use custom keys. Instead, set the aggregate key type to `IP` or `FORWARDED_IP` .\n- `FORWARDED_IP` - Aggregate the request counts on the first IP address in an HTTP header.\n\nWith this option, you must specify the header to use in the `ForwardedIPConfig` property.\n\nTo aggregate on a combination of the forwarded IP address with other aggregate keys, use `CUSTOM_KEYS` .\n- `IP` - Aggregate the request counts on the IP address from the web request origin.\n\nTo aggregate on a combination of the IP address with other aggregate keys, use `CUSTOM_KEYS` .", + "ForwardedIPConfig": "The configuration for inspecting IP addresses in an HTTP header that you specify, instead of using the IP address that's reported by the web request origin. Commonly, this is the X-Forwarded-For (XFF) header, but you can specify any header name.\n\n> If the specified header isn't present in the request, AWS WAF doesn't apply the rule to the web request at all. \n\nThis is required if you specify a forwarded IP in the rule's aggregate key settings.", + "Limit": "The limit on requests per 5-minute period for a single aggregation instance for the rate-based rule. If the rate-based statement includes a `ScopeDownStatement` , this limit is applied only to the requests that match the statement.\n\nExamples:\n\n- If you aggregate on just the IP address, this is the limit on requests from any single IP address.\n- If you aggregate on the HTTP method and the query argument name \"city\", then this is the limit on requests for any single method, city pair.", "ScopeDownStatement": "An optional nested statement that narrows the scope of the web requests that are evaluated by the rate-based statement. Requests are only tracked by the rate-based statement if they match the scope-down statement. You can use any nestable statement in the scope-down statement, and you can nest statements at any level, the same as you can for a rule statement." } }, @@ -64299,7 +64299,7 @@ "properties": { "FieldToMatch": "The part of the web request that you want AWS WAF to inspect.", "RegexString": "The string representing the regular expression.", - "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. If you specify one or more transformations in a rule statement, AWS WAF performs all transformations on the content of the request component identified by `FieldToMatch` , starting from the lowest priority setting, before inspecting the content for a match." + "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. Text transformations are used in rule match statements, to transform the `FieldToMatch` request component before inspecting it, and they're used in rate-based rule statements, to transform request components before using them as custom aggregation keys. If you specify one or more transformations to apply, AWS WAF performs all transformations on the specified content, starting from the lowest priority setting, and then uses the component contents." } }, "AWS::WAFv2::RuleGroup.RegexPatternSetReferenceStatement": { @@ -64308,7 +64308,7 @@ "properties": { "Arn": "The Amazon Resource Name (ARN) of the `RegexPatternSet` that this statement references.", "FieldToMatch": "The part of the web request that you want AWS WAF to inspect.", - "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. If you specify one or more transformations in a rule statement, AWS WAF performs all transformations on the content of the request component identified by `FieldToMatch` , starting from the lowest priority setting, before inspecting the content for a match." + "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. Text transformations are used in rule match statements, to transform the `FieldToMatch` request component before inspecting it, and they're used in rate-based rule statements, to transform request components before using them as custom aggregation keys. If you specify one or more transformations to apply, AWS WAF performs all transformations on the specified content, starting from the lowest priority setting, and then uses the component contents." } }, "AWS::WAFv2::RuleGroup.Rule": { @@ -64357,7 +64357,7 @@ "ComparisonOperator": "The operator to use to compare the request part to the size setting.", "FieldToMatch": "The part of the web request that you want AWS WAF to inspect.", "Size": "The size, in byte, to compare to the request part, after any transformations.", - "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. If you specify one or more transformations in a rule statement, AWS WAF performs all transformations on the content of the request component identified by `FieldToMatch` , starting from the lowest priority setting, before inspecting the content for a match." + "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. Text transformations are used in rule match statements, to transform the `FieldToMatch` request component before inspecting it, and they're used in rate-based rule statements, to transform request components before using them as custom aggregation keys. If you specify one or more transformations to apply, AWS WAF performs all transformations on the specified content, starting from the lowest priority setting, and then uses the component contents." } }, "AWS::WAFv2::RuleGroup.SqliMatchStatement": { @@ -64366,7 +64366,7 @@ "properties": { "FieldToMatch": "The part of the web request that you want AWS WAF to inspect.", "SensitivityLevel": "The sensitivity that you want AWS WAF to use to inspect for SQL injection attacks.\n\n`HIGH` detects more attacks, but might generate more false positives, especially if your web requests frequently contain unusual strings. For information about identifying and mitigating false positives, see [Testing and tuning](https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-testing.html) in the *AWS WAF Developer Guide* .\n\n`LOW` is generally a better choice for resources that already have other protections against SQL injection attacks or that have a low tolerance for false positives.\n\nDefault: `LOW`", - "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. If you specify one or more transformations in a rule statement, AWS WAF performs all transformations on the content of the request component identified by `FieldToMatch` , starting from the lowest priority setting, before inspecting the content for a match." + "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. Text transformations are used in rule match statements, to transform the `FieldToMatch` request component before inspecting it, and they're used in rate-based rule statements, to transform request components before using them as custom aggregation keys. If you specify one or more transformations to apply, AWS WAF performs all transformations on the specified content, starting from the lowest priority setting, and then uses the component contents." } }, "AWS::WAFv2::RuleGroup.Statement": { @@ -64380,7 +64380,7 @@ "LabelMatchStatement": "A rule statement to match against labels that have been added to the web request by rules that have already run in the web ACL.\n\nThe label match statement provides the label or namespace string to search for. The label string can represent a part or all of the fully qualified label name that had been added to the web request. Fully qualified labels have a prefix, optional namespaces, and label name. The prefix identifies the rule group or web ACL context of the rule that added the label. If you do not provide the fully qualified name in your label match string, AWS WAF performs the search for labels that were added in the same context as the label match statement.", "NotStatement": "A logical rule statement used to negate the results of another rule statement. You provide one `Statement` within the `NotStatement` .", "OrStatement": "A logical rule statement used to combine other rule statements with OR logic. You provide more than one `Statement` within the `OrStatement` .", - "RateBasedStatement": "A rate-based rule tracks the rate of requests for each originating IP address, and triggers the rule action when the rate exceeds a limit that you specify on the number of requests in any 5-minute time span. You can use this to put a temporary block on requests from an IP address that is sending excessive requests.\n\nAWS WAF tracks and manages web requests separately for each instance of a rate-based rule that you use. For example, if you provide the same rate-based rule settings in two web ACLs, each of the two rule statements represents a separate instance of the rate-based rule and gets its own tracking and management by AWS WAF . If you define a rate-based rule inside a rule group, and then use that rule group in multiple places, each use creates a separate instance of the rate-based rule that gets its own tracking and management by AWS WAF .\n\nWhen the rule action triggers, AWS WAF blocks additional requests from the IP address until the request rate falls below the limit.\n\nYou can optionally nest another statement inside the rate-based statement, to narrow the scope of the rule so that it only counts requests that match the nested statement. For example, based on recent requests that you have seen from an attacker, you might create a rate-based rule with a nested AND rule statement that contains the following nested statements:\n\n- An IP match statement with an IP set that specifies the address 192.0.2.44.\n- A string match statement that searches in the User-Agent header for the string BadBot.\n\nIn this rate-based rule, you also define a rate limit. For this example, the rate limit is 1,000. Requests that meet the criteria of both of the nested statements are counted. If the count exceeds 1,000 requests per five minutes, the rule action triggers. Requests that do not meet the criteria of both of the nested statements are not counted towards the rate limit and are not affected by this rule.\n\nYou cannot nest a `RateBasedStatement` inside another statement, for example inside a `NotStatement` or `OrStatement` . You can define a `RateBasedStatement` inside a web ACL and inside a rule group.", + "RateBasedStatement": "A rate-based rule counts incoming requests and rate limits requests when they are coming at too fast a rate. The rule categorizes requests according to your aggregation criteria, collects them into aggregation instances, and counts and rate limits the requests for each instance.\n\nYou can specify individual aggregation keys, like IP address or HTTP method. You can also specify aggregation key combinations, like IP address and HTTP method, or HTTP method, query argument, and cookie.\n\nEach unique set of values for the aggregation keys that you specify is a separate aggregation instance, with the value from each key contributing to the aggregation instance definition.\n\nFor example, assume the rule evaluates web requests with the following IP address and HTTP method values:\n\n- IP address 10.1.1.1, HTTP method POST\n- IP address 10.1.1.1, HTTP method GET\n- IP address 127.0.0.0, HTTP method POST\n- IP address 10.1.1.1, HTTP method GET\n\nThe rule would create different aggregation instances according to your aggregation criteria, for example:\n\n- If the aggregation criteria is just the IP address, then each individual address is an aggregation instance, and AWS WAF counts requests separately for each. The aggregation instances and request counts for our example would be the following:\n\n- IP address 10.1.1.1: count 3\n- IP address 127.0.0.0: count 1\n- If the aggregation criteria is HTTP method, then each individual HTTP method is an aggregation instance. The aggregation instances and request counts for our example would be the following:\n\n- HTTP method POST: count 2\n- HTTP method GET: count 2\n- If the aggregation criteria is IP address and HTTP method, then each IP address and each HTTP method would contribute to the combined aggregation instance. The aggregation instances and request counts for our example would be the following:\n\n- IP address 10.1.1.1, HTTP method POST: count 1\n- IP address 10.1.1.1, HTTP method GET: count 2\n- IP address 127.0.0.0, HTTP method POST: count 1\n\nFor any n-tuple of aggregation keys, each unique combination of values for the keys defines a separate aggregation instance, which AWS WAF counts and rate-limits individually.\n\nYou can optionally nest another statement inside the rate-based statement, to narrow the scope of the rule so that it only counts and rate limits requests that match the nested statement. You can use this nested scope-down statement in conjunction with your aggregation key specifications or you can just count and rate limit all requests that match the scope-down statement, without additional aggregation. When you choose to just manage all requests that match a scope-down statement, the aggregation instance is singular for the rule.\n\nYou cannot nest a `RateBasedStatement` inside another statement, for example inside a `NotStatement` or `OrStatement` . You can define a `RateBasedStatement` inside a web ACL and inside a rule group.\n\nFor additional information about the options, see [Rate limiting web requests using rate-based rules](https://docs.aws.amazon.com/waf/latest/developerguide/waf-rate-based-rules.html) in the *AWS WAF Developer Guide* .\n\nIf you only aggregate on the individual IP address or forwarded IP address, you can retrieve the list of IP addresses that AWS WAF is currently rate limiting for a rule through the API call `GetRateBasedStatementManagedKeys` . This option is not available for other aggregation configurations.\n\nAWS WAF tracks and manages web requests separately for each instance of a rate-based rule that you use. For example, if you provide the same rate-based rule settings in two web ACLs, each of the two rule statements represents a separate instance of the rate-based rule and gets its own tracking and management by AWS WAF . If you define a rate-based rule inside a rule group, and then use that rule group in multiple places, each use creates a separate instance of the rate-based rule that gets its own tracking and management by AWS WAF .", "RegexMatchStatement": "A rule statement used to search web request components for a match against a single regular expression.", "RegexPatternSetReferenceStatement": "A rule statement used to search web request components for matches with regular expressions. To use this, create a `RegexPatternSet` that specifies the expressions that you want to detect, then use the ARN of that set in this statement. A web request matches the pattern set rule statement if the request component matches any of the patterns in the set.\n\nEach regex pattern set rule statement references a regex pattern set. You create and maintain the set independent of your rules. This allows you to use the single set in multiple rules. When you update the referenced set, AWS WAF automatically updates all rules that reference it.", "SizeConstraintStatement": "A rule statement that compares a number of bytes against the size of a request component, using a comparison operator, such as greater than (>) or less than (<). For example, you can use a size constraint statement to look for query strings that are longer than 100 bytes.\n\nIf you configure AWS WAF to inspect the request body, AWS WAF inspects only the number of bytes of the body up to the limit for the web ACL. By default, for regional web ACLs, this limit is 8 KB (8,192 kilobytes) and for CloudFront web ACLs, this limit is 16 KB (16,384 kilobytes). For CloudFront web ACLs, you can increase the limit in the web ACL `AssociationConfig` , for additional fees. If you know that the request body for your web requests should never exceed the inspection limit, you could use a size constraint statement to block requests that have a larger request body size.\n\nIf you choose URI for the value of Part of the request to filter on, the slash (/) in the URI counts as one character. For example, the URI `/logo.jpg` is nine characters long.", @@ -64392,7 +64392,7 @@ "attributes": {}, "description": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection.", "properties": { - "Priority": "Sets the relative processing order for multiple transformations that are defined for a rule statement. AWS WAF processes all transformations, from lowest priority to highest, before inspecting the transformed content. The priorities don't need to be consecutive, but they must all be different.", + "Priority": "Sets the relative processing order for multiple transformations. AWS WAF processes all transformations, from lowest priority to highest, before inspecting the transformed content. The priorities don't need to be consecutive, but they must all be different.", "Type": "You can specify the following transformation types:\n\n*BASE64_DECODE* - Decode a `Base64` -encoded string.\n\n*BASE64_DECODE_EXT* - Decode a `Base64` -encoded string, but use a forgiving implementation that ignores characters that aren't valid.\n\n*CMD_LINE* - Command-line transformations. These are helpful in reducing effectiveness of attackers who inject an operating system command-line command and use unusual formatting to disguise some or all of the command.\n\n- Delete the following characters: `\\ \" ' ^`\n- Delete spaces before the following characters: `/ (`\n- Replace the following characters with a space: `, ;`\n- Replace multiple spaces with one space\n- Convert uppercase letters (A-Z) to lowercase (a-z)\n\n*COMPRESS_WHITE_SPACE* - Replace these characters with a space character (decimal 32):\n\n- `\\f` , formfeed, decimal 12\n- `\\t` , tab, decimal 9\n- `\\n` , newline, decimal 10\n- `\\r` , carriage return, decimal 13\n- `\\v` , vertical tab, decimal 11\n- Non-breaking space, decimal 160\n\n`COMPRESS_WHITE_SPACE` also replaces multiple spaces with one space.\n\n*CSS_DECODE* - Decode characters that were encoded using CSS 2.x escape rules `syndata.html#characters` . This function uses up to two bytes in the decoding process, so it can help to uncover ASCII characters that were encoded using CSS encoding that wouldn\u2019t typically be encoded. It's also useful in countering evasion, which is a combination of a backslash and non-hexadecimal characters. For example, `ja\\vascript` for javascript.\n\n*ESCAPE_SEQ_DECODE* - Decode the following ANSI C escape sequences: `\\a` , `\\b` , `\\f` , `\\n` , `\\r` , `\\t` , `\\v` , `\\\\` , `\\?` , `\\'` , `\\\"` , `\\xHH` (hexadecimal), `\\0OOO` (octal). Encodings that aren't valid remain in the output.\n\n*HEX_DECODE* - Decode a string of hexadecimal characters into a binary.\n\n*HTML_ENTITY_DECODE* - Replace HTML-encoded characters with unencoded characters. `HTML_ENTITY_DECODE` performs these operations:\n\n- Replaces `(ampersand)quot;` with `\"`\n- Replaces `(ampersand)nbsp;` with a non-breaking space, decimal 160\n- Replaces `(ampersand)lt;` with a \"less than\" symbol\n- Replaces `(ampersand)gt;` with `>`\n- Replaces characters that are represented in hexadecimal format, `(ampersand)#xhhhh;` , with the corresponding characters\n- Replaces characters that are represented in decimal format, `(ampersand)#nnnn;` , with the corresponding characters\n\n*JS_DECODE* - Decode JavaScript escape sequences. If a `\\` `u` `HHHH` code is in the full-width ASCII code range of `FF01-FF5E` , then the higher byte is used to detect and adjust the lower byte. If not, only the lower byte is used and the higher byte is zeroed, causing a possible loss of information.\n\n*LOWERCASE* - Convert uppercase letters (A-Z) to lowercase (a-z).\n\n*MD5* - Calculate an MD5 hash from the data in the input. The computed hash is in a raw binary form.\n\n*NONE* - Specify `NONE` if you don't want any text transformations.\n\n*NORMALIZE_PATH* - Remove multiple slashes, directory self-references, and directory back-references that are not at the beginning of the input from an input string.\n\n*NORMALIZE_PATH_WIN* - This is the same as `NORMALIZE_PATH` , but first converts backslash characters to forward slashes.\n\n*REMOVE_NULLS* - Remove all `NULL` bytes from the input.\n\n*REPLACE_COMMENTS* - Replace each occurrence of a C-style comment ( `/* ... */` ) with a single space. Multiple consecutive occurrences are not compressed. Unterminated comments are also replaced with a space (ASCII 0x20). However, a standalone termination of a comment ( `*/` ) is not acted upon.\n\n*REPLACE_NULLS* - Replace NULL bytes in the input with space characters (ASCII `0x20` ).\n\n*SQL_HEX_DECODE* - Decode SQL hex data. Example ( `0x414243` ) will be decoded to ( `ABC` ).\n\n*URL_DECODE* - Decode a URL-encoded value.\n\n*URL_DECODE_UNI* - Like `URL_DECODE` , but with support for Microsoft-specific `%u` encoding. If the code is in the full-width ASCII code range of `FF01-FF5E` , the higher byte is used to detect and adjust the lower byte. Otherwise, only the lower byte is used and the higher byte is zeroed.\n\n*UTF8_TO_UNICODE* - Convert all UTF-8 character sequences to Unicode. This helps input normalization, and minimizing false-positives and false-negatives for non-English languages." } }, @@ -64400,7 +64400,7 @@ "attributes": {}, "description": "Defines and enables Amazon CloudWatch metrics and web request sample collection.", "properties": { - "CloudWatchMetricsEnabled": "A boolean indicating whether the associated resource sends metrics to Amazon CloudWatch. For the list of available metrics, see [AWS WAF Metrics](https://docs.aws.amazon.com/waf/latest/developerguide/monitoring-cloudwatch.html#waf-metrics) in the *AWS WAF Developer Guide* .", + "CloudWatchMetricsEnabled": "A boolean indicating whether the associated resource sends metrics to Amazon CloudWatch. For the list of available metrics, see [AWS WAF Metrics](https://docs.aws.amazon.com/waf/latest/developerguide/monitoring-cloudwatch.html#waf-metrics) in the *AWS WAF Developer Guide* .\n\nFor web ACLs, the metrics are for web requests that have the web ACL default action applied. AWS WAF applies the default action to web requests that pass the inspection of all rules in the web ACL without being either allowed or blocked. For more information,\nsee [The web ACL default action](https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-default-action.html) in the *AWS WAF Developer Guide* .", "MetricName": "A name of the Amazon CloudWatch metric dimension. The name can contain only the characters: A-Z, a-z, 0-9, - (hyphen), and _ (underscore). The name can be from one to 128 characters long. It can't contain whitespace or metric names that are reserved for AWS WAF , for example `All` and `Default_Action` .", "SampledRequestsEnabled": "A boolean indicating whether AWS WAF should store a sampling of the web requests that match the rules. You can view the sampled requests through the AWS WAF console." } @@ -64410,7 +64410,7 @@ "description": "A rule statement that inspects for cross-site scripting (XSS) attacks. In XSS attacks, the attacker uses vulnerabilities in a benign website as a vehicle to inject malicious client-site scripts into other legitimate web browsers.", "properties": { "FieldToMatch": "The part of the web request that you want AWS WAF to inspect.", - "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. If you specify one or more transformations in a rule statement, AWS WAF performs all transformations on the content of the request component identified by `FieldToMatch` , starting from the lowest priority setting, before inspecting the content for a match." + "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. Text transformations are used in rule match statements, to transform the `FieldToMatch` request component before inspecting it, and they're used in rate-based rule statements, to transform request components before using them as custom aggregation keys. If you specify one or more transformations to apply, AWS WAF performs all transformations on the specified content, starting from the lowest priority setting, and then uses the component contents." } }, "AWS::WAFv2::WebACL": { @@ -64488,7 +64488,7 @@ "PositionalConstraint": "The area within the portion of the web request that you want AWS WAF to search for `SearchString` . Valid values include the following:\n\n*CONTAINS*\n\nThe specified part of the web request must include the value of `SearchString` , but the location doesn't matter.\n\n*CONTAINS_WORD*\n\nThe specified part of the web request must include the value of `SearchString` , and `SearchString` must contain only alphanumeric characters or underscore (A-Z, a-z, 0-9, or _). In addition, `SearchString` must be a word, which means that both of the following are true:\n\n- `SearchString` is at the beginning of the specified part of the web request or is preceded by a character other than an alphanumeric character or underscore (_). Examples include the value of a header and `;BadBot` .\n- `SearchString` is at the end of the specified part of the web request or is followed by a character other than an alphanumeric character or underscore (_), for example, `BadBot;` and `-BadBot;` .\n\n*EXACTLY*\n\nThe value of the specified part of the web request must exactly match the value of `SearchString` .\n\n*STARTS_WITH*\n\nThe value of `SearchString` must appear at the beginning of the specified part of the web request.\n\n*ENDS_WITH*\n\nThe value of `SearchString` must appear at the end of the specified part of the web request.", "SearchString": "A string value that you want AWS WAF to search for. AWS WAF searches only in the part of web requests that you designate for inspection in `FieldToMatch` . The maximum length of the value is 200 bytes. For alphabetic characters A-Z and a-z, the value is case sensitive.\n\nDon't encode this string. Provide the value that you want AWS WAF to search for. AWS CloudFormation automatically base64 encodes the value for you.\n\nFor example, suppose the value of `Type` is `HEADER` and the value of `Data` is `User-Agent` . If you want to search the `User-Agent` header for the value `BadBot` , you provide the string `BadBot` in the value of `SearchString` .\n\nYou must specify either `SearchString` or `SearchStringBase64` in a `ByteMatchStatement` .", "SearchStringBase64": "String to search for in a web request component, base64-encoded. If you don't want to encode the string, specify the unencoded value in `SearchString` instead.\n\nYou must specify either `SearchString` or `SearchStringBase64` in a `ByteMatchStatement` .", - "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. If you specify one or more transformations in a rule statement, AWS WAF performs all transformations on the content of the request component identified by `FieldToMatch` , starting from the lowest priority setting, before inspecting the content for a match." + "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. Text transformations are used in rule match statements, to transform the `FieldToMatch` request component before inspecting it, and they're used in rate-based rule statements, to transform request components before using them as custom aggregation keys. If you specify one or more transformations to apply, AWS WAF performs all transformations on the specified content, starting from the lowest priority setting, and then uses the component contents." } }, "AWS::WAFv2::WebACL.CaptchaAction": { @@ -64754,11 +64754,11 @@ }, "AWS::WAFv2::WebACL.RateBasedStatement": { "attributes": {}, - "description": "A rate-based rule tracks the rate of requests for each originating IP address, and triggers the rule action when the rate exceeds a limit that you specify on the number of requests in any 5-minute time span. You can use this to put a temporary block on requests from an IP address that is sending excessive requests.\n\nAWS WAF tracks and manages web requests separately for each instance of a rate-based rule that you use. For example, if you provide the same rate-based rule settings in two web ACLs, each of the two rule statements represents a separate instance of the rate-based rule and gets its own tracking and management by AWS WAF . If you define a rate-based rule inside a rule group, and then use that rule group in multiple places, each use creates a separate instance of the rate-based rule that gets its own tracking and management by AWS WAF .\n\nWhen the rule action triggers, AWS WAF blocks additional requests from the IP address until the request rate falls below the limit.\n\nYou can optionally nest another statement inside the rate-based statement, to narrow the scope of the rule so that it only counts requests that match the nested statement. For example, based on recent requests that you have seen from an attacker, you might create a rate-based rule with a nested AND rule statement that contains the following nested statements:\n\n- An IP match statement with an IP set that specifies the address 192.0.2.44.\n- A string match statement that searches in the User-Agent header for the string BadBot.\n\nIn this rate-based rule, you also define a rate limit. For this example, the rate limit is 1,000. Requests that meet the criteria of both of the nested statements are counted. If the count exceeds 1,000 requests per five minutes, the rule action triggers. Requests that do not meet the criteria of both of the nested statements are not counted towards the rate limit and are not affected by this rule.\n\nYou cannot nest a `RateBasedStatement` inside another statement, for example inside a `NotStatement` or `OrStatement` . You can define a `RateBasedStatement` inside a web ACL and inside a rule group.", + "description": "A rate-based rule counts incoming requests and rate limits requests when they are coming at too fast a rate. The rule categorizes requests according to your aggregation criteria, collects them into aggregation instances, and counts and rate limits the requests for each instance.\n\nYou can specify individual aggregation keys, like IP address or HTTP method. You can also specify aggregation key combinations, like IP address and HTTP method, or HTTP method, query argument, and cookie.\n\nEach unique set of values for the aggregation keys that you specify is a separate aggregation instance, with the value from each key contributing to the aggregation instance definition.\n\nFor example, assume the rule evaluates web requests with the following IP address and HTTP method values:\n\n- IP address 10.1.1.1, HTTP method POST\n- IP address 10.1.1.1, HTTP method GET\n- IP address 127.0.0.0, HTTP method POST\n- IP address 10.1.1.1, HTTP method GET\n\nThe rule would create different aggregation instances according to your aggregation criteria, for example:\n\n- If the aggregation criteria is just the IP address, then each individual address is an aggregation instance, and AWS WAF counts requests separately for each. The aggregation instances and request counts for our example would be the following:\n\n- IP address 10.1.1.1: count 3\n- IP address 127.0.0.0: count 1\n- If the aggregation criteria is HTTP method, then each individual HTTP method is an aggregation instance. The aggregation instances and request counts for our example would be the following:\n\n- HTTP method POST: count 2\n- HTTP method GET: count 2\n- If the aggregation criteria is IP address and HTTP method, then each IP address and each HTTP method would contribute to the combined aggregation instance. The aggregation instances and request counts for our example would be the following:\n\n- IP address 10.1.1.1, HTTP method POST: count 1\n- IP address 10.1.1.1, HTTP method GET: count 2\n- IP address 127.0.0.0, HTTP method POST: count 1\n\nFor any n-tuple of aggregation keys, each unique combination of values for the keys defines a separate aggregation instance, which AWS WAF counts and rate-limits individually.\n\nYou can optionally nest another statement inside the rate-based statement, to narrow the scope of the rule so that it only counts and rate limits requests that match the nested statement. You can use this nested scope-down statement in conjunction with your aggregation key specifications or you can just count and rate limit all requests that match the scope-down statement, without additional aggregation. When you choose to just manage all requests that match a scope-down statement, the aggregation instance is singular for the rule.\n\nYou cannot nest a `RateBasedStatement` inside another statement, for example inside a `NotStatement` or `OrStatement` . You can define a `RateBasedStatement` inside a web ACL and inside a rule group.\n\nFor additional information about the options, see [Rate limiting web requests using rate-based rules](https://docs.aws.amazon.com/waf/latest/developerguide/waf-rate-based-rules.html) in the *AWS WAF Developer Guide* .\n\nIf you only aggregate on the individual IP address or forwarded IP address, you can retrieve the list of IP addresses that AWS WAF is currently rate limiting for a rule through the API call `GetRateBasedStatementManagedKeys` . This option is not available for other aggregation configurations.\n\nAWS WAF tracks and manages web requests separately for each instance of a rate-based rule that you use. For example, if you provide the same rate-based rule settings in two web ACLs, each of the two rule statements represents a separate instance of the rate-based rule and gets its own tracking and management by AWS WAF . If you define a rate-based rule inside a rule group, and then use that rule group in multiple places, each use creates a separate instance of the rate-based rule that gets its own tracking and management by AWS WAF .", "properties": { - "AggregateKeyType": "Setting that indicates how to aggregate the request counts. The options are the following:\n\n- IP - Aggregate the request counts on the IP address from the web request origin.\n- FORWARDED_IP - Aggregate the request counts on the first IP address in an HTTP header. If you use this, configure the `ForwardedIPConfig` , to specify the header to use.", - "ForwardedIPConfig": "The configuration for inspecting IP addresses in an HTTP header that you specify, instead of using the IP address that's reported by the web request origin. Commonly, this is the X-Forwarded-For (XFF) header, but you can specify any header name.\n\n> If the specified header isn't present in the request, AWS WAF doesn't apply the rule to the web request at all. \n\nThis is required if `AggregateKeyType` is set to `FORWARDED_IP` .", - "Limit": "The limit on requests per 5-minute period for a single originating IP address. If the statement includes a `ScopeDownStatement` , this limit is applied only to the requests that match the statement.", + "AggregateKeyType": "Setting that indicates how to aggregate the request counts.\n\n> Web requests that are missing any of the components specified in the aggregation keys are omitted from the rate-based rule evaluation and handling. \n\n- `CONSTANT` - Count and limit the requests that match the rate-based rule's scope-down statement. With this option, the counted requests aren't further aggregated. The scope-down statement is the only specification used. When the count of all requests that satisfy the scope-down statement goes over the limit, AWS WAF applies the rule action to all requests that satisfy the scope-down statement.\n\nWith this option, you must configure the `ScopeDownStatement` property.\n- `CUSTOM_KEYS` - Aggregate the request counts using one or more web request components as the aggregate keys.\n\nWith this option, you must specify the aggregate keys in the `CustomKeys` property.\n\nTo aggregate on only the IP address or only the forwarded IP address, don't use custom keys. Instead, set the aggregate key type to `IP` or `FORWARDED_IP` .\n- `FORWARDED_IP` - Aggregate the request counts on the first IP address in an HTTP header.\n\nWith this option, you must specify the header to use in the `ForwardedIPConfig` property.\n\nTo aggregate on a combination of the forwarded IP address with other aggregate keys, use `CUSTOM_KEYS` .\n- `IP` - Aggregate the request counts on the IP address from the web request origin.\n\nTo aggregate on a combination of the IP address with other aggregate keys, use `CUSTOM_KEYS` .", + "ForwardedIPConfig": "The configuration for inspecting IP addresses in an HTTP header that you specify, instead of using the IP address that's reported by the web request origin. Commonly, this is the X-Forwarded-For (XFF) header, but you can specify any header name.\n\n> If the specified header isn't present in the request, AWS WAF doesn't apply the rule to the web request at all. \n\nThis is required if you specify a forwarded IP in the rule's aggregate key settings.", + "Limit": "The limit on requests per 5-minute period for a single aggregation instance for the rate-based rule. If the rate-based statement includes a `ScopeDownStatement` , this limit is applied only to the requests that match the statement.\n\nExamples:\n\n- If you aggregate on just the IP address, this is the limit on requests from any single IP address.\n- If you aggregate on the HTTP method and the query argument name \"city\", then this is the limit on requests for any single method, city pair.", "ScopeDownStatement": "An optional nested statement that narrows the scope of the web requests that are evaluated by the rate-based statement. Requests are only tracked by the rate-based statement if they match the scope-down statement. You can use any nestable `Statement` in the scope-down statement, and you can nest statements at any level, the same as you can for a rule statement." } }, @@ -64768,7 +64768,7 @@ "properties": { "FieldToMatch": "The part of the web request that you want AWS WAF to inspect.", "RegexString": "The string representing the regular expression.", - "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. If you specify one or more transformations in a rule statement, AWS WAF performs all transformations on the content of the request component identified by `FieldToMatch` , starting from the lowest priority setting, before inspecting the content for a match." + "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. Text transformations are used in rule match statements, to transform the `FieldToMatch` request component before inspecting it, and they're used in rate-based rule statements, to transform request components before using them as custom aggregation keys. If you specify one or more transformations to apply, AWS WAF performs all transformations on the specified content, starting from the lowest priority setting, and then uses the component contents." } }, "AWS::WAFv2::WebACL.RegexPatternSetReferenceStatement": { @@ -64777,7 +64777,7 @@ "properties": { "Arn": "The Amazon Resource Name (ARN) of the `RegexPatternSet` that this statement references.", "FieldToMatch": "The part of the web request that you want AWS WAF to inspect.", - "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. If you specify one or more transformations in a rule statement, AWS WAF performs all transformations on the content of the request component identified by `FieldToMatch` , starting from the lowest priority setting, before inspecting the content for a match." + "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. Text transformations are used in rule match statements, to transform the `FieldToMatch` request component before inspecting it, and they're used in rate-based rule statements, to transform request components before using them as custom aggregation keys. If you specify one or more transformations to apply, AWS WAF performs all transformations on the specified content, starting from the lowest priority setting, and then uses the component contents." } }, "AWS::WAFv2::WebACL.RequestInspection": { @@ -64897,7 +64897,7 @@ "ComparisonOperator": "The operator to use to compare the request part to the size setting.", "FieldToMatch": "The part of the web request that you want AWS WAF to inspect.", "Size": "The size, in byte, to compare to the request part, after any transformations.", - "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. If you specify one or more transformations in a rule statement, AWS WAF performs all transformations on the content of the request component identified by `FieldToMatch` , starting from the lowest priority setting, before inspecting the content for a match." + "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. Text transformations are used in rule match statements, to transform the `FieldToMatch` request component before inspecting it, and they're used in rate-based rule statements, to transform request components before using them as custom aggregation keys. If you specify one or more transformations to apply, AWS WAF performs all transformations on the specified content, starting from the lowest priority setting, and then uses the component contents." } }, "AWS::WAFv2::WebACL.SqliMatchStatement": { @@ -64906,7 +64906,7 @@ "properties": { "FieldToMatch": "The part of the web request that you want AWS WAF to inspect.", "SensitivityLevel": "The sensitivity that you want AWS WAF to use to inspect for SQL injection attacks.\n\n`HIGH` detects more attacks, but might generate more false positives, especially if your web requests frequently contain unusual strings. For information about identifying and mitigating false positives, see [Testing and tuning](https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-testing.html) in the *AWS WAF Developer Guide* .\n\n`LOW` is generally a better choice for resources that already have other protections against SQL injection attacks or that have a low tolerance for false positives.\n\nDefault: `LOW`", - "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. If you specify one or more transformations in a rule statement, AWS WAF performs all transformations on the content of the request component identified by `FieldToMatch` , starting from the lowest priority setting, before inspecting the content for a match." + "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. Text transformations are used in rule match statements, to transform the `FieldToMatch` request component before inspecting it, and they're used in rate-based rule statements, to transform request components before using them as custom aggregation keys. If you specify one or more transformations to apply, AWS WAF performs all transformations on the specified content, starting from the lowest priority setting, and then uses the component contents." } }, "AWS::WAFv2::WebACL.Statement": { @@ -64921,7 +64921,7 @@ "ManagedRuleGroupStatement": "A rule statement used to run the rules that are defined in a managed rule group. To use this, provide the vendor name and the name of the rule group in this statement.\n\nYou cannot nest a `ManagedRuleGroupStatement` , for example for use inside a `NotStatement` or `OrStatement` . It can only be referenced as a top-level statement within a rule.", "NotStatement": "A logical rule statement used to negate the results of another rule statement. You provide one `Statement` within the `NotStatement` .", "OrStatement": "A logical rule statement used to combine other rule statements with OR logic. You provide more than one `Statement` within the `OrStatement` .", - "RateBasedStatement": "A rate-based rule tracks the rate of requests for each originating IP address, and triggers the rule action when the rate exceeds a limit that you specify on the number of requests in any 5-minute time span. You can use this to put a temporary block on requests from an IP address that is sending excessive requests.\n\nAWS WAF tracks and manages web requests separately for each instance of a rate-based rule that you use. For example, if you provide the same rate-based rule settings in two web ACLs, each of the two rule statements represents a separate instance of the rate-based rule and gets its own tracking and management by AWS WAF . If you define a rate-based rule inside a rule group, and then use that rule group in multiple places, each use creates a separate instance of the rate-based rule that gets its own tracking and management by AWS WAF .\n\nWhen the rule action triggers, AWS WAF blocks additional requests from the IP address until the request rate falls below the limit.\n\nYou can optionally nest another statement inside the rate-based statement, to narrow the scope of the rule so that it only counts requests that match the nested statement. For example, based on recent requests that you have seen from an attacker, you might create a rate-based rule with a nested AND rule statement that contains the following nested statements:\n\n- An IP match statement with an IP set that specifies the address 192.0.2.44.\n- A string match statement that searches in the User-Agent header for the string BadBot.\n\nIn this rate-based rule, you also define a rate limit. For this example, the rate limit is 1,000. Requests that meet the criteria of both of the nested statements are counted. If the count exceeds 1,000 requests per five minutes, the rule action triggers. Requests that do not meet the criteria of both of the nested statements are not counted towards the rate limit and are not affected by this rule.\n\nYou cannot nest a `RateBasedStatement` inside another statement, for example inside a `NotStatement` or `OrStatement` . You can define a `RateBasedStatement` inside a web ACL and inside a rule group.", + "RateBasedStatement": "A rate-based rule counts incoming requests and rate limits requests when they are coming at too fast a rate. The rule categorizes requests according to your aggregation criteria, collects them into aggregation instances, and counts and rate limits the requests for each instance.\n\nYou can specify individual aggregation keys, like IP address or HTTP method. You can also specify aggregation key combinations, like IP address and HTTP method, or HTTP method, query argument, and cookie.\n\nEach unique set of values for the aggregation keys that you specify is a separate aggregation instance, with the value from each key contributing to the aggregation instance definition.\n\nFor example, assume the rule evaluates web requests with the following IP address and HTTP method values:\n\n- IP address 10.1.1.1, HTTP method POST\n- IP address 10.1.1.1, HTTP method GET\n- IP address 127.0.0.0, HTTP method POST\n- IP address 10.1.1.1, HTTP method GET\n\nThe rule would create different aggregation instances according to your aggregation criteria, for example:\n\n- If the aggregation criteria is just the IP address, then each individual address is an aggregation instance, and AWS WAF counts requests separately for each. The aggregation instances and request counts for our example would be the following:\n\n- IP address 10.1.1.1: count 3\n- IP address 127.0.0.0: count 1\n- If the aggregation criteria is HTTP method, then each individual HTTP method is an aggregation instance. The aggregation instances and request counts for our example would be the following:\n\n- HTTP method POST: count 2\n- HTTP method GET: count 2\n- If the aggregation criteria is IP address and HTTP method, then each IP address and each HTTP method would contribute to the combined aggregation instance. The aggregation instances and request counts for our example would be the following:\n\n- IP address 10.1.1.1, HTTP method POST: count 1\n- IP address 10.1.1.1, HTTP method GET: count 2\n- IP address 127.0.0.0, HTTP method POST: count 1\n\nFor any n-tuple of aggregation keys, each unique combination of values for the keys defines a separate aggregation instance, which AWS WAF counts and rate-limits individually.\n\nYou can optionally nest another statement inside the rate-based statement, to narrow the scope of the rule so that it only counts and rate limits requests that match the nested statement. You can use this nested scope-down statement in conjunction with your aggregation key specifications or you can just count and rate limit all requests that match the scope-down statement, without additional aggregation. When you choose to just manage all requests that match a scope-down statement, the aggregation instance is singular for the rule.\n\nYou cannot nest a `RateBasedStatement` inside another statement, for example inside a `NotStatement` or `OrStatement` . You can define a `RateBasedStatement` inside a web ACL and inside a rule group.\n\nFor additional information about the options, see [Rate limiting web requests using rate-based rules](https://docs.aws.amazon.com/waf/latest/developerguide/waf-rate-based-rules.html) in the *AWS WAF Developer Guide* .\n\nIf you only aggregate on the individual IP address or forwarded IP address, you can retrieve the list of IP addresses that AWS WAF is currently rate limiting for a rule through the API call `GetRateBasedStatementManagedKeys` . This option is not available for other aggregation configurations.\n\nAWS WAF tracks and manages web requests separately for each instance of a rate-based rule that you use. For example, if you provide the same rate-based rule settings in two web ACLs, each of the two rule statements represents a separate instance of the rate-based rule and gets its own tracking and management by AWS WAF . If you define a rate-based rule inside a rule group, and then use that rule group in multiple places, each use creates a separate instance of the rate-based rule that gets its own tracking and management by AWS WAF .", "RegexMatchStatement": "A rule statement used to search web request components for a match against a single regular expression.", "RegexPatternSetReferenceStatement": "A rule statement used to search web request components for matches with regular expressions. To use this, create a `RegexPatternSet` that specifies the expressions that you want to detect, then use the ARN of that set in this statement. A web request matches the pattern set rule statement if the request component matches any of the patterns in the set.\n\nEach regex pattern set rule statement references a regex pattern set. You create and maintain the set independent of your rules. This allows you to use the single set in multiple rules. When you update the referenced set, AWS WAF automatically updates all rules that reference it.", "RuleGroupReferenceStatement": "A rule statement used to run the rules that are defined in a `RuleGroup` . To use this, create a rule group with your rules, then provide the ARN of the rule group in this statement.\n\nYou cannot nest a `RuleGroupReferenceStatement` , for example for use inside a `NotStatement` or `OrStatement` . You can only use a rule group reference statement at the top level inside a web ACL.", @@ -64934,7 +64934,7 @@ "attributes": {}, "description": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection.", "properties": { - "Priority": "Sets the relative processing order for multiple transformations that are defined for a rule statement. AWS WAF processes all transformations, from lowest priority to highest, before inspecting the transformed content. The priorities don't need to be consecutive, but they must all be different.", + "Priority": "Sets the relative processing order for multiple transformations. AWS WAF processes all transformations, from lowest priority to highest, before inspecting the transformed content. The priorities don't need to be consecutive, but they must all be different.", "Type": "You can specify the following transformation types:\n\n*BASE64_DECODE* - Decode a `Base64` -encoded string.\n\n*BASE64_DECODE_EXT* - Decode a `Base64` -encoded string, but use a forgiving implementation that ignores characters that aren't valid.\n\n*CMD_LINE* - Command-line transformations. These are helpful in reducing effectiveness of attackers who inject an operating system command-line command and use unusual formatting to disguise some or all of the command.\n\n- Delete the following characters: `\\ \" ' ^`\n- Delete spaces before the following characters: `/ (`\n- Replace the following characters with a space: `, ;`\n- Replace multiple spaces with one space\n- Convert uppercase letters (A-Z) to lowercase (a-z)\n\n*COMPRESS_WHITE_SPACE* - Replace these characters with a space character (decimal 32):\n\n- `\\f` , formfeed, decimal 12\n- `\\t` , tab, decimal 9\n- `\\n` , newline, decimal 10\n- `\\r` , carriage return, decimal 13\n- `\\v` , vertical tab, decimal 11\n- Non-breaking space, decimal 160\n\n`COMPRESS_WHITE_SPACE` also replaces multiple spaces with one space.\n\n*CSS_DECODE* - Decode characters that were encoded using CSS 2.x escape rules `syndata.html#characters` . This function uses up to two bytes in the decoding process, so it can help to uncover ASCII characters that were encoded using CSS encoding that wouldn\u2019t typically be encoded. It's also useful in countering evasion, which is a combination of a backslash and non-hexadecimal characters. For example, `ja\\vascript` for javascript.\n\n*ESCAPE_SEQ_DECODE* - Decode the following ANSI C escape sequences: `\\a` , `\\b` , `\\f` , `\\n` , `\\r` , `\\t` , `\\v` , `\\\\` , `\\?` , `\\'` , `\\\"` , `\\xHH` (hexadecimal), `\\0OOO` (octal). Encodings that aren't valid remain in the output.\n\n*HEX_DECODE* - Decode a string of hexadecimal characters into a binary.\n\n*HTML_ENTITY_DECODE* - Replace HTML-encoded characters with unencoded characters. `HTML_ENTITY_DECODE` performs these operations:\n\n- Replaces `(ampersand)quot;` with `\"`\n- Replaces `(ampersand)nbsp;` with a non-breaking space, decimal 160\n- Replaces `(ampersand)lt;` with a \"less than\" symbol\n- Replaces `(ampersand)gt;` with `>`\n- Replaces characters that are represented in hexadecimal format, `(ampersand)#xhhhh;` , with the corresponding characters\n- Replaces characters that are represented in decimal format, `(ampersand)#nnnn;` , with the corresponding characters\n\n*JS_DECODE* - Decode JavaScript escape sequences. If a `\\` `u` `HHHH` code is in the full-width ASCII code range of `FF01-FF5E` , then the higher byte is used to detect and adjust the lower byte. If not, only the lower byte is used and the higher byte is zeroed, causing a possible loss of information.\n\n*LOWERCASE* - Convert uppercase letters (A-Z) to lowercase (a-z).\n\n*MD5* - Calculate an MD5 hash from the data in the input. The computed hash is in a raw binary form.\n\n*NONE* - Specify `NONE` if you don't want any text transformations.\n\n*NORMALIZE_PATH* - Remove multiple slashes, directory self-references, and directory back-references that are not at the beginning of the input from an input string.\n\n*NORMALIZE_PATH_WIN* - This is the same as `NORMALIZE_PATH` , but first converts backslash characters to forward slashes.\n\n*REMOVE_NULLS* - Remove all `NULL` bytes from the input.\n\n*REPLACE_COMMENTS* - Replace each occurrence of a C-style comment ( `/* ... */` ) with a single space. Multiple consecutive occurrences are not compressed. Unterminated comments are also replaced with a space (ASCII 0x20). However, a standalone termination of a comment ( `*/` ) is not acted upon.\n\n*REPLACE_NULLS* - Replace NULL bytes in the input with space characters (ASCII `0x20` ).\n\n*SQL_HEX_DECODE* - Decode SQL hex data. Example ( `0x414243` ) will be decoded to ( `ABC` ).\n\n*URL_DECODE* - Decode a URL-encoded value.\n\n*URL_DECODE_UNI* - Like `URL_DECODE` , but with support for Microsoft-specific `%u` encoding. If the code is in the full-width ASCII code range of `FF01-FF5E` , the higher byte is used to detect and adjust the lower byte. Otherwise, only the lower byte is used and the higher byte is zeroed.\n\n*UTF8_TO_UNICODE* - Convert all UTF-8 character sequences to Unicode. This helps input normalization, and minimizing false-positives and false-negatives for non-English languages." } }, @@ -64942,7 +64942,7 @@ "attributes": {}, "description": "Defines and enables Amazon CloudWatch metrics and web request sample collection.", "properties": { - "CloudWatchMetricsEnabled": "A boolean indicating whether the associated resource sends metrics to Amazon CloudWatch. For the list of available metrics, see [AWS WAF Metrics](https://docs.aws.amazon.com/waf/latest/developerguide/monitoring-cloudwatch.html#waf-metrics) in the *AWS WAF Developer Guide* .", + "CloudWatchMetricsEnabled": "A boolean indicating whether the associated resource sends metrics to Amazon CloudWatch. For the list of available metrics, see [AWS WAF Metrics](https://docs.aws.amazon.com/waf/latest/developerguide/monitoring-cloudwatch.html#waf-metrics) in the *AWS WAF Developer Guide* .\n\nFor web ACLs, the metrics are for web requests that have the web ACL default action applied. AWS WAF applies the default action to web requests that pass the inspection of all rules in the web ACL without being either allowed or blocked. For more information,\nsee [The web ACL default action](https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-default-action.html) in the *AWS WAF Developer Guide* .", "MetricName": "A name of the Amazon CloudWatch metric dimension. The name can contain only the characters: A-Z, a-z, 0-9, - (hyphen), and _ (underscore). The name can be from one to 128 characters long. It can't contain whitespace or metric names that are reserved for AWS WAF , for example `All` and `Default_Action` .", "SampledRequestsEnabled": "A boolean indicating whether AWS WAF should store a sampling of the web requests that match the rules. You can view the sampled requests through the AWS WAF console." } @@ -64952,7 +64952,7 @@ "description": "A rule statement that inspects for cross-site scripting (XSS) attacks. In XSS attacks, the attacker uses vulnerabilities in a benign website as a vehicle to inject malicious client-site scripts into other legitimate web browsers.", "properties": { "FieldToMatch": "The part of the web request that you want AWS WAF to inspect.", - "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. If you specify one or more transformations in a rule statement, AWS WAF performs all transformations on the content of the request component identified by `FieldToMatch` , starting from the lowest priority setting, before inspecting the content for a match." + "TextTransformations": "Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. Text transformations are used in rule match statements, to transform the `FieldToMatch` request component before inspecting it, and they're used in rate-based rule statements, to transform request components before using them as custom aggregation keys. If you specify one or more transformations to apply, AWS WAF performs all transformations on the specified content, starting from the lowest priority setting, and then uses the component contents." } }, "AWS::WAFv2::WebACLAssociation": { @@ -64961,7 +64961,7 @@ }, "description": "> This is the latest version of *AWS WAF* , named AWS WAF V2, released in November, 2019. For information, including how to migrate your AWS WAF resources from the prior release, see the [AWS WAF Developer Guide](https://docs.aws.amazon.com/waf/latest/developerguide/waf-chapter.html) . \n\nUse a web ACL association to define an association between a web ACL and a regional application resource, to protect the resource. A regional application can be an Application Load Balancer (ALB), an Amazon API Gateway REST API, an AWS AppSync GraphQL API, an Amazon Cognito user pool, or an AWS App Runner service.\n\nFor Amazon CloudFront , don't use this resource. Instead, use your CloudFront distribution configuration. To associate a web ACL with a distribution, provide the Amazon Resource Name (ARN) of the `WebACL` to your CloudFront distribution configuration. To disassociate a web ACL, provide an empty ARN. For information, see [AWS::CloudFront::Distribution](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-distribution.html) .\n\nWhen you create a web ACL or make changes to a web ACL or web ACL components, like rules and rule groups, AWS WAF propagates the changes everywhere that the web ACL and its components are stored and used. Your changes are applied within seconds, but there might be a brief period of inconsistency when the changes have arrived in some places and not in others. So, for example, if you change a rule action setting, the action might be the old action in one area and the new action in another area. Or if you add an IP address to an IP set used in a blocking rule, the new address might briefly be blocked in one area while still allowed in another. This temporary inconsistency can occur when you first associate a web ACL with an AWS resource and when you change a web ACL that is already associated with a resource. Generally, any inconsistencies of this type last only a few seconds.", "properties": { - "ResourceArn": "The Amazon Resource Name (ARN) of the resource to associate with the web ACL.\n\nThe ARN must be in one of the following formats:\n\n- For an Application Load Balancer: `arn: *partition* :elasticloadbalancing: *region* : *account-id* :loadbalancer/app/ *load-balancer-name* / *load-balancer-id*`\n- For an Amazon API Gateway REST API: `arn: *partition* :apigateway: *region* ::/restapis/ *api-id* /stages/ *stage-name*`\n- For an AWS AppSync GraphQL API: `arn: *partition* :appsync: *region* : *account-id* :apis/ *GraphQLApiId*`\n- For an Amazon Cognito user pool: `arn: *partition* :cognito-idp: *region* : *account-id* :userpool/ *user-pool-id*`\n- For an AWS App Runner service: `arn: *partition* :apprunner: *region* : *account-id* :service/ *apprunner-service-name* / *apprunner-service-id*`\n- For an AWS Verified Access instance: `arn: *partition* :ec2: *region* : *account-id* :verified-access-instance/ *instance-id*`", + "ResourceArn": "The Amazon Resource Name (ARN) of the resource to associate with the web ACL.\n\nThe ARN must be in one of the following formats:\n\n- For an Application Load Balancer: `arn:aws:elasticloadbalancing: *region* : *account-id* :loadbalancer/app/ *load-balancer-name* / *load-balancer-id*`\n- For an Amazon API Gateway REST API: `arn:aws:apigateway: *region* ::/restapis/ *api-id* /stages/ *stage-name*`\n- For an AWS AppSync GraphQL API: `arn:aws:appsync: *region* : *account-id* :apis/ *GraphQLApiId*`\n- For an Amazon Cognito user pool: `arn:aws:cognito-idp: *region* : *account-id* :userpool/ *user-pool-id*`\n- For an AWS App Runner service: `arn:aws:apprunner: *region* : *account-id* :service/ *apprunner-service-name* / *apprunner-service-id*`", "WebACLArn": "The Amazon Resource Name (ARN) of the web ACL that you want to associate with the resource." } }, @@ -65142,7 +65142,7 @@ "Ref": "`Ref` returns the Amazon Resource Name (ARN) of the sampling rule.", "RuleARN": "The sampling rule ARN that was created or updated." }, - "description": "Use the `AWS::XRay::SamplingRule` resource to specify a sampling rule, which controls sampling behavior for instrumented applications. Include a `SamplingRule` entity to create or update a sampling rule.\n\n> `SamplingRule.Version` cannot be changed when updating a sampling rule, and will be ignored. \n\nServices retrieve rules with [GetSamplingRules](https://docs.aws.amazon.com//xray/latest/api/API_GetSamplingRules.html) , and evaluate each rule in ascending order of *priority* for each request. If a rule matches, the service records a trace, borrowing it from the reservoir size. After 10 seconds, the service reports back to X-Ray with [GetSamplingTargets](https://docs.aws.amazon.com//xray/latest/api/API_GetSamplingTargets.html) to get updated versions of each in-use rule. The updated rule contains a trace quota that the service can use instead of borrowing from the reservoir.", + "description": "Use the `AWS::XRay::SamplingRule` resource to specify a sampling rule, which controls sampling behavior for instrumented applications. Include a `SamplingRule` entity to create or update a sampling rule.\n\n> `SamplingRule.Version` can only be set when creating a sampling rule. Updating the version will cause the update to fail. \n\nServices retrieve rules with [GetSamplingRules](https://docs.aws.amazon.com//xray/latest/api/API_GetSamplingRules.html) , and evaluate each rule in ascending order of *priority* for each request. If a rule matches, the service records a trace, borrowing it from the reservoir size. After 10 seconds, the service reports back to X-Ray with [GetSamplingTargets](https://docs.aws.amazon.com//xray/latest/api/API_GetSamplingTargets.html) to get updated versions of each in-use rule. The updated rule contains a trace quota that the service can use instead of borrowing from the reservoir.", "properties": { "SamplingRule": "The sampling rule to be created or updated.", "Tags": "An array of key-value pairs to apply to this resource." diff --git a/packages/@aws-cdk/integ-runner/THIRD_PARTY_LICENSES b/packages/@aws-cdk/integ-runner/THIRD_PARTY_LICENSES index 0b9054f7609f0..9b7d8a71bbda8 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.1378.0 - https://www.npmjs.com/package/aws-sdk/v/2.1378.0 | Apache-2.0 +** aws-sdk@2.1379.0 - https://www.npmjs.com/package/aws-sdk/v/2.1379.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-alpha/package.json b/packages/@aws-cdk/integ-tests-alpha/package.json index 9b3139532395c..71c4566de8fe0 100644 --- a/packages/@aws-cdk/integ-tests-alpha/package.json +++ b/packages/@aws-cdk/integ-tests-alpha/package.json @@ -71,7 +71,7 @@ "@aws-cdk/pkglint": "0.0.0", "@types/fs-extra": "^9.0.13", "@types/jest": "^29.5.1", - "aws-sdk": "^2.1378.0", + "aws-sdk": "^2.1379.0", "aws-sdk-mock": "5.6.0", "jest": "^29.5.0", "nock": "^13.3.1", diff --git a/packages/aws-cdk-lib/aws-apigateway/README.md b/packages/aws-cdk-lib/aws-apigateway/README.md index 9acadea42b75b..3008ba27848b3 100644 --- a/packages/aws-cdk-lib/aws-apigateway/README.md +++ b/packages/aws-cdk-lib/aws-apigateway/README.md @@ -60,6 +60,14 @@ book.addMethod('GET'); book.addMethod('DELETE'); ``` +To give an IAM User or Role permission to invoke a method, use `grantExecute`: + +```ts +declare user: iam.User; +const books = api.root.addResource('books'); +books.grantExecute(user); +``` + ## AWS Lambda-backed APIs A very common practice is to use Amazon API Gateway with AWS Lambda as the diff --git a/packages/aws-cdk-lib/aws-apigateway/lib/method.ts b/packages/aws-cdk-lib/aws-apigateway/lib/method.ts index 7a2850babbd4d..53f4b44eeed03 100644 --- a/packages/aws-cdk-lib/aws-apigateway/lib/method.ts +++ b/packages/aws-cdk-lib/aws-apigateway/lib/method.ts @@ -12,6 +12,7 @@ import { IRestApi, RestApi, RestApiBase } from './restapi'; import { IStage } from './stage'; import { validateHttpMethod } from './util'; import * as cloudwatch from '../../aws-cloudwatch'; +import * as iam from '../../aws-iam'; import { ArnFormat, FeatureFlags, Lazy, Names, Resource, Stack } from '../../core'; import { APIGATEWAY_REQUEST_VALIDATOR_UNIQUE_ID } from '../../cx-api'; @@ -455,6 +456,19 @@ export class Method extends Resource { return this.cannedMetric(ApiGatewayMetrics.latencyAverage, stage, props); } + /** + * Grants an IAM principal permission to invoke this method. + * + * @param grantee the principal + */ + public grantExecute(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions: ['execute-api:Invoke'], + resourceArns: [this.methodArn], + }); + } + private cannedMetric(fn: (dims: { ApiName: string; Method: string; diff --git a/packages/aws-cdk-lib/aws-apigateway/test/method.test.ts b/packages/aws-cdk-lib/aws-apigateway/test/method.test.ts index 81cd18ef8b0bb..1772c509ff3c4 100644 --- a/packages/aws-cdk-lib/aws-apigateway/test/method.test.ts +++ b/packages/aws-cdk-lib/aws-apigateway/test/method.test.ts @@ -1073,5 +1073,53 @@ describe('method', () => { expect(metric.color).toEqual(color); expect(metric.dimensions).toEqual({ ApiName: 'test-api', Method: 'GET', Resource: '/pets', Stage: api.deploymentStage.stageName }); }); + + test('grantExecute', () => { + // GIVEN + const stack = new cdk.Stack(); + const user = new iam.User(stack, 'user'); + + // WHEN + const api = new apigw.RestApi(stack, 'test-api'); + const method = api.root.addResource('pets').addMethod('GET'); + method.grantExecute(user); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'execute-api:Invoke', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':execute-api:', + { + Ref: 'AWS::Region', + }, + ':', + { Ref: 'AWS::AccountId' }, + ':', + { Ref: 'testapiD6451F70' }, + '/', + { Ref: 'testapiDeploymentStageprod5C9E92A4' }, + '/GET/pets', + ], + ], + }, + }, + ], + }, + Users: [{ + Ref: 'user2C2B57AE', + }], + }); + }); }); }); diff --git a/packages/aws-cdk-lib/aws-codepipeline-actions/README.md b/packages/aws-cdk-lib/aws-codepipeline-actions/README.md index 74a37565664ee..e5a2b9976c60f 100644 --- a/packages/aws-cdk-lib/aws-codepipeline-actions/README.md +++ b/packages/aws-cdk-lib/aws-codepipeline-actions/README.md @@ -859,12 +859,16 @@ To use an S3 Bucket as a deployment target in CodePipeline: ```ts const sourceOutput = new codepipeline.Artifact(); const targetBucket = new s3.Bucket(this, 'MyBucket'); +const key: kms.IKey = new kms.Key(stack, 'EnvVarEncryptKey', { + description: 'sample key', +}); const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); const deployAction = new codepipeline_actions.S3DeployAction({ actionName: 'S3Deploy', bucket: targetBucket, input: sourceOutput, + encryptionKey: key, }); const deployStage = pipeline.addStage({ stageName: 'Deploy', diff --git a/packages/aws-cdk-lib/aws-codepipeline-actions/lib/s3/deploy-action.ts b/packages/aws-cdk-lib/aws-codepipeline-actions/lib/s3/deploy-action.ts index 8bf60a2696395..91b265d4e3b01 100644 --- a/packages/aws-cdk-lib/aws-codepipeline-actions/lib/s3/deploy-action.ts +++ b/packages/aws-cdk-lib/aws-codepipeline-actions/lib/s3/deploy-action.ts @@ -1,6 +1,7 @@ import { kebab as toKebabCase } from 'case'; import { Construct } from 'constructs'; import * as codepipeline from '../../../aws-codepipeline'; +import * as kms from '../../../aws-kms'; import * as s3 from '../../../aws-s3'; import { Duration } from '../../../core'; import { Action } from '../action'; @@ -86,6 +87,13 @@ export interface S3DeployActionProps extends codepipeline.CommonAwsActionProps { * @default - none, decided by the HTTP client */ readonly cacheControl?: CacheControl[]; + + /** + * The AWS KMS encryption key for the host bucket. + * The encryptionKey parameter encrypts uploaded artifacts with the provided AWS KMS key. + * @default - none + */ + readonly encryptionKey?: kms.IKey; } /** @@ -121,6 +129,8 @@ export class S3DeployAction extends Action { // the Action Role also needs to read from the Pipeline's bucket options.bucket.grantRead(options.role); + this.props.encryptionKey?.grantEncrypt(options.role); + const acl = this.props.accessControl; return { configuration: { @@ -129,6 +139,7 @@ export class S3DeployAction extends Action { ObjectKey: this.props.objectKey, CannedACL: acl ? toKebabCase(acl.toString()) : undefined, CacheControl: this.props.cacheControl && this.props.cacheControl.map(ac => ac.value).join(', '), + KMSEncryptionKeyARN: this.props.encryptionKey?.keyArn, }, }; } diff --git a/packages/aws-cdk-lib/aws-codepipeline-actions/test/s3/s3-deploy-action.test.ts b/packages/aws-cdk-lib/aws-codepipeline-actions/test/s3/s3-deploy-action.test.ts index a3ebd51a2cd4c..a2d362a9602e0 100644 --- a/packages/aws-cdk-lib/aws-codepipeline-actions/test/s3/s3-deploy-action.test.ts +++ b/packages/aws-cdk-lib/aws-codepipeline-actions/test/s3/s3-deploy-action.test.ts @@ -1,5 +1,6 @@ import { Template } from '../../../assertions'; import * as codepipeline from '../../../aws-codepipeline'; +import * as kms from '../../../aws-kms'; import * as s3 from '../../../aws-s3'; import { App, Duration, SecretValue, Stack } from '../../../core'; import * as cpactions from '../../lib'; @@ -177,6 +178,26 @@ describe('S3 Deploy Action', () => { }); }); +test('KMSEncryptionKeyARN value', () => { + const stack = new Stack(); + minimalPipeline(stack); + + Template.fromStack(stack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + 'Stages': [ + {}, + { + 'Actions': [ + { + 'Configuration': { + 'KMSEncryptionKeyARN': { 'Fn::GetAtt': ['EnvVarEncryptKey1A7CABDB', 'Arn'] }, + }, + }, + ], + }, + ], + }); +}); + interface MinimalPipelineOptions { readonly accessControl?: s3.BucketAccessControl; readonly bucket?: s3.IBucket; @@ -186,6 +207,9 @@ interface MinimalPipelineOptions { } function minimalPipeline(stack: Stack, options: MinimalPipelineOptions = {}): codepipeline.IStage { + const key: kms.IKey = new kms.Key(stack, 'EnvVarEncryptKey', { + description: 'sample key', + }); const sourceOutput = new codepipeline.Artifact(); const sourceAction = new cpactions.GitHubSourceAction({ actionName: 'Source', @@ -215,6 +239,7 @@ function minimalPipeline(stack: Stack, options: MinimalPipelineOptions = {}): co extract: options.extract, input: sourceOutput, objectKey: options.objectKey, + encryptionKey: key, }), ], }); diff --git a/packages/aws-cdk-lib/aws-ecr-assets/lib/image-asset.ts b/packages/aws-cdk-lib/aws-ecr-assets/lib/image-asset.ts index 3ca82c7194cb8..a626c596c814a 100644 --- a/packages/aws-cdk-lib/aws-ecr-assets/lib/image-asset.ts +++ b/packages/aws-cdk-lib/aws-ecr-assets/lib/image-asset.ts @@ -266,6 +266,14 @@ export interface DockerImageAssetOptions extends FingerprintOptions, FileFingerp */ readonly outputs?: string[]; + /** + * Unique identifier of the docker image asset and its potential revisions. + * Required if using AppScopedStagingSynthesizer. + * + * @default - no asset name + */ + readonly assetName?: string; + /** * Cache from options to pass to the `docker build` command. * @@ -361,6 +369,14 @@ export class DockerImageAsset extends Construct implements IAsset { */ private readonly dockerOutputs?: string[]; + /** + * Unique identifier of the docker image asset and its potential revisions. + * Required if using AppScopedStagingSynthesizer. + * + * @default - no asset name + */ + private readonly assetName?: string; + /** * Cache from options to pass to the `docker build` command. */ @@ -453,11 +469,12 @@ export class DockerImageAsset extends Construct implements IAsset { : JSON.stringify(extraHash), }); - this.sourceHash = staging.assetHash; this.assetHash = staging.assetHash; + this.sourceHash = this.assetHash; const stack = Stack.of(this); this.assetPath = staging.relativeStagedPath(stack); + this.assetName = props.assetName; this.dockerBuildArgs = props.buildArgs; this.dockerBuildSecrets = props.buildSecrets; this.dockerBuildTarget = props.target; @@ -467,6 +484,7 @@ export class DockerImageAsset extends Construct implements IAsset { const location = stack.synthesizer.addDockerImageAsset({ directoryName: this.assetPath, + assetName: this.assetName, dockerBuildArgs: this.dockerBuildArgs, dockerBuildSecrets: this.dockerBuildSecrets, dockerBuildTarget: this.dockerBuildTarget, diff --git a/packages/aws-cdk-lib/aws-s3-assets/lib/asset.ts b/packages/aws-cdk-lib/aws-s3-assets/lib/asset.ts index a5b5f58e38d12..d20059505d674 100644 --- a/packages/aws-cdk-lib/aws-s3-assets/lib/asset.ts +++ b/packages/aws-cdk-lib/aws-s3-assets/lib/asset.ts @@ -34,6 +34,21 @@ export interface AssetOptions extends CopyOptions, cdk.FileCopyOptions, cdk.Asse * @deprecated see `assetHash` and `assetHashType` */ readonly sourceHash?: string; + + /** + * Whether or not the asset needs to exist beyond deployment time; i.e. + * are copied over to a different location and not needed afterwards. + * Setting this property to true has an impact on the lifecycle of the asset, + * because we will assume that it is safe to delete after the CloudFormation + * deployment succeeds. + * + * For example, Lambda Function assets are copied over to Lambda during + * deployment. Therefore, it is not necessary to store the asset in S3, so + * we consider those deployTime assets. + * + * @default false + */ + readonly deployTime?: boolean; } export interface AssetProps extends AssetOptions { @@ -147,6 +162,7 @@ export class Asset extends Construct implements cdk.IAsset { packaging: staging.packaging, sourceHash: this.sourceHash, fileName: this.assetPath, + deployTime: props.deployTime, }); this.s3BucketName = location.bucketName; diff --git a/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts b/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts index abbc4dc836d10..03303e44eb7c5 100644 --- a/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts +++ b/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts @@ -579,7 +579,7 @@ export class StringParameter extends ParameterBase implements IStringParameter { * @param scope Some scope within a stack * @param parameterName The name of the SSM parameter * @param version The parameter version (required for secure strings) - * @deprecated Use `SecretValue.ssmSecure()` instead, it will correctly type the imported value as a `SecretValue` and allow importing without version. + * @deprecated Use `SecretValue.ssmSecure()` instead, it will correctly type the imported value as a `SecretValue` and allow importing without version. `SecretValue` lives in the core `aws-cdk-lib` module. */ public static valueForSecureStringParameter(scope: Construct, parameterName: string, version: number): string { const stack = Stack.of(scope); diff --git a/packages/aws-cdk-lib/core/lib/assets.ts b/packages/aws-cdk-lib/core/lib/assets.ts index 99c9202b968ca..d4243b6f7a7b7 100644 --- a/packages/aws-cdk-lib/core/lib/assets.ts +++ b/packages/aws-cdk-lib/core/lib/assets.ts @@ -131,6 +131,21 @@ export interface FileAssetSource { * @default - Required if `fileName` is specified. */ readonly packaging?: FileAssetPackaging; + + /** + * Whether or not the asset needs to exist beyond deployment time; i.e. + * are copied over to a different location and not needed afterwards. + * Setting this property to true has an impact on the lifecycle of the asset, + * because we will assume that it is safe to delete after the CloudFormation + * deployment succeeds. + * + * For example, Lambda Function assets are copied over to Lambda during + * deployment. Therefore, it is not necessary to store the asset in S3, so + * we consider those deployTime assets. + * + * @default false + */ + readonly deployTime?: boolean; } export interface DockerImageAssetSource { @@ -242,18 +257,27 @@ export interface DockerImageAssetSource { */ readonly dockerOutputs?: string[]; + /** + * Unique identifier of the docker image asset and its potential revisions. + * Required if using AppScopedStagingSynthesizer. + * + * @default - no asset name + */ + readonly assetName?: string; + /** * Cache from options to pass to the `docker build` command. + * * @default - no cache from args are passed */ readonly dockerCacheFrom?: DockerCacheOption[]; /** * Cache to options to pass to the `docker build` command. + * * @default - no cache to args are passed */ readonly dockerCacheTo?: DockerCacheOption; - } /** diff --git a/packages/aws-cdk-lib/core/lib/helpers-internal/index.ts b/packages/aws-cdk-lib/core/lib/helpers-internal/index.ts index 9a36222b224cf..bc3f28e0107bb 100644 --- a/packages/aws-cdk-lib/core/lib/helpers-internal/index.ts +++ b/packages/aws-cdk-lib/core/lib/helpers-internal/index.ts @@ -2,3 +2,4 @@ export * from './cfn-parse'; // Other libraries are going to need this as well export { md5hash } from '../private/md5'; export * from './customize-roles'; +export * from './string-specializer'; \ No newline at end of file diff --git a/packages/aws-cdk-lib/core/lib/helpers-internal/string-specializer.ts b/packages/aws-cdk-lib/core/lib/helpers-internal/string-specializer.ts new file mode 100644 index 0000000000000..052cd2dafcd8a --- /dev/null +++ b/packages/aws-cdk-lib/core/lib/helpers-internal/string-specializer.ts @@ -0,0 +1,93 @@ +import * as cxapi from '../../../cx-api'; +import { Aws } from '../cfn-pseudo'; +import { Stack } from '../stack'; +import { Token } from '../token'; + +/** + * A "replace-all" function that doesn't require us escaping a literal string to a regex + */ +function replaceAll(s: string, search: string, replace: string) { + return s.split(search).join(replace); +} + +export class StringSpecializer { + /** + * Validate that the given string does not contain tokens + */ + public static validateNoTokens(s: string, what: string) { + if (Token.isUnresolved(s)) { + throw new Error(`${what} may not contain tokens; only the following literal placeholder strings are allowed: ` + [ + '${Qualifier}', + cxapi.EnvironmentPlaceholders.CURRENT_REGION, + cxapi.EnvironmentPlaceholders.CURRENT_ACCOUNT, + cxapi.EnvironmentPlaceholders.CURRENT_PARTITION, + ].join(', ') + `. Got: ${s}`); + } + } + + constructor(private readonly stack: Stack, private readonly qualifier: string) { } + + /** + * Function to replace placeholders in the input string as much as possible + * + * We replace: + * - ${Qualifier}: always + * - ${AWS::AccountId}, ${AWS::Region}: only if we have the actual values available + * - ${AWS::Partition}: never, since we never have the actual partition value. + */ + public specialize(s: string): string { + s = replaceAll(s, '${Qualifier}', this.qualifier); + return cxapi.EnvironmentPlaceholders.replace(s, { + region: resolvedOr(this.stack.region, cxapi.EnvironmentPlaceholders.CURRENT_REGION), + accountId: resolvedOr(this.stack.account, cxapi.EnvironmentPlaceholders.CURRENT_ACCOUNT), + partition: cxapi.EnvironmentPlaceholders.CURRENT_PARTITION, + }); + } + + /** + * Specialize the given string, make sure it doesn't contain tokens + */ + public specializeNoTokens(s: string, what: string): string { + StringSpecializer.validateNoTokens(s, what); + return this.specialize(s); + } + + /** + * Specialize only the qualifier + */ + public qualifierOnly(s: string): string { + return replaceAll(s, '${Qualifier}', this.qualifier); + } +} + +/** + * Return the given value if resolved or fall back to a default + */ +export function resolvedOr(x: string, def: A): string | A { + return Token.isUnresolved(x) ? def : x; +} + +const ASSET_TOKENS = ['${AWS::Partition}', '${AWS::Region}', '${AWS::AccountId}']; +const CFN_TOKENS = [Aws.PARTITION, Aws.REGION, Aws.ACCOUNT_ID]; + +/** + * Replaces CloudFormation Tokens (i.e. 'Aws.PARTITION') with corresponding + * Asset Tokens (i.e. '${AWS::Partition}'). + */ +export function translateCfnTokenToAssetToken(arn: string) { + for (let i = 0; i < CFN_TOKENS.length; i++) { + arn = replaceAll(arn, CFN_TOKENS[i], ASSET_TOKENS[i]); + } + return arn; +} + +/** + * Replaces Asset Tokens (i.e. '${AWS::Partition}') with corresponding + * CloudFormation Tokens (i.e. 'Aws.PARTITION'). + */ +export function translateAssetTokenToCfnToken(arn: string) { + for (let i = 0; i < ASSET_TOKENS.length; i++) { + arn = replaceAll(arn, ASSET_TOKENS[i], CFN_TOKENS[i]); + } + return arn; +} diff --git a/packages/aws-cdk-lib/core/lib/stack-synthesizers/_shared.ts b/packages/aws-cdk-lib/core/lib/stack-synthesizers/_shared.ts index a9c882dcb1fa1..1017f172a850e 100644 --- a/packages/aws-cdk-lib/core/lib/stack-synthesizers/_shared.ts +++ b/packages/aws-cdk-lib/core/lib/stack-synthesizers/_shared.ts @@ -2,9 +2,7 @@ import * as crypto from 'crypto'; import { Node, IConstruct } from 'constructs'; import { ISynthesisSession } from './types'; import * as cxschema from '../../../cloud-assembly-schema'; -import * as cxapi from '../../../cx-api'; import { Stack } from '../stack'; -import { Token } from '../token'; /** * Shared logic of writing stack artifact to the Cloud Assembly @@ -126,46 +124,3 @@ export function assertBound(x: A | undefined): asserts x is NonNullable { function nonEmptyDict(xs: Record) { return Object.keys(xs).length > 0 ? xs : undefined; } - -/** - * A "replace-all" function that doesn't require us escaping a literal string to a regex - */ -function replaceAll(s: string, search: string, replace: string) { - return s.split(search).join(replace); -} - -export class StringSpecializer { - constructor(private readonly stack: Stack, private readonly qualifier: string) { - } - - /** - * Function to replace placeholders in the input string as much as possible - * - * We replace: - * - ${Qualifier}: always - * - ${AWS::AccountId}, ${AWS::Region}: only if we have the actual values available - * - ${AWS::Partition}: never, since we never have the actual partition value. - */ - public specialize(s: string): string { - s = replaceAll(s, '${Qualifier}', this.qualifier); - return cxapi.EnvironmentPlaceholders.replace(s, { - region: resolvedOr(this.stack.region, cxapi.EnvironmentPlaceholders.CURRENT_REGION), - accountId: resolvedOr(this.stack.account, cxapi.EnvironmentPlaceholders.CURRENT_ACCOUNT), - partition: cxapi.EnvironmentPlaceholders.CURRENT_PARTITION, - }); - } - - /** - * Specialize only the qualifier - */ - public qualifierOnly(s: string): string { - return replaceAll(s, '${Qualifier}', this.qualifier); - } -} - -/** - * Return the given value if resolved or fall back to a default - */ -export function resolvedOr(x: string, def: A): string | A { - return Token.isUnresolved(x) ? def : x; -} diff --git a/packages/aws-cdk-lib/core/lib/stack-synthesizers/asset-manifest-builder.ts b/packages/aws-cdk-lib/core/lib/stack-synthesizers/asset-manifest-builder.ts index da10ef7e247d0..4ad800e23ba88 100644 --- a/packages/aws-cdk-lib/core/lib/stack-synthesizers/asset-manifest-builder.ts +++ b/packages/aws-cdk-lib/core/lib/stack-synthesizers/asset-manifest-builder.ts @@ -1,9 +1,9 @@ import * as fs from 'fs'; import * as path from 'path'; -import { resolvedOr } from './_shared'; import { ISynthesisSession } from './types'; import * as cxschema from '../../../cloud-assembly-schema'; import { FileAssetSource, FileAssetPackaging, DockerImageAssetSource } from '../assets'; +import { resolvedOr } from '../helpers-internal/string-specializer'; import { Stack } from '../stack'; /** @@ -61,7 +61,8 @@ export class AssetManifestBuilder { const imageTag = `${target.dockerTagPrefix ?? ''}${asset.sourceHash}`; // Add to manifest - return this.addDockerImageAsset(stack, asset.sourceHash, { + const sourceHash = asset.assetName ? `${asset.assetName}-${asset.sourceHash}` : asset.sourceHash; + return this.addDockerImageAsset(stack, sourceHash, { executable: asset.executable, directory: asset.directoryName, dockerBuildArgs: asset.dockerBuildArgs, @@ -131,6 +132,7 @@ export class AssetManifestBuilder { stack: Stack, session: ISynthesisSession, options: cxschema.AssetManifestOptions = {}, + dependencies: string[] = [], ): string { const artifactId = `${stack.artifactId}.assets`; const manifestFile = `${artifactId}.json`; @@ -150,6 +152,7 @@ export class AssetManifestBuilder { file: manifestFile, ...options, }, + dependencies: dependencies.length > 0 ? dependencies : undefined, }); return artifactId; diff --git a/packages/aws-cdk-lib/core/lib/stack-synthesizers/bootstrapless-synthesizer.ts b/packages/aws-cdk-lib/core/lib/stack-synthesizers/bootstrapless-synthesizer.ts index ac1095c81f4ee..f9d949ff712b6 100644 --- a/packages/aws-cdk-lib/core/lib/stack-synthesizers/bootstrapless-synthesizer.ts +++ b/packages/aws-cdk-lib/core/lib/stack-synthesizers/bootstrapless-synthesizer.ts @@ -41,7 +41,7 @@ export interface BootstraplessSynthesizerProps { * synthesizer directly. */ export class BootstraplessSynthesizer extends DefaultStackSynthesizer { - constructor(props: BootstraplessSynthesizerProps) { + constructor(props: BootstraplessSynthesizerProps = {}) { super({ deployRoleArn: props.deployRoleArn, cloudFormationExecutionRole: props.cloudFormationExecutionRoleArn, diff --git a/packages/aws-cdk-lib/core/lib/stack-synthesizers/cli-credentials-synthesizer.ts b/packages/aws-cdk-lib/core/lib/stack-synthesizers/cli-credentials-synthesizer.ts index d56604a35f21c..982530c851296 100644 --- a/packages/aws-cdk-lib/core/lib/stack-synthesizers/cli-credentials-synthesizer.ts +++ b/packages/aws-cdk-lib/core/lib/stack-synthesizers/cli-credentials-synthesizer.ts @@ -1,10 +1,11 @@ -import { assertBound, StringSpecializer } from './_shared'; +import { assertBound } from './_shared'; import { AssetManifestBuilder } from './asset-manifest-builder'; import { BOOTSTRAP_QUALIFIER_CONTEXT, DefaultStackSynthesizer } from './default-synthesizer'; import { StackSynthesizer } from './stack-synthesizer'; import { ISynthesisSession, IReusableStackSynthesizer, IBoundStackSynthesizer } from './types'; import * as cxapi from '../../../cx-api'; import { DockerImageAssetLocation, DockerImageAssetSource, FileAssetLocation, FileAssetSource } from '../assets'; +import { StringSpecializer } from '../helpers-internal/string-specializer'; import { Stack } from '../stack'; import { Token } from '../token'; diff --git a/packages/aws-cdk-lib/core/lib/stack-synthesizers/default-synthesizer.ts b/packages/aws-cdk-lib/core/lib/stack-synthesizers/default-synthesizer.ts index 7140b7bffbfa5..2bfc7f6989a21 100644 --- a/packages/aws-cdk-lib/core/lib/stack-synthesizers/default-synthesizer.ts +++ b/packages/aws-cdk-lib/core/lib/stack-synthesizers/default-synthesizer.ts @@ -1,9 +1,10 @@ -import { assertBound, StringSpecializer } from './_shared'; +import { assertBound } from './_shared'; import { AssetManifestBuilder } from './asset-manifest-builder'; import { StackSynthesizer } from './stack-synthesizer'; import { ISynthesisSession, IReusableStackSynthesizer, IBoundStackSynthesizer } from './types'; import * as cxapi from '../../../cx-api'; import { DockerImageAssetLocation, DockerImageAssetSource, FileAssetLocation, FileAssetSource } from '../assets'; +import { StringSpecializer } from '../helpers-internal/string-specializer'; import { Stack } from '../stack'; import { Token } from '../token'; diff --git a/packages/aws-cdk-lib/core/lib/stack-synthesizers/stack-synthesizer.ts b/packages/aws-cdk-lib/core/lib/stack-synthesizers/stack-synthesizer.ts index 444643eb04ff3..f8d5bb30ef344 100644 --- a/packages/aws-cdk-lib/core/lib/stack-synthesizers/stack-synthesizer.ts +++ b/packages/aws-cdk-lib/core/lib/stack-synthesizers/stack-synthesizer.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; -import { addStackArtifactToAssembly, contentHash, resolvedOr } from './_shared'; +import { addStackArtifactToAssembly, contentHash } from './_shared'; import { IStackSynthesizer, ISynthesisSession } from './types'; import * as cxschema from '../../../cloud-assembly-schema'; import * as cxapi from '../../../cx-api'; @@ -8,6 +8,7 @@ import { DockerImageAssetLocation, DockerImageAssetSource, FileAssetLocation, Fi import { Fn } from '../cfn-fn'; import { CfnParameter } from '../cfn-parameter'; import { CfnRule } from '../cfn-rule'; +import { resolvedOr } from '../helpers-internal/string-specializer'; import { Stack } from '../stack'; /** @@ -291,6 +292,7 @@ function stackTemplateFileAsset(stack: Stack, session: ISynthesisSession): FileA fileName: stack.templateFile, packaging: FileAssetPackaging.FILE, sourceHash, + deployTime: true, }; } diff --git a/packages/aws-cdk-lib/core/lib/stack.ts b/packages/aws-cdk-lib/core/lib/stack.ts index cb25807207a07..113c4ccd2bc9e 100644 --- a/packages/aws-cdk-lib/core/lib/stack.ts +++ b/packages/aws-cdk-lib/core/lib/stack.ts @@ -1726,7 +1726,7 @@ import { Names } from './names'; import { Reference } from './reference'; import { IResolvable } from './resolvable'; import { DefaultStackSynthesizer, IStackSynthesizer, ISynthesisSession, LegacyStackSynthesizer, BOOTSTRAP_QUALIFIER_CONTEXT, isReusableStackSynthesizer } from './stack-synthesizers'; -import { StringSpecializer } from './stack-synthesizers/_shared'; +import { StringSpecializer } from './helpers-internal/string-specializer'; import { Stage } from './stage'; import { ITaggable, TagManager } from './tag-manager'; import { Token, Tokenization } from './token'; diff --git a/packages/aws-cdk-lib/core/test/helpers-internal/string-specializer.test.ts b/packages/aws-cdk-lib/core/test/helpers-internal/string-specializer.test.ts new file mode 100644 index 0000000000000..382b3d268b148 --- /dev/null +++ b/packages/aws-cdk-lib/core/test/helpers-internal/string-specializer.test.ts @@ -0,0 +1,15 @@ +import { Aws } from '../../lib'; +import { translateAssetTokenToCfnToken, translateCfnTokenToAssetToken } from '../../lib/helpers-internal'; + +describe('translations between token kinds', () => { + const CfnTokenArn = `arn:${Aws.PARTITION}:resource:${Aws.REGION}:${Aws.ACCOUNT_ID}:name`; + const AssetTokenArn = 'arn:${AWS::Partition}:resource:${AWS::Region}:${AWS::AccountId}:name'; + + test('translateAssetTokenToCfnToken', () => { + expect(translateAssetTokenToCfnToken(AssetTokenArn)).toEqual(CfnTokenArn); + }); + + test('translateCfnTokenToAssetToken', () => { + expect(translateCfnTokenToAssetToken(CfnTokenArn)).toEqual(AssetTokenArn); + }); +}); \ No newline at end of file diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index b7e44f2ee4a00..c75c09543aec3 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -118,8 +118,8 @@ "yaml" ], "dependencies": { - "@aws-cdk/asset-awscli-v1": "^2.2.176", - "@aws-cdk/asset-node-proxy-agent-v5": "^2.0.147", + "@aws-cdk/asset-awscli-v1": "^2.2.177", + "@aws-cdk/asset-node-proxy-agent-v5": "^2.0.148", "@aws-cdk/asset-kubectl-v20": "^2.1.1", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", @@ -141,9 +141,9 @@ "@types/jest": "^29.5.1", "@types/lodash": "^4.14.194", "@types/punycode": "^2.1.0", - "aws-sdk": "^2.1378.0", + "aws-sdk": "^2.1379.0", "aws-sdk-mock": "5.6.0", - "cdk8s": "^2.7.65", + "cdk8s": "^2.7.68", "constructs": "^10.0.0", "delay": "5.0.0", "esbuild": "^0.17.19", diff --git a/packages/aws-cdk/THIRD_PARTY_LICENSES b/packages/aws-cdk/THIRD_PARTY_LICENSES index 0ec8bbc711de6..a66cd65af927e 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.1378.0 - https://www.npmjs.com/package/aws-sdk/v/2.1378.0 | Apache-2.0 +** aws-sdk@2.1379.0 - https://www.npmjs.com/package/aws-sdk/v/2.1379.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/sdk-provider.ts b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts index e77fefa61083b..9eeebb0347c8d 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts @@ -174,6 +174,7 @@ export class SdkProvider { environment: cxapi.Environment, mode: Mode, options?: CredentialsOptions, + quiet = false, ): Promise { const env = await this.resolveEnvironment(environment); @@ -213,7 +214,8 @@ export class SdkProvider { // but if we can't then let's just try with available credentials anyway. if (baseCreds.source === 'correctDefault' || baseCreds.source === 'plugin') { debug(e.message); - warning(`${fmtObtainedCredentials(baseCreds)} could not be used to assume '${options.assumeRoleArn}', but are for the right account. Proceeding anyway.`); + const logger = quiet ? debug : warning; + logger(`${fmtObtainedCredentials(baseCreds)} could not be used to assume '${options.assumeRoleArn}', but are for the right account. Proceeding anyway.`); return { sdk: new SDK(baseCreds.credentials, env.region, this.sdkOptions), didAssumeRole: false }; } diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index 8e56480e9f4c5..7cf0ccc6b42f1 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -17,7 +17,7 @@ import { CloudWatchLogEventMonitor } from './api/logs/logs-monitor'; import { StackActivityProgress } from './api/util/cloudformation/stack-activity-monitor'; import { printSecurityDiff, printStackDiff, RequireApproval } from './diff'; import { ResourceImporter } from './import'; -import { data, debug, error, highlight, print, success, warning } from './logging'; +import { data, debug, error, highlight, print, success, warning, withCorkedLogging } from './logging'; import { deserializeStructure, serializeStructure } from './serialize'; import { Configuration, PROJECT_CONFIG } from './settings'; import { numberFromBool, partition } from './util'; @@ -238,23 +238,24 @@ export class CdkToolkit { if (requireApproval !== RequireApproval.Never) { const currentTemplate = await this.props.deployments.readCurrentTemplate(stack); if (printSecurityDiff(currentTemplate, stack, requireApproval)) { - - // only talk to user if STDIN is a terminal (otherwise, fail) - if (!process.stdin.isTTY) { - throw new Error( - '"--require-approval" is enabled and stack includes security-sensitive updates, ' + - 'but terminal (TTY) is not attached so we are unable to get a confirmation from the user'); - } - - // only talk to user if concurrency is 1 (otherwise, fail) - if (concurrency > 1) { - throw new Error( - '"--require-approval" is enabled and stack includes security-sensitive updates, ' + - 'but concurrency is greater than 1 so we are unable to get a confirmation from the user'); - } - - const confirmed = await promptly.confirm('Do you wish to deploy these changes (y/n)?'); - if (!confirmed) { throw new Error('Aborted by user'); } + await withCorkedLogging(async () => { + // only talk to user if STDIN is a terminal (otherwise, fail) + if (!process.stdin.isTTY) { + throw new Error( + '"--require-approval" is enabled and stack includes security-sensitive updates, ' + + 'but terminal (TTY) is not attached so we are unable to get a confirmation from the user'); + } + + // only talk to user if concurrency is 1 (otherwise, fail) + if (concurrency > 1) { + throw new Error( + '"--require-approval" is enabled and stack includes security-sensitive updates, ' + + 'but concurrency is greater than 1 so we are unable to get a confirmation from the user'); + } + + const confirmed = await promptly.confirm('Do you wish to deploy these changes (y/n)?'); + if (!confirmed) { throw new Error('Aborted by user'); } + }); } } diff --git a/packages/aws-cdk/lib/init-templates/app/typescript/package.json b/packages/aws-cdk/lib/init-templates/app/typescript/package.json index b4afb2231879e..49842ea1629ec 100644 --- a/packages/aws-cdk/lib/init-templates/app/typescript/package.json +++ b/packages/aws-cdk/lib/init-templates/app/typescript/package.json @@ -12,7 +12,7 @@ }, "devDependencies": { "@types/jest": "^29.5.1", - "@types/node": "20.1.5", + "@types/node": "20.1.7", "jest": "^29.5.0", "ts-jest": "^29.1.0", "aws-cdk": "%cdk-version%", diff --git a/packages/aws-cdk/lib/init-templates/lib/typescript/package.json b/packages/aws-cdk/lib/init-templates/lib/typescript/package.json index 7a2f9e1746cfc..5833efc79b7a2 100644 --- a/packages/aws-cdk/lib/init-templates/lib/typescript/package.json +++ b/packages/aws-cdk/lib/init-templates/lib/typescript/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "@types/jest": "^29.5.1", - "@types/node": "20.1.5", + "@types/node": "20.1.7", "aws-cdk-lib": "%cdk-version%", "constructs": "%constructs-version%", "jest": "^29.5.0", diff --git a/packages/aws-cdk/lib/init-templates/sample-app/typescript/package.json b/packages/aws-cdk/lib/init-templates/sample-app/typescript/package.json index 2c598f238645a..9ee0e75f5ebf4 100644 --- a/packages/aws-cdk/lib/init-templates/sample-app/typescript/package.json +++ b/packages/aws-cdk/lib/init-templates/sample-app/typescript/package.json @@ -12,7 +12,7 @@ }, "devDependencies": { "@types/jest": "^29.5.1", - "@types/node": "20.1.5", + "@types/node": "20.1.7", "jest": "^29.5.0", "ts-jest": "^29.1.0", "aws-cdk": "%cdk-version%", diff --git a/packages/aws-cdk/lib/logging.ts b/packages/aws-cdk/lib/logging.ts index ceade56caed6d..50f739f185216 100644 --- a/packages/aws-cdk/lib/logging.ts +++ b/packages/aws-cdk/lib/logging.ts @@ -7,6 +7,34 @@ const { stdout, stderr } = process; type WritableFactory = () => Writable; +export async function withCorkedLogging(block: () => Promise): Promise { + corkLogging(); + try { + return await block(); + } finally { + uncorkLogging(); + } +} + +let CORK_COUNTER = 0; +const logBuffer: [Writable, string][] = []; + +function corked() { + return CORK_COUNTER !== 0; +} + +function corkLogging() { + CORK_COUNTER += 1; +} + +function uncorkLogging() { + CORK_COUNTER -= 1; + if (!corked()) { + logBuffer.forEach(([stream, str]) => stream.write(str + '\n')); + logBuffer.splice(0); + } +} + const logger = (stream: Writable | WritableFactory, styles?: StyleFn[], timestamp?: boolean) => (fmt: string, ...args: unknown[]) => { const ts = timestamp ? `[${formatTime(new Date())}] ` : ''; @@ -15,11 +43,19 @@ const logger = (stream: Writable | WritableFactory, styles?: StyleFn[], timestam str = styles.reduce((a, style) => style(a), str); } - const realStream = typeof stream === 'function' ? stream() : stream; + + // Logger is currently corked, so we store the message to be printed + // later when we are uncorked. + if (corked()) { + logBuffer.push([realStream, str]); + return; + } + realStream.write(str + '\n'); }; + function formatTime(d: Date) { return `${lpad(d.getHours(), 2)}:${lpad(d.getMinutes(), 2)}:${lpad(d.getSeconds(), 2)}`; diff --git a/packages/aws-cdk/lib/util/asset-publishing.ts b/packages/aws-cdk/lib/util/asset-publishing.ts index c94c9bab94a94..7b4c4f943d9ff 100644 --- a/packages/aws-cdk/lib/util/asset-publishing.ts +++ b/packages/aws-cdk/lib/util/asset-publishing.ts @@ -169,6 +169,7 @@ export class PublishingAws implements cdk_assets.IAws { env, // region, name, account assumeRuleArn: options.assumeRoleArn, assumeRoleExternalId: options.assumeRoleExternalId, + quiet: options.quiet, }); const maybeSdk = this.sdkCache.get(cacheKey); @@ -179,7 +180,7 @@ export class PublishingAws implements cdk_assets.IAws { const sdk = (await this.aws.forEnvironment(env, Mode.ForWriting, { assumeRoleArn: options.assumeRoleArn, assumeRoleExternalId: options.assumeRoleExternalId, - })).sdk; + }, options.quiet)).sdk; this.sdkCache.set(cacheKey, sdk); return sdk; diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 3393c560971fa..4f9690ef2111f 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -99,7 +99,7 @@ "@aws-cdk/region-info": "0.0.0", "@jsii/check-node": "1.81.0", "archiver": "^5.3.1", - "aws-sdk": "^2.1378.0", + "aws-sdk": "^2.1379.0", "camelcase": "^6.3.0", "cdk-assets": "0.0.0", "chokidar": "^3.5.3", diff --git a/packages/cdk-assets/lib/aws.ts b/packages/cdk-assets/lib/aws.ts index 02bb67d41916c..4d9e731692d4e 100644 --- a/packages/cdk-assets/lib/aws.ts +++ b/packages/cdk-assets/lib/aws.ts @@ -18,6 +18,7 @@ export interface ClientOptions { region?: string; assumeRoleArn?: string; assumeRoleExternalId?: string; + quiet?: boolean; } /** diff --git a/packages/cdk-assets/lib/private/handlers/container-images.ts b/packages/cdk-assets/lib/private/handlers/container-images.ts index 0537c788970c9..670c813dd8b20 100644 --- a/packages/cdk-assets/lib/private/handlers/container-images.ts +++ b/packages/cdk-assets/lib/private/handlers/container-images.ts @@ -46,8 +46,13 @@ export class ContainerImageAssetHandler implements IAssetHandler { } public async isPublished(): Promise { - const initOnce = await this.initOnce(); - return initOnce.destinationAlreadyExists; + try { + const initOnce = await this.initOnce({ quiet: true }); + return initOnce.destinationAlreadyExists; + } catch (e: any) { + this.host.emitMessage(EventType.DEBUG, `${e.message}`); + } + return false; } public async publish(): Promise { @@ -68,13 +73,16 @@ export class ContainerImageAssetHandler implements IAssetHandler { await dockerForPushing.push(initOnce.imageUri); } - private async initOnce(): Promise { + private async initOnce(options: { quiet?: boolean } = {}): Promise { if (this.init) { return this.init; } const destination = await replaceAwsPlaceholders(this.asset.destination, this.host.aws); - const ecr = await this.host.aws.ecrClient(destination); + const ecr = await this.host.aws.ecrClient({ + ...destination, + quiet: options.quiet, + }); const account = async () => (await this.host.aws.discoverCurrentAccount())?.accountId; const repoUri = await repositoryUri(ecr, destination.repositoryName); diff --git a/packages/cdk-assets/lib/private/handlers/files.ts b/packages/cdk-assets/lib/private/handlers/files.ts index edc2addd61ada..fc538a82c95d0 100644 --- a/packages/cdk-assets/lib/private/handlers/files.ts +++ b/packages/cdk-assets/lib/private/handlers/files.ts @@ -33,7 +33,10 @@ export class FileAssetHandler implements IAssetHandler { const destination = await replaceAwsPlaceholders(this.asset.destination, this.host.aws); const s3Url = `s3://${destination.bucketName}/${destination.objectKey}`; try { - const s3 = await this.host.aws.s3Client(destination); + const s3 = await this.host.aws.s3Client({ + ...destination, + quiet: true, + }); this.host.emitMessage(EventType.CHECK, `Check ${s3Url}`); if (await objectExists(s3, destination.bucketName, destination.objectKey)) { diff --git a/packages/cdk-assets/lib/publishing.ts b/packages/cdk-assets/lib/publishing.ts index c35a0254e55ed..9e38308cd7e66 100644 --- a/packages/cdk-assets/lib/publishing.ts +++ b/packages/cdk-assets/lib/publishing.ts @@ -82,9 +82,6 @@ export class AssetPublishing implements IPublishProgress { private readonly publishInParallel: boolean; private readonly buildAssets: boolean; private readonly publishAssets: boolean; - private readonly startMessagePrefix: string; - private readonly successMessagePrefix: string; - private readonly errorMessagePrefix: string; private readonly handlerCache = new Map(); constructor(private readonly manifest: AssetManifest, private readonly options: AssetPublishingOptions) { @@ -94,34 +91,6 @@ export class AssetPublishing implements IPublishProgress { this.buildAssets = options.buildAssets ?? true; this.publishAssets = options.publishAssets ?? true; - const getMessages = () => { - if (this.buildAssets && this.publishAssets) { - return { - startMessagePrefix: 'Building and publishing', - successMessagePrefix: 'Built and published', - errorMessagePrefix: 'Error building and publishing', - }; - } else if (this.buildAssets) { - return { - startMessagePrefix: 'Building', - successMessagePrefix: 'Built', - errorMessagePrefix: 'Error building', - }; - } else { - return { - startMessagePrefix: 'Publishing', - successMessagePrefix: 'Published', - errorMessagePrefix: 'Error publishing', - }; - } - }; - - const messages = getMessages(); - - this.startMessagePrefix = messages.startMessagePrefix; - this.successMessagePrefix = messages.successMessagePrefix; - this.errorMessagePrefix = messages.errorMessagePrefix; - const self = this; this.handlerHost = { aws: this.options.aws, @@ -146,7 +115,7 @@ export class AssetPublishing implements IPublishProgress { } if ((this.options.throwOnError ?? true) && this.failures.length > 0) { - throw new Error(`${this.errorMessagePrefix}: ${this.failures.map(e => e.error.message)}`); + throw new Error(`Error publishing: ${this.failures.map(e => e.error.message)}`); } } @@ -155,7 +124,7 @@ export class AssetPublishing implements IPublishProgress { */ public async buildEntry(asset: IManifestEntry) { try { - if (this.progressEvent(EventType.START, `${this.startMessagePrefix} ${asset.id}`)) { return false; } + if (this.progressEvent(EventType.START, `Building ${asset.id}`)) { return false; } const handler = this.assetHandler(asset); await handler.build(); @@ -163,6 +132,9 @@ export class AssetPublishing implements IPublishProgress { if (this.aborted) { throw new Error('Aborted'); } + + this.completedOperations++; + if (this.progressEvent(EventType.SUCCESS, `Built ${asset.id}`)) { return false; } } catch (e: any) { this.failures.push({ asset, error: e }); this.completedOperations++; @@ -177,7 +149,7 @@ export class AssetPublishing implements IPublishProgress { */ public async publishEntry(asset: IManifestEntry) { try { - if (this.progressEvent(EventType.UPLOAD, `${this.startMessagePrefix} ${asset.id}`)) { return false; } + if (this.progressEvent(EventType.START, `Publishing ${asset.id}`)) { return false; } const handler = this.assetHandler(asset); await handler.publish(); @@ -187,7 +159,7 @@ export class AssetPublishing implements IPublishProgress { } this.completedOperations++; - if (this.progressEvent(EventType.SUCCESS, `${this.successMessagePrefix} ${asset.id}`)) { return false; } + if (this.progressEvent(EventType.SUCCESS, `Published ${asset.id}`)) { return false; } } catch (e: any) { this.failures.push({ asset, error: e }); this.completedOperations++; @@ -212,7 +184,7 @@ export class AssetPublishing implements IPublishProgress { */ private async publishAsset(asset: IManifestEntry) { try { - if (this.progressEvent(EventType.START, `${this.startMessagePrefix} ${asset.id}`)) { return false; } + if (this.progressEvent(EventType.START, `Publishing ${asset.id}`)) { return false; } const handler = this.assetHandler(asset); @@ -229,7 +201,7 @@ export class AssetPublishing implements IPublishProgress { } this.completedOperations++; - if (this.progressEvent(EventType.SUCCESS, `${this.successMessagePrefix} ${asset.id}`)) { return false; } + if (this.progressEvent(EventType.SUCCESS, `Published ${asset.id}`)) { return false; } } catch (e: any) { this.failures.push({ asset, error: e }); this.completedOperations++; diff --git a/packages/cdk-assets/package.json b/packages/cdk-assets/package.json index 84671df8d769b..9d1fdc50fbe4b 100644 --- a/packages/cdk-assets/package.json +++ b/packages/cdk-assets/package.json @@ -46,7 +46,7 @@ "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "archiver": "^5.3.1", - "aws-sdk": "^2.1378.0", + "aws-sdk": "^2.1379.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 688828a68c021..18b713947c365 100644 --- a/packages/cdk-assets/test/docker-images.test.ts +++ b/packages/cdk-assets/test/docker-images.test.ts @@ -247,7 +247,7 @@ describe('with a complete manifest', () => { test('Displays an error if the ECR repository cannot be found', async () => { aws.mockEcr.describeImages = mockedApiFailure('RepositoryNotFoundException', 'Repository not Found'); - await expect(pub.publish()).rejects.toThrow('Error building and publishing: Repository not Found'); + await expect(pub.publish()).rejects.toThrow('Error publishing: Repository not Found'); }); test('successful run does not need to query account ID', async () => { diff --git a/tools/@aws-cdk/node-bundle/package.json b/tools/@aws-cdk/node-bundle/package.json index 64cceeefd7082..78499e9d6a187 100644 --- a/tools/@aws-cdk/node-bundle/package.json +++ b/tools/@aws-cdk/node-bundle/package.json @@ -38,7 +38,7 @@ "jest": "^29", "jest-junit": "^13", "npm-check-updates": "^16", - "projen": "^0.71.57", + "projen": "^0.71.60", "standard-version": "^9", "ts-jest": "^29", "typescript": "^4.5.5" diff --git a/version.v2.json b/version.v2.json index 5bfe47ffc5615..123906fe7ce30 100644 --- a/version.v2.json +++ b/version.v2.json @@ -1,4 +1,4 @@ { - "version": "2.79.1", - "alphaVersion": "2.79.1-alpha.0" + "version": "2.80.0", + "alphaVersion": "2.80.0-alpha.0" } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 798388c8dabfb..78c7f4519ba55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,25 +35,25 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@aws-cdk/asset-awscli-v1@^2.2.176": - version "2.2.176" - resolved "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.176.tgz#f9347ca6721644d6e5083567356dc50438bcf105" - integrity sha512-rAJ2KO0SK9uC9Wv6vAS1V2Eg88dK9nlojhm/HrEBWDsIPWcxvvT9EACkElrNXK9X6CF10i6W4UApCROERF1qjg== +"@aws-cdk/asset-awscli-v1@^2.2.177": + version "2.2.177" + resolved "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.177.tgz#c2e29a97b06c9c4a15a56ea228799ad12aac5097" + integrity sha512-jNskqpWyDbTeWnIsyMwrtanz/MltBm1gMHOHiFS/4CBBHYfDT3h7Go5uruNSW32W1gWDej58PGJ+26E4g1gLhA== "@aws-cdk/asset-kubectl-v20@^2.1.1": version "2.1.1" resolved "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.1.tgz#d01c1efb867fb7f2cfd8c8b230b8eae16447e156" integrity sha512-U1ntiX8XiMRRRH5J1IdC+1t5CE89015cwyt5U63Cpk0GnMlN5+h9WsWMlKlPXZR4rdq/m806JRlBMRpBUB2Dhw== -"@aws-cdk/asset-node-proxy-agent-v5@^2.0.147": - version "2.0.147" - resolved "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v5/-/asset-node-proxy-agent-v5-2.0.147.tgz#0b69a8ee8f077a8e5b3ca1f3f32c71de7ddad1b6" - integrity sha512-CnRj0KQwF6bpoGl2kABRqSdkf9gyj6IdEcuReaWbExPGYKKk+Pc0aGr32SwcPftP2Z8754g3Msw+tBhfE3IYdg== +"@aws-cdk/asset-node-proxy-agent-v5@^2.0.148": + version "2.0.148" + resolved "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v5/-/asset-node-proxy-agent-v5-2.0.148.tgz#b6ac2f6c9a725db14dc7592d449675c13eb0d86e" + integrity sha512-WnBr+sA5MCPyRuiL9u2CH0Vnn6Peq0TYEXlk0FCqdViVFUCz4/hplEn0mcFV5/iqOkx2OFXOktSRsc8grRZCLw== -"@aws-cdk/lambda-layer-kubectl-v24@^2.0.194": - version "2.0.194" - resolved "https://registry.npmjs.org/@aws-cdk/lambda-layer-kubectl-v24/-/lambda-layer-kubectl-v24-2.0.194.tgz#d7e0f0dad32050804aa24865b07526523c5d942c" - integrity sha512-ENWueG5ejX1kRyQrnb0Yf0JGtxVcUxsRyFt3NODpzUtBtqzysNtW5W3YUFMCdKiDPbRzMPWngYvolU8zb89DWg== +"@aws-cdk/lambda-layer-kubectl-v24@^2.0.195": + version "2.0.195" + resolved "https://registry.npmjs.org/@aws-cdk/lambda-layer-kubectl-v24/-/lambda-layer-kubectl-v24-2.0.195.tgz#750d627032388ac896e430efd80f1c98150f8fab" + integrity sha512-PA/z81pTWceoLVk8R+YuiRwxxNJA5KA7GWHVCGTaMuIppY17ZJMEp1ldZbtsLxxpJcoJmiDuDWndrVqSzQazzQ== "@babel/code-frame@7.12.11": version "7.12.11" @@ -1267,11 +1267,14 @@ json-parse-even-better-errors "^2.3.1" "@npmcli/package-json@^3.0.0": - version "3.0.0" - resolved "https://registry.npmjs.org/@npmcli/package-json/-/package-json-3.0.0.tgz#c9219a197e1be8dbf43c4ef8767a72277c0533b6" - integrity sha512-NnuPuM97xfiCpbTEJYtEuKz6CFbpUHtaT0+5via5pQeI25omvQDFbp1GcGJ/c4zvL/WX0qbde6YiLgfZbWFgvg== + version "3.1.0" + resolved "https://registry.npmjs.org/@npmcli/package-json/-/package-json-3.1.0.tgz#d9eb34083be4275520f3844d17fc74926d47cae1" + integrity sha512-qNPy6Yf9ruFST99xcrl5EWAvrb7qFrwgVbwdzcTJlIgxbArKOq5e/bgZ6rTL1X9hDgAdPbvL8RWx/OTLSB0ToA== dependencies: + glob "^10.2.2" json-parse-even-better-errors "^3.0.0" + normalize-package-data "^5.0.0" + npm-normalize-package-bin "^3.0.1" "@npmcli/promise-spawn@^3.0.0": version "3.0.0" @@ -1594,9 +1597,9 @@ universal-user-agent "^6.0.0" "@octokit/request@^6.0.0": - version "6.2.3" - resolved "https://registry.npmjs.org/@octokit/request/-/request-6.2.3.tgz#76d5d6d44da5c8d406620a4c285d280ae310bdb4" - integrity sha512-TNAodj5yNzrrZ/VxP+H5HiYaZep0H3GU0O7PaF+fhDrt8FPrnkei9Aal/txsN/1P7V3CPiThG0tIvpPDYUsyAA== + version "6.2.4" + resolved "https://registry.npmjs.org/@octokit/request/-/request-6.2.4.tgz#b00a7185865c72bdd432e63168b1e900953ded0c" + integrity sha512-at92SYQstwh7HH6+Kf3bFMnHrle7aIrC0r5rTP+Bb30118B6j1vI2/M4walh6qcQgfuLIKs8NUO5CytHTnUI3A== dependencies: "@octokit/endpoint" "^7.0.0" "@octokit/request-error" "^3.0.0" @@ -2033,9 +2036,9 @@ integrity sha512-uv53RrNdhbkV/3VmVCtfImfYCWC3GTTRn3R11Whni3EJ+gb178tkZBVNj2edLY5CMrB749dQi+SJkg87jsN8UQ== "@types/node@*": - version "20.1.5" - resolved "https://registry.npmjs.org/@types/node/-/node-20.1.5.tgz#e94b604c67fc408f215fcbf3bd84d4743bf7f710" - integrity sha512-IvGD1CD/nego63ySR7vrAKEX3AJTcmrAN2kn+/sDNLi1Ff5kBzDeEdqWDplK+0HAEoLYej137Sk0cUU8OLOlMg== + version "20.1.7" + resolved "https://registry.npmjs.org/@types/node/-/node-20.1.7.tgz#ce10c802f7731909d0a44ac9888e8b3a9125eb62" + integrity sha512-WCuw/o4GSwDGMoonES8rcvwsig77dGCMbZDrZr2x4ZZiNW4P/gcoZXe/0twgtobcTkmg9TuKflxYL/DuwDyJzg== "@types/node@18.11.19": version "18.11.19" @@ -2048,9 +2051,9 @@ integrity sha512-OuJi8bIng4wYHHA3YpKauL58dZrPxro3d0tabPHyiNF8rKfGKuVfr83oFlPLmKri1cX+Z3cJP39GXmnqkP11Gw== "@types/node@^16.9.2": - version "16.18.30" - resolved "https://registry.npmjs.org/@types/node/-/node-16.18.30.tgz#4a2c426370712a10c630a55ba086c55c17ca54e0" - integrity sha512-Kmp/wBZk19Dn7uRiol8kF8agnf8m0+TU9qIwyfPmXglVxMlmiIz0VQSMw5oFgwhmD2aKTlfBIO5FtsVj3y7hKQ== + version "16.18.31" + resolved "https://registry.npmjs.org/@types/node/-/node-16.18.31.tgz#7de39c2b9363f0d95b129cc969fcbf98e870251c" + integrity sha512-KPXltf4z4g517OlVJO9XQ2357CYw7fvuJ3ZuBynjXC5Jos9i+K7LvFb7bUIwtJXSZj0vTp9Q6NJBSQpkwwO8Zw== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -2758,10 +2761,10 @@ aws-sdk-mock@5.6.0: sinon "^11.1.1" traverse "^0.6.6" -aws-sdk@^2.1378.0, aws-sdk@^2.928.0: - version "2.1378.0" - resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1378.0.tgz#a6ba9785bdc2677864b77ff757e9dbca0e65e7d3" - integrity sha512-9ZY11zkc3nMSejrrj08NdL+7hgiY7GZE3Sk7eVhH4VAJeoNqAMAyxc61Sg9yi83Y1i5rRWIcIgX9CYwenokyWQ== +aws-sdk@^2.1379.0, aws-sdk@^2.928.0: + version "2.1379.0" + resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1379.0.tgz#2d5609a37287d25edda4395fbb89306939f98890" + integrity sha512-kziOtAtJxdgYJwhzY+uhNi/AGPrDEMHd0dEz46YR1AB5bVqjS9/SjOZHemB88QfpW11IVB/FoiIusXlGEvgq9Q== dependencies: buffer "4.9.2" events "1.1.1" @@ -3074,9 +3077,9 @@ cacache@^16.0.0, cacache@^16.1.0, cacache@^16.1.3: unique-filename "^2.0.0" cacache@^17.0.0, cacache@^17.0.4: - version "17.1.0" - resolved "https://registry.npmjs.org/cacache/-/cacache-17.1.0.tgz#b7286ef941dafe55b461cdcdceda71cacc1eb98d" - integrity sha512-hXpFU+Z3AfVmNuiLve1qxWHMq0RSIt5gjCKAHi/M6DktwFwDdAXAtunl1i4WSKaaVcU9IsRvXFg42jTHigcC6Q== + version "17.1.2" + resolved "https://registry.npmjs.org/cacache/-/cacache-17.1.2.tgz#57ce9b79d300373f7266f2f1e4e1718fe09e84b4" + integrity sha512-VcRDUtZd9r7yfGDpdm3dBDBSQbLd19IqWs9q1tuB9g6kmxYLwIjfLngRKMCfDHxReuf0SBclRuYn66Xds7jzUQ== dependencies: "@npmcli/fs" "^3.1.0" fs-minipass "^3.0.0" @@ -3157,19 +3160,19 @@ camelcase@^7.0.1: integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw== caniuse-lite@^1.0.30001449: - version "1.0.30001487" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001487.tgz#d882d1a34d89c11aea53b8cdc791931bdab5fe1b" - integrity sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA== + version "1.0.30001488" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001488.tgz#d19d7b6e913afae3e98f023db97c19e9ddc5e91f" + integrity sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ== case@1.6.3, case@^1.6.3: version "1.6.3" resolved "https://registry.npmjs.org/case/-/case-1.6.3.tgz#0a4386e3e9825351ca2e6216c60467ff5f1ea1c9" integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ== -cdk-generate-synthetic-examples@^0.1.243: - version "0.1.243" - resolved "https://registry.npmjs.org/cdk-generate-synthetic-examples/-/cdk-generate-synthetic-examples-0.1.243.tgz#969af9e5031e2f8e60e82d619cff1be23315cdb1" - integrity sha512-Z6tKWU4CUrJBFjKihtA9Gw+06L4oUB0kyvQdxfsVr+cY/uReFTapWL6VuY8VK5rcGE66sLX6KWzOInm+9FWlsg== +cdk-generate-synthetic-examples@^0.1.244: + version "0.1.244" + resolved "https://registry.npmjs.org/cdk-generate-synthetic-examples/-/cdk-generate-synthetic-examples-0.1.244.tgz#4779aea46f105678aaa7579f17d83b6655e0dfaa" + integrity sha512-goVWAgqB14uFZ/VEUYBsFFdK4pnSBfpi8w5020p2FCW+35nfjb/x3CdDqdV48YxcXby6ijhcXk5L0txCzXkNCg== dependencies: "@jsii/spec" "^1.81.0" fs-extra "^10.1.0" @@ -3178,17 +3181,17 @@ cdk-generate-synthetic-examples@^0.1.243: jsii-rosetta "^1.81.0" yargs "^17.7.2" -cdk8s-plus-24@2.7.30: - version "2.7.30" - resolved "https://registry.npmjs.org/cdk8s-plus-24/-/cdk8s-plus-24-2.7.30.tgz#6d3d53e642faebea273002d6c1e326df2b77ccae" - integrity sha512-EdhCHRJR7S3rg9l/HUibz9Ha/hPU6cA+JmMUcw6ec7ErURZ7FwzDEkl+HB9JJFwwGCblWNEtfW9/p4gVk/msPQ== +cdk8s-plus-24@2.7.31: + version "2.7.31" + resolved "https://registry.npmjs.org/cdk8s-plus-24/-/cdk8s-plus-24-2.7.31.tgz#c5547b916af4a840d9373c03e86cda9fca81fbf3" + integrity sha512-3bs2WUHZCz5nPSoLtfdfdRUzAlMlQadLS4CjJd/V3h4PkFKIq+rSXT1HvCUoRWpORF177/PlbKEz52PbsVB+Fw== dependencies: minimatch "^3.1.2" -cdk8s@^2.7.65: - version "2.7.65" - resolved "https://registry.npmjs.org/cdk8s/-/cdk8s-2.7.65.tgz#b94b726f767da5b53188e7980549440574c44571" - integrity sha512-tg3PKENLKPNvcUuOCVFlBGscv0EBdjfL+OrYoGSFNuC8RwcPcPqKOBUaP4wrzR3SN2gcVBN4K36sVQ4ObPce5Q== +cdk8s@^2.7.68: + version "2.7.68" + resolved "https://registry.npmjs.org/cdk8s/-/cdk8s-2.7.68.tgz#05b0a37bd2f380005af2058ceebc313a06b93eba" + integrity sha512-bwtkyAzfhqTST1OhFTs7MjMD1CH+NmoESGO5sq6wI3AQsgrAhvYX8LKHyvn7YGNmPcpC2wz+dv1sPdmIJAjyvg== dependencies: fast-json-patch "^3.1.1" follow-redirects "^1.15.2" @@ -3613,9 +3616,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.2.25" - resolved "https://registry.npmjs.org/constructs/-/constructs-10.2.25.tgz#9a1463caf7d420e737f3bc1c0cebbf832ff04cf9" - integrity sha512-hNhL7Lq+MZ/6QGaa5BWsuBMKmj30Wr81AjJaQfyGN5W9sZmJXAoI/ZEgRxTHCqkB6o5QB0bnlrZc2mwwnqFsSg== + version "10.2.26" + resolved "https://registry.npmjs.org/constructs/-/constructs-10.2.26.tgz#dad719633cc143f9c8d4224815dab01a273cbd4e" + integrity sha512-MfxcFDMOx0MTIs6CqFlpdEMFF6y8cQCFrl3o1ArDJavToWdzeNKuDddMV1Ry3yWYQTpQjyfoue6fwnP8ukD49g== conventional-changelog-angular@5.0.12: version "5.0.12" @@ -4322,9 +4325,9 @@ ejs@^3.1.7: jake "^10.8.5" electron-to-chromium@^1.4.284: - version "1.4.396" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.396.tgz#3d3664eb58d86376fbe2fece3705f68ca197205c" - integrity sha512-pqKTdqp/c5vsrc0xUPYXTDBo9ixZuGY8es4ZOjjd6HD6bFYbu5QA09VoW3fkY4LF1T0zYk86lN6bZnNlBuOpdQ== + version "1.4.397" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.397.tgz#82a7e26c657538d59bb713b97ac22f97ea3a90ea" + integrity sha512-jwnPxhh350Q/aMatQia31KAIQdhEsYS0fFZ0BQQlN9tfvOEwShu6ZNwI4kL/xBabjcB/nTy6lSt17kNIluJZ8Q== emittery@^0.13.1: version "0.13.1" @@ -5950,9 +5953,9 @@ ini@^3.0.0, ini@^3.0.1, ini@~3.0.0: integrity sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ== ini@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/ini/-/ini-4.1.0.tgz#3bca65a0ae224f07f8f8b3392d8c94a7f1bb007b" - integrity sha512-HLR38RSF2iulAzc3I/sma4CoYxQP844rPYCNfzGDOHqa/YqVlwuuZgBx6M50/X8dKgzk0cm1qRg3+47mK2N+cQ== + version "4.1.1" + resolved "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz#d95b3d843b1e906e56d6747d5447904ff50ce7a1" + integrity sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== init-package-json@3.0.2, init-package-json@^3.0.2: version "3.0.2" @@ -6119,9 +6122,9 @@ is-cidr@^4.0.2: cidr-regex "^3.1.1" is-core-module@^2.11.0, is-core-module@^2.5.0, is-core-module@^2.8.1: - version "2.12.0" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4" - integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ== + version "2.12.1" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" + integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== dependencies: has "^1.0.3" @@ -8464,7 +8467,7 @@ npm-normalize-package-bin@^2.0.0: resolved "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz#9447a1adaaf89d8ad0abe24c6c84ad614a675fff" integrity sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ== -npm-normalize-package-bin@^3.0.0: +npm-normalize-package-bin@^3.0.0, npm-normalize-package-bin@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz#25447e32a9a7de1f51362c61a559233b89947832" integrity sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ== @@ -9506,10 +9509,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.71.57: - version "0.71.57" - resolved "https://registry.npmjs.org/projen/-/projen-0.71.57.tgz#b8e90cea6cc7697168c4180873c2aec6cab188f7" - integrity sha512-A9EWJz1AX4HHxnFdWrLwkAmpHCPchZh8kYZviSVYGQ/AIC18ELrcF/tlbOIZ6w+TJ8NnCPe1oQg3U549Z4rrew== +projen@^0.71.60: + version "0.71.60" + resolved "https://registry.npmjs.org/projen/-/projen-0.71.60.tgz#4c3973b8a2754947eb36746c1e2f6e1f8299d18e" + integrity sha512-/up45q54xkLQdG09lEnPWF/Yzph2YKW9RmOO5rXkrVUUJTDmFSw8gogxbLR3MIWxBovDeHj7cUElvev1x+VMnw== dependencies: "@iarna/toml" "^2.2.5" case "^1.6.3" @@ -10863,9 +10866,9 @@ tar@6.1.11: yallist "^4.0.0" tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: - version "6.1.14" - resolved "https://registry.npmjs.org/tar/-/tar-6.1.14.tgz#e87926bec1cfe7c9e783a77a79f3e81c1cfa3b66" - integrity sha512-piERznXu0U7/pW7cdSn7hjqySIVTYT6F76icmFk7ptU7dDYlXTm5r9A6K04R2vU3olYgoKeo1Cg3eeu5nhftAw== + version "6.1.15" + resolved "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69" + integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -11252,9 +11255,9 @@ typescript@^3.9.10, typescript@^3.9.5, typescript@^3.9.7, typescript@~3.9.10: integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== typescript@next: - version "5.2.0-dev.20230516" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.2.0-dev.20230516.tgz#9dc4d02c031cdf311ddb6ee646be09d5f13de4c9" - integrity sha512-DGK8md4PQgA6QG9JnvC6LecNnBstc1h6zrg71isrlZTsRFVl3EsID6D2Exrh4ULbxynA61PE13M+uOmLFWOu4w== + version "5.2.0-dev.20230517" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.2.0-dev.20230517.tgz#6927c37887406ff250bc11f4256eb6136cc21475" + integrity sha512-mTT3ka5HTeudnNirocnOfr/0d2CEvZExrJpP8YXqAvdwlwjwNPJ4TqHHiL1y9lx3RfsUWu6HSX539ZxngUTqeA== typescript@~5.0.4: version "5.0.4"