diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 6ad382cc4cef9..3440978038512 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -1,6 +1,7 @@ --- name: "\U0001F41B Bug Report" about: Report a bug +title: "[module]: " labels: bug, needs-triage --- diff --git a/.github/ISSUE_TEMPLATE/doc.md b/.github/ISSUE_TEMPLATE/doc.md index 942e2baa8f7fb..e17cba209d061 100644 --- a/.github/ISSUE_TEMPLATE/doc.md +++ b/.github/ISSUE_TEMPLATE/doc.md @@ -1,6 +1,7 @@ --- name: "๐Ÿ“• Documentation Issue" about: Issue in the reference documentation or developer guide +title: "[module]: " labels: feature-request, documentation, needs-triage --- diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index c2812d3e6aea3..ab9b981d50b58 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -1,6 +1,7 @@ --- name: "\U0001F680 Feature Request" about: Request a new feature +title: "[module]: " labels: feature-request, needs-triage --- diff --git a/.github/ISSUE_TEMPLATE/general-issues.md b/.github/ISSUE_TEMPLATE/general-issues.md index edd2ef2798236..802f8b386ebdb 100644 --- a/.github/ISSUE_TEMPLATE/general-issues.md +++ b/.github/ISSUE_TEMPLATE/general-issues.md @@ -1,6 +1,7 @@ --- name: "\U00002753 General Issue" about: Create a new issue +title: "[module]: " labels: needs-triage --- diff --git a/.github/workflows/close-stale-issues.yml b/.github/workflows/close-stale-issues.yml index a92dd4b594ad9..df8e9f3d5ffb5 100644 --- a/.github/workflows/close-stale-issues.yml +++ b/.github/workflows/close-stale-issues.yml @@ -14,7 +14,7 @@ jobs: with: # Setting messages to an empty string will cause the automation to skip # that category - ancient-issue-message: This issue has not received any attention in 2 years. If you want to keep this issue open, please leave a comment below and auto-close will be canceled. + ancient-issue-message: This issue has not received any attention in 1 year. If you want to keep this issue open, please leave a comment below and auto-close will be canceled. stale-issue-message: This issue has not received a response in a while. If you want to keep this issue open, please leave a comment below and auto-close will be canceled. stale-pr-message: This PR has not received a response in a while. If you want to keep this issue open, please leave a comment below and auto-close will be canceled. @@ -32,7 +32,7 @@ jobs: # Issue timing days-before-stale: 7 days-before-close: 4 - days-before-ancient: 730 + days-before-ancient: 365 # If you don't want to mark a issue as being ancient based on a # threshold of "upvotes", you can set this here. An "upvote" is diff --git a/.github/workflows/issue-label-assign.yml b/.github/workflows/issue-label-assign.yml new file mode 100644 index 0000000000000..fa5512db3c158 --- /dev/null +++ b/.github/workflows/issue-label-assign.yml @@ -0,0 +1,171 @@ +name: "Set Issue Label and Assignee" +on: + issues: + types: [opened, edited] + pull_request: + types: [opened, edited] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: Naturalclar/issue-action@f229cda + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + title-or-body: 'title' + parameters: > + [ + {"keywords":["cli","command line"],"labels":["package/tools"],"assignees":["shivlaks"]}, + {"keywords":["alexa-ask","alexa ask"],"labels":["@aws-cdk/alexa-ask"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["app-delivery","app delivery"],"labels":["@aws-cdk/app-delivery"],"assignees":["skinny85"]}, + {"keywords":["assert"],"labels":["@aws-cdk/assert"],"assignees":["eladb"]}, + {"keywords":["assets"],"labels":["@aws-cdk/assets"],"assignees":["eladb"]}, + {"keywords":["accessanalyzer","access analyzer"],"labels":["@aws-cdk/aws-accessanalyzer"],"assignees":["rix0rrr"]}, + {"keywords":["amazonmq","amazon mq"],"labels":["@aws-cdk/aws-amazonmq"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["amplify"],"labels":["@aws-cdk/aws-amplify"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["apigateway","api gateway"],"labels":["@aws-cdk/aws-apigateway"],"assignees":["nija-at"]}, + {"keywords":["applicationautoscaling","application autoscaling"],"labels":["@aws-cdk/aws-applicationautoscaling"],"assignees":["NetaNir"]}, + {"keywords":["appmesh","app mesh"],"labels":["@aws-cdk/aws-appmesh"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["appstream","app stream"],"labels":["@aws-cdk/aws-appstream"],"assignees":["NetaNir"]}, + {"keywords":["appsync","app sync"],"labels":["@aws-cdk/aws-appsync"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["athena"],"labels":["@aws-cdk/aws-athena"],"assignees":["iliapolo"]}, + {"keywords":["autoscaling"],"labels":["@aws-cdk/aws-autoscaling"],"assignees":["NetaNir"]}, + {"keywords":["autoscaling-api","autoscaling api"],"labels":["@aws-cdk/aws-autoscaling-api"],"assignees":["NetaNir"]}, + {"keywords":["autoscaling-common","autoscaling common"],"labels":["@aws-cdk/aws-autoscaling-common"],"assignees":["NetaNir"]}, + {"keywords":["autoscaling-hooktargets","autoscaling hooktargets"],"labels":["@aws-cdk/aws-autoscaling-hooktargets"],"assignees":["NetaNir"]}, + {"keywords":["autoscalingplans","autoscaling plans"],"labels":["@aws-cdk/aws-autoscalingplans"],"assignees":["NetaNir"]}, + {"keywords":["backup"],"labels":["@aws-cdk/aws-backup"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["batch"],"labels":["@aws-cdk/aws-batch"],"assignees":["iliapolo"]}, + {"keywords":["budgets"],"labels":["@aws-cdk/aws-budgets"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["certificatemanager","certificate manager"],"labels":["@aws-cdk/aws-certificatemanager"],"assignees":["skinny85"]}, + {"keywords":["cloud9","cloud 9"],"labels":["@aws-cdk/aws-cloud9"],"assignees":["skinny85"]}, + {"keywords":["cloudformation"],"labels":["@aws-cdk/aws-cloudformation"],"assignees":["eladb"]}, + {"keywords":["cloudfront","cloud front"],"labels":["@aws-cdk/aws-cloudfront"],"assignees":["iliapolo"]}, + {"keywords":["cloudtrail","cloud trail"],"labels":["@aws-cdk/aws-cloudtrail"],"assignees":["rix0rrr"]}, + {"keywords":["cloudwatch","cloud watch"],"labels":["@aws-cdk/aws-cloudwatch"],"assignees":["rix0rrr"]}, + {"keywords":["cloudwatch-actions","cloudwatch actions"],"labels":["@aws-cdk/aws-cloudwatch-actions"],"assignees":["rix0rrr"]}, + {"keywords":["codebuild","code build"],"labels":["@aws-cdk/aws-codebuild"],"assignees":["skinny85"]}, + {"keywords":["codecommit","code commit"],"labels":["@aws-cdk/aws-codecommit"],"assignees":["skinny85"]}, + {"keywords":["codedeploy","code deploy"],"labels":["@aws-cdk/aws-codedeploy"],"assignees":["skinny85"]}, + {"keywords":["codepipeline","code pipeline"],"labels":["@aws-cdk/aws-codepipeline"],"assignees":["skinny85"]}, + {"keywords":["codepipeline-actions","codepipeline actions"],"labels":["@aws-cdk/aws-codepipeline-actions"],"assignees":["skinny85"]}, + {"keywords":["codestar"],"labels":["@aws-cdk/aws-codestar"],"assignees":["skinny85"]}, + {"keywords":["codestarconnections","codestar connections"],"labels":["@aws-cdk/aws-codestarconnections"],"assignees":["skinny85"]}, + {"keywords":["codestarnotifications","codestar notifications"],"labels":["@aws-cdk/aws-codestarnotifications"],"assignees":["skinny85"]}, + {"keywords":["cognito"],"labels":["@aws-cdk/aws-cognito"],"assignees":["nija-at"]}, + {"keywords":["config"],"labels":["@aws-cdk/aws-config"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["datapipeline","data pipeline"],"labels":["@aws-cdk/aws-datapipeline"],"assignees":["iliapolo"]}, + {"keywords":["dax"],"labels":["@aws-cdk/aws-dax"],"assignees":["RomainMuller"]}, + {"keywords":["directoryservice","directory service"],"labels":["@aws-cdk/aws-directoryservice"],"assignees":["NetaNir"]}, + {"keywords":["dlm"],"labels":["@aws-cdk/aws-dlm"],"assignees":["nija-at"]}, + {"keywords":["dms"],"labels":["@aws-cdk/aws-dms"],"assignees":["nija-at"]}, + {"keywords":["docdb","doc db"],"labels":["@aws-cdk/aws-docdb"],"assignees":["iliapolo"]}, + {"keywords":["dynamodb","dynamo db"],"labels":["@aws-cdk/aws-dynamodb"],"assignees":["RomainMuller"]}, + {"keywords":["dynamodb-global","dynamodb global"],"labels":["@aws-cdk/aws-dynamodb-global"],"assignees":["RomainMuller"]}, + {"keywords":["ec2"],"labels":["@aws-cdk/aws-ec2"],"assignees":["rix0rrr"]}, + {"keywords":["ecr"],"labels":["@aws-cdk/aws-ecr"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["ecr-assets","ecr assets"],"labels":["@aws-cdk/aws-ecr-assets"],"assignees":["eladb"]}, + {"keywords":["ecs"],"labels":["@aws-cdk/aws-ecs"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["ecs-patterns","ecs patterns"],"labels":["@aws-cdk/aws-ecs-patterns"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["efs"],"labels":["@aws-cdk/aws-efs"],"assignees":["rix0rrr"]}, + {"keywords":["eks"],"labels":["@aws-cdk/aws-eks"],"assignees":["eladb"]}, + {"keywords":["elasticache","elastic cache"],"labels":["@aws-cdk/aws-elasticache"],"assignees":["iliapolo"]}, + {"keywords":["elasticbeanstalk","beanstalk"],"labels":["@aws-cdk/aws-elasticbeanstalk"],"assignees":["skinny85"]}, + {"keywords":["elasticloadbalancing","elb"],"labels":["@aws-cdk/aws-elasticloadbalancing"],"assignees":["rix0rrr"]}, + {"keywords":["elasticloadbalancingv2","elbv2"],"labels":["@aws-cdk/aws-elasticloadbalancingv2"],"assignees":["rix0rrr"]}, + {"keywords":["elasticloadbalancingv2-targets","elasticloadbalancingv2 targets","elbv2 targets"],"labels":["@aws-cdk/aws-elasticloadbalancingv2-targets"],"assignees":["rix0rrr"]}, + {"keywords":["elasticsearch","elastic search"],"labels":["@aws-cdk/aws-elasticsearch"],"assignees":["iliapolo"]}, + {"keywords":["emr"],"labels":["@aws-cdk/aws-emr"],"assignees":["iliapolo"]}, + {"keywords":["events","eventbridge","cloudwatch events"],"labels":["@aws-cdk/aws-events"],"assignees":["rix0rrr"]}, + {"keywords":["events-targets","events targets"],"labels":["@aws-cdk/aws-events-targets"],"assignees":["rix0rrr"]}, + {"keywords":["fsx"],"labels":["@aws-cdk/aws-fsx"],"assignees":["rix0rrr"]}, + {"keywords":["gamelift","game lift"],"labels":["@aws-cdk/aws-gamelift"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["glue"],"labels":["@aws-cdk/aws-glue"],"assignees":["iliapolo"]}, + {"keywords":["greengrass","green grass"],"labels":["@aws-cdk/aws-greengrass"],"assignees":["shivlaks"]}, + {"keywords":["guardduty","guard duty"],"labels":["@aws-cdk/aws-guardduty"],"assignees":["rix0rrr"]}, + {"keywords":["iam"],"labels":["@aws-cdk/aws-iam"],"assignees":["rix0rrr"]}, + {"keywords":["inspector"],"labels":["@aws-cdk/aws-inspector"],"assignees":["rix0rrr"]}, + {"keywords":["iot"],"labels":["@aws-cdk/aws-iot"],"assignees":["shivlaks"]}, + {"keywords":["iot1click","iot 1 click"],"labels":["@aws-cdk/aws-iot1click"],"assignees":["shivlaks"]}, + {"keywords":["iotanalytics","iot analytics"],"labels":["@aws-cdk/aws-iotanalytics"],"assignees":["shivlaks"]}, + {"keywords":["iotevents","iot events"],"labels":["@aws-cdk/aws-iotevents"],"assignees":["shivlaks"]}, + {"keywords":["iotthingsgraph","iot things graph"],"labels":["@aws-cdk/aws-iotthingsgraph"],"assignees":["shivlaks"]}, + {"keywords":["kinesis"],"labels":["@aws-cdk/aws-kinesis"],"assignees":["iliapolo"]}, + {"keywords":["kinesisanalytics","kinesis analytics"],"labels":["@aws-cdk/aws-kinesisanalytics"],"assignees":["iliapolo"]}, + {"keywords":["kinesisfirehose","firehose"],"labels":["@aws-cdk/aws-kinesisfirehose"],"assignees":["iliapolo"]}, + {"keywords":["kms"],"labels":["@aws-cdk/aws-kms"],"assignees":["skinny85"]}, + {"keywords":["lakeformation","lake formation"],"labels":["@aws-cdk/aws-lakeformation"],"assignees":["iliapolo"]}, + {"keywords":["lambda"],"labels":["@aws-cdk/aws-lambda"],"assignees":["nija-at"]}, + {"keywords":["lambda-event-sources","lambda event sources"],"labels":["@aws-cdk/aws-lambda-event-sources"],"assignees":["nija-at"]}, + {"keywords":["logs"],"labels":["@aws-cdk/aws-logs"],"assignees":["rix0rrr"]}, + {"keywords":["logs-destinations","logs destinations"],"labels":["@aws-cdk/aws-logs-destinations"],"assignees":["rix0rrr"]}, + {"keywords":["managedblockchain","managed blockchain"],"labels":["@aws-cdk/aws-managedblockchain"],"assignees":["shivlaks"]}, + {"keywords":["mediaconvert","media convert"],"labels":["@aws-cdk/aws-mediaconvert"],"assignees":["rix0rrr"]}, + {"keywords":["medialive","media live"],"labels":["@aws-cdk/aws-medialive"],"assignees":["rix0rrr"]}, + {"keywords":["mediastore","media store"],"labels":["@aws-cdk/aws-mediastore"],"assignees":["rix0rrr"]}, + {"keywords":["msk"],"labels":["@aws-cdk/aws-msk"],"assignees":["iliapolo"]}, + {"keywords":["neptune"],"labels":["@aws-cdk/aws-neptune"],"assignees":["nija-at"]}, + {"keywords":["opsworks","ops works"],"labels":["@aws-cdk/aws-opsworks"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["opsworkscm"],"labels":["@aws-cdk/aws-opsworkscm"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["personalize"],"labels":["@aws-cdk/aws-personalize"],"assignees":["NetaNir"]}, + {"keywords":["pinpoint"],"labels":["@aws-cdk/aws-pinpoint"],"assignees":["iliapolo"]}, + {"keywords":["pinpointemail","pinpoint email"],"labels":["@aws-cdk/aws-pinpointemail"],"assignees":["iliapolo"]}, + {"keywords":["qldb"],"labels":["@aws-cdk/aws-qldb"],"assignees":["shivlaks"]}, + {"keywords":["ram"],"labels":["@aws-cdk/aws-ram"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["rds"],"labels":["@aws-cdk/aws-rds"],"assignees":["nija-at"]}, + {"keywords":["redshift","red shift"],"labels":["@aws-cdk/aws-redshift"],"assignees":["nija-at"]}, + {"keywords":["robomaker"],"labels":["@aws-cdk/aws-robomaker"],"assignees":["NetaNir"]}, + {"keywords":["route53","route 53"],"labels":["@aws-cdk/aws-route53"],"assignees":["shivlaks"]}, + {"keywords":["route53-patterns","route53 patterns"],"labels":["@aws-cdk/aws-route53-patterns"],"assignees":["shivlaks"]}, + {"keywords":["route53-targets","route53 targets"],"labels":["@aws-cdk/aws-route53-targets"],"assignees":["shivlaks"]}, + {"keywords":["route53resolver","route53 resolver"],"labels":["@aws-cdk/aws-route53resolver"],"assignees":["shivlaks"]}, + {"keywords":["s3"],"labels":["@aws-cdk/aws-s3"],"assignees":["iliapolo"]}, + {"keywords":["s3-assets","s3 assets"],"labels":["@aws-cdk/aws-s3-assets"],"assignees":["iliapolo"]}, + {"keywords":["s3-deployment","s3 deployment"],"labels":["@aws-cdk/aws-s3-deployment"],"assignees":["iliapolo"]}, + {"keywords":["s3-notifications","s3 notifications"],"labels":["@aws-cdk/aws-s3-notifications"],"assignees":["iliapolo"]}, + {"keywords":["sagemaker"],"labels":["@aws-cdk/aws-sagemaker"],"assignees":["NetaNir"]}, + {"keywords":["sam"],"labels":["@aws-cdk/aws-sam"],"assignees":["nija-at"]}, + {"keywords":["sdb"],"labels":["@aws-cdk/aws-sdb"],"assignees":["nija-at"]}, + {"keywords":["secretsmanager","secrets manager"],"labels":["@aws-cdk/aws-secretsmanager"],"assignees":["skinny85"]}, + {"keywords":["securityhub","security hub"],"labels":["@aws-cdk/aws-securityhub"],"assignees":["rix0rrr"]}, + {"keywords":["servicecatalog","service catalog"],"labels":["@aws-cdk/aws-servicecatalog"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["servicediscovery","service discovery"],"labels":["@aws-cdk/aws-servicediscovery"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["ses"],"labels":["@aws-cdk/aws-ses"],"assignees":["iliapolo"]}, + {"keywords":["ses-actions","ses actions"],"labels":["@aws-cdk/aws-ses-actions"],"assignees":["iliapolo"]}, + {"keywords":["sns"],"labels":["@aws-cdk/aws-sns"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["sns-subscriptions","sns subscriptions"],"labels":["@aws-cdk/aws-sns-subscriptions"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["sqs"],"labels":["@aws-cdk/aws-sqs"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["ssm"],"labels":["@aws-cdk/aws-ssm"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["stepfunctions","step functions"],"labels":["@aws-cdk/aws-stepfunctions"],"assignees":["shivlaks"]}, + {"keywords":["stepfunctions-tasks","stepfunctions tasks"],"labels":["@aws-cdk/aws-stepfunctions-tasks"],"assignees":["shiv"]}, + {"keywords":["aws-transfer"],"labels":["@aws-cdk/aws-transfer"],"assignees":["iliapolo"]}, + {"keywords":["waf"],"labels":["@aws-cdk/aws-waf"],"assignees":["rix0rrr"]}, + {"keywords":["wafregional","waf regional"],"labels":["@aws-cdk/aws-wafregional"],"assignees":["rix0rrr"]}, + {"keywords":["wafv2","waf v2"],"labels":["@aws-cdk/aws-wafv2"],"assignees":["rix0rrr"]}, + {"keywords":["workspaces"],"labels":["@aws-cdk/aws-workspaces"],"assignees":["NetaNir"]}, + {"keywords":["cfnspec","cfn spec"],"labels":["@aws-cdk/cfnspec"],"assignees":["eladb"]}, + {"keywords":["cloudformation-diff","cloudformation diff"],"labels":["@aws-cdk/cloudformation-diff"],"assignees":["shivlaks"]}, + {"keywords":["core"],"labels":["@aws-cdk/core"],"assignees":["eladb"]}, + {"keywords":["custom-resources","custom resources"],"labels":["@aws-cdk/custom-resources"],"assignees":["eladb"]}, + {"keywords":["cx-api","cx api"],"labels":["@aws-cdk/cx-api"],"assignees":["eladb"]}, + {"keywords":["region-info","region info"],"labels":["@aws-cdk/region-info"],"assignees":["eladb"]}, + {"keywords":["@aws-cdk/aws-acmpca","aws-acmpca","acmpca"],"labels":["@aws-cdk/aws-acmpca"],"assignees":["skinny85"]}, + {"keywords":["@aws-cdk/aws-appconfig","aws-appconfig","app config","appconfig"],"labels":["@aws-cdk/aws-appconfig"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["@aws-cdk/aws-cassandra","aws-cassandra","cassandra"],"labels":["@aws-cdk/aws-cassandra"],"assignees":["iliapolo"]}, + {"keywords":["@aws-cdk/aws-ce","aws-ce"],"labels":["@aws-cdk/aws-ce"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["@aws-cdk/aws-chatbot","aws-chatbot","chatbot"],"labels":["@aws-cdk/aws-chatbot"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["@aws-cdk/aws-codeguruprofiler","aws-codeguruprofiler","code guru profiler","codeguruprofiler","codeguru profiler"],"labels":["@aws-cdk/aws-codeguruprofiler"],"assignees":["skinny85"]}, + {"keywords":["@aws-cdk/aws-detective","aws-detective","detective"],"labels":["@aws-cdk/aws-detective"],"assignees":["rix0rrr"]}, + {"keywords":["@aws-cdk/aws-eventschemas","aws-eventschemas","event schemas","eventschemas"],"labels":["@aws-cdk/aws-eventschemas"],"assignees":["rix0rrr"]}, + {"keywords":["@aws-cdk/aws-fms","aws-fms","fms"],"labels":["@aws-cdk/aws-fms"],"assignees":["rix0rrr"]}, + {"keywords":["@aws-cdk/aws-globalaccelerator","aws-globalaccelerator","global accelerator","globalaccelerator"],"labels":["@aws-cdk/aws-globalaccelerator"],"assignees":["rix0rrr"]}, + {"keywords":["@aws-cdk/aws-imagebuilder","aws-imagebuilder","image builder","imagebuilder"],"labels":["@aws-cdk/aws-imagebuilder"],"assignees":["rix0rrr"]}, + {"keywords":["@aws-cdk/aws-networkmanager","aws-networkmanager","network manager","networkmanager"],"labels":["@aws-cdk/aws-networkmanager"],"assignees":["rix0rrr"]}, + {"keywords":["@aws-cdk/aws-resourcegroups","aws-resourcegroups","resource groups","resourcegroups"],"labels":["@aws-cdk/aws-resourcegroups"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["@aws-cdk/aws-synthetics","aws-synthetics","synthetics"],"labels":["@aws-cdk/aws-synthetics"],"assignees":["rix0rrr"]}, + {"keywords":["@aws-cdk/aws-apigatewayv2","aws-apigatewayv2","apigateway v2","apigatewayv2"],"labels":["@aws-cdk/aws-apigatewayv2"],"assignees":["nija-at"]}, + {"keywords":["@aws-cdk/aws-lambda-nodejs","aws-lambda-nodejs","lambda-nodejs","lambda nodejs"],"labels":["@aws-cdk/aws-lambda-nodejs"],"assignees":["eladb"]}, + {"keywords":["@aws-cdk/cloudformation-include","cloudformation-include","cloudformation-include","cloudformation include"],"labels":["@aws-cdk/cloudformation-include"],"assignees":["skinny85"]}, + {"keywords":["@aws-cdk/cloud-assembly-schema","cloud-assembly-schema","cloud-assembly-schema","cloud assembly schema"],"labels":["@aws-cdk/cloud-assembly-schema"],"assignees":["eladb"]} + ] + diff --git a/package.json b/package.json index 60b8110db0128..30179b16c05b3 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,12 @@ "devDependencies": { "conventional-changelog-cli": "^2.0.34", "fs-extra": "^9.0.1", + "graceful-fs": "^4.2.4", "jsii-diff": "^1.6.0", "jsii-pacmak": "^1.6.0", "jsii-rosetta": "^1.6.0", "lerna": "^3.22.1", "standard-version": "^8.0.0", - "graceful-fs": "^4.2.4", "typescript": "~3.8.3" }, "resolutions-comment": "should be removed or reviewed when nodeunit dependency is dropped or adjusted", @@ -62,12 +62,16 @@ "@aws-cdk/cloud-assembly-schema/semver/**", "@aws-cdk/cloudformation-include/yaml", "@aws-cdk/cloudformation-include/yaml/**", + "@aws-cdk/core/fs-extra", + "@aws-cdk/core/fs-extra/**", "@aws-cdk/core/minimatch", "@aws-cdk/core/minimatch/**", "@aws-cdk/cx-api/semver", "@aws-cdk/cx-api/semver/**", "monocdk-experiment/case", "monocdk-experiment/case/**", + "monocdk-experiment/fs-extra", + "monocdk-experiment/fs-extra/**", "monocdk-experiment/jsonschema", "monocdk-experiment/jsonschema/**", "monocdk-experiment/minimatch", diff --git a/packages/@aws-cdk/alexa-ask/.eslintrc.js b/packages/@aws-cdk/alexa-ask/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/alexa-ask/.eslintrc.js +++ b/packages/@aws-cdk/alexa-ask/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/app-delivery/.eslintrc.js b/packages/@aws-cdk/app-delivery/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/app-delivery/.eslintrc.js +++ b/packages/@aws-cdk/app-delivery/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/app-delivery/package.json b/packages/@aws-cdk/app-delivery/package.json index db77409db42d4..d1f6bedf06839 100644 --- a/packages/@aws-cdk/app-delivery/package.json +++ b/packages/@aws-cdk/app-delivery/package.json @@ -57,7 +57,7 @@ "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "fast-check": "^1.24.2", + "fast-check": "^1.25.1", "nodeunit": "^0.11.3", "pkglint": "0.0.0" }, diff --git a/packages/@aws-cdk/assert/.eslintrc.js b/packages/@aws-cdk/assert/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/assert/.eslintrc.js +++ b/packages/@aws-cdk/assert/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/assert/package.json b/packages/@aws-cdk/assert/package.json index 1ad7d397c5c6f..edf1445b82fd2 100644 --- a/packages/@aws-cdk/assert/package.json +++ b/packages/@aws-cdk/assert/package.json @@ -21,7 +21,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^25.2.3", + "@types/jest": "^26.0.0", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", "pkglint": "0.0.0", diff --git a/packages/@aws-cdk/assets/.eslintrc.js b/packages/@aws-cdk/assets/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/assets/.eslintrc.js +++ b/packages/@aws-cdk/assets/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-accessanalyzer/.eslintrc.js b/packages/@aws-cdk/aws-accessanalyzer/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-accessanalyzer/.eslintrc.js +++ b/packages/@aws-cdk/aws-accessanalyzer/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-acmpca/.eslintrc.js b/packages/@aws-cdk/aws-acmpca/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-acmpca/.eslintrc.js +++ b/packages/@aws-cdk/aws-acmpca/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-amazonmq/.eslintrc.js b/packages/@aws-cdk/aws-amazonmq/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-amazonmq/.eslintrc.js +++ b/packages/@aws-cdk/aws-amazonmq/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-amplify/.eslintrc.js b/packages/@aws-cdk/aws-amplify/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-amplify/.eslintrc.js +++ b/packages/@aws-cdk/aws-amplify/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-amplify/lib/app.ts b/packages/@aws-cdk/aws-amplify/lib/app.ts index 483bbbb58d6c7..640c9684625df 100644 --- a/packages/@aws-cdk/aws-amplify/lib/app.ts +++ b/packages/@aws-cdk/aws-amplify/lib/app.ts @@ -372,6 +372,11 @@ export enum RedirectStatus { * Not found (404) */ NOT_FOUND = '404', + + /** + * Not found rewrite (404) + */ + NOT_FOUND_REWRITE = '404-200', } /** diff --git a/packages/@aws-cdk/aws-apigateway/.eslintrc.js b/packages/@aws-cdk/aws-apigateway/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-apigateway/.eslintrc.js +++ b/packages/@aws-cdk/aws-apigateway/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-apigateway/README.md b/packages/@aws-cdk/aws-apigateway/README.md index 3be9cea704d17..6045d52aa95d3 100644 --- a/packages/@aws-cdk/aws-apigateway/README.md +++ b/packages/@aws-cdk/aws-apigateway/README.md @@ -19,6 +19,7 @@ running on AWS Lambda, or any web application. ## Table of Contents - [Defining APIs](#defining-apis) + - [Breaking up Methods and Resources across Stacks](#breaking-up-methods-and-resources-across-stacks) - [AWS Lambda-backed APIs](#aws-lambda-backed-apis) - [Integration Targets](#integration-targets) - [Working with models](#working-with-models) @@ -99,6 +100,18 @@ item.addMethod('GET'); // GET /items/{item} item.addMethod('DELETE', new apigateway.HttpIntegration('http://amazon.com')); ``` +### Breaking up Methods and Resources across Stacks + +It is fairly common for REST APIs with a large number of Resources and Methods to hit the [CloudFormation +limit](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html) of 200 resources per +stack. + +To help with this, Resources and Methods for the same REST API can be re-organized across multiple stacks. A common +way to do this is to have a stack per Resource or groups of Resources, but this is not the only possible way. +The following example uses sets up two Resources '/pets' and '/books' in separate stacks using nested stacks: + +[Resources grouped into nested stacks](test/integ.restapi-import.lit.ts) + ## Integration Targets Methods are associated with backend integrations, which are invoked when this @@ -956,8 +969,20 @@ The following code creates a REST API using an external OpenAPI definition JSON const api = new apigateway.SpecRestApi(this, 'books-api', { apiDefinition: apigateway.ApiDefinition.fromAsset('path-to-file.json') }); + +const booksResource = api.root.addResource('books') +booksResource.addMethod('GET', ...); ``` +It is possible to use the `addResource()` API to define additional API Gateway Resources. + +**Note:** Deployment will fail if a Resource of the same name is already defined in the Open API specification. + +**Note:** Any default properties configured, such as `defaultIntegration`, `defaultMethodOptions`, etc. will only be +applied to Resources and Methods defined in the CDK, and not the ones defined in the spec. Use the [API Gateway +extensions to OpenAPI](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions.html) +to configure these. + There are a number of limitations in using OpenAPI definitions in API Gateway. Read the [Amazon API Gateway important notes for REST APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-known-issues.html#api-gateway-known-issues-rest-apis) for more details. @@ -965,8 +990,6 @@ for more details. **Note:** When starting off with an OpenAPI definition using `SpecRestApi`, it is not possible to configure some properties that can be configured directly in the OpenAPI specification file. This is to prevent people duplication of these properties and potential confusion. -Further, it is currently also not possible to configure Methods and Resources in addition to the ones in the -specification file. ## APIGateway v2 diff --git a/packages/@aws-cdk/aws-apigateway/lib/authorizer.ts b/packages/@aws-cdk/aws-apigateway/lib/authorizer.ts index a51f30e14514c..ea414a1c43584 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/authorizer.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/authorizer.ts @@ -1,6 +1,6 @@ import { Construct, Resource, ResourceProps } from '@aws-cdk/core'; import { AuthorizationType } from './method'; -import { RestApi } from './restapi'; +import { IRestApi } from './restapi'; const AUTHORIZER_SYMBOL = Symbol.for('@aws-cdk/aws-apigateway.Authorizer'); @@ -28,7 +28,7 @@ export abstract class Authorizer extends Resource implements IAuthorizer { * Called when the authorizer is used from a specific REST API. * @internal */ - public abstract _attachToApi(restApi: RestApi): void; + public abstract _attachToApi(restApi: IRestApi): void; } /** diff --git a/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts index 9215c28de1e61..f79d675af1e7f 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts @@ -3,7 +3,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import { Construct, Duration, Lazy, Stack } from '@aws-cdk/core'; import { CfnAuthorizer } from '../apigateway.generated'; import { Authorizer, IAuthorizer } from '../authorizer'; -import { RestApi } from '../restapi'; +import { IRestApi } from '../restapi'; /** * Base properties for all lambda authorizers @@ -83,7 +83,7 @@ abstract class LambdaAuthorizer extends Authorizer implements IAuthorizer { * Attaches this authorizer to a specific REST API. * @internal */ - public _attachToApi(restApi: RestApi) { + public _attachToApi(restApi: IRestApi) { if (this.restApiId && this.restApiId !== restApi.restApiId) { throw new Error('Cannot attach authorizer to two different rest APIs'); } diff --git a/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts b/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts index c9893469c4f7f..f44ebf953dcac 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts @@ -1,5 +1,5 @@ import * as acm from '@aws-cdk/aws-certificatemanager'; -import { Construct, IResource, Resource } from '@aws-cdk/core'; +import { Construct, IResource, Resource, Token } from '@aws-cdk/core'; import { CfnDomainName } from './apigateway.generated'; import { BasePathMapping, BasePathMappingOptions } from './base-path-mapping'; import { EndpointType, IRestApi } from './restapi'; @@ -102,6 +102,11 @@ export class DomainName extends Resource implements IDomainName { const endpointType = props.endpointType || EndpointType.REGIONAL; const edge = endpointType === EndpointType.EDGE; + if (!Token.isUnresolved(props.domainName) && /[A-Z]/.test(props.domainName)) { + throw new Error('domainName does not support uppercase letters. ' + + `got: '${props.domainName}'`); + } + const resource = new CfnDomainName(this, 'Resource', { domainName: props.domainName, certificateArn: edge ? props.certificate.certificateArn : undefined, diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index 58c504ab4aa8a..a7af625a4d121 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -7,7 +7,7 @@ import { MethodResponse } from './methodresponse'; import { IModel } from './model'; import { IRequestValidator, RequestValidatorOptions } from './requestvalidator'; import { IResource } from './resource'; -import { RestApi } from './restapi'; +import { IRestApi, RestApi, RestApiBase } from './restapi'; import { validateHttpMethod } from './util'; export interface MethodOptions { @@ -159,13 +159,16 @@ export class Method extends Resource { public readonly httpMethod: string; public readonly resource: IResource; - public readonly restApi: RestApi; + /** + * The API Gateway RestApi associated with this method. + */ + public readonly api: IRestApi; constructor(scope: Construct, id: string, props: MethodProps) { super(scope, id); this.resource = props.resource; - this.restApi = props.resource.restApi; + this.api = props.resource.api; this.httpMethod = props.httpMethod.toUpperCase(); validateHttpMethod(this.httpMethod); @@ -186,12 +189,12 @@ export class Method extends Resource { } if (Authorizer.isAuthorizer(authorizer)) { - authorizer._attachToApi(this.restApi); + authorizer._attachToApi(this.api); } const methodProps: CfnMethodProps = { resourceId: props.resource.resourceId, - restApiId: this.restApi.restApiId, + restApiId: this.api.restApiId, httpMethod: this.httpMethod, operationName: options.operationName || defaultMethodOptions.operationName, apiKeyRequired: options.apiKeyRequired || defaultMethodOptions.apiKeyRequired, @@ -209,15 +212,25 @@ export class Method extends Resource { this.methodId = resource.ref; - props.resource.restApi._attachMethod(this); + if (RestApiBase._isRestApiBase(props.resource.api)) { + props.resource.api._attachMethod(this); + } - const deployment = props.resource.restApi.latestDeployment; + const deployment = props.resource.api.latestDeployment; if (deployment) { deployment.node.addDependency(resource); deployment.addToLogicalId({ method: methodProps }); } } + /** + * The RestApi associated with this Method + * @deprecated - Throws an error if this Resource is not associated with an instance of `RestApi`. Use `api` instead. + */ + public get restApi(): RestApi { + return this.resource.restApi; + } + /** * Returns an execute-api ARN for this method: * diff --git a/packages/@aws-cdk/aws-apigateway/lib/resource.ts b/packages/@aws-cdk/aws-apigateway/lib/resource.ts index 2d916780bf3e2..102a17a5cdc27 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/resource.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/resource.ts @@ -4,7 +4,7 @@ import { Cors, CorsOptions } from './cors'; import { Integration } from './integration'; import { MockIntegration } from './integrations'; import { Method, MethodOptions } from './method'; -import { RestApi } from './restapi'; +import { IRestApi, RestApi } from './restapi'; export interface IResource extends IResourceBase { /** @@ -12,6 +12,13 @@ export interface IResource extends IResourceBase { */ readonly parentResource?: IResource; + /** + * The rest API that this resource is part of. + * + * @deprecated - Throws an error if this Resource is not associated with an instance of `RestApi`. Use `api` instead. + */ + readonly restApi: RestApi; + /** * The rest API that this resource is part of. * @@ -20,7 +27,7 @@ export interface IResource extends IResourceBase { * hash to determine the ID of the deployment. This allows us to automatically update * the deployment when the model of the REST API changes. */ - readonly restApi: RestApi; + readonly api: IRestApi; /** * The ID of the resource. @@ -154,7 +161,11 @@ export interface ResourceProps extends ResourceOptions { export abstract class ResourceBase extends ResourceConstruct implements IResource { public abstract readonly parentResource?: IResource; + /** + * @deprecated - Throws an error if this Resource is not associated with an instance of `RestApi`. Use `api` instead. + */ public abstract readonly restApi: RestApi; + public abstract readonly api: IRestApi; public abstract readonly resourceId: string; public abstract readonly path: string; public abstract readonly defaultIntegration?: Integration; @@ -353,6 +364,9 @@ export abstract class ResourceBase extends ResourceConstruct implements IResourc return resource.resourceForPath(parts.join('/')); } + /** + * @deprecated - Throws error in some use cases that have been enabled since this deprecation notice. Use `RestApi.urlForPath()` instead. + */ public get url(): string { return this.restApi.urlForPath(this.path); } @@ -360,7 +374,7 @@ export abstract class ResourceBase extends ResourceConstruct implements IResourc export class Resource extends ResourceBase { public readonly parentResource?: IResource; - public readonly restApi: RestApi; + public readonly api: IRestApi; public readonly resourceId: string; public readonly path: string; @@ -380,21 +394,21 @@ export class Resource extends ResourceBase { } const resourceProps: CfnResourceProps = { - restApiId: props.parent.restApi.restApiId, + restApiId: props.parent.api.restApiId, parentId: props.parent.resourceId, pathPart: props.pathPart, }; const resource = new CfnResource(this, 'Resource', resourceProps); this.resourceId = resource.ref; - this.restApi = props.parent.restApi; + this.api = props.parent.api; // render resource path (special case for root) this.path = props.parent.path; if (!this.path.endsWith('/')) { this.path += '/'; } this.path += props.pathPart; - const deployment = props.parent.restApi.latestDeployment; + const deployment = props.parent.api.latestDeployment; if (deployment) { deployment.node.addDependency(resource); deployment.addToLogicalId({ resource: resourceProps }); @@ -413,6 +427,17 @@ export class Resource extends ResourceBase { this.addCorsPreflight(this.defaultCorsPreflightOptions); } } + + /** + * The RestApi associated with this Resource + * @deprecated - Throws an error if this Resource is not associated with an instance of `RestApi`. Use `api` instead. + */ + public get restApi(): RestApi { + if (!this.parentResource) { + throw new Error('parentResource was unexpectedly not defined'); + } + return this.parentResource.restApi; + } } export interface ProxyResourceOptions extends ResourceOptions { diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 5a43b562ff279..4d08a0b01ce36 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -16,12 +16,36 @@ import { IResource, ResourceBase, ResourceOptions } from './resource'; import { Stage, StageOptions } from './stage'; import { UsagePlan, UsagePlanProps } from './usage-plan'; +const RESTAPI_SYMBOL = Symbol.for('@aws-cdk/aws-apigateway.RestApiBase'); + export interface IRestApi extends IResourceBase { /** * The ID of this API Gateway RestApi. * @attribute */ readonly restApiId: string; + + /** + * The resource ID of the root resource. + * @attribute + */ + readonly restApiRootResourceId: string; + + /** + * API Gateway deployment that represents the latest changes of the API. + * This resource will be automatically updated every time the REST API model changes. + * `undefined` when no deployment is configured. + */ + readonly latestDeployment?: Deployment; + + /** + * Represents the root resource ("/") of this API. Use it to define the API model: + * + * api.root.addMethod('ANY', redirectToHomePage); // "ANY /" + * api.root.addResource('friends').addMethod('GET', getFriendsHandler); // "GET /friends" + * + */ + readonly root: IResource; } /** @@ -197,7 +221,36 @@ export interface SpecRestApiProps extends RestApiOptions { readonly apiDefinition: ApiDefinition; } -abstract class RestApiBase extends Resource implements IRestApi { +/** + * Base implementation that are common to various implementations of IRestApi + */ +export abstract class RestApiBase extends Resource implements IRestApi { + + /** + * Checks if the given object is an instance of RestApiBase. + * @internal + */ + public static _isRestApiBase(x: any): x is RestApiBase { + return x !== null && typeof(x) === 'object' && RESTAPI_SYMBOL in x; + } + + /** + * API Gateway deployment that represents the latest changes of the API. + * This resource will be automatically updated every time the REST API model changes. + * This will be undefined if `deploy` is false. + */ + public get latestDeployment() { + return this._latestDeployment; + } + + /** + * The first domain name mapped to this API, if defined through the `domainName` + * configuration prop, or added via `addDomainName` + */ + public get domainName() { + return this._domainName; + } + /** * The ID of this API Gateway RestApi. */ @@ -210,6 +263,12 @@ abstract class RestApiBase extends Resource implements IRestApi { */ public abstract readonly restApiRootResourceId: string; + /** + * Represents the root resource of this API endpoint ('/'). + * Resources and Methods are added to this resource. + */ + public abstract readonly root: IResource; + /** * API Gateway stage that points to the latest deployment (if defined). * @@ -225,6 +284,8 @@ abstract class RestApiBase extends Resource implements IRestApi { super(scope, id, { physicalName: props.restApiName || id, }); + + Object.defineProperty(this, RESTAPI_SYMBOL, { value: true }); } /** @@ -240,15 +301,6 @@ abstract class RestApiBase extends Resource implements IRestApi { return this.deploymentStage.urlForPath(path); } - /** - * API Gateway deployment that represents the latest changes of the API. - * This resource will be automatically updated every time the REST API model changes. - * This will be undefined if `deploy` is false. - */ - public get latestDeployment() { - return this._latestDeployment; - } - /** * Defines an API Gateway domain name and maps it to this API. * @param id The construct id @@ -272,14 +324,6 @@ abstract class RestApiBase extends Resource implements IRestApi { return new UsagePlan(this, id, props); } - /** - * The first domain name mapped to this API, if defined through the `domainName` - * configuration prop, or added via `addDomainName` - */ - public get domainName() { - return this._domainName; - } - /** * Gets the "execute-api" ARN * @returns The "execute-api" ARN. @@ -316,6 +360,16 @@ abstract class RestApiBase extends Resource implements IRestApi { }); } + /** + * Internal API used by `Method` to keep an inventory of methods at the API + * level for validation purposes. + * + * @internal + */ + public _attachMethod(method: Method) { + ignore(method); + } + protected configureCloudWatchRole(apiResource: CfnRestApi) { const role = new iam.Role(this, 'CloudWatchRole', { assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), @@ -384,6 +438,8 @@ export class SpecRestApi extends RestApiBase { */ public readonly restApiRootResourceId: string; + public readonly root: IResource; + constructor(scope: Construct, id: string, props: SpecRestApiProps) { super(scope, id, props); const apiDefConfig = props.apiDefinition.bind(this); @@ -398,6 +454,7 @@ export class SpecRestApi extends RestApiBase { this.node.defaultChild = resource; this.restApiId = resource.ref; this.restApiRootResourceId = resource.attrRootResourceId; + this.root = new RootResource(this, props, this.restApiRootResourceId); this.configureDeployment(props); if (props.domainName) { @@ -411,6 +468,21 @@ export class SpecRestApi extends RestApiBase { } } +/** + * Attributes that can be specified when importing a RestApi + */ +export interface RestApiAttributes { + /** + * The ID of the API Gateway RestApi. + */ + readonly restApiId: string; + + /** + * The resource ID of the root resource. + */ + readonly rootResourceId: string; +} + /** * Represents a REST API in Amazon API Gateway. * @@ -419,34 +491,44 @@ export class SpecRestApi extends RestApiBase { * By default, the API will automatically be deployed and accessible from a * public endpoint. */ -export class RestApi extends RestApiBase implements IRestApi { +export class RestApi extends RestApiBase { + /** + * Import an existing RestApi. + */ public static fromRestApiId(scope: Construct, id: string, restApiId: string): IRestApi { class Import extends Resource implements IRestApi { public readonly restApiId = restApiId; + + public get root(): IResource { + throw new Error('root is not configured when imported using `fromRestApiId()`. Use `fromRestApiAttributes()` API instead.'); + } + + public get restApiRootResourceId(): string { + throw new Error('restApiRootResourceId is not configured when imported using `fromRestApiId()`. Use `fromRestApiAttributes()` API instead.'); + } } return new Import(scope, id); } /** - * The ID of this API Gateway RestApi. + * Import an existing RestApi that can be configured with additional Methods and Resources. + * @experimental */ + public static fromRestApiAttributes(scope: Construct, id: string, attrs: RestApiAttributes): IRestApi { + class Import extends RestApiBase { + public readonly restApiId = attrs.restApiId; + public readonly restApiRootResourceId = attrs.rootResourceId; + public readonly root: IResource = new RootResource(this, {}, this.restApiRootResourceId); + } + + return new Import(scope, id); + } + public readonly restApiId: string; - /** - * Represents the root resource ("/") of this API. Use it to define the API model: - * - * api.root.addMethod('ANY', redirectToHomePage); // "ANY /" - * api.root.addResource('friends').addMethod('GET', getFriendsHandler); // "GET /friends" - * - */ public readonly root: IResource; - /** - * The resource ID of the root resource. - * - * @attribute - */ public readonly restApiRootResourceId: string; /** @@ -613,26 +695,47 @@ export enum EndpointType { class RootResource extends ResourceBase { public readonly parentResource?: IResource; - public readonly restApi: RestApi; + public readonly api: RestApiBase; public readonly resourceId: string; public readonly path: string; public readonly defaultIntegration?: Integration | undefined; public readonly defaultMethodOptions?: MethodOptions | undefined; public readonly defaultCorsPreflightOptions?: CorsOptions | undefined; - constructor(api: RestApi, props: RestApiProps, resourceId: string) { + private readonly _restApi?: RestApi; + + constructor(api: RestApiBase, props: ResourceOptions, resourceId: string) { super(api, 'Default'); this.parentResource = undefined; this.defaultIntegration = props.defaultIntegration; this.defaultMethodOptions = props.defaultMethodOptions; this.defaultCorsPreflightOptions = props.defaultCorsPreflightOptions; - this.restApi = api; + this.api = api; this.resourceId = resourceId; this.path = '/'; + if (api instanceof RestApi) { + this._restApi = api; + } + if (this.defaultCorsPreflightOptions) { this.addCorsPreflight(this.defaultCorsPreflightOptions); } } + + /** + * Get the RestApi associated with this Resource. + * @deprecated - Throws an error if this Resource is not associated with an instance of `RestApi`. Use `api` instead. + */ + public get restApi(): RestApi { + if (!this._restApi) { + throw new Error('RestApi is not available on Resource not connected to an instance of RestApi. Use `api` instead'); + } + return this._restApi; + } } + +function ignore(_x: any) { + return; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/package.json b/packages/@aws-cdk/aws-apigateway/package.json index c3003c458ea57..6816f6cc02ab7 100644 --- a/packages/@aws-cdk/aws-apigateway/package.json +++ b/packages/@aws-cdk/aws-apigateway/package.json @@ -176,14 +176,12 @@ "docs-public-apis:@aws-cdk/aws-apigateway.Method.httpMethod", "docs-public-apis:@aws-cdk/aws-apigateway.Method.methodId", "docs-public-apis:@aws-cdk/aws-apigateway.Method.resource", - "docs-public-apis:@aws-cdk/aws-apigateway.Method.restApi", "docs-public-apis:@aws-cdk/aws-apigateway.Model", "docs-public-apis:@aws-cdk/aws-apigateway.Model.fromModelName", "docs-public-apis:@aws-cdk/aws-apigateway.RequestValidator", "docs-public-apis:@aws-cdk/aws-apigateway.RequestValidator.fromRequestValidatorId", "docs-public-apis:@aws-cdk/aws-apigateway.Resource", "docs-public-apis:@aws-cdk/aws-apigateway.ResourceBase", - "docs-public-apis:@aws-cdk/aws-apigateway.RestApi.fromRestApiId", "docs-public-apis:@aws-cdk/aws-apigateway.RestApi.arnForExecuteApi", "docs-public-apis:@aws-cdk/aws-apigateway.Stage", "docs-public-apis:@aws-cdk/aws-apigateway.Stage.restApi", diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.asset.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.asset.expected.json index bcf74c12601fa..8946e415c6874 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.asset.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.asset.expected.json @@ -44,14 +44,63 @@ "Name": "my-api" } }, - "myapiDeployment92F2CB49eb6b0027bfbdb20b09988607569e06bd": { + "myapibooks51D54548": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "myapi4C7BF186", + "RootResourceId" + ] + }, + "PathPart": "books", + "RestApiId": { + "Ref": "myapi4C7BF186" + } + } + }, + "myapibooksGETD6B2F597": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "GET", + "ResourceId": { + "Ref": "myapibooks51D54548" + }, + "RestApiId": { + "Ref": "myapi4C7BF186" + }, + "AuthorizationType": "NONE", + "Integration": { + "IntegrationResponses": [ + { + "StatusCode": "200" + } + ], + "PassthroughBehavior": "NEVER", + "RequestTemplates": { + "application/json": "{ \"statusCode\": 200 }" + }, + "Type": "MOCK" + }, + "MethodResponses": [ + { + "StatusCode": "200" + } + ] + } + }, + "myapiDeployment92F2CB49fe116fef7f552ff0fc433c9aa3930d2f": { "Type": "AWS::ApiGateway::Deployment", "Properties": { "RestApiId": { "Ref": "myapi4C7BF186" }, "Description": "Automatically created by the RestApi construct" - } + }, + "DependsOn": [ + "myapibooksGETD6B2F597", + "myapibooks51D54548" + ] }, "myapiDeploymentStageprod298F01AF": { "Type": "AWS::ApiGateway::Stage", @@ -60,7 +109,7 @@ "Ref": "myapi4C7BF186" }, "DeploymentId": { - "Ref": "myapiDeployment92F2CB49eb6b0027bfbdb20b09988607569e06bd" + "Ref": "myapiDeployment92F2CB49fe116fef7f552ff0fc433c9aa3930d2f" }, "StageName": "prod" } @@ -163,6 +212,32 @@ ] ] } + }, + "BooksURL": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "myapi4C7BF186" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "myapiDeploymentStageprod298F01AF" + }, + "/books" + ] + ] + } } }, "Parameters": { diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.asset.ts b/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.asset.ts index 1b8531ccad8d5..63e6343f4de26 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.asset.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.asset.ts @@ -4,7 +4,8 @@ import * as apigateway from '../lib'; /* * Stack verification steps: - * * `curl -i ` should return HTTP code 200 + * * `curl -s -o /dev/null -w "%{http_code}" ` should return HTTP code 200 + * * `curl -s -o /dev/null -w "%{http_code}" ` should return HTTP code 200 */ const app = new cdk.App(); @@ -14,8 +15,24 @@ const api = new apigateway.SpecRestApi(stack, 'my-api', { apiDefinition: apigateway.ApiDefinition.fromAsset(path.join(__dirname, 'sample-definition.yaml')), }); +api.root.addResource('books').addMethod('GET', new apigateway.MockIntegration({ + integrationResponses: [{ + statusCode: '200', + }], + passthroughBehavior: apigateway.PassthroughBehavior.NEVER, + requestTemplates: { + 'application/json': '{ "statusCode": 200 }', + }, +}), { + methodResponses: [ { statusCode: '200' } ], +}); + new cdk.CfnOutput(stack, 'PetsURL', { value: api.urlForPath('/pets'), }); +new cdk.CfnOutput(stack, 'BooksURL', { + value: api.urlForPath('/books'), +}); + app.synth(); diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi-import.lit.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi-import.lit.expected.json new file mode 100644 index 0000000000000..349ae37ce27c8 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi-import.lit.expected.json @@ -0,0 +1,334 @@ +{ + "Resources": { + "RestApi0C43BF4B": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Name": "RestApi" + } + }, + "RestApiCloudWatchRoleE3ED6605": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + ] + } + ] + } + }, + "RestApiAccount7C83CF5A": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "RestApiCloudWatchRoleE3ED6605", + "Arn" + ] + } + }, + "DependsOn": [ + "RestApi0C43BF4B" + ] + }, + "RestApiANYA7C1DC94": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AuthorizationType": "NONE", + "Integration": { + "Type": "MOCK" + } + } + }, + "integrestapiimportPetsStackNestedStackintegrestapiimportPetsStackNestedStackResource2B31898B": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParametersc6464ef3a9925cfe5c28d912ee7fc0952eb5135b281419c8d450a3aa8825e1efS3BucketFE7B8A1B" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersc6464ef3a9925cfe5c28d912ee7fc0952eb5135b281419c8d450a3aa8825e1efS3VersionKeyB80604FE" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersc6464ef3a9925cfe5c28d912ee7fc0952eb5135b281419c8d450a3aa8825e1efS3VersionKeyB80604FE" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetointegrestapiimportRootStackRestApi2647DA4CRootResourceId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "referencetointegrestapiimportRootStackRestApi2647DA4CRef": { + "Ref": "RestApi0C43BF4B" + } + } + } + }, + "integrestapiimportBooksStackNestedStackintegrestapiimportBooksStackNestedStackResource395C2C9B": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters480caddfb9aa669df64905982e75c672d967ce9d9ed261ee8c73f6bdcaf97141S3Bucket74F8A623" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters480caddfb9aa669df64905982e75c672d967ce9d9ed261ee8c73f6bdcaf97141S3VersionKeyC855AC3B" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters480caddfb9aa669df64905982e75c672d967ce9d9ed261ee8c73f6bdcaf97141S3VersionKeyC855AC3B" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetointegrestapiimportRootStackRestApi2647DA4CRootResourceId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "referencetointegrestapiimportRootStackRestApi2647DA4CRef": { + "Ref": "RestApi0C43BF4B" + } + } + } + }, + "integrestapiimportDeployStackNestedStackintegrestapiimportDeployStackNestedStackResource0D0EE737": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters04407a85c5bf6d4da110e25ee35b1f67903f760cd7835965518b0f7ad37e86abS3BucketADE4C6AE" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters04407a85c5bf6d4da110e25ee35b1f67903f760cd7835965518b0f7ad37e86abS3VersionKeyF36B0062" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters04407a85c5bf6d4da110e25ee35b1f67903f760cd7835965518b0f7ad37e86abS3VersionKeyF36B0062" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetointegrestapiimportRootStackRestApi2647DA4CRef": { + "Ref": "RestApi0C43BF4B" + } + } + }, + "DependsOn": [ + "integrestapiimportBooksStackNestedStackintegrestapiimportBooksStackNestedStackResource395C2C9B", + "integrestapiimportPetsStackNestedStackintegrestapiimportPetsStackNestedStackResource2B31898B" + ] + } + }, + "Outputs": { + "PetsURL": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com/prod/pets" + ] + ] + } + }, + "BooksURL": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com/prod/books" + ] + ] + } + } + }, + "Parameters": { + "AssetParametersc6464ef3a9925cfe5c28d912ee7fc0952eb5135b281419c8d450a3aa8825e1efS3BucketFE7B8A1B": { + "Type": "String", + "Description": "S3 bucket for asset \"c6464ef3a9925cfe5c28d912ee7fc0952eb5135b281419c8d450a3aa8825e1ef\"" + }, + "AssetParametersc6464ef3a9925cfe5c28d912ee7fc0952eb5135b281419c8d450a3aa8825e1efS3VersionKeyB80604FE": { + "Type": "String", + "Description": "S3 key for asset version \"c6464ef3a9925cfe5c28d912ee7fc0952eb5135b281419c8d450a3aa8825e1ef\"" + }, + "AssetParametersc6464ef3a9925cfe5c28d912ee7fc0952eb5135b281419c8d450a3aa8825e1efArtifactHashED1A6259": { + "Type": "String", + "Description": "Artifact hash for asset \"c6464ef3a9925cfe5c28d912ee7fc0952eb5135b281419c8d450a3aa8825e1ef\"" + }, + "AssetParameters480caddfb9aa669df64905982e75c672d967ce9d9ed261ee8c73f6bdcaf97141S3Bucket74F8A623": { + "Type": "String", + "Description": "S3 bucket for asset \"480caddfb9aa669df64905982e75c672d967ce9d9ed261ee8c73f6bdcaf97141\"" + }, + "AssetParameters480caddfb9aa669df64905982e75c672d967ce9d9ed261ee8c73f6bdcaf97141S3VersionKeyC855AC3B": { + "Type": "String", + "Description": "S3 key for asset version \"480caddfb9aa669df64905982e75c672d967ce9d9ed261ee8c73f6bdcaf97141\"" + }, + "AssetParameters480caddfb9aa669df64905982e75c672d967ce9d9ed261ee8c73f6bdcaf97141ArtifactHash1198374C": { + "Type": "String", + "Description": "Artifact hash for asset \"480caddfb9aa669df64905982e75c672d967ce9d9ed261ee8c73f6bdcaf97141\"" + }, + "AssetParameters04407a85c5bf6d4da110e25ee35b1f67903f760cd7835965518b0f7ad37e86abS3BucketADE4C6AE": { + "Type": "String", + "Description": "S3 bucket for asset \"04407a85c5bf6d4da110e25ee35b1f67903f760cd7835965518b0f7ad37e86ab\"" + }, + "AssetParameters04407a85c5bf6d4da110e25ee35b1f67903f760cd7835965518b0f7ad37e86abS3VersionKeyF36B0062": { + "Type": "String", + "Description": "S3 key for asset version \"04407a85c5bf6d4da110e25ee35b1f67903f760cd7835965518b0f7ad37e86ab\"" + }, + "AssetParameters04407a85c5bf6d4da110e25ee35b1f67903f760cd7835965518b0f7ad37e86abArtifactHash6DD5E125": { + "Type": "String", + "Description": "Artifact hash for asset \"04407a85c5bf6d4da110e25ee35b1f67903f760cd7835965518b0f7ad37e86ab\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi-import.lit.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi-import.lit.ts new file mode 100644 index 0000000000000..bea2be6c5b05f --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi-import.lit.ts @@ -0,0 +1,124 @@ +import { App, CfnOutput, Construct, NestedStack, NestedStackProps, Stack } from '@aws-cdk/core'; +import { Deployment, Method, MockIntegration, PassthroughBehavior, RestApi, Stage } from '../lib'; + +/** + * This file showcases how to split up a RestApi's Resources and Methods across nested stacks. + * + * The root stack 'RootStack' first defines a RestApi. + * Two nested stacks BooksStack and PetsStack, create corresponding Resources '/books' and '/pets'. + * They are then deployed to a 'prod' Stage via a third nested stack - DeployStack. + * + * To verify this worked, go to the APIGateway + */ + +class RootStack extends Stack { + constructor(scope: Construct) { + super(scope, 'integ-restapi-import-RootStack'); + + const restApi = new RestApi(this, 'RestApi', { + deploy: false, + }); + restApi.root.addMethod('ANY'); + + const petsStack = new PetsStack(this, { + restApiId: restApi.restApiId, + rootResourceId: restApi.restApiRootResourceId, + }); + const booksStack = new BooksStack(this, { + restApiId: restApi.restApiId, + rootResourceId: restApi.restApiRootResourceId, + }); + new DeployStack(this, { + restApiId: restApi.restApiId, + methods: [ ...petsStack.methods, ...booksStack.methods ], + }); + + new CfnOutput(this, 'PetsURL', { + value: `https://${restApi.restApiId}.execute-api.${this.region}.amazonaws.com/prod/pets`, + }); + + new CfnOutput(this, 'BooksURL', { + value: `https://${restApi.restApiId}.execute-api.${this.region}.amazonaws.com/prod/books`, + }); + } +} + +interface ResourceNestedStackProps extends NestedStackProps { + readonly restApiId: string; + + readonly rootResourceId: string; +} + +class PetsStack extends NestedStack { + public readonly methods: Method[] = []; + + constructor(scope: Construct, props: ResourceNestedStackProps) { + super(scope, 'integ-restapi-import-PetsStack', props); + + const api = RestApi.fromRestApiAttributes(this, 'RestApi', { + restApiId: props.restApiId, + rootResourceId: props.rootResourceId, + }); + + const method = api.root.addResource('pets').addMethod('GET', new MockIntegration({ + integrationResponses: [{ + statusCode: '200', + }], + passthroughBehavior: PassthroughBehavior.NEVER, + requestTemplates: { + 'application/json': '{ "statusCode": 200 }', + }, + }), { + methodResponses: [ { statusCode: '200' } ], + }); + + this.methods.push(method); + } +} + +class BooksStack extends NestedStack { + public readonly methods: Method[] = []; + + constructor(scope: Construct, props: ResourceNestedStackProps) { + super(scope, 'integ-restapi-import-BooksStack', props); + + const api = RestApi.fromRestApiAttributes(this, 'RestApi', { + restApiId: props.restApiId, + rootResourceId: props.rootResourceId, + }); + + const method = api.root.addResource('books').addMethod('GET', new MockIntegration({ + integrationResponses: [{ + statusCode: '200', + }], + passthroughBehavior: PassthroughBehavior.NEVER, + requestTemplates: { + 'application/json': '{ "statusCode": 200 }', + }, + }), { + methodResponses: [ { statusCode: '200' } ], + }); + + this.methods.push(method); + } +} + +interface DeployStackProps extends NestedStackProps { + readonly restApiId: string; + + readonly methods?: Method[]; +} + +class DeployStack extends NestedStack { + constructor(scope: Construct, props: DeployStackProps) { + super(scope, 'integ-restapi-import-DeployStack', props); + + const deployment = new Deployment(this, 'Deployment', { + api: RestApi.fromRestApiId(this, 'RestApi', props.restApiId), + }); + (props.methods ?? []).forEach((method) => deployment.node.addDependency(method)); + new Stage(this, 'Stage', { deployment }); + } +} + +new RootStack(new App()); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/test.authorizer.ts b/packages/@aws-cdk/aws-apigateway/test/test.authorizer.ts index ce5a5279228e3..89b6905fddb4e 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.authorizer.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.authorizer.ts @@ -1,12 +1,12 @@ import { Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; -import { Authorizer, RestApi } from '../lib'; +import { Authorizer, IRestApi } from '../lib'; export = { 'isAuthorizer correctly detects an instance of type Authorizer'(test: Test) { class MyAuthorizer extends Authorizer { public readonly authorizerId = 'test-authorizer-id'; - public _attachToApi(_: RestApi): void { + public _attachToApi(_: IRestApi): void { // do nothing } } diff --git a/packages/@aws-cdk/aws-apigateway/test/test.domains.ts b/packages/@aws-cdk/aws-apigateway/test/test.domains.ts index b672565b94358..197978922c5a1 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.domains.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.domains.ts @@ -267,6 +267,20 @@ export = { test.done(); }, + 'domain name cannot contain uppercase letters'(test: Test) { + // GIVEN + const stack = new Stack(); + const certificate = new acm.Certificate(stack, 'cert', { domainName: 'someDomainWithUpercase.domain.com' }); + + // WHEN + test.throws(() => { + new apigw.DomainName(stack, 'someDomain', {domainName: 'someDomainWithUpercase.domain.com', certificate}); + }, /uppercase/); + + // THEN + test.done(); + }, + 'multiple domain names can be added'(test: Test) { // GIVEN const domainName = 'my.domain.com'; diff --git a/packages/@aws-cdk/aws-apigateway/test/test.method.ts b/packages/@aws-cdk/aws-apigateway/test/test.method.ts index e4383ecf768ac..bb0d28b976094 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.method.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.method.ts @@ -928,4 +928,38 @@ export = { test.done(); }, + + '"restApi" and "api" properties return the RestApi correctly'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const api = new apigw.RestApi(stack, 'test-api'); + const method = api.root.addResource('pets').addMethod('GET'); + + // THEN + test.ok(method.restApi); + test.ok(method.api); + test.deepEqual(stack.resolve(method.api.restApiId), stack.resolve(method.restApi.restApiId)); + + test.done(); + }, + + '"restApi" throws an error on imported while "api" returns correctly'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const api = apigw.RestApi.fromRestApiAttributes(stack, 'test-api', { + restApiId: 'test-rest-api-id', + rootResourceId: 'test-root-resource-id', + }); + const method = api.root.addResource('pets').addMethod('GET'); + + // THEN + test.throws(() => method.restApi, /not available on Resource not connected to an instance of RestApi/); + test.ok(method.api); + + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts index d512b924cfe98..4df5bd3fd2755 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts @@ -335,18 +335,6 @@ export = { test.done(); }, - 'fromRestApiId'(test: Test) { - // GIVEN - const stack = new Stack(); - - // WHEN - const imported = apigw.RestApi.fromRestApiId(stack, 'imported-api', 'api-rxt4498f'); - - // THEN - test.deepEqual(stack.resolve(imported.restApiId), 'api-rxt4498f'); - test.done(); - }, - '"url" and "urlForPath" return the URL endpoints of the deployed API'(test: Test) { // GIVEN const stack = new Stack(); @@ -933,4 +921,102 @@ export = { test.done(); }, + + '"restApi" and "api" properties return the RestApi correctly'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const api = new apigw.RestApi(stack, 'test-api'); + const method = api.root.addResource('pets').addMethod('GET'); + + // THEN + test.ok(method.restApi); + test.ok(method.api); + test.deepEqual(stack.resolve(method.api.restApiId), stack.resolve(method.restApi.restApiId)); + + test.done(); + }, + + '"restApi" throws an error on imported while "api" returns correctly'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const api = apigw.RestApi.fromRestApiAttributes(stack, 'test-api', { + restApiId: 'test-rest-api-id', + rootResourceId: 'test-root-resource-id', + }); + const method = api.root.addResource('pets').addMethod('GET'); + + // THEN + test.throws(() => method.restApi, /not available on Resource not connected to an instance of RestApi/); + test.ok(method.api); + + test.done(); + }, + + 'Import': { + 'fromRestApiId()'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const imported = apigw.RestApi.fromRestApiId(stack, 'imported-api', 'api-rxt4498f'); + + // THEN + test.deepEqual(stack.resolve(imported.restApiId), 'api-rxt4498f'); + test.done(); + }, + + 'fromRestApiAttributes()'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const imported = apigw.RestApi.fromRestApiAttributes(stack, 'imported-api', { + restApiId: 'test-restapi-id', + rootResourceId: 'test-root-resource-id', + }); + const resource = imported.root.addResource('pets'); + resource.addMethod('GET'); + + // THEN + expect(stack).to(haveResource('AWS::ApiGateway::Resource', { + PathPart: 'pets', + ParentId: stack.resolve(imported.restApiRootResourceId), + })); + expect(stack).to(haveResource('AWS::ApiGateway::Method', { + HttpMethod: 'GET', + ResourceId: stack.resolve(resource.resourceId), + })); + + test.done(); + }, + }, + + 'SpecRestApi': { + 'add Methods and Resources'(test: Test) { + // GIVEN + const stack = new Stack(); + const api = new apigw.SpecRestApi(stack, 'SpecRestApi', { + apiDefinition: apigw.ApiDefinition.fromInline({ foo: 'bar' }), + }); + + // WHEN + const resource = api.root.addResource('pets'); + resource.addMethod('GET'); + + // THEN + expect(stack).to(haveResource('AWS::ApiGateway::Resource', { + PathPart: 'pets', + ParentId: stack.resolve(api.restApiRootResourceId), + })); + expect(stack).to(haveResource('AWS::ApiGateway::Method', { + HttpMethod: 'GET', + ResourceId: stack.resolve(resource.resourceId), + })); + test.done(); + }, + }, }; diff --git a/packages/@aws-cdk/aws-apigatewayv2/.eslintrc.js b/packages/@aws-cdk/aws-apigatewayv2/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/.eslintrc.js +++ b/packages/@aws-cdk/aws-apigatewayv2/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-appconfig/.eslintrc.js b/packages/@aws-cdk/aws-appconfig/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-appconfig/.eslintrc.js +++ b/packages/@aws-cdk/aws-appconfig/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-applicationautoscaling/.eslintrc.js b/packages/@aws-cdk/aws-applicationautoscaling/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/.eslintrc.js +++ b/packages/@aws-cdk/aws-applicationautoscaling/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-applicationautoscaling/package.json b/packages/@aws-cdk/aws-applicationautoscaling/package.json index 5f4b909ff44b4..64ede897c926b 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/package.json +++ b/packages/@aws-cdk/aws-applicationautoscaling/package.json @@ -66,7 +66,7 @@ "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", - "fast-check": "^1.24.2", + "fast-check": "^1.25.1", "nodeunit": "^0.11.3", "pkglint": "0.0.0" }, diff --git a/packages/@aws-cdk/aws-appmesh/.eslintrc.js b/packages/@aws-cdk/aws-appmesh/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-appmesh/.eslintrc.js +++ b/packages/@aws-cdk/aws-appmesh/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-appstream/.eslintrc.js b/packages/@aws-cdk/aws-appstream/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-appstream/.eslintrc.js +++ b/packages/@aws-cdk/aws-appstream/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-appsync/.eslintrc.js b/packages/@aws-cdk/aws-appsync/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-appsync/.eslintrc.js +++ b/packages/@aws-cdk/aws-appsync/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index bcb0fb3eff572..0c0b27c8e6f92 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -54,6 +54,7 @@ type Mutation { saveCustomer(id: String!, customer: SaveCustomerInput!): Customer removeCustomer(id: String!): Customer saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order + doPostOnAws: String! } ``` @@ -157,6 +158,40 @@ export class ApiStack extends Stack { requestMappingTemplate: MappingTemplate.dynamoDbDeleteItem('id', 'id'), responseMappingTemplate: MappingTemplate.dynamoDbResultItem(), }); + + const httpDS = api.addHttpDataSource('http', 'The http data source', 'https://aws.amazon.com/'); + + httpDS.createResolver({ + typeName: 'Mutation', + fieldName: 'doPostOnAws', + requestMappingTemplate: MappingTemplate.fromString(`{ + "version": "2018-05-29", + "method": "POST", + # if full path is https://api.xxxxxxxxx.com/posts then resourcePath would be /posts + "resourcePath": "/path/123", + "params":{ + "body": $util.toJson($ctx.args), + "headers":{ + "Content-Type": "application/json", + "Authorization": "$ctx.request.headers.Authorization" + } + } + }`), + responseMappingTemplate: MappingTemplate.fromString(` + ## Raise a GraphQL field error in case of a datasource invocation error + #if($ctx.error) + $util.error($ctx.error.message, $ctx.error.type) + #end + ## if the response status code is not 200, then return an error. Else return the body ** + #if($ctx.result.statusCode == 200) + ## If response is 200, return the body. + $ctx.result.body + #else + ## If response is not 200, append the response to error block. + $utils.appendError($ctx.result.body, "$ctx.result.statusCode") + #end + `), + }); } } -``` \ No newline at end of file +``` diff --git a/packages/@aws-cdk/aws-appsync/jest.config.js b/packages/@aws-cdk/aws-appsync/jest.config.js index cd664e1d069e5..d9634b8ea0c5d 100644 --- a/packages/@aws-cdk/aws-appsync/jest.config.js +++ b/packages/@aws-cdk/aws-appsync/jest.config.js @@ -1,2 +1,10 @@ const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); -module.exports = baseConfig; +module.exports = { + ...baseConfig, + coverageThreshold: { + global: { + branches: 1, + statements: 1, + } + } +}; diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 6487d401401b8..2d4f802f72048 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -408,6 +408,21 @@ export class GraphQLApi extends Construct { }); } + /** + * add a new http data source to this API + * @param name The name of the data source + * @param description The description of the data source + * @param endpoint The http endpoint + */ + public addHttpDataSource(name: string, description: string, endpoint: string): HttpDataSource { + return new HttpDataSource(this, `${name}DS`, { + api: this, + description, + endpoint, + name, + }); + } + /** * add a new Lambda data source to this API * @param name The name of the data source @@ -751,6 +766,30 @@ export class DynamoDbDataSource extends BackedDataSource { } } +/** + * Properties for an AppSync http datasource + */ +export interface HttpDataSourceProps extends BaseDataSourceProps { + /** + * The http endpoint + */ + readonly endpoint: string; +} + +/** + * An AppSync datasource backed by a http endpoint + */ +export class HttpDataSource extends BaseDataSource { + constructor(scope: Construct, id: string, props: HttpDataSourceProps) { + super(scope, id, props, { + httpConfig: { + endpoint: props.endpoint, + }, + type: 'HTTP', + }); + } +} + /** * Properties for an AppSync Lambda datasource */ @@ -986,7 +1025,7 @@ export class Assign { * Renders the assignment as a map element. */ public putInMap(map: string): string { - return `$util.qr($${map}.put("${this.attr}", "${this.arg}"))`; + return `$util.qr($${map}.put("${this.attr}", ${this.arg}))`; } } @@ -1057,8 +1096,8 @@ export class PrimaryKey { assignments.push(this.skey.renderAsAssignment()); } return `"key" : { - ${assignments.join(',')} - }`; + ${assignments.join(',')} + }`; } } @@ -1092,14 +1131,19 @@ export class AttributeValues { return new AttributeValuesStep(attr, this.container, this.assignments); } + /** + * Renders the variables required for `renderTemplate`. + */ + public renderVariables(): string { + return `#set($input = ${this.container}) + ${this.assignments.map(a => a.putInMap('input')).join('\n')}`; + } + /** * Renders the attribute value assingments to a VTL string. */ public renderTemplate(): string { - return ` - #set($input = ${this.container}) - ${this.assignments.map(a => a.putInMap('input')).join('\n')} - "attributeValues": $util.dynamodb.toMapValuesJson($input)`; + return '"attributeValues": $util.dynamodb.toMapValuesJson($input)'; } } @@ -1216,12 +1260,14 @@ export abstract class MappingTemplate { * @param values the assignment of Mutation values to the table attributes */ public static dynamoDbPutItem(key: PrimaryKey, values: AttributeValues): MappingTemplate { - return this.fromString(`{ - "version" : "2017-02-28", - "operation" : "PutItem", - ${key.renderTemplate()}, - ${values.renderTemplate()} - }`); + return this.fromString(` + ${values.renderVariables()} + { + "version": "2017-02-28", + "operation": "PutItem", + ${key.renderTemplate()}, + ${values.renderTemplate()} + }`); } /** diff --git a/packages/@aws-cdk/aws-appsync/package.json b/packages/@aws-cdk/aws-appsync/package.json index 9cc5bfa71f0e3..ed431e29e7367 100644 --- a/packages/@aws-cdk/aws-appsync/package.json +++ b/packages/@aws-cdk/aws-appsync/package.json @@ -67,6 +67,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", + "jest": "^25.5.4", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts new file mode 100644 index 0000000000000..37e1ecc0ea57b --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts @@ -0,0 +1,73 @@ +import '@aws-cdk/assert/jest'; +import { MappingTemplate, PrimaryKey, Values } from '../lib'; + +function joined(str: string): string { + return str.replace(/\s+/g, ''); +} + +describe('DynamoDB Mapping Templates', () => { + test('PutItem projecting all', () => { + const template = MappingTemplate.dynamoDbPutItem( + PrimaryKey.partition('id').is('id'), + Values.projecting(), + ); + + const rendered = joined(template.renderTemplate()); + + expect(rendered).toStrictEqual(joined(` + #set($input = $ctx.args) + { + "version" : "2017-02-28", + "operation" : "PutItem", + "key" : { + "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) + }, + "attributeValues": $util.dynamodb.toMapValuesJson($input) + }`), + ); + }); + + test('PutItem with invididual attributes', () => { + const template = MappingTemplate.dynamoDbPutItem( + PrimaryKey.partition('id').is('id'), + Values.attribute('val').is('ctx.args.val'), + ); + + const rendered = joined(template.renderTemplate()); + + expect(rendered).toStrictEqual(joined(` + #set($input = {}) + $util.qr($input.put("val", ctx.args.val)) + { + "version" : "2017-02-28", + "operation" : "PutItem", + "key" : { + "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) + }, + "attributeValues": $util.dynamodb.toMapValuesJson($input) + }`), + ); + }); + + test('PutItem with additional attributes', () => { + const template = MappingTemplate.dynamoDbPutItem( + PrimaryKey.partition('id').is('id'), + Values.projecting().attribute('val').is('ctx.args.val'), + ); + + const rendered = joined(template.renderTemplate()); + + expect(rendered).toStrictEqual(joined(` + #set($input = $ctx.args) + $util.qr($input.put("val", ctx.args.val)) + { + "version" : "2017-02-28", + "operation" : "PutItem", + "key" : { + "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) + }, + "attributeValues": $util.dynamodb.toMapValuesJson($input) + }`), + ); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json index 07215fce52330..9fe51c84d0b28 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json @@ -40,6 +40,12 @@ "PoolD3F588B8": { "Type": "AWS::Cognito::UserPool", "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { "Name": "verified_phone_number", "Priority": 1 }, + { "Name": "verified_email", "Priority": 2 } + ] + }, "AdminCreateUserConfig": { "AllowAdminCreateUserOnly": true }, @@ -100,7 +106,7 @@ "ApiId": { "Fn::GetAtt": ["ApiF70053CD", "ApiId"] }, - "Definition": "type ServiceVersion {\n version: String!\n}\n\ntype Customer {\n id: String!\n name: String!\n}\n\ninput SaveCustomerInput {\n name: String!\n}\n\ntype Order {\n customer: String!\n order: String!\n}\n\ntype Query {\n getServiceVersion: ServiceVersion\n getCustomers: [Customer]\n getCustomer(id: String): Customer\n getCustomerOrdersEq(customer: String): Order\n getCustomerOrdersLt(customer: String): Order\n getCustomerOrdersLe(customer: String): Order\n getCustomerOrdersGt(customer: String): Order\n getCustomerOrdersGe(customer: String): Order\n getCustomerOrdersFilter(customer: String, order: String): Order\n getCustomerOrdersBetween(customer: String, order1: String, order2: String): Order\n}\n\ninput FirstOrderInput {\n product: String!\n quantity: Int!\n}\n\ntype Mutation {\n addCustomer(customer: SaveCustomerInput!): Customer\n saveCustomer(id: String!, customer: SaveCustomerInput!): Customer\n removeCustomer(id: String!): Customer\n saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order\n}" + "Definition": "type ServiceVersion {\n version: String!\n}\n\ntype Customer {\n id: String!\n name: String!\n}\n\ninput SaveCustomerInput {\n name: String!\n}\n\ntype Order {\n customer: String!\n order: String!\n}\n\ntype Query {\n getServiceVersion: ServiceVersion\n getCustomers: [Customer]\n getCustomer(id: String): Customer\n getCustomerOrdersEq(customer: String): Order\n getCustomerOrdersLt(customer: String): Order\n getCustomerOrdersLe(customer: String): Order\n getCustomerOrdersGt(customer: String): Order\n getCustomerOrdersGe(customer: String): Order\n getCustomerOrdersFilter(customer: String, order: String): Order\n getCustomerOrdersBetween(customer: String, order1: String, order2: String): Order\n}\n\ninput FirstOrderInput {\n product: String!\n quantity: Int!\n}\n\ntype Mutation {\n addCustomer(customer: SaveCustomerInput!): Customer\n saveCustomer(id: String!, customer: SaveCustomerInput!): Customer\n removeCustomer(id: String!): Customer\n saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order\n doPostOnAws: String!\n}\n" } }, "ApiNoneDSB4E6495F": { @@ -280,7 +286,7 @@ "TypeName": "Mutation", "DataSourceName": "Customer", "Kind": "UNIT", - "RequestMappingTemplate": "{\n \"version\" : \"2017-02-28\",\n \"operation\" : \"PutItem\",\n \"key\" : {\n \"id\" : $util.dynamodb.toDynamoDBJson($util.autoId())\n },\n \n #set($input = $ctx.args.customer)\n \n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }", + "RequestMappingTemplate": "\n #set($input = $ctx.args.customer)\n \n {\n \"version\": \"2017-02-28\",\n \"operation\": \"PutItem\",\n \"key\" : {\n \"id\" : $util.dynamodb.toDynamoDBJson($util.autoId())\n },\n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }", "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ @@ -301,7 +307,7 @@ "TypeName": "Mutation", "DataSourceName": "Customer", "Kind": "UNIT", - "RequestMappingTemplate": "{\n \"version\" : \"2017-02-28\",\n \"operation\" : \"PutItem\",\n \"key\" : {\n \"id\" : $util.dynamodb.toDynamoDBJson($ctx.args.id)\n },\n \n #set($input = $ctx.args.customer)\n \n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }", + "RequestMappingTemplate": "\n #set($input = $ctx.args.customer)\n \n {\n \"version\": \"2017-02-28\",\n \"operation\": \"PutItem\",\n \"key\" : {\n \"id\" : $util.dynamodb.toDynamoDBJson($ctx.args.id)\n },\n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }", "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ @@ -322,7 +328,7 @@ "TypeName": "Mutation", "DataSourceName": "Customer", "Kind": "UNIT", - "RequestMappingTemplate": "{\n \"version\" : \"2017-02-28\",\n \"operation\" : \"PutItem\",\n \"key\" : {\n \"order\" : $util.dynamodb.toDynamoDBJson($util.autoId()),\"customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer.id)\n },\n \n #set($input = $ctx.args.order)\n $util.qr($input.put(\"referral\", \"referral\"))\n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }", + "RequestMappingTemplate": "\n #set($input = $ctx.args.order)\n $util.qr($input.put(\"referral\", referral))\n {\n \"version\": \"2017-02-28\",\n \"operation\": \"PutItem\",\n \"key\" : {\n \"order\" : $util.dynamodb.toDynamoDBJson($util.autoId()),\"customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer.id)\n },\n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }", "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ @@ -585,6 +591,67 @@ "ApiSchema510EECD7" ] }, + "ApihttpDSServiceRole8B5C9457": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "appsync.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ApihttpDS91F12990": { + "Type": "AWS::AppSync::DataSource", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "Name": "http", + "Type": "HTTP", + "Description": "The http data source", + "HttpConfig": { + "Endpoint": "https://aws.amazon.com/" + }, + "ServiceRoleArn": { + "Fn::GetAtt": [ + "ApihttpDSServiceRole8B5C9457", + "Arn" + ] + } + } + }, + "ApihttpDSMutationdoPostOnAwsResolverA9027953": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "FieldName": "doPostOnAws", + "TypeName": "Mutation", + "DataSourceName": "http", + "Kind": "UNIT", + "RequestMappingTemplate": "{\n \"version\": \"2018-05-29\",\n \"method\": \"POST\",\n # if full path is https://api.xxxxxxxxx.com/posts then resourcePath would be /posts\n \"resourcePath\": \"/path/123\",\n \"params\":{\n \"body\": $util.toJson($ctx.args),\n \"headers\":{\n \"Content-Type\": \"application/json\",\n \"Authorization\": \"$ctx.request.headers.Authorization\"\n }\n }\n }", + "ResponseMappingTemplate": "\n ## Raise a GraphQL field error in case of a datasource invocation error\n #if($ctx.error)\n $util.error($ctx.error.message, $ctx.error.type)\n #end\n ## if the response status code is not 200, then return an error. Else return the body **\n #if($ctx.result.statusCode == 200)\n ## If response is 200, return the body.\n $ctx.result.body\n #else\n ## If response is not 200, append the response to error block.\n $utils.appendError($ctx.result.body, \"$ctx.result.statusCode\")\n #end\n " + }, + "DependsOn": [ + "ApihttpDS91F12990", + "ApiSchema510EECD7" + ] + }, "CustomerTable260DCC08": { "Type": "AWS::DynamoDB::Table", "Properties": { @@ -634,4 +701,4 @@ "DeletionPolicy": "Delete" } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts index 07cf7f028d41c..fb2be9eeac531 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts @@ -148,4 +148,38 @@ orderDS.createResolver({ responseMappingTemplate: MappingTemplate.dynamoDbResultList(), }); -app.synth(); \ No newline at end of file +const httpDS = api.addHttpDataSource('http', 'The http data source', 'https://aws.amazon.com/'); + +httpDS.createResolver({ + typeName: 'Mutation', + fieldName: 'doPostOnAws', + requestMappingTemplate: MappingTemplate.fromString(`{ + "version": "2018-05-29", + "method": "POST", + # if full path is https://api.xxxxxxxxx.com/posts then resourcePath would be /posts + "resourcePath": "/path/123", + "params":{ + "body": $util.toJson($ctx.args), + "headers":{ + "Content-Type": "application/json", + "Authorization": "$ctx.request.headers.Authorization" + } + } + }`), + responseMappingTemplate: MappingTemplate.fromString(` + ## Raise a GraphQL field error in case of a datasource invocation error + #if($ctx.error) + $util.error($ctx.error.message, $ctx.error.type) + #end + ## if the response status code is not 200, then return an error. Else return the body ** + #if($ctx.result.statusCode == 200) + ## If response is 200, return the body. + $ctx.result.body + #else + ## If response is not 200, append the response to error block. + $utils.appendError($ctx.result.body, "$ctx.result.statusCode") + #end + `), +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-appsync/test/schema.graphql b/packages/@aws-cdk/aws-appsync/test/schema.graphql index 5f82e9279ccbc..24af9a154ec59 100644 --- a/packages/@aws-cdk/aws-appsync/test/schema.graphql +++ b/packages/@aws-cdk/aws-appsync/test/schema.graphql @@ -39,4 +39,5 @@ type Mutation { saveCustomer(id: String!, customer: SaveCustomerInput!): Customer removeCustomer(id: String!): Customer saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order -} \ No newline at end of file + doPostOnAws: String! +} diff --git a/packages/@aws-cdk/aws-athena/.eslintrc.js b/packages/@aws-cdk/aws-athena/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-athena/.eslintrc.js +++ b/packages/@aws-cdk/aws-athena/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-autoscaling-common/.eslintrc.js b/packages/@aws-cdk/aws-autoscaling-common/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/.eslintrc.js +++ b/packages/@aws-cdk/aws-autoscaling-common/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-autoscaling-common/package.json b/packages/@aws-cdk/aws-autoscaling-common/package.json index 4c31c377051ca..ccabb1ecb87e2 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/package.json +++ b/packages/@aws-cdk/aws-autoscaling-common/package.json @@ -62,7 +62,7 @@ "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "fast-check": "^1.24.2", + "fast-check": "^1.25.1", "nodeunit": "^0.11.3", "pkglint": "0.0.0" }, diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/.eslintrc.js b/packages/@aws-cdk/aws-autoscaling-hooktargets/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/.eslintrc.js +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-autoscaling/.eslintrc.js b/packages/@aws-cdk/aws-autoscaling/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-autoscaling/.eslintrc.js +++ b/packages/@aws-cdk/aws-autoscaling/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-autoscalingplans/.eslintrc.js b/packages/@aws-cdk/aws-autoscalingplans/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-autoscalingplans/.eslintrc.js +++ b/packages/@aws-cdk/aws-autoscalingplans/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-backup/.eslintrc.js b/packages/@aws-cdk/aws-backup/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-backup/.eslintrc.js +++ b/packages/@aws-cdk/aws-backup/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-batch/.eslintrc.js b/packages/@aws-cdk/aws-batch/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-batch/.eslintrc.js +++ b/packages/@aws-cdk/aws-batch/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-budgets/.eslintrc.js b/packages/@aws-cdk/aws-budgets/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-budgets/.eslintrc.js +++ b/packages/@aws-cdk/aws-budgets/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cassandra/.eslintrc.js b/packages/@aws-cdk/aws-cassandra/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-cassandra/.eslintrc.js +++ b/packages/@aws-cdk/aws-cassandra/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-ce/.eslintrc.js b/packages/@aws-cdk/aws-ce/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-ce/.eslintrc.js +++ b/packages/@aws-cdk/aws-ce/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-certificatemanager/.eslintrc.js b/packages/@aws-cdk/aws-certificatemanager/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-certificatemanager/.eslintrc.js +++ b/packages/@aws-cdk/aws-certificatemanager/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-certificatemanager/README.md b/packages/@aws-cdk/aws-certificatemanager/README.md index 9545a7bf21224..7afb3c8f82251 100644 --- a/packages/@aws-cdk/aws-certificatemanager/README.md +++ b/packages/@aws-cdk/aws-certificatemanager/README.md @@ -1,4 +1,4 @@ -## Amazon Certificate Manager Construct Library +## AWS Certificate Manager Construct Library --- @@ -35,7 +35,7 @@ email on one of a number of predefined domains and following the instructions in the email. See [Validate with Email](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate-email.html) -in the Amazon Certificate Manager User Guide. +in the AWS Certificate Manager User Guide. ### DNS validation @@ -43,7 +43,7 @@ DNS-validated certificates are validated by configuring appropriate DNS records for your domain. See [Validate with DNS](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate-dns.html) -in the Amazon Certificate Manager User Guide. +in the AWS Certificate Manager User Guide. ### Automatic DNS-validated certificates using Route53 diff --git a/packages/@aws-cdk/aws-chatbot/.eslintrc.js b/packages/@aws-cdk/aws-chatbot/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-chatbot/.eslintrc.js +++ b/packages/@aws-cdk/aws-chatbot/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cloud9/.eslintrc.js b/packages/@aws-cdk/aws-cloud9/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-cloud9/.eslintrc.js +++ b/packages/@aws-cdk/aws-cloud9/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cloudformation/.eslintrc.js b/packages/@aws-cdk/aws-cloudformation/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-cloudformation/.eslintrc.js +++ b/packages/@aws-cdk/aws-cloudformation/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cloudfront/.eslintrc.js b/packages/@aws-cdk/aws-cloudfront/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-cloudfront/.eslintrc.js +++ b/packages/@aws-cdk/aws-cloudfront/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cloudfront/README.md b/packages/@aws-cdk/aws-cloudfront/README.md index 32c12c9959181..b0ae2f6f79ba3 100644 --- a/packages/@aws-cdk/aws-cloudfront/README.md +++ b/packages/@aws-cdk/aws-cloudfront/README.md @@ -49,7 +49,7 @@ Example: #### ACM certificate -You can change the default certificate by one stored Amazon Certificate Manager, or ACM. +You can change the default certificate by one stored AWS Certificate Manager, or ACM. Those certificate can either be generated by AWS, or purchased by another CA imported into ACM. For more information, see [the aws-certificatemanager module documentation](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-certificatemanager-readme.html) or [Importing Certificates into AWS Certificate Manager](https://docs.aws.amazon.com/acm/latest/userguide/import-certificate.html) in the AWS Certificate Manager User Guide. diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index 2fafc2b6653ee..bf719b1b3d869 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.691.0", + "aws-sdk": "^2.699.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudtrail/.eslintrc.js b/packages/@aws-cdk/aws-cloudtrail/.eslintrc.js index d8fd56c07016a..ced30c8435282 100644 --- a/packages/@aws-cdk/aws-cloudtrail/.eslintrc.js +++ b/packages/@aws-cdk/aws-cloudtrail/.eslintrc.js @@ -1,4 +1,4 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts b/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts index 3b3f39d64eb4c..a7c18b6b87609 100644 --- a/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts +++ b/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts @@ -345,7 +345,7 @@ export class Trail extends Resource { * @default false */ public logAllLambdaDataEvents(options: AddEventSelectorOptions = {}) { - return this.addEventSelector(DataResourceType.LAMBDA_FUNCTION, [ 'arn:aws:lambda' ], options); + return this.addEventSelector(DataResourceType.LAMBDA_FUNCTION, [ `arn:${this.stack.partition}:lambda` ], options); } /** @@ -372,7 +372,7 @@ export class Trail extends Resource { * @default false */ public logAllS3DataEvents(options: AddEventSelectorOptions = {}) { - return this.addEventSelector(DataResourceType.S3_OBJECT, [ 'arn:aws:s3:::' ], options); + return this.addEventSelector(DataResourceType.S3_OBJECT, [ `arn:${this.stack.partition}:s3:::` ], options); } /** diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index e0ee07263ef09..7562725de6095 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/package.json @@ -64,7 +64,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "aws-sdk": "^2.691.0", + "aws-sdk": "^2.699.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts b/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts index 50c2b766bb4c3..b78f6a5f5a741 100644 --- a/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts +++ b/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts @@ -257,7 +257,20 @@ describe('cloudtrail', () => { { DataResources: [{ Type: 'AWS::S3::Object', - Values: [ 'arn:aws:s3:::' ], + Values: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':s3:::', + ], + ], + }, + ], }], IncludeManagementEvents: ABSENT, ReadWriteType: ABSENT, @@ -331,7 +344,20 @@ describe('cloudtrail', () => { { DataResources: [{ Type: 'AWS::S3::Object', - Values: [ 'arn:aws:s3:::' ], + Values: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':s3:::', + ], + ], + }, + ], }], IncludeManagementEvents: false, ReadWriteType: 'ReadOnly', @@ -391,7 +417,20 @@ describe('cloudtrail', () => { { DataResources: [{ Type: 'AWS::Lambda::Function', - Values: [ 'arn:aws:lambda' ], + Values: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':lambda', + ], + ], + }, + ], }], }, ], diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/.eslintrc.js b/packages/@aws-cdk/aws-cloudwatch-actions/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/.eslintrc.js +++ b/packages/@aws-cdk/aws-cloudwatch-actions/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cloudwatch/.eslintrc.js b/packages/@aws-cdk/aws-cloudwatch/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-cloudwatch/.eslintrc.js +++ b/packages/@aws-cdk/aws-cloudwatch/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-codebuild/.eslintrc.js b/packages/@aws-cdk/aws-codebuild/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-codebuild/.eslintrc.js +++ b/packages/@aws-cdk/aws-codebuild/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index 45ddf7bbf51f0..6f986a4237889 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -70,7 +70,7 @@ "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sqs": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.691.0", + "aws-sdk": "^2.699.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codecommit/.eslintrc.js b/packages/@aws-cdk/aws-codecommit/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-codecommit/.eslintrc.js +++ b/packages/@aws-cdk/aws-codecommit/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index 01da8326c2cfe..41dd725f755fc 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -70,7 +70,7 @@ "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.691.0", + "aws-sdk": "^2.699.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codedeploy/.eslintrc.js b/packages/@aws-cdk/aws-codedeploy/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-codedeploy/.eslintrc.js +++ b/packages/@aws-cdk/aws-codedeploy/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-codeguruprofiler/.eslintrc.js b/packages/@aws-cdk/aws-codeguruprofiler/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/.eslintrc.js +++ b/packages/@aws-cdk/aws-codeguruprofiler/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-codepipeline-actions/.eslintrc.js b/packages/@aws-cdk/aws-codepipeline-actions/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/.eslintrc.js +++ b/packages/@aws-cdk/aws-codepipeline-actions/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-codepipeline/.eslintrc.js b/packages/@aws-cdk/aws-codepipeline/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-codepipeline/.eslintrc.js +++ b/packages/@aws-cdk/aws-codepipeline/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-codestar/.eslintrc.js b/packages/@aws-cdk/aws-codestar/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-codestar/.eslintrc.js +++ b/packages/@aws-cdk/aws-codestar/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-codestarconnections/.eslintrc.js b/packages/@aws-cdk/aws-codestarconnections/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-codestarconnections/.eslintrc.js +++ b/packages/@aws-cdk/aws-codestarconnections/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-codestarnotifications/.eslintrc.js b/packages/@aws-cdk/aws-codestarnotifications/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-codestarnotifications/.eslintrc.js +++ b/packages/@aws-cdk/aws-codestarnotifications/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cognito/.eslintrc.js b/packages/@aws-cdk/aws-cognito/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-cognito/.eslintrc.js +++ b/packages/@aws-cdk/aws-cognito/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index c063e532b26a7..5f67c80b204b8 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -33,6 +33,7 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw - [Attributes](#attributes) - [Security](#security) - [Multi-factor Authentication](#multi-factor-authentication-mfa) + - [Account Recovery Settings](#account-recovery-settings) - [Emails](#emails) - [Lambda Triggers](#lambda-triggers) - [Import](#importing-user-pools) @@ -268,6 +269,18 @@ new UserPool(this, 'myuserpool', { Note that, `tempPasswordValidity` can be specified only in whole days. Specifying fractional days would throw an error. +#### Account Recovery Settings + +User pools can be configured on which method a user should use when recovering the password for their account. This +can either be email and/or SMS. Read more at [Recovering User Accounts](https://docs.aws.amazon.com/cognito/latest/developerguide/how-to-recover-a-user-account.html) + +```ts +new UserPool(this, 'UserPool', { + ..., + accountRecoverySettings: AccountRecovery.EMAIL_ONLY, +}) +``` + ### Emails Cognito sends emails to users in the user pool, when particular actions take place, such as welcome emails, invitation diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts index b4b70c1c82a4a..c584ca8be7e46 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts @@ -205,9 +205,16 @@ export interface UserPoolClientOptions { */ readonly authFlows?: AuthFlow; + /** + * Turns off all OAuth interactions for this client. + * @default false + */ + readonly disableOAuth?: boolean; + /** * OAuth settings for this to client to interact with the app. - * @default - see defaults in `OAuthSettings` + * An error is thrown when this is specified and `disableOAuth` is set. + * @default - see defaults in `OAuthSettings`. meaningless if `disableOAuth` is set. */ readonly oAuth?: OAuthSettings; @@ -284,6 +291,10 @@ export class UserPoolClient extends Resource implements IUserPoolClient { constructor(scope: Construct, id: string, props: UserPoolClientProps) { super(scope, id); + if (props.disableOAuth && props.oAuth) { + throw new Error('OAuth settings cannot be specified when disableOAuth is set.'); + } + this.oAuthFlows = props.oAuth?.flows ?? { implicitCodeGrant: true, authorizationCodeGrant: true, @@ -303,10 +314,10 @@ export class UserPoolClient extends Resource implements IUserPoolClient { generateSecret: props.generateSecret, userPoolId: props.userPool.userPoolId, explicitAuthFlows: this.configureAuthFlows(props), - allowedOAuthFlows: this.configureOAuthFlows(), - allowedOAuthScopes: this.configureOAuthScopes(props.oAuth), + allowedOAuthFlows: props.disableOAuth ? undefined : this.configureOAuthFlows(), + allowedOAuthScopes: props.disableOAuth ? undefined : this.configureOAuthScopes(props.oAuth), callbackUrLs: callbackUrls && callbackUrls.length > 0 ? callbackUrls : undefined, - allowedOAuthFlowsUserPoolClient: props.oAuth ? true : undefined, + allowedOAuthFlowsUserPoolClient: !props.disableOAuth, preventUserExistenceErrors: this.configurePreventUserExistenceErrors(props.preventUserExistenceErrors), supportedIdentityProviders: this.configureIdentityProviders(props), }); diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index bfd38a9bd2b36..6ff23e96dde5d 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -385,6 +385,47 @@ export interface EmailSettings { readonly replyTo?: string; } +/** + * How will a user be able to recover their account? + * + * When a user forgets their password, they can have a code sent to their verified email or verified phone to recover their account. + * You can choose the preferred way to send codes below. + * We recommend not allowing phone to be used for both password resets and multi-factor authentication (MFA). + * + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/how-to-recover-a-user-account.html + */ +export enum AccountRecovery { + /** + * Email if available, otherwise phone, but donโ€™t allow a user to reset their password via phone if they are also using it for MFA + */ + EMAIL_AND_PHONE_WITHOUT_MFA, + + /** + * Phone if available, otherwise email, but donโ€™t allow a user to reset their password via phone if they are also using it for MFA + */ + PHONE_WITHOUT_MFA_AND_EMAIL, + + /** + * Email only + */ + EMAIL_ONLY, + + /** + * Phone only, but donโ€™t allow a user to reset their password via phone if they are also using it for MFA + */ + PHONE_ONLY_WITHOUT_MFA, + + /** + * (Not Recommended) Phone if available, otherwise email, and do allow a user to reset their password via phone if they are also using it for MFA. + */ + PHONE_AND_EMAIL, + + /** + * None โ€“ users will have to contact an administrator to reset their passwords + */ + NONE, +} + /** * Props for the UserPool construct */ @@ -509,6 +550,13 @@ export interface UserPoolProps { * @default true */ readonly signInCaseSensitive?: boolean; + + /** + * How will a user be able to recover their account? + * + * @default AccountRecovery.PHONE_WITHOUT_MFA_AND_EMAIL + */ + readonly accountRecovery?: AccountRecovery; } /** @@ -622,7 +670,7 @@ export class UserPool extends UserPoolBase { */ public readonly userPoolProviderUrl: string; - private triggers: CfnUserPool.LambdaConfigProperty = { }; + private triggers: CfnUserPool.LambdaConfigProperty = {}; constructor(scope: Construct, id: string, props: UserPoolProps = {}) { super(scope, id); @@ -683,6 +731,7 @@ export class UserPool extends UserPoolBase { usernameConfiguration: undefinedIfNoKeys({ caseSensitive: props.signInCaseSensitive, }), + accountRecoverySetting: this.accountRecovery(props), }); this.userPoolId = userPool.ref; @@ -908,6 +957,42 @@ export class UserPool extends UserPoolBase { } return schema; } + + private accountRecovery(props: UserPoolProps): undefined | CfnUserPool.AccountRecoverySettingProperty { + const accountRecovery = props.accountRecovery ?? AccountRecovery.PHONE_WITHOUT_MFA_AND_EMAIL; + switch (accountRecovery) { + case AccountRecovery.EMAIL_AND_PHONE_WITHOUT_MFA: + return { + recoveryMechanisms: [ + { name: 'verified_email', priority: 1 }, + { name: 'verified_phone_number', priority: 2 }, + ], + }; + case AccountRecovery.PHONE_WITHOUT_MFA_AND_EMAIL: + return { + recoveryMechanisms: [ + { name: 'verified_phone_number', priority: 1 }, + { name: 'verified_email', priority: 2 }, + ], + }; + case AccountRecovery.EMAIL_ONLY: + return { + recoveryMechanisms: [{ name: 'verified_email', priority: 1 }], + }; + case AccountRecovery.PHONE_ONLY_WITHOUT_MFA: + return { + recoveryMechanisms: [{ name: 'verified_phone_number', priority: 1 }], + }; + case AccountRecovery.NONE: + return { + recoveryMechanisms: [{ name: 'admin_only', priority: 1 }], + }; + case AccountRecovery.PHONE_AND_EMAIL: + return undefined; + default: + throw new Error(`Unsupported AccountRecovery type - ${accountRecovery}`); + } + } } function undefinedIfNoKeys(struct: object): object | undefined { diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.expected.json index c39124006db33..8b5246dfedf58 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.expected.json @@ -40,6 +40,12 @@ "myuserpool01998219": { "Type": "AWS::Cognito::UserPool", "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { "Name": "verified_phone_number", "Priority": 1 }, + { "Name": "verified_email", "Priority": 2 } + ] + }, "AdminCreateUserConfig": { "AllowAdminCreateUserOnly": true }, diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-domain-cfdist.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-domain-cfdist.expected.json index 15fd0ce903e93..f51fe3b47866a 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-domain-cfdist.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-domain-cfdist.expected.json @@ -40,6 +40,12 @@ "UserPool6BA7E5F2": { "Type": "AWS::Cognito::UserPool", "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { "Name": "verified_phone_number", "Priority": 1 }, + { "Name": "verified_email", "Priority": 2 } + ] + }, "AdminCreateUserConfig": { "AllowAdminCreateUserOnly": true }, diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-domain-signinurl.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-domain-signinurl.expected.json index 254b68b5d32b1..6bb3d7edab140 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-domain-signinurl.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-domain-signinurl.expected.json @@ -40,6 +40,12 @@ "UserPool6BA7E5F2": { "Type": "AWS::Cognito::UserPool", "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { "Name": "verified_phone_number", "Priority": 1 }, + { "Name": "verified_email", "Priority": 2 } + ] + }, "AdminCreateUserConfig": { "AllowAdminCreateUserOnly": true }, diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.expected.json index 7625b4a9a80d7..5e2c8662f3f60 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.expected.json @@ -680,6 +680,12 @@ "myuserpool01998219": { "Type": "AWS::Cognito::UserPool", "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { "Name": "verified_phone_number", "Priority": 1 }, + { "Name": "verified_email", "Priority": 2 } + ] + }, "AdminCreateUserConfig": { "AllowAdminCreateUserOnly": false, "InviteMessageTemplate": { diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json index c826a9380e222..3fa00974541cf 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json @@ -40,6 +40,12 @@ "pool056F3F7E": { "Type": "AWS::Cognito::UserPool", "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { "Name": "verified_phone_number", "Priority": 1 }, + { "Name": "verified_email", "Priority": 2 } + ] + }, "AdminCreateUserConfig": { "AllowAdminCreateUserOnly": true }, @@ -73,6 +79,7 @@ "implicit", "code" ], + "AllowedOAuthFlowsUserPoolClient": true, "AllowedOAuthScopes": [ "profile", "phone", diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-code.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-code.expected.json index b14204b367441..90d858978f043 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-code.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-code.expected.json @@ -40,6 +40,12 @@ "myuserpool01998219": { "Type": "AWS::Cognito::UserPool", "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { "Name": "verified_phone_number", "Priority": 1 }, + { "Name": "verified_email", "Priority": 2 } + ] + }, "AdminCreateUserConfig": { "AllowAdminCreateUserOnly": false }, @@ -87,6 +93,7 @@ "implicit", "code" ], + "AllowedOAuthFlowsUserPoolClient": true, "AllowedOAuthScopes": [ "profile", "phone", diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-link.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-link.expected.json index 02893c7ef113f..45661be1e0766 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-link.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-link.expected.json @@ -40,6 +40,12 @@ "myuserpool01998219": { "Type": "AWS::Cognito::UserPool", "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { "Name": "verified_phone_number", "Priority": 1 }, + { "Name": "verified_email", "Priority": 2 } + ] + }, "AdminCreateUserConfig": { "AllowAdminCreateUserOnly": false }, @@ -94,6 +100,7 @@ "implicit", "code" ], + "AllowedOAuthFlowsUserPoolClient": true, "AllowedOAuthScopes": [ "profile", "phone", diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool.expected.json index 075fb3542f6ad..f2beef72d6eb4 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool.expected.json @@ -40,6 +40,12 @@ "myuserpool01998219": { "Type": "AWS::Cognito::UserPool", "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { "Name": "verified_phone_number", "Priority": 1 }, + { "Name": "verified_email", "Priority": 2 } + ] + }, "AdminCreateUserConfig": { "AllowAdminCreateUserOnly": true }, diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts index 81b08dbec3750..dc44777ebce7d 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts @@ -409,4 +409,48 @@ describe('User Pool Client', () => { SupportedIdentityProviders: [ 'COGNITO', 'Facebook', 'LoginWithAmazon' ], }); }); + + test('disableOAuth', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + pool.addClient('OAuthDisabled', { + userPoolClientName: 'OAuthDisabled', + disableOAuth: true, + }); + pool.addClient('OAuthEnabled', { + userPoolClientName: 'OAuthEnabled', + disableOAuth: false, + }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { + ClientName: 'OAuthDisabled', + AllowedOAuthFlows: ABSENT, + AllowedOAuthScopes: ABSENT, + AllowedOAuthFlowsUserPoolClient: false, + }); + expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { + ClientName: 'OAuthEnabled', + AllowedOAuthFlows: [ 'implicit', 'code' ], + AllowedOAuthScopes: [ 'profile', 'phone', 'email', 'openid', 'aws.cognito.signin.user.admin' ], + AllowedOAuthFlowsUserPoolClient: true, + }); + }); + + test('fails when oAuth is specified but is disableOAuth is set', () => { + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + expect(() => pool.addClient('Client', { + disableOAuth: true, + oAuth: { + flows: { + authorizationCodeGrant: true, + }, + }, + })).toThrow(/disableOAuth is set/); + }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts index 61eb7a0ed229c..801879f6ea55a 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts @@ -3,7 +3,7 @@ import { ABSENT } from '@aws-cdk/assert/lib/assertions/have-resource'; import { Role } from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import { CfnParameter, Construct, Duration, Stack, Tag } from '@aws-cdk/core'; -import { Mfa, NumberAttribute, StringAttribute, UserPool, UserPoolIdentityProvider, UserPoolOperation, VerificationEmailStyle } from '../lib'; +import { AccountRecovery, Mfa, NumberAttribute, StringAttribute, UserPool, UserPoolIdentityProvider, UserPoolOperation, VerificationEmailStyle } from '../lib'; describe('User Pool', () => { test('default setup', () => { @@ -975,3 +975,123 @@ function fooFunction(scope: Construct, name: string): lambda.IFunction { handler: 'index.handler', }); } + +describe('AccountRecoverySetting should be configured correctly', () => { + test('EMAIL_AND_PHONE_WITHOUT_MFA', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new UserPool(stack, 'pool', { accountRecovery: AccountRecovery.EMAIL_AND_PHONE_WITHOUT_MFA }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPool', { + AccountRecoverySetting: { + RecoveryMechanisms: [ + { Name: 'verified_email', Priority: 1 }, + { Name: 'verified_phone_number', Priority: 2 }, + ], + }, + }); + }); + + test('PHONE_WITHOUT_MFA_AND_EMAIL', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new UserPool(stack, 'pool', { accountRecovery: AccountRecovery.PHONE_WITHOUT_MFA_AND_EMAIL }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPool', { + AccountRecoverySetting: { + RecoveryMechanisms: [ + { Name: 'verified_phone_number', Priority: 1 }, + { Name: 'verified_email', Priority: 2 }, + ], + }, + }); + }); + + test('EMAIL_ONLY', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new UserPool(stack, 'pool', { accountRecovery: AccountRecovery.EMAIL_ONLY }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPool', { + AccountRecoverySetting: { + RecoveryMechanisms: [ + { Name: 'verified_email', Priority: 1 }, + ], + }, + }); + }); + + test('PHONE_ONLY_WITHOUT_MFA', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new UserPool(stack, 'pool', { accountRecovery: AccountRecovery.PHONE_ONLY_WITHOUT_MFA }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPool', { + AccountRecoverySetting: { + RecoveryMechanisms: [ + { Name: 'verified_phone_number', Priority: 1 }, + ], + }, + }); + }); + + test('NONE', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new UserPool(stack, 'pool', { accountRecovery: AccountRecovery.NONE }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPool', { + AccountRecoverySetting: { + RecoveryMechanisms: [ + { Name: 'admin_only', Priority: 1 }, + ], + }, + }); + }); + + test('PHONE_AND_EMAIL', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new UserPool(stack, 'pool', { accountRecovery: AccountRecovery.PHONE_AND_EMAIL }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPool', { + AccountRecoverySetting: ABSENT, + }); + }); + + test('default', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new UserPool(stack, 'pool'); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPool', { + AccountRecoverySetting: { + RecoveryMechanisms: [ + { Name: 'verified_phone_number', Priority: 1 }, + { Name: 'verified_email', Priority: 2 }, + ], + }, + }); + }); +}); diff --git a/packages/@aws-cdk/aws-config/.eslintrc.js b/packages/@aws-cdk/aws-config/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-config/.eslintrc.js +++ b/packages/@aws-cdk/aws-config/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-datapipeline/.eslintrc.js b/packages/@aws-cdk/aws-datapipeline/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-datapipeline/.eslintrc.js +++ b/packages/@aws-cdk/aws-datapipeline/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-dax/.eslintrc.js b/packages/@aws-cdk/aws-dax/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-dax/.eslintrc.js +++ b/packages/@aws-cdk/aws-dax/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-detective/.eslintrc.js b/packages/@aws-cdk/aws-detective/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-detective/.eslintrc.js +++ b/packages/@aws-cdk/aws-detective/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-directoryservice/.eslintrc.js b/packages/@aws-cdk/aws-directoryservice/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-directoryservice/.eslintrc.js +++ b/packages/@aws-cdk/aws-directoryservice/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-dlm/.eslintrc.js b/packages/@aws-cdk/aws-dlm/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-dlm/.eslintrc.js +++ b/packages/@aws-cdk/aws-dlm/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-dms/.eslintrc.js b/packages/@aws-cdk/aws-dms/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-dms/.eslintrc.js +++ b/packages/@aws-cdk/aws-dms/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-docdb/.eslintrc.js b/packages/@aws-cdk/aws-docdb/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-docdb/.eslintrc.js +++ b/packages/@aws-cdk/aws-docdb/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-dynamodb-global/.eslintrc.js b/packages/@aws-cdk/aws-dynamodb-global/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/.eslintrc.js +++ b/packages/@aws-cdk/aws-dynamodb-global/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-dynamodb/.eslintrc.js b/packages/@aws-cdk/aws-dynamodb/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-dynamodb/.eslintrc.js +++ b/packages/@aws-cdk/aws-dynamodb/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index 00d8eb67b9398..c5dc0819124b4 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -64,8 +64,8 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/jest": "^25.2.3", - "aws-sdk": "^2.691.0", + "@types/jest": "^26.0.0", + "aws-sdk": "^2.699.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-ec2/.eslintrc.js b/packages/@aws-cdk/aws-ec2/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-ec2/.eslintrc.js +++ b/packages/@aws-cdk/aws-ec2/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index 450cd9fc52e8a..a49ff09fe6634 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -297,6 +297,7 @@ export class InterfaceVpcEndpointAwsService implements IInterfaceVpcEndpointServ public static readonly STORAGE_GATEWAY = new InterfaceVpcEndpointAwsService('storagegateway'); public static readonly REKOGNITION = new InterfaceVpcEndpointAwsService('rekognition'); public static readonly REKOGNITION_FIPS = new InterfaceVpcEndpointAwsService('rekognition-fips'); + public static readonly STEP_FUNCTIONS = new InterfaceVpcEndpointAwsService('states'); /** * The name of the service. diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index 7bed5e57ba6f5..458f2d1b4bc2e 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -231,6 +231,7 @@ "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.STORAGE_GATEWAY", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.REKOGNITION", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.REKOGNITION_FIPS", + "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.STEP_FUNCTIONS", "docs-public-apis:@aws-cdk/aws-ec2.Port.toString", "docs-public-apis:@aws-cdk/aws-ec2.PrivateSubnet.fromPrivateSubnetAttributes", "docs-public-apis:@aws-cdk/aws-ec2.PublicSubnet.fromPublicSubnetAttributes", diff --git a/packages/@aws-cdk/aws-ecr-assets/.eslintrc.js b/packages/@aws-cdk/aws-ecr-assets/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-ecr-assets/.eslintrc.js +++ b/packages/@aws-cdk/aws-ecr-assets/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-ecr/.eslintrc.js b/packages/@aws-cdk/aws-ecr/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-ecr/.eslintrc.js +++ b/packages/@aws-cdk/aws-ecr/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-ecs-patterns/.eslintrc.js b/packages/@aws-cdk/aws-ecs-patterns/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/.eslintrc.js +++ b/packages/@aws-cdk/aws-ecs-patterns/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-ecs/.eslintrc.js b/packages/@aws-cdk/aws-ecs/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-ecs/.eslintrc.js +++ b/packages/@aws-cdk/aws-ecs/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index d492687c3263d..9d89a1f03704f 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -24,7 +24,7 @@ export interface Ec2ServiceProps extends BaseServiceOptions { * * This property is only used for tasks that use the awsvpc network mode. * - * @default - Use subnet default. + * @default false */ readonly assignPublicIp?: boolean; diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts index 185bc800e5da9..4633a4b42fd57 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts @@ -21,7 +21,7 @@ export interface FargateServiceProps extends BaseServiceOptions { * * If true, each task will receive a public IP address. * - * @default - Use subnet default. + * @default false */ readonly assignPublicIp?: boolean; diff --git a/packages/@aws-cdk/aws-efs/.eslintrc.js b/packages/@aws-cdk/aws-efs/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-efs/.eslintrc.js +++ b/packages/@aws-cdk/aws-efs/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-eks-legacy/.eslintrc.js b/packages/@aws-cdk/aws-eks-legacy/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-eks-legacy/.eslintrc.js +++ b/packages/@aws-cdk/aws-eks-legacy/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-eks/.eslintrc.js b/packages/@aws-cdk/aws-eks/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-eks/.eslintrc.js +++ b/packages/@aws-cdk/aws-eks/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts index 18dfaa4716752..745dcc799b954 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts @@ -23,13 +23,13 @@ export class ClusterResource extends Construct { public readonly attrOpenIdConnectIssuerUrl: string; public readonly attrOpenIdConnectIssuer: string; public readonly ref: string; - /** * The IAM role which created the cluster. Initially this is the only IAM role * that gets administrator privilages on the cluster (`system:masters`), and * will be able to issue `kubectl` commands against it. */ - private readonly creationRole: iam.Role; + public readonly creationRole: iam.Role; + private readonly trustedPrincipals: string[] = []; constructor(scope: Construct, id: string, props: CfnClusterProps) { @@ -142,28 +142,23 @@ export class ClusterResource extends Construct { } /** - * Returns the ARN of the cluster creation role and grants `trustedRole` - * permissions to assume this role. + * Grants `trustedRole` permissions to assume the creation role. */ - public getCreationRoleArn(trustedRole?: iam.IRole): string { - - if (!trustedRole) { - return this.creationRole.roleArn; + public addTrustedRole(trustedRole: iam.IRole): void { + if (this.trustedPrincipals.includes(trustedRole.roleArn)) { + return; } - if (!this.trustedPrincipals.includes(trustedRole.roleArn)) { - if (!this.creationRole.assumeRolePolicy) { - throw new Error('unexpected: cluster creation role must have trust policy'); - } + if (!this.creationRole.assumeRolePolicy) { + throw new Error('unexpected: cluster creation role must have trust policy'); + } - this.creationRole.assumeRolePolicy.addStatements(new iam.PolicyStatement({ - actions: [ 'sts:AssumeRole' ], - principals: [ new iam.ArnPrincipal(trustedRole.roleArn) ], - })); + this.creationRole.assumeRolePolicy.addStatements(new iam.PolicyStatement({ + actions: [ 'sts:AssumeRole' ], + principals: [ new iam.ArnPrincipal(trustedRole.roleArn) ], + })); - this.trustedPrincipals.push(trustedRole.roleArn); - } - return this.creationRole.roleArn; + this.trustedPrincipals.push(trustedRole.roleArn); } } @@ -173,4 +168,4 @@ export function clusterArnComponents(clusterName: string): ArnComponents { resource: 'cluster', resourceName: clusterName, }; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 6bdd9526044ee..491d357387073 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -776,12 +776,12 @@ export class Cluster extends Resource implements ICluster { * * @internal */ - public _getKubectlCreationRoleArn(assumedBy?: iam.IRole) { + public get _kubectlCreationRole() { if (!this._clusterResource) { throw new Error('Unable to perform this operation since kubectl is not enabled for this cluster'); } - return this._clusterResource.getCreationRoleArn(assumedBy); + return this._clusterResource.creationRole; } /** @@ -794,7 +794,12 @@ export class Cluster extends Resource implements ICluster { } const uid = '@aws-cdk/aws-eks.KubectlProvider'; - return this.stack.node.tryFindChild(uid) as KubectlProvider || new KubectlProvider(this.stack, uid); + const provider = this.stack.node.tryFindChild(uid) as KubectlProvider || new KubectlProvider(this.stack, uid); + + // allow the kubectl provider to assume the cluster creation role. + this._clusterResource.addTrustedRole(provider.role); + + return provider; } /** diff --git a/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts b/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts index bd245f8c9a4b5..76a32929de190 100644 --- a/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts +++ b/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts @@ -129,6 +129,14 @@ export class FargateProfile extends Construct implements ITaggable { */ public readonly tags: TagManager; + /** + * The pod execution role to use for pods that match the selectors in the + * Fargate profile. The pod execution role allows Fargate infrastructure to + * register with your cluster as a node, and it provides read access to Amazon + * ECR image repositories. + */ + public readonly podExecutionRole: iam.IRole; + constructor(scope: Construct, id: string, props: FargateProfileProps) { super(scope, id); @@ -140,11 +148,13 @@ export class FargateProfile extends Construct implements ITaggable { const provider = ClusterResourceProvider.getOrCreate(this); - const role = props.podExecutionRole ?? new iam.Role(this, 'PodExecutionRole', { + this.podExecutionRole = props.podExecutionRole ?? new iam.Role(this, 'PodExecutionRole', { assumedBy: new iam.ServicePrincipal('eks-fargate-pods.amazonaws.com'), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSFargatePodExecutionRolePolicy') ], }); + this.podExecutionRole.grantPassRole(props.cluster._kubectlCreationRole); + let subnets: string[] | undefined; if (props.vpc) { const selection: ec2.SubnetSelection = props.subnetSelection ?? { subnetType: ec2.SubnetType.PRIVATE }; @@ -165,11 +175,11 @@ export class FargateProfile extends Construct implements ITaggable { serviceToken: provider.serviceToken, resourceType: FARGATE_PROFILE_RESOURCE_TYPE, properties: { - AssumeRoleArn: props.cluster._getKubectlCreationRoleArn(), + AssumeRoleArn: props.cluster._kubectlCreationRole.roleArn, Config: { clusterName: props.cluster.clusterName, fargateProfileName: props.fargateProfileName, - podExecutionRoleArn: role.roleArn, + podExecutionRoleArn: this.podExecutionRole.roleArn, selectors: props.selectors, subnets, tags: Lazy.anyValue({ produce: () => this.tags.renderTags() }), @@ -190,7 +200,7 @@ export class FargateProfile extends Construct implements ITaggable { // map the fargate pod execution role to the relevant groups in rbac // see https://github.com/aws/aws-cdk/issues/7981 - props.cluster.awsAuth.addRoleMapping(role, { + props.cluster.awsAuth.addRoleMapping(this.podExecutionRole, { username: 'system:node:{{SessionName}}', groups: [ 'system:bootstrappers', diff --git a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts index f3c6141f5cd0a..8d5b50d870151 100644 --- a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts +++ b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts @@ -95,14 +95,14 @@ export class HelmChart extends Construct { resourceType: HelmChart.RESOURCE_TYPE, properties: { ClusterName: props.cluster.clusterName, - RoleArn: props.cluster._getKubectlCreationRoleArn(provider.role), - Release: props.release || this.node.uniqueId.slice(-53).toLowerCase(), // Helm has a 53 character limit for the name + RoleArn: props.cluster._kubectlCreationRole.roleArn, + Release: props.release ?? this.node.uniqueId.slice(-53).toLowerCase(), // Helm has a 53 character limit for the name Chart: props.chart, Version: props.version, - Wait: props.wait || false, + Wait: props.wait ?? false, Timeout: timeout, Values: (props.values ? stack.toJsonString(props.values) : undefined), - Namespace: props.namespace || 'default', + Namespace: props.namespace ?? 'default', Repository: props.repository, }, }); diff --git a/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts b/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts index 15bf72839153a..1c819a3b36968 100644 --- a/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts +++ b/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts @@ -81,7 +81,7 @@ export class KubernetesPatch extends Construct { ApplyPatchJson: stack.toJsonString(props.applyPatch), RestorePatchJson: stack.toJsonString(props.restorePatch), ClusterName: props.cluster.clusterName, - RoleArn: props.cluster._getKubectlCreationRoleArn(provider.role), + RoleArn: props.cluster._kubectlCreationRole.roleArn, PatchType: props.patchType ?? PatchType.STRATEGIC, }, }); diff --git a/packages/@aws-cdk/aws-eks/lib/k8s-resource.ts b/packages/@aws-cdk/aws-eks/lib/k8s-resource.ts index ac033dd8a5b5d..3a58cfacff102 100644 --- a/packages/@aws-cdk/aws-eks/lib/k8s-resource.ts +++ b/packages/@aws-cdk/aws-eks/lib/k8s-resource.ts @@ -65,7 +65,7 @@ export class KubernetesResource extends Construct { // StepFunctions, CloudWatch Dashboards etc). Manifest: stack.toJsonString(props.manifest), ClusterName: props.cluster.clusterName, - RoleArn: props.cluster._getKubectlCreationRoleArn(provider.role), + RoleArn: props.cluster._kubectlCreationRole.roleArn, }, }); } diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index 9cb02bc5759d1..4ea262000916b 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.691.0", + "aws-sdk": "^2.699.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json index 3196329daeaf2..fb9d4b2701b74 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json @@ -823,6 +823,16 @@ "Action": "iam:CreateServiceLinkedRole", "Effect": "Allow", "Resource": "*" + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "ClusterfargateprofiledefaultPodExecutionRole09952CFF", + "Arn" + ] + } } ], "Version": "2012-10-17" diff --git a/packages/@aws-cdk/aws-eks/test/test.fargate.ts b/packages/@aws-cdk/aws-eks/test/test.fargate.ts index 8599090df444b..593a5eccd42ca 100644 --- a/packages/@aws-cdk/aws-eks/test/test.fargate.ts +++ b/packages/@aws-cdk/aws-eks/test/test.fargate.ts @@ -1,4 +1,4 @@ -import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import { expect, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import { Stack, Tag } from '@aws-cdk/core'; @@ -336,4 +336,84 @@ export = { test.done(); }, + + 'allow cluster creation role to iam:PassRole on fargate pod execution role'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + new eks.FargateCluster(stack, 'FargateCluster'); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'iam:PassRole', + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'FargateClusterRole8E36B33A', + 'Arn', + ], + }, + }, + { + Action: [ + 'ec2:DescribeSubnets', + 'ec2:DescribeRouteTables', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'eks:CreateCluster', + 'eks:DescribeCluster', + 'eks:DescribeUpdate', + 'eks:DeleteCluster', + 'eks:UpdateClusterVersion', + 'eks:UpdateClusterConfig', + 'eks:CreateFargateProfile', + 'eks:TagResource', + 'eks:UntagResource', + ], + Effect: 'Allow', + Resource: [ + '*', + ], + }, + { + Action: [ + 'eks:DescribeFargateProfile', + 'eks:DeleteFargateProfile', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'iam:GetRole', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'iam:CreateServiceLinkedRole', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'iam:PassRole', + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'FargateClusterfargateprofiledefaultPodExecutionRole66F2610E', + 'Arn', + ], + }, + }, + ], + }, + })); + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-elasticache/.eslintrc.js b/packages/@aws-cdk/aws-elasticache/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-elasticache/.eslintrc.js +++ b/packages/@aws-cdk/aws-elasticache/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-elasticbeanstalk/.eslintrc.js b/packages/@aws-cdk/aws-elasticbeanstalk/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-elasticbeanstalk/.eslintrc.js +++ b/packages/@aws-cdk/aws-elasticbeanstalk/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/.eslintrc.js b/packages/@aws-cdk/aws-elasticloadbalancing/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/.eslintrc.js +++ b/packages/@aws-cdk/aws-elasticloadbalancing/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.eslintrc.js b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.eslintrc.js index 1b28bad193ceb..01db0e3e9f01c 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.eslintrc.js +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.eslintrc.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/test/integ.cognito.lit.expected.json b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/test/integ.cognito.lit.expected.json index ca495599afaf6..e35271c92c173 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/test/integ.cognito.lit.expected.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/test/integ.cognito.lit.expected.json @@ -493,6 +493,12 @@ "UserPool6BA7E5F2": { "Type": "AWS::Cognito::UserPool", "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { "Name": "verified_phone_number", "Priority": 1 }, + { "Name": "verified_email", "Priority": 2 } + ] + }, "AdminCreateUserConfig": { "AllowAdminCreateUserOnly": true }, diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.eslintrc.js b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.eslintrc.js +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/.eslintrc.js b/packages/@aws-cdk/aws-elasticloadbalancingv2/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/.eslintrc.js +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-elasticsearch/.eslintrc.js b/packages/@aws-cdk/aws-elasticsearch/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-elasticsearch/.eslintrc.js +++ b/packages/@aws-cdk/aws-elasticsearch/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-emr/.eslintrc.js b/packages/@aws-cdk/aws-emr/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-emr/.eslintrc.js +++ b/packages/@aws-cdk/aws-emr/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-events-targets/.eslintrc.js b/packages/@aws-cdk/aws-events-targets/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-events-targets/.eslintrc.js +++ b/packages/@aws-cdk/aws-events-targets/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index 64b7060517815..89a46367e3876 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -67,7 +67,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", - "aws-sdk": "^2.691.0", + "aws-sdk": "^2.699.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-events/.eslintrc.js b/packages/@aws-cdk/aws-events/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-events/.eslintrc.js +++ b/packages/@aws-cdk/aws-events/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-eventschemas/.eslintrc.js b/packages/@aws-cdk/aws-eventschemas/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-eventschemas/.eslintrc.js +++ b/packages/@aws-cdk/aws-eventschemas/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-fms/.eslintrc.js b/packages/@aws-cdk/aws-fms/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-fms/.eslintrc.js +++ b/packages/@aws-cdk/aws-fms/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-fsx/.eslintrc.js b/packages/@aws-cdk/aws-fsx/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-fsx/.eslintrc.js +++ b/packages/@aws-cdk/aws-fsx/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-gamelift/.eslintrc.js b/packages/@aws-cdk/aws-gamelift/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-gamelift/.eslintrc.js +++ b/packages/@aws-cdk/aws-gamelift/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-globalaccelerator/.eslintrc.js b/packages/@aws-cdk/aws-globalaccelerator/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/.eslintrc.js +++ b/packages/@aws-cdk/aws-globalaccelerator/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-globalaccelerator/README.md b/packages/@aws-cdk/aws-globalaccelerator/README.md index 1765922b5589b..ac304fe3ea03e 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/README.md +++ b/packages/@aws-cdk/aws-globalaccelerator/README.md @@ -6,11 +6,90 @@ > All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. + --- -This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. +## Introduction + +AWS Global Accelerator is a service that improves the availability and performance of your applications with local or global users. It provides static IP addresses that act as a fixed entry point to your application endpoints in a single or multiple AWS Regions, such as your Application Load Balancers, Network Load Balancers or Amazon EC2 instances. + +This module supports features under [AWS Global Accelerator](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_GlobalAccelerator.html) that allows users set up resources using the `@aws-cdk/aws-globalaccelerator` module. + +## Accelerator + +The `Accelerator` resource is a Global Accelerator resource type that contains information about how you create an accelerator. An accelerator includes one or more listeners that process inbound connections and direct traffic to one or more endpoint groups, each of which includes endpoints, such as Application Load Balancers, Network Load Balancers, and Amazon EC2 instances. + +To create the `Accelerator`: ```ts import globalaccelerator = require('@aws-cdk/aws-globalaccelerator'); + +new globalaccelerator.Accelerator(stack, 'Accelerator'); + +``` + +## Listener + +The `Listener` resource is a Global Accelerator resource type that contains information about how you create a listener to process inbound connections from clients to an accelerator. Connections arrive to assigned static IP addresses on a port, port range, or list of port ranges that you specify. + +To create the `Listener` listening on TCP 80: + +```ts +new globalaccelerator.Listener(stack, 'Listener', { + accelerator, + portRanges: [ + { + fromPort: 80, + toPort: 80, + }, + ], +}); +``` + + +## EndpointGroup + +The `EndpointGroup` resource is a Global Accelerator resource type that contains information about how you create an endpoint group for the specified listener. An endpoint group is a collection of endpoints in one AWS Region. + +To create the `EndpointGroup`: + +```ts +new globalaccelerator.EndpointGroup(stack, 'Group', { listener }); + +``` + +## Add Endpoint into EndpointGroup + +You may use the following methods to add endpoints into the `EndpointGroup`: + +- `addEndpoint` to add a generic `endpoint` into the `EndpointGroup`. +- `addLoadBalancer` to add an Application Load Balancer or Network Load Balancer. +- `addEc2Instance` to add an EC2 Instance. +- `addElasticIpAddress` to add an Elastic IP Address. + + +```ts +const endpointGroup = new globalaccelerator.EndpointGroup(stack, 'Group', { listener }); +const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc, internetFacing: true }); +const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', { vpc, internetFacing: true }); +const eip = new ec2.CfnEIP(stack, 'ElasticIpAddress'); +const instances = new Array(); + +for ( let i = 0; i < 2; i++) { + instances.push(new ec2.Instance(stack, `Instance${i}`, { + vpc, + machineImage: new ec2.AmazonLinuxImage(), + instanceType: new ec2.InstanceType('t3.small'), + })); +} + +endpointGroup.addLoadBalancer('AlbEndpoint', alb); +endpointGroup.addLoadBalancer('NlbEndpoint', nlb); +endpointGroup.addElasticIpAddress('EipEndpoint', eip); +endpointGroup.addEc2Instance('InstanceEndpoint', instances[0]); +endpointGroup.addEndpoint('InstanceEndpoint2', instances[1].instanceId); ``` diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator.ts new file mode 100644 index 0000000000000..bdd5b02c0c063 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator.ts @@ -0,0 +1,94 @@ +import * as cdk from '@aws-cdk/core'; +import * as ga from './globalaccelerator.generated'; + +/** + * The interface of the Accelerator + */ +export interface IAccelerator extends cdk.IResource { + /** + * The ARN of the accelerator + * + * @attribute + */ + readonly acceleratorArn: string; + + /** + * The Domain Name System (DNS) name that Global Accelerator creates that points to your accelerator's static + * IP addresses. + * + * @attribute + */ + readonly dnsName: string; +} + +/** + * Construct properties of the Accelerator + */ +export interface AcceleratorProps { + /** + * The name of the accelerator + * + * @default - resource ID + */ + readonly acceleratorName?: string; + + /** + * Indicates whether the accelerator is enabled. + * + * @default true + */ + readonly enabled?: boolean; +} + +/** + * Attributes required to import an existing accelerator to the stack + */ +export interface AcceleratorAttributes { + /** + * The ARN of the accelerator + */ + readonly acceleratorArn: string; + + /** + * The DNS name of the accelerator + */ + readonly dnsName: string; +} + +/** + * The Accelerator construct + */ +export class Accelerator extends cdk.Resource implements IAccelerator { + /** + * import from attributes + */ + public static fromAcceleratorAttributes(scope: cdk.Construct, id: string, attrs: AcceleratorAttributes ): IAccelerator { + class Import extends cdk.Resource implements IAccelerator { + public readonly acceleratorArn = attrs.acceleratorArn; + public readonly dnsName = attrs.dnsName; + } + return new Import(scope, id); + } + /** + * The ARN of the accelerator. + */ + public readonly acceleratorArn: string; + + /** + * The Domain Name System (DNS) name that Global Accelerator creates that points to your accelerator's static + * IP addresses. + */ + public readonly dnsName: string; + + constructor(scope: cdk.Construct, id: string, props: AcceleratorProps = {}) { + super(scope, id); + + const resource = new ga.CfnAccelerator(this, 'Resource', { + enabled: props.enabled ?? true, + name: props.acceleratorName ?? id, + }); + + this.acceleratorArn = resource.attrAcceleratorArn; + this.dnsName = resource.attrDnsName; + } +} diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint-group.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint-group.ts new file mode 100644 index 0000000000000..cc81779c7131c --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint-group.ts @@ -0,0 +1,234 @@ +import * as cdk from '@aws-cdk/core'; +import * as ga from './globalaccelerator.generated'; +import { IListener } from './listener'; + +/** + * The interface of the EndpointGroup + */ +export interface IEndpointGroup extends cdk.IResource { + /** + * EndpointGroup ARN + * @attribute + */ + readonly endpointGroupArn: string; +} + +/** + * Options for `addLoadBalancer`, `addElasticIpAddress` and `addEc2Instance` to add endpoints into the endpoint group + */ +export interface EndpointConfigurationOptions { + /** + * Indicates whether client IP address preservation is enabled for an Application Load Balancer endpoint + * + * @default true + */ + readonly clientIpReservation?: boolean; + + /** + * The weight associated with the endpoint. When you add weights to endpoints, you configure AWS Global Accelerator + * to route traffic based on proportions that you specify. For example, you might specify endpoint weights of 4, 5, + * 5, and 6 (sum=20). The result is that 4/20 of your traffic, on average, is routed to the first endpoint, 5/20 is + * routed both to the second and third endpoints, and 6/20 is routed to the last endpoint. + * @see https://docs.aws.amazon.com/global-accelerator/latest/dg/about-endpoints-endpoint-weights.html + * @default - not specified + */ + readonly weight?: number; +} + +/** + * Properties to create EndpointConfiguration + * + */ +export interface EndpointConfigurationProps extends EndpointConfigurationOptions { + /** + * The endopoint group reesource + * + * [disable-awslint:ref-via-interface] + */ + readonly endpointGroup: EndpointGroup; + + /** + * An ID for the endpoint. If the endpoint is a Network Load Balancer or Application Load Balancer, + * this is the Amazon Resource Name (ARN) of the resource. If the endpoint is an Elastic IP address, + * this is the Elastic IP address allocation ID. For EC2 instances, this is the EC2 instance ID. + */ + readonly endpointId: string; +} + +/** + * LoadBalancer Interface + */ +export interface LoadBalancer { + /** + * The ARN of this load balancer + */ + readonly loadBalancerArn: string; +} + +/** + * EC2 Instance interface + */ +export interface Ec2Instance { + /** + * The id of the instance resource + */ + readonly instanceId: string; +} + +/** + * EIP Interface + */ +export interface ElasticIpAddress { + /** + * allocation ID of the EIP resoruce + */ + readonly attrAllocationId: string +} + +/** + * Property of the EndpointGroup + */ +export interface EndpointGroupProps { + /** + * Name of the endpoint group + * + * @default - logical ID of the resource + */ + readonly endpointGroupName?: string; + + /** + * The Amazon Resource Name (ARN) of the listener. + */ + readonly listener: IListener; + + /** + * The AWS Region where the endpoint group is located. + * + * @default - the region of the current stack + */ + readonly region?: string; +} + +/** + * The class for endpoint configuration + */ +export class EndpointConfiguration extends cdk.Construct { + /** + * The property containing all the configuration to be rendered + */ + public readonly props: EndpointConfigurationProps; + constructor(scope: cdk.Construct, id: string, props: EndpointConfigurationProps) { + super(scope, id); + this.props = props; + props.endpointGroup._linkEndpoint(this); + } + + /** + * render the endpoint configuration for the endpoint group + */ + public renderEndpointConfiguration(): ga.CfnEndpointGroup.EndpointConfigurationProperty { + return { + clientIpPreservationEnabled: this.props.clientIpReservation, + endpointId: this.props.endpointId, + weight: this.props.weight, + }; + } +} + +/** + * EndpointGroup construct + */ +export class EndpointGroup extends cdk.Resource implements IEndpointGroup { + /** + * import from ARN + */ + public static fromEndpointGroupArn(scope: cdk.Construct, id: string, endpointGroupArn: string): IEndpointGroup { + class Import extends cdk.Resource implements IEndpointGroup { + public readonly endpointGroupArn = endpointGroupArn; + } + return new Import(scope, id); + } + + public readonly endpointGroupArn: string; + /** + * + * The name of the endpoint group + * + * @attribute + */ + public readonly endpointGroupName: string; + /** + * The array of the endpoints in this endpoint group + */ + protected readonly endpoints = new Array(); + + constructor(scope: cdk.Construct, id: string, props: EndpointGroupProps) { + super(scope, id); + + const resource = new ga.CfnEndpointGroup(this, 'Resource', { + listenerArn: props.listener.listenerArn, + endpointGroupRegion: props.region ?? cdk.Stack.of(this).region, + endpointConfigurations: cdk.Lazy.anyValue({ produce: () => this.renderEndpoints() }, { omitEmptyArray: true }), + }); + + this.endpointGroupArn = resource.attrEndpointGroupArn; + this.endpointGroupName = props.endpointGroupName ?? resource.logicalId; + } + + /** + * Add an endpoint + */ + public addEndpoint(id: string, endpointId: string, props: EndpointConfigurationOptions = + {}) { + return new EndpointConfiguration(this, id, { + endpointGroup: this, + endpointId, + ...props, + }); + } + + /** + * Add an Elastic Load Balancer as an endpoint in this endpoint group + */ + public addLoadBalancer(id: string, lb: LoadBalancer, props: EndpointConfigurationOptions = {}) { + return new EndpointConfiguration(this, id, { + endpointId: lb.loadBalancerArn, + endpointGroup: this, + ...props, + }); + } + + /** + * Add an EIP as an endpoint in this endpoint group + */ + public addElasticIpAddress(id: string, eip: ElasticIpAddress, props: EndpointConfigurationOptions = {}) { + return new EndpointConfiguration(this, id, { + endpointId: eip.attrAllocationId, + endpointGroup: this, + ...props, + }); + } + + /** + * Add an EC2 Instance as an endpoint in this endpoint group + */ + public addEc2Instance(id: string, instance: Ec2Instance, props: EndpointConfigurationOptions = {}) { + return new EndpointConfiguration(this, id, { + endpointId: instance.instanceId, + endpointGroup: this, + ...props, + }); + } + + /** + * Links a endpoint to this endpoint group + * @internal + */ + public _linkEndpoint(endpoint: EndpointConfiguration) { + this.endpoints.push(endpoint); + } + + private renderEndpoints() { + return this.endpoints.map(e => e.renderEndpointConfiguration()); + } +} diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/index.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/index.ts index 32d3860d45724..ce94d8991fb5d 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/lib/index.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/index.ts @@ -1,2 +1,5 @@ // AWS::GlobalAccelerator CloudFormation Resources: export * from './globalaccelerator.generated'; +export * from './accelerator'; +export * from './listener'; +export * from './endpoint-group'; diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/listener.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/listener.ts new file mode 100644 index 0000000000000..e482d954a9cd4 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/listener.ts @@ -0,0 +1,139 @@ +import * as cdk from '@aws-cdk/core'; +import { IAccelerator } from './accelerator'; +import * as ga from './globalaccelerator.generated'; + +/** + * Interface of the Listener + */ +export interface IListener extends cdk.IResource { + /** + * The ARN of the listener + * + * @attribute + */ + readonly listenerArn: string; +} + +/** + * construct properties for Listener + */ +export interface ListenerProps { + /** + * Name of the listener + * + * @default - logical ID of the resource + */ + readonly listenerName?: string; + + /** + * The accelerator for this listener + */ + readonly accelerator: IAccelerator; + + /** + * The list of port ranges for the connections from clients to the accelerator + */ + readonly portRanges: PortRange[]; + + /** + * The protocol for the connections from clients to the accelerator + * + * @default TCP + */ + readonly protocol?: ConnectionProtocol; + + /** + * Client affinity to direct all requests from a user to the same endpoint + * + * @default NONE + */ + readonly clientAffinity?: ClientAffinity; +} + +/** + * The list of port ranges for the connections from clients to the accelerator. + */ +export interface PortRange { + /** + * The first port in the range of ports, inclusive. + */ + readonly fromPort: number, + /** + * The last port in the range of ports, inclusive. + */ + readonly toPort: number, +} + +/** + * The protocol for the connections from clients to the accelerator. + */ +export enum ConnectionProtocol { + /** + * TCP + */ + TCP = 'TCP', + /** + * UDP + */ + UDP = 'UDP', +} + +/** + * Client affinity lets you direct all requests from a user to the same endpoint, if you have stateful applications, + * regardless of the port and protocol of the client request. Client affinity gives you control over whether to always + * route each client to the same specific endpoint. If you want a given client to always be routed to the same + * endpoint, set client affinity to SOURCE_IP. + * + * @see https://docs.aws.amazon.com/global-accelerator/latest/dg/about-listeners.html#about-listeners-client-affinity + */ +export enum ClientAffinity { + /** + * default affinity + */ + NONE = 'NONE', + /** + * affinity by source IP + */ + SOURCE_IP = 'SOURCE_IP', +} + +/** + * The construct for the Listener + */ +export class Listener extends cdk.Resource implements IListener { + /** + * import from ARN + */ + public static fromListenerArn(scope: cdk.Construct, id: string, listenerArn: string): IListener { + class Import extends cdk.Resource implements IListener { + public readonly listenerArn = listenerArn; + } + return new Import(scope, id); + } + + public readonly listenerArn: string; + /** + * The name of the listener + * + * @attribute + */ + public readonly listenerName: string; + + constructor(scope: cdk.Construct, id: string, props: ListenerProps) { + super(scope, id); + + const resource = new ga.CfnListener(this, 'Resource', { + acceleratorArn: props.accelerator.acceleratorArn, + portRanges: props.portRanges.map(m => ({ + fromPort: m.fromPort, + toPort: m.toPort, + })), + protocol: props.protocol ?? ConnectionProtocol.TCP, + clientAffinity: props.clientAffinity ?? ClientAffinity.NONE, + }); + + this.listenerArn = resource.attrListenerArn; + this.listenerName = props.listenerName ?? resource.logicalId; + + } +} diff --git a/packages/@aws-cdk/aws-globalaccelerator/package.json b/packages/@aws-cdk/aws-globalaccelerator/package.json index 4fab6990574a5..fb84dd0d1d5eb 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/package.json +++ b/packages/@aws-cdk/aws-globalaccelerator/package.json @@ -66,21 +66,26 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", + "cdk-integ-tools": "0.0.0", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", "pkglint": "0.0.0" }, "dependencies": { - "@aws-cdk/core": "0.0.0" + "@aws-cdk/core": "0.0.0", + "constructs": "^3.0.2" }, "peerDependencies": { - "@aws-cdk/core": "0.0.0" + "@aws-cdk/core": "0.0.0", + "constructs": "^3.0.2" }, "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, "stability": "experimental", - "maturity": "cfn-only", + "maturity": "experimental", "awscdkio": { "announce": false } diff --git a/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator.test.ts b/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator.test.ts index e394ef336bfb4..72e1c79e586dd 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator.test.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator.test.ts @@ -1,6 +1,219 @@ -import '@aws-cdk/assert/jest'; -import {} from '../lib'; +import { expect, haveResourceLike } from '@aws-cdk/assert'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as ga from '../lib'; +import { testFixture } from './util'; -test('No tests are specified for this package', () => { - expect(true).toBe(true); +test('create accelerator', () => { + // GIVEN + const { stack } = testFixture(); + + // WHEN + new ga.Accelerator(stack, 'Accelerator'); + + // THEN + expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::Accelerator', { + Enabled: true, + })); +}); + +test('create listener', () => { + // GIVEN + const { stack } = testFixture(); + + // WHEN + const accelerator = new ga.Accelerator(stack, 'Accelerator'); + new ga.Listener(stack, 'Listener', { + accelerator, + portRanges: [ + { + fromPort: 80, + toPort: 80, + }, + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::Listener', { + AcceleratorArn: { + 'Fn::GetAtt': [ + 'Accelerator8EB0B6B1', + 'AcceleratorArn', + ], + }, + PortRanges: [ + { + FromPort: 80, + ToPort: 80, + }, + ], + Protocol: 'TCP', + ClientAffinity: 'NONE', + })); +}); + +test('create endpointgroup', () => { + // GIVEN + const { stack } = testFixture(); + + // WHEN + const accelerator = new ga.Accelerator(stack, 'Accelerator'); + const listener = new ga.Listener(stack, 'Listener', { + accelerator, + portRanges: [ + { + fromPort: 80, + toPort: 80, + }, + ], + }); + new ga.EndpointGroup(stack, 'Group', { listener }); + + // THEN + expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointGroupRegion: { + Ref: 'AWS::Region', + }, + ListenerArn: { + 'Fn::GetAtt': [ + 'Listener828B0E81', + 'ListenerArn', + ], + }, + })); +}); + +test('addEndpoint', () => { + // GIVEN + const { stack, vpc } = testFixture(); + + // WHEN + const accelerator = new ga.Accelerator(stack, 'Accelerator'); + const listener = new ga.Listener(stack, 'Listener', { + accelerator, + portRanges: [ + { + fromPort: 80, + toPort: 80, + }, + ], + }); + const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); + const instance = new ec2.Instance(stack, 'Instance', { + vpc, + machineImage: new ec2.AmazonLinuxImage(), + instanceType: new ec2.InstanceType('t3.small'), + }); + endpointGroup.addEndpoint('endpoint', instance.instanceId); + + // THEN + expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointConfigurations: [ + { + EndpointId: { + Ref: 'InstanceC1063A87', + }, + }, + ], + })); +}); + +test('addLoadBalancer', () => { + // GIVEN + const { stack, vpc } = testFixture(); + + // WHEN + const accelerator = new ga.Accelerator(stack, 'Accelerator'); + const listener = new ga.Listener(stack, 'Listener', { + accelerator, + portRanges: [ + { + fromPort: 80, + toPort: 80, + }, + ], + }); + const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); + const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc, internetFacing: true }); + endpointGroup.addLoadBalancer('endpoint', alb); + + // THEN + expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointConfigurations: [ + { + EndpointId: { + Ref: 'ALBAEE750D2', + }, + }, + ], + })); +}); + +test('addElasticIpAddress', () => { + // GIVEN + const { stack } = testFixture(); + + // WHEN + const accelerator = new ga.Accelerator(stack, 'Accelerator'); + const listener = new ga.Listener(stack, 'Listener', { + accelerator, + portRanges: [ + { + fromPort: 80, + toPort: 80, + }, + ], + }); + const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); + const eip = new ec2.CfnEIP(stack, 'ElasticIpAddress'); + endpointGroup.addElasticIpAddress('endpoint', eip); + + // THEN + expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointConfigurations: [ + { + EndpointId: { + 'Fn::GetAtt': [ + 'ElasticIpAddress', + 'AllocationId', + ], + }, + }, + ], + })); +}); + +test('addEc2Instance', () => { + // GIVEN + const { stack, vpc } = testFixture(); + + // WHEN + const accelerator = new ga.Accelerator(stack, 'Accelerator'); + const listener = new ga.Listener(stack, 'Listener', { + accelerator, + portRanges: [ + { + fromPort: 80, + toPort: 80, + }, + ], + }); + const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); + const instance = new ec2.Instance(stack, 'Instance', { + vpc, + machineImage: new ec2.AmazonLinuxImage(), + instanceType: new ec2.InstanceType('t3.small'), + }); + endpointGroup.addEc2Instance('endpoint', instance); + + // THEN + expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointConfigurations: [ + { + EndpointId: { + Ref: 'InstanceC1063A87', + }, + }, + ], + })); }); diff --git a/packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.expected.json b/packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.expected.json new file mode 100644 index 0000000000000..305149bc84041 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.expected.json @@ -0,0 +1,807 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociation0B0896DC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet3Subnet631C5E25": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3RouteTable98AE0E14": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3RouteTableAssociation427FE0C6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + } + } + }, + "VPCPublicSubnet3DefaultRouteA0D29D46": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociation347902D1": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet3Subnet3EDCD457": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC/PrivateSubnet3" + } + ] + } + }, + "VPCPrivateSubnet3RouteTable192186F8": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC/PrivateSubnet3" + } + ] + } + }, + "VPCPrivateSubnet3RouteTableAssociationC28D144E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + } + }, + "VPCPrivateSubnet3DefaultRoute27F311AE": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "Accelerator8EB0B6B1": { + "Type": "AWS::GlobalAccelerator::Accelerator", + "Properties": { + "Name": "Accelerator", + "Enabled": true + } + }, + "Listener828B0E81": { + "Type": "AWS::GlobalAccelerator::Listener", + "Properties": { + "AcceleratorArn": { + "Fn::GetAtt": [ + "Accelerator8EB0B6B1", + "AcceleratorArn" + ] + }, + "PortRanges": [ + { + "FromPort": 80, + "ToPort": 80 + } + ], + "Protocol": "TCP", + "ClientAffinity": "NONE" + } + }, + "GroupC77FDACD": { + "Type": "AWS::GlobalAccelerator::EndpointGroup", + "Properties": { + "EndpointGroupRegion": { + "Ref": "AWS::Region" + }, + "ListenerArn": { + "Fn::GetAtt": [ + "Listener828B0E81", + "ListenerArn" + ] + }, + "EndpointConfigurations": [ + { + "EndpointId": { + "Ref": "ALBAEE750D2" + } + }, + { + "EndpointId": { + "Ref": "NLB55158F82" + } + }, + { + "EndpointId": { + "Fn::GetAtt": [ + "ElasticIpAddress", + "AllocationId" + ] + } + }, + { + "EndpointId": { + "Ref": "Instance008A4B15C" + } + }, + { + "EndpointId": { + "Ref": "Instance14BC3991D" + } + } + ] + } + }, + "ALBAEE750D2": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "Scheme": "internet-facing", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ALBSecurityGroup8B8624F8", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + } + ], + "Type": "application" + }, + "DependsOn": [ + "VPCPublicSubnet1DefaultRoute91CEF279", + "VPCPublicSubnet2DefaultRouteB7481BBA", + "VPCPublicSubnet3DefaultRouteA0D29D46" + ] + }, + "ALBSecurityGroup8B8624F8": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Automatically created Security Group for ELB integglobalacceleratorALBEE1DE7F7", + "SecurityGroupEgress": [ + { + "CidrIp": "255.255.255.255/32", + "Description": "Disallow all traffic", + "FromPort": 252, + "IpProtocol": "icmp", + "ToPort": 86 + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "NLB55158F82": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "Scheme": "internet-facing", + "Subnets": [ + { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + } + ], + "Type": "network" + }, + "DependsOn": [ + "VPCPublicSubnet1DefaultRoute91CEF279", + "VPCPublicSubnet2DefaultRouteB7481BBA", + "VPCPublicSubnet3DefaultRouteA0D29D46" + ] + }, + "ElasticIpAddress": { + "Type": "AWS::EC2::EIP" + }, + "Instance0InstanceSecurityGroup7897592D": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "integ-globalaccelerator/Instance0/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/Instance0" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "Instance0InstanceRole6927D768": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/Instance0" + } + ] + } + }, + "Instance0InstanceProfile3A61DE71": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "Instance0InstanceRole6927D768" + } + ] + } + }, + "Instance008A4B15C": { + "Type": "AWS::EC2::Instance", + "Properties": { + "AvailabilityZone": "test-region-1a", + "IamInstanceProfile": { + "Ref": "Instance0InstanceProfile3A61DE71" + }, + "ImageId": { + "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "t3.small", + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "Instance0InstanceSecurityGroup7897592D", + "GroupId" + ] + } + ], + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/Instance0" + } + ], + "UserData": { + "Fn::Base64": "#!/bin/bash" + } + }, + "DependsOn": [ + "Instance0InstanceRole6927D768" + ] + }, + "Instance1InstanceSecurityGroup50841F79": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "integ-globalaccelerator/Instance1/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/Instance1" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "Instance1InstanceRoleBC4D05C6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/Instance1" + } + ] + } + }, + "Instance1InstanceProfileC04770B7": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "Instance1InstanceRoleBC4D05C6" + } + ] + } + }, + "Instance14BC3991D": { + "Type": "AWS::EC2::Instance", + "Properties": { + "AvailabilityZone": "test-region-1a", + "IamInstanceProfile": { + "Ref": "Instance1InstanceProfileC04770B7" + }, + "ImageId": { + "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "t3.small", + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "Instance1InstanceSecurityGroup50841F79", + "GroupId" + ] + } + ], + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-globalaccelerator/Instance1" + } + ], + "UserData": { + "Fn::Base64": "#!/bin/bash" + } + }, + "DependsOn": [ + "Instance1InstanceRoleBC4D05C6" + ] + } + }, + "Parameters": { + "SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.ts b/packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.ts new file mode 100644 index 0000000000000..2c9a632ec2734 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.ts @@ -0,0 +1,48 @@ + +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as cdk from '@aws-cdk/core'; +import * as ga from '../lib'; +import * as testfixture from './util'; + +class GaStack extends testfixture.TestStack { + constructor(scope: cdk.Construct, id: string) { + super(scope, id); + + const vpc = new ec2.Vpc(this, 'VPC', { maxAzs: 3, natGateways: 1}); + const accelerator = new ga.Accelerator(this, 'Accelerator'); + const listener = new ga.Listener(this, 'Listener', { + accelerator, + portRanges: [ + { + fromPort: 80, + toPort: 80, + }, + ], + }); + const endpointGroup = new ga.EndpointGroup(this, 'Group', { listener }); + const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', { vpc, internetFacing: true }); + const nlb = new elbv2.NetworkLoadBalancer(this, 'NLB', { vpc, internetFacing: true }); + const eip = new ec2.CfnEIP(this, 'ElasticIpAddress'); + const instances = new Array(); + + for ( let i = 0; i < 2; i++) { + instances.push(new ec2.Instance(this, `Instance${i}`, { + vpc, + machineImage: new ec2.AmazonLinuxImage(), + instanceType: new ec2.InstanceType('t3.small'), + })); + } + + endpointGroup.addLoadBalancer('AlbEndpoint', alb); + endpointGroup.addLoadBalancer('NlbEndpoint', nlb); + endpointGroup.addElasticIpAddress('EipEndpoint', eip); + endpointGroup.addEc2Instance('InstanceEndpoint', instances[0]); + endpointGroup.addEndpoint('InstanceEndpoint2', instances[1].instanceId); + + } +} + +const app = new cdk.App(); + +new GaStack(app, 'integ-globalaccelerator'); diff --git a/packages/@aws-cdk/aws-globalaccelerator/test/util.ts b/packages/@aws-cdk/aws-globalaccelerator/test/util.ts new file mode 100644 index 0000000000000..03fc491788e21 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/test/util.ts @@ -0,0 +1,54 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import { App, Construct, Stack } from '@aws-cdk/core'; + +export function testFixture() { + const { stack, app } = testFixtureNoVpc(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + return { stack, vpc, app }; +} + +export function testFixtureNoVpc() { + const app = new App(); + const stack = new Stack(app, 'Stack'); + return { stack, app }; +} + +export function testFixtureAlb() { + const { stack, app, vpc } = testFixture(); + const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc, internetFacing: true }); + + return { stack, app, alb }; +} + +export function testFixtureNlb() { + const { stack, app, vpc } = testFixture(); + const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', { vpc, internetFacing: true }); + + return { stack, app, nlb }; +} + +export function testFixtureEip() { + const { stack, app } = testFixtureNoVpc(); + const eip = new ec2.CfnEIP(stack, 'ElasticIpAddress'); + + return { stack, app, eip }; +} + +export function testFixtureEc2() { + const { stack, app, vpc } = testFixture(); + const instance = new ec2.Instance(stack, 'Ec2', { + vpc, + machineImage: new ec2.AmazonLinuxImage(), + instanceType: new ec2.InstanceType('t3.small'), + }); + + return { stack, app, instance }; +} + +export class TestStack extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + } +} diff --git a/packages/@aws-cdk/aws-glue/.eslintrc.js b/packages/@aws-cdk/aws-glue/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-glue/.eslintrc.js +++ b/packages/@aws-cdk/aws-glue/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-greengrass/.eslintrc.js b/packages/@aws-cdk/aws-greengrass/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-greengrass/.eslintrc.js +++ b/packages/@aws-cdk/aws-greengrass/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-guardduty/.eslintrc.js b/packages/@aws-cdk/aws-guardduty/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-guardduty/.eslintrc.js +++ b/packages/@aws-cdk/aws-guardduty/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iam/.eslintrc.js b/packages/@aws-cdk/aws-iam/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-iam/.eslintrc.js +++ b/packages/@aws-cdk/aws-iam/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-imagebuilder/.eslintrc.js b/packages/@aws-cdk/aws-imagebuilder/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-imagebuilder/.eslintrc.js +++ b/packages/@aws-cdk/aws-imagebuilder/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-inspector/.eslintrc.js b/packages/@aws-cdk/aws-inspector/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-inspector/.eslintrc.js +++ b/packages/@aws-cdk/aws-inspector/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iot/.eslintrc.js b/packages/@aws-cdk/aws-iot/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-iot/.eslintrc.js +++ b/packages/@aws-cdk/aws-iot/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iot1click/.eslintrc.js b/packages/@aws-cdk/aws-iot1click/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-iot1click/.eslintrc.js +++ b/packages/@aws-cdk/aws-iot1click/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iotanalytics/.eslintrc.js b/packages/@aws-cdk/aws-iotanalytics/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-iotanalytics/.eslintrc.js +++ b/packages/@aws-cdk/aws-iotanalytics/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iotevents/.eslintrc.js b/packages/@aws-cdk/aws-iotevents/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-iotevents/.eslintrc.js +++ b/packages/@aws-cdk/aws-iotevents/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iotthingsgraph/.eslintrc.js b/packages/@aws-cdk/aws-iotthingsgraph/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-iotthingsgraph/.eslintrc.js +++ b/packages/@aws-cdk/aws-iotthingsgraph/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-kinesis/.eslintrc.js b/packages/@aws-cdk/aws-kinesis/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-kinesis/.eslintrc.js +++ b/packages/@aws-cdk/aws-kinesis/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-kinesisanalytics/.eslintrc.js b/packages/@aws-cdk/aws-kinesisanalytics/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics/.eslintrc.js +++ b/packages/@aws-cdk/aws-kinesisanalytics/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-kinesisfirehose/.eslintrc.js b/packages/@aws-cdk/aws-kinesisfirehose/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/.eslintrc.js +++ b/packages/@aws-cdk/aws-kinesisfirehose/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-kms/.eslintrc.js b/packages/@aws-cdk/aws-kms/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-kms/.eslintrc.js +++ b/packages/@aws-cdk/aws-kms/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-kms/README.md b/packages/@aws-cdk/aws-kms/README.md index f835232ff30e2..5bd6f5e6f1474 100644 --- a/packages/@aws-cdk/aws-kms/README.md +++ b/packages/@aws-cdk/aws-kms/README.md @@ -55,6 +55,21 @@ Note that a call to `.addToPolicy(statement)` on `myKeyImported` will not have an affect on the key's policy because it is not owned by your stack. The call will be a no-op. +If a Key has an associated Alias, the Alias can be imported by name and used in place +of the Key as a reference. A common scenario for this is in referencing AWS managed keys. + +```ts +const myKeyAlias = kms.Alias.fromAliasName(this, 'myKey', 'alias/aws/s3'); +const trail = new cloudtrail.Trail(this, 'myCloudTrail', { + sendToCloudWatchLogs: true, + kmsKey: myKeyAlias +}); +``` + +Note that calls to `addToResourcePolicy` and `grant*` methods on `myKeyAlias` will be +no-ops, and `addAlias` and `aliasTargetKey` will fail, as the imported alias does not +have a reference to the underlying KMS Key. + ### Trust Account Identities KMS keys can be created to trust IAM policies. This is the default behavior in @@ -102,7 +117,7 @@ bucket.grantReadWrite(fn); key.grantEncryptDecrypt(fn); ``` -The challenge in this scenario is the KMS key policy behavior. The simple way to understand +The challenge in this scenario is the KMS key policy behavior. The simple way to understand this, is IAM policies for account entities can only grant the permissions granted to the account root principle in the key policy. When `trustAccountIdentities` is true, the following policy statement is added: diff --git a/packages/@aws-cdk/aws-kms/lib/alias.ts b/packages/@aws-cdk/aws-kms/lib/alias.ts index df210f40afcc1..2ba88fdb650ce 100644 --- a/packages/@aws-cdk/aws-kms/lib/alias.ts +++ b/packages/@aws-cdk/aws-kms/lib/alias.ts @@ -137,6 +137,34 @@ export class Alias extends AliasBase { return new _Alias(scope, id); } + /** + * Import an existing KMS Alias defined outside the CDK app, by the alias name. This method should be used + * instead of 'fromAliasAttributes' when the underlying KMS Key ARN is not available. + * This Alias will not have a direct reference to the KMS Key, so addAlias and grant* methods are not supported. + * + * @param scope The parent creating construct (usually `this`). + * @param id The construct's name. + * @param aliasName The full name of the KMS Alias (e.g., 'alias/aws/s3', 'alias/myKeyAlias'). + */ + public static fromAliasName(scope: Construct, id: string, aliasName: string): IAlias { + class Import extends Resource implements IAlias { + public readonly keyArn = Stack.of(this).formatArn({service: 'kms', resource: aliasName}); + public readonly keyId = aliasName; + public readonly aliasName = aliasName; + public get aliasTargetKey(): IKey { throw new Error('Cannot access aliasTargetKey on an Alias imnported by Alias.fromAliasName().'); } + public addAlias(_alias: string): Alias { throw new Error('Cannot call addAlias on an Alias imported by Alias.fromAliasName().'); } + public addToResourcePolicy(_statement: iam.PolicyStatement, _allowNoOp?: boolean): iam.AddToResourcePolicyResult { + return { statementAdded: false }; + } + public grant(grantee: iam.IGrantable, ..._actions: string[]): iam.Grant { return iam.Grant.drop(grantee, ''); } + public grantDecrypt(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); } + public grantEncrypt(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); } + public grantEncryptDecrypt(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); } + } + + return new Import(scope, id); + } + public readonly aliasName: string; public readonly aliasTargetKey: IKey; diff --git a/packages/@aws-cdk/aws-kms/test/test.alias.ts b/packages/@aws-cdk/aws-kms/test/test.alias.ts index 309c0e91e6d77..33df260bbf8e2 100644 --- a/packages/@aws-cdk/aws-kms/test/test.alias.ts +++ b/packages/@aws-cdk/aws-kms/test/test.alias.ts @@ -173,4 +173,58 @@ export = { test.done(); }, + + 'imported alias by name - can be used where a key is expected'(test: Test) { + const stack = new Stack(); + + const myAlias = Alias.fromAliasName(stack, 'MyAlias', 'alias/myAlias'); + + class MyConstruct extends Construct { + constructor(scope: Construct, id: string, key: IKey) { + super(scope, id); + + new CfnOutput(stack, 'OutId', { + value: key.keyId, + }); + new CfnOutput(stack, 'OutArn', { + value: key.keyArn, + }); + } + } + + new MyConstruct(stack, 'MyConstruct', myAlias); + + const template = SynthUtils.synthesize(stack).template.Outputs; + + test.deepEqual(template, { + 'OutId': { + 'Value': 'alias/myAlias', + }, + 'OutArn': { + 'Value': { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':kms:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':alias/myAlias', + ]], + }, + }, + }); + + test.done(); + }, + + 'imported alias by name - will throw an error when accessing the key'(test: Test) { + const stack = new Stack(); + + const myAlias = Alias.fromAliasName(stack, 'MyAlias', 'alias/myAlias'); + + test.throws(() => myAlias.aliasTargetKey, 'Cannot access aliasTargetKey on an Alias imnported by Alias.fromAliasName().'); + + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-lakeformation/.eslintrc.js b/packages/@aws-cdk/aws-lakeformation/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-lakeformation/.eslintrc.js +++ b/packages/@aws-cdk/aws-lakeformation/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-lambda-destinations/.eslintrc.js b/packages/@aws-cdk/aws-lambda-destinations/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/.eslintrc.js +++ b/packages/@aws-cdk/aws-lambda-destinations/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-lambda-event-sources/.eslintrc.js b/packages/@aws-cdk/aws-lambda-event-sources/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/.eslintrc.js +++ b/packages/@aws-cdk/aws-lambda-event-sources/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-lambda-nodejs/.eslintrc.js b/packages/@aws-cdk/aws-lambda-nodejs/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/.eslintrc.js +++ b/packages/@aws-cdk/aws-lambda-nodejs/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index 9643aff6f3ab1..84b938347b6f1 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -34,7 +34,7 @@ automatically transpiled and bundled whether it's written in JavaScript or TypeS Alternatively, an entry file and handler can be specified: ```ts new lambda.NodejsFunction(this, 'MyFunction', { - entry: '/path/to/my/file.ts' + entry: '/path/to/my/file.ts', handler: 'myExportedFunc' }); ``` diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/builder.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/builder.ts deleted file mode 100644 index 20ddcfd45c54d..0000000000000 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/builder.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { spawnSync } from 'child_process'; -import * as fs from 'fs'; -import * as path from 'path'; -import { findPkgPath } from './util'; - -/** - * Builder options - */ -export interface BuilderOptions { - /** - * Entry file - */ - readonly entry: string; - - /** - * The output directory - */ - readonly outDir: string; - - /** - * Expose modules as UMD under this name - */ - readonly global: string; - - /** - * Minify - */ - readonly minify?: boolean; - - /** - * Include source maps - */ - readonly sourceMaps?: boolean; - - /** - * The cache directory - */ - readonly cacheDir?: string; - - /** - * The node version to use as target for Babel - */ - readonly nodeVersion: string; - - /** - * The docker tag of the node base image to use in the parcel-bundler docker image - * - * @see https://hub.docker.com/_/node/?tab=tags - */ - readonly nodeDockerTag: string; - - /** - * The root of the project. This will be used as the source for the volume - * mounted in the Docker container. - */ - readonly projectRoot: string; - - /** - * The environment variables to pass to the container running Parcel. - * - * @default - no environment variables are passed to the container - */ - readonly environment?: { [key: string]: string; }; -} - -/** - * Builder - */ -export class Builder { - private readonly pkgPath: string; - - private readonly originalPkg: Buffer; - - private readonly originalPkgJson: { [key: string]: any }; - - constructor(private readonly options: BuilderOptions) { - // Original package.json - const pkgPath = findPkgPath(); - if (!pkgPath) { - throw new Error('Cannot find a `package.json` in this project.'); - } - this.pkgPath = path.join(pkgPath, 'package.json'); - this.originalPkg = fs.readFileSync(this.pkgPath); - this.originalPkgJson = JSON.parse(this.originalPkg.toString()); - } - - /** - * Build with parcel in a Docker container - */ - public build(): void { - try { - this.updatePkg(); - - const dockerBuildArgs = [ - 'build', - '--build-arg', `NODE_TAG=${this.options.nodeDockerTag}`, - '-t', 'parcel-bundler', - path.join(__dirname, '../parcel-bundler'), - ]; - - const build = spawnSync('docker', dockerBuildArgs); - - if (build.error) { - throw build.error; - } - - if (build.status !== 0) { - throw new Error(`[Status ${build.status}] stdout: ${build.stdout?.toString().trim()}\n\n\nstderr: ${build.stderr?.toString().trim()}`); - } - - const containerProjectRoot = '/project'; - const containerOutDir = '/out'; - const containerCacheDir = '/cache'; - const containerEntryPath = path.join(containerProjectRoot, path.relative(this.options.projectRoot, path.resolve(this.options.entry))); - - const dockerRunArgs = [ - 'run', '--rm', - '-v', `${this.options.projectRoot}:${containerProjectRoot}`, - '-v', `${path.resolve(this.options.outDir)}:${containerOutDir}`, - ...(this.options.cacheDir ? ['-v', `${path.resolve(this.options.cacheDir)}:${containerCacheDir}`] : []), - ...flatten(Object.entries(this.options.environment || {}).map(([k, v]) => ['--env', `${k}=${v}`])), - '-w', path.dirname(containerEntryPath).replace(/\\/g, '/'), // Always use POSIX paths in the container - 'parcel-bundler', - ]; - const parcelArgs = [ - 'parcel', 'build', containerEntryPath.replace(/\\/g, '/'), // Always use POSIX paths in the container - '--out-dir', containerOutDir, - '--out-file', 'index.js', - '--global', this.options.global, - '--target', 'node', - '--bundle-node-modules', - '--log-level', '2', - !this.options.minify && '--no-minify', - !this.options.sourceMaps && '--no-source-maps', - ...(this.options.cacheDir ? ['--cache-dir', containerCacheDir] : []), - ].filter(Boolean) as string[]; - - const parcel = spawnSync('docker', [...dockerRunArgs, ...parcelArgs]); - - if (parcel.error) { - throw parcel.error; - } - - if (parcel.status !== 0) { - throw new Error(`[Status ${parcel.status}] stdout: ${parcel.stdout?.toString().trim()}\n\n\nstderr: ${parcel.stderr?.toString().trim()}`); - } - } catch (err) { - throw new Error(`Failed to build file at ${this.options.entry}: ${err}`); - } finally { // Always restore package.json to original - this.restorePkg(); - } - } - - /** - * Updates the package.json to configure Parcel - */ - private updatePkg() { - const updateData: { [key: string]: any } = {}; - // Update engines.node (Babel target) - updateData.engines = { node: `>= ${this.options.nodeVersion}` }; - - // Write new package.json - if (Object.keys(updateData).length !== 0) { - fs.writeFileSync(this.pkgPath, JSON.stringify({ - ...this.originalPkgJson, - ...updateData, - }, null, 2)); - } - } - - private restorePkg() { - fs.writeFileSync(this.pkgPath, this.originalPkg); - } -} - -function flatten(x: string[][]) { - return Array.prototype.concat([], ...x); -} diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts new file mode 100644 index 0000000000000..15457bcf45869 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -0,0 +1,137 @@ +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as fs from 'fs'; +import * as path from 'path'; +import { findPkgPath } from './util'; + +/** + * Options for Parcel bundling + */ +export interface ParcelOptions { + /** + * Entry file + */ + readonly entry: string; + + /** + * Expose modules as UMD under this name + */ + readonly global: string; + + /** + * Minify + */ + readonly minify?: boolean; + + /** + * Include source maps + */ + readonly sourceMaps?: boolean; + + /** + * The cache directory + */ + readonly cacheDir?: string; + + /** + * The node version to use as target for Babel + */ + readonly nodeVersion: string; + + /** + * The docker tag of the node base image to use in the parcel-bundler docker image + * + * @see https://hub.docker.com/_/node/?tab=tags + */ + readonly nodeDockerTag: string; + + /** + * The root of the project. This will be used as the source for the volume + * mounted in the Docker container. + */ + readonly projectRoot: string; + + /** + * The environment variables to pass to the container running Parcel. + * + * @default - no environment variables are passed to the container + */ + readonly environment?: { [key: string]: string; }; +} + +/** + * Parcel code + */ +export class Bundling { + public static parcel(options: ParcelOptions): lambda.AssetCode { + // Original package.json path and content + let pkgPath = findPkgPath(); + if (!pkgPath) { + throw new Error('Cannot find a `package.json` in this project.'); + } + pkgPath = path.join(pkgPath, 'package.json'); + const originalPkg = fs.readFileSync(pkgPath); + const originalPkgJson = JSON.parse(originalPkg.toString()); + + // Update engines.node in package.json to set the right Babel target + setEngines(options.nodeVersion, pkgPath, originalPkgJson); + + // Entry file path relative to container path + const containerEntryPath = path.join(cdk.AssetStaging.BUNDLING_INPUT_DIR, path.relative(options.projectRoot, path.resolve(options.entry))); + + try { + const command = [ + 'parcel', 'build', containerEntryPath.replace(/\\/g, '/'), // Always use POSIX paths in the container + '--out-dir', cdk.AssetStaging.BUNDLING_OUTPUT_DIR, + '--out-file', 'index.js', + '--global', options.global, + '--target', 'node', + '--bundle-node-modules', + '--log-level', '2', + !options.minify && '--no-minify', + !options.sourceMaps && '--no-source-maps', + ...(options.cacheDir ? ['--cache-dir', '/parcel-cache'] : []), + ].filter(Boolean) as string[]; + + return lambda.Code.fromAsset(options.projectRoot, { + assetHashType: cdk.AssetHashType.BUNDLE, + bundling: { + image: cdk.BundlingDockerImage.fromAsset(path.join(__dirname, '../parcel-bundler'), { + buildArgs: { + NODE_TAG: options.nodeDockerTag ?? `${process.versions.node}-alpine`, + }, + }), + environment: options.environment, + volumes: options.cacheDir + ? [{ containerPath: '/parcel-cache', hostPath: options.cacheDir }] + : [], + workingDirectory: path.dirname(containerEntryPath).replace(/\\/g, '/'), // Always use POSIX paths in the container + command, + }, + }); + } finally { + restorePkg(pkgPath, originalPkg); + } + } +} + +function setEngines(nodeVersion: string, pkgPath: string, originalPkgJson: any): void { + // Update engines.node (Babel target) + const updateData = { + engines: { + node: `>= ${nodeVersion}`, + }, + }; + + // Write new package.json + if (Object.keys(updateData).length !== 0) { + fs.writeFileSync(pkgPath, JSON.stringify({ + ...originalPkgJson, + ...updateData, + }, null, 2)); + } +} + +function restorePkg(pkgPath: string, originalPkg: Buffer): void { + fs.writeFileSync(pkgPath, originalPkg); +} diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts index 82c7b2df7833b..c231eb06d7f84 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts @@ -1,9 +1,8 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; -import * as crypto from 'crypto'; import * as fs from 'fs'; import * as path from 'path'; -import { Builder } from './builder'; +import { Bundling } from './bundling'; import { findGitPath, nodeMajorVersion, parseStackTrace } from './util'; /** @@ -103,37 +102,31 @@ export class NodejsFunction extends lambda.Function { } const entry = findEntry(id, props.entry); - const handler = props.handler || 'handler'; - const buildDir = props.buildDir || path.join(path.dirname(entry), '.build'); - const handlerDir = path.join(buildDir, crypto.createHash('sha256').update(entry).digest('hex')); + const handler = props.handler ?? 'handler'; const defaultRunTime = nodeMajorVersion() >= 12 ? lambda.Runtime.NODEJS_12_X : lambda.Runtime.NODEJS_10_X; - const runtime = props.runtime || defaultRunTime; + const runtime = props.runtime ?? defaultRunTime; const projectRoot = props.projectRoot ?? findGitPath(); if (!projectRoot) { throw new Error('Cannot find project root. Please specify it with `projectRoot`.'); } - - // Build with Parcel - const builder = new Builder({ - entry, - outDir: handlerDir, - global: handler, - minify: props.minify, - sourceMaps: props.sourceMaps, - cacheDir: props.cacheDir, - nodeVersion: extractVersion(runtime), - nodeDockerTag: props.nodeDockerTag || `${process.versions.node}-alpine`, - projectRoot: path.resolve(projectRoot), - environment: props.containerEnvironment, - }); - builder.build(); + const nodeDockerTag = props.nodeDockerTag ?? `${process.versions.node}-alpine`; super(scope, id, { ...props, runtime, - code: lambda.Code.fromAsset(handlerDir), + code: Bundling.parcel({ + entry, + global: handler, + minify: props.minify, + sourceMaps: props.sourceMaps, + cacheDir: props.cacheDir, + nodeVersion: extractVersion(runtime), + nodeDockerTag, + projectRoot: path.resolve(projectRoot), + environment: props.containerEnvironment, + }), handler: `index.${handler}`, }); } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/builder.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/builder.test.ts deleted file mode 100644 index 55502d783ec26..0000000000000 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/builder.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { spawnSync } from 'child_process'; -import * as path from 'path'; -import { Builder } from '../lib/builder'; - -jest.mock('child_process', () => ({ - spawnSync: jest.fn((_cmd: string, args: string[]) => { - if (args.includes('/project/folder/error')) { - return { error: 'parcel-error' }; - } - - if (args.includes('/project/folder/status')) { - return { status: 1, stdout: Buffer.from('status-error') }; - } - - if (args.includes('/project/folder/no-docker')) { - return { error: 'Error: spawnSync docker ENOENT' }; - } - - return { error: null, status: 0 }; - }), -})); - -beforeEach(() => { - jest.clearAllMocks(); -}); - -test('calls docker with the correct args', () => { - const builder = new Builder({ - entry: '/project/folder/entry.ts', - global: 'handler', - outDir: '/out-dir', - cacheDir: '/cache-dir', - nodeDockerTag: 'lts-alpine', - nodeVersion: '12', - projectRoot: '/project', - }); - builder.build(); - - // docker build - expect(spawnSync).toHaveBeenNthCalledWith(1, 'docker', [ - 'build', '--build-arg', 'NODE_TAG=lts-alpine', '-t', 'parcel-bundler', path.join(__dirname, '../parcel-bundler'), - ]); - - // docker run - expect(spawnSync).toHaveBeenNthCalledWith(2, 'docker', [ - 'run', '--rm', - '-v', '/project:/project', - '-v', '/out-dir:/out', - '-v', '/cache-dir:/cache', - '-w', '/project/folder', - 'parcel-bundler', - 'parcel', 'build', '/project/folder/entry.ts', - '--out-dir', '/out', - '--out-file', 'index.js', - '--global', 'handler', - '--target', 'node', - '--bundle-node-modules', - '--log-level', '2', - '--no-minify', - '--no-source-maps', - '--cache-dir', '/cache', - ]); -}); - -test('with Windows paths', () => { - const builder = new Builder({ - entry: 'C:\\my-project\\lib\\entry.ts', - global: 'handler', - outDir: '/out-dir', - cacheDir: '/cache-dir', - nodeDockerTag: 'lts-alpine', - nodeVersion: '12', - projectRoot: 'C:\\my-project', - }); - builder.build(); - - // docker run - expect(spawnSync).toHaveBeenCalledWith('docker', expect.arrayContaining([ - 'parcel', 'build', expect.stringContaining('/lib/entry.ts'), - ])); -}); - -test('with env vars', () => { - const builder = new Builder({ - entry: '/project/folder/entry.ts', - global: 'handler', - outDir: '/out-dir', - cacheDir: '/cache-dir', - nodeDockerTag: 'lts-alpine', - nodeVersion: '12', - projectRoot: '/project', - environment: { - KEY1: 'VALUE1', - KEY2: 'VALUE2', - }, - }); - builder.build(); - - // docker run - expect(spawnSync).toHaveBeenCalledWith('docker', expect.arrayContaining([ - 'run', - '--env', 'KEY1=VALUE1', - '--env', 'KEY2=VALUE2', - ])); -}); - -test('throws in case of error', () => { - const builder = new Builder({ - entry: '/project/folder/error', - global: 'handler', - outDir: 'out-dir', - nodeDockerTag: 'lts-alpine', - nodeVersion: '12', - projectRoot: '/project', - }); - expect(() => builder.build()).toThrow('parcel-error'); -}); - -test('throws if status is not 0', () => { - const builder = new Builder({ - entry: '/project/folder/status', - global: 'handler', - outDir: 'out-dir', - nodeDockerTag: 'lts-alpine', - nodeVersion: '12', - projectRoot: '/project', - }); - expect(() => builder.build()).toThrow('status-error'); -}); - -test('throws if docker is not installed', () => { - const builder = new Builder({ - entry: '/project/folder/no-docker', - global: 'handler', - outDir: 'out-dir', - nodeDockerTag: 'lts-alpine', - nodeVersion: '12', - projectRoot: '/project', - }); - expect(() => builder.build()).toThrow('Error: spawnSync docker ENOENT'); -}); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts new file mode 100644 index 0000000000000..9a58350934516 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -0,0 +1,75 @@ + +import { Code } from '@aws-cdk/aws-lambda'; +import { AssetHashType } from '@aws-cdk/core'; +import * as fs from 'fs'; +import { Bundling } from '../lib/bundling'; + +jest.mock('@aws-cdk/aws-lambda'); +const writeFileSyncMock = jest.spyOn(fs, 'writeFileSync'); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +test('Parcel bundling', () => { + Bundling.parcel({ + entry: '/project/folder/entry.ts', + global: 'handler', + cacheDir: '/cache-dir', + nodeDockerTag: 'lts-alpine', + nodeVersion: '12', + projectRoot: '/project', + environment: { + KEY: 'value', + }, + }); + + // Correctly bundles with parcel + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.BUNDLE, + bundling: expect.objectContaining({ + environment: { + KEY: 'value', + }, + volumes: [{ containerPath: '/parcel-cache', hostPath: '/cache-dir' }], + workingDirectory: '/asset-input/folder', + command: [ + 'parcel', 'build', '/asset-input/folder/entry.ts', + '--out-dir', '/asset-output', + '--out-file', 'index.js', + '--global', 'handler', + '--target', 'node', + '--bundle-node-modules', + '--log-level', '2', + '--no-minify', + '--no-source-maps', + '--cache-dir', '/parcel-cache', + ], + }), + }); + + // Correctly updates package.json + expect(writeFileSyncMock).toHaveBeenCalledWith( + expect.stringContaining('package.json'), + expect.stringContaining('"node": ">= 12"'), + ); +}); + +test('Parcel with Windows paths', () => { + Bundling.parcel({ + entry: 'C:\\my-project\\lib\\entry.ts', + global: 'handler', + cacheDir: '/cache-dir', + nodeDockerTag: 'lts-alpine', + nodeVersion: '12', + projectRoot: 'C:\\my-project', + }); + + expect(Code.fromAsset).toHaveBeenCalledWith('C:\\my-project', expect.objectContaining({ + bundling: expect.objectContaining({ + command: expect.arrayContaining([ + 'parcel', 'build', expect.stringContaining('/lib/entry.ts'), + ]), + }), + })); +}); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts index bd3bbeb5a0d9c..25f5589e9e388 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts @@ -1,42 +1,35 @@ import '@aws-cdk/assert/jest'; import { Runtime } from '@aws-cdk/aws-lambda'; import { Stack } from '@aws-cdk/core'; -import * as fs from 'fs-extra'; -import * as path from 'path'; import { NodejsFunction } from '../lib'; -import { Builder, BuilderOptions } from '../lib/builder'; +import { Bundling } from '../lib/bundling'; -jest.mock('../lib/builder', () => { +jest.mock('../lib/bundling', () => { return { - Builder: jest.fn().mockImplementation((options: BuilderOptions) => { - return { - build: jest.fn(() => { - require('fs-extra').ensureDirSync(options.outDir); // eslint-disable-line @typescript-eslint/no-require-imports - }), - }; - }), + Bundling: { + parcel: jest.fn().mockReturnValue({ + bind: () => { + return { inlineCode: 'code' }; + }, + bindToResource: () => { return; }, + }), + }, }; }); let stack: Stack; -const buildDir = path.join(__dirname, '.build'); beforeEach(() => { stack = new Stack(); - fs.removeSync(buildDir); -}); - -afterEach(() => { - fs.removeSync(buildDir); + jest.clearAllMocks(); }); test('NodejsFunction with .ts handler', () => { // WHEN new NodejsFunction(stack, 'handler1'); - expect(Builder).toHaveBeenCalledWith(expect.objectContaining({ + expect(Bundling.parcel).toHaveBeenCalledWith(expect.objectContaining({ entry: expect.stringContaining('function.test.handler1.ts'), // Automatically finds .ts handler file global: 'handler', - outDir: expect.stringContaining(buildDir), })); expect(stack).toHaveResource('AWS::Lambda::Function', { @@ -49,7 +42,7 @@ test('NodejsFunction with .js handler', () => { new NodejsFunction(stack, 'handler2'); // THEN - expect(Builder).toHaveBeenCalledWith(expect.objectContaining({ + expect(Bundling.parcel).toHaveBeenCalledWith(expect.objectContaining({ entry: expect.stringContaining('function.test.handler2.js'), // Automatically finds .ts handler file })); }); @@ -62,7 +55,7 @@ test('NodejsFunction with container env vars', () => { }, }); - expect(Builder).toHaveBeenCalledWith(expect.objectContaining({ + expect(Bundling.parcel).toHaveBeenCalledWith(expect.objectContaining({ environment: { KEY: 'VALUE', }, diff --git a/packages/@aws-cdk/aws-lambda/.eslintrc.js b/packages/@aws-cdk/aws-lambda/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-lambda/.eslintrc.js +++ b/packages/@aws-cdk/aws-lambda/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index 01b211d16e142..fb7283b2643d8 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -266,6 +266,9 @@ The `logRetention` property can be used to set a different expiration period. It is possible to obtain the function's log group as a `logs.ILogGroup` by calling the `logGroup` property of the `Function` construct. +By default, CDK uses the AWS SDK retry options when creating a log group. The `logRetentionRetryOptions` property +allows you to customize the maximum number of retries and base backoff duration. + *Note* that, if either `logRetention` is set or `logGroup` property is called, a [CloudFormation custom resource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html) is added to the stack that pre-creates the log group as part of the stack deployment, if it already doesn't exist, and sets the diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index ea2d2bf1f18ef..d99d1b1ce8377 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -12,7 +12,7 @@ import { calculateFunctionHash, trimFromStart } from './function-hash'; import { Version, VersionOptions } from './lambda-version'; import { CfnFunction } from './lambda.generated'; import { ILayerVersion } from './layers'; -import { LogRetention } from './log-retention'; +import { LogRetention, LogRetentionRetryOptions } from './log-retention'; import { Runtime } from './runtime'; /** @@ -232,6 +232,14 @@ export interface FunctionOptions extends EventInvokeConfigOptions { */ readonly logRetentionRole?: iam.IRole; + /** + * When log retention is specified, a custom resource attempts to create the CloudWatch log group. + * These options control the retry policy when interacting with CloudWatch APIs. + * + * @default - Default AWS SDK retry options. + */ + readonly logRetentionRetryOptions?: LogRetentionRetryOptions; + /** * Options for the `lambda.Version` resource automatically created by the * `fn.currentVersion` method. @@ -544,6 +552,7 @@ export class Function extends FunctionBase { logGroupName: `/aws/lambda/${this.functionName}`, retention: props.logRetention, role: props.logRetentionRole, + logRetentionRetryOptions: props.logRetentionRetryOptions, }); this._logGroup = logs.LogGroup.fromLogGroupArn(this, 'LogGroup', logretention.logGroupArn); } diff --git a/packages/@aws-cdk/aws-lambda/lib/log-retention-provider/index.ts b/packages/@aws-cdk/aws-lambda/lib/log-retention-provider/index.ts index 38ac1fb709d8b..16d44a5fd83de 100644 --- a/packages/@aws-cdk/aws-lambda/lib/log-retention-provider/index.ts +++ b/packages/@aws-cdk/aws-lambda/lib/log-retention-provider/index.ts @@ -2,15 +2,23 @@ // eslint-disable-next-line import/no-extraneous-dependencies import * as AWS from 'aws-sdk'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { RetryDelayOptions } from 'aws-sdk/lib/config'; + +interface SdkRetryOptions { + maxRetries?: number; + retryOptions?: RetryDelayOptions; +} /** * Creates a log group and doesn't throw if it exists. * - * @param logGroupName the name of the log group to create + * @param logGroupName the name of the log group to create. + * @param options CloudWatch API SDK options. */ -async function createLogGroupSafe(logGroupName: string) { +async function createLogGroupSafe(logGroupName: string, options?: SdkRetryOptions) { try { // Try to create the log group - const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28' }); + const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28', ...options }); await cloudwatchlogs.createLogGroup({ logGroupName }).promise(); } catch (e) { if (e.code !== 'ResourceAlreadyExistsException') { @@ -23,10 +31,11 @@ async function createLogGroupSafe(logGroupName: string) { * Puts or deletes a retention policy on a log group. * * @param logGroupName the name of the log group to create + * @param options CloudWatch API SDK options. * @param retentionInDays the number of days to retain the log events in the specified log group. */ -async function setRetentionPolicy(logGroupName: string, retentionInDays?: number) { - const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28' }); +async function setRetentionPolicy(logGroupName: string, options?: SdkRetryOptions, retentionInDays?: number) { + const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28', ...options }); if (!retentionInDays) { await cloudwatchlogs.deleteRetentionPolicy({ logGroupName }).promise(); } else { @@ -41,10 +50,13 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent // The target log group const logGroupName = event.ResourceProperties.LogGroupName; + // Parse to AWS SDK retry options + const retryOptions = parseRetryOptions(event.ResourceProperties.SdkRetry); + if (event.RequestType === 'Create' || event.RequestType === 'Update') { // Act on the target log group - await createLogGroupSafe(logGroupName); - await setRetentionPolicy(logGroupName, parseInt(event.ResourceProperties.RetentionInDays, 10)); + await createLogGroupSafe(logGroupName, retryOptions); + await setRetentionPolicy(logGroupName, retryOptions, parseInt(event.ResourceProperties.RetentionInDays, 10)); if (event.RequestType === 'Create') { // Set a retention policy of 1 day on the logs of this function. The log @@ -56,8 +68,8 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent // same time. This can sometime result in an OperationAbortedException. To // avoid this and because this operation is not critical we catch all errors. try { - await createLogGroupSafe(`/aws/lambda/${context.functionName}`); - await setRetentionPolicy(`/aws/lambda/${context.functionName}`, 1); + await createLogGroupSafe(`/aws/lambda/${context.functionName}`, retryOptions); + await setRetentionPolicy(`/aws/lambda/${context.functionName}`, retryOptions, 1); } catch (e) { console.log(e); } @@ -108,4 +120,19 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent } }); } + + function parseRetryOptions(rawOptions: any): SdkRetryOptions { + const retryOptions: SdkRetryOptions = {}; + if (rawOptions) { + if (rawOptions.maxRetries) { + retryOptions.maxRetries = parseInt(rawOptions.maxRetries, 10); + } + if (rawOptions.base) { + retryOptions.retryOptions = { + base: parseInt(rawOptions.base, 10), + }; + } + } + return retryOptions; + } } diff --git a/packages/@aws-cdk/aws-lambda/lib/log-retention.ts b/packages/@aws-cdk/aws-lambda/lib/log-retention.ts index 74feeb5e62794..6c5fec2da7cd9 100644 --- a/packages/@aws-cdk/aws-lambda/lib/log-retention.ts +++ b/packages/@aws-cdk/aws-lambda/lib/log-retention.ts @@ -26,6 +26,31 @@ export interface LogRetentionProps { * @default - A new role is created */ readonly role?: iam.IRole; + + /** + * Retry options for all AWS API calls. + * + * @default - AWS SDK default retry options + */ + readonly logRetentionRetryOptions?: LogRetentionRetryOptions; +} + +/** + * Retry options for all AWS API calls. + */ +export interface LogRetentionRetryOptions { + /** + * The maximum amount of retries. + * + * @default 3 (AWS SDK default) + */ + readonly maxRetries?: number; + /** + * The base duration to use in the exponential backoff for operation retries. + * + * @default Duration.millis(100) (AWS SDK default) + */ + readonly base?: cdk.Duration; } /** @@ -64,11 +89,16 @@ export class LogRetention extends cdk.Construct { // Need to use a CfnResource here to prevent lerna dependency cycles // @aws-cdk/aws-cloudformation -> @aws-cdk/aws-lambda -> @aws-cdk/aws-cloudformation + const retryOptions = props.logRetentionRetryOptions; const resource = new cdk.CfnResource(this, 'Resource', { type: 'Custom::LogRetention', properties: { ServiceToken: provider.functionArn, LogGroupName: props.logGroupName, + SdkRetry: retryOptions ? { + maxRetries: retryOptions.maxRetries, + base: retryOptions.base?.toMilliseconds(), + } : undefined, RetentionInDays: props.retention === logs.RetentionDays.INFINITE ? undefined : props.retention, }, }); diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index 0bba808919445..d46b25cee471c 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -71,7 +71,7 @@ "@types/lodash": "^4.14.155", "@types/nodeunit": "^0.0.31", "@types/sinon": "^9.0.4", - "aws-sdk": "^2.691.0", + "aws-sdk": "^2.699.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda/test/integ.log-retention.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.log-retention.expected.json index 4ca25f4d0ba99..3e45c9ce6d65d 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.log-retention.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.log-retention.expected.json @@ -133,7 +133,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3Bucket7046E6CE" + "Ref": "AssetParameters3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3S3Bucket21D86049" }, "S3Key": { "Fn::Join": [ @@ -146,7 +146,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3VersionKey3194A583" + "Ref": "AssetParameters3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3S3VersionKey1F67C4C1" } ] } @@ -159,7 +159,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3VersionKey3194A583" + "Ref": "AssetParameters3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3S3VersionKey1F67C4C1" } ] } @@ -331,17 +331,17 @@ } }, "Parameters": { - "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3Bucket7046E6CE": { + "AssetParameters3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3S3Bucket21D86049": { "Type": "String", - "Description": "S3 bucket for asset \"82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4e\"" + "Description": "S3 bucket for asset \"3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3\"" }, - "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3VersionKey3194A583": { + "AssetParameters3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3S3VersionKey1F67C4C1": { "Type": "String", - "Description": "S3 key for asset version \"82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4e\"" + "Description": "S3 key for asset version \"3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3\"" }, - "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eArtifactHashB967D42A": { + "AssetParameters3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3ArtifactHash31AA1F7C": { "Type": "String", - "Description": "Artifact hash for asset \"82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4e\"" + "Description": "Artifact hash for asset \"3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/test.log-retention-provider.ts b/packages/@aws-cdk/aws-lambda/test/test.log-retention-provider.ts index bfc37c0d1b6f2..007f2836bf44b 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.log-retention-provider.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.log-retention-provider.ts @@ -296,4 +296,41 @@ export = { test.done(); }, + + async 'custom log retention retry options'(test: Test) { + AWS.mock('CloudWatchLogs', 'createLogGroup', sinon.fake.resolves({})); + AWS.mock('CloudWatchLogs', 'putRetentionPolicy', sinon.fake.resolves({})); + AWS.mock('CloudWatchLogs', 'deleteRetentionPolicy', sinon.fake.resolves({})); + + const event = { + ...eventCommon, + RequestType: 'Create', + ResourceProperties: { + ServiceToken: 'token', + RetentionInDays: '30', + LogGroupName: 'group', + SdkRetry: { + maxRetries: '5', + base: '300', + }, + }, + }; + + const request = createRequest('SUCCESS'); + + await provider.handler(event as AWSLambda.CloudFormationCustomResourceCreateEvent, context); + + sinon.assert.calledWith(AWSSDK.CloudWatchLogs as any, { + apiVersion: '2014-03-28', + maxRetries: 5, + retryOptions: { + base: 300, + }, + }); + + test.equal(request.isDone(), true); + + test.done(); + }, + }; diff --git a/packages/@aws-cdk/aws-logs-destinations/.eslintrc.js b/packages/@aws-cdk/aws-logs-destinations/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-logs-destinations/.eslintrc.js +++ b/packages/@aws-cdk/aws-logs-destinations/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-logs/.eslintrc.js b/packages/@aws-cdk/aws-logs/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-logs/.eslintrc.js +++ b/packages/@aws-cdk/aws-logs/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-macie/.eslintrc.js b/packages/@aws-cdk/aws-macie/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-macie/.eslintrc.js +++ b/packages/@aws-cdk/aws-macie/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-managedblockchain/.eslintrc.js b/packages/@aws-cdk/aws-managedblockchain/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-managedblockchain/.eslintrc.js +++ b/packages/@aws-cdk/aws-managedblockchain/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-mediaconvert/.eslintrc.js b/packages/@aws-cdk/aws-mediaconvert/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-mediaconvert/.eslintrc.js +++ b/packages/@aws-cdk/aws-mediaconvert/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-medialive/.eslintrc.js b/packages/@aws-cdk/aws-medialive/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-medialive/.eslintrc.js +++ b/packages/@aws-cdk/aws-medialive/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-mediastore/.eslintrc.js b/packages/@aws-cdk/aws-mediastore/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-mediastore/.eslintrc.js +++ b/packages/@aws-cdk/aws-mediastore/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-msk/.eslintrc.js b/packages/@aws-cdk/aws-msk/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-msk/.eslintrc.js +++ b/packages/@aws-cdk/aws-msk/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-neptune/.eslintrc.js b/packages/@aws-cdk/aws-neptune/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-neptune/.eslintrc.js +++ b/packages/@aws-cdk/aws-neptune/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-networkmanager/.eslintrc.js b/packages/@aws-cdk/aws-networkmanager/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-networkmanager/.eslintrc.js +++ b/packages/@aws-cdk/aws-networkmanager/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-opsworks/.eslintrc.js b/packages/@aws-cdk/aws-opsworks/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-opsworks/.eslintrc.js +++ b/packages/@aws-cdk/aws-opsworks/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-opsworkscm/.eslintrc.js b/packages/@aws-cdk/aws-opsworkscm/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-opsworkscm/.eslintrc.js +++ b/packages/@aws-cdk/aws-opsworkscm/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-pinpoint/.eslintrc.js b/packages/@aws-cdk/aws-pinpoint/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-pinpoint/.eslintrc.js +++ b/packages/@aws-cdk/aws-pinpoint/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-pinpointemail/.eslintrc.js b/packages/@aws-cdk/aws-pinpointemail/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-pinpointemail/.eslintrc.js +++ b/packages/@aws-cdk/aws-pinpointemail/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-qldb/.eslintrc.js b/packages/@aws-cdk/aws-qldb/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-qldb/.eslintrc.js +++ b/packages/@aws-cdk/aws-qldb/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-ram/.eslintrc.js b/packages/@aws-cdk/aws-ram/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-ram/.eslintrc.js +++ b/packages/@aws-cdk/aws-ram/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-rds/.eslintrc.js b/packages/@aws-cdk/aws-rds/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-rds/.eslintrc.js +++ b/packages/@aws-cdk/aws-rds/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index 070ac5ca1698c..5ce914f99f47a 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -47,7 +47,7 @@ your instances will be launched privately or publicly: ```ts const instance = new DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.ORACLE_SE1, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'syscdk', vpc }); @@ -62,7 +62,7 @@ Example for max storage configuration: ```ts const instance = new DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.ORACLE_SE1, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'syscdk', vpc, maxAllocatedStorage: 200 @@ -76,14 +76,13 @@ a source database respectively: new DatabaseInstanceFromSnapshot(stack, 'Instance', { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.POSTGRES, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc }); new DatabaseInstanceReadReplica(stack, 'ReadReplica', { sourceDatabaseInstance: sourceInstance, - engine: rds.DatabaseInstanceEngine.POSTGRES, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc }); ``` diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts b/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts index 6bd029f7ff38c..20e0d66b499eb 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts @@ -33,11 +33,6 @@ export interface IDatabaseCluster extends IResource, ec2.IConnectable, secretsma * Endpoints which address each individual replica. */ readonly instanceEndpoints: Endpoint[]; - - /** - * The security group for this database cluster - */ - readonly securityGroupId: string; } /** @@ -50,9 +45,9 @@ export interface DatabaseClusterAttributes { readonly port: number; /** - * The security group of the database cluster + * The security groups of the database cluster */ - readonly securityGroup: ec2.ISecurityGroup; + readonly securityGroups: ec2.ISecurityGroup[]; /** * Identifier for the cluster diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 577a5420d0632..b11bdb40a84ff 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -239,11 +239,6 @@ abstract class DatabaseClusterBase extends Resource implements IDatabaseCluster */ public abstract readonly connections: ec2.Connections; - /** - * Security group identifier of this database - */ - public abstract readonly securityGroupId: string; - /** * Renders the secret attachment target specifications. */ @@ -268,7 +263,7 @@ export class DatabaseCluster extends DatabaseClusterBase { class Import extends DatabaseClusterBase implements IDatabaseCluster { public readonly defaultPort = ec2.Port.tcp(attrs.port); public readonly connections = new ec2.Connections({ - securityGroups: [attrs.securityGroup], + securityGroups: attrs.securityGroups, defaultPort: this.defaultPort, }); public readonly clusterIdentifier = attrs.clusterIdentifier; @@ -276,7 +271,6 @@ export class DatabaseCluster extends DatabaseClusterBase { public readonly clusterEndpoint = new Endpoint(attrs.clusterEndpointAddress, attrs.port); public readonly clusterReadEndpoint = new Endpoint(attrs.readerEndpointAddress, attrs.port); public readonly instanceEndpoints = attrs.instanceEndpointAddresses.map(a => new Endpoint(a, attrs.port)); - public readonly securityGroupId = attrs.securityGroup.securityGroupId; } return new Import(scope, id); @@ -312,11 +306,6 @@ export class DatabaseCluster extends DatabaseClusterBase { */ public readonly connections: ec2.Connections; - /** - * Security group identifier of this database - */ - public readonly securityGroupId: string; - /** * The secret attached to this cluster */ @@ -358,12 +347,12 @@ export class DatabaseCluster extends DatabaseClusterBase { subnetGroup.applyRemovalPolicy(RemovalPolicy.RETAIN); } - const securityGroup = props.instanceProps.securityGroup !== undefined ? - props.instanceProps.securityGroup : new ec2.SecurityGroup(this, 'SecurityGroup', { + const securityGroups = props.instanceProps.securityGroups ?? [ + new ec2.SecurityGroup(this, 'SecurityGroup', { description: 'RDS security group', vpc: props.instanceProps.vpc, - }); - this.securityGroupId = securityGroup.securityGroupId; + }), + ]; let secret: DatabaseSecret | undefined; if (!props.masterUser.password) { @@ -444,7 +433,7 @@ export class DatabaseCluster extends DatabaseClusterBase { engineVersion: props.engineVersion, dbClusterIdentifier: props.clusterIdentifier, dbSubnetGroupName: subnetGroup.ref, - vpcSecurityGroupIds: [this.securityGroupId], + vpcSecurityGroupIds: securityGroups.map(sg => sg.securityGroupId), port: props.port, dbClusterParameterGroupName: clusterParameterGroup && clusterParameterGroup.parameterGroupName, associatedRoles: clusterAssociatedRoles.length > 0 ? clusterAssociatedRoles : undefined, @@ -546,7 +535,7 @@ export class DatabaseCluster extends DatabaseClusterBase { } const defaultPort = ec2.Port.tcp(this.clusterEndpoint.port); - this.connections = new ec2.Connections({ securityGroups: [securityGroup], defaultPort }); + this.connections = new ec2.Connections({ securityGroups, defaultPort }); } /** diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 5ed0925bf5d7d..7af0ebe13a58c 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -313,7 +313,7 @@ export interface DatabaseInstanceNewProps { /** * The name of the compute and memory capacity classes. */ - readonly instanceClass: ec2.InstanceType; + readonly instanceType: ec2.InstanceType; /** * Specifies if the database instance is a multiple Availability Zone deployment. @@ -610,7 +610,7 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData availabilityZone: props.multiAz ? undefined : props.availabilityZone, backupRetentionPeriod: props.backupRetention ? props.backupRetention.toDays() : undefined, copyTagsToSnapshot: props.copyTagsToSnapshot !== undefined ? props.copyTagsToSnapshot : true, - dbInstanceClass: `db.${props.instanceClass}`, + dbInstanceClass: `db.${props.instanceType}`, dbInstanceIdentifier: props.instanceIdentifier, dbSubnetGroupName: subnetGroup.ref, deleteAutomatedBackups: props.deleteAutomatedBackups, @@ -995,7 +995,7 @@ export class DatabaseInstanceFromSnapshot extends DatabaseInstanceSource impleme /** * Construction properties for a DatabaseInstanceReadReplica. */ -export interface DatabaseInstanceReadReplicaProps extends DatabaseInstanceSourceProps { +export interface DatabaseInstanceReadReplicaProps extends DatabaseInstanceNewProps { /** * The source database instance. * diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index 95e04ec684069..e52841da926c0 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -116,7 +116,7 @@ export interface InstanceProps { * * @default a new security group is created. */ - readonly securityGroup?: ec2.ISecurityGroup; + readonly securityGroups?: ec2.ISecurityGroup[]; /** * The DB parameter group to associate with the instance. diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json index d5c7708151b53..01687488c079e 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json @@ -967,7 +967,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3Bucket7046E6CE" + "Ref": "AssetParameters3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3S3Bucket21D86049" }, "S3Key": { "Fn::Join": [ @@ -980,7 +980,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3VersionKey3194A583" + "Ref": "AssetParameters3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3S3VersionKey1F67C4C1" } ] } @@ -993,7 +993,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3VersionKey3194A583" + "Ref": "AssetParameters3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3S3VersionKey1F67C4C1" } ] } @@ -1108,17 +1108,17 @@ } }, "Parameters": { - "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3Bucket7046E6CE": { + "AssetParameters3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3S3Bucket21D86049": { "Type": "String", - "Description": "S3 bucket for asset \"82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4e\"" + "Description": "S3 bucket for asset \"3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3\"" }, - "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3VersionKey3194A583": { + "AssetParameters3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3S3VersionKey1F67C4C1": { "Type": "String", - "Description": "S3 key for asset version \"82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4e\"" + "Description": "S3 key for asset version \"3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3\"" }, - "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eArtifactHashB967D42A": { + "AssetParameters3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3ArtifactHash31AA1F7C": { "Type": "String", - "Description": "Artifact hash for asset \"82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4e\"" + "Description": "Artifact hash for asset \"3974ceb096f16a0d6c372c0c821ca2ab0333112497b2d3bc462ccaf2fc6037c3\"" } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts index f386c04b0a1d6..d37fd89f9b935 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts @@ -46,7 +46,7 @@ class DatabaseInstanceStack extends cdk.Stack { const instance = new rds.DatabaseInstance(this, 'Instance', { engine: rds.DatabaseInstanceEngine.ORACLE_SE1, licenseModel: rds.LicenseModel.BRING_YOUR_OWN_LICENSE, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MEDIUM), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MEDIUM), multiAz: true, storageType: rds.StorageType.IO1, masterUsername: 'syscdk', diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster.ts b/packages/@aws-cdk/aws-rds/test/test.cluster.ts index 597027e267f2e..8351399263c6a 100644 --- a/packages/@aws-cdk/aws-rds/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.cluster.ts @@ -98,7 +98,7 @@ export = { instanceProps: { instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc, - securityGroup: sg, + securityGroups: [sg], }, }); @@ -383,9 +383,9 @@ export = { instanceIdentifiers: ['identifier'], port: 3306, readerEndpointAddress: 'reader-address', - securityGroup: ec2.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'sg-123456789', { + securityGroups: [ec2.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'sg-123456789', { allowAllOutbound: false, - }), + })], }); // WHEN diff --git a/packages/@aws-cdk/aws-rds/test/test.instance.ts b/packages/@aws-cdk/aws-rds/test/test.instance.ts index baefed5b6b157..c22f926c95fbe 100644 --- a/packages/@aws-cdk/aws-rds/test/test.instance.ts +++ b/packages/@aws-cdk/aws-rds/test/test.instance.ts @@ -18,7 +18,7 @@ export = { new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.ORACLE_SE1, licenseModel: rds.LicenseModel.BRING_YOUR_OWN_LICENSE, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MEDIUM), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MEDIUM), multiAz: true, storageType: rds.StorageType.IO1, masterUsername: 'syscdk', @@ -215,7 +215,7 @@ export = { // WHEN new rds.DatabaseInstance(stack, 'Database', { engine: rds.DatabaseInstanceEngine.SQL_SERVER_EE, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'syscdk', masterUserPassword: cdk.SecretValue.plainText('tooshort'), vpc, @@ -244,7 +244,7 @@ export = { new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.POSTGRES, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc, }); @@ -264,7 +264,7 @@ export = { test.throws(() => new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc, generateMasterUserPassword: true, }), '`masterUsername` must be specified when `generateMasterUserPassword` is set to true.'); @@ -281,7 +281,7 @@ export = { test.throws(() => new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc, masterUsername: 'superadmin', }), 'Cannot specify `masterUsername` when `generateMasterUserPassword` is set to false.'); @@ -298,7 +298,7 @@ export = { test.throws(() => new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc, masterUserPassword: cdk.SecretValue.plainText('supersecret'), generateMasterUserPassword: true, @@ -313,7 +313,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const sourceInstance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, }); @@ -321,8 +321,7 @@ export = { // WHEN new rds.DatabaseInstanceReadReplica(stack, 'ReadReplica', { sourceDatabaseInstance: sourceInstance, - engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc, }); @@ -354,7 +353,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, }); @@ -421,7 +420,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, }); @@ -474,7 +473,7 @@ export = { // WHEN const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, }); @@ -499,7 +498,7 @@ export = { // WHEN const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, }); @@ -530,7 +529,7 @@ export = { // WHEN new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, backupRetention: cdk.Duration.seconds(0), @@ -583,7 +582,7 @@ export = { // WHEN new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, monitoringInterval: cdk.Duration.minutes(1), @@ -612,7 +611,7 @@ export = { // WHEN const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, securityGroups: [securityGroup], @@ -649,7 +648,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const instance = new rds.DatabaseInstance(stack, 'Database', { engine: rds.DatabaseInstanceEngine.SQL_SERVER_EE, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'syscdk', masterUserPassword: cdk.SecretValue.plainText('tooshort'), vpc, @@ -667,7 +666,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const instance = new rds.DatabaseInstance(stack, 'Database', { engine: rds.DatabaseInstanceEngine.SQL_SERVER_EE, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'syscdk', vpc, }); @@ -694,7 +693,7 @@ export = { tzSupportedEngines.forEach((engine) => { test.ok(new rds.DatabaseInstance(stack, `${engine.name}-db`, { engine, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), masterUsername: 'master', timezone: 'Europe/Zurich', vpc, @@ -704,7 +703,7 @@ export = { tzUnsupportedEngines.forEach((engine) => { test.throws(() => new rds.DatabaseInstance(stack, `${engine.name}-db`, { engine, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), masterUsername: 'master', timezone: 'Europe/Zurich', vpc, @@ -723,7 +722,7 @@ export = { new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.POSTGRES, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc, maxAllocatedStorage: 200, }); @@ -744,7 +743,7 @@ export = { // WHEN new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, backupRetention: cdk.Duration.seconds(0), diff --git a/packages/@aws-cdk/aws-redshift/.eslintrc.js b/packages/@aws-cdk/aws-redshift/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-redshift/.eslintrc.js +++ b/packages/@aws-cdk/aws-redshift/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-resourcegroups/.eslintrc.js b/packages/@aws-cdk/aws-resourcegroups/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-resourcegroups/.eslintrc.js +++ b/packages/@aws-cdk/aws-resourcegroups/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-robomaker/.eslintrc.js b/packages/@aws-cdk/aws-robomaker/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-robomaker/.eslintrc.js +++ b/packages/@aws-cdk/aws-robomaker/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-route53-patterns/.eslintrc.js b/packages/@aws-cdk/aws-route53-patterns/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-route53-patterns/.eslintrc.js +++ b/packages/@aws-cdk/aws-route53-patterns/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-route53-targets/.eslintrc.js b/packages/@aws-cdk/aws-route53-targets/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-route53-targets/.eslintrc.js +++ b/packages/@aws-cdk/aws-route53-targets/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-route53/.eslintrc.js b/packages/@aws-cdk/aws-route53/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-route53/.eslintrc.js +++ b/packages/@aws-cdk/aws-route53/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 55bc1fec26d11..d406063cea940 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.691.0", + "aws-sdk": "^2.699.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-route53resolver/.eslintrc.js b/packages/@aws-cdk/aws-route53resolver/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-route53resolver/.eslintrc.js +++ b/packages/@aws-cdk/aws-route53resolver/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-s3-assets/.eslintrc.js b/packages/@aws-cdk/aws-s3-assets/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-s3-assets/.eslintrc.js +++ b/packages/@aws-cdk/aws-s3-assets/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-s3-assets/lib/asset.ts b/packages/@aws-cdk/aws-s3-assets/lib/asset.ts index 5c3f0a514f07e..307575cf561ae 100644 --- a/packages/@aws-cdk/aws-s3-assets/lib/asset.ts +++ b/packages/@aws-cdk/aws-s3-assets/lib/asset.ts @@ -1,5 +1,6 @@ import * as assets from '@aws-cdk/assets'; import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; @@ -19,7 +20,9 @@ export interface AssetOptions extends assets.CopyOptions, cdk.AssetOptions { readonly readers?: iam.IGrantable[]; /** - * Custom source hash to use when identifying the specific version of the asset. + * Custom hash to use when identifying the specific version of the asset. For consistency, + * this custom hash will be SHA256 hashed and encoded as hex. The resulting hash will be + * the asset hash. * * NOTE: the source hash is used in order to identify a specific revision of the asset, * and used for optimizing and caching deployment activities related to this asset such as @@ -144,7 +147,12 @@ export class Asset extends cdk.Construct implements cdk.IAsset { this.httpUrl = location.httpUrl; this.s3Url = location.httpUrl; // for backwards compatibility - this.bucket = s3.Bucket.fromBucketName(this, 'AssetBucket', this.s3BucketName); + const kmsKey = location.kmsKeyArn ? kms.Key.fromKeyArn(this, 'Key', location.kmsKeyArn) : undefined; + + this.bucket = s3.Bucket.fromBucketAttributes(this, 'AssetBucket', { + bucketName: this.s3BucketName, + encryptionKey: kmsKey, + }); for (const reader of (props.readers ?? [])) { this.grantRead(reader); diff --git a/packages/@aws-cdk/aws-s3-assets/package.json b/packages/@aws-cdk/aws-s3-assets/package.json index 3b8fe5bdebded..21b237022c32e 100644 --- a/packages/@aws-cdk/aws-s3-assets/package.json +++ b/packages/@aws-cdk/aws-s3-assets/package.json @@ -72,6 +72,7 @@ "@aws-cdk/assets": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "constructs": "^3.0.2" @@ -81,6 +82,7 @@ "@aws-cdk/assets": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "constructs": "^3.0.2" diff --git a/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts b/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts index 4da45143c59f8..4f0f8b9ab4519 100644 --- a/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts +++ b/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts @@ -1,4 +1,4 @@ -import { ResourcePart, SynthUtils } from '@aws-cdk/assert'; +import { arrayWith, ResourcePart, SynthUtils } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; @@ -40,7 +40,7 @@ test('simple use case', () => { artifactHashParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2ArtifactHash220DE9BD', }); - const template = JSON.parse(fs.readFileSync(path.join(session.directory, 'MyStack.template.json'), 'utf-8')); + const template = JSON.parse(fs.readFileSync(path.join(session.directory, 'MyStack.template.json'), { encoding: 'utf-8' })); expect(template.Parameters.AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B.Type).toBe('String'); expect(template.Parameters.AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3VersionKey1F7D75F9.Type).toBe('String'); @@ -124,6 +124,31 @@ test('"readers" or "grantRead" can be used to grant read permissions on the asse }); }); +test('"grantRead" also gives KMS permissions when using the new bootstrap stack', () => { + const stack = new cdk.Stack(undefined, undefined, { + synthesizer: new cdk.DefaultStackSynthesizer(), + }); + const group = new iam.Group(stack, 'MyGroup'); + + const asset = new Asset(stack, 'MyAsset', { + path: path.join(__dirname, 'sample-asset-directory'), + readers: [ group ], + }); + + asset.grantRead(group); + + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: arrayWith({ + Action: ['kms:Decrypt', 'kms:DescribeKey'], + Effect: 'Allow', + Resource: { 'Fn::ImportValue': 'CdkBootstrap-hnb659fds-FileAssetKeyArn' }, + }), + }, + }); +}); + test('fails if directory not found', () => { const stack = new cdk.Stack(); expect(() => new Asset(stack, 'MyDirectory', { diff --git a/packages/@aws-cdk/aws-s3-deployment/.eslintrc.js b/packages/@aws-cdk/aws-s3-deployment/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-s3-deployment/.eslintrc.js +++ b/packages/@aws-cdk/aws-s3-deployment/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-s3-deployment/lambda/test.sh b/packages/@aws-cdk/aws-s3-deployment/lambda/test.sh index a11706a3cee31..e87f8dfc2492b 100755 --- a/packages/@aws-cdk/aws-s3-deployment/lambda/test.sh +++ b/packages/@aws-cdk/aws-s3-deployment/lambda/test.sh @@ -4,6 +4,7 @@ # # prepares a staging directory with the requirements set -e +set -x scriptdir=$(cd $(dirname $0) && pwd) # prepare staging directory @@ -16,7 +17,7 @@ cp -f ${scriptdir}/src/* $PWD cp -f ${scriptdir}/test/* $PWD # install deps -pip3 install -r requirements.txt -t . +pip3 install --no-user -r requirements.txt -t . # run our tests exec python3 test.py $@ diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts index e8f4fda42651b..b46e239a75a83 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts +++ b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts @@ -161,11 +161,11 @@ export class BucketDeployment extends cdk.Construct { throw new Error('Distribution must be specified if distribution paths are specified'); } - const sourceHash = calcSourceHash(handlerSourceDirectory); + const assetHash = calcSourceHash(handlerSourceDirectory); const handler = new lambda.SingletonFunction(this, 'CustomResourceHandler', { uuid: this.renderSingletonUuid(props.memoryLimit), - code: lambda.Code.fromAsset(handlerCodeBundle, { sourceHash }), + code: lambda.Code.fromAsset(handlerCodeBundle, { assetHash }), runtime: lambda.Runtime.PYTHON_3_6, handler: 'index.handler', lambdaPurpose: 'Custom::CDKBucketDeployment', @@ -174,8 +174,10 @@ export class BucketDeployment extends cdk.Construct { memorySize: props.memoryLimit, }); - const sources: SourceConfig[] = props.sources.map((source: ISource) => source.bind(this)); - sources.forEach(source => source.bucket.grantRead(handler)); + const handlerRole = handler.role; + if (!handlerRole) { throw new Error('lambda.SingletonFunction should have created a Role'); } + + const sources: SourceConfig[] = props.sources.map((source: ISource) => source.bind(this, { handlerRole })); props.destinationBucket.grantReadWrite(handler); if (props.distribution) { diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/source.ts b/packages/@aws-cdk/aws-s3-deployment/lib/source.ts index b9ed382aee75f..6f0f877662891 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/source.ts +++ b/packages/@aws-cdk/aws-s3-deployment/lib/source.ts @@ -1,3 +1,4 @@ +import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import * as s3_assets from '@aws-cdk/aws-s3-assets'; import * as cdk from '@aws-cdk/core'; @@ -14,15 +15,25 @@ export interface SourceConfig { readonly zipObjectKey: string; } +/** + * Bind context for ISources + */ +export interface DeploymentSourceContext { + /** + * The role for the handler + */ + readonly handlerRole: iam.IRole; +} + /** * Represents a source for bucket deployments. */ export interface ISource { /** * Binds the source to a bucket deployment. - * @param context The construct tree context. + * @param scope The construct tree context. */ - bind(context: cdk.Construct): SourceConfig; + bind(scope: cdk.Construct, context?: DeploymentSourceContext): SourceConfig; } /** @@ -43,7 +54,14 @@ export class Source { */ public static bucket(bucket: s3.IBucket, zipObjectKey: string): ISource { return { - bind: () => ({ bucket, zipObjectKey }), + bind: (_: cdk.Construct, context?: DeploymentSourceContext) => { + if (!context) { + throw new Error('To use a Source.bucket(), context must be provided'); + } + + bucket.grantRead(context.handlerRole); + return { bucket, zipObjectKey }; + }, }; } @@ -53,18 +71,24 @@ export class Source { */ public static asset(path: string, options?: s3_assets.AssetOptions): ISource { return { - bind(context: cdk.Construct): SourceConfig { + bind(scope: cdk.Construct, context?: DeploymentSourceContext): SourceConfig { + if (!context) { + throw new Error('To use a Source.asset(), context must be provided'); + } + let id = 1; - while (context.node.tryFindChild(`Asset${id}`)) { + while (scope.node.tryFindChild(`Asset${id}`)) { id++; } - const asset = new s3_assets.Asset(context, `Asset${id}`, { + const asset = new s3_assets.Asset(scope, `Asset${id}`, { path, ...options, }); if (!asset.isZipArchive) { throw new Error('Asset path must be either a .zip file or a directory'); } + asset.grantRead(context.handlerRole); + return { bucket: asset.bucket, zipObjectKey: asset.s3ObjectKey, diff --git a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json index cd76f93ae2e36..d0e61e14a23ae 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json @@ -248,7 +248,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersa9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ffS3Bucket848A1F31" + "Ref": "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3Bucket88A20322" }, "S3Key": { "Fn::Join": [ @@ -261,7 +261,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersa9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ffS3VersionKey983DBE96" + "Ref": "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3VersionKey5726B1E8" } ] } @@ -274,7 +274,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersa9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ffS3VersionKey983DBE96" + "Ref": "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3VersionKey5726B1E8" } ] } @@ -301,17 +301,17 @@ } }, "Parameters": { - "AssetParametersa9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ffS3Bucket848A1F31": { + "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3Bucket88A20322": { "Type": "String", - "Description": "S3 bucket for asset \"a9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ff\"" + "Description": "S3 bucket for asset \"85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0\"" }, - "AssetParametersa9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ffS3VersionKey983DBE96": { + "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3VersionKey5726B1E8": { "Type": "String", - "Description": "S3 key for asset version \"a9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ff\"" + "Description": "S3 key for asset version \"85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0\"" }, - "AssetParametersa9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ffArtifactHash08605F5E": { + "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0ArtifactHash877EFA91": { "Type": "String", - "Description": "Artifact hash for asset \"a9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ff\"" + "Description": "Artifact hash for asset \"85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0\"" }, "AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3Bucket9CD8B20A": { "Type": "String", diff --git a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json index 628b948fbd440..e3308f63a431d 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json @@ -291,7 +291,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersa9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ffS3Bucket848A1F31" + "Ref": "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3Bucket88A20322" }, "S3Key": { "Fn::Join": [ @@ -304,7 +304,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersa9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ffS3VersionKey983DBE96" + "Ref": "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3VersionKey5726B1E8" } ] } @@ -317,7 +317,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersa9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ffS3VersionKey983DBE96" + "Ref": "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3VersionKey5726B1E8" } ] } @@ -478,17 +478,17 @@ } }, "Parameters": { - "AssetParametersa9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ffS3Bucket848A1F31": { + "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3Bucket88A20322": { "Type": "String", - "Description": "S3 bucket for asset \"a9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ff\"" + "Description": "S3 bucket for asset \"85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0\"" }, - "AssetParametersa9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ffS3VersionKey983DBE96": { + "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3VersionKey5726B1E8": { "Type": "String", - "Description": "S3 key for asset version \"a9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ff\"" + "Description": "S3 key for asset version \"85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0\"" }, - "AssetParametersa9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ffArtifactHash08605F5E": { + "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0ArtifactHash877EFA91": { "Type": "String", - "Description": "Artifact hash for asset \"a9125fa9a40550c71cde90bd478cc23091e868067a12380c1df0827d013ad2ff\"" + "Description": "Artifact hash for asset \"85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0\"" }, "AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3Bucket9CD8B20A": { "Type": "String", diff --git a/packages/@aws-cdk/aws-s3-deployment/test/test.bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/test/test.bucket-deployment.ts index 0850702dbf414..fa4fad93d681e 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/test.bucket-deployment.ts +++ b/packages/@aws-cdk/aws-s3-deployment/test/test.bucket-deployment.ts @@ -1,4 +1,4 @@ -import { countResources, expect, haveResource } from '@aws-cdk/assert'; +import { arrayWith, countResources, expect, haveResource } from '@aws-cdk/assert'; import * as cloudfront from '@aws-cdk/aws-cloudfront'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; @@ -565,6 +565,33 @@ export = { test.done(); }, + 'Deployment role gets KMS permissions when using assets from new style synthesizer'(test: Test) { + const stack = new cdk.Stack(undefined, undefined, { + synthesizer: new cdk.DefaultStackSynthesizer(), + }); + const bucket = new s3.Bucket(stack, 'Dest'); + + // WHEN + new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], + destinationBucket: bucket, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: arrayWith({ + Action: ['kms:Decrypt', 'kms:DescribeKey'], + Effect: 'Allow', + Resource: { 'Fn::ImportValue': 'CdkBootstrap-hnb659fds-FileAssetKeyArn' }, + }), + }, + })); + + test.done(); + }, + 'memoryLimit can be used to specify the memory limit for the deployment resource handler'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-s3-notifications/.eslintrc.js b/packages/@aws-cdk/aws-s3-notifications/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-s3-notifications/.eslintrc.js +++ b/packages/@aws-cdk/aws-s3-notifications/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-s3/.eslintrc.js b/packages/@aws-cdk/aws-s3/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-s3/.eslintrc.js +++ b/packages/@aws-cdk/aws-s3/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-sagemaker/.eslintrc.js b/packages/@aws-cdk/aws-sagemaker/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-sagemaker/.eslintrc.js +++ b/packages/@aws-cdk/aws-sagemaker/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-sam/.eslintrc.js b/packages/@aws-cdk/aws-sam/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-sam/.eslintrc.js +++ b/packages/@aws-cdk/aws-sam/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-sam/package.json b/packages/@aws-cdk/aws-sam/package.json index e61c29fcf3d35..d472fc497b882 100644 --- a/packages/@aws-cdk/aws-sam/package.json +++ b/packages/@aws-cdk/aws-sam/package.json @@ -65,7 +65,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/jest": "^25.2.3", + "@types/jest": "^26.0.0", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", "jest": "^25.5.4", diff --git a/packages/@aws-cdk/aws-sdb/.eslintrc.js b/packages/@aws-cdk/aws-sdb/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-sdb/.eslintrc.js +++ b/packages/@aws-cdk/aws-sdb/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-secretsmanager/.eslintrc.js b/packages/@aws-cdk/aws-secretsmanager/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-secretsmanager/.eslintrc.js +++ b/packages/@aws-cdk/aws-secretsmanager/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-securityhub/.eslintrc.js b/packages/@aws-cdk/aws-securityhub/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-securityhub/.eslintrc.js +++ b/packages/@aws-cdk/aws-securityhub/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-servicecatalog/.eslintrc.js b/packages/@aws-cdk/aws-servicecatalog/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-servicecatalog/.eslintrc.js +++ b/packages/@aws-cdk/aws-servicecatalog/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-servicediscovery/.eslintrc.js b/packages/@aws-cdk/aws-servicediscovery/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-servicediscovery/.eslintrc.js +++ b/packages/@aws-cdk/aws-servicediscovery/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-ses-actions/.eslintrc.js b/packages/@aws-cdk/aws-ses-actions/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-ses-actions/.eslintrc.js +++ b/packages/@aws-cdk/aws-ses-actions/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-ses/.eslintrc.js b/packages/@aws-cdk/aws-ses/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-ses/.eslintrc.js +++ b/packages/@aws-cdk/aws-ses/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-sns-subscriptions/.eslintrc.js b/packages/@aws-cdk/aws-sns-subscriptions/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/.eslintrc.js +++ b/packages/@aws-cdk/aws-sns-subscriptions/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-sns-subscriptions/README.md b/packages/@aws-cdk/aws-sns-subscriptions/README.md index 9fb284ae82782..4195220f8ec93 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/README.md +++ b/packages/@aws-cdk/aws-sns-subscriptions/README.md @@ -18,6 +18,7 @@ Subscriptions can be added to the following endpoints: * Amazon SQS * AWS Lambda * Email +* SMS Subscriptions to Amazon SQS and AWS Lambda can be added on topics across regions. @@ -104,3 +105,23 @@ myTopic.addSubscription(new subscriptions.EmailSubscription(emailAddress.valueAs Note that email subscriptions require confirmation by visiting the link sent to the email address. + +### SMS + +Subscribe an sms number to your topic: + +```ts +import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'; + +myTopic.addSubscription(new subscriptions.SmsSubscription('+15551231234')); +``` + +The number being subscribed can also be [tokens](https://docs.aws.amazon.com/cdk/latest/guide/tokens.html), that resolve +to a number during deployment. A typical use case is when the number is passed in as a [CloudFormation +parameter](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html). The +following code defines a CloudFormation parameter and uses it in an sms subscription. + +```ts +const smsNumber = new CfnParameter(this, 'sms-param'); +myTopic.addSubscription(new subscriptions.SmsSubscription(smsNumber.valueAsString())); +``` diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/index.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/index.ts index a6646dfa2e0af..fd799b554cc80 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/index.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/index.ts @@ -3,3 +3,4 @@ export * from './email'; export * from './lambda'; export * from './sqs'; export * from './url'; +export * from './sms'; diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/sms.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/sms.ts new file mode 100644 index 0000000000000..151fc7b494a25 --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/sms.ts @@ -0,0 +1,25 @@ +import * as sns from '@aws-cdk/aws-sns'; +import { SubscriptionProps } from './subscription'; + +/** + * Options for SMS subscriptions. + */ +export interface SmsSubscriptionProps extends SubscriptionProps { +} + +/** + * Use an sms address as a subscription target + */ +export class SmsSubscription implements sns.ITopicSubscription { + constructor(private readonly phoneNumber: string, private readonly props: SmsSubscriptionProps = {}) { + } + + public bind(_topic: sns.ITopic): sns.TopicSubscriptionConfig { + return { + subscriberId: this.phoneNumber, + endpoint: this.phoneNumber, + protocol: sns.SubscriptionProtocol.SMS, + filterPolicy: this.props.filterPolicy, + }; + } +} diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts index 7222b4711be9d..241c1dfdb1491 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts @@ -998,3 +998,58 @@ test('region property on an imported topic as a parameter - lambda', () => { }, }); }); + +test('sms subscription', () => { + topic.addSubscription(new subs.SmsSubscription('+15551231234')); + + expect(stack).toMatchTemplate({ + 'Resources': { + 'MyTopic86869434': { + 'Type': 'AWS::SNS::Topic', + 'Properties': { + 'DisplayName': 'displayName', + 'TopicName': 'topicName', + }, + }, + 'MyTopic155512312349C8DEEEE': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'sms', + 'TopicArn': { + 'Ref': 'MyTopic86869434', + }, + 'Endpoint': '+15551231234', + }, + }, + }, + }); +}); + +test('sms subscription with unresolved', () => { + const smsToken = Token.asString({ Ref : 'my-sms-1' }); + topic.addSubscription(new subs.SmsSubscription(smsToken)); + + expect(stack).toMatchTemplate({ + 'Resources': { + 'MyTopic86869434': { + 'Type': 'AWS::SNS::Topic', + 'Properties': { + 'DisplayName': 'displayName', + 'TopicName': 'topicName', + }, + }, + 'MyTopicTokenSubscription141DD1BE2': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Endpoint': { + 'Ref' : 'my-sms-1', + }, + 'Protocol': 'sms', + 'TopicArn': { + 'Ref': 'MyTopic86869434', + }, + }, + }, + }, + }); +}); diff --git a/packages/@aws-cdk/aws-sns/.eslintrc.js b/packages/@aws-cdk/aws-sns/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-sns/.eslintrc.js +++ b/packages/@aws-cdk/aws-sns/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-sqs/.eslintrc.js b/packages/@aws-cdk/aws-sqs/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-sqs/.eslintrc.js +++ b/packages/@aws-cdk/aws-sqs/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index 7436e4722811b..a6f5971457b23 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -65,7 +65,7 @@ "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.691.0", + "aws-sdk": "^2.699.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-ssm/.eslintrc.js b/packages/@aws-cdk/aws-ssm/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-ssm/.eslintrc.js +++ b/packages/@aws-cdk/aws-ssm/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/.eslintrc.js b/packages/@aws-cdk/aws-stepfunctions-tasks/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/.eslintrc.js +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md index e0e89b4ecd924..801a4b322bd03 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md @@ -85,8 +85,8 @@ The following example provides the field named `input` as the input to the `Task state that runs a Lambda function. ```ts -const submitJob = new sfn.Task(stack, 'Invoke Handler', { - task: new tasks.RunLambdaTask(submitJobLambda), +const submitJob = new tasks.LambdaInvoke(stack, 'Invoke Handler', { + lambdaFunction: submitJobLambda, inputPath: '$.input' }); ``` @@ -105,8 +105,8 @@ as well as other metadata. The following example assigns the output from the Task to a field named `result` ```ts -const submitJob = new sfn.Task(stack, 'Invoke Handler', { - task: new tasks.RunLambdaTask(submitJobLambda), +const submitJob = new tasks.LambdaInvoke(stack, 'Invoke Handler', { + lambdaFunction: submitJobLambda, outputPath: '$.Payload.result' }); ``` @@ -150,11 +150,10 @@ The following example provides the field named `input` as the input to the Lambd and invokes it asynchronously. ```ts -const submitJob = new sfn.Task(stack, 'Invoke Handler', { - task: new tasks.RunLambdaTask(submitJobLambda, { - payload: sfn.Data.StringAt('$.input'), - invocationType: tasks.InvocationType.EVENT, - }), +const submitJob = new tasks.LambdaInvoke(stack, 'Invoke Handler', { + lambdaFunction: submitJobLambda, + payload: sfn.Data.StringAt('$.input'), + invocationType: tasks.InvocationType.EVENT, }); ``` diff --git a/packages/@aws-cdk/aws-stepfunctions/.eslintrc.js b/packages/@aws-cdk/aws-stepfunctions/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-stepfunctions/.eslintrc.js +++ b/packages/@aws-cdk/aws-stepfunctions/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-synthetics/.eslintrc.js b/packages/@aws-cdk/aws-synthetics/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-synthetics/.eslintrc.js +++ b/packages/@aws-cdk/aws-synthetics/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-transfer/.eslintrc.js b/packages/@aws-cdk/aws-transfer/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-transfer/.eslintrc.js +++ b/packages/@aws-cdk/aws-transfer/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-waf/.eslintrc.js b/packages/@aws-cdk/aws-waf/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-waf/.eslintrc.js +++ b/packages/@aws-cdk/aws-waf/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-wafregional/.eslintrc.js b/packages/@aws-cdk/aws-wafregional/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-wafregional/.eslintrc.js +++ b/packages/@aws-cdk/aws-wafregional/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-wafv2/.eslintrc.js b/packages/@aws-cdk/aws-wafv2/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-wafv2/.eslintrc.js +++ b/packages/@aws-cdk/aws-wafv2/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-workspaces/.eslintrc.js b/packages/@aws-cdk/aws-workspaces/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/aws-workspaces/.eslintrc.js +++ b/packages/@aws-cdk/aws-workspaces/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/cdk-assets-schema/.eslintrc.js b/packages/@aws-cdk/cdk-assets-schema/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/cdk-assets-schema/.eslintrc.js +++ b/packages/@aws-cdk/cdk-assets-schema/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/cdk-assets-schema/package.json b/packages/@aws-cdk/cdk-assets-schema/package.json index 770f78deeb115..43f4a115a2aec 100644 --- a/packages/@aws-cdk/cdk-assets-schema/package.json +++ b/packages/@aws-cdk/cdk-assets-schema/package.json @@ -49,7 +49,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^25.2.3", + "@types/jest": "^26.0.0", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", "pkglint": "0.0.0" diff --git a/packages/@aws-cdk/cfnspec/.eslintrc.js b/packages/@aws-cdk/cfnspec/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/cfnspec/.eslintrc.js +++ b/packages/@aws-cdk/cfnspec/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/cfnspec/CHANGELOG.md b/packages/@aws-cdk/cfnspec/CHANGELOG.md index e161d97d380c6..85710f5c8e9b3 100644 --- a/packages/@aws-cdk/cfnspec/CHANGELOG.md +++ b/packages/@aws-cdk/cfnspec/CHANGELOG.md @@ -1,3 +1,198 @@ +# CloudFormation Resource Specification v15.1.0 + +## New Resource Types + +* AWS::EC2::PrefixList +* AWS::EFS::AccessPoint +* AWS::IoT::ProvisioningTemplate +* AWS::RDS::DBProxy +* AWS::RDS::DBProxyTargetGroup + +## Attribute Changes + +* AWS::Chatbot::SlackChannelConfiguration Arn (__added__) +* AWS::EFS::FileSystem FileSystemId (__added__) +* AWS::ElastiCache::ReplicationGroup ReaderEndPoint.Address (__added__) +* AWS::ElastiCache::ReplicationGroup ReaderEndPoint.Port (__added__) +* AWS::ImageBuilder::Image OutputResources (__deleted__) + +## Property Changes + +* AWS::CertificateManager::Certificate CertificateAuthorityArn (__added__) +* AWS::CertificateManager::Certificate CertificateTransparencyLoggingPreference (__added__) +* AWS::Chatbot::SlackChannelConfiguration Arn (__deleted__) +* AWS::CodeGuruProfiler::ProfilingGroup AgentPermissions (__added__) +* AWS::DynamoDB::Table SSESpecification.UpdateType (__changed__) + * Old: Conditional + * New: Mutable +* AWS::EC2::Volume OutpostArn (__added__) +* AWS::EFS::FileSystem FileSystemPolicy (__added__) +* AWS::EFS::FileSystem LifecyclePolicies.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-elasticfilesystem-filesystem-lifecyclepolicies + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-efs-filesystem-lifecyclepolicies +* AWS::EFS::FileSystem ProvisionedThroughputInMibps.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-elasticfilesystem-filesystem-provisionedthroughputinmibps + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-efs-filesystem-provisionedthroughputinmibps +* AWS::EFS::FileSystem ThroughputMode.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-elasticfilesystem-filesystem-throughputmode + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-efs-filesystem-throughputmode +* AWS::ElastiCache::ReplicationGroup MultiAZEnabled (__added__) +* AWS::MSK::Cluster ConfigurationInfo.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::MSK::Cluster KafkaVersion.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::RDS::DBInstance MultiAZ.UpdateType (__changed__) + * Old: Mutable + * New: Conditional +* AWS::SNS::Topic ContentBasedDeduplication (__added__) +* AWS::SNS::Topic FifoTopic (__added__) + +## Property Type Changes + +* AWS::EC2::ClientVpnEndpoint.FederatedAuthenticationRequest (__added__) +* AWS::Events::Rule.HttpParameters (__added__) +* AWS::KinesisFirehose::DeliveryStream.RedshiftRetryOptions (__added__) +* AWS::KinesisFirehose::DeliveryStream.VpcConfiguration (__added__) +* AWS::S3::Bucket.DeleteMarkerReplication (__added__) +* AWS::S3::Bucket.Metrics (__added__) +* AWS::S3::Bucket.ReplicationRuleAndOperator (__added__) +* AWS::S3::Bucket.ReplicationRuleFilter (__added__) +* AWS::S3::Bucket.ReplicationTime (__added__) +* AWS::S3::Bucket.ReplicationTimeValue (__added__) +* AWS::CertificateManager::Certificate.DomainValidationOption HostedZoneId (__added__) +* AWS::CertificateManager::Certificate.DomainValidationOption ValidationDomain.Required (__changed__) + * Old: true + * New: false +* AWS::CloudFront::Distribution.Origin ConnectionAttempts (__added__) +* AWS::CloudFront::Distribution.Origin ConnectionTimeout (__added__) +* AWS::CloudWatch::Alarm.MetricDataQuery Period (__added__) +* AWS::EC2::ClientVpnEndpoint.ClientAuthenticationRequest FederatedAuthentication (__added__) +* AWS::ECS::TaskDefinition.InferenceAccelerator DevicePolicy (__deleted__) +* AWS::EFS::FileSystem.ElasticFileSystemTag Key.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-filesystemtags.html#cfn-efs-filesystem-filesystemtags-key + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-elasticfilesystemtag.html#cfn-efs-filesystem-elasticfilesystemtag-key +* AWS::EFS::FileSystem.ElasticFileSystemTag Value.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-filesystemtags.html#cfn-efs-filesystem-filesystemtags-value + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-elasticfilesystemtag.html#cfn-efs-filesystem-elasticfilesystemtag-value +* AWS::EFS::FileSystem.LifecyclePolicy TransitionToIA.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticfilesystem-filesystem-lifecyclepolicy.html#cfn-elasticfilesystem-filesystem-lifecyclepolicy-transitiontoia + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-lifecyclepolicy.html#cfn-efs-filesystem-lifecyclepolicy-transitiontoia +* AWS::ElasticLoadBalancingV2::LoadBalancer.SubnetMapping PrivateIPv4Address (__added__) +* AWS::ElasticLoadBalancingV2::LoadBalancer.SubnetMapping AllocationId.Required (__changed__) + * Old: true + * New: false +* AWS::Events::Rule.Target HttpParameters (__added__) +* AWS::KinesisFirehose::DeliveryStream.BufferingHints IntervalInSeconds.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.BufferingHints SizeInMBs.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.DataFormatConversionConfiguration Enabled.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.DataFormatConversionConfiguration InputFormatConfiguration.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.DataFormatConversionConfiguration OutputFormatConfiguration.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.DataFormatConversionConfiguration SchemaConfiguration.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.ElasticsearchBufferingHints IntervalInSeconds.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.ElasticsearchBufferingHints SizeInMBs.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.ElasticsearchDestinationConfiguration ClusterEndpoint (__added__) +* AWS::KinesisFirehose::DeliveryStream.ElasticsearchDestinationConfiguration VpcConfiguration (__added__) +* AWS::KinesisFirehose::DeliveryStream.ElasticsearchDestinationConfiguration BufferingHints.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.ElasticsearchDestinationConfiguration DomainARN.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.ElasticsearchDestinationConfiguration IndexRotationPeriod.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.ElasticsearchDestinationConfiguration RetryOptions.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.ElasticsearchDestinationConfiguration S3BackupMode.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.ElasticsearchDestinationConfiguration TypeName.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.ElasticsearchRetryOptions DurationInSeconds.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.ExtendedS3DestinationConfiguration BufferingHints.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.ExtendedS3DestinationConfiguration CompressionFormat.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.InputFormatConfiguration Deserializer.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.OpenXJsonSerDe ColumnToJsonKeyMappings.DuplicatesAllowed (__deleted__) +* AWS::KinesisFirehose::DeliveryStream.OutputFormatConfiguration Serializer.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.Processor Parameters.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.RedshiftDestinationConfiguration RetryOptions (__added__) +* AWS::KinesisFirehose::DeliveryStream.RedshiftDestinationConfiguration S3BackupConfiguration (__added__) +* AWS::KinesisFirehose::DeliveryStream.RedshiftDestinationConfiguration S3BackupMode (__added__) +* AWS::KinesisFirehose::DeliveryStream.S3DestinationConfiguration BufferingHints.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.S3DestinationConfiguration CompressionFormat.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.SchemaConfiguration CatalogId.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.SchemaConfiguration DatabaseName.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.SchemaConfiguration Region.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.SchemaConfiguration RoleARN.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.SchemaConfiguration TableName.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.SchemaConfiguration VersionId.Required (__changed__) + * Old: true + * New: false +* AWS::KinesisFirehose::DeliveryStream.SplunkRetryOptions DurationInSeconds.Required (__changed__) + * Old: true + * New: false +* AWS::MSK::Cluster.ConfigurationInfo Arn.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::MSK::Cluster.ConfigurationInfo Revision.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::S3::Bucket.ReplicationDestination Metrics (__added__) +* AWS::S3::Bucket.ReplicationDestination ReplicationTime (__added__) +* AWS::S3::Bucket.ReplicationRule DeleteMarkerReplication (__added__) +* AWS::S3::Bucket.ReplicationRule Filter (__added__) +* AWS::S3::Bucket.ReplicationRule Priority (__added__) +* AWS::S3::Bucket.ReplicationRule Prefix.Required (__changed__) + * Old: true + * New: false + + # CloudFormation Resource Specification v14.4.0 ## New Resource Types diff --git a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts index 64ba6ae0e26d1..d2cb97e565a29 100644 --- a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts +++ b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts @@ -269,7 +269,7 @@ async function main() { ]); await write('.eslintrc.js', [ - "const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc');", + "const baseConfig = require('cdk-build-tools/config/eslintrc');", "baseConfig.parserOptions.project = __dirname + '/tsconfig.json';", 'module.exports = baseConfig;', ]); diff --git a/packages/@aws-cdk/cfnspec/build-tools/update.sh b/packages/@aws-cdk/cfnspec/build-tools/update.sh index b2415c71a93c7..12dacedc5f5c2 100755 --- a/packages/@aws-cdk/cfnspec/build-tools/update.sh +++ b/packages/@aws-cdk/cfnspec/build-tools/update.sh @@ -71,11 +71,11 @@ node ${scriptdir}/create-missing-libraries.js || { # update decdk dep list (cd ${scriptdir}/../../../decdk && node ./deps.js || true) -(cd ${scriptdir}/../../../monocdk-experiment && node ./deps.js || true) +(cd ${scriptdir}/../../../monocdk-experiment && yarn gen || true) # append old changelog after new and replace as the last step because otherwise we will not be idempotent _changelog_contents=$(cat CHANGELOG.md.new) if [ -n "${_changelog_contents}" ]; then cat CHANGELOG.md >> CHANGELOG.md.new cp CHANGELOG.md.new CHANGELOG.md -fi \ No newline at end of file +fi diff --git a/packages/@aws-cdk/cfnspec/cfn.version b/packages/@aws-cdk/cfnspec/cfn.version index 72f51351fcd88..d14dfbac36926 100644 --- a/packages/@aws-cdk/cfnspec/cfn.version +++ b/packages/@aws-cdk/cfnspec/cfn.version @@ -1 +1 @@ -14.4.0 +15.1.0 diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json index 364df4c34f6dd..4904014995091 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json @@ -5146,10 +5146,16 @@ "Required": true, "UpdateType": "Mutable" }, + "HostedZoneId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-certificatemanager-certificate-domainvalidationoption.html#cfn-certificatemanager-certificate-domainvalidationoption-hostedzoneid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "ValidationDomain": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-certificatemanager-certificate-domainvalidationoption.html#cfn-certificatemanager-certificate-domainvalidationoption-validationdomain", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" } } @@ -5654,6 +5660,18 @@ "AWS::CloudFront::Distribution.Origin": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-origin.html", "Properties": { + "ConnectionAttempts": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-origin.html#cfn-cloudfront-distribution-origin-connectionattempts", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "ConnectionTimeout": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-origin.html#cfn-cloudfront-distribution-origin-connectiontimeout", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, "CustomOriginConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-origin.html#cfn-cloudfront-distribution-origin-customoriginconfig", "Required": false, @@ -6085,6 +6103,12 @@ "Type": "MetricStat", "UpdateType": "Mutable" }, + "Period": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-alarm-metricdataquery.html#cfn-cloudwatch-alarm-metricdataquery-period", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, "ReturnData": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-alarm-metricdataquery.html#cfn-cloudwatch-alarm-metricdataquery-returndata", "PrimitiveType": "Boolean", @@ -9560,6 +9584,12 @@ "Type": "DirectoryServiceAuthenticationRequest", "UpdateType": "Mutable" }, + "FederatedAuthentication": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-clientvpnendpoint-clientauthenticationrequest.html#cfn-ec2-clientvpnendpoint-clientauthenticationrequest-federatedauthentication", + "Required": false, + "Type": "FederatedAuthenticationRequest", + "UpdateType": "Mutable" + }, "MutualAuthentication": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-clientvpnendpoint-clientauthenticationrequest.html#cfn-ec2-clientvpnendpoint-clientauthenticationrequest-mutualauthentication", "Required": false, @@ -9608,6 +9638,17 @@ } } }, + "AWS::EC2::ClientVpnEndpoint.FederatedAuthenticationRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-clientvpnendpoint-federatedauthenticationrequest.html", + "Properties": { + "SAMLProviderArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-clientvpnendpoint-federatedauthenticationrequest.html#cfn-ec2-clientvpnendpoint-federatedauthenticationrequest-samlproviderarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::EC2::ClientVpnEndpoint.TagSpecification": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-clientvpnendpoint-tagspecification.html", "Properties": { @@ -10974,6 +11015,23 @@ } } }, + "AWS::EC2::PrefixList.Entry": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-prefixlist-entry.html", + "Properties": { + "Cidr": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-prefixlist-entry.html#cfn-ec2-prefixlist-entry-cidr", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-prefixlist-entry.html#cfn-ec2-prefixlist-entry-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::EC2::SecurityGroup.Egress": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-rule.html", "Properties": { @@ -12359,12 +12417,6 @@ "Required": false, "UpdateType": "Immutable" }, - "DevicePolicy": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-inferenceaccelerator.html#cfn-ecs-taskdefinition-inferenceaccelerator-devicepolicy", - "PrimitiveType": "String", - "Required": false, - "UpdateType": "Immutable" - }, "DeviceType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-inferenceaccelerator.html#cfn-ecs-taskdefinition-inferenceaccelerator-devicetype", "PrimitiveType": "String", @@ -12838,17 +12890,98 @@ } } }, + "AWS::EFS::AccessPoint.AccessPointTag": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-accesspoint-accesspointtag.html", + "Properties": { + "Key": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-accesspoint-accesspointtag.html#cfn-efs-accesspoint-accesspointtag-key", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-accesspoint-accesspointtag.html#cfn-efs-accesspoint-accesspointtag-value", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::EFS::AccessPoint.CreationInfo": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-accesspoint-creationinfo.html", + "Properties": { + "OwnerGid": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-accesspoint-creationinfo.html#cfn-efs-accesspoint-creationinfo-ownergid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "OwnerUid": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-accesspoint-creationinfo.html#cfn-efs-accesspoint-creationinfo-owneruid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Permissions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-accesspoint-creationinfo.html#cfn-efs-accesspoint-creationinfo-permissions", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::EFS::AccessPoint.PosixUser": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-accesspoint-posixuser.html", + "Properties": { + "Gid": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-accesspoint-posixuser.html#cfn-efs-accesspoint-posixuser-gid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "SecondaryGids": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-accesspoint-posixuser.html#cfn-efs-accesspoint-posixuser-secondarygids", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "Uid": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-accesspoint-posixuser.html#cfn-efs-accesspoint-posixuser-uid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::EFS::AccessPoint.RootDirectory": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-accesspoint-rootdirectory.html", + "Properties": { + "CreationInfo": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-accesspoint-rootdirectory.html#cfn-efs-accesspoint-rootdirectory-creationinfo", + "Required": false, + "Type": "CreationInfo", + "UpdateType": "Immutable" + }, + "Path": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-accesspoint-rootdirectory.html#cfn-efs-accesspoint-rootdirectory-path", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::EFS::FileSystem.ElasticFileSystemTag": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-filesystemtags.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-elasticfilesystemtag.html", "Properties": { "Key": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-filesystemtags.html#cfn-efs-filesystem-filesystemtags-key", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-elasticfilesystemtag.html#cfn-efs-filesystem-elasticfilesystemtag-key", "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" }, "Value": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-filesystemtags.html#cfn-efs-filesystem-filesystemtags-value", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-elasticfilesystemtag.html#cfn-efs-filesystem-elasticfilesystemtag-value", "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" @@ -12856,10 +12989,10 @@ } }, "AWS::EFS::FileSystem.LifecyclePolicy": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticfilesystem-filesystem-lifecyclepolicy.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-lifecyclepolicy.html", "Properties": { "TransitionToIA": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticfilesystem-filesystem-lifecyclepolicy.html#cfn-elasticfilesystem-filesystem-lifecyclepolicy-transitiontoia", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-lifecyclepolicy.html#cfn-efs-filesystem-lifecyclepolicy-transitiontoia", "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" @@ -15431,7 +15564,13 @@ "AllocationId": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-loadbalancer-subnetmapping.html#cfn-elasticloadbalancingv2-loadbalancer-subnetmapping-allocationid", "PrimitiveType": "String", - "Required": true, + "Required": false, + "UpdateType": "Mutable" + }, + "PrivateIPv4Address": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-loadbalancer-subnetmapping.html#cfn-elasticloadbalancingv2-loadbalancer-subnetmapping-privateipv4address", + "PrimitiveType": "String", + "Required": false, "UpdateType": "Mutable" }, "SubnetId": { @@ -15879,6 +16018,35 @@ } } }, + "AWS::Events::Rule.HttpParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-httpparameters.html", + "Properties": { + "HeaderParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-httpparameters.html#cfn-events-rule-httpparameters-headerparameters", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "Map", + "UpdateType": "Mutable" + }, + "PathParameterValues": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-httpparameters.html#cfn-events-rule-httpparameters-pathparametervalues", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "QueryStringParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-httpparameters.html#cfn-events-rule-httpparameters-querystringparameters", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "Map", + "UpdateType": "Mutable" + } + } + }, "AWS::Events::Rule.InputTransformer": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-inputtransformer.html", "Properties": { @@ -15984,6 +16152,12 @@ "Type": "EcsParameters", "UpdateType": "Mutable" }, + "HttpParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-target.html#cfn-events-rule-target-httpparameters", + "Required": false, + "Type": "HttpParameters", + "UpdateType": "Mutable" + }, "Id": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-target.html#cfn-events-rule-target-id", "PrimitiveType": "String", @@ -19133,6 +19307,23 @@ } } }, + "AWS::IoT::ProvisioningTemplate.ProvisioningHook": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-provisioningtemplate-provisioninghook.html", + "Properties": { + "PayloadVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-provisioningtemplate-provisioninghook.html#cfn-iot-provisioningtemplate-provisioninghook-payloadversion", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TargetArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-provisioningtemplate-provisioninghook.html#cfn-iot-provisioningtemplate-provisioninghook-targetarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::IoT::Thing.AttributePayload": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-thing-attributepayload.html", "Properties": { @@ -22541,13 +22732,13 @@ "IntervalInSeconds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-bufferinghints.html#cfn-kinesisfirehose-deliverystream-bufferinghints-intervalinseconds", "PrimitiveType": "Integer", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "SizeInMBs": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-bufferinghints.html#cfn-kinesisfirehose-deliverystream-bufferinghints-sizeinmbs", "PrimitiveType": "Integer", - "Required": true, + "Required": false, "UpdateType": "Mutable" } } @@ -22604,24 +22795,24 @@ "Enabled": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-dataformatconversionconfiguration.html#cfn-kinesisfirehose-deliverystream-dataformatconversionconfiguration-enabled", "PrimitiveType": "Boolean", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "InputFormatConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-dataformatconversionconfiguration.html#cfn-kinesisfirehose-deliverystream-dataformatconversionconfiguration-inputformatconfiguration", - "Required": true, + "Required": false, "Type": "InputFormatConfiguration", "UpdateType": "Mutable" }, "OutputFormatConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-dataformatconversionconfiguration.html#cfn-kinesisfirehose-deliverystream-dataformatconversionconfiguration-outputformatconfiguration", - "Required": true, + "Required": false, "Type": "OutputFormatConfiguration", "UpdateType": "Mutable" }, "SchemaConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-dataformatconversionconfiguration.html#cfn-kinesisfirehose-deliverystream-dataformatconversionconfiguration-schemaconfiguration", - "Required": true, + "Required": false, "Type": "SchemaConfiguration", "UpdateType": "Mutable" } @@ -22650,13 +22841,13 @@ "IntervalInSeconds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-elasticsearchbufferinghints.html#cfn-kinesisfirehose-deliverystream-elasticsearchbufferinghints-intervalinseconds", "PrimitiveType": "Integer", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "SizeInMBs": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-elasticsearchbufferinghints.html#cfn-kinesisfirehose-deliverystream-elasticsearchbufferinghints-sizeinmbs", "PrimitiveType": "Integer", - "Required": true, + "Required": false, "UpdateType": "Mutable" } } @@ -22666,7 +22857,7 @@ "Properties": { "BufferingHints": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration-bufferinghints", - "Required": true, + "Required": false, "Type": "ElasticsearchBufferingHints", "UpdateType": "Mutable" }, @@ -22676,10 +22867,16 @@ "Type": "CloudWatchLoggingOptions", "UpdateType": "Mutable" }, + "ClusterEndpoint": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration-clusterendpoint", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "DomainARN": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration-domainarn", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "IndexName": { @@ -22691,7 +22888,7 @@ "IndexRotationPeriod": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration-indexrotationperiod", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "ProcessingConfiguration": { @@ -22702,7 +22899,7 @@ }, "RetryOptions": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration-retryoptions", - "Required": true, + "Required": false, "Type": "ElasticsearchRetryOptions", "UpdateType": "Mutable" }, @@ -22715,7 +22912,7 @@ "S3BackupMode": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration-s3backupmode", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "S3Configuration": { @@ -22727,8 +22924,14 @@ "TypeName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration-typename", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" + }, + "VpcConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-elasticsearchdestinationconfiguration-vpcconfiguration", + "Required": false, + "Type": "VpcConfiguration", + "UpdateType": "Immutable" } } }, @@ -22738,7 +22941,7 @@ "DurationInSeconds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-elasticsearchretryoptions.html#cfn-kinesisfirehose-deliverystream-elasticsearchretryoptions-durationinseconds", "PrimitiveType": "Integer", - "Required": true, + "Required": false, "UpdateType": "Mutable" } } @@ -22771,7 +22974,7 @@ }, "BufferingHints": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-extendeds3destinationconfiguration.html#cfn-kinesisfirehose-deliverystream-extendeds3destinationconfiguration-bufferinghints", - "Required": true, + "Required": false, "Type": "BufferingHints", "UpdateType": "Mutable" }, @@ -22784,7 +22987,7 @@ "CompressionFormat": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-extendeds3destinationconfiguration.html#cfn-kinesisfirehose-deliverystream-extendeds3destinationconfiguration-compressionformat", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "DataFormatConversionConfiguration": { @@ -22855,7 +23058,7 @@ "Properties": { "Deserializer": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-inputformatconfiguration.html#cfn-kinesisfirehose-deliverystream-inputformatconfiguration-deserializer", - "Required": true, + "Required": false, "Type": "Deserializer", "UpdateType": "Mutable" } @@ -22900,7 +23103,6 @@ }, "ColumnToJsonKeyMappings": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-openxjsonserde.html#cfn-kinesisfirehose-deliverystream-openxjsonserde-columntojsonkeymappings", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "Map", @@ -22986,7 +23188,7 @@ "Properties": { "Serializer": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-outputformatconfiguration.html#cfn-kinesisfirehose-deliverystream-outputformatconfiguration-serializer", - "Required": true, + "Required": false, "Type": "Serializer", "UpdateType": "Mutable" } @@ -23059,7 +23261,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-processor.html#cfn-kinesisfirehose-deliverystream-processor-parameters", "DuplicatesAllowed": false, "ItemType": "ProcessorParameter", - "Required": true, + "Required": false, "Type": "List", "UpdateType": "Mutable" }, @@ -23121,12 +23323,30 @@ "Type": "ProcessingConfiguration", "UpdateType": "Mutable" }, + "RetryOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-redshiftdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-redshiftdestinationconfiguration-retryoptions", + "Required": false, + "Type": "RedshiftRetryOptions", + "UpdateType": "Mutable" + }, "RoleARN": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-redshiftdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-redshiftdestinationconfiguration-rolearn", "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" }, + "S3BackupConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-redshiftdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-redshiftdestinationconfiguration-s3backupconfiguration", + "Required": false, + "Type": "S3DestinationConfiguration", + "UpdateType": "Mutable" + }, + "S3BackupMode": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-redshiftdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-redshiftdestinationconfiguration-s3backupmode", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "S3Configuration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-redshiftdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-redshiftdestinationconfiguration-s3configuration", "Required": true, @@ -23141,6 +23361,17 @@ } } }, + "AWS::KinesisFirehose::DeliveryStream.RedshiftRetryOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-redshiftretryoptions.html", + "Properties": { + "DurationInSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-redshiftretryoptions.html#cfn-kinesisfirehose-deliverystream-redshiftretryoptions-durationinseconds", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::KinesisFirehose::DeliveryStream.S3DestinationConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-s3destinationconfiguration.html", "Properties": { @@ -23152,7 +23383,7 @@ }, "BufferingHints": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-s3destinationconfiguration.html#cfn-kinesisfirehose-deliverystream-s3destinationconfiguration-bufferinghints", - "Required": true, + "Required": false, "Type": "BufferingHints", "UpdateType": "Mutable" }, @@ -23165,7 +23396,7 @@ "CompressionFormat": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-s3destinationconfiguration.html#cfn-kinesisfirehose-deliverystream-s3destinationconfiguration-compressionformat", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "EncryptionConfiguration": { @@ -23200,37 +23431,37 @@ "CatalogId": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-schemaconfiguration.html#cfn-kinesisfirehose-deliverystream-schemaconfiguration-catalogid", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "DatabaseName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-schemaconfiguration.html#cfn-kinesisfirehose-deliverystream-schemaconfiguration-databasename", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Region": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-schemaconfiguration.html#cfn-kinesisfirehose-deliverystream-schemaconfiguration-region", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "RoleARN": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-schemaconfiguration.html#cfn-kinesisfirehose-deliverystream-schemaconfiguration-rolearn", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "TableName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-schemaconfiguration.html#cfn-kinesisfirehose-deliverystream-schemaconfiguration-tablename", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "VersionId": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-schemaconfiguration.html#cfn-kinesisfirehose-deliverystream-schemaconfiguration-versionid", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" } } @@ -23317,11 +23548,38 @@ "DurationInSeconds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-splunkretryoptions.html#cfn-kinesisfirehose-deliverystream-splunkretryoptions-durationinseconds", "PrimitiveType": "Integer", - "Required": true, + "Required": false, "UpdateType": "Mutable" } } }, + "AWS::KinesisFirehose::DeliveryStream.VpcConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-vpcconfiguration.html", + "Properties": { + "RoleARN": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-vpcconfiguration.html#cfn-kinesisfirehose-deliverystream-vpcconfiguration-rolearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "SecurityGroupIds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-vpcconfiguration.html#cfn-kinesisfirehose-deliverystream-vpcconfiguration-securitygroupids", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Immutable" + }, + "SubnetIds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-vpcconfiguration.html#cfn-kinesisfirehose-deliverystream-vpcconfiguration-subnetids", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Immutable" + } + } + }, "AWS::LakeFormation::DataLakeSettings.Admins": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lakeformation-datalakesettings-admins.html", "ItemType": "DataLakePrincipal", @@ -23806,13 +24064,13 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-configurationinfo.html#cfn-msk-cluster-configurationinfo-arn", "PrimitiveType": "String", "Required": true, - "UpdateType": "Immutable" + "UpdateType": "Mutable" }, "Revision": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-configurationinfo.html#cfn-msk-cluster-configurationinfo-revision", "PrimitiveType": "Integer", "Required": true, - "UpdateType": "Immutable" + "UpdateType": "Mutable" } } }, @@ -26673,6 +26931,94 @@ } } }, + "AWS::RDS::DBProxy.AuthFormat": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbproxy-authformat.html", + "Properties": { + "AuthScheme": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbproxy-authformat.html#cfn-rds-dbproxy-authformat-authscheme", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbproxy-authformat.html#cfn-rds-dbproxy-authformat-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "IAMAuth": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbproxy-authformat.html#cfn-rds-dbproxy-authformat-iamauth", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SecretArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbproxy-authformat.html#cfn-rds-dbproxy-authformat-secretarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "UserName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbproxy-authformat.html#cfn-rds-dbproxy-authformat-username", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::RDS::DBProxy.TagFormat": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbproxy-tagformat.html", + "Properties": { + "Key": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbproxy-tagformat.html#cfn-rds-dbproxy-tagformat-key", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbproxy-tagformat.html#cfn-rds-dbproxy-tagformat-value", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::RDS::DBProxyTargetGroup.ConnectionPoolConfigurationInfoFormat": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbproxytargetgroup-connectionpoolconfigurationinfoformat.html", + "Properties": { + "ConnectionBorrowTimeout": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbproxytargetgroup-connectionpoolconfigurationinfoformat.html#cfn-rds-dbproxytargetgroup-connectionpoolconfigurationinfoformat-connectionborrowtimeout", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "InitQuery": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbproxytargetgroup-connectionpoolconfigurationinfoformat.html#cfn-rds-dbproxytargetgroup-connectionpoolconfigurationinfoformat-initquery", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "MaxConnectionsPercent": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbproxytargetgroup-connectionpoolconfigurationinfoformat.html#cfn-rds-dbproxytargetgroup-connectionpoolconfigurationinfoformat-maxconnectionspercent", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "MaxIdleConnectionsPercent": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbproxytargetgroup-connectionpoolconfigurationinfoformat.html#cfn-rds-dbproxytargetgroup-connectionpoolconfigurationinfoformat-maxidleconnectionspercent", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "SessionPinningFilters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbproxytargetgroup-connectionpoolconfigurationinfoformat.html#cfn-rds-dbproxytargetgroup-connectionpoolconfigurationinfoformat-sessionpinningfilters", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::RDS::DBSecurityGroup.Ingress": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-security-group-rule.html", "Properties": { @@ -27611,6 +27957,17 @@ } } }, + "AWS::S3::Bucket.DeleteMarkerReplication": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-deletemarkerreplication.html", + "Properties": { + "Status": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-deletemarkerreplication.html#cfn-s3-bucket-deletemarkerreplication-status", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::S3::Bucket.Destination": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-destination.html", "Properties": { @@ -27770,6 +28127,23 @@ } } }, + "AWS::S3::Bucket.Metrics": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-metrics.html", + "Properties": { + "EventThreshold": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-metrics.html#cfn-s3-bucket-metrics-eventthreshold", + "Required": true, + "Type": "ReplicationTimeValue", + "UpdateType": "Mutable" + }, + "Status": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-metrics.html#cfn-s3-bucket-metrics-status", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::S3::Bucket.MetricsConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-metricsconfiguration.html", "Properties": { @@ -28030,6 +28404,18 @@ "Type": "EncryptionConfiguration", "UpdateType": "Mutable" }, + "Metrics": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationconfiguration-rules-destination.html#cfn-s3-bucket-replicationdestination-metrics", + "Required": false, + "Type": "Metrics", + "UpdateType": "Mutable" + }, + "ReplicationTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationconfiguration-rules-destination.html#cfn-s3-bucket-replicationdestination-replicationtime", + "Required": false, + "Type": "ReplicationTime", + "UpdateType": "Mutable" + }, "StorageClass": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationconfiguration-rules-destination.html#cfn-s3-bucket-replicationconfiguration-rules-destination-storageclass", "PrimitiveType": "String", @@ -28041,12 +28427,24 @@ "AWS::S3::Bucket.ReplicationRule": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationconfiguration-rules.html", "Properties": { + "DeleteMarkerReplication": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationconfiguration-rules.html#cfn-s3-bucket-replicationrule-deletemarkerreplication", + "Required": false, + "Type": "DeleteMarkerReplication", + "UpdateType": "Mutable" + }, "Destination": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationconfiguration-rules.html#cfn-s3-bucket-replicationconfiguration-rules-destination", "Required": true, "Type": "ReplicationDestination", "UpdateType": "Mutable" }, + "Filter": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationconfiguration-rules.html#cfn-s3-bucket-replicationrule-filter", + "Required": false, + "Type": "ReplicationRuleFilter", + "UpdateType": "Mutable" + }, "Id": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationconfiguration-rules.html#cfn-s3-bucket-replicationconfiguration-rules-id", "PrimitiveType": "String", @@ -28056,7 +28454,13 @@ "Prefix": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationconfiguration-rules.html#cfn-s3-bucket-replicationconfiguration-rules-prefix", "PrimitiveType": "String", - "Required": true, + "Required": false, + "UpdateType": "Mutable" + }, + "Priority": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationconfiguration-rules.html#cfn-s3-bucket-replicationrule-priority", + "PrimitiveType": "Integer", + "Required": false, "UpdateType": "Mutable" }, "SourceSelectionCriteria": { @@ -28073,6 +28477,76 @@ } } }, + "AWS::S3::Bucket.ReplicationRuleAndOperator": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationruleandoperator.html", + "Properties": { + "Prefix": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationruleandoperator.html#cfn-s3-bucket-replicationruleandoperator-prefix", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TagFilters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationruleandoperator.html#cfn-s3-bucket-replicationruleandoperator-tagfilters", + "DuplicatesAllowed": false, + "ItemType": "TagFilter", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::S3::Bucket.ReplicationRuleFilter": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationrulefilter.html", + "Properties": { + "And": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationrulefilter.html#cfn-s3-bucket-replicationrulefilter-and", + "Required": false, + "Type": "ReplicationRuleAndOperator", + "UpdateType": "Mutable" + }, + "Prefix": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationrulefilter.html#cfn-s3-bucket-replicationrulefilter-prefix", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TagFilter": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationrulefilter.html#cfn-s3-bucket-replicationrulefilter-tagfilter", + "Required": false, + "Type": "TagFilter", + "UpdateType": "Mutable" + } + } + }, + "AWS::S3::Bucket.ReplicationTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationtime.html", + "Properties": { + "Status": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationtime.html#cfn-s3-bucket-replicationtime-status", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Time": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationtime.html#cfn-s3-bucket-replicationtime-time", + "Required": true, + "Type": "ReplicationTimeValue", + "UpdateType": "Mutable" + } + } + }, + "AWS::S3::Bucket.ReplicationTimeValue": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationtimevalue.html", + "Properties": { + "Minutes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-replicationtimevalue.html#cfn-s3-bucket-replicationtimevalue-minutes", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::S3::Bucket.RoutingRule": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-websiteconfiguration-routingrules.html", "Properties": { @@ -31914,7 +32388,7 @@ } } }, - "ResourceSpecificationVersion": "14.4.0", + "ResourceSpecificationVersion": "15.1.0", "ResourceTypes": { "AWS::ACMPCA::Certificate": { "Attributes": { @@ -36181,6 +36655,18 @@ "AWS::CertificateManager::Certificate": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-certificatemanager-certificate.html", "Properties": { + "CertificateAuthorityArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-certificatemanager-certificate.html#cfn-certificatemanager-certificate-certificateauthorityarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "CertificateTransparencyLoggingPreference": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-certificatemanager-certificate.html#cfn-certificatemanager-certificate-certificatetransparencyloggingpreference", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "DomainName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-certificatemanager-certificate.html#cfn-certificatemanager-certificate-domainname", "PrimitiveType": "String", @@ -36220,14 +36706,13 @@ } }, "AWS::Chatbot::SlackChannelConfiguration": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-chatbot-slackchannelconfiguration.html", "Properties": { - "Arn": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-chatbot-slackchannelconfiguration.html#cfn-chatbot-slackchannelconfiguration-arn", - "PrimitiveType": "String", - "Required": false, - "UpdateType": "Mutable" - }, "ConfigurationName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-chatbot-slackchannelconfiguration.html#cfn-chatbot-slackchannelconfiguration-configurationname", "PrimitiveType": "String", @@ -37299,6 +37784,12 @@ }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeguruprofiler-profilinggroup.html", "Properties": { + "AgentPermissions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeguruprofiler-profilinggroup.html#cfn-codeguruprofiler-profilinggroup-agentpermissions", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, "ProfilingGroupName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeguruprofiler-profilinggroup.html#cfn-codeguruprofiler-profilinggroup-profilinggroupname", "PrimitiveType": "String", @@ -39728,7 +40219,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-ssespecification", "Required": false, "Type": "SSESpecification", - "UpdateType": "Conditional" + "UpdateType": "Mutable" }, "StreamSpecification": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-streamspecification", @@ -40963,6 +41454,57 @@ } } }, + "AWS::EC2::PrefixList": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "OwnerId": { + "PrimitiveType": "String" + }, + "PrefixListId": { + "PrimitiveType": "String" + }, + "Version": { + "PrimitiveType": "Integer" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-prefixlist.html", + "Properties": { + "AddressFamily": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-prefixlist.html#cfn-ec2-prefixlist-addressfamily", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Entries": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-prefixlist.html#cfn-ec2-prefixlist-entries", + "ItemType": "Entry", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "MaxEntries": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-prefixlist.html#cfn-ec2-prefixlist-maxentries", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "PrefixListName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-prefixlist.html#cfn-ec2-prefixlist-prefixlistname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-prefixlist.html#cfn-ec2-prefixlist-tags", + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::EC2::Route": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route.html", "Properties": { @@ -42160,6 +42702,12 @@ "Required": false, "UpdateType": "Mutable" }, + "OutpostArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ebs-volume.html#cfn-ec2-ebs-volume-outpostarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "Size": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ebs-volume.html#cfn-ec2-ebs-volume-size", "PrimitiveType": "Integer", @@ -42613,7 +43161,57 @@ } } }, + "AWS::EFS::AccessPoint": { + "Attributes": { + "AccessPointId": { + "PrimitiveType": "String" + }, + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-accesspoint.html", + "Properties": { + "AccessPointTags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-accesspoint.html#cfn-efs-accesspoint-accesspointtags", + "DuplicatesAllowed": false, + "ItemType": "AccessPointTag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "ClientToken": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-accesspoint.html#cfn-efs-accesspoint-clienttoken", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "FileSystemId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-accesspoint.html#cfn-efs-accesspoint-filesystemid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "PosixUser": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-accesspoint.html#cfn-efs-accesspoint-posixuser", + "Required": false, + "Type": "PosixUser", + "UpdateType": "Immutable" + }, + "RootDirectory": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-accesspoint.html#cfn-efs-accesspoint-rootdirectory", + "Required": false, + "Type": "RootDirectory", + "UpdateType": "Immutable" + } + } + }, "AWS::EFS::FileSystem": { + "Attributes": { + "FileSystemId": { + "PrimitiveType": "String" + } + }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html", "Properties": { "Encrypted": { @@ -42622,6 +43220,12 @@ "Required": false, "UpdateType": "Immutable" }, + "FileSystemPolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-efs-filesystem-filesystempolicy", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, "FileSystemTags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-efs-filesystem-filesystemtags", "DuplicatesAllowed": false, @@ -42637,7 +43241,7 @@ "UpdateType": "Immutable" }, "LifecyclePolicies": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-elasticfilesystem-filesystem-lifecyclepolicies", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-efs-filesystem-lifecyclepolicies", "DuplicatesAllowed": false, "ItemType": "LifecyclePolicy", "Required": false, @@ -42651,13 +43255,13 @@ "UpdateType": "Immutable" }, "ProvisionedThroughputInMibps": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-elasticfilesystem-filesystem-provisionedthroughputinmibps", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-efs-filesystem-provisionedthroughputinmibps", "PrimitiveType": "Double", "Required": false, "UpdateType": "Mutable" }, "ThroughputMode": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-elasticfilesystem-filesystem-throughputmode", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-efs-filesystem-throughputmode", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" @@ -43358,6 +43962,12 @@ "ReadEndPoint.Ports.List": { "PrimitiveItemType": "String", "Type": "List" + }, + "ReaderEndPoint.Address": { + "PrimitiveType": "String" + }, + "ReaderEndPoint.Port": { + "PrimitiveType": "String" } }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticache-replicationgroup.html", @@ -43430,6 +44040,12 @@ "Required": false, "UpdateType": "Immutable" }, + "MultiAZEnabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticache-replicationgroup.html#cfn-elasticache-replicationgroup-multiazenabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, "NodeGroupConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticache-replicationgroup.html#cfn-elasticache-replicationgroup-nodegroupconfiguration", "DuplicatesAllowed": false, @@ -47070,9 +47686,6 @@ }, "ImageId": { "PrimitiveType": "String" - }, - "OutputResources": { - "Type": "OutputResources" } }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-image.html", @@ -47548,6 +48161,59 @@ } } }, + "AWS::IoT::ProvisioningTemplate": { + "Attributes": { + "TemplateArn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-provisioningtemplate.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-provisioningtemplate.html#cfn-iot-provisioningtemplate-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Enabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-provisioningtemplate.html#cfn-iot-provisioningtemplate-enabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "PreProvisioningHook": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-provisioningtemplate.html#cfn-iot-provisioningtemplate-preprovisioninghook", + "Required": false, + "Type": "ProvisioningHook", + "UpdateType": "Mutable" + }, + "ProvisioningRoleArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-provisioningtemplate.html#cfn-iot-provisioningtemplate-provisioningrolearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-provisioningtemplate.html#cfn-iot-provisioningtemplate-tags", + "ItemType": "Json", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "TemplateBody": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-provisioningtemplate.html#cfn-iot-provisioningtemplate-templatebody", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "TemplateName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-provisioningtemplate.html#cfn-iot-provisioningtemplate-templatename", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::IoT::Thing": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-thing.html", "Properties": { @@ -48811,7 +49477,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-msk-cluster.html#cfn-msk-cluster-configurationinfo", "Required": false, "Type": "ConfigurationInfo", - "UpdateType": "Immutable" + "UpdateType": "Mutable" }, "EncryptionInfo": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-msk-cluster.html#cfn-msk-cluster-encryptioninfo", @@ -48829,7 +49495,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-msk-cluster.html#cfn-msk-cluster-kafkaversion", "PrimitiveType": "String", "Required": true, - "UpdateType": "Immutable" + "UpdateType": "Mutable" }, "LoggingInfo": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-msk-cluster.html#cfn-msk-cluster-logginginfo", @@ -52257,7 +52923,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-database-instance.html#cfn-rds-dbinstance-multiaz", "PrimitiveType": "Boolean", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Conditional" }, "OptionGroupName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-database-instance.html#cfn-rds-dbinstance-optiongroupname", @@ -52402,6 +53068,122 @@ } } }, + "AWS::RDS::DBProxy": { + "Attributes": { + "DBProxyArn": { + "PrimitiveType": "String" + }, + "Endpoint": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxy.html", + "Properties": { + "Auth": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxy.html#cfn-rds-dbproxy-auth", + "ItemType": "AuthFormat", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + }, + "DBProxyName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxy.html#cfn-rds-dbproxy-dbproxyname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "DebugLogging": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxy.html#cfn-rds-dbproxy-debuglogging", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "EngineFamily": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxy.html#cfn-rds-dbproxy-enginefamily", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "IdleClientTimeout": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxy.html#cfn-rds-dbproxy-idleclienttimeout", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "RequireTLS": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxy.html#cfn-rds-dbproxy-requiretls", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "RoleArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxy.html#cfn-rds-dbproxy-rolearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxy.html#cfn-rds-dbproxy-tags", + "ItemType": "TagFormat", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "VpcSecurityGroupIds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxy.html#cfn-rds-dbproxy-vpcsecuritygroupids", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "VpcSubnetIds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxy.html#cfn-rds-dbproxy-vpcsubnetids", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Immutable" + } + } + }, + "AWS::RDS::DBProxyTargetGroup": { + "Attributes": { + "TargetGroupArn": { + "PrimitiveType": "String" + }, + "TargetGroupName": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxytargetgroup.html", + "Properties": { + "ConnectionPoolConfigurationInfo": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxytargetgroup.html#cfn-rds-dbproxytargetgroup-connectionpoolconfigurationinfo", + "Required": false, + "Type": "ConnectionPoolConfigurationInfoFormat", + "UpdateType": "Mutable" + }, + "DBClusterIdentifiers": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxytargetgroup.html#cfn-rds-dbproxytargetgroup-dbclusteridentifiers", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "DBInstanceIdentifiers": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxytargetgroup.html#cfn-rds-dbproxytargetgroup-dbinstanceidentifiers", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "DBProxyName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxytargetgroup.html#cfn-rds-dbproxytargetgroup-dbproxyname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::RDS::DBSecurityGroup": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-security-group.html", "Properties": { @@ -53824,12 +54606,24 @@ }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sns-topic.html", "Properties": { + "ContentBasedDeduplication": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sns-topic.html#cfn-sns-topic-contentbaseddeduplication", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, "DisplayName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sns-topic.html#cfn-sns-topic-displayname", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, + "FifoTopic": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sns-topic.html#cfn-sns-topic-fifotopic", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, "KmsMasterKeyId": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sns-topic.html#cfn-sns-topic-kmsmasterkeyid", "PrimitiveType": "String", diff --git a/packages/@aws-cdk/cfnspec/spec-source/500_IoT_ProvisioningTemplate_Tags_CorrectItemType_patch.json b/packages/@aws-cdk/cfnspec/spec-source/500_IoT_ProvisioningTemplate_Tags_CorrectItemType_patch.json new file mode 100644 index 0000000000000..d8e3332662ebd --- /dev/null +++ b/packages/@aws-cdk/cfnspec/spec-source/500_IoT_ProvisioningTemplate_Tags_CorrectItemType_patch.json @@ -0,0 +1,21 @@ +{ + "ResourceTypes": { + "AWS::IoT::ProvisioningTemplate": { + "patch": { + "description": "AWS::IoT::ProvisioningTemplate.Properties.Tag.ItemType should have been PrimitiveItemType", + "operations": [ + { + "op": "remove", + "path": "/Properties/Tags/ItemType", + "value": "Json" + }, + { + "op": "add", + "path": "/Properties/Tags/PrimitiveItemType", + "value": "Json" + } + ] + } + } + } +} diff --git a/packages/@aws-cdk/cfnspec/spec-source/550_ImageBuilder_Image_Attributes_OutputResources_patch.json b/packages/@aws-cdk/cfnspec/spec-source/550_ImageBuilder_Image_Attributes_OutputResources_patch.json deleted file mode 100644 index 858350a6c2bc5..0000000000000 --- a/packages/@aws-cdk/cfnspec/spec-source/550_ImageBuilder_Image_Attributes_OutputResources_patch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ResourceTypes": { - "AWS::ImageBuilder::Image": { - "patch": { - "description": "Replaces 'OutputResources' attribute type to be an array of Strings as it is (currently) not defined in the spec", - "operations": [ - { - "op": "replace", - "path": "/Attributes/OutputResources/Type", - "value": "List" - }, - { - "op": "add", - "path": "/Attributes/OutputResources/PrimitiveItemType", - "value": "String" - } - ] - } - } - } -} diff --git a/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.js b/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.js +++ b/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts index 17b53ca499a50..c2e53fe2c626b 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts @@ -28,7 +28,7 @@ export class Manifest { * @param filePath - path to the manifest file. */ public static load(filePath: string): assembly.AssemblyManifest { - const raw: assembly.AssemblyManifest = JSON.parse(fs.readFileSync(filePath, 'UTF-8')); + const raw: assembly.AssemblyManifest = JSON.parse(fs.readFileSync(filePath, { encoding: 'utf-8' })); Manifest.patchStackTags(raw); Manifest.validate(raw); return raw; diff --git a/packages/@aws-cdk/cloud-assembly-schema/package.json b/packages/@aws-cdk/cloud-assembly-schema/package.json index 9bc8daa7abfb6..b39ddd629873b 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/package.json +++ b/packages/@aws-cdk/cloud-assembly-schema/package.json @@ -47,7 +47,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^25.2.3", + "@types/jest": "^26.0.0", "@types/mock-fs": "^4.10.0", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", diff --git a/packages/@aws-cdk/cloud-assembly-schema/test/manifest.test.ts b/packages/@aws-cdk/cloud-assembly-schema/test/manifest.test.ts index a90c2e411a39c..40d801812006c 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/test/manifest.test.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/test/manifest.test.ts @@ -44,7 +44,7 @@ test('manifest save', () => { Manifest.save(assemblyManifest, manifestFile); - const saved = JSON.parse(fs.readFileSync(manifestFile, 'UTF-8')); + const saved = JSON.parse(fs.readFileSync(manifestFile, { encoding: 'utf-8' })); expect(saved).toEqual(assemblyManifest); diff --git a/packages/@aws-cdk/cloudformation-diff/.eslintrc.js b/packages/@aws-cdk/cloudformation-diff/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/cloudformation-diff/.eslintrc.js +++ b/packages/@aws-cdk/cloudformation-diff/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/cloudformation-diff/package.json b/packages/@aws-cdk/cloudformation-diff/package.json index 88342e0ba3835..4916f1702a90e 100644 --- a/packages/@aws-cdk/cloudformation-diff/package.json +++ b/packages/@aws-cdk/cloudformation-diff/package.json @@ -29,11 +29,11 @@ "table": "^5.4.6" }, "devDependencies": { - "@types/jest": "^25.2.3", + "@types/jest": "^26.0.0", "@types/string-width": "^4.0.1", "@types/table": "^4.0.7", "cdk-build-tools": "0.0.0", - "fast-check": "^1.24.2", + "fast-check": "^1.25.1", "jest": "^25.5.4", "pkglint": "0.0.0", "ts-jest": "^26.1.0" diff --git a/packages/@aws-cdk/cloudformation-include/.eslintrc.js b/packages/@aws-cdk/cloudformation-include/.eslintrc.js index 1b28bad193ceb..61dd8dd001f63 100644 --- a/packages/@aws-cdk/cloudformation-include/.eslintrc.js +++ b/packages/@aws-cdk/cloudformation-include/.eslintrc.js @@ -1,2 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/cloudformation-include/README.md b/packages/@aws-cdk/cloudformation-include/README.md index a64d7b988e9bb..08e999e87a25c 100644 --- a/packages/@aws-cdk/cloudformation-include/README.md +++ b/packages/@aws-cdk/cloudformation-include/README.md @@ -124,8 +124,8 @@ All items unchecked below are currently not supported. - [x] Properties - [x] Condition - [x] DependsOn -- [ ] CreationPolicy -- [ ] UpdatePolicy +- [x] CreationPolicy +- [x] UpdatePolicy - [x] UpdateReplacePolicy - [x] DeletionPolicy - [x] Metadata diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index 9b1c21e5a590a..07c6dee3bfcbf 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -129,7 +129,10 @@ export class CfnInclude extends core.CfnElement { throw new Error(`Unrecognized CloudFormation resource type: '${resourceAttributes.Type}'`); } // fail early for resource attributes we don't support yet - const knownAttributes = ['Type', 'Properties', 'Condition', 'DependsOn', 'DeletionPolicy', 'UpdateReplacePolicy', 'Metadata']; + const knownAttributes = [ + 'Type', 'Properties', 'Condition', 'DependsOn', 'Metadata', + 'CreationPolicy', 'UpdatePolicy', 'DeletionPolicy', 'UpdateReplacePolicy', + ]; for (const attribute of Object.keys(resourceAttributes)) { if (!knownAttributes.includes(attribute)) { throw new Error(`The ${attribute} resource attribute is not supported by cloudformation-include yet. ` + diff --git a/packages/@aws-cdk/cloudformation-include/package.json b/packages/@aws-cdk/cloudformation-include/package.json index bf3be1afd8d19..4ba1c65e4ae9e 100644 --- a/packages/@aws-cdk/cloudformation-include/package.json +++ b/packages/@aws-cdk/cloudformation-include/package.json @@ -302,7 +302,7 @@ }, "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/jest": "^25.2.3", + "@types/jest": "^26.0.0", "@types/yaml": "1.2.0", "cdk-build-tools": "0.0.0", "jest": "^25.4.0", diff --git a/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts index 038ea1e9e6dde..099597c1b0526 100644 --- a/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts @@ -52,6 +52,12 @@ describe('CDK Include', () => { includeTestTemplate(stack, 'non-existent-condition.json'); }).toThrow(/Resource 'Bucket' uses Condition 'AlwaysFalseCond' that doesn't exist/); }); + + test('throws a validation exception when encountering an unrecognized resource attribute', () => { + expect(() => { + includeTestTemplate(stack, 'non-existent-resource-attribute.json'); + }).toThrow(/The NonExistentResourceAttribute resource attribute is not supported by cloudformation-include yet/); + }); }); function includeTestTemplate(scope: core.Construct, testTemplate: string): inc.CfnInclude { diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/non-existent-resource-attribute.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/non-existent-resource-attribute.json new file mode 100644 index 0000000000000..fc8d8f5f83454 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/non-existent-resource-attribute.json @@ -0,0 +1,8 @@ +{ + "Resources": { + "Bucket2": { + "Type": "AWS::S3::Bucket", + "NonExistentResourceAttribute": "Bucket1" + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/resource-attribute-creation-policy.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/resource-attribute-creation-policy.json index c342227788535..a3c1f9e69e88f 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/resource-attribute-creation-policy.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/resource-attribute-creation-policy.json @@ -1,10 +1,22 @@ { + "Parameters": { + "CountParameter": { + "Type": "Number", + "Default": 3 + } + }, "Resources": { "Bucket": { "Type": "AWS::S3::Bucket", "CreationPolicy": { "AutoScalingCreationPolicy": { "MinSuccessfulInstancesPercent": 50 + }, + "ResourceSignal": { + "Count": { + "Ref": "CountParameter" + }, + "Timeout":"PT5H4M3S" } } } diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/resource-attribute-update-policy.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/resource-attribute-update-policy.json index 7032979006266..e1440a46193be 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/resource-attribute-update-policy.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/resource-attribute-update-policy.json @@ -1,11 +1,60 @@ { + "Parameters": { + "WaitOnResourceSignals": { + "Type": "String", + "Default": "true" + } + }, "Resources": { + "CodeDeployApp": { + "Type": "AWS::CodeDeploy::Application" + }, + "CodeDeployDg": { + "Type": "AWS::CodeDeploy::DeploymentGroup", + "Properties": { + "ApplicationName": { "Ref": "CodeDeployApp" }, + "ServiceRoleArn": "my-role-arn" + } + }, "Bucket": { "Type": "AWS::S3::Bucket", "UpdatePolicy": { + "AutoScalingReplacingUpdate": { + "WillReplace": false + }, + "AutoScalingRollingUpdate": { + "MaxBatchSize" : 1, + "MinInstancesInService" : 2, + "MinSuccessfulInstancesPercent" : 3, + "PauseTime" : "PT4M3S", + "SuspendProcesses" : [ + "Launch", + "Terminate", + "HealthCheck", + "ReplaceUnhealthy", + "AZRebalance", + "AlarmNotification", + "ScheduledActions", + "AddToLoadBalancer" + ], + "WaitOnResourceSignals" : { + "Fn::Equals": [ + "true", + { "Ref": "WaitOnResourceSignals" } + ] + } + }, "AutoScalingScheduledAction": { "IgnoreUnmodifiedGroupSizeProperties": true - } + }, + "CodeDeployLambdaAliasUpdate" : { + "AfterAllowTrafficHook" : "Lambda1", + "ApplicationName" : { "Ref": "CodeDeployApp" }, + "BeforeAllowTrafficHook" : "Lambda2", + "DeploymentGroupName" : { "Ref": "CodeDeployDg" } + }, + "EnableVersionUpgrade": true, + "UseOnlineResharding": false } } } diff --git a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts index 0291de98eba95..21737bdc6b972 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -264,6 +264,28 @@ describe('CDK Include', () => { }); }); + test('correctly handles the CreationPolicy resource attribute', () => { + const cfnTemplate = includeTestTemplate(stack, 'resource-attribute-creation-policy.json'); + const cfnBucket = cfnTemplate.getResource('Bucket'); + + expect(cfnBucket.cfnOptions.creationPolicy).toBeDefined(); + + expect(stack).toMatchTemplate( + loadTestFileToJsObject('resource-attribute-creation-policy.json'), + ); + }); + + test('correctly handles the UpdatePolicy resource attribute', () => { + const cfnTemplate = includeTestTemplate(stack, 'resource-attribute-update-policy.json'); + const cfnBucket = cfnTemplate.getResource('Bucket'); + + expect(cfnBucket.cfnOptions.updatePolicy).toBeDefined(); + + expect(stack).toMatchTemplate( + loadTestFileToJsObject('resource-attribute-update-policy.json'), + ); + }); + test("throws an exception when encountering a Resource type it doesn't recognize", () => { expect(() => { includeTestTemplate(stack, 'non-existent-resource-type.json'); @@ -275,18 +297,6 @@ describe('CDK Include', () => { includeTestTemplate(stack, 'only-codecommit-repo-using-cfn-functions.json'); }).toThrow(/Unsupported CloudFormation function 'Fn::Base64'/); }); - - test('throws an exception when encountering the CreationPolicy attribute in a resource', () => { - expect(() => { - includeTestTemplate(stack, 'resource-attribute-creation-policy.json'); - }).toThrow(/The CreationPolicy resource attribute is not supported by cloudformation-include yet/); - }); - - test('throws an exception when encountering the UpdatePolicy attribute in a resource', () => { - expect(() => { - includeTestTemplate(stack, 'resource-attribute-update-policy.json'); - }).toThrow(/The UpdatePolicy resource attribute is not supported by cloudformation-include yet/); - }); }); interface IncludeTestTemplateProps { diff --git a/packages/@aws-cdk/core/.eslintrc.js b/packages/@aws-cdk/core/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/core/.eslintrc.js +++ b/packages/@aws-cdk/core/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/core/lib/asset-staging.ts b/packages/@aws-cdk/core/lib/asset-staging.ts index 519844689177c..e25994beeb821 100644 --- a/packages/@aws-cdk/core/lib/asset-staging.ts +++ b/packages/@aws-cdk/core/lib/asset-staging.ts @@ -1,5 +1,6 @@ import * as cxapi from '@aws-cdk/cx-api'; -import * as fs from 'fs'; +import * as crypto from 'crypto'; +import * as fs from 'fs-extra'; import * as os from 'os'; import * as path from 'path'; import { AssetHashType, AssetOptions } from './assets'; @@ -7,6 +8,8 @@ import { BundlingOptions } from './bundling'; import { Construct, ISynthesisSession } from './construct-compat'; import { FileSystem, FingerprintOptions } from './fs'; +const STAGING_TMP = '.cdk.staging'; + /** * Initialization properties for `AssetStaging`. */ @@ -117,22 +120,9 @@ export class AssetStaging extends Construct { // Asset has been bundled if (this.bundleDir) { - // Try to rename bundling directory to staging directory - try { - fs.renameSync(this.bundleDir, targetPath); - return; - } catch (err) { - // /tmp and cdk.out could be mounted across different mount points - // in this case we will fallback to copying. This can happen in Windows - // Subsystem for Linux (WSL). - if (err.code === 'EXDEV') { - fs.mkdirSync(targetPath); - FileSystem.copyDirectory(this.bundleDir, targetPath, this.fingerprintOptions); - return; - } - - throw err; - } + // Move bundling directory to staging directory + fs.moveSync(this.bundleDir, targetPath); + return; } // Copy file/directory to staging directory @@ -148,8 +138,12 @@ export class AssetStaging extends Construct { } private bundle(options: BundlingOptions): string { - // Create temporary directory for bundling - const bundleDir = FileSystem.mkdtemp('cdk-asset-bundle-'); + // Temp staging directory in the working directory + const stagingTmp = path.join('.', STAGING_TMP); + fs.ensureDirSync(stagingTmp); + + // Create temp directory for bundling inside the temp staging directory + const bundleDir = path.resolve(fs.mkdtempSync(path.join(stagingTmp, 'asset-bundle-'))); let user: string; if (options.user) { @@ -219,7 +213,9 @@ export class AssetStaging extends Construct { if (!props.assetHash) { throw new Error('`assetHash` must be specified when `assetHashType` is set to `AssetHashType.CUSTOM`.'); } - return props.assetHash; + // Hash the hash to make sure we can use it in a file/directory name. + // The resulting hash will also have the same length as for the other hash types. + return crypto.createHash('sha256').update(props.assetHash).digest('hex'); default: throw new Error('Unknown asset hash type.'); } diff --git a/packages/@aws-cdk/core/lib/assets.ts b/packages/@aws-cdk/core/lib/assets.ts index bad303dbd8c31..50dbcddbcaf5f 100644 --- a/packages/@aws-cdk/core/lib/assets.ts +++ b/packages/@aws-cdk/core/lib/assets.ts @@ -18,7 +18,9 @@ export interface IAsset { export interface AssetOptions { /** * Specify a custom hash for this asset. If `assetHashType` is set it must - * be set to `AssetHashType.CUSTOM`. + * be set to `AssetHashType.CUSTOM`. For consistency, this custom hash will + * be SHA256 hashed and encoded as hex. The resulting hash will be the asset + * hash. * * NOTE: the hash is used in order to identify a specific revision of the asset, and * used for optimizing and caching deployment activities related to this asset such as @@ -206,6 +208,23 @@ export interface FileAssetLocation { * @example s3://mybucket/myobject */ readonly s3ObjectUrl: string; + + /** + * The ARN of the KMS key used to encrypt the file asset bucket, if any + * + * If so, the consuming role should be given "kms:Decrypt" permissions in its + * identity policy. + * + * It's the responsibility of they key's creator to make sure that all + * consumers that the key's key policy is configured such that the key can be used + * by all consumers that need it. + * + * The default bootstrap stack provisioned by the CDK CLI ensures this, and + * can be used as an example for how to configure the key properly. + * + * @default - Asset bucket is not encrypted + */ + readonly kmsKeyArn?: string; } /** diff --git a/packages/@aws-cdk/core/lib/cfn-parse.ts b/packages/@aws-cdk/core/lib/cfn-parse.ts index 67e2d2390ef62..68cfa6234e2d6 100644 --- a/packages/@aws-cdk/core/lib/cfn-parse.ts +++ b/packages/@aws-cdk/core/lib/cfn-parse.ts @@ -1,6 +1,9 @@ import { Fn } from './cfn-fn'; import { Aws } from './cfn-pseudo'; -import { CfnDeletionPolicy } from './cfn-resource-policy'; +import { + CfnAutoScalingReplacingUpdate, CfnAutoScalingRollingUpdate, CfnAutoScalingScheduledAction, CfnCodeDeployLambdaAliasUpdate, + CfnCreationPolicy, CfnDeletionPolicy, CfnResourceAutoScalingCreationPolicy, CfnResourceSignal, CfnUpdatePolicy, +} from './cfn-resource-policy'; import { CfnTag } from './cfn-tag'; import { IResolvable } from './resolvable'; import { isResolvableObject, Token } from './token'; @@ -107,6 +110,91 @@ export class FromCloudFormation { return ret; } + public static parseCreationPolicy(policy: any): CfnCreationPolicy | undefined { + if (typeof policy !== 'object') { return undefined; } + + // change simple JS values to their CDK equivalents + policy = FromCloudFormation.parseValue(policy); + + return undefinedIfAllValuesAreEmpty({ + autoScalingCreationPolicy: parseAutoScalingCreationPolicy(policy.AutoScalingCreationPolicy), + resourceSignal: parseResourceSignal(policy.ResourceSignal), + }); + + function parseAutoScalingCreationPolicy(p: any): CfnResourceAutoScalingCreationPolicy | undefined { + if (typeof p !== 'object') { return undefined; } + + return undefinedIfAllValuesAreEmpty({ + minSuccessfulInstancesPercent: FromCloudFormation.getNumber(p.MinSuccessfulInstancesPercent), + }); + } + + function parseResourceSignal(p: any): CfnResourceSignal | undefined { + if (typeof p !== 'object') { return undefined; } + + return undefinedIfAllValuesAreEmpty({ + count: FromCloudFormation.getNumber(p.Count), + timeout: FromCloudFormation.getString(p.Timeout), + }); + } + } + + public static parseUpdatePolicy(policy: any): CfnUpdatePolicy | undefined { + if (typeof policy !== 'object') { return undefined; } + + // change simple JS values to their CDK equivalents + policy = FromCloudFormation.parseValue(policy); + + return undefinedIfAllValuesAreEmpty({ + autoScalingReplacingUpdate: parseAutoScalingReplacingUpdate(policy.AutoScalingReplacingUpdate), + autoScalingRollingUpdate: parseAutoScalingRollingUpdate(policy.AutoScalingRollingUpdate), + autoScalingScheduledAction: parseAutoScalingScheduledAction(policy.AutoScalingScheduledAction), + codeDeployLambdaAliasUpdate: parseCodeDeployLambdaAliasUpdate(policy.CodeDeployLambdaAliasUpdate), + enableVersionUpgrade: policy.EnableVersionUpgrade, + useOnlineResharding: policy.UseOnlineResharding, + }); + + function parseAutoScalingReplacingUpdate(p: any): CfnAutoScalingReplacingUpdate | undefined { + if (typeof p !== 'object') { return undefined; } + + return undefinedIfAllValuesAreEmpty({ + willReplace: p.WillReplace, + }); + } + + function parseAutoScalingRollingUpdate(p: any): CfnAutoScalingRollingUpdate | undefined { + if (typeof p !== 'object') { return undefined; } + + return undefinedIfAllValuesAreEmpty({ + maxBatchSize: FromCloudFormation.getNumber(p.MaxBatchSize), + minInstancesInService: FromCloudFormation.getNumber(p.MinInstancesInService), + minSuccessfulInstancesPercent: FromCloudFormation.getNumber(p.MinSuccessfulInstancesPercent), + pauseTime: FromCloudFormation.getString(p.PauseTime), + suspendProcesses: FromCloudFormation.getStringArray(p.SuspendProcesses), + waitOnResourceSignals: p.WaitOnResourceSignals, + }); + } + + function parseCodeDeployLambdaAliasUpdate(p: any): CfnCodeDeployLambdaAliasUpdate | undefined { + if (typeof p !== 'object') { return undefined; } + + return { + beforeAllowTrafficHook: FromCloudFormation.getString(p.BeforeAllowTrafficHook), + afterAllowTrafficHook: FromCloudFormation.getString(p.AfterAllowTrafficHook), + applicationName: FromCloudFormation.getString(p.ApplicationName), + deploymentGroupName: FromCloudFormation.getString(p.DeploymentGroupName), + }; + } + + function parseAutoScalingScheduledAction(p: any): CfnAutoScalingScheduledAction | undefined { + if (typeof p !== 'object') { return undefined; } + + return undefinedIfAllValuesAreEmpty({ + ignoreUnmodifiedGroupSizeProperties: p.IgnoreUnmodifiedGroupSizeProperties, + }); + } + } + public static parseDeletionPolicy(policy: any): CfnDeletionPolicy | undefined { switch (policy) { case null: return undefined; @@ -220,3 +308,7 @@ function specialCaseRefs(value: any): any { default: return undefined; } } + +function undefinedIfAllValuesAreEmpty(object: object): object | undefined { + return Object.values(object).some(v => v !== undefined) ? object : undefined; +} diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts index 5cef2ac3daab4..2021e71355566 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts @@ -16,7 +16,7 @@ export const BOOTSTRAP_QUALIFIER_CONTEXT = '@aws-cdk/core:bootstrapQualifier'; /** * The minimum bootstrap stack version required by this app. */ -const MIN_BOOTSTRAP_STACK_VERSION = 2; +const MIN_BOOTSTRAP_STACK_VERSION = 3; /** * Configuration properties for DefaultStackSynthesizer @@ -114,6 +114,19 @@ export interface DefaultStackSynthesizerProps { */ readonly cloudFormationExecutionRole?: string; + /** + * Name of the CloudFormation Export with the asset key name + * + * You must supply this if you have given a non-standard name to the KMS key export + * + * The placeholders `${Qualifier}`, `${AWS::AccountId}` and `${AWS::Region}` will + * be replaced with the values of qualifier and the stack's account and region, + * respectively. + * + * @default DefaultStackSynthesizer.DEFAULT_FILE_ASSET_KEY_ARN_EXPORT_NAME + */ + readonly fileAssetKeyArnExportName?: string; + /** * Qualifier to disambiguate multiple environments in the same account * @@ -170,10 +183,16 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { */ public static readonly DEFAULT_FILE_ASSETS_BUCKET_NAME = 'cdk-${Qualifier}-assets-${AWS::AccountId}-${AWS::Region}'; + /** + * Name of the CloudFormation Export with the asset key name + */ + public static readonly DEFAULT_FILE_ASSET_KEY_ARN_EXPORT_NAME = 'CdkBootstrap-${Qualifier}-FileAssetKeyArn'; + private _stack?: Stack; private bucketName?: string; private repositoryName?: string; private _deployRoleArn?: string; + private _kmsKeyArnExportName?: string; private _cloudFormationExecutionRoleArn?: string; private fileAssetPublishingRoleArn?: string; private imageAssetPublishingRoleArn?: string; @@ -211,12 +230,14 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { this._cloudFormationExecutionRoleArn = specialize(this.props.cloudFormationExecutionRole ?? DefaultStackSynthesizer.DEFAULT_CLOUDFORMATION_ROLE_ARN); this.fileAssetPublishingRoleArn = specialize(this.props.fileAssetPublishingRoleArn ?? DefaultStackSynthesizer.DEFAULT_FILE_ASSET_PUBLISHING_ROLE_ARN); this.imageAssetPublishingRoleArn = specialize(this.props.imageAssetPublishingRoleArn ?? DefaultStackSynthesizer.DEFAULT_IMAGE_ASSET_PUBLISHING_ROLE_ARN); + this._kmsKeyArnExportName = specialize(this.props.fileAssetKeyArnExportName ?? DefaultStackSynthesizer.DEFAULT_FILE_ASSET_KEY_ARN_EXPORT_NAME); // tslint:enable:max-line-length } public addFileAsset(asset: FileAssetSource): FileAssetLocation { assertBound(this.stack); assertBound(this.bucketName); + assertBound(this._kmsKeyArnExportName); const objectKey = asset.sourceHash + (asset.packaging === FileAssetPackaging.ZIP_DIRECTORY ? '.zip' : ''); @@ -237,7 +258,8 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { }, }; - const httpUrl = cfnify(`https://s3.${this.stack.region}.${this.stack.urlSuffix}/${this.bucketName}/${objectKey}`); + const { region, urlSuffix } = stackLocationOrInstrinsics(this.stack); + const httpUrl = cfnify(`https://s3.${region}.${urlSuffix}/${this.bucketName}/${objectKey}`); const s3ObjectUrl = cfnify(`s3://${this.bucketName}/${objectKey}`); // Return CFN expression @@ -247,6 +269,7 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { httpUrl, s3ObjectUrl, s3Url: httpUrl, + kmsKeyArn: Fn.importValue(cfnify(this._kmsKeyArnExportName)), }; } @@ -275,10 +298,12 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { }, }; + const { account, region, urlSuffix } = stackLocationOrInstrinsics(this.stack); + // Return CFN expression return { repositoryName: cfnify(this.repositoryName), - imageUri: cfnify(`${this.stack.account}.dkr.ecr.${this.stack.region}.${this.stack.urlSuffix}/${this.repositoryName}:${imageTag}`), + imageUri: cfnify(`${account}.dkr.ecr.${region}.${urlSuffix}/${this.repositoryName}:${imageTag}`), }; } @@ -408,7 +433,7 @@ function replaceAll(s: string, search: string, replace: string) { } /** - * If the string still contains placeholders, wrap it in a Fn::Sub so they will be substituted at CFN deploymen time + * If the string still contains placeholders, wrap it in a Fn::Sub so they will be substituted at CFN deployment time * * (This happens to work because the placeholders we picked map directly onto CFN * placeholders. If they didn't we'd have to do a transformation here). @@ -416,3 +441,23 @@ function replaceAll(s: string, search: string, replace: string) { function cfnify(s: string): string { return s.indexOf('${') > -1 ? Fn.sub(s) : s; } + +/** + * Return the stack locations if they're concrete, or the original CFN intrisics otherwise + * + * We need to return these instead of the tokenized versions of the strings, + * since we must accept those same ${AWS::AccountId}/${AWS::Region} placeholders + * in bucket names and role names (in order to allow environment-agnostic stacks). + * + * We'll wrap a single {Fn::Sub} around the final string in order to replace everything, + * but we can't have the token system render part of the string to {Fn::Join} because + * the CFN specification doesn't allow the {Fn::Sub} template string to be an arbitrary + * expression--it must be a string literal. + */ +function stackLocationOrInstrinsics(stack: Stack) { + return { + account: resolvedOr(stack.account, '${AWS::AccountId}'), + region: resolvedOr(stack.region, '${AWS::Region}'), + urlSuffix: resolvedOr(stack.urlSuffix, '${AWS::URLSuffix}'), + }; +} diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index c1066fd4799a6..0c99c177e2027 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -152,13 +152,13 @@ "license": "Apache-2.0", "devDependencies": { "@types/lodash": "^4.14.155", - "@types/node": "^10.17.25", + "@types/node": "^10.17.26", "@types/nodeunit": "^0.0.31", "@types/minimatch": "^3.0.3", "@types/sinon": "^9.0.4", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", - "fast-check": "^1.24.2", + "fast-check": "^1.25.1", "lodash": "^4.17.15", "nodeunit": "^0.11.3", "pkglint": "0.0.0", @@ -166,6 +166,7 @@ "ts-mock-imports": "^1.3.0" }, "dependencies": { + "fs-extra": "^9.0.1", "minimatch": "^3.0.4", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/cdk-assets-schema": "0.0.0", @@ -173,6 +174,7 @@ "constructs": "^3.0.2" }, "bundledDependencies": [ + "fs-extra", "minimatch" ], "homepage": "https://github.com/aws/aws-cdk", diff --git a/packages/@aws-cdk/core/test/evaluate-cfn.ts b/packages/@aws-cdk/core/test/evaluate-cfn.ts index 1c5a07dab7b5e..7dfa66328c319 100644 --- a/packages/@aws-cdk/core/test/evaluate-cfn.ts +++ b/packages/@aws-cdk/core/test/evaluate-cfn.ts @@ -27,8 +27,19 @@ export function evaluateCFN(object: any, context: {[key: string]: string} = {}): }, 'Fn::Sub'(argument: string | [string, Record]) { - const template: string = evaluate(Array.isArray(argument) ? argument[0] : argument); - const placeholders: Record = Array.isArray(argument) ? evaluate(argument[1]) : context; + let template; + let placeholders: Record; + if (Array.isArray(argument)) { + template = argument[0]; + placeholders = evaluate(argument[1]); + } else { + template = argument; + placeholders = 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]; } diff --git a/packages/@aws-cdk/core/test/test.app.ts b/packages/@aws-cdk/core/test/test.app.ts index af7a301f5b95b..eef7f2db88d33 100644 --- a/packages/@aws-cdk/core/test/test.app.ts +++ b/packages/@aws-cdk/core/test/test.app.ts @@ -325,6 +325,32 @@ export = { test.done(); }, + + 'stacks are written to the assembly file in a topological order'(test: Test) { + // WHEN + const assembly = withApp({}, (app) => { + const stackC = new Stack(app, 'StackC'); + const stackD = new Stack(app, 'StackD'); + const stackA = new Stack(app, 'StackA'); + const stackB = new Stack(app, 'StackB'); + + // Create the following dependency order: + // A -> + // C -> D + // B -> + stackC.addDependency(stackA); + stackC.addDependency(stackB); + stackD.addDependency(stackC); + }); + + // THEN + const artifactsIds = assembly.artifacts.map(a => a.id); + test.ok(artifactsIds.indexOf('StackA') < artifactsIds.indexOf('StackC')); + test.ok(artifactsIds.indexOf('StackB') < artifactsIds.indexOf('StackC')); + test.ok(artifactsIds.indexOf('StackC') < artifactsIds.indexOf('StackD')); + + test.done(); + }, }; class MyConstruct extends Construct { diff --git a/packages/@aws-cdk/core/test/test.cfn-parse.ts b/packages/@aws-cdk/core/test/test.cfn-parse.ts new file mode 100644 index 0000000000000..47f6bdbb947cc --- /dev/null +++ b/packages/@aws-cdk/core/test/test.cfn-parse.ts @@ -0,0 +1,56 @@ +import { Test } from 'nodeunit'; +import { FromCloudFormation } from '../lib/cfn-parse'; + +export = { + 'FromCloudFormation class': { + '#parseCreationPolicy': { + 'returns undefined when given a non-object as the argument'(test: Test) { + test.equal(FromCloudFormation.parseCreationPolicy('blah'), undefined); + + test.done(); + }, + + 'returns undefined when given an empty object as the argument'(test: Test) { + test.equal(FromCloudFormation.parseCreationPolicy({}), undefined); + + test.done(); + }, + + 'returns undefined when given empty sub-objects as the argument'(test: Test) { + test.equal(FromCloudFormation.parseCreationPolicy({ + AutoScalingCreationPolicy: null, + ResourceSignal: { + Count: undefined, + }, + }), undefined); + + test.done(); + }, + }, + + '#parseUpdatePolicy': { + 'returns undefined when given a non-object as the argument'(test: Test) { + test.equal(FromCloudFormation.parseUpdatePolicy('blah'), undefined); + + test.done(); + }, + + 'returns undefined when given an empty object as the argument'(test: Test) { + test.equal(FromCloudFormation.parseUpdatePolicy({}), undefined); + + test.done(); + }, + + 'returns undefined when given empty sub-objects as the argument'(test: Test) { + test.equal(FromCloudFormation.parseUpdatePolicy({ + AutoScalingReplacingUpdate: null, + AutoScalingRollingUpdate: { + PauseTime: undefined, + }, + }), undefined); + + test.done(); + }, + }, + }, +}; diff --git a/packages/@aws-cdk/core/test/test.staging.ts b/packages/@aws-cdk/core/test/test.staging.ts index 9bd123a50da0d..81766f8a34879 100644 --- a/packages/@aws-cdk/core/test/test.staging.ts +++ b/packages/@aws-cdk/core/test/test.staging.ts @@ -1,8 +1,9 @@ import * as cxapi from '@aws-cdk/cx-api'; -import * as fs from 'fs'; +import * as fs from 'fs-extra'; import { Test } from 'nodeunit'; import * as os from 'os'; import * as path from 'path'; +import * as sinon from 'sinon'; import { App, AssetHashType, AssetStaging, BundlingDockerImage, Stack } from '../lib'; const STUB_INPUT_FILE = '/tmp/docker-stub.input'; @@ -26,6 +27,7 @@ export = { fs.unlinkSync(STUB_INPUT_FILE); } cb(); + sinon.restore(); }, 'base case'(test: Test) { @@ -103,6 +105,8 @@ export = { const app = new App(); const stack = new Stack(app, 'stack'); const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + const ensureDirSyncSpy = sinon.spy(fs, 'ensureDirSync'); + const mkdtempSyncSpy = sinon.spy(fs, 'mkdtempSync'); // WHEN new AssetStaging(stack, 'Asset', { @@ -127,6 +131,11 @@ export = { 'tree.json', ]); + // asset is bundled in a directory inside .cdk.staging + const stagingTmp = path.join('.', '.cdk.staging'); + test.ok(ensureDirSyncSpy.calledWith(stagingTmp)); + test.ok(mkdtempSyncSpy.calledWith(sinon.match(path.join(stagingTmp, 'asset-bundle-')))); + test.done(); }, @@ -192,7 +201,7 @@ export = { // THEN test.equal(fs.existsSync(STUB_INPUT_FILE), false); - test.equal(asset.assetHash, 'my-custom-hash'); + test.equal(asset.assetHash, 'b9c77053f5b83bbe5ba343bc18e92db939a49017010813225fea91fa892c4823'); // hash of 'my-custom-hash' test.done(); }, diff --git a/packages/@aws-cdk/custom-resources/.eslintrc.js b/packages/@aws-cdk/custom-resources/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/custom-resources/.eslintrc.js +++ b/packages/@aws-cdk/custom-resources/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/custom-resources/README.md b/packages/@aws-cdk/custom-resources/README.md index 67d4e16a14263..a4aba7be33eff 100644 --- a/packages/@aws-cdk/custom-resources/README.md +++ b/packages/@aws-cdk/custom-resources/README.md @@ -260,8 +260,8 @@ This module includes a few examples for custom resource implementations: Provisions an object in an S3 bucket with textual contents. See the source code for the -[construct](test/provider-framework/integration-test-fixtures/s3-file.ts) and -[handler](test/provider-framework/integration-test-fixtures/s3-file-handler/index.ts). +[construct](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file.ts) and +[handler](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file-handler/index.ts). The following example will create the file `folder/file1.txt` inside `myBucket` with the contents `hello!`. diff --git a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts index d0a7994740a19..8c56de166305e 100644 --- a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts +++ b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts @@ -63,7 +63,7 @@ export interface AwsSdkCall { /** * The parameters for the service action * - * @default - no paramters + * @default - no parameters * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/index.html */ readonly parameters?: any; diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index 561ac6ef58a6f..b9e70560fa7d1 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -73,7 +73,7 @@ "@types/aws-lambda": "^8.10.39", "@types/fs-extra": "^8.1.0", "@types/sinon": "^9.0.4", - "aws-sdk": "^2.691.0", + "aws-sdk": "^2.699.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/cx-api/.eslintrc.js b/packages/@aws-cdk/cx-api/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/cx-api/.eslintrc.js +++ b/packages/@aws-cdk/cx-api/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/cx-api/package.json b/packages/@aws-cdk/cx-api/package.json index b83a13a50b26a..766e671454da6 100644 --- a/packages/@aws-cdk/cx-api/package.json +++ b/packages/@aws-cdk/cx-api/package.json @@ -53,7 +53,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^25.2.3", + "@types/jest": "^26.0.0", "@types/mock-fs": "^4.10.0", "@types/semver": "^7.2.0", "cdk-build-tools": "0.0.0", diff --git a/packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts b/packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts index 1512c86ff5044..c3ae0aa6609ea 100644 --- a/packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts +++ b/packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts @@ -166,4 +166,62 @@ test('write and read nested cloud assembly artifact', () => { const nested = art?.nestedAssembly; expect(nested?.artifacts.length).toEqual(0); +}); + +test('artifcats are written in topological order', () => { + // GIVEN + const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'cloud-assembly-builder-tests')); + const session = new cxapi.CloudAssemblyBuilder(outdir); + const templateFile = 'foo.template.json'; + + const innerAsmDir = path.join(outdir, 'hello'); + new cxapi.CloudAssemblyBuilder(innerAsmDir).buildAssembly(); + + // WHEN + + // Create the following dependency order: + // A -> + // C -> D + // B -> + session.addArtifact('artifact-D', { + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, + environment: 'aws://1222344/us-east-1', + dependencies: ['artifact-C'], + properties: { + templateFile, + }, + }); + + session.addArtifact('artifact-C', { + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, + environment: 'aws://1222344/us-east-1', + dependencies: ['artifact-B', 'artifact-A'], + properties: { + templateFile, + }, + }); + + session.addArtifact('artifact-B', { + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, + environment: 'aws://1222344/us-east-1', + properties: { + templateFile, + }, + }); + + session.addArtifact('artifact-A', { + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, + environment: 'aws://1222344/us-east-1', + properties: { + templateFile, + }, + }); + + const asm = session.buildAssembly(); + const artifactsIds = asm.artifacts.map(a => a.id); + + // THEN + expect(artifactsIds.indexOf('artifact-A')).toBeLessThan(artifactsIds.indexOf('artifact-C')); + expect(artifactsIds.indexOf('artifact-B')).toBeLessThan(artifactsIds.indexOf('artifact-C')); + expect(artifactsIds.indexOf('artifact-C')).toBeLessThan(artifactsIds.indexOf('artifact-D')); }); \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts b/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts index 42d7dffd94233..f3ee5f817caa6 100644 --- a/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts +++ b/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts @@ -85,7 +85,7 @@ test('assets', () => { test('can-read-0.36.0', () => { // WHEN new CloudAssembly(path.join(FIXTURES, 'single-stack-0.36')); - // THEN: no eexception + // THEN: no exception expect(true).toBeTruthy(); }); diff --git a/packages/@aws-cdk/example-construct-library/.eslintrc.js b/packages/@aws-cdk/example-construct-library/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/example-construct-library/.eslintrc.js +++ b/packages/@aws-cdk/example-construct-library/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@aws-cdk/region-info/.eslintrc.js b/packages/@aws-cdk/region-info/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@aws-cdk/region-info/.eslintrc.js +++ b/packages/@aws-cdk/region-info/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@monocdk-experiment/assert/.eslintrc.js b/packages/@monocdk-experiment/assert/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@monocdk-experiment/assert/.eslintrc.js +++ b/packages/@monocdk-experiment/assert/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@monocdk-experiment/assert/package.json b/packages/@monocdk-experiment/assert/package.json index 762a4b67eb193..cc6753fd598bf 100644 --- a/packages/@monocdk-experiment/assert/package.json +++ b/packages/@monocdk-experiment/assert/package.json @@ -36,8 +36,8 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^25.2.3", - "@types/node": "^10.17.25", + "@types/jest": "^26.0.0", + "@types/node": "^10.17.26", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", "pkglint": "0.0.0", diff --git a/packages/@monocdk-experiment/rewrite-imports/.eslintrc.js b/packages/@monocdk-experiment/rewrite-imports/.eslintrc.js index a9d39af55b7e5..61dd8dd001f63 100644 --- a/packages/@monocdk-experiment/rewrite-imports/.eslintrc.js +++ b/packages/@monocdk-experiment/rewrite-imports/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/@monocdk-experiment/rewrite-imports/package.json b/packages/@monocdk-experiment/rewrite-imports/package.json index d9a1f74eb18e8..c686c9bcfded8 100644 --- a/packages/@monocdk-experiment/rewrite-imports/package.json +++ b/packages/@monocdk-experiment/rewrite-imports/package.json @@ -35,9 +35,9 @@ "typescript": "~3.8.3" }, "devDependencies": { - "@types/glob": "^7.1.1", - "@types/jest": "^25.2.3", - "@types/node": "^10.17.25", + "@types/glob": "^7.1.2", + "@types/jest": "^26.0.0", + "@types/node": "^10.17.26", "cdk-build-tools": "0.0.0", "pkglint": "0.0.0" }, diff --git a/packages/aws-cdk/.eslintrc.js b/packages/aws-cdk/.eslintrc.js index f3c7e1564f9e9..463cac21e7e1d 100644 --- a/packages/aws-cdk/.eslintrc.js +++ b/packages/aws-cdk/.eslintrc.js @@ -1,4 +1,4 @@ -const baseConfig = require('../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.ignorePatterns.push('lib/init-templates/*/typescript/**/*.ts'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml index 5b61c2e99e7dd..c73539d7a29a2 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml @@ -347,6 +347,16 @@ Outputs: Description: The domain name of the S3 bucket owned by the CDK toolkit stack Value: Fn::Sub: "${StagingBucket.RegionalDomainName}" + FileAssetKeyArn: + Description: The ARN of the KMS key used to encrypt the asset bucket + Value: + Fn::If: + - CreateNewKey + - Fn::Sub: "${FileAssetsBucketEncryptionKey.Arn}" + - Fn::Sub: "${FileAssetsBucketKmsKeyId}" + Export: + Name: + Fn::Sub: CdkBootstrap-${Qualifier}-FileAssetKeyArn ImageRepositoryName: Description: The name of the ECR repository which hosts docker image assets Value: @@ -354,7 +364,7 @@ Outputs: BootstrapVersion: Description: The version of the bootstrap resources that are currently mastered in this stack - Value: '2' + Value: '3' Export: Name: Fn::Sub: CdkBootstrap-${Qualifier}-Version \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts index 8f54b1b665a68..3ced4622c74b7 100644 --- a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts +++ b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts @@ -49,6 +49,7 @@ export async function deployBootstrapStack( resolvedEnvironment, sdk: await sdkProvider.forEnvironment(resolvedEnvironment, Mode.ForWriting), sdkProvider, + force: options.force, roleArn: options.roleArn, tags: options.parameters?.tags, execute: options?.parameters?.execute, diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index 583e6dc3d6ee8..e7c7999b1a542 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -257,7 +257,9 @@ export async function deployStack(options: DeployStackOptions): Promise { const cfn = sdk.cloudFormation(); - const stack = await waitForStack(cfn, stackName ?? DEFAULT_TOOLKIT_STACK_NAME); + const stack = await stabilizeStack(cfn, stackName ?? DEFAULT_TOOLKIT_STACK_NAME); if (!stack) { debug('The environment %s doesn\'t have the CDK toolkit stack (%s) installed. Use %s to setup your environment for use with the toolkit.', environment.name, stackName, colors.blue(`cdk bootstrap "${environment.name}"`)); return undefined; } + if (stack.stackStatus.isCreationFailure) { + // Treat a "failed to create" bootstrap stack as an absent one. + debug('The environment %s has a CDK toolkit stack (%s) that failed to create. Use %s to try provisioning it again.', + environment.name, stackName, colors.blue(`cdk bootstrap "${environment.name}"`)); + return undefined; + } const outputs = stack.outputs; diff --git a/packages/aws-cdk/lib/api/util/cloudformation.ts b/packages/aws-cdk/lib/api/util/cloudformation.ts index 8b2b8db65a3b4..c0ec78e05decd 100644 --- a/packages/aws-cdk/lib/api/util/cloudformation.ts +++ b/packages/aws-cdk/lib/api/util/cloudformation.ts @@ -228,6 +228,8 @@ export function changeSetHasNoChanges(description: CloudFormation.DescribeChange /** * Waits for a CloudFormation stack to stabilize in a complete/available state. * + * Fails if the stacks is not in a SUCCESSFUL state. + * * @param cfn a CloudFormation client * @param stackName the name of the stack to wait for * @param failOnDeletedStack whether to fail if the awaited stack is deleted. @@ -238,6 +240,26 @@ export async function waitForStack( cfn: CloudFormation, stackName: string, failOnDeletedStack: boolean = true): Promise { + + const stack = await stabilizeStack(cfn, stackName); + if (!stack) { return undefined; } + + const status = stack.stackStatus; + if (status.isCreationFailure) { + throw new Error(`The stack named ${stackName} failed creation, it may need to be manually deleted from the AWS console: ${status}`); + } else if (!status.isSuccess) { + throw new Error(`The stack named ${stackName} is in a failed state: ${status}`); + } else if (status.isDeleted) { + if (failOnDeletedStack) { throw new Error(`The stack named ${stackName} was deleted`); } + return undefined; + } + return stack; +} + +/** + * Wait for a stack to become stable (no longer _IN_PROGRESS), returning it + */ +export async function stabilizeStack(cfn: CloudFormation, stackName: string) { debug('Waiting for stack %s to finish creating or updating...', stackName); return waitFor(async () => { const stack = await CloudFormationStack.lookup(cfn, stackName); @@ -250,14 +272,7 @@ export async function waitForStack( debug('Stack %s is still not stable (%s)', stackName, status); return undefined; } - if (status.isCreationFailure) { - throw new Error(`The stack named ${stackName} failed creation, it may need to be manually deleted from the AWS console: ${status}`); - } else if (!status.isSuccess) { - throw new Error(`The stack named ${stackName} is in a failed state: ${status}`); - } else if (status.isDeleted) { - if (failOnDeletedStack) { throw new Error(`The stack named ${stackName} was deleted`); } - return null; - } + return stack; }); } diff --git a/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts b/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts index 056f285695a45..ce1ed37b59876 100644 --- a/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts +++ b/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts @@ -3,31 +3,48 @@ import * as cxapi from '@aws-cdk/cx-api'; import * as aws from 'aws-sdk'; import * as colors from 'colors/safe'; import * as util from 'util'; -import { error } from '../../../logging'; +import { error, isVerbose, setVerbose } from '../../../logging'; +import { RewritableBlock } from '../display'; interface StackActivity { readonly event: aws.CloudFormation.StackEvent; + readonly metadata?: ResourceMetadata; flushed: boolean; } -export class StackActivityMonitor { - private active = false; - private activity: { [eventId: string]: StackActivity } = { }; +interface ResourceMetadata { + entry: cxschema.MetadataEntry; + constructPath: string; +} +export interface StackActivityMonitorProps { /** - * Number of ms to wait between pings + * Total number of resources to update + * + * Used to calculate a progress bar. + * + * @default - No progress reporting. */ - private readonly tickSleep = 5000; + readonly resourcesTotal?: number; /** - * Number of ms to wait between pagination calls + * Whether 'verbose' was requested in the CLI + * + * If verbose is requested, we'll always use the full history printer. + * + * @default - Use value from logging.isVerbose */ - private readonly pageSleep = 500; + readonly verbose?: boolean; +} + +export class StackActivityMonitor { + private active = false; + private activity: { [eventId: string]: StackActivity } = { }; /** - * Number of ms of change absence before we tell the user about the resources that are currently in progress. + * Number of ms to wait between pagination calls */ - private readonly inProgressDelay = 30_000; + private readonly pageSleep = 500; /** * Determines which events not to display @@ -39,64 +56,46 @@ export class StackActivityMonitor { */ private tickTimer?: NodeJS.Timer; - /** - * A list of resource IDs which are currently being processed - */ - private resourcesInProgress = new Set(); - - /** - * Count of resources that have reported a _COMPLETE status - */ - private resourcesDone: number = 0; - - /** - * How many digits we need to represent the total count (for lining up the status reporting) - */ - private resourceDigits: number = 0; - - /** - * Last time we printed something to the console. - * - * Used to measure timeout for progress reporting. - */ - private lastPrintTime = Date.now(); - /** * Set to the activity of reading the current events */ private readPromise?: Promise; - /** - * The with of the "resource type" column. - */ - private readonly resourceTypeColumnWidth: number; + private readonly printer: ActivityPrinterBase; constructor( private readonly cfn: aws.CloudFormation, private readonly stackName: string, private readonly stack: cxapi.CloudFormationStackArtifact, - private readonly resourcesTotal?: number) { + options: StackActivityMonitorProps = {}) { - if (this.resourcesTotal != null) { - // +1 because the stack also emits a "COMPLETE" event at the end, and that wasn't - // counted yet. This makes it line up with the amount of events we expect. - this.resourcesTotal++; + const stream = process.stderr; - // How many digits does this number take to represent? - this.resourceDigits = Math.ceil(Math.log10(this.resourcesTotal)); - } + const props: PrinterProps = { + resourceTypeColumnWidth: calcMaxResourceTypeLength(this.stack.template), + resourcesTotal: options.resourcesTotal, + stream, + }; + + const isWindows = process.platform === 'win32'; + const verbose = options.verbose ?? isVerbose; + const fancyOutputAvailable = !isWindows && stream.isTTY; - this.resourceTypeColumnWidth = calcMaxResourceTypeLength(this.stack.template); + this.printer = fancyOutputAvailable && !verbose + ? new CurrentActivityPrinter(props) + : new HistoryActivityPrinter(props); } public start() { this.active = true; + this.printer.start(); this.scheduleNextTick(); return this; } public async stop() { this.active = false; + this.printer.stop(); if (this.tickTimer) { clearTimeout(this.tickTimer); } @@ -112,7 +111,7 @@ export class StackActivityMonitor { if (!this.active) { return; } - this.tickTimer = setTimeout(() => this.tick().then(), this.tickSleep); + this.tickTimer = setTimeout(() => this.tick().then(), this.printer.updateSleep); } private async tick() { @@ -144,58 +143,279 @@ export class StackActivityMonitor { .filter(a => a.event.Timestamp.valueOf() > this.startTime) .filter(a => !a.flushed) .sort((lhs, rhs) => lhs.event.Timestamp.valueOf() - rhs.event.Timestamp.valueOf()) - .forEach(a => this.flushActivity(a)); + .forEach(a => { + a.flushed = true; + this.printer.addActivity(a); + }); - this.printInProgress(); + this.printer.print(); + } + + private findMetadataFor(logicalId: string | undefined): ResourceMetadata | undefined { + const metadata = this.stack.manifest.metadata; + if (!logicalId || !metadata) { return undefined; } + for (const path of Object.keys(metadata)) { + const entry = metadata[path] + .filter(e => e.type === cxschema.ArtifactMetadataEntryType.LOGICAL_ID) + .find(e => e.data === logicalId); + if (entry) { + return { + entry, + constructPath: this.simplifyConstructPath(path), + }; + } + } + return undefined; + } + + private async readEvents(nextToken?: string): Promise { + const output = await this.cfn.describeStackEvents({ StackName: this.stackName, NextToken: nextToken }).promise() + .catch( e => { + if (e.code === 'ValidationError' && e.message === `Stack [${this.stackName}] does not exist`) { + return undefined; + } + throw e; + }); + + let events = output && output.StackEvents || [ ]; + let allNew = true; + + // merge events into the activity and dedup by event id + for (const e of events) { + if (e.EventId in this.activity) { + allNew = false; + break; + } + + this.activity[e.EventId] = { + flushed: false, + event: e, + metadata: this.findMetadataFor(e.LogicalResourceId), + }; + } + + // only read next page if all the events we read are new events. otherwise, we can rest. + if (allNew && output && output.NextToken) { + await new Promise(cb => setTimeout(cb, this.pageSleep)); + events = events.concat(await this.readEvents(output.NextToken)); + } + + return events; } - private flushActivity(activity: StackActivity) { - this.rememberActivity(activity); - this.printActivity(activity); - activity.flushed = true; + private simplifyConstructPath(path: string) { + path = path.replace(/\/Resource$/, ''); + path = path.replace(/^\//, ''); // remove "/" prefix + + // remove "/" prefix + if (path.startsWith(this.stackName + '/')) { + path = path.substr(this.stackName.length + 1); + } + return path; } +} + +function padRight(n: number, x: string): string { + return x + ' '.repeat(Math.max(0, n - x.length)); +} + +/** + * Infamous padLeft() + */ +function padLeft(n: number, x: string): string { + return ' '.repeat(Math.max(0, n - x.length)) + x; +} - private rememberActivity(activity: StackActivity) { +function calcMaxResourceTypeLength(template: any) { + const resources = (template && template.Resources) || {}; + let maxWidth = 0; + for (const id of Object.keys(resources)) { + const type = resources[id].Type || ''; + if (type.length > maxWidth) { + maxWidth = type.length; + } + } + return maxWidth; +} + +interface PrinterProps { + /** + * Total resources to deploy + */ + readonly resourcesTotal?: number + + /** + * The with of the "resource type" column. + */ + readonly resourceTypeColumnWidth: number; + + /** + * Stream to write to + */ + readonly stream: NodeJS.WriteStream; +} + +abstract class ActivityPrinterBase { + /** + * Fetch new activity every 5 seconds + */ + public readonly updateSleep: number = 5_000; + + /** + * A list of resource IDs which are currently being processed + */ + protected resourcesInProgress: Record = {}; + + /** + * Previous completion state observed by logical ID + * + * We use this to detect that if we see a DELETE_COMPLETE after a + * CREATE_COMPLETE, it's actually a rollback and we should DECREASE + * resourcesDone instead of increase it + */ + protected resourcesPrevCompleteState: Record = {}; + + /** + * Count of resources that have reported a _COMPLETE status + */ + protected resourcesDone: number = 0; + + /** + * How many digits we need to represent the total count (for lining up the status reporting) + */ + protected readonly resourceDigits: number = 0; + + protected readonly resourcesTotal?: number; + + protected rollingBack = false; + + protected readonly failures = new Array(); + + protected readonly stream: NodeJS.WriteStream; + + constructor(protected readonly props: PrinterProps) { + // +1 because the stack also emits a "COMPLETE" event at the end, and that wasn't + // counted yet. This makes it line up with the amount of events we expect. + this.resourcesTotal = props.resourcesTotal ? props.resourcesTotal + 1 : undefined; + + // How many digits does this number take to represent? + this.resourceDigits = this.resourcesTotal ? Math.ceil(Math.log10(this.resourcesTotal)) : 0; + + this.stream = props.stream; + } + + public addActivity(activity: StackActivity) { const status = activity.event.ResourceStatus; if (!status || !activity.event.LogicalResourceId) { return; } + if (status === 'ROLLBACK_IN_PROGRESS') { + // Only triggered on the stack once we've strated doing a rollback + this.rollingBack = true; + } + if (status.endsWith('_IN_PROGRESS')) { - this.resourcesInProgress.add(activity.event.LogicalResourceId); + this.resourcesInProgress[activity.event.LogicalResourceId] = activity; + } + + if (status.endsWith('_FAILED')) { + const isCancelled = (activity.event.ResourceStatusReason ?? '').indexOf('cancelled') > -1; + + // Cancelled is not an interesting failure reason + if (!isCancelled) { + this.failures.push(activity); + } } if (status.endsWith('_COMPLETE') || status.endsWith('_FAILED')) { - this.resourcesInProgress.delete(activity.event.LogicalResourceId); - this.resourcesDone++; + delete this.resourcesInProgress[activity.event.LogicalResourceId]; + } + + if (status.endsWith('_COMPLETE')) { + const prevState = this.resourcesPrevCompleteState[activity.event.LogicalResourceId]; + if (!prevState) { + this.resourcesDone++; + } else { + // If we completed this before and we're completing it AGAIN, means we're rolling back. + // Protect against silly underflow. + this.resourcesDone--; + if (this.resourcesDone < 0) { + this.resourcesDone = 0; + } + } + this.resourcesPrevCompleteState[activity.event.LogicalResourceId] = status; } } - private printActivity(activity: StackActivity) { + public abstract print(): void; + + public start() { + // Empty on purpose + } + + public stop() { + // Empty on purpose + } +} + +/** + * Activity Printer which shows a full log of all CloudFormation events + * + * When there hasn't been activity for a while, it will print the resources + * that are currently in progress, to show what's holding up the deployment. + */ +export class HistoryActivityPrinter extends ActivityPrinterBase { + /** + * Last time we printed something to the console. + * + * Used to measure timeout for progress reporting. + */ + private lastPrintTime = Date.now(); + + /** + * Number of ms of change absence before we tell the user about the resources that are currently in progress. + */ + private readonly inProgressDelay = 30_000; + + private readonly printable = new Array(); + + constructor(props: PrinterProps) { + super(props); + } + + public addActivity(activity: StackActivity) { + super.addActivity(activity); + this.printable.push(activity); + } + + public print() { + for (const activity of this.printable) { + this.printOne(activity); + } + this.printable.splice(0, this.printable.length); + this.printInProgress(); + } + + private printOne(activity: StackActivity) { const e = activity.event; - const color = this.colorFromStatus(e.ResourceStatus); - const md = this.findMetadataFor(e.LogicalResourceId); + const color = colorFromStatusResult(e.ResourceStatus); let reasonColor = colors.cyan; let stackTrace = ''; + const md = activity.metadata; if (md && e.ResourceStatus && e.ResourceStatus.indexOf('FAILED') !== -1) { stackTrace = md.entry.trace ? `\n\t${md.entry.trace.join('\n\t\\_ ')}` : ''; reasonColor = colors.red; } - let resourceName = md ? md.path.replace(/\/Resource$/, '') : (e.LogicalResourceId || ''); - resourceName = resourceName.replace(/^\//, ''); // remove "/" prefix - - // remove "/" prefix - if (resourceName.startsWith(this.stackName + '/')) { - resourceName = resourceName.substr(this.stackName.length + 1); - } + const resourceName = md ? md.constructPath : (e.LogicalResourceId || ''); const logicalId = resourceName !== e.LogicalResourceId ? `(${e.LogicalResourceId}) ` : ''; - process.stderr.write(util.format(' %s | %s | %s | %s | %s %s%s%s\n', + this.stream.write(util.format(' %s | %s | %s | %s | %s %s%s%s\n', this.progress(), new Date(e.Timestamp).toLocaleTimeString(), - color(padRight(20, (e.ResourceStatus || '').substr(0, 20))), // pad left and trim - padRight(this.resourceTypeColumnWidth, e.ResourceType || ''), + color(padRight(STATUS_WIDTH, (e.ResourceStatus || '').substr(0, STATUS_WIDTH))), // pad left and trim + padRight(this.props.resourceTypeColumnWidth, e.ResourceType || ''), color(colors.bold(resourceName)), logicalId, reasonColor(colors.bold(e.ResourceStatusReason ? e.ResourceStatusReason : '')), @@ -226,10 +446,10 @@ export class StackActivityMonitor { return; } - if (this.resourcesInProgress.size > 0) { - process.stderr.write(util.format('%s Currently in progress: %s\n', + if (Object.keys(this.resourcesInProgress).length > 0) { + this.stream.write(util.format('%s Currently in progress: %s\n', this.progress(), - colors.bold(Array.from(this.resourcesInProgress).join(', ')))); + colors.bold(Object.keys(this.resourcesInProgress).join(', ')))); } // We cheat a bit here. To prevent printInProgress() from repeatedly triggering, @@ -238,89 +458,167 @@ export class StackActivityMonitor { this.lastPrintTime = +Infinity; } - private findMetadataFor(logicalId: string | undefined): { entry: cxschema.MetadataEntry, path: string } | undefined { - const metadata = this.stack.manifest.metadata; - if (!logicalId || !metadata) { return undefined; } - for (const path of Object.keys(metadata)) { - const entry = metadata[path] - .filter(e => e.type === cxschema.ArtifactMetadataEntryType.LOGICAL_ID) - .find(e => e.data === logicalId); - if (entry) { - return { entry, path }; - } - } - return undefined; +} + +/** + * Activity Printer which shows the resources currently being updated + * + * It will continuously reupdate the terminal and show only the resources + * that are currently being updated, in addition to a progress bar which + * shows how far along the deployment is. + * + * Resources that have failed will always be shown, and will be recapitulated + * along with their stack trace when the monitoring ends. + * + * Resources that failed deployment because they have been cancelled are + * not included. + */ +export class CurrentActivityPrinter extends ActivityPrinterBase { + /** + * This looks very disorienting sleeping for 5 seconds. Update quicker. + */ + public readonly updateSleep: number = 2_000; + + private oldVerbose: boolean = false; + private block = new RewritableBlock(this.stream); + + constructor(props: PrinterProps) { + super(props); } - private colorFromStatus(status?: string) { - if (!status) { - return colors.reset; - } + public print(): void { + const lines = []; - if (status.indexOf('FAILED') !== -1) { - return colors.red; - } - if (status.indexOf('ROLLBACK') !== -1) { - return colors.yellow; - } - if (status.indexOf('COMPLETE') !== -1) { - return colors.green; + // Add a progress bar at the top + const progressWidth = Math.min((this.block.width ?? 80) - PROGRESSBAR_EXTRA_SPACE - 1, MAX_PROGRESSBAR_WIDTH); + const prog = this.progressBar(progressWidth); + if (prog) { + lines.push(' ' + prog, ''); } - return colors.reset; + // Normally we'd only print "resources in progress", but it's also useful + // to keep an eye on the failures and know about the specific errors asquickly + // as possible (while the stack is still rolling back), so add those in. + const toPrint: StackActivity[] = [...this.failures, ...Object.values(this.resourcesInProgress)]; + toPrint.sort((a, b) => a.event.Timestamp.getTime() - b.event.Timestamp.getTime()); + + lines.push(...toPrint.map(res => { + const color = colorFromStatusActivity(res.event.ResourceStatus); + const resourceName = res.metadata?.constructPath ?? res.event.LogicalResourceId ?? ''; + + return util.format('%s | %s | %s | %s%s', + padLeft(TIMESTAMP_WIDTH, new Date(res.event.Timestamp).toLocaleTimeString()), + color(padRight(STATUS_WIDTH, (res.event.ResourceStatus || '').substr(0, STATUS_WIDTH))), + padRight(this.props.resourceTypeColumnWidth, res.event.ResourceType || ''), + color(colors.bold(shorten(40, resourceName))), + this.failureReasonOnNextLine(res)); + })); + + this.block.displayLines(lines); } - private async readEvents(nextToken?: string): Promise { - const output = await this.cfn.describeStackEvents({ StackName: this.stackName, NextToken: nextToken }).promise() - .catch( e => { - if (e.code === 'ValidationError' && e.message === `Stack [${this.stackName}] does not exist`) { - return undefined; - } - throw e; - }); - - let events = output && output.StackEvents || [ ]; - let allNew = true; + public start() { + // Need to prevent the waiter from printing 'stack not stable' every 5 seconds, it messes + // with the output calculations. + this.oldVerbose = isVerbose; + setVerbose(false); + } - // merge events into the activity and dedup by event id - for (const e of events) { - if (e.EventId in this.activity) { - allNew = false; - break; + public stop() { + setVerbose(this.oldVerbose); + + // Print failures at the end + const lines = new Array(); + for (const failure of this.failures) { + lines.push(util.format(colors.red('%s | %s | %s | %s%s') + '\n', + padLeft(TIMESTAMP_WIDTH, new Date(failure.event.Timestamp).toLocaleTimeString()), + padRight(STATUS_WIDTH, (failure.event.ResourceStatus || '').substr(0, STATUS_WIDTH)), + padRight(this.props.resourceTypeColumnWidth, failure.event.ResourceType || ''), + shorten(40, failure.event.LogicalResourceId ?? ''), + this.failureReasonOnNextLine(failure))); + + const trace = failure.metadata?.entry?.trace; + if (trace) { + lines.push(colors.red(`\t${trace.join('\n\t\\_ ')}\n`)); } - - this.activity[e.EventId] = { flushed: false, event: e }; } - // only read next page if all the events we read are new events. otherwise, we can rest. - if (allNew && output && output.NextToken) { - await new Promise(cb => setTimeout(cb, this.pageSleep)); - events = events.concat(await this.readEvents(output.NextToken)); - } + // Display in the same block space, otherwise we're going to have silly empty lines. + this.block.displayLines(lines); + } - return events; + private progressBar(width: number) { + if (!this.resourcesTotal) { return ''; } + const fraction = Math.min(this.resourcesDone / this.resourcesTotal, 1); + const chars = (width - 2) * fraction; + const remainder = chars - Math.floor(chars); + + const fullChars = FULL_BLOCK.repeat(Math.floor(chars)); + const partialChar = PARTIAL_BLOCK[Math.floor(remainder * PARTIAL_BLOCK.length)]; + const filler = 'ยท'.repeat(width - 2 - Math.floor(chars) - (partialChar ? 1 : 0)); + + const color = this.rollingBack ? colors.yellow : colors.green; + + return '[' + color(fullChars + partialChar) + filler + `] (${this.resourcesDone}/${this.resourcesTotal})`; } -} -function padRight(n: number, x: string): string { - return x + ' '.repeat(Math.max(0, n - x.length)); + private failureReasonOnNextLine(activity: StackActivity) { + return (activity.event.ResourceStatus ?? '').endsWith('_FAILED') + ? `\n${' '.repeat(TIMESTAMP_WIDTH + STATUS_WIDTH + 6)}${colors.red(activity.event.ResourceStatusReason ?? '')}` + : ''; + } } -/** - * Infamous padLeft() - */ -function padLeft(n: number, x: string): string { - return ' '.repeat(Math.max(0, n - x.length)) + x; +const FULL_BLOCK = 'โ–ˆ'; +const PARTIAL_BLOCK = ['', 'โ–', 'โ–Ž', 'โ–', 'โ–Œ', 'โ–‹', 'โ–Š', 'โ–‰']; +const MAX_PROGRESSBAR_WIDTH = 60; +const PROGRESSBAR_EXTRA_SPACE = 2 /* leading spaces */ + 2 /* brackets */ + 4 /* progress number decoration */ + 6 /* 2 progress numbers up to 999 */; + +function colorFromStatusResult(status?: string) { + if (!status) { + return colors.reset; + } + + if (status.indexOf('FAILED') !== -1) { + return colors.red; + } + if (status.indexOf('ROLLBACK') !== -1) { + return colors.yellow; + } + if (status.indexOf('COMPLETE') !== -1) { + return colors.green; + } + + return colors.reset; } -function calcMaxResourceTypeLength(template: any) { - const resources = (template && template.Resources) || {}; - let maxWidth = 0; - for (const id of Object.keys(resources)) { - const type = resources[id].Type || ''; - if (type.length > maxWidth) { - maxWidth = type.length; - } +function colorFromStatusActivity(status?: string) { + if (!status) { + return colors.reset; } - return maxWidth; + + if (status.endsWith('_FAILED')) { + return colors.red; + } + + if (status.startsWith('CREATE_') || status.startsWith('UPDATE_')) { + return colors.green; + } + if (status.startsWith('ROLLBACK_')) { + return colors.yellow; + } + if (status.startsWith('DELETE_')) { + return colors.yellow; + } + + return colors.reset; +} + +function shorten(maxWidth: number, p: string) { + if (p.length <= maxWidth) { return p; } + const half = Math.floor((maxWidth - 3) / 2); + return p.substr(0, half) + '...' + p.substr(p.length - half); } + +const TIMESTAMP_WIDTH = 12; +const STATUS_WIDTH = 20; \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/util/display.ts b/packages/aws-cdk/lib/api/util/display.ts new file mode 100644 index 0000000000000..bf8f80b983296 --- /dev/null +++ b/packages/aws-cdk/lib/api/util/display.ts @@ -0,0 +1,74 @@ +import * as wrapAnsi from 'wrap-ansi'; + +/** + * A class representing rewritable display lines + */ +export class RewritableBlock { + private lastHeight = 0; + + constructor(private readonly stream: NodeJS.WriteStream) { + } + + public get width() { + // Might get changed if the user resizes the terminal + return this.stream.columns; + } + + public displayLines(lines: string[]) { + lines = terminalWrap(this.width, expandNewlines(lines)); + + this.stream.write(cursorUp(this.lastHeight)); + for (const line of lines) { + this.stream.write(cll() + line + '\n'); + } + // Clear remainder of unwritten lines + for (let i = 0; i < this.lastHeight - lines.length; i++) { + this.stream.write(cll() + '\n'); + } + + // The block can only ever get bigger + this.lastHeight = Math.max(this.lastHeight, lines.length); + } +} + +const ESC = '\u001b'; + +/* + * Move cursor up `n` lines. Default is 1 + */ +function cursorUp(n: number) { + n = typeof n === 'number' ? n : 1; + return n > 0 ? ESC + '[' + n + 'A' : ''; +} + +/** + * Clear to end of line + */ +function cll() { + return ESC + '[K'; +} + +function terminalWrap(width: number | undefined, lines: string[]) { + if (width === undefined) { return lines; } + + const ret = new Array(); + for (const line of lines) { + ret.push(...wrapAnsi(line, width - 1, { + hard: true, + trim: true, + wordWrap: false, + }).split('\n')); + } + return ret; +} + +/** + * Make sure there are no hidden newlines in the gin strings + */ +function expandNewlines(lines: string[]): string[] { + const ret = new Array(); + for (const line of lines) { + ret.push(...line.split('\n')); + } + return ret; +} \ No newline at end of file diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 7a056f1b1ba02..026594e1ad5b2 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -43,16 +43,17 @@ "@aws-cdk/core": "0.0.0", "@types/archiver": "^3.1.0", "@types/fs-extra": "^8.1.0", - "@types/glob": "^7.1.1", - "@types/jest": "^25.2.3", + "@types/glob": "^7.1.2", + "@types/jest": "^26.0.0", "@types/minimatch": "^3.0.3", "@types/mockery": "^1.4.29", - "@types/node": "^10.17.25", + "@types/node": "^10.17.26", "@types/promptly": "^3.0.0", "@types/semver": "^7.2.0", "@types/sinon": "^9.0.4", "@types/table": "^4.0.7", "@types/uuid": "^8.0.0", + "@types/wrap-ansi": "^3.0.0", "@types/yaml": "^1.9.7", "@types/yargs": "^15.0.5", "aws-sdk-mock": "^5.1.0", @@ -66,12 +67,12 @@ }, "dependencies": { "@aws-cdk/cdk-assets-schema": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/cloudformation-diff": "0.0.0", "@aws-cdk/cx-api": "0.0.0", - "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/region-info": "0.0.0", "archiver": "^4.0.1", - "aws-sdk": "^2.691.0", + "aws-sdk": "^2.699.0", "camelcase": "^6.0.0", "cdk-assets": "0.0.0", "colors": "^1.4.0", @@ -86,6 +87,7 @@ "source-map-support": "^0.5.19", "table": "^5.4.6", "uuid": "^8.1.0", + "wrap-ansi": "^7.0.0", "yaml": "^1.10.0", "yargs": "^15.3.1" }, diff --git a/packages/aws-cdk/test/api/bootstrap.test.ts b/packages/aws-cdk/test/api/bootstrap.test.ts index bed2e574d924f..ddc3086877c7e 100644 --- a/packages/aws-cdk/test/api/bootstrap.test.ts +++ b/packages/aws-cdk/test/api/bootstrap.test.ts @@ -42,6 +42,11 @@ beforeEach(() => { executed = true; return {}; }), + getTemplate: jest.fn(() => { + executed = true; + return {}; + }), + deleteStack: jest.fn(), }; sdk.stubCloudFormation(cfnMocks); }); @@ -134,3 +139,96 @@ test('passing CFN execution policies to the old bootstrapping results in an erro .rejects .toThrow('--cloudformation-execution-policies can only be passed for the new bootstrap experience.'); }); + +test('even if the bootstrap stack is in a rollback state, can still retry bootstrapping it', async () => { + (cfnMocks.describeStacks! as jest.Mock) + .mockReset() + // First two calls, the stack exists with a 'rollback complete' status + // (first is for version checking, second is in deploy-stack.ts) + .mockImplementationOnce(() => ({ Stacks: [ + { + StackStatus: 'UPDATE_ROLLBACK_COMPLETE', + StackStatusReason: 'It is magic', + Outputs: [ + { OutputKey: 'BucketName', OutputValue: 'bucket' }, + { OutputKey: 'BucketDomainName', OutputValue: 'aws.com' }, + ], + }, + ] })) + .mockImplementationOnce(() => ({ Stacks: [ + { + StackStatus: 'UPDATE_ROLLBACK_COMPLETE', + StackStatusReason: 'It is magic', + Outputs: [ + { OutputKey: 'BucketName', OutputValue: 'bucket' }, + { OutputKey: 'BucketDomainName', OutputValue: 'aws.com' }, + ], + }, + ] })) + // Third call, stack has been created + .mockImplementationOnce(() => ({ Stacks: [ + { + StackStatus: 'CREATE_COMPLETE', + StackStatusReason: 'It is magic', + EnableTerminationProtection: false, + }, + ]})); + + // WHEN + const ret = await bootstrapEnvironment(env, sdk, { toolkitStackName: 'mockStack' }); + + // THEN + const bucketProperties = changeSetTemplate.Resources.StagingBucket.Properties; + expect(bucketProperties.BucketName).toBeUndefined(); + expect(bucketProperties.BucketEncryption.ServerSideEncryptionConfiguration[0].ServerSideEncryptionByDefault.KMSMasterKeyID) + .toBeUndefined(); + expect(ret.noOp).toBeFalsy(); + expect(executed).toBeTruthy(); +}); + +test('even if the bootstrap stack failed to create, can still retry bootstrapping it', async () => { + (cfnMocks.describeStacks! as jest.Mock) + .mockReset() + // First two calls, the stack exists with a 'rollback complete' status + // (first is for version checking, second is in deploy-stack.ts) + .mockImplementationOnce(() => ({ Stacks: [ + { + StackStatus: 'ROLLBACK_COMPLETE', + StackStatusReason: 'It is magic', + Outputs: [ + { OutputKey: 'BucketName', OutputValue: 'bucket' }, + ], + } as AWS.CloudFormation.Stack, + ] })) + .mockImplementationOnce(() => ({ Stacks: [ + { + StackStatus: 'ROLLBACK_COMPLETE', + StackStatusReason: 'It is magic', + Outputs: [ + { OutputKey: 'BucketName', OutputValue: 'bucket' }, + ], + }, + ] })) + // Third call, we just did a delete and want to see it gone + .mockImplementationOnce(() => ({ Stacks: [] })) + // Fourth call, stack has been created + .mockImplementationOnce(() => ({ Stacks: [ + { + StackStatus: 'CREATE_COMPLETE', + StackStatusReason: 'It is magic', + EnableTerminationProtection: false, + }, + ]})); + + // WHEN + const ret = await bootstrapEnvironment(env, sdk, { toolkitStackName: 'mockStack' }); + + // THEN + const bucketProperties = changeSetTemplate.Resources.StagingBucket.Properties; + expect(bucketProperties.BucketName).toBeUndefined(); + expect(bucketProperties.BucketEncryption.ServerSideEncryptionConfiguration[0].ServerSideEncryptionByDefault.KMSMasterKeyID) + .toBeUndefined(); + expect(ret.noOp).toBeFalsy(); + expect(executed).toBeTruthy(); + expect(cfnMocks.deleteStack).toHaveBeenCalled(); +}); \ No newline at end of file diff --git a/packages/aws-cdk/test/api/bootstrap2.test.ts b/packages/aws-cdk/test/api/bootstrap2.test.ts index f90db81806d24..d3fa823588052 100644 --- a/packages/aws-cdk/test/api/bootstrap2.test.ts +++ b/packages/aws-cdk/test/api/bootstrap2.test.ts @@ -15,6 +15,7 @@ jest.mock('../../lib/api/toolkit-info', () => ({ })); import { bootstrapEnvironment2 } from '../../lib/api/bootstrap'; +import { DeployStackOptions } from '../../lib/api/deploy-stack'; import { MockSdkProvider } from '../util/mock-sdk'; describe('Bootstrapping v2', () => { @@ -23,8 +24,12 @@ describe('Bootstrapping v2', () => { region: 'us-east-1', name: 'mock', }; - const sdk = new MockSdkProvider(); - mockToolkitInfo = undefined; + + let sdk: MockSdkProvider; + beforeEach(() => { + sdk = new MockSdkProvider(); + mockToolkitInfo = undefined; + }); test('passes the bucket name as a CFN parameter', async () => { await bootstrapEnvironment2(env, sdk, { @@ -74,6 +79,27 @@ describe('Bootstrapping v2', () => { .rejects.toThrow('Not downgrading existing bootstrap stack'); }); + test('bootstrap template has the right exports', async () => { + let template: any; + mockDeployStack.mockImplementation((args: DeployStackOptions) => { + template = args.stack.template; + }); + + await bootstrapEnvironment2(env, sdk, {}); + + const exports = Object.values(template.Outputs ?? {}) + .filter((o: any) => o.Export !== undefined) + .map((o: any) => o.Export.Name); + + expect(exports).toEqual([ + // This is used by aws-s3-assets + { 'Fn::Sub': 'CdkBootstrap-${Qualifier}-FileAssetKeyArn' }, + // This is used by the CLI to verify the bootstrap stack version, + // and could also be used by templates which are deployed through pipelines. + { 'Fn::Sub': 'CdkBootstrap-${Qualifier}-Version' }, + ]); + }); + afterEach(() => { mockDeployStack.mockClear(); }); diff --git a/packages/cdk-assets/.eslintrc.js b/packages/cdk-assets/.eslintrc.js index 0c8afb4aeb0c3..61dd8dd001f63 100644 --- a/packages/cdk-assets/.eslintrc.js +++ b/packages/cdk-assets/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/cdk-assets/package.json b/packages/cdk-assets/package.json index 91c272bb380c8..ce045cd8738b7 100644 --- a/packages/cdk-assets/package.json +++ b/packages/cdk-assets/package.json @@ -31,13 +31,13 @@ "license": "Apache-2.0", "devDependencies": { "@types/archiver": "^3.1.0", - "@types/glob": "^7.1.1", - "@types/jest": "^25.2.3", + "@types/glob": "^7.1.2", + "@types/jest": "^26.0.0", "@types/mock-fs": "^4.10.0", - "@types/node": "^10.17.25", + "@types/node": "^10.17.26", "@types/yargs": "^15.0.5", "@types/jszip": "^3.4.1", - "jszip": "^3.4.0", + "jszip": "^3.5.0", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", "mock-fs": "^4.12.0", @@ -47,7 +47,7 @@ "@aws-cdk/cdk-assets-schema": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "archiver": "^4.0.1", - "aws-sdk": "^2.691.0", + "aws-sdk": "^2.699.0", "glob": "^7.1.6", "yargs": "^15.3.1" }, diff --git a/packages/cdk-dasm/package.json b/packages/cdk-dasm/package.json index 67043c1b1f359..1c7f82005319e 100644 --- a/packages/cdk-dasm/package.json +++ b/packages/cdk-dasm/package.json @@ -30,7 +30,7 @@ "yaml": "1.10.0" }, "devDependencies": { - "@types/jest": "^25.2.3", + "@types/jest": "^26.0.0", "@types/yaml": "1.9.7", "jest": "^25.5.4" }, diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 2efed31310e63..01dc28f00b008 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -186,7 +186,7 @@ }, "devDependencies": { "@types/fs-extra": "^8.1.0", - "@types/jest": "^25.2.3", + "@types/jest": "^26.0.0", "@types/yaml": "1.9.7", "@types/yargs": "^15.0.5", "jest": "^25.5.4", diff --git a/packages/monocdk-experiment/.eslintrc.js b/packages/monocdk-experiment/.eslintrc.js index 0c8afb4aeb0c3..61dd8dd001f63 100644 --- a/packages/monocdk-experiment/.eslintrc.js +++ b/packages/monocdk-experiment/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/packages/monocdk-experiment/package.json b/packages/monocdk-experiment/package.json index 24d6079e29cee..10e68d49cb898 100644 --- a/packages/monocdk-experiment/package.json +++ b/packages/monocdk-experiment/package.json @@ -81,6 +81,7 @@ "license": "Apache-2.0", "bundledDependencies": [ "case", + "fs-extra", "jsonschema", "minimatch", "semver", @@ -89,6 +90,7 @@ "dependencies": { "case": "1.6.3", "constructs": "^3.0.2", + "fs-extra": "^9.0.1", "jsonschema": "^1.2.5", "minimatch": "^3.0.4", "semver": "^7.2.2", @@ -246,7 +248,7 @@ "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/region-info": "0.0.0", "@types/fs-extra": "^8.1.1", - "@types/node": "^10.17.25", + "@types/node": "^10.17.26", "cdk-build-tools": "0.0.0", "fs-extra": "^9.0.1", "pkglint": "0.0.0", diff --git a/scripts/foreach.sh b/scripts/foreach.sh index 8f4b3ef820e0a..39fcb574be7e8 100755 --- a/scripts/foreach.sh +++ b/scripts/foreach.sh @@ -44,6 +44,23 @@ if [[ "${1:-}" == "--reset" ]]; then exit 0 fi +if [[ "${1:-}" == "--skip" ]]; then + if [ ! -f ${statefile} ]; then + error "skip failed. no active sessions found." + exit 1 + fi + next=$(head -1 ${statefile}) + if [ -z "${next}" ]; then + error "skip failed. queue is empty. to reset:" + error " $0 --reset" + exit 1 + fi + tail -n +2 "${statefile}" > "${statefile}.tmp" + cp "${statefile}.tmp" "${statefile}" + success "directory '$next' skipped. re-run the original foreach command to resume." + exit 0 +fi + up="" up_desc="" if [[ "${1:-}" == "--up" ]]; then diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index 7a7601093e329..60a51cd90dc9a 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -34,19 +34,19 @@ "license": "Apache-2.0", "devDependencies": { "@types/fs-extra": "^8.1.0", - "@types/jest": "^25.2.3", + "@types/jest": "^26.0.0", "@types/yargs": "^15.0.5", "pkglint": "0.0.0" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^3.1.0", + "@typescript-eslint/eslint-plugin": "^3.3.0", "@typescript-eslint/parser": "^2.19.2", "awslint": "0.0.0", "colors": "^1.4.0", "eslint": "^6.8.0", - "eslint-import-resolver-node": "^0.3.3", + "eslint-import-resolver-node": "^0.3.4", "eslint-import-resolver-typescript": "^2.0.0", - "eslint-plugin-import": "^2.20.2", + "eslint-plugin-import": "^2.21.2", "fs-extra": "^9.0.1", "jest": "^25.5.4", "jsii": "^1.6.0", diff --git a/tools/cdk-integ-tools/.eslintrc.js b/tools/cdk-integ-tools/.eslintrc.js index 0c8afb4aeb0c3..61dd8dd001f63 100644 --- a/tools/cdk-integ-tools/.eslintrc.js +++ b/tools/cdk-integ-tools/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/tools/cdk-integ-tools/lib/integ-helpers.ts b/tools/cdk-integ-tools/lib/integ-helpers.ts index 75a7c56edc813..43630263e2c38 100644 --- a/tools/cdk-integ-tools/lib/integ-helpers.ts +++ b/tools/cdk-integ-tools/lib/integ-helpers.ts @@ -170,7 +170,7 @@ export class IntegrationTest { * contents. This allows integ tests to supply custom command line arguments to "cdk deploy" and "cdk synth". */ private async readStackPragma(): Promise { - const source = await fs.readFile(this.sourceFilePath, 'utf-8'); + const source = await fs.readFile(this.sourceFilePath, { encoding: 'utf-8' }); const pragmaLine = source.split('\n').find(x => x.startsWith(CDK_INTEG_STACK_PRAGMA + ' ')); if (!pragmaLine) { return []; diff --git a/tools/cfn2ts/.eslintrc.js b/tools/cfn2ts/.eslintrc.js index e86ff743a6498..41de30ad40f8f 100644 --- a/tools/cfn2ts/.eslintrc.js +++ b/tools/cfn2ts/.eslintrc.js @@ -1,4 +1,4 @@ -const baseConfig = require('../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.ignorePatterns.push('test/enrichments/**'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/tools/cfn2ts/lib/codegen.ts b/tools/cfn2ts/lib/codegen.ts index 8bd831d9de73e..6bba39e50abd5 100644 --- a/tools/cfn2ts/lib/codegen.ts +++ b/tools/cfn2ts/lib/codegen.ts @@ -249,6 +249,8 @@ export default class CodeGenerator { // handle all non-property attributes // (retention policies, conditions, metadata, etc.) this.code.line('const cfnOptions = ret.cfnOptions;'); + this.code.line(`cfnOptions.creationPolicy = ${CFN_PARSE}.FromCloudFormation.parseCreationPolicy(resourceAttributes.CreationPolicy);`); + this.code.line(`cfnOptions.updatePolicy = ${CFN_PARSE}.FromCloudFormation.parseUpdatePolicy(resourceAttributes.UpdatePolicy);`); this.code.line(`cfnOptions.deletionPolicy = ${CFN_PARSE}.FromCloudFormation.parseDeletionPolicy(resourceAttributes.DeletionPolicy);`); this.code.line(`cfnOptions.updateReplacePolicy = ${CFN_PARSE}.FromCloudFormation.parseDeletionPolicy(resourceAttributes.UpdateReplacePolicy);`); this.code.line(`cfnOptions.metadata = ${CFN_PARSE}.FromCloudFormation.parseValue(resourceAttributes.Metadata);`); diff --git a/tools/cfn2ts/package.json b/tools/cfn2ts/package.json index 8a0dcf6e662a1..9fe11127213c1 100644 --- a/tools/cfn2ts/package.json +++ b/tools/cfn2ts/package.json @@ -37,7 +37,7 @@ }, "devDependencies": { "@types/fs-extra": "^8.1.0", - "@types/jest": "^25.2.3", + "@types/jest": "^26.0.0", "@types/yargs": "^15.0.5", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", diff --git a/tools/pkglint/lib/aws-service-official-names.json b/tools/pkglint/lib/aws-service-official-names.json index 17e741cca18c5..dcea9ca2092d2 100644 --- a/tools/pkglint/lib/aws-service-official-names.json +++ b/tools/pkglint/lib/aws-service-official-names.json @@ -12,7 +12,7 @@ "AWS::Backup": "AWS Backup", "AWS::Batch": "AWS Batch", "AWS::Budgets": "AWS Budgets", - "AWS::CertificateManager": "Amazon Certificate Manager", + "AWS::CertificateManager": "AWS Certificate Manager", "AWS::Cloud9": "AWS Cloud9", "AWS::CloudFormation": "AWS CloudFormation", "AWS::CloudFront": "Amazon CloudFront", diff --git a/tools/pkgtools/.eslintrc.js b/tools/pkgtools/.eslintrc.js index 0c8afb4aeb0c3..61dd8dd001f63 100644 --- a/tools/pkgtools/.eslintrc.js +++ b/tools/pkgtools/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/tools/yarn-cling/.eslintrc.js b/tools/yarn-cling/.eslintrc.js index 0c60e21090199..61dd8dd001f63 100644 --- a/tools/yarn-cling/.eslintrc.js +++ b/tools/yarn-cling/.eslintrc.js @@ -1,3 +1,3 @@ -const baseConfig = require('../tools/cdk-build-tools/config/eslintrc'); +const baseConfig = require('cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; module.exports = baseConfig; diff --git a/tools/yarn-cling/package.json b/tools/yarn-cling/package.json index 2c87c0e9467ec..64b881655f5b5 100644 --- a/tools/yarn-cling/package.json +++ b/tools/yarn-cling/package.json @@ -39,9 +39,9 @@ }, "devDependencies": { "@types/yarnpkg__lockfile": "^1.1.3", - "@types/jest": "^25.2.3", + "@types/jest": "^26.0.0", "jest": "^25.5.4", - "@types/node": "^10.17.25", + "@types/node": "^10.17.26", "typescript": "~3.8.3", "pkglint": "0.0.0" }, diff --git a/yarn.lock b/yarn.lock index 63d2c1379acd2..128cec85f6895 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1427,11 +1427,6 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== -"@types/events@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" - integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== - "@types/fs-extra@^8.1.0": version "8.1.0" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.0.tgz#1114834b53c3914806cd03b3304b37b3bd221a4d" @@ -1446,12 +1441,11 @@ dependencies: "@types/node" "*" -"@types/glob@*", "@types/glob@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" - integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== +"@types/glob@*", "@types/glob@^7.1.1", "@types/glob@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.2.tgz#06ca26521353a545d94a0adc74f38a59d232c987" + integrity sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA== dependencies: - "@types/events" "*" "@types/minimatch" "*" "@types/node" "*" @@ -1482,10 +1476,10 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" -"@types/jest@^25.2.3": - version "25.2.3" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.2.3.tgz#33d27e4c4716caae4eced355097a47ad363fdcaf" - integrity sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw== +"@types/jest@^26.0.0": + version "26.0.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.0.tgz#a6d7573dffa9c68cbbdf38f2e0de26f159e11134" + integrity sha512-/yeMsH9HQ1RLORlXAwoLXe8S98xxvhNtUz3yrgrwbaxYjT+6SFPZZRksmRKRA6L5vsUtSHeN71viDOTTyYAD+g== dependencies: jest-diff "^25.2.1" pretty-format "^25.2.1" @@ -1546,10 +1540,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.0.tgz#30d2d09f623fe32cde9cb582c7a6eda2788ce4a8" integrity sha512-WE4IOAC6r/yBZss1oQGM5zs2D7RuKR6Q+w+X2SouPofnWn+LbCqClRyhO3ZE7Ix8nmFgo/oVuuE01cJT2XB13A== -"@types/node@^10.17.25": - version "10.17.25" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.25.tgz#64f64cd3e8641e8163c81045e545d2825d300e37" - integrity sha512-EWPw3jDB0jip4HafDkoezNOwG00TtVZ1TOe74MaxIBWgpyM60UF/LXzFVx9+8AdSYNNOPgx7TuJoRmgnhHZ/7g== +"@types/node@^10.17.26": + version "10.17.26" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.26.tgz#a8a119960bff16b823be4c617da028570779bcfd" + integrity sha512-myMwkO2Cr82kirHY8uknNRHEVtn0wV3DTQfkrjx17jmkstDRZ24gNUdl8AHXVyVclTYI/bNjgTPTAWvWLqXqkw== "@types/nodeunit@^0.0.31": version "0.0.31" @@ -1619,6 +1613,11 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== +"@types/wrap-ansi@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" + integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== + "@types/yaml@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@types/yaml/-/yaml-1.2.0.tgz#4ed577fc4ebbd6b829b28734e56d10c9e6984e09" @@ -1648,12 +1647,12 @@ resolved "https://registry.yarnpkg.com/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.3.tgz#38fb31d82ed07dea87df6bd565721d11979fd761" integrity sha512-mhdQq10tYpiNncMkg1vovCud5jQm+rWeRVz6fxjCJlY6uhDlAn9GnMSmBa2DQwqPf/jS5YR0K/xChDEh1jdOQg== -"@typescript-eslint/eslint-plugin@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.1.0.tgz#4ac00ecca3bbea740c577f1843bc54fa69c3def2" - integrity sha512-D52KwdgkjYc+fmTZKW7CZpH5ZBJREJKZXRrveMiRCmlzZ+Rw9wRVJ1JAmHQ9b/+Ehy1ZeaylofDB9wwXUt83wg== +"@typescript-eslint/eslint-plugin@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.3.0.tgz#89518e5c5209a349bde161c3489b0ec187ae5d37" + integrity sha512-Ybx/wU75Tazz6nU2d7nN6ll0B98odoiYLXwcuwS5WSttGzK46t0n7TPRQ4ozwcTv82UY6TQoIvI+sJfTzqK9dQ== dependencies: - "@typescript-eslint/experimental-utils" "3.1.0" + "@typescript-eslint/experimental-utils" "3.3.0" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" semver "^7.3.2" @@ -1669,13 +1668,13 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/experimental-utils@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.1.0.tgz#2d5dba7c2ac2a3da3bfa3f461ff64de38587a872" - integrity sha512-Zf8JVC2K1svqPIk1CB/ehCiWPaERJBBokbMfNTNRczCbQSlQXaXtO/7OfYz9wZaecNvdSvVADt6/XQuIxhC79w== +"@typescript-eslint/experimental-utils@3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.3.0.tgz#d72a946e056a83d4edf97f3411cceb639b0b8c87" + integrity sha512-d4pGIAbu/tYsrPrdHCQ5xfadJGvlkUxbeBB56nO/VGmEDi/sKmfa5fGty5t5veL1OyJBrUmSiRn1R1qfVDydrg== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "3.1.0" + "@typescript-eslint/typescript-estree" "3.3.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" @@ -1702,10 +1701,10 @@ semver "^6.3.0" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.1.0.tgz#eaff52d31e615e05b894f8b9d2c3d8af152a5dd2" - integrity sha512-+4nfYauqeQvK55PgFrmBWFVYb6IskLyOosYEmhH3mSVhfBp9AIJnjExdgDmKWoOBHRcPM8Ihfm2BFpZf0euUZQ== +"@typescript-eslint/typescript-estree@3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.3.0.tgz#841ffed25c29b0049ebffb4c2071268a34558a2a" + integrity sha512-3SqxylENltEvJsjjMSDCUx/edZNSC7wAqifUU1Ywp//0OWEZwMZJfecJud9XxJ/40rAKEbJMKBOQzeOjrLJFzQ== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" @@ -2005,7 +2004,7 @@ array-ify@^1.0.0: resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= -array-includes@^3.0.3: +array-includes@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== @@ -2031,7 +2030,7 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.flat@^1.2.1: +array.prototype.flat@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== @@ -2124,10 +2123,10 @@ aws-sdk-mock@^5.1.0: sinon "^9.0.1" traverse "^0.6.6" -aws-sdk@^2.637.0, aws-sdk@^2.691.0: - version "2.691.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.691.0.tgz#92361b63117e94d065dad2f215296f5a19fe0c70" - integrity sha512-HV/iANH5PJvexubWr/oDmWMKtV/n1shtrACrLIUa5vTXIT6O7CzUouExNOvOtFMZw8zJkLmyEpa/0bDpMmo0Zg== +aws-sdk@^2.637.0, aws-sdk@^2.699.0: + version "2.699.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.699.0.tgz#e77b6ffa4c860882e2779c060b74fab33d42f554" + integrity sha512-EC431z/+i/cJgOgnDpOJ8Fa6+p7Oo1vIvdm/uJqP9tJX3+pxi/M/tvQavfz4yAlLBFqjQwxa8nrPisby0Mr5MQ== dependencies: buffer "4.9.2" events "1.1.1" @@ -3823,10 +3822,10 @@ escodegen@1.x.x, escodegen@^1.11.1: optionalDependencies: source-map "~0.6.1" -eslint-import-resolver-node@^0.3.2, eslint-import-resolver-node@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404" - integrity sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg== +eslint-import-resolver-node@^0.3.3, eslint-import-resolver-node@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" + integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== dependencies: debug "^2.6.9" resolve "^1.13.1" @@ -3842,7 +3841,7 @@ eslint-import-resolver-typescript@^2.0.0: tiny-glob "^0.2.6" tsconfig-paths "^3.9.0" -eslint-module-utils@^2.4.1: +eslint-module-utils@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== @@ -3850,23 +3849,24 @@ eslint-module-utils@^2.4.1: debug "^2.6.9" pkg-dir "^2.0.0" -eslint-plugin-import@^2.20.2: - version "2.20.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz#91fc3807ce08be4837141272c8b99073906e588d" - integrity sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg== +eslint-plugin-import@^2.21.2: + version "2.21.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.21.2.tgz#8fef77475cc5510801bedc95f84b932f7f334a7c" + integrity sha512-FEmxeGI6yaz+SnEB6YgNHlQK1Bs2DKLM+YF+vuTk5H8J9CLbJLtlPvRFgZZ2+sXiKAlN5dpdlrWOjK8ZoZJpQA== dependencies: - array-includes "^3.0.3" - array.prototype.flat "^1.2.1" + array-includes "^3.1.1" + array.prototype.flat "^1.2.3" contains-path "^0.1.0" debug "^2.6.9" doctrine "1.5.0" - eslint-import-resolver-node "^0.3.2" - eslint-module-utils "^2.4.1" + eslint-import-resolver-node "^0.3.3" + eslint-module-utils "^2.6.0" has "^1.0.3" minimatch "^3.0.4" - object.values "^1.1.0" + object.values "^1.1.1" read-pkg-up "^2.0.0" - resolve "^1.12.0" + resolve "^1.17.0" + tsconfig-paths "^3.9.0" eslint-scope@^5.0.0: version "5.0.0" @@ -4123,13 +4123,13 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-check@^1.24.2: - version "1.24.2" - resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-1.24.2.tgz#1f5e4a3c20530c3a85a861e60f680c32229d4fcb" - integrity sha512-ZL48cyZZLJnVsUj127Zi1mfFLM98yzw0LlSSH8CMeVmpL5RCfSRcZSZZ0kJWrRK4eOgNFnXXKNDbzuRb3Vsdhg== +fast-check@^1.25.1: + version "1.25.1" + resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-1.25.1.tgz#aab6e34496a23ba7d7d20188699d9abdcaaa2dcc" + integrity sha512-4lyIDY2YKpSiPXpceCQBTfDxLh/7/C3OHgvzToea3y1YAlv38Wz9mfIsu+MD4go0NX3ow/g98kEmlW00+CoH3w== dependencies: pure-rand "^2.0.0" - tslib "^1.10.0" + tslib "^2.0.0" fast-deep-equal@^2.0.1: version "2.0.1" @@ -6137,10 +6137,10 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jszip@*, jszip@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.4.0.tgz#1a69421fa5f0bb9bc222a46bca88182fba075350" - integrity sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg== +jszip@*, jszip@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.5.0.tgz#b4fd1f368245346658e781fec9675802489e15f6" + integrity sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA== dependencies: lie "~3.3.0" pako "~1.0.2" @@ -7255,7 +7255,7 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -object.values@^1.1.0: +object.values@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== @@ -8287,20 +8287,13 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.1.6, resolve@^1.17.0: +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" -resolve@^1.10.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.3.2: - version "1.16.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.16.1.tgz#49fac5d8bacf1fd53f200fa51247ae736175832c" - integrity sha512-rmAglCSqWWMrrBv/XM6sW0NuRFiKViw/W4d9EbC4pt+49H8JwHy+mcGmALTEg504AUDcLTvb1T2q3E9AnmY+ig== - dependencies: - path-parse "^1.0.6" - restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -9480,11 +9473,16 @@ tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@^1.10.0, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: +tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: version "1.11.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== +tslib@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.0.tgz#18d13fc2dce04051e20f074cc8387fd8089ce4f3" + integrity sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g== + tslint@^5.20.1: version "5.20.1" resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d" @@ -9949,6 +9947,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"