diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 6ad382cc4cef9..69581271f774d 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..53cacd3ae3bfd 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..20a51e351f0e4 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..8cb4679f0889d 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..64b8fd2e08019 --- /dev/null +++ b/.github/workflows/issue-label-assign.yml @@ -0,0 +1,171 @@ +name: "Set Issue Label and Assignee" +on: + issues: + types: [opened] + pull_request: + types: [opened] + +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":["(@aws-cdk/alexa-ask)","(alexa-ask)","(alexa ask)"],"labels":["@aws-cdk/alexa-ask"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/app-delivery)","(app-delivery)","(app delivery)"],"labels":["@aws-cdk/app-delivery"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/assert)","(assert)"],"labels":["@aws-cdk/assert"],"assignees":["nija-at"]}, + {"keywords":["(@aws-cdk/assets)","(assets)"],"labels":["@aws-cdk/assets"],"assignees":["eladb"]}, + {"keywords":["(@aws-cdk/aws-accessanalyzer)","(aws-accessanalyzer)","(accessanalyzer)","(access analyzer)"],"labels":["@aws-cdk/aws-accessanalyzer"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-acmpca)","(aws-acmpca)","(acmpca)"],"labels":["@aws-cdk/aws-acmpca"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/aws-amazonmq)","(aws-amazonmq)","(amazonmq)","(amazon mq)"],"labels":["@aws-cdk/aws-amazonmq"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-amplify)","(aws-amplify)","(amplify)"],"labels":["@aws-cdk/aws-amplify"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-apigateway)","(aws-apigateway)","(apigateway)", "(api gateway)"],"labels":["@aws-cdk/aws-apigateway"],"assignees":["nija-at"]}, + {"keywords":["(@aws-cdk/aws-apigatewayv2)","(aws-apigatewayv2)","(apigatewayv2)","(apigateway v2)"],"labels":["@aws-cdk/aws-apigatewayv2"],"assignees":["nija-at"]}, + {"keywords":["(@aws-cdk/aws-appconfig)","(aws-appconfig)","(appconfig)","(app config)"],"labels":["@aws-cdk/aws-appconfig"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-applicationautoscaling)","(aws-applicationautoscaling)","(applicationautoscaling)","(application autoscaling)"],"labels":["@aws-cdk/aws-applicationautoscaling"],"assignees":["NetaNir"]}, + {"keywords":["(@aws-cdk/aws-appmesh)","(aws-appmesh)","(appmesh)","(app mesh)"],"labels":["@aws-cdk/aws-appmesh"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-appstream)","(aws-appstream)","(appstream)","(app stream)"],"labels":["@aws-cdk/aws-appstream"],"assignees":["NetaNir"]}, + {"keywords":["(@aws-cdk/aws-appsync)","(aws-appsync)","(appsync)","(app sync)"],"labels":["@aws-cdk/aws-appsync"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-athena)","(aws-athena)","(athena)"],"labels":["@aws-cdk/aws-athena"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-autoscaling)","(aws-autoscaling)","(autoscaling)","(auto scaling)"],"labels":["@aws-cdk/aws-autoscaling"],"assignees":["NetaNir"]}, + {"keywords":["(@aws-cdk/aws-autoscaling-api)","(aws-autoscaling-api)","(autoscaling-api)","(autoscaling api)"],"labels":["@aws-cdk/aws-autoscaling-api"],"assignees":["NetaNir"]}, + {"keywords":["(@aws-cdk/aws-autoscaling-common)","(aws-autoscaling-common)","(autoscaling-common)","(autoscaling common)"],"labels":["@aws-cdk/aws-autoscaling-common"],"assignees":["NetaNir"]}, + {"keywords":["(@aws-cdk/aws-autoscaling-hooktargets)","(aws-autoscaling-hooktargets)","(autoscaling-hooktargets)","(autoscaling hooktargets)"],"labels":["@aws-cdk/aws-autoscaling-hooktargets"],"assignees":["NetaNir"]}, + {"keywords":["(@aws-cdk/aws-autoscalingplans)","(aws-autoscalingplans)","(autoscalingplans)","(autoscaling plans)"],"labels":["@aws-cdk/aws-autoscalingplans"],"assignees":["NetaNir"]}, + {"keywords":["(@aws-cdk/aws-backup)","(aws-backup)","(backup)"],"labels":["@aws-cdk/aws-backup"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-batch)","(aws-batch)","(batch)"],"labels":["@aws-cdk/aws-batch"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-budgets)","(aws-budgets)","(budgets)"],"labels":["@aws-cdk/aws-budgets"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-cassandra)","(aws-cassandra)","(cassandra)"],"labels":["@aws-cdk/aws-cassandra"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-ce)","(aws-ce)","(ce)"],"labels":["@aws-cdk/aws-ce"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-certificatemanager)","(aws-certificatemanager)","(certificatemanager)","(certificate manager)"],"labels":["@aws-cdk/aws-certificatemanager"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/aws-chatbot)","(aws-chatbot)","(chatbot)"],"labels":["@aws-cdk/aws-chatbot"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-cloud9)","(aws-cloud9)","(cloud9)","(cloud 9)"],"labels":["@aws-cdk/aws-cloud9"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/aws-cloudformation)","(aws-cloudformation)","(cloudformation)","(cloud formation)"],"labels":["@aws-cdk/aws-cloudformation"],"assignees":["eladb"]}, + {"keywords":["(@aws-cdk/aws-cloudfront)","(aws-cloudfront)","(cloudfront)","(cloud front)"],"labels":["@aws-cdk/aws-cloudfront"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-cloudtrail)","(aws-cloudtrail)","(cloudtrail)","(cloud trail)"],"labels":["@aws-cdk/aws-cloudtrail"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-cloudwatch)","(aws-cloudwatch)","(cloudwatch)","(cloud watch)"],"labels":["@aws-cdk/aws-cloudwatch"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-cloudwatch-actions)","(aws-cloudwatch-actions)","(cloudwatch-actions)","(cloudwatch actions)"],"labels":["@aws-cdk/aws-cloudwatch-actions"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-codebuild)","(aws-codebuild)","(codebuild)","(code build)"],"labels":["@aws-cdk/aws-codebuild"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/aws-codecommit)","(aws-codecommit)","(codecommit)","(code commit)"],"labels":["@aws-cdk/aws-codecommit"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/aws-codedeploy)","(aws-codedeploy)","(codedeploy)","(code deploy)"],"labels":["@aws-cdk/aws-codedeploy"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/aws-codeguruprofiler)","(aws-codeguruprofiler)","(codeguruprofiler)","(codeguru profiler)"],"labels":["@aws-cdk/aws-codeguruprofiler"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/aws-codepipeline)","(aws-codepipeline)","(codepipeline)","(code pipeline)"],"labels":["@aws-cdk/aws-codepipeline"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/aws-codepipeline-actions)","(aws-codepipeline-actions)","(codepipeline-actions)","(codepipeline actions)"],"labels":["@aws-cdk/aws-codepipeline-actions"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/aws-codestar)","(aws-codestar)","(codestar)"],"labels":["@aws-cdk/aws-codestar"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/aws-codestarconnections)","(aws-codestarconnections)","(codestarconnections)","(codestar connections)"],"labels":["@aws-cdk/aws-codestarconnections"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/aws-codestarnotifications)","(aws-codestarnotifications)","(codestarnotifications)","(codestar notifications)"],"labels":["@aws-cdk/aws-codestarnotifications"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/aws-cognito)","(aws-cognito)","(cognito)"],"labels":["@aws-cdk/aws-cognito"],"assignees":["nija-at"]}, + {"keywords":["(@aws-cdk/aws-config)","(aws-config)","(config)"],"labels":["@aws-cdk/aws-config"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-datapipeline)","(aws-datapipeline)","(datapipeline)","(data pipeline)"],"labels":["@aws-cdk/aws-datapipeline"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-dax)","(aws-dax)","(dax)"],"labels":["@aws-cdk/aws-dax"],"assignees":["RomainMuller"]}, + {"keywords":["(@aws-cdk/aws-detective)","(aws-detective)","(detective)"],"labels":["@aws-cdk/aws-detective"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-directoryservice)","(aws-directoryservice)","(directoryservice)","(directory service)"],"labels":["@aws-cdk/aws-directoryservice"],"assignees":["NetaNir"]}, + {"keywords":["(@aws-cdk/aws-dlm)","(aws-dlm)","(dlm)"],"labels":["@aws-cdk/aws-dlm"],"assignees":["nija-at"]}, + {"keywords":["(@aws-cdk/aws-dms)","(aws-dms)","(dms)"],"labels":["@aws-cdk/aws-dms"],"assignees":["nija-at"]}, + {"keywords":["(@aws-cdk/aws-docdb)","(aws-docdb)","(docdb)","(doc db)"],"labels":["@aws-cdk/aws-docdb"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-dynamodb)","(aws-dynamodb)","(dynamodb)","(dynamo db)"],"labels":["@aws-cdk/aws-dynamodb"],"assignees":["RomainMuller"]}, + {"keywords":["(@aws-cdk/aws-dynamodb-global)","(aws-dynamodb-global)","(dynamodb-global)","(dynamodb global)"],"labels":["@aws-cdk/aws-dynamodb-global"],"assignees":["RomainMuller"]}, + {"keywords":["(@aws-cdk/aws-ec2)","(aws-ec2)","(ec2)"],"labels":["@aws-cdk/aws-ec2"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-ecr)","(aws-ecr)","(ecr)"],"labels":["@aws-cdk/aws-ecr"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-ecr-assets)","(aws-ecr-assets)","(ecr-assets)","(ecr assets)"],"labels":["@aws-cdk/aws-ecr-assets"],"assignees":["eladb"]}, + {"keywords":["(@aws-cdk/aws-ecs)","(aws-ecs)","(ecs)"],"labels":["@aws-cdk/aws-ecs"],"assignees":["uttarasridhar"]}, + {"keywords":["(@aws-cdk/aws-ecs-patterns)","(aws-ecs-patterns)","(ecs-patterns)","(ecs patterns)"],"labels":["@aws-cdk/aws-ecs-patterns"],"assignees":["uttarasridhar"]}, + {"keywords":["(@aws-cdk/aws-efs)","(aws-efs)","(efs)"],"labels":["@aws-cdk/aws-efs"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-eks)","(aws-eks)","(eks)"],"labels":["@aws-cdk/aws-eks"],"assignees":["eladb"]}, + {"keywords":["(@aws-cdk/aws-elasticache)","(aws-elasticache)","(elasticache)","(elastic cache)"],"labels":["@aws-cdk/aws-elasticache"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-elasticbeanstalk)","(aws-elasticbeanstalk)","(elasticbeanstalk)","(elastic beanstalk)"],"labels":["@aws-cdk/aws-elasticbeanstalk"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/aws-elasticloadbalancing)","(aws-elasticloadbalancing)","(elasticloadbalancing)","(elastic loadbalancing)","(elb)"],"labels":["@aws-cdk/aws-elasticloadbalancing"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-elasticloadbalancingv2)","(aws-elasticloadbalancingv2)","(elasticloadbalancingv2)","(elbv2)","(elb v2)"],"labels":["@aws-cdk/aws-elasticloadbalancingv2"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-elasticloadbalancingv2-targets)","(aws-elasticloadbalancingv2-targets)","(elasticloadbalancingv2-targets)","(elasticloadbalancingv2 targets)","(elbv2 targets)"],"labels":["@aws-cdk/aws-elasticloadbalancingv2-targets"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-elasticsearch)","(aws-elasticsearch)","(elasticsearch)","(elastic search)"],"labels":["@aws-cdk/aws-elasticsearch"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-emr)","(aws-emr)","(emr)"],"labels":["@aws-cdk/aws-emr"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-events)","(aws-events)","(events)", "eventbridge"],"labels":["@aws-cdk/aws-events"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-events-targets)","(aws-events-targets)","(events-targets)","(events targets)"],"labels":["@aws-cdk/aws-events-targets"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-eventschemas)","(aws-eventschemas)","(eventschemas)","(event schemas)"],"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-fsx)","(aws-fsx)","(fsx)"],"labels":["@aws-cdk/aws-fsx"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-gamelift)","(aws-gamelift)","(gamelift)","(game lift)"],"labels":["@aws-cdk/aws-gamelift"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-globalaccelerator)","(aws-globalaccelerator)","(globalaccelerator)","(global accelerator)"],"labels":["@aws-cdk/aws-globalaccelerator"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-glue)","(aws-glue)","(glue)"],"labels":["@aws-cdk/aws-glue"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-greengrass)","(aws-greengrass)","(greengrass)","(green grass)"],"labels":["@aws-cdk/aws-greengrass"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/aws-guardduty)","(aws-guardduty)","(guardduty)", "(guard duty)"],"labels":["@aws-cdk/aws-guardduty"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-iam)","(aws-iam)","(iam)"],"labels":["@aws-cdk/aws-iam"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-imagebuilder)","(aws-imagebuilder)","(imagebuilder)","(image builder)"],"labels":["@aws-cdk/aws-imagebuilder"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-inspector)","(aws-inspector)","(inspector)"],"labels":["@aws-cdk/aws-inspector"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-iot)","(aws-iot)","(iot)"],"labels":["@aws-cdk/aws-iot"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/aws-iot1click)","(aws-iot1click)","(iot1click)","(iot 1click)"],"labels":["@aws-cdk/aws-iot1click"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/aws-iotanalytics)","(aws-iotanalytics)","(iotanalytics)","(iot analytics)"],"labels":["@aws-cdk/aws-iotanalytics"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/aws-iotevents)","(aws-iotevents)","(iotevents)","(iot events)"],"labels":["@aws-cdk/aws-iotevents"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/aws-iotthingsgraph)","(aws-iotthingsgraph)","(iotthingsgraph)","(iot things graph)"],"labels":["@aws-cdk/aws-iotthingsgraph"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/aws-kinesis)","(aws-kinesis)","(kinesis)"],"labels":["@aws-cdk/aws-kinesis"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-kinesisanalytics)","(aws-kinesisanalytics)","(kinesisanalytics)", "(kinesis analytics)"],"labels":["@aws-cdk/aws-kinesisanalytics"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-kinesisfirehose)","(aws-kinesisfirehose)","(kinesisfirehose)", "(kinesis firehose)"],"labels":["@aws-cdk/aws-kinesisfirehose"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-kms)","(aws-kms)","(kms)"],"labels":["@aws-cdk/aws-kms"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/aws-lakeformation)","(aws-lakeformation)","(lakeformation)", "(lake formation)"],"labels":["@aws-cdk/aws-lakeformation"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-lambda)","(aws-lambda)","(lambda)"],"labels":["@aws-cdk/aws-lambda"],"assignees":["nija-at"]}, + {"keywords":["(@aws-cdk/aws-lambda-event-sources)","(aws-lambda-event-sources)","(lambda-event-sources)","(lambda event sources)"],"labels":["@aws-cdk/aws-lambda-event-sources"],"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/aws-logs)","(aws-logs)","(logs)"],"labels":["@aws-cdk/aws-logs"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-logs-destinations)","(aws-logs-destinations)","(logs-destinations)","(logs destinations)"],"labels":["@aws-cdk/aws-logs-destinations"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-managedblockchain)","(aws-managedblockchain)","(managedblockchain)","(managed blockchain)"],"labels":["@aws-cdk/aws-managedblockchain"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/aws-mediaconvert)","(aws-mediaconvert)","(mediaconvert)","(media convert)"],"labels":["@aws-cdk/aws-mediaconvert"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-medialive)","(aws-medialive)","(medialive)","(media live)"],"labels":["@aws-cdk/aws-medialive"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-mediastore)","(aws-mediastore)","(mediastore)","(media store)"],"labels":["@aws-cdk/aws-mediastore"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-msk)","(aws-msk)","(msk)"],"labels":["@aws-cdk/aws-msk"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-neptune)","(aws-neptune)","(neptune)"],"labels":["@aws-cdk/aws-neptune"],"assignees":["nija-at"]}, + {"keywords":["(@aws-cdk/aws-networkmanager)","(aws-networkmanager)","(networkmanager)","(network manager)"],"labels":["@aws-cdk/aws-networkmanager"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-opsworks)","(aws-opsworks)","(opsworks)","(ops works)"],"labels":["@aws-cdk/aws-opsworks"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-opsworkscm)","(aws-opsworkscm)","(opsworkscm)", "(opsworks cm)"],"labels":["@aws-cdk/aws-opsworkscm"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-personalize)","(aws-personalize)","(personalize)"],"labels":["@aws-cdk/aws-personalize"],"assignees":["NetaNir"]}, + {"keywords":["(@aws-cdk/aws-pinpoint)","(aws-pinpoint)","(pinpoint)"],"labels":["@aws-cdk/aws-pinpoint"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-pinpointemail)","(aws-pinpointemail)","(pinpointemail)", "(pinpoint email)"],"labels":["@aws-cdk/aws-pinpointemail"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-qldb)","(aws-qldb)","(qldb)"],"labels":["@aws-cdk/aws-qldb"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/aws-ram)","(aws-ram)","(ram)"],"labels":["@aws-cdk/aws-ram"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-rds)","(aws-rds)","(rds)"],"labels":["@aws-cdk/aws-rds"],"assignees":["nija-at"]}, + {"keywords":["(@aws-cdk/aws-redshift)","(aws-redshift)","(redshift)","(red shift)"],"labels":["@aws-cdk/aws-redshift"],"assignees":["nija-at"]}, + {"keywords":["(@aws-cdk/aws-resourcegroups)","(aws-resourcegroups)","(resourcegroups)","(resource groups)"],"labels":["@aws-cdk/aws-resourcegroups"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-robomaker)","(aws-robomaker)","(robomaker)","(robo maker)"],"labels":["@aws-cdk/aws-robomaker"],"assignees":["NetaNir"]}, + {"keywords":["(@aws-cdk/aws-route53)","(aws-route53)","(route53)","(route 53)"],"labels":["@aws-cdk/aws-route53"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/aws-route53-patterns)","(aws-route53-patterns)","(route53-patterns)","(route53 patterns)"],"labels":["@aws-cdk/aws-route53-patterns"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/aws-route53-targets)","(aws-route53-targets)","(route53-targets)","(route53 targets)"],"labels":["@aws-cdk/aws-route53-targets"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/aws-route53resolver)","(aws-route53resolver)","(route53resolver)","(route53 resolver)"],"labels":["@aws-cdk/aws-route53resolver"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/aws-s3)","(aws-s3)","(s3)"],"labels":["@aws-cdk/aws-s3"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-s3-assets)","(aws-s3-assets)","(s3-assets)","(s3 assets)"],"labels":["@aws-cdk/aws-s3-assets"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-s3-deployment)","(aws-s3-deployment)","(s3-deployment)","(s3 deployment)"],"labels":["@aws-cdk/aws-s3-deployment"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-s3-notifications)","(aws-s3-notifications)","(s3-notifications)","(s3 notifications)"],"labels":["@aws-cdk/aws-s3-notifications"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-sagemaker)","(aws-sagemaker)","(sagemaker)","(sage maker)"],"labels":["@aws-cdk/aws-sagemaker"],"assignees":["NetaNir"]}, + {"keywords":["(@aws-cdk/aws-sam)","(aws-sam)","(sam)"],"labels":["@aws-cdk/aws-sam"],"assignees":["nija-at"]}, + {"keywords":["(@aws-cdk/aws-sdb)","(aws-sdb)","(sdb)"],"labels":["@aws-cdk/aws-sdb"],"assignees":["nija-at"]}, + {"keywords":["(@aws-cdk/aws-secretsmanager)","(aws-secretsmanager)","(secretsmanager)","(secrets manager)"],"labels":["@aws-cdk/aws-secretsmanager"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/aws-securityhub)","(aws-securityhub)","(securityhub)","(security hub)"],"labels":["@aws-cdk/aws-securityhub"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-servicecatalog)","(aws-servicecatalog)","(servicecatalog)","(service catalog)"],"labels":["@aws-cdk/aws-servicecatalog"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-servicediscovery)","(aws-servicediscovery)","(servicediscovery)","(service discovery)"],"labels":["@aws-cdk/aws-servicediscovery"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-ses)","(aws-ses)","(ses)"],"labels":["@aws-cdk/aws-ses"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-ses-actions)","(aws-ses-actions)","(ses-actions)","(ses actions)"],"labels":["@aws-cdk/aws-ses-actions"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-sns)","(aws-sns)","(sns)"],"labels":["@aws-cdk/aws-sns"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-sns-subscriptions)","(aws-sns-subscriptions)","(sns-subscriptions)","(sns subscriptions)"],"labels":["@aws-cdk/aws-sns-subscriptions"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-sqs)","(aws-sqs)","(sqs)"],"labels":["@aws-cdk/aws-sqs"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-ssm)","(aws-ssm)","(ssm)"],"labels":["@aws-cdk/aws-ssm"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["(@aws-cdk/aws-stepfunctions)","(aws-stepfunctions)","(stepfunctions)","(step functions)"],"labels":["@aws-cdk/aws-stepfunctions"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/aws-stepfunctions-tasks)","(aws-stepfunctions-tasks)","(stepfunctions-tasks)","(stepfunctions tasks)"],"labels":["@aws-cdk/aws-stepfunctions-tasks"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/aws-synthetics)","(aws-synthetics)","(synthetics)"],"labels":["@aws-cdk/aws-synthetics"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-transfer)","(aws-transfer)","(transfer)"],"labels":["@aws-cdk/aws-transfer"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-waf)","(aws-waf)","(waf)"],"labels":["@aws-cdk/aws-waf"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-wafregional)","(aws-wafregional)","(wafregional)","(waf regional)"],"labels":["@aws-cdk/aws-wafregional"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-wafv2)","(aws-wafv2)","(wafv2)","(waf v2)"],"labels":["@aws-cdk/aws-wafv2"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-workspaces)","(aws-workspaces)","(workspaces)"],"labels":["@aws-cdk/aws-workspaces"],"assignees":["NetaNir"]}, + {"keywords":["(@aws-cdk/cfnspec)","(cfnspec)","(cfn spec)"],"labels":["@aws-cdk/cfnspec"],"assignees":["eladb"]}, + {"keywords":["(@aws-cdk/cloud-assembly-schema)","(cloud-assembly-schema)","(cloud assembly schema)"],"labels":["@aws-cdk/cloud-assembly-schema"],"assignees":["eladb"]}, + {"keywords":["(@aws-cdk/cloudformation-diff)","(cloudformation-diff)","(cloudformation diff)","(cfn diff)"],"labels":["@aws-cdk/cloudformation-diff"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/cloudformation-include)","(cloudformation-include)","(cloudformation include)","(cfn include)"],"labels":["@aws-cdk/cloudformation-include"],"assignees":["skinny85"]}, + {"keywords":["(@aws-cdk/core)","(core)"],"labels":["@aws-cdk/core"],"assignees":["eladb"]}, + {"keywords":["(@aws-cdk/custom-resources)","(custom-resources)","(custom resources)"],"labels":["@aws-cdk/custom-resources"],"assignees":["eladb"]}, + {"keywords":["(@aws-cdk/cx-api)","(cx-api)","(cx api)"],"labels":["@aws-cdk/cx-api"],"assignees":["eladb"]}, + {"keywords":["(@aws-cdk/region-info)","(region-info)","(region info)"],"labels":["@aws-cdk/region-info"],"assignees":["RomainMuller"]} + ] + diff --git a/.mergify.yml b/.mergify.yml index 3e03c9a7533cf..8e19f26cd586a 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -6,7 +6,7 @@ pull_request_rules: label: add: [ contribution/core ] conditions: - - author~=^(eladb|RomainMuller|garnaat|nija-at|shivlaks|skinny85|rix0rrr|NGL321|Jerry-AWS|SomayaB|MrArnoldPalmer|NetaNir|iliapolo)$ + - author~=^(eladb|RomainMuller|garnaat|nija-at|shivlaks|skinny85|rix0rrr|NGL321|Jerry-AWS|SomayaB|MrArnoldPalmer|NetaNir|iliapolo|njlynch)$ - -label~="contribution/core" - name: automatic merge actions: @@ -66,20 +66,7 @@ pull_request_rules: conditions: - author!=dependabot[bot] - author!=dependabot-preview[bot] - # List out all the people whose work is okay to provisionally approve - - author!=eladb - - author!=RomainMuller - - author!=garnaat - - author!=nija-at - - author!=shivlaks - - author!=skinny85 - - author!=rix0rrr - - author!=NGL321 - - author!=Jerry-AWS - - author!=SomayaB - - author!=MrArnoldPalmer - - author!=NetaNir - - author!=iliapolo + - label!=contribution/core - base=master - -merged - -closed diff --git a/CHANGELOG.md b/CHANGELOG.md index dbb4c1ce8f5ea..7862a9041888f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,79 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.46.0](https://github.com/aws/aws-cdk/compare/v1.45.0...v1.46.0) (2020-06-19) + + +### ⚠ BREAKING CHANGES + +* **stepfunctions-tasks:** constructs for `EMR*` have been introduced to replace +previous implementation which implemented `IStepFUnctionsTask`. +* **stepfunctions-tasks:** `sizeInGB` property in `VolumeSpecification` has been renamed to `volumeSize` and is of type `cdk.Size` as we want to enable specifying any unit +* **stepfunctions-tasks:** `ebsRootVolumeSize` property in `EmrCreateCluster` is now of type `cdk.Size` as we want to enable specifying any unit +* **stepfunctions-tasks:** `Tags` in `EmrCreateCluster` type has changed from `cdk.CfnTag[]` to a map of string to string as we do not want to leak `Cfn` types +* **rds:** the attribute securityGroupId has been removed from IDatabaseCluster, +use cluster.connections.securityGroups instead +* **rds**: DatabaseClusterAttributes.securityGroup has been changed to securityGroups, and its type to an array +* **rds**: InstanceProps.securityGroup has been changed to securityGroups, and its type to an array +* **rds:** the property `engine` can no longer be passed when creating a DatabaseInstanceReadReplica +* **rds:** the property 'instanceClass' in DatabaseInstanceNewProps has been renamed to 'instanceType' +* **appsync:** Changes way of auth config even for existing supported methods viz., User Pools and API Key. + +### Features + +* **amplify:** add "404 (Rewrite)" RedirectStatus ([#7944](https://github.com/aws/aws-cdk/issues/7944)) ([21dda30](https://github.com/aws/aws-cdk/commit/21dda300fcf4fc67abc742cc6bb2ef61ea1cf7aa)) +* **amplify:** support for GitLab source code provider ([#8353](https://github.com/aws/aws-cdk/issues/8353)) ([f10da03](https://github.com/aws/aws-cdk/commit/f10da031ff3e6a07acc4000a321bfa8834fad77d)) +* **apigateway:** define Resources on imported RestApi ([#8270](https://github.com/aws/aws-cdk/issues/8270)) ([21a1de3](https://github.com/aws/aws-cdk/commit/21a1de308101a5f7e07558ff8c786f27e5235289)), closes [#7391](https://github.com/aws/aws-cdk/issues/7391) [#1477](https://github.com/aws/aws-cdk/issues/1477) [#7391](https://github.com/aws/aws-cdk/issues/7391) [#8347](https://github.com/aws/aws-cdk/issues/8347) +* **appsync:** add Construct for AppSync HTTP DataSource ([#8009](https://github.com/aws/aws-cdk/issues/8009)) ([0592b36](https://github.com/aws/aws-cdk/commit/0592b36f3949bddd9b6a367ac0df198da983b41e)), closes [#8007](https://github.com/aws/aws-cdk/issues/8007) +* **appsync:** enhances and completes auth config ([#7878](https://github.com/aws/aws-cdk/issues/7878)) ([6d7ce65](https://github.com/aws/aws-cdk/commit/6d7ce65ae969e53494920cad9b8913b9aef60838)) +* **autoscaling:** add instanceMonitoring option ([#8213](https://github.com/aws/aws-cdk/issues/8213)) ([6e23ae7](https://github.com/aws/aws-cdk/commit/6e23ae75184116953833ce93e87853fe9f933037)), closes [#8212](https://github.com/aws/aws-cdk/issues/8212) +* **awslint:** publish as an external module ([#8558](https://github.com/aws/aws-cdk/issues/8558)) ([378939c](https://github.com/aws/aws-cdk/commit/378939ca2a06910bf40267c314d9562388f9b3e7)) +* **cfn-include:** add support for all remaining CloudFormation functions except Fn::Sub ([#8591](https://github.com/aws/aws-cdk/issues/8591)) ([8d699c5](https://github.com/aws/aws-cdk/commit/8d699c5af8b617c2cebe85286299cd0eba67b567)), closes [#8590](https://github.com/aws/aws-cdk/issues/8590) +* **cfn-include:** add support for CreationPolicy and UpdatePolicy resource attributes ([#8457](https://github.com/aws/aws-cdk/issues/8457)) ([2fc5372](https://github.com/aws/aws-cdk/commit/2fc5372c437fd02b000a4b1f976e4999620ef4b5)) +* **cfnspec:** cloudformation spec v15.1.0 ([#8547](https://github.com/aws/aws-cdk/issues/8547)) ([50f4a21](https://github.com/aws/aws-cdk/commit/50f4a21f1b103910f029328d84347c5bfa0c7d56)) +* **cli:** allow disabling of Public Access Block Configuration on bootstrap Bucket ([#8171](https://github.com/aws/aws-cdk/issues/8171)) ([33f4746](https://github.com/aws/aws-cdk/commit/33f4746b3da9ccd5dbc2bcf879feabf05e52baf0)) +* **cli:** new deployment monitoring ([#8165](https://github.com/aws/aws-cdk/issues/8165)) ([f066c52](https://github.com/aws/aws-cdk/commit/f066c527dd5ad058b422bedc878833a21229c1cd)) +* **cloudtrail:** cloudtrail module is now stable! ([#8651](https://github.com/aws/aws-cdk/issues/8651)) ([835f375](https://github.com/aws/aws-cdk/commit/835f375ad5a88b236297b4501d7dd13f3437b530)) +* **cloudwatch:** liveData in GraphWidget ([#8579](https://github.com/aws/aws-cdk/issues/8579)) ([831092e](https://github.com/aws/aws-cdk/commit/831092eb05b3886affa968515043ddf68e3bbdd3)), closes [#8376](https://github.com/aws/aws-cdk/issues/8376) +* **cognito:** user pool - account recovery ([#8531](https://github.com/aws/aws-cdk/issues/8531)) ([1112abb](https://github.com/aws/aws-cdk/commit/1112abb74a6a69089bbf75702dc493901cbaa794)), closes [#8502](https://github.com/aws/aws-cdk/issues/8502) +* **cognito:** user pool - identity provider attribute mapping ([#8445](https://github.com/aws/aws-cdk/issues/8445)) ([1bd513b](https://github.com/aws/aws-cdk/commit/1bd513b605bfa7b5c2d5e2a1bdbf99aae00c271c)) +* **cognito:** user pool client - disable OAuth easily ([#8496](https://github.com/aws/aws-cdk/issues/8496)) ([f69cdfd](https://github.com/aws/aws-cdk/commit/f69cdfdcfcb95252fe44a312313e1f7b25fee50b)), closes [#8429](https://github.com/aws/aws-cdk/issues/8429) +* **logs:** MetricFilter exposes extracted Metric object ([#8556](https://github.com/aws/aws-cdk/issues/8556)) ([a35a53b](https://github.com/aws/aws-cdk/commit/a35a53b5acadca668c12aaea533af8d6360edac0)), closes [#1353](https://github.com/aws/aws-cdk/issues/1353) +* upgrade JSII to version 1.7.0 ([#8632](https://github.com/aws/aws-cdk/issues/8632)) ([1d26dbd](https://github.com/aws/aws-cdk/commit/1d26dbda134f0ff2b9ee34998bd702a893fdb5db)) +* **cognito:** user pools are now in developer preview ([#8522](https://github.com/aws/aws-cdk/issues/8522)) ([4fcad9a](https://github.com/aws/aws-cdk/commit/4fcad9ab771b772e6b157e3af19b158b18c34680)) +* **core,s3-assets:** custom bundling docker command ([#8481](https://github.com/aws/aws-cdk/issues/8481)) ([2a6d90c](https://github.com/aws/aws-cdk/commit/2a6d90cec248640251f43dda1ee4957ba5579c50)), closes [#8460](https://github.com/aws/aws-cdk/issues/8460) +* **ec2:** Add Step Functions interface endpoint ([#8512](https://github.com/aws/aws-cdk/issues/8512)) ([d21231f](https://github.com/aws/aws-cdk/commit/d21231f53a1c8096a70b70172531ee641fb6da85)) +* **efs:** removal policy on FileSystem ([#8593](https://github.com/aws/aws-cdk/issues/8593)) ([b17863b](https://github.com/aws/aws-cdk/commit/b17863b214d8f87d184fde9fb09a3ea9439f927d)) +* **eks:** expose cluster security group and encryption configuration ([#8317](https://github.com/aws/aws-cdk/issues/8317)) ([03e85eb](https://github.com/aws/aws-cdk/commit/03e85eb5629f87b34005422dfeb367d5581e85e8)), closes [#8276](https://github.com/aws/aws-cdk/issues/8276) [#8276](https://github.com/aws/aws-cdk/issues/8276) [#8236](https://github.com/aws/aws-cdk/issues/8236) +* **eks:** timeout option helm charts ([#8338](https://github.com/aws/aws-cdk/issues/8338)) ([d1403cc](https://github.com/aws/aws-cdk/commit/d1403cc9849fd4e20278a2a5d3d80855c7e16f72)), closes [#8215](https://github.com/aws/aws-cdk/issues/8215) +* **globalaccelerator:** support Accelerator, Listener and EndpointGroup ([#8221](https://github.com/aws/aws-cdk/issues/8221)) ([e4e8270](https://github.com/aws/aws-cdk/commit/e4e827044848d858d63371b092b8b382e9624266)), closes [#5527](https://github.com/aws/aws-cdk/issues/5527) +* **kms:** import an Alias by name ([#8299](https://github.com/aws/aws-cdk/issues/8299)) ([4611e69](https://github.com/aws/aws-cdk/commit/4611e690014204d0895045d75e8821f3de3e9470)), closes [#5953](https://github.com/aws/aws-cdk/issues/5953) +* **lambda:** configurable retries for log retention custom resource ([#8258](https://github.com/aws/aws-cdk/issues/8258)) ([e17a49a](https://github.com/aws/aws-cdk/commit/e17a49aa7e6e4e42c78edccc8ed1bac09d75ab01)), closes [#8257](https://github.com/aws/aws-cdk/issues/8257) +* **rds:** multiple security groups in Cluster and Instance ([#8510](https://github.com/aws/aws-cdk/issues/8510)) ([31925c1](https://github.com/aws/aws-cdk/commit/31925c1916f4570de0f3bbe5be40f639a3d6eafd)) +* **sns-subscriptions:** Add support for SMS subscriptions ([#8582](https://github.com/aws/aws-cdk/issues/8582)) ([82d8f11](https://github.com/aws/aws-cdk/commit/82d8f11842b26dd2dd5ffa2591157d38a642636a)), closes [#7882](https://github.com/aws/aws-cdk/issues/7882) + + +### Bug Fixes + +* **apigateway:** deployment fails when domain name has uppercase letters ([#8456](https://github.com/aws/aws-cdk/issues/8456)) ([1e6a8e9](https://github.com/aws/aws-cdk/commit/1e6a8e99d1d14fe0c68fd84392385f347aeb7be6)), closes [#8428](https://github.com/aws/aws-cdk/issues/8428) +* **appsync:** don't mix the json result with setting variables ([#8290](https://github.com/aws/aws-cdk/issues/8290)) ([7ca74e0](https://github.com/aws/aws-cdk/commit/7ca74e08a92f21cbefe3cdb231fd63105ca80a74)), closes [#7026](https://github.com/aws/aws-cdk/issues/7026) +* **autoscaling:** can't configure notificationTypes ([#8294](https://github.com/aws/aws-cdk/issues/8294)) ([01ef1ca](https://github.com/aws/aws-cdk/commit/01ef1ca9818b2bd9f219de04ce2ec657de4e2149)) +* **cli:** bootstrapping cannot be retried ([#8577](https://github.com/aws/aws-cdk/issues/8577)) ([cad6649](https://github.com/aws/aws-cdk/commit/cad66499aa9944ab088d87987c9e82aafd841319)) +* **cloudtrail:** Invalid arn partition for GovCloud ([#8248](https://github.com/aws/aws-cdk/issues/8248)) ([5189170](https://github.com/aws/aws-cdk/commit/5189170e15d2a93c617891232ae75f070877269d)), closes [#8247](https://github.com/aws/aws-cdk/issues/8247) +* **core:** asset bundling runs as root ([#8492](https://github.com/aws/aws-cdk/issues/8492)) ([6df546f](https://github.com/aws/aws-cdk/commit/6df546f24d237a4985d1870497e3de41b394a1c1)), closes [#8489](https://github.com/aws/aws-cdk/issues/8489) +* **core:** asset staging custom hash generates invalid file names ([#8521](https://github.com/aws/aws-cdk/issues/8521)) ([4521ae3](https://github.com/aws/aws-cdk/commit/4521ae3734f7e76c864d2b883fc290091b5fcf3d)), closes [#8513](https://github.com/aws/aws-cdk/issues/8513) +* **core:** cannot use container assets with new-style synthesis ([#8575](https://github.com/aws/aws-cdk/issues/8575)) ([357d5f7](https://github.com/aws/aws-cdk/commit/357d5f7a20a3e9d7860e12afae9e494fe7ed7f3c)), closes [#8540](https://github.com/aws/aws-cdk/issues/8540) +* **core:** incorrect temp directory when bundling assets ([#8469](https://github.com/aws/aws-cdk/issues/8469)) ([9dc2e04](https://github.com/aws/aws-cdk/commit/9dc2e04c34379a4806566657115d53299eb51db1)), closes [#8465](https://github.com/aws/aws-cdk/issues/8465) +* **core:** s3-deployments don't work with new bootstrap stack ([#8578](https://github.com/aws/aws-cdk/issues/8578)) ([b2006c3](https://github.com/aws/aws-cdk/commit/b2006c3efc4eab6a9b5d984a148586eb9fa7b410)), closes [#8541](https://github.com/aws/aws-cdk/issues/8541) +* **ec2:** can't set natGateways=0 using reserved private subnets ([#8407](https://github.com/aws/aws-cdk/issues/8407)) ([d7bf724](https://github.com/aws/aws-cdk/commit/d7bf72452e588f1f474debda5d8e2a8a85e71430)), closes [#8203](https://github.com/aws/aws-cdk/issues/8203) +* **eks:** can't define a cluster with multiple Fargate profiles ([#8374](https://github.com/aws/aws-cdk/issues/8374)) ([1e78a68](https://github.com/aws/aws-cdk/commit/1e78a68d0a4968a649990a7e15df24881d690de2)), closes [#6084](https://github.com/aws/aws-cdk/issues/6084) +* **eks:** fargate profile deployment fails with missing iam:PassRole ([#8548](https://github.com/aws/aws-cdk/issues/8548)) ([d6190f2](https://github.com/aws/aws-cdk/commit/d6190f23b7cc17b005de6295cc2c35c703054b44)), closes [#8546](https://github.com/aws/aws-cdk/issues/8546) +* **eks:** fargate profile role not added to aws-auth by the cdk ([#8447](https://github.com/aws/aws-cdk/issues/8447)) ([f656ea7](https://github.com/aws/aws-cdk/commit/f656ea7926f593811ea1df224636015a5c820f7a)), closes [#7981](https://github.com/aws/aws-cdk/issues/7981) +* **elbv2:** allow non-TCP protocols in NLB TargetGroup ([#8525](https://github.com/aws/aws-cdk/issues/8525)) ([387c1a8](https://github.com/aws/aws-cdk/commit/387c1a8d495e7f0e51fe3bacd132f43aa34c341e)) +* **rds:** 'engine' is no longer required in DatabaseInstanceReadReplica ([#8509](https://github.com/aws/aws-cdk/issues/8509)) ([86d84e6](https://github.com/aws/aws-cdk/commit/86d84e6d592b2bee110ca0fd9890ce32e46055c3)) +* **rds:** rename 'instanceClass' in DatabaseInstance to 'instanceType' ([#8507](https://github.com/aws/aws-cdk/issues/8507)) ([e35cb1a](https://github.com/aws/aws-cdk/commit/e35cb1a7355606180c20dad56fa4ca0ea6652bf7)) +* **secretsmanager:** rotation function name can exceed 64 chars ([#7896](https://github.com/aws/aws-cdk/issues/7896)) ([24e474b](https://github.com/aws/aws-cdk/commit/24e474b68ceada06271194a122e0bcdbd41e6c31)), closes [#7885](https://github.com/aws/aws-cdk/issues/7885), [#8442](https://github.com/aws/aws-cdk/issues/8442) + ## [1.45.0](https://github.com/aws/aws-cdk/compare/v1.44.0...v1.45.0) (2020-06-09) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 810267a9ab428..edaef65689c87 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -184,6 +184,17 @@ Integration tests perform a few functions in the CDK code base - 3. (Optionally) Acts as a way to validate that constructs set up the CloudFormation resources as expected. A successful CloudFormation deployment does not mean that the resources are set up correctly. +For Gitpod users only! The best way to supply CDK with your AWS credentials is to add them as +[persisting environment variables](https://www.gitpod.io/docs/environment-variables). +Adding them works as follows via terminal: + +```shell +eval $(gp env -e AWS_ACCESS_KEY_ID=XXXXXXXXX) +eval $(gp env -e AWS_SECRET_ACCESS_KEY=YYYYYYY) +eval $(gp env -e AWS_DEFAULT_REGION=ZZZZZZZZ) +eval $(gp env -e) +``` + If you are working on a new feature that is using previously unused CloudFormation resource types, or involves configuring resource types across services, you need to write integration tests that use these resource types or features. diff --git a/design/cdk-bootstrap.md b/design/cdk-bootstrap.md index 718f475b51426..d2a3434ef5059 100644 --- a/design/cdk-bootstrap.md +++ b/design/cdk-bootstrap.md @@ -102,6 +102,9 @@ and need to be kept for backwards compatibility reasons: * `--bootstrap-kms-key-id`: optional identifier of the KMS key used for encrypting the file assets S3 bucket. +* `--public-access-block-configuration`: allows you to explicitly enable or disable public access bucket block configuration + on the file assets S3 Bucket (enabled by default). + #### New options These options will be added to the `bootstrap` command: diff --git a/lerna.json b/lerna.json index 95b92c6940487..cc8251a51f0a9 100644 --- a/lerna.json +++ b/lerna.json @@ -10,5 +10,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.45.0" + "version": "1.46.0" } diff --git a/package.json b/package.json index 199c117aa6641..1f8cda209553d 100644 --- a/package.json +++ b/package.json @@ -16,13 +16,13 @@ "devDependencies": { "conventional-changelog-cli": "^2.0.34", "fs-extra": "^9.0.1", - "jsii-diff": "^1.6.0", - "jsii-pacmak": "^1.6.0", - "jsii-rosetta": "^1.6.0", - "lerna": "^3.22.0", - "standard-version": "^8.0.0", "graceful-fs": "^4.2.4", - "typescript": "~3.8.3" + "jsii-diff": "^1.7.0", + "jsii-pacmak": "^1.7.0", + "jsii-rosetta": "^1.7.0", + "lerna": "^3.22.1", + "standard-version": "^8.0.0", + "typescript": "~3.9.5" }, "resolutions-comment": "should be removed or reviewed when nodeunit dependency is dropped or adjusted", "resolutions": { @@ -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/lib/assertions/have-resource.ts b/packages/@aws-cdk/assert/lib/assertions/have-resource.ts index 3676f06352068..c44a4b176e6fd 100644 --- a/packages/@aws-cdk/assert/lib/assertions/have-resource.ts +++ b/packages/@aws-cdk/assert/lib/assertions/have-resource.ts @@ -313,7 +313,7 @@ export function arrayWith(...elements: any[]): PropertyMatcher { const ret = (value: any, inspection: InspectionFailure): boolean => { if (!Array.isArray(value)) { - return failMatcher(inspection, `Expect an object but got '${typeof value}'`); + return failMatcher(inspection, `Expect an array but got '${typeof value}'`); } for (const element of elements) { @@ -412,4 +412,4 @@ function isCallable(x: any): x is ((...args: any[]) => any) { function isObject(x: any): x is object { // Because `typeof null === 'object'`. return x && typeof x === 'object'; -} \ No newline at end of file +} 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/README.md b/packages/@aws-cdk/aws-amplify/README.md index f6e9b17e71627..9a6ec9b6e48f0 100644 --- a/packages/@aws-cdk/aws-amplify/README.md +++ b/packages/@aws-cdk/aws-amplify/README.md @@ -52,6 +52,17 @@ const amplifyApp = new amplify.App(this, 'MyApp', { }); ``` +To connect your `App` to GitLab, use the `GitLabSourceCodeProvider`: +```ts +const amplifyApp = new amplify.App(this, 'MyApp', { + sourceCodeProvider: new amplify.GitLabSourceCodeProvider({ + owner: '', + repository: '', + oauthToken: cdk.SecretValue.secretsManager('my-gitlab-token') + }) +}); +``` + To connect your `App` to CodeCommit, use the `CodeCommitSourceCodeProvider`: ```ts const repository = new codecommit.Repository(this, 'Repo', { 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-amplify/lib/source-code-providers.ts b/packages/@aws-cdk/aws-amplify/lib/source-code-providers.ts index 1b280d49e6170..8736c76ff7649 100644 --- a/packages/@aws-cdk/aws-amplify/lib/source-code-providers.ts +++ b/packages/@aws-cdk/aws-amplify/lib/source-code-providers.ts @@ -36,6 +36,40 @@ export class GitHubSourceCodeProvider implements ISourceCodeProvider { } } +/** + * Properties for a GitLab source code provider + */ +export interface GitLabSourceCodeProviderProps { + /** + * The user or organization owning the repository + */ + readonly owner: string; + + /** + * The name of the repository + */ + readonly repository: string; + + /** + * A personal access token with the `repo` scope + */ + readonly oauthToken: SecretValue; +} + +/** + * GitLab source code provider + */ +export class GitLabSourceCodeProvider implements ISourceCodeProvider { + constructor(private readonly props: GitLabSourceCodeProviderProps) { } + + public bind(_app: App): SourceCodeProviderConfig { + return { + repository: `https://gitlab.com/${this.props.owner}/${this.props.repository}`, + oauthToken: this.props.oauthToken, + }; + } +} + /** * Properties for a CodeCommit source code provider */ diff --git a/packages/@aws-cdk/aws-amplify/test/app.test.ts b/packages/@aws-cdk/aws-amplify/test/app.test.ts index b5c9a3b3a7942..5af765cae6d75 100644 --- a/packages/@aws-cdk/aws-amplify/test/app.test.ts +++ b/packages/@aws-cdk/aws-amplify/test/app.test.ts @@ -61,6 +61,58 @@ test('create an app connected to a GitHub repository', () => { }); }); +test('create an app connected to a GitLab repository', () => { + // WHEN + new amplify.App(stack, 'App', { + sourceCodeProvider: new amplify.GitLabSourceCodeProvider({ + owner: 'aws', + repository: 'aws-cdk', + oauthToken: SecretValue.plainText('secret'), + }), + buildSpec: codebuild.BuildSpec.fromObject({ + version: '1.0', + frontend: { + phases: { + build: { + commands: [ + 'npm run build', + ], + }, + }, + }, + }), + }); + + // THEN + expect(stack).toHaveResource('AWS::Amplify::App', { + Name: 'App', + BuildSpec: '{\n \"version\": \"1.0\",\n \"frontend\": {\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"npm run build\"\n ]\n }\n }\n }\n}', + IAMServiceRole: { + 'Fn::GetAtt': [ + 'AppRole1AF9B530', + 'Arn', + ], + }, + OauthToken: 'secret', + Repository: 'https://gitlab.com/aws/aws-cdk', + }); + + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'amplify.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }); +}); + test('create an app connected to a CodeCommit repository', () => { // WHEN new amplify.App(stack, 'App', { 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/authorizers/integ.request-authorizer.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.expected.json index 89ab550818465..53cfbe6135685 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersa4fe33e0257e9c62ac536e63b53e36b44b0791e1715f23fcb34c0e6a16402771S3Bucket3AA102C2" + "Ref": "AssetParameterse7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0S3Bucket316B0B52" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersa4fe33e0257e9c62ac536e63b53e36b44b0791e1715f23fcb34c0e6a16402771S3VersionKeyA8B9EEA1" + "Ref": "AssetParameterse7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0S3VersionKey4A4C6C19" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersa4fe33e0257e9c62ac536e63b53e36b44b0791e1715f23fcb34c0e6a16402771S3VersionKeyA8B9EEA1" + "Ref": "AssetParameterse7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0S3VersionKey4A4C6C19" } ] } @@ -272,17 +272,17 @@ } }, "Parameters": { - "AssetParametersa4fe33e0257e9c62ac536e63b53e36b44b0791e1715f23fcb34c0e6a16402771S3Bucket3AA102C2": { + "AssetParameterse7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0S3Bucket316B0B52": { "Type": "String", - "Description": "S3 bucket for asset \"a4fe33e0257e9c62ac536e63b53e36b44b0791e1715f23fcb34c0e6a16402771\"" + "Description": "S3 bucket for asset \"e7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0\"" }, - "AssetParametersa4fe33e0257e9c62ac536e63b53e36b44b0791e1715f23fcb34c0e6a16402771S3VersionKeyA8B9EEA1": { + "AssetParameterse7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0S3VersionKey4A4C6C19": { "Type": "String", - "Description": "S3 key for asset version \"a4fe33e0257e9c62ac536e63b53e36b44b0791e1715f23fcb34c0e6a16402771\"" + "Description": "S3 key for asset version \"e7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0\"" }, - "AssetParametersa4fe33e0257e9c62ac536e63b53e36b44b0791e1715f23fcb34c0e6a16402771ArtifactHash6ECBBD25": { + "AssetParameterse7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0ArtifactHash2FE6C4D8": { "Type": "String", - "Description": "Artifact hash for asset \"a4fe33e0257e9c62ac536e63b53e36b44b0791e1715f23fcb34c0e6a16402771\"" + "Description": "Artifact hash for asset \"e7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0\"" } }, "Outputs": { @@ -313,4 +313,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json index 339f10a1d17e0..4850214d48c82 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54S3BucketC3061470" + "Ref": "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3Bucket4E51347F" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54S3VersionKey6C903EB9" + "Ref": "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3VersionKey707D1166" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54S3VersionKey6C903EB9" + "Ref": "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3VersionKey707D1166" } ] } @@ -281,17 +281,17 @@ } }, "Parameters": { - "AssetParametersef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54S3BucketC3061470": { + "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3Bucket4E51347F": { "Type": "String", - "Description": "S3 bucket for asset \"ef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54\"" + "Description": "S3 bucket for asset \"4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181e\"" }, - "AssetParametersef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54S3VersionKey6C903EB9": { + "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3VersionKey707D1166": { "Type": "String", - "Description": "S3 key for asset version \"ef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54\"" + "Description": "S3 key for asset version \"4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181e\"" }, - "AssetParametersef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54ArtifactHashB5C070D4": { + "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eArtifactHash267391ED": { "Type": "String", - "Description": "Artifact hash for asset \"ef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54\"" + "Description": "Artifact hash for asset \"4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181e\"" } }, "Outputs": { @@ -322,4 +322,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.expected.json index 0d4f784d0362d..4a18847f20311 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54S3BucketC3061470" + "Ref": "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3Bucket4E51347F" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54S3VersionKey6C903EB9" + "Ref": "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3VersionKey707D1166" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54S3VersionKey6C903EB9" + "Ref": "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3VersionKey707D1166" } ] } @@ -272,17 +272,17 @@ } }, "Parameters": { - "AssetParametersef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54S3BucketC3061470": { + "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3Bucket4E51347F": { "Type": "String", - "Description": "S3 bucket for asset \"ef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54\"" + "Description": "S3 bucket for asset \"4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181e\"" }, - "AssetParametersef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54S3VersionKey6C903EB9": { + "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3VersionKey707D1166": { "Type": "String", - "Description": "S3 key for asset version \"ef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54\"" + "Description": "S3 key for asset version \"4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181e\"" }, - "AssetParametersef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54ArtifactHashB5C070D4": { + "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eArtifactHash267391ED": { "Type": "String", - "Description": "Artifact hash for asset \"ef05e22c21f4e7103ee216cfd0417e73da848ef2f9505b3b2f1611bb3ff5bf54\"" + "Description": "Artifact hash for asset \"4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181e\"" } }, "Outputs": { @@ -313,4 +313,4 @@ } } } -} \ No newline at end of file +} 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 5e978b796045f..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! } ``` @@ -75,13 +76,16 @@ export class ApiStack extends Stack { }, authorizationConfig: { defaultAuthorization: { - userPool, - defaultAction: UserPoolDefaultAction.ALLOW, + authorizationType: AuthorizationType.USER_POOL, + userPoolConfig: { + userPool, + defaultAction: UserPoolDefaultAction.ALLOW + }, }, additionalAuthorizationModes: [ { - apiKeyDesc: 'My API Key', - }, + authorizationType: AuthorizationType.API_KEY, + } ], }, schemaDefinitionFile: './schema.graphql', @@ -154,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 b1da44a40bee0..2d4f802f72048 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -1,15 +1,74 @@ import { IUserPool } from '@aws-cdk/aws-cognito'; import { Table } from '@aws-cdk/aws-dynamodb'; -import { IGrantable, IPrincipal, IRole, ManagedPolicy, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; +import { + IGrantable, + IPrincipal, + IRole, + ManagedPolicy, + Role, + ServicePrincipal, +} from '@aws-cdk/aws-iam'; import { IFunction } from '@aws-cdk/aws-lambda'; import { Construct, Duration, IResolvable } from '@aws-cdk/core'; import { readFileSync } from 'fs'; -import { CfnApiKey, CfnDataSource, CfnGraphQLApi, CfnGraphQLSchema, CfnResolver } from './appsync.generated'; +import { + CfnApiKey, + CfnDataSource, + CfnGraphQLApi, + CfnGraphQLSchema, + CfnResolver, +} from './appsync.generated'; /** - * Marker interface for the different authorization modes. + * enum with all possible values for AppSync authorization type */ -export interface AuthMode { } +export enum AuthorizationType { + /** + * API Key authorization type + */ + API_KEY = 'API_KEY', + /** + * AWS IAM authorization type. Can be used with Cognito Identity Pool federated credentials + */ + IAM = 'AWS_IAM', + /** + * Cognito User Pool authorization type + */ + USER_POOL = 'AMAZON_COGNITO_USER_POOLS', + /** + * OpenID Connect authorization type + */ + OIDC = 'OPENID_CONNECT', +} + +/** + * Interface to specify default or additional authorization(s) + */ +export interface AuthorizationMode { + /** + * One of possible four values AppSync supports + * + * @see https://docs.aws.amazon.com/appsync/latest/devguide/security.html + * + * @default - `AuthorizationType.API_KEY` + */ + readonly authorizationType: AuthorizationType; + /** + * If authorizationType is `AuthorizationType.USER_POOL`, this option is required. + * @default - none + */ + readonly userPoolConfig?: UserPoolConfig; + /** + * If authorizationType is `AuthorizationType.API_KEY`, this option can be configured. + * @default - check default values of `ApiKeyConfig` memebers + */ + readonly apiKeyConfig?: ApiKeyConfig; + /** + * If authorizationType is `AuthorizationType.OIDC`, this option is required. + * @default - none + */ + readonly openIdConnectConfig?: OpenIdConnectConfig; +} /** * enum with all possible values for Cognito user-pool default actions @@ -28,8 +87,7 @@ export enum UserPoolDefaultAction { /** * Configuration for Cognito user-pools in AppSync */ -export interface UserPoolConfig extends AuthMode { - +export interface UserPoolConfig { /** * The Cognito user pool to use as identity source */ @@ -48,18 +106,20 @@ export interface UserPoolConfig extends AuthMode { readonly defaultAction?: UserPoolDefaultAction; } -function isUserPoolConfig(obj: unknown): obj is UserPoolConfig { - return (obj as UserPoolConfig).userPool !== undefined; -} - /** * Configuration for API Key authorization in AppSync */ -export interface ApiKeyConfig extends AuthMode { +export interface ApiKeyConfig { /** - * Unique description of the API key + * Unique name of the API Key + * @default - 'DefaultAPIKey' */ - readonly apiKeyDesc: string; + readonly name?: string; + /** + * Description of API key + * @default - 'Default API Key created by CDK' + */ + readonly description?: string; /** * The time from creation time after which the API key expires, using RFC3339 representation. @@ -70,8 +130,33 @@ export interface ApiKeyConfig extends AuthMode { readonly expires?: string; } -function isApiKeyConfig(obj: unknown): obj is ApiKeyConfig { - return (obj as ApiKeyConfig).apiKeyDesc !== undefined; +/** + * Configuration for OpenID Connect authorization in AppSync + */ +export interface OpenIdConnectConfig { + /** + * The number of milliseconds an OIDC token is valid after being authenticated by OIDC provider. + * `auth_time` claim in OIDC token is required for this validation to work. + * @default - no validation + */ + readonly tokenExpiryFromAuth?: number; + /** + * The number of milliseconds an OIDC token is valid after being issued to a user. + * This validation uses `iat` claim of OIDC token. + * @default - no validation + */ + readonly tokenExpiryFromIssue?: number; + /** + * The client identifier of the Relying party at the OpenID identity provider. + * A regular expression can be specified so AppSync can validate against multiple client identifiers at a time. + * @example - 'ABCD|CDEF' where ABCD and CDEF are two different clientId + * @default - * (All) + */ + readonly clientId?: string; + /** + * The issuer for the OIDC configuration. The issuer returned by discovery must exactly match the value of `iss` in the OIDC token. + */ + readonly oidcProvider: string; } /** @@ -83,14 +168,14 @@ export interface AuthorizationConfig { * * @default - API Key authorization */ - readonly defaultAuthorization?: AuthMode; + readonly defaultAuthorization?: AuthorizationMode; /** * Additional authorization modes * * @default - No other modes */ - readonly additionalAuthorizationModes?: [AuthMode] + readonly additionalAuthorizationModes?: AuthorizationMode[]; } /** @@ -206,22 +291,56 @@ export class GraphQLApi extends Construct { constructor(scope: Construct, id: string, props: GraphQLApiProps) { super(scope, id); + this.validateAuthorizationProps(props); + const defaultAuthorizationType = + props.authorizationConfig?.defaultAuthorization?.authorizationType || + AuthorizationType.API_KEY; + let apiLogsRole; if (props.logConfig) { - apiLogsRole = new Role(this, 'ApiLogsRole', { assumedBy: new ServicePrincipal('appsync') }); - apiLogsRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSAppSyncPushToCloudWatchLogs')); + apiLogsRole = new Role(this, 'ApiLogsRole', { + assumedBy: new ServicePrincipal('appsync'), + }); + apiLogsRole.addManagedPolicy( + ManagedPolicy.fromAwsManagedPolicyName( + 'service-role/AWSAppSyncPushToCloudWatchLogs', + ), + ); } this.api = new CfnGraphQLApi(this, 'Resource', { name: props.name, - authenticationType: 'API_KEY', - ...props.logConfig && { + authenticationType: defaultAuthorizationType, + ...(props.logConfig && { logConfig: { cloudWatchLogsRoleArn: apiLogsRole ? apiLogsRole.roleArn : undefined, excludeVerboseContent: props.logConfig.excludeVerboseContent, - fieldLogLevel: props.logConfig.fieldLogLevel ? props.logConfig.fieldLogLevel.toString() : undefined, + fieldLogLevel: props.logConfig.fieldLogLevel + ? props.logConfig.fieldLogLevel.toString() + : undefined, }, - }, + }), + openIdConnectConfig: + props.authorizationConfig?.defaultAuthorization?.authorizationType === + AuthorizationType.OIDC + ? this.formatOpenIdConnectConfig( + props.authorizationConfig.defaultAuthorization + .openIdConnectConfig!, + ) + : undefined, + userPoolConfig: + props.authorizationConfig?.defaultAuthorization?.authorizationType === + AuthorizationType.USER_POOL + ? this.formatUserPoolConfig( + props.authorizationConfig.defaultAuthorization.userPoolConfig!, + ) + : undefined, + additionalAuthenticationProviders: props.authorizationConfig + ?.additionalAuthorizationModes!.length + ? this.formatAdditionalAuthorizationModes( + props.authorizationConfig!.additionalAuthorizationModes!, + ) + : undefined, }); this.apiId = this.api.attrApiId; @@ -229,8 +348,18 @@ export class GraphQLApi extends Construct { this.graphQlUrl = this.api.attrGraphQlUrl; this.name = this.api.name; - if (props.authorizationConfig) { - this.setupAuth(props.authorizationConfig); + if ( + defaultAuthorizationType === AuthorizationType.API_KEY || + props.authorizationConfig?.additionalAuthorizationModes?.findIndex( + (authMode) => authMode.authorizationType === AuthorizationType.API_KEY + ) !== -1 + ) { + const apiKeyConfig: ApiKeyConfig = props.authorizationConfig + ?.defaultAuthorization?.apiKeyConfig || { + name: 'DefaultAPIKey', + description: 'Default API Key created by CDK', + }; + this.createAPIKey(apiKeyConfig); } let definition; @@ -266,7 +395,11 @@ export class GraphQLApi extends Construct { * @param description The description of the data source * @param table The DynamoDB table backing this data source [disable-awslint:ref-via-interface] */ - public addDynamoDbDataSource(name: string, description: string, table: Table): DynamoDbDataSource { + public addDynamoDbDataSource( + name: string, + description: string, + table: Table, + ): DynamoDbDataSource { return new DynamoDbDataSource(this, `${name}DS`, { api: this, description, @@ -275,13 +408,32 @@ 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 * @param description The description of the data source * @param lambdaFunction The Lambda function to call to interact with this data source */ - public addLambdaDataSource(name: string, description: string, lambdaFunction: IFunction): LambdaDataSource { + public addLambdaDataSource( + name: string, + description: string, + lambdaFunction: IFunction, + ): LambdaDataSource { return new LambdaDataSource(this, `${name}DS`, { api: this, description, @@ -290,55 +442,132 @@ export class GraphQLApi extends Construct { }); } - private setupAuth(auth: AuthorizationConfig) { - if (isUserPoolConfig(auth.defaultAuthorization)) { - const { authenticationType, userPoolConfig } = this.userPoolDescFrom(auth.defaultAuthorization); - this.api.authenticationType = authenticationType; - this.api.userPoolConfig = userPoolConfig; - } else if (isApiKeyConfig(auth.defaultAuthorization)) { - this.api.authenticationType = this.apiKeyDesc(auth.defaultAuthorization).authenticationType; + private validateAuthorizationProps(props: GraphQLApiProps) { + const defaultAuthorizationType = + props.authorizationConfig?.defaultAuthorization?.authorizationType || + AuthorizationType.API_KEY; + + if ( + defaultAuthorizationType === AuthorizationType.OIDC && + !props.authorizationConfig?.defaultAuthorization?.openIdConnectConfig + ) { + throw new Error('Missing default OIDC Configuration'); } - this.api.additionalAuthenticationProviders = []; - for (const mode of (auth.additionalAuthorizationModes || [])) { - if (isUserPoolConfig(mode)) { - this.api.additionalAuthenticationProviders.push(this.userPoolDescFrom(mode)); - } else if (isApiKeyConfig(mode)) { - this.api.additionalAuthenticationProviders.push(this.apiKeyDesc(mode)); - } + if ( + defaultAuthorizationType === AuthorizationType.USER_POOL && + !props.authorizationConfig?.defaultAuthorization?.userPoolConfig + ) { + throw new Error('Missing default User Pool Configuration'); + } + + if (props.authorizationConfig?.additionalAuthorizationModes) { + props.authorizationConfig.additionalAuthorizationModes.forEach( + (authorizationMode) => { + if ( + authorizationMode.authorizationType === AuthorizationType.API_KEY && + defaultAuthorizationType === AuthorizationType.API_KEY + ) { + throw new Error( + "You can't duplicate API_KEY in additional authorization config. See https://docs.aws.amazon.com/appsync/latest/devguide/security.html", + ); + } + + if ( + authorizationMode.authorizationType === AuthorizationType.IAM && + defaultAuthorizationType === AuthorizationType.IAM + ) { + throw new Error( + "You can't duplicate IAM in additional authorization config. See https://docs.aws.amazon.com/appsync/latest/devguide/security.html", + ); + } + + if ( + authorizationMode.authorizationType === AuthorizationType.OIDC && + !authorizationMode.openIdConnectConfig + ) { + throw new Error( + 'Missing OIDC Configuration inside an additional authorization mode', + ); + } + + if ( + authorizationMode.authorizationType === + AuthorizationType.USER_POOL && + !authorizationMode.userPoolConfig + ) { + throw new Error( + 'Missing User Pool Configuration inside an additional authorization mode', + ); + } + }, + ); } } - private userPoolDescFrom(upConfig: UserPoolConfig): { authenticationType: string; userPoolConfig: CfnGraphQLApi.UserPoolConfigProperty } { + private formatOpenIdConnectConfig( + config: OpenIdConnectConfig, + ): CfnGraphQLApi.OpenIDConnectConfigProperty { return { - authenticationType: 'AMAZON_COGNITO_USER_POOLS', - userPoolConfig: { - appIdClientRegex: upConfig.appIdClientRegex, - userPoolId: upConfig.userPool.userPoolId, - awsRegion: upConfig.userPool.stack.region, - defaultAction: upConfig.defaultAction ? upConfig.defaultAction.toString() : 'ALLOW', - }, + authTtl: config.tokenExpiryFromAuth, + clientId: config.clientId, + iatTtl: config.tokenExpiryFromIssue, + issuer: config.oidcProvider, + }; + } + + private formatUserPoolConfig( + config: UserPoolConfig, + ): CfnGraphQLApi.UserPoolConfigProperty { + return { + userPoolId: config.userPool.userPoolId, + awsRegion: config.userPool.stack.region, + appIdClientRegex: config.appIdClientRegex, + defaultAction: config.defaultAction || 'ALLOW', }; } - private apiKeyDesc(akConfig: ApiKeyConfig): { authenticationType: string } { + private createAPIKey(config: ApiKeyConfig) { let expires: number | undefined; - if (akConfig.expires) { - expires = new Date(akConfig.expires).valueOf(); - const now = Date.now(); - const days = (d: number) => now + Duration.days(d).toMilliseconds(); + if (config.expires) { + expires = new Date(config.expires).valueOf(); + const days = (d: number) => + Date.now() + Duration.days(d).toMilliseconds(); if (expires < days(1) || expires > days(365)) { throw Error('API key expiration must be between 1 and 365 days.'); } expires = Math.round(expires / 1000); } - const key = new CfnApiKey(this, `${akConfig.apiKeyDesc || ''}ApiKey`, { + const key = new CfnApiKey(this, `${config.name || 'DefaultAPIKey'}ApiKey`, { expires, - description: akConfig.apiKeyDesc, + description: config.description || 'Default API Key created by CDK', apiId: this.apiId, }); this._apiKey = key.attrApiKey; - return { authenticationType: 'API_KEY' }; + } + + private formatAdditionalAuthorizationModes( + authModes: AuthorizationMode[], + ): CfnGraphQLApi.AdditionalAuthenticationProviderProperty[] { + return authModes.reduce< + CfnGraphQLApi.AdditionalAuthenticationProviderProperty[] + >( + (acc, authMode) => [ + ...acc, + { + authenticationType: authMode.authorizationType, + userPoolConfig: + authMode.authorizationType === AuthorizationType.USER_POOL + ? this.formatUserPoolConfig(authMode.userPoolConfig!) + : undefined, + openIdConnectConfig: + authMode.authorizationType === AuthorizationType.OIDC + ? this.formatOpenIdConnectConfig(authMode.openIdConnectConfig!) + : undefined, + }, + ], + [], + ); } } @@ -537,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 */ @@ -772,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}))`; } } @@ -843,8 +1096,8 @@ export class PrimaryKey { assignments.push(this.skey.renderAsAssignment()); } return `"key" : { - ${assignments.join(',')} - }`; + ${assignments.join(',')} + }`; } } @@ -878,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)'; } } @@ -1002,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 f51065c3287c2..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 }, @@ -85,28 +91,22 @@ } } }, - "ApiMyAPIKeyApiKeyACDEE2CC": { + "ApiDefaultAPIKeyApiKey74F5313B": { "Type": "AWS::AppSync::ApiKey", "Properties": { "ApiId": { - "Fn::GetAtt": [ - "ApiF70053CD", - "ApiId" - ] + "Fn::GetAtt": ["ApiF70053CD", "ApiId"] }, - "Description": "My API Key" + "Description": "Default API Key created by CDK" } }, "ApiSchema510EECD7": { "Type": "AWS::AppSync::GraphQLSchema", "Properties": { "ApiId": { - "Fn::GetAtt": [ - "ApiF70053CD", - "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": { @@ -286,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": [ @@ -307,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": [ @@ -328,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": [ @@ -591,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": { @@ -640,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 a04b33bcdb000..fb2be9eeac531 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts @@ -2,7 +2,15 @@ import { UserPool } from '@aws-cdk/aws-cognito'; import { AttributeType, BillingMode, Table } from '@aws-cdk/aws-dynamodb'; import { App, RemovalPolicy, Stack } from '@aws-cdk/core'; import { join } from 'path'; -import { GraphQLApi, KeyCondition, MappingTemplate, PrimaryKey, UserPoolDefaultAction, Values } from '../lib'; +import { + AuthorizationType, + GraphQLApi, + KeyCondition, + MappingTemplate, + PrimaryKey, + UserPoolDefaultAction, + Values, +} from '../lib'; const app = new App(); const stack = new Stack(app, 'aws-appsync-integ'); @@ -16,14 +24,15 @@ const api = new GraphQLApi(stack, 'Api', { schemaDefinitionFile: join(__dirname, 'schema.graphql'), authorizationConfig: { defaultAuthorization: { - userPool, - defaultAction: UserPoolDefaultAction.ALLOW, + authorizationType: AuthorizationType.USER_POOL, + userPoolConfig: { + userPool, + defaultAction: UserPoolDefaultAction.ALLOW, + }, }, additionalAuthorizationModes: [ { - apiKeyDesc: 'My API Key', - // Can't specify a date because it will inevitably be in the past. - // expires: '2019-02-05T12:00:00Z', + authorizationType: AuthorizationType.API_KEY, }, ], }, @@ -139,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-autoscaling/README.md b/packages/@aws-cdk/aws-autoscaling/README.md index 260d8d0e0b693..ce55c411671f7 100644 --- a/packages/@aws-cdk/aws-autoscaling/README.md +++ b/packages/@aws-cdk/aws-autoscaling/README.md @@ -224,6 +224,11 @@ To enable the max instance lifetime support, specify `maxInstanceLifetime` prope for the `AutoscalingGroup` resource. The value must be between 7 and 365 days(inclusive). To clear a previously set value, just leave this property undefinied. +### Instance Monitoring + +To disable detailed instance monitoring, specify `instanceMonitoring` property +for the `AutoscalingGroup` resource as `Monitoring.BASIC`. Otherwise detailed monitoring +will be enabled. ### Future work diff --git a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts index 7e84e7620bbe4..31483d53d40a6 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -21,6 +21,21 @@ import { BlockDevice, BlockDeviceVolume, EbsDeviceVolumeType } from './volume'; */ const NAME_TAG: string = 'Name'; +/** + * The monitoring mode for instances launched in an autoscaling group + */ +export enum Monitoring { + /** + * Generates metrics every 5 minutes + */ + BASIC, + + /** + * Generates metrics every minute + */ + DETAILED, +} + /** * Basic properties of an AutoScalingGroup, except the exact machines to run and where they should run * @@ -71,9 +86,17 @@ export interface CommonAutoScalingGroupProps { * SNS topic to send notifications about fleet changes * * @default - No fleet change notifications will be sent. + * @deprecated use `notifications` */ readonly notificationsTopic?: sns.ITopic; + /** + * Configure autoscaling group to send notifications about fleet changes to an SNS topic(s) + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html#cfn-as-group-notificationconfigurations + * @default - No fleet change notifications will be sent. + */ + readonly notifications?: NotificationConfiguration[]; + /** * Whether the instances can initiate connections to anywhere by default * @@ -199,6 +222,18 @@ export interface CommonAutoScalingGroupProps { * @default none */ readonly maxInstanceLifetime?: Duration; + + /** + * Controls whether instances in this group are launched with detailed or basic monitoring. + * + * When detailed monitoring is enabled, Amazon CloudWatch generates metrics every minute and your account + * is charged a fee. When you disable detailed monitoring, CloudWatch generates metrics every 5 minutes. + * + * @see https://docs.aws.amazon.com/autoscaling/latest/userguide/as-instance-monitoring.html#enable-as-instance-metrics + * + * @default - Monitoring.DETAILED + */ + readonly instanceMonitoring?: Monitoring; } /** @@ -435,6 +470,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements private readonly securityGroups: ec2.ISecurityGroup[] = []; private readonly loadBalancerNames: string[] = []; private readonly targetGroupArns: string[] = []; + private readonly notifications: NotificationConfiguration[] = []; constructor(scope: Construct, id: string, props: AutoScalingGroupProps) { super(scope, id); @@ -468,6 +504,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements imageId: imageConfig.imageId, keyName: props.keyName, instanceType: props.instanceType.toString(), + instanceMonitoring: (props.instanceMonitoring !== undefined ? (props.instanceMonitoring === Monitoring.DETAILED) : undefined), securityGroups: securityGroupsToken, iamInstanceProfile: iamProfile.ref, userData: userDataToken, @@ -513,6 +550,23 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements throw new Error('maxInstanceLifetime must be between 7 and 365 days (inclusive)'); } + if (props.notificationsTopic && props.notifications) { + throw new Error('Cannot set \'notificationsTopic\' and \'notifications\', \'notificationsTopic\' is deprecated use \'notifications\' instead'); + } + + if (props.notificationsTopic) { + this.notifications = [{ + topic: props.notificationsTopic, + }]; + } + + if (props.notifications) { + this.notifications = props.notifications.map(nc => ({ + topic: nc.topic, + scalingEvents: nc.scalingEvents ?? ScalingEvents.ALL, + })); + } + const { subnetIds, hasPublic } = props.vpc.selectSubnets(props.vpcSubnets); const asgProps: CfnAutoScalingGroupProps = { cooldown: props.cooldown !== undefined ? props.cooldown.toSeconds().toString() : undefined, @@ -522,17 +576,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements launchConfigurationName: launchConfig.ref, loadBalancerNames: Lazy.listValue({ produce: () => this.loadBalancerNames }, { omitEmpty: true }), targetGroupArns: Lazy.listValue({ produce: () => this.targetGroupArns }, { omitEmpty: true }), - notificationConfigurations: !props.notificationsTopic ? undefined : [ - { - topicArn: props.notificationsTopic.topicArn, - notificationTypes: [ - 'autoscaling:EC2_INSTANCE_LAUNCH', - 'autoscaling:EC2_INSTANCE_LAUNCH_ERROR', - 'autoscaling:EC2_INSTANCE_TERMINATE', - 'autoscaling:EC2_INSTANCE_TERMINATE_ERROR', - ], - }, - ], + notificationConfigurations: this.renderNotificationConfiguration(), vpcZoneIdentifier: subnetIds, healthCheckType: props.healthCheck && props.healthCheck.type, healthCheckGracePeriod: props.healthCheck && props.healthCheck.gracePeriod && props.healthCheck.gracePeriod.toSeconds(), @@ -667,6 +711,17 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements }; } } + + private renderNotificationConfiguration(): CfnAutoScalingGroup.NotificationConfigurationProperty[] | undefined { + if (this.notifications.length === 0) { + return undefined; + } + + return this.notifications.map(notification => ({ + topicArn: notification.topic.topicArn, + notificationTypes: notification.scalingEvents ? notification.scalingEvents._types : ScalingEvents.ALL._types, + })); + } } /** @@ -691,6 +746,53 @@ export enum UpdateType { ROLLING_UPDATE = 'RollingUpdate', } +/** + * AutoScalingGroup fleet change notifications configurations. + * You can configure AutoScaling to send an SNS notification whenever your Auto Scaling group scales. + */ +export interface NotificationConfiguration { + /** + * SNS topic to send notifications about fleet scaling events + */ + readonly topic: sns.ITopic; + + /** + * Which fleet scaling events triggers a notification + * @default ScalingEvents.ALL + */ + readonly scalingEvents?: ScalingEvents; +} + +/** + * Fleet scaling events + */ +export enum ScalingEvent { + /** + * Notify when an instance was launced + */ + INSTANCE_LAUNCH = 'autoscaling:EC2_INSTANCE_LAUNCH', + + /** + * Notify when an instance was terminated + */ + INSTANCE_TERMINATE = 'autoscaling:EC2_INSTANCE_TERMINATE', + + /** + * Notify when an instance failed to terminate + */ + INSTANCE_TERMINATE_ERROR = 'autoscaling:EC2_INSTANCE_TERMINATE_ERROR', + + /** + * Notify when an instance failed to launch + */ + INSTANCE_LAUNCH_ERROR = 'autoscaling:EC2_INSTANCE_LAUNCH_ERROR', + + /** + * Send a test notification to the topic + */ + TEST_NOTIFICATION = 'autoscaling:TEST_NOTIFICATION' +} + /** * Additional settings when a rolling update is selected */ @@ -766,6 +868,39 @@ export interface RollingUpdateConfiguration { readonly suspendProcesses?: ScalingProcess[]; } +/** + * A list of ScalingEvents, you can use one of the predefined lists, such as ScalingEvents.ERRORS + * or create a custome group by instantiating a `NotificationTypes` object, e.g: `new NotificationTypes(`NotificationType.INSTANCE_LAUNCH`)`. + */ +export class ScalingEvents { + /** + * Fleet scaling errors + */ + public static readonly ERRORS = new ScalingEvents(ScalingEvent.INSTANCE_LAUNCH_ERROR, ScalingEvent.INSTANCE_TERMINATE_ERROR); + + /** + * All fleet scaling events + */ + public static readonly ALL = new ScalingEvents(ScalingEvent.INSTANCE_LAUNCH, + ScalingEvent.INSTANCE_LAUNCH_ERROR, + ScalingEvent.INSTANCE_TERMINATE, + ScalingEvent.INSTANCE_TERMINATE_ERROR); + + /** + * Fleet scaling launch events + */ + public static readonly LAUNCH_EVENTS = new ScalingEvents(ScalingEvent.INSTANCE_LAUNCH, ScalingEvent.INSTANCE_LAUNCH_ERROR); + + /** + * @internal + */ + public readonly _types: ScalingEvent[]; + + constructor(...types: ScalingEvent[]) { + this._types = types; + } +} + export enum ScalingProcess { LAUNCH = 'Launch', TERMINATE = 'Terminate', diff --git a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts index 5ee4d7bff64dd..faea5fe7cd8f9 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts @@ -2,6 +2,7 @@ import { ABSENT, expect, haveResource, haveResourceLike, InspectionFailure, Reso import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; +import * as sns from '@aws-cdk/aws-sns'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; @@ -830,6 +831,45 @@ export = { test.done(); }, + 'can configure instance monitoring'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = mockVpc(stack); + + // WHEN + new autoscaling.AutoScalingGroup(stack, 'MyStack', { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage(), + vpc, + instanceMonitoring: autoscaling.Monitoring.BASIC, + }); + + // THEN + expect(stack).to(haveResource('AWS::AutoScaling::LaunchConfiguration', { + InstanceMonitoring: false, + })); + test.done(); + }, + + 'instance monitoring defaults to absent'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = mockVpc(stack); + + // WHEN + new autoscaling.AutoScalingGroup(stack, 'MyStack', { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage(), + vpc, + }); + + // THEN + expect(stack).to(haveResource('AWS::AutoScaling::LaunchConfiguration', { + InstanceMonitoring: ABSENT, + })); + test.done(); + }, + 'throws if ephemeral volumeIndex < 0'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -1026,6 +1066,144 @@ export = { test.done(); }, + 'throw if notification and notificationsTopics are both configured'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = mockVpc(stack); + const topic = new sns.Topic(stack, 'MyTopic'); + + // THEN + test.throws(() => { + new autoscaling.AutoScalingGroup(stack, 'MyASG', { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage(), + vpc, + notificationsTopic: topic, + notifications: [{ + topic, + }], + }); + }, 'Can not set notificationsTopic and notifications, notificationsTopic is deprected use notifications instead'); + test.done(); + }, + + 'allow configuring notifications'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = mockVpc(stack); + const topic = new sns.Topic(stack, 'MyTopic'); + + // WHEN + new autoscaling.AutoScalingGroup(stack, 'MyASG', { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage(), + vpc, + notifications: [ + { + topic, + scalingEvents: autoscaling.ScalingEvents.ERRORS, + }, + { + topic, + scalingEvents: new autoscaling.ScalingEvents(autoscaling.ScalingEvent.INSTANCE_TERMINATE), + }, + ], + }); + + // THEN + expect(stack).to(haveResource('AWS::AutoScaling::AutoScalingGroup', { + NotificationConfigurations : [ + { + TopicARN : { Ref : 'MyTopic86869434' }, + NotificationTypes : [ + 'autoscaling:EC2_INSTANCE_LAUNCH_ERROR', + 'autoscaling:EC2_INSTANCE_TERMINATE_ERROR', + ], + }, + { + TopicARN : { Ref : 'MyTopic86869434' }, + NotificationTypes : [ + 'autoscaling:EC2_INSTANCE_TERMINATE', + ], + }, + ]}, + )); + + test.done(); + }, + + 'notificationTypes default includes all non test NotificationType'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = mockVpc(stack); + const topic = new sns.Topic(stack, 'MyTopic'); + + // WHEN + new autoscaling.AutoScalingGroup(stack, 'MyASG', { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage(), + vpc, + notifications: [ + { + topic, + }, + ], + }); + + // THEN + expect(stack).to(haveResource('AWS::AutoScaling::AutoScalingGroup', { + NotificationConfigurations : [ + { + TopicARN : { Ref : 'MyTopic86869434' }, + NotificationTypes : [ + 'autoscaling:EC2_INSTANCE_LAUNCH', + 'autoscaling:EC2_INSTANCE_LAUNCH_ERROR', + 'autoscaling:EC2_INSTANCE_TERMINATE', + 'autoscaling:EC2_INSTANCE_TERMINATE_ERROR', + ], + }, + ]}, + )); + + test.done(); + }, + + 'setting notificationTopic configures all non test NotificationType'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = mockVpc(stack); + const topic = new sns.Topic(stack, 'MyTopic'); + + // WHEN + new autoscaling.AutoScalingGroup(stack, 'MyASG', { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage(), + vpc, + notificationsTopic: topic, + }); + + // THEN + expect(stack).to(haveResource('AWS::AutoScaling::AutoScalingGroup', { + NotificationConfigurations : [ + { + TopicARN : { Ref : 'MyTopic86869434' }, + NotificationTypes : [ + 'autoscaling:EC2_INSTANCE_LAUNCH', + 'autoscaling:EC2_INSTANCE_LAUNCH_ERROR', + 'autoscaling:EC2_INSTANCE_TERMINATE', + 'autoscaling:EC2_INSTANCE_TERMINATE_ERROR', + ], + }, + ]}, + )); + + test.done(); + }, + + 'NotificationTypes.ALL includes all non test NotificationType'(test: Test) { + test.deepEqual(Object.values(autoscaling.ScalingEvent).length - 1, autoscaling.ScalingEvents.ALL._types.length); + test.done(); + }, }; function mockVpc(stack: cdk.Stack) { 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-cloudformation/test/integ.core-custom-resources.expected.json b/packages/@aws-cdk/aws-cloudformation/test/integ.core-custom-resources.expected.json index 5e3d50351b9d3..23c4884db164f 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.core-custom-resources.expected.json +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.core-custom-resources.expected.json @@ -27,7 +27,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersd46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7aS3Bucket1D703CB8" + "Ref": "AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226S3Bucket8B4D0E9E" }, "S3Key": { "Fn::Join": [ @@ -40,7 +40,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersd46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7aS3VersionKey01A97AE3" + "Ref": "AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226S3VersionKeyDECB34FE" } ] } @@ -53,7 +53,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersd46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7aS3VersionKey01A97AE3" + "Ref": "AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226S3VersionKeyDECB34FE" } ] } @@ -98,17 +98,17 @@ } }, "Parameters": { - "AssetParametersd46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7aS3Bucket1D703CB8": { + "AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226S3Bucket8B4D0E9E": { "Type": "String", - "Description": "S3 bucket for asset \"d46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7a\"" + "Description": "S3 bucket for asset \"925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226\"" }, - "AssetParametersd46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7aS3VersionKey01A97AE3": { + "AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226S3VersionKeyDECB34FE": { "Type": "String", - "Description": "S3 key for asset version \"d46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7a\"" + "Description": "S3 key for asset version \"925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226\"" }, - "AssetParametersd46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7aArtifactHash16A571C9": { + "AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226ArtifactHashEEC400F2": { "Type": "String", - "Description": "Artifact hash for asset \"d46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7a\"" + "Description": "Artifact hash for asset \"925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226\"" } }, "Outputs": { @@ -134,4 +134,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-nested-export-to-sibling.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-nested-export-to-sibling.ts index d3f6263e64c4f..8d99bb75fc8af 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-nested-export-to-sibling.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-nested-export-to-sibling.ts @@ -1,4 +1,4 @@ -/// !cdk-integ Stack2 +/// !cdk-integ Stack1 Stack2 import * as sns from '@aws-cdk/aws-sns'; import { App, Fn, Stack } from '@aws-cdk/core'; 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/README.md b/packages/@aws-cdk/aws-cloudtrail/README.md index 541c926cab6fb..358eb9c5a796b 100644 --- a/packages/@aws-cdk/aws-cloudtrail/README.md +++ b/packages/@aws-cdk/aws-cloudtrail/README.md @@ -4,11 +4,7 @@ ![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) -> All classes with the `Cfn` prefix in this module ([CFN Resources](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. +![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge) --- diff --git a/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts b/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts index 3b3f39d64eb4c..a802c32d79d9f 100644 --- a/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts +++ b/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts @@ -79,11 +79,17 @@ export interface TrailProps { readonly cloudWatchLogGroup?: logs.ILogGroup; /** The AWS Key Management Service (AWS KMS) key ID that you want to use to encrypt CloudTrail logs. - * * @default - No encryption. + * @deprecated - use encryptionKey instead. */ readonly kmsKey?: kms.IKey; + /** The AWS Key Management Service (AWS KMS) key ID that you want to use to encrypt CloudTrail logs. + * + * @default - No encryption. + */ + readonly encryptionKey?: kms.IKey; + /** SNS topic that is notified when new log files are published. * * @default - No notifications. @@ -254,6 +260,10 @@ export class Trail extends Resource { this.eventSelectors.push(managementEvent); } + if (props.kmsKey && props.encryptionKey) { + throw new Error('Both kmsKey and encryptionKey must not be specified. Use only encryptionKey'); + } + // TODO: not all regions support validation. Use service configuration data to fail gracefully const trail = new CfnTrail(this, 'Resource', { isLogging: true, @@ -261,7 +271,7 @@ export class Trail extends Resource { isMultiRegionTrail: props.isMultiRegionTrail == null ? true : props.isMultiRegionTrail, includeGlobalServiceEvents: props.includeGlobalServiceEvents == null ? true : props.includeGlobalServiceEvents, trailName: this.physicalName, - kmsKeyId: props.kmsKey && props.kmsKey.keyArn, + kmsKeyId: props.encryptionKey?.keyArn ?? props.kmsKey?.keyArn, s3BucketName: this.s3bucket.bucketName, s3KeyPrefix: props.s3KeyPrefix, cloudWatchLogsLogGroupArn: this.logGroup?.logGroupArn, @@ -345,7 +355,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 +382,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..3fc5308269de3 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", @@ -103,8 +103,8 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, - "stability": "experimental", - "maturity": "experimental", + "stability": "stable", + "maturity": "stable", "awscdkio": { "announce": false } diff --git a/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts b/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts index 50c2b766bb4c3..cf0766ad43032 100644 --- a/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts +++ b/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts @@ -1,6 +1,7 @@ import { ABSENT, SynthUtils } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; import * as lambda from '@aws-cdk/aws-lambda'; import { LogGroup, RetentionDays } from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; @@ -156,6 +157,44 @@ describe('cloudtrail', () => { }); }); + test('encryption keys', () => { + const stack = new Stack(); + const key = new kms.Key(stack, 'key'); + new Trail(stack, 'EncryptionKeyTrail', { + trailName: 'EncryptionKeyTrail', + encryptionKey: key, + }); + new Trail(stack, 'KmsKeyTrail', { + trailName: 'KmsKeyTrail', + kmsKey: key, + }); + new Trail(stack, 'UnencryptedTrail', { + trailName: 'UnencryptedTrail', + }); + expect(() => new Trail(stack, 'ErrorTrail', { + trailName: 'ErrorTrail', + encryptionKey: key, + kmsKey: key, + })).toThrow(/Both kmsKey and encryptionKey must not be specified/); + + expect(stack).toHaveResource('AWS::CloudTrail::Trail', { + TrailName: 'EncryptionKeyTrail', + KMSKeyId: { + 'Fn::GetAtt': [ 'keyFEDD6EC0', 'Arn' ], + }, + }); + expect(stack).toHaveResource('AWS::CloudTrail::Trail', { + TrailName: 'KmsKeyTrail', + KMSKeyId: { + 'Fn::GetAtt': [ 'keyFEDD6EC0', 'Arn' ], + }, + }); + expect(stack).toHaveResource('AWS::CloudTrail::Trail', { + TrailName: 'UnencryptedTrail', + KMSKeyId: ABSENT, + }); + }); + describe('with cloud watch logs', () => { test('enabled', () => { const stack = getTestStack(); @@ -257,7 +296,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 +383,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 +456,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-cloudwatch/README.md b/packages/@aws-cdk/aws-cloudwatch/README.md index 94959b6e8de80..e1eca96157a15 100644 --- a/packages/@aws-cdk/aws-cloudwatch/README.md +++ b/packages/@aws-cdk/aws-cloudwatch/README.md @@ -252,6 +252,17 @@ dashboard.addWidgets(new GraphWidget({ })); ``` +The graph can publish live data within the last minute that has not been fully aggregated. + +```ts +dashboard.addWidgets(new GraphWidget({ + // ... + // ... + + liveData: true, +})); +``` + ### Alarm widget An alarm widget shows the graph and the alarm line of a single alarm: diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts b/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts index 0c98e288c34ca..52b8f7a03687f 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts @@ -180,6 +180,13 @@ export interface GraphWidgetProps extends MetricWidgetProps { * @default - bottom */ readonly legendPosition?: LegendPosition; + + /** + * Whether the graph should show live data + * + * @default false + */ + readonly liveData?: boolean; } /** @@ -218,6 +225,7 @@ export class GraphWidget extends ConcreteWidget { right: this.props.rightYAxis !== undefined ? this.props.rightYAxis : undefined, }, legend: this.props.legendPosition !== undefined ? { position: this.props.legendPosition } : undefined, + liveData: this.props.liveData, }, }]; } diff --git a/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts b/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts index 9e4724eed05c9..e533b0fba304e 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts @@ -303,6 +303,37 @@ export = { test.done(); }, + 'specify liveData property on graph'(test: Test) { + // WHEN + const stack = new Stack(); + const widget = new GraphWidget({ + title: 'My live graph', + left: [ + new Metric({ namespace: 'CDK', metricName: 'Test' }), + ], + liveData: true, + }); + + // THEN + test.deepEqual(stack.resolve(widget.toJson()), [{ + type: 'metric', + width: 6, + height: 6, + properties: { + view: 'timeSeries', + title: 'My live graph', + region: { Ref: 'AWS::Region' }, + metrics: [ + ['CDK', 'Test'], + ], + liveData: true, + yAxis: {}, + }, + }]); + + test.done(); + }, + 'can use imported alarm with graph'(test: Test) { // GIVEN const stack = new Stack(); 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 2cca87a32e726..4674fb7570ef8 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -2,17 +2,18 @@ --- -![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) +| Features | Stability | +| --- | --- | +| CFN Resources | ![Stable](https://img.shields.io/badge/stable-success.svg?style=for-the-badge) | +| Higher level constructs for User Pools | ![Developer Preview](https://img.shields.io/badge/developer--preview-informational.svg?style=for-the-badge) | +| Higher level constructs for Identity Pools | ![Not Implemented](https://img.shields.io/badge/not--implemented-black.svg?style=for-the-badge) | -> 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. +> **CFN Resources:** 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. +> **Developer Preview:** Higher level constructs in this module that are marked as developer preview have completed their phase of active development and are looking for adoption and feedback. While the same caveats around non-backward compatible as Experimental constructs apply, they will undergo fewer breaking changes. Just as with Experimental constructs, these are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. --- - [Amazon Cognito](https://docs.aws.amazon.com/cognito/latest/developerguide/what-is-amazon-cognito.html) provides authentication, authorization, and user management for your web and mobile apps. Your users can sign in directly with a user name and password, or through a third party such as Facebook, Amazon, Google or Apple. @@ -33,6 +34,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 +270,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 @@ -367,9 +381,26 @@ const provider = new UserPoolIdentityProviderAmazon(stack, 'Amazon', { }); ``` -In order to allow users to sign in with a third-party identity provider, the app client that faces the user should be -configured to use the identity provider. See [App Clients](#app-clients) section to know more about App Clients. -The identity providers should be configured on `identityProviders` property available on the `UserPoolClient` construct. +Attribute mapping allows mapping attributes provided by the third-party identity providers to [standard and custom +attributes](#Attributes) of the user pool. Learn more about [Specifying Identity Provider Attribute Mappings for Your +User Pool](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-specifying-attribute-mapping.html). + +The following code shows how different attributes provided by 'Login With Amazon' can be mapped to standard and custom +user pool attributes. + +```ts +new UserPoolIdentityProviderAmazon(stack, 'Amazon', { + // ... + attributeMapping: { + email: ProviderAttribute.AMAZON_EMAIL, + website: ProviderAttribute.other('url'), // use other() when an attribute is not pre-defined in the CDK + custom: { + // custom user pool attributes go here + uniqueId: ProviderAttribute.AMAZON_USER_ID, + } + } +}); +``` ### App Clients @@ -456,7 +487,7 @@ pool.addClient('app-client', { All identity providers created in the CDK app are automatically registered into the corresponding user pool. All app clients created in the CDK have all of the identity providers enabled by default. The 'Cognito' identity provider, -that allows users to register and sign in directly with the Cognito user pool, is also enabled by default. +that allows users to register and sign in directly with the Cognito user pool, is also enabled by default. Alternatively, the list of supported identity providers for a client can be explicitly specified - ```ts @@ -528,3 +559,4 @@ const signInUrl = domain.signInUrl(client, { redirectUrl: 'https://myapp.com/home', // must be a URL configured under 'callbackUrls' with the client }) ``` + diff --git a/packages/@aws-cdk/aws-cognito/lib/private/attr-names.ts b/packages/@aws-cdk/aws-cognito/lib/private/attr-names.ts new file mode 100644 index 0000000000000..1f0891cec1704 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/lib/private/attr-names.ts @@ -0,0 +1,19 @@ +export const StandardAttributeNames = { + address: 'address', + birthdate: 'birthdate', + email: 'email', + familyName: 'family_name', + gender: 'gender', + givenName: 'given_name', + locale: 'locale', + middleName: 'middle_name', + fullname: 'name', + nickname: 'nickname', + phoneNumber: 'phone_number', + profilePicture: 'picture', + preferredUsername: 'preferred_username', + profilePage: 'profile', + timezone: 'zoneinfo', + lastUpdateTime: 'updated_at', + website: 'website', +}; \ No newline at end of file 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-idps/amazon.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/amazon.ts index d5f4fd5402609..04d5098b7f83a 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/amazon.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/amazon.ts @@ -45,6 +45,7 @@ export class UserPoolIdentityProviderAmazon extends UserPoolIdentityProviderBase client_secret: props.clientSecret, authorize_scopes: scopes.join(' '), }, + attributeMapping: super.configureAttributeMapping(), }); this.providerName = super.getResourceNameAttribute(resource.ref); diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/base.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/base.ts index b95ffd106a285..8be81c88334be 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/base.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/base.ts @@ -1,7 +1,169 @@ import { Construct, Resource } from '@aws-cdk/core'; +import { StandardAttributeNames } from '../private/attr-names'; import { IUserPool } from '../user-pool'; import { IUserPoolIdentityProvider } from '../user-pool-idp'; +/** + * An attribute available from a third party identity provider. + */ +export class ProviderAttribute { + /** The user id attribute provided by Amazon */ + public static readonly AMAZON_USER_ID = new ProviderAttribute('user_id'); + /** The email attribute provided by Amazon */ + public static readonly AMAZON_EMAIL = new ProviderAttribute('email'); + /** The name attribute provided by Amazon */ + public static readonly AMAZON_NAME = new ProviderAttribute('name'); + /** The postal code attribute provided by Amazon */ + public static readonly AMAZON_POSTAL_CODE = new ProviderAttribute('postal_code'); + + /** The user id attribute provided by Facebook */ + public static readonly FACEBOOK_ID = new ProviderAttribute('id'); + /** The birthday attribute provided by Facebook */ + public static readonly FACEBOOK_BIRTHDAY = new ProviderAttribute('birthday'); + /** The email attribute provided by Facebook */ + public static readonly FACEBOOK_EMAIL = new ProviderAttribute('email'); + /** The name attribute provided by Facebook */ + public static readonly FACEBOOK_NAME = new ProviderAttribute('name'); + /** The first name attribute provided by Facebook */ + public static readonly FACEBOOK_FIRST_NAME = new ProviderAttribute('first_name'); + /** The last name attribute provided by Facebook */ + public static readonly FACEBOOK_LAST_NAME = new ProviderAttribute('last_name'); + /** The middle name attribute provided by Facebook */ + public static readonly FACEBOOK_MIDDLE_NAME = new ProviderAttribute('middle_name'); + /** The gender attribute provided by Facebook */ + public static readonly FACEBOOK_GENDER = new ProviderAttribute('gender'); + /** The locale attribute provided by Facebook */ + public static readonly FACEBOOK_LOCALE = new ProviderAttribute('locale'); + + /** + * Use this to specify an attribute from the identity provider that is not pre-defined in the CDK. + * @param attributeName the attribute value string as recognized by the provider + */ + public static other(attributeName: string): ProviderAttribute { + return new ProviderAttribute(attributeName); + } + + /** The attribute value string as recognized by the provider. */ + public readonly attributeName: string; + + private constructor(attributeName: string) { + this.attributeName = attributeName; + } +} + +/** + * The mapping of user pool attributes to the attributes provided by the identity providers. + */ +export interface AttributeMapping { + /** + * The user's postal address is a required attribute. + * @default - not mapped + */ + readonly address?: ProviderAttribute; + + /** + * The user's birthday. + * @default - not mapped + */ + readonly birthdate?: ProviderAttribute; + + /** + * The user's e-mail address. + * @default - not mapped + */ + readonly email?: ProviderAttribute; + + /** + * The surname or last name of user. + * @default - not mapped + */ + readonly familyName?: ProviderAttribute; + + /** + * The user's gender. + * @default - not mapped + */ + readonly gender?: ProviderAttribute; + + /** + * The user's first name or give name. + * @default - not mapped + */ + readonly givenName?: ProviderAttribute; + + /** + * The user's locale. + * @default - not mapped + */ + readonly locale?: ProviderAttribute; + + /** + * The user's middle name. + * @default - not mapped + */ + readonly middleName?: ProviderAttribute; + + /** + * The user's full name in displayable form. + * @default - not mapped + */ + readonly fullname?: ProviderAttribute; + + /** + * The user's nickname or casual name. + * @default - not mapped + */ + readonly nickname?: ProviderAttribute; + + /** + * The user's telephone number. + * @default - not mapped + */ + readonly phoneNumber?: ProviderAttribute; + + /** + * The URL to the user's profile picture. + * @default - not mapped + */ + readonly profilePicture?: ProviderAttribute; + + /** + * The user's preferred username. + * @default - not mapped + */ + readonly preferredUsername?: ProviderAttribute; + + /** + * The URL to the user's profile page. + * @default - not mapped + */ + readonly profilePage?: ProviderAttribute; + + /** + * The user's time zone. + * @default - not mapped + */ + readonly timezone?: ProviderAttribute; + + /** + * Time, the user's information was last updated. + * @default - not mapped + */ + readonly lastUpdateTime?: ProviderAttribute; + + /** + * The URL to the user's web page or blog. + * @default - not mapped + */ + readonly website?: ProviderAttribute; + + /** + * Specify custom attribute mapping here and mapping for any standard attributes not supported yet. + * @default - no custom attribute mapping + */ + readonly custom?: { [key: string]: ProviderAttribute }; +} + /** * Properties to create a new instance of UserPoolIdentityProvider */ @@ -10,6 +172,12 @@ export interface UserPoolIdentityProviderProps { * The user pool to which this construct provides identities. */ readonly userPool: IUserPool; + + /** + * Mapping attributes from the identity provider to standard and custom attributes of the user pool. + * @default - no attribute mapping + */ + readonly attributeMapping?: AttributeMapping; } /** @@ -18,8 +186,28 @@ export interface UserPoolIdentityProviderProps { export abstract class UserPoolIdentityProviderBase extends Resource implements IUserPoolIdentityProvider { public abstract readonly providerName: string; - public constructor(scope: Construct, id: string, props: UserPoolIdentityProviderProps) { + public constructor(scope: Construct, id: string, private readonly props: UserPoolIdentityProviderProps) { super(scope, id); props.userPool.registerIdentityProvider(this); } + + protected configureAttributeMapping(): any { + if (!this.props.attributeMapping) { + return undefined; + } + type SansCustom = Omit; + let mapping: { [key: string]: string } = {}; + mapping = Object.entries(this.props.attributeMapping) + .filter(([k, _]) => k !== 'custom') // 'custom' handled later separately + .reduce((agg, [k, v]) => { + return { ...agg, [StandardAttributeNames[k as keyof SansCustom]]: v.attributeName }; + }, mapping); + if (this.props.attributeMapping.custom) { + mapping = Object.entries(this.props.attributeMapping.custom).reduce((agg, [k, v]) => { + return { ...agg, [k]: v.attributeName }; + }, mapping); + } + if (Object.keys(mapping).length === 0) { return undefined; } + return mapping; + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/facebook.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/facebook.ts index d404c40965575..fee333011ffe6 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/facebook.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/facebook.ts @@ -50,6 +50,7 @@ export class UserPoolIdentityProviderFacebook extends UserPoolIdentityProviderBa authorize_scopes: scopes.join(','), api_version: props.apiVersion, }, + attributeMapping: super.configureAttributeMapping(), }); this.providerName = super.getResourceNameAttribute(resource.ref); diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index 4f7b29a22e325..6ff23e96dde5d 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -2,6 +2,7 @@ import { IRole, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from ' import * as lambda from '@aws-cdk/aws-lambda'; import { Construct, Duration, IResource, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; import { CfnUserPool } from './cognito.generated'; +import { StandardAttributeNames } from './private/attr-names'; import { ICustomAttribute, StandardAttribute, StandardAttributes } from './user-pool-attr'; import { UserPoolClient, UserPoolClientOptions } from './user-pool-client'; import { UserPoolDomain, UserPoolDomainOptions } from './user-pool-domain'; @@ -384,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 */ @@ -508,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; } /** @@ -621,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); @@ -682,6 +731,7 @@ export class UserPool extends UserPoolBase { usernameConfiguration: undefinedIfNoKeys({ caseSensitive: props.signInCaseSensitive, }), + accountRecoverySetting: this.accountRecovery(props), }); this.userPoolId = userPool.ref; @@ -907,27 +957,43 @@ export class UserPool extends UserPoolBase { } return schema; } -} -const StandardAttributeNames: Record = { - address: 'address', - birthdate: 'birthdate', - email: 'email', - familyName: 'family_name', - gender: 'gender', - givenName: 'given_name', - locale: 'locale', - middleName: 'middle_name', - fullname: 'name', - nickname: 'nickname', - phoneNumber: 'phone_number', - profilePicture: 'picture', - preferredUsername: 'preferred_username', - profilePage: 'profile', - timezone: 'zoneinfo', - lastUpdateTime: 'updated_at', - website: 'website', -}; + 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 { const allUndefined = Object.values(struct).reduce((acc, v) => acc && (v === undefined), true); diff --git a/packages/@aws-cdk/aws-cognito/package.json b/packages/@aws-cdk/aws-cognito/package.json index fee82c6b6c883..5a72c3d09f551 100644 --- a/packages/@aws-cdk/aws-cognito/package.json +++ b/packages/@aws-cdk/aws-cognito/package.json @@ -102,7 +102,16 @@ ] }, "stability": "experimental", - "maturity": "experimental", + "features": [ + { + "name": "Higher level constructs for User Pools", + "stability": "Developer Preview" + }, + { + "name": "Higher level constructs for Identity Pools", + "stability": "Not Implemented" + } + ], "awscdkio": { "announce": false } 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..511d200ed5d90 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 }, @@ -170,7 +176,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bS3BucketA3488101" + "Ref": "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4S3BucketC6CBC09E" }, "S3Key": { "Fn::Join": [ @@ -183,7 +189,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bS3VersionKey23A2E46C" + "Ref": "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4S3VersionKeyB194AB23" } ] } @@ -196,7 +202,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bS3VersionKey23A2E46C" + "Ref": "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4S3VersionKeyB194AB23" } ] } @@ -238,17 +244,17 @@ } }, "Parameters": { - "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bS3BucketA3488101": { + "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4S3BucketC6CBC09E": { "Type": "String", - "Description": "S3 bucket for asset \"0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5b\"" + "Description": "S3 bucket for asset \"a75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4\"" }, - "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bS3VersionKey23A2E46C": { + "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4S3VersionKeyB194AB23": { "Type": "String", - "Description": "S3 key for asset version \"0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5b\"" + "Description": "S3 key for asset version \"a75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4\"" }, - "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bArtifactHashF6409D44": { + "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4ArtifactHashBE5BD63C": { "Type": "String", - "Description": "Artifact hash for asset \"0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5b\"" + "Description": "Artifact hash for asset \"a75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4\"" } } -} \ No newline at end of file +} 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 bbed1eca96f4c..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", @@ -108,6 +115,11 @@ "UserPoolId": { "Ref": "pool056F3F7E" }, + "AttributeMapping": { + "given_name": "name", + "email": "email", + "userId": "user_id" + }, "ProviderDetails": { "client_id": "amzn-client-id", "client_secret": "amzn-client-secret", diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts index e22b504cf8ad7..31804f1ce95e8 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts @@ -1,5 +1,5 @@ import { App, CfnOutput, Stack } from '@aws-cdk/core'; -import { UserPool, UserPoolIdentityProviderAmazon } from '../lib'; +import { ProviderAttribute, UserPool, UserPoolIdentityProviderAmazon } from '../lib'; /* * Stack verification steps @@ -15,6 +15,13 @@ new UserPoolIdentityProviderAmazon(stack, 'amazon', { userPool: userpool, clientId: 'amzn-client-id', clientSecret: 'amzn-client-secret', + attributeMapping: { + givenName: ProviderAttribute.AMAZON_NAME, + email: ProviderAttribute.AMAZON_EMAIL, + custom: { + userId: ProviderAttribute.AMAZON_USER_ID, + }, + }, }); const client = userpool.addClient('client'); 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-idps/amazon.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/amazon.test.ts index 78300c6b13e5f..00e4182e70f3b 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-idps/amazon.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/amazon.test.ts @@ -1,6 +1,6 @@ import '@aws-cdk/assert/jest'; import { Stack } from '@aws-cdk/core'; -import { UserPool, UserPoolIdentityProviderAmazon } from '../../lib'; +import { ProviderAttribute, UserPool, UserPoolIdentityProviderAmazon } from '../../lib'; describe('UserPoolIdentityProvider', () => { describe('amazon', () => { @@ -66,5 +66,36 @@ describe('UserPoolIdentityProvider', () => { // THEN expect(pool.identityProviders).toContain(provider); }); + + test('attribute mapping', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderAmazon(stack, 'userpoolidp', { + userPool: pool, + clientId: 'amazn-client-id', + clientSecret: 'amzn-client-secret', + attributeMapping: { + givenName: ProviderAttribute.AMAZON_NAME, + address: ProviderAttribute.other('amzn-address'), + custom: { + customAttr1: ProviderAttribute.AMAZON_EMAIL, + customAttr2: ProviderAttribute.other('amzn-custom-attr'), + }, + }, + }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + AttributeMapping: { + given_name: 'name', + address: 'amzn-address', + customAttr1: 'email', + customAttr2: 'amzn-custom-attr', + }, + }); + }); }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idps/base.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/base.test.ts new file mode 100644 index 0000000000000..f4a6ba4ae7f04 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/base.test.ts @@ -0,0 +1,94 @@ +import '@aws-cdk/assert/jest'; +import { Stack } from '@aws-cdk/core'; +import { ProviderAttribute, UserPool, UserPoolIdentityProviderBase } from '../../lib'; + +class MyIdp extends UserPoolIdentityProviderBase { + public readonly providerName = 'MyProvider'; + public readonly mapping = this.configureAttributeMapping(); +} + +describe('UserPoolIdentityProvider', () => { + describe('attribute mapping', () => { + test('absent or empty', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'UserPool'); + + // WHEN + const idp1 = new MyIdp(stack, 'MyIdp1', { + userPool: pool, + }); + const idp2 = new MyIdp(stack, 'MyIdp2', { + userPool: pool, + attributeMapping: {}, + }); + + // THEN + expect(idp1.mapping).toBeUndefined(); + expect(idp2.mapping).toBeUndefined(); + }); + + test('standard attributes', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'UserPool'); + + // WHEN + const idp = new MyIdp(stack, 'MyIdp', { + userPool: pool, + attributeMapping: { + givenName: ProviderAttribute.FACEBOOK_NAME, + birthdate: ProviderAttribute.FACEBOOK_BIRTHDAY, + }, + }); + + // THEN + expect(idp.mapping).toStrictEqual({ + given_name: 'name', + birthdate: 'birthday', + }); + }); + + test('custom', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'UserPool'); + + // WHEN + const idp = new MyIdp(stack, 'MyIdp', { + userPool: pool, + attributeMapping: { + custom: { + 'custom-attr-1': ProviderAttribute.AMAZON_EMAIL, + 'custom-attr-2': ProviderAttribute.AMAZON_NAME, + }, + }, + }); + + // THEN + expect(idp.mapping).toStrictEqual({ + 'custom-attr-1': 'email', + 'custom-attr-2': 'name', + }); + }); + + test('custom provider attribute', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'UserPool'); + + // WHEN + const idp = new MyIdp(stack, 'MyIdp', { + userPool: pool, + attributeMapping: { + address: ProviderAttribute.other('custom-provider-attr'), + }, + }); + + // THEN + expect(idp.mapping).toStrictEqual({ + address: 'custom-provider-attr', + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idps/facebook.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/facebook.test.ts index 40bc9287b5733..3f43ce01f378b 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-idps/facebook.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/facebook.test.ts @@ -1,6 +1,6 @@ import '@aws-cdk/assert/jest'; import { Stack } from '@aws-cdk/core'; -import { UserPool, UserPoolIdentityProviderFacebook } from '../../lib'; +import { ProviderAttribute, UserPool, UserPoolIdentityProviderFacebook } from '../../lib'; describe('UserPoolIdentityProvider', () => { describe('facebook', () => { @@ -68,5 +68,36 @@ describe('UserPoolIdentityProvider', () => { // THEN expect(pool.identityProviders).toContain(provider); }); + + test('attribute mapping', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderFacebook(stack, 'userpoolidp', { + userPool: pool, + clientId: 'fb-client-id', + clientSecret: 'fb-client-secret', + attributeMapping: { + givenName: ProviderAttribute.FACEBOOK_NAME, + address: ProviderAttribute.other('fb-address'), + custom: { + customAttr1: ProviderAttribute.FACEBOOK_EMAIL, + customAttr2: ProviderAttribute.other('fb-custom-attr'), + }, + }, + }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + AttributeMapping: { + given_name: 'name', + address: 'fb-address', + customAttr1: 'email', + customAttr2: 'fb-custom-attr', + }, + }); + }); }); }); \ 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-global/test/integ.dynamodb.global.expected.json b/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.expected.json index d536ea8c8bb23..6050c1497c918 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.expected.json +++ b/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.expected.json @@ -1,5 +1,4 @@ [ - {}, { "Resources": { "globdynamodbintegGlobalTableuseast162596384": { @@ -248,5 +247,6 @@ "Description": "Artifact hash for asset \"94575de3fd18404e41e94a6c1d4b7a1eec2985fd23d2c11999d095e09667ba3a\"" } } - } + }, + {} ] \ No newline at end of file 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-dynamodb/test/integ.global.expected.json b/packages/@aws-cdk/aws-dynamodb/test/integ.global.expected.json index 9057e8c7ae31b..f2a65b428507b 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.global.expected.json +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.global.expected.json @@ -231,7 +231,7 @@ }, "/", { - "Ref": "AssetParametersffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947S3Bucket5148F39F" + "Ref": "AssetParametersb83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3eS3Bucket09EBFB87" }, "/", { @@ -241,7 +241,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947S3VersionKey0618C4C3" + "Ref": "AssetParametersb83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3eS3VersionKey46B9F32F" } ] } @@ -254,7 +254,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947S3VersionKey0618C4C3" + "Ref": "AssetParametersb83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3eS3VersionKey46B9F32F" } ] } @@ -264,58 +264,58 @@ ] }, "Parameters": { - "referencetocdkdynamodbglobal20191121AssetParameters012c6b101abc4ea1f510921af61a3e08e05f30f84d7b35c40ca4adb1ace60746S3BucketE0999323Ref": { - "Ref": "AssetParameters012c6b101abc4ea1f510921af61a3e08e05f30f84d7b35c40ca4adb1ace60746S3BucketBDDEC9DD" + "referencetocdkdynamodbglobal20191121AssetParameters23c5f8cc1bdef276fc20dbb166d24d1b7d8cb516a5d5822ed0d38feec9631fd1S3BucketFE71C2D0Ref": { + "Ref": "AssetParameters23c5f8cc1bdef276fc20dbb166d24d1b7d8cb516a5d5822ed0d38feec9631fd1S3Bucket492F02B7" }, - "referencetocdkdynamodbglobal20191121AssetParameters012c6b101abc4ea1f510921af61a3e08e05f30f84d7b35c40ca4adb1ace60746S3VersionKey8D3D9B9ARef": { - "Ref": "AssetParameters012c6b101abc4ea1f510921af61a3e08e05f30f84d7b35c40ca4adb1ace60746S3VersionKey1C286880" + "referencetocdkdynamodbglobal20191121AssetParameters23c5f8cc1bdef276fc20dbb166d24d1b7d8cb516a5d5822ed0d38feec9631fd1S3VersionKey849A4FA5Ref": { + "Ref": "AssetParameters23c5f8cc1bdef276fc20dbb166d24d1b7d8cb516a5d5822ed0d38feec9631fd1S3VersionKeyC7F72494" }, - "referencetocdkdynamodbglobal20191121AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket6627F4A7Ref": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" + "referencetocdkdynamodbglobal20191121AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket14A4D0F4Ref": { + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18" }, - "referencetocdkdynamodbglobal20191121AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyD04C038CRef": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "referencetocdkdynamodbglobal20191121AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKeyF9E42BF5Ref": { + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" } } } } }, "Parameters": { - "AssetParameters012c6b101abc4ea1f510921af61a3e08e05f30f84d7b35c40ca4adb1ace60746S3BucketBDDEC9DD": { + "AssetParameters23c5f8cc1bdef276fc20dbb166d24d1b7d8cb516a5d5822ed0d38feec9631fd1S3Bucket492F02B7": { "Type": "String", - "Description": "S3 bucket for asset \"012c6b101abc4ea1f510921af61a3e08e05f30f84d7b35c40ca4adb1ace60746\"" + "Description": "S3 bucket for asset \"23c5f8cc1bdef276fc20dbb166d24d1b7d8cb516a5d5822ed0d38feec9631fd1\"" }, - "AssetParameters012c6b101abc4ea1f510921af61a3e08e05f30f84d7b35c40ca4adb1ace60746S3VersionKey1C286880": { + "AssetParameters23c5f8cc1bdef276fc20dbb166d24d1b7d8cb516a5d5822ed0d38feec9631fd1S3VersionKeyC7F72494": { "Type": "String", - "Description": "S3 key for asset version \"012c6b101abc4ea1f510921af61a3e08e05f30f84d7b35c40ca4adb1ace60746\"" + "Description": "S3 key for asset version \"23c5f8cc1bdef276fc20dbb166d24d1b7d8cb516a5d5822ed0d38feec9631fd1\"" }, - "AssetParameters012c6b101abc4ea1f510921af61a3e08e05f30f84d7b35c40ca4adb1ace60746ArtifactHashBB09F15F": { + "AssetParameters23c5f8cc1bdef276fc20dbb166d24d1b7d8cb516a5d5822ed0d38feec9631fd1ArtifactHash43BB7053": { "Type": "String", - "Description": "Artifact hash for asset \"012c6b101abc4ea1f510921af61a3e08e05f30f84d7b35c40ca4adb1ace60746\"" + "Description": "Artifact hash for asset \"23c5f8cc1bdef276fc20dbb166d24d1b7d8cb516a5d5822ed0d38feec9631fd1\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C": { + "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18": { "Type": "String", - "Description": "S3 bucket for asset \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "S3 bucket for asset \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB": { + "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471": { "Type": "String", - "Description": "S3 key for asset version \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "S3 key for asset version \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1ArtifactHash251241BC": { + "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6ArtifactHash3651BF53": { "Type": "String", - "Description": "Artifact hash for asset \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "Artifact hash for asset \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" }, - "AssetParametersffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947S3Bucket5148F39F": { + "AssetParametersb83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3eS3Bucket09EBFB87": { "Type": "String", - "Description": "S3 bucket for asset \"ffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947\"" + "Description": "S3 bucket for asset \"b83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3e\"" }, - "AssetParametersffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947S3VersionKey0618C4C3": { + "AssetParametersb83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3eS3VersionKey46B9F32F": { "Type": "String", - "Description": "S3 key for asset version \"ffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947\"" + "Description": "S3 key for asset version \"b83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3e\"" }, - "AssetParametersffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947ArtifactHashBF6B619B": { + "AssetParametersb83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3eArtifactHash55859AF2": { "Type": "String", - "Description": "Artifact hash for asset \"ffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947\"" + "Description": "Artifact hash for asset \"b83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3e\"" } } -} \ No newline at end of file +} 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/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 25a3473e2d3f3..49a3af5b19311 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -1901,7 +1901,7 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat * They seem pointless but I see no reason to prevent it. */ function determineNatGatewayCount(requestedCount: number | undefined, subnetConfig: SubnetConfiguration[], azCount: number) { - const hasPrivateSubnets = subnetConfig.some(c => c.subnetType === SubnetType.PRIVATE); + const hasPrivateSubnets = subnetConfig.some(c => c.subnetType === SubnetType.PRIVATE && !c.reserved); const hasPublicSubnets = subnetConfig.some(c => c.subnetType === SubnetType.PUBLIC); const count = requestedCount !== undefined ? Math.min(requestedCount, azCount) : (hasPrivateSubnets ? azCount : 0); 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-ec2/test/integ.reserved-private-subnet.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.reserved-private-subnet.expected.json new file mode 100644 index 0000000000000..8f40b525fec84 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.reserved-private-subnet.expected.json @@ -0,0 +1,236 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/VPC" + } + ] + } + }, + "VPCingressSubnet1SubnetBB7FDF67": { + "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": "ingress" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/VPC/ingressSubnet1" + } + ] + } + }, + "VPCingressSubnet1RouteTableEEF02A64": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/VPC/ingressSubnet1" + } + ] + } + }, + "VPCingressSubnet1RouteTableAssociation7700457B": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCingressSubnet1RouteTableEEF02A64" + }, + "SubnetId": { + "Ref": "VPCingressSubnet1SubnetBB7FDF67" + } + } + }, + "VPCingressSubnet1DefaultRouteC1C9D77C": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCingressSubnet1RouteTableEEF02A64" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCingressSubnet2SubnetE30F0091": { + "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": "ingress" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/VPC/ingressSubnet2" + } + ] + } + }, + "VPCingressSubnet2RouteTable8565F2D0": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/VPC/ingressSubnet2" + } + ] + } + }, + "VPCingressSubnet2RouteTableAssociation35C35494": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCingressSubnet2RouteTable8565F2D0" + }, + "SubnetId": { + "Ref": "VPCingressSubnet2SubnetE30F0091" + } + } + }, + "VPCingressSubnet2DefaultRoute8E2F45A7": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCingressSubnet2RouteTable8565F2D0" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCingressSubnet3Subnet38A3BA95": { + "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": "ingress" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/VPC/ingressSubnet3" + } + ] + } + }, + "VPCingressSubnet3RouteTable83539693": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/VPC/ingressSubnet3" + } + ] + } + }, + "VPCingressSubnet3RouteTableAssociation421877E8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCingressSubnet3RouteTable83539693" + }, + "SubnetId": { + "Ref": "VPCingressSubnet3Subnet38A3BA95" + } + } + }, + "VPCingressSubnet3DefaultRouteDF537F13": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCingressSubnet3RouteTable83539693" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/integ.reserved-private-subnet.ts b/packages/@aws-cdk/aws-ec2/test/integ.reserved-private-subnet.ts new file mode 100644 index 0000000000000..8caf7fcdf087d --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.reserved-private-subnet.ts @@ -0,0 +1,40 @@ +import * as cdk from '@aws-cdk/core'; +import * as ec2 from '../lib'; + +/* + * Creates a simple vpc with a public subnet and a private reserved subnet. + * Public subnet should be visible but a private reserved subnet only has IP space reserved. + * No resources are provisioned in a reserved subnet. + * + * Stack verification steps: + * -- aws ec2 describe-nat-gateways returns { "natGateways": []} + */ + +const app = new cdk.App(); + +class VpcReservedPrivateSubnetStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + /// !show + // Specify no NAT gateways with a reserved private subnet + new ec2.Vpc(this, 'VPC', { + cidr: '10.0.0.0/16', + subnetConfiguration: [ + { + name: 'ingress', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + name: 'private', + subnetType: ec2.SubnetType.PRIVATE, + reserved: true, + }, + ], + natGateways: 0, + }); + /// !hide + } +} +new VpcReservedPrivateSubnetStack(app, 'aws-cdk-ec2-vpc-endpoint'); +app.synth(); diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index 94df845e9fab8..fedf67b95c738 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -428,6 +428,30 @@ export = { }, + 'natGateways = 0 allows RESERVED PRIVATE subnets'(test: Test) { + const stack = getTestStack(); + new Vpc(stack, 'VPC', { + cidr: '10.0.0.0/16', + subnetConfiguration: [ + { + name: 'ingress', + subnetType: SubnetType.PUBLIC, + }, + { + name: 'private', + subnetType: SubnetType.PRIVATE, + reserved: true, + }, + ], + natGateways: 0, + }); + expect(stack).to(haveResource('AWS::EC2::Subnet', hasTags([{ + Key: 'aws-cdk:subnet-name', + Value: 'ingress', + }]))); + test.done(); + }, + 'with mis-matched nat and subnet configs it throws'(test: Test) { const stack = getTestStack(); test.throws(() => new Vpc(stack, 'VPC', { 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-ecr/test/integ.imagescan.expected.json b/packages/@aws-cdk/aws-ecr/test/integ.imagescan.expected.json index 1a4f3cee824be..f5f757cd9c432 100644 --- a/packages/@aws-cdk/aws-ecr/test/integ.imagescan.expected.json +++ b/packages/@aws-cdk/aws-ecr/test/integ.imagescan.expected.json @@ -164,7 +164,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bS3BucketA3488101" + "Ref": "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4S3BucketC6CBC09E" }, "S3Key": { "Fn::Join": [ @@ -177,7 +177,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bS3VersionKey23A2E46C" + "Ref": "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4S3VersionKeyB194AB23" } ] } @@ -190,7 +190,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bS3VersionKey23A2E46C" + "Ref": "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4S3VersionKeyB194AB23" } ] } @@ -217,17 +217,17 @@ } }, "Parameters": { - "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bS3BucketA3488101": { + "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4S3BucketC6CBC09E": { "Type": "String", - "Description": "S3 bucket for asset \"0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5b\"" + "Description": "S3 bucket for asset \"a75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4\"" }, - "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bS3VersionKey23A2E46C": { + "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4S3VersionKeyB194AB23": { "Type": "String", - "Description": "S3 key for asset version \"0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5b\"" + "Description": "S3 key for asset version \"a75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4\"" }, - "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bArtifactHashF6409D44": { + "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4ArtifactHashBE5BD63C": { "Type": "String", - "Description": "Artifact hash for asset \"0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5b\"" + "Description": "Artifact hash for asset \"a75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4\"" } }, "Outputs": { @@ -282,4 +282,4 @@ } } } -} \ No newline at end of file +} 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/container-definition.ts b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts index 6e2a4f2868192..26da3b453bf46 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts @@ -263,7 +263,7 @@ export interface ContainerDefinitionOptions { * Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. * For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html). * - * @default - No Linux paramters. + * @default - No Linux parameters. */ readonly linuxParameters?: LinuxParameters; 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-efs/README.md b/packages/@aws-cdk/aws-efs/README.md index dbbc97d8f9a07..40329a9803780 100644 --- a/packages/@aws-cdk/aws-efs/README.md +++ b/packages/@aws-cdk/aws-efs/README.md @@ -28,6 +28,15 @@ const fileSystem = new efs.FileSystem(this, 'MyEfsFileSystem', { }); ``` +A file system can set `RemovalPolicy`. Default policy is `RETAIN`. + +```ts +const fileSystem = new FileSystem(this, 'EfsFileSystem', { + vpc, + removalPolicy: RemovalPolicy.DESTROY +}); +``` + ### Connecting To control who can access the EFS, use the `.connections` attribute. EFS has diff --git a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts index e3212e6b1ec68..aa42acb2b6372 100644 --- a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts +++ b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts @@ -1,6 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; -import { Construct, IResource, Resource, Size, Tag } from '@aws-cdk/core'; +import { Construct, IResource, RemovalPolicy, Resource, Size, Tag } from '@aws-cdk/core'; import { CfnFileSystem, CfnMountTarget } from './efs.generated'; // tslint:disable:max-line-length @@ -158,6 +158,13 @@ export interface FileSystemProps { * @default - none, errors out */ readonly provisionedThroughputPerSecond?: Size; + + /** + * The removal policy to apply to the file system. + * + * @default RemovalPolicy.RETAIN + */ + readonly removalPolicy?: RemovalPolicy; } /** @@ -237,6 +244,7 @@ export class FileSystem extends Resource implements IFileSystem { throughputMode: props.throughputMode, provisionedThroughputInMibps: props.provisionedThroughputPerSecond?.toMebibytes(), }); + filesystem.applyRemovalPolicy(props.removalPolicy); this.fileSystemId = filesystem.ref; Tag.add(this, 'Name', props.fileSystemName || this.node.path); @@ -264,4 +272,4 @@ export class FileSystem extends Resource implements IFileSystem { }); }); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts b/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts index d9c2cf12f0dac..42fa89ee8d9ae 100644 --- a/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts +++ b/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts @@ -1,8 +1,8 @@ -import {expect as expectCDK, haveResource} from '@aws-cdk/assert'; +import { expect as expectCDK, haveResource, ResourcePart } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; -import { Size, Stack, Tag } from '@aws-cdk/core'; -import { FileSystem, LifecyclePolicy, PerformanceMode, ThroughputMode} from '../lib'; +import { RemovalPolicy, Size, Stack, Tag } from '@aws-cdk/core'; +import { FileSystem, LifecyclePolicy, PerformanceMode, ThroughputMode } from '../lib'; let stack = new Stack(); let vpc = new ec2.Vpc(stack, 'VPC'); @@ -18,7 +18,10 @@ test('default file system is created correctly', () => { vpc, }); // THEN - expectCDK(stack).to(haveResource('AWS::EFS::FileSystem')); + expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { + DeletionPolicy: 'Retain', + UpdateReplacePolicy: 'Retain', + }, ResourcePart.CompleteDefinition)); expectCDK(stack).to(haveResource('AWS::EFS::MountTarget')); expectCDK(stack).to(haveResource('AWS::EC2::SecurityGroup')); }); @@ -211,3 +214,13 @@ test('auto-named if none provided', () => { ], })); }); + +test('removalPolicy is DESTROY', () => { + new FileSystem(stack, 'EfsFileSystem', {vpc, removalPolicy: RemovalPolicy.DESTROY}); + + expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { + DeletionPolicy: 'Delete', + UpdateReplacePolicy: 'Delete', + }, ResourcePart.CompleteDefinition)); + +}); 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/README.md b/packages/@aws-cdk/aws-eks/README.md index d77259e7fb3fb..fb70871bfe8e5 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -386,6 +386,33 @@ A convenience method for mapping a role to the `system:masters` group is also av cluster.awsAuth.addMastersRole(role) ``` +### Cluster Security Group + +When you create an Amazon EKS cluster, a +[cluster security group](https://docs.aws.amazon.com/eks/latest/userguide/sec-group-reqs.html) +is automatically created as well. This security group is designed to allow +all traffic from the control plane and managed node groups to flow freely +between each other. + +The ID for that security group can be retrieved after creating the cluster. + +```ts +const clusterSecurityGroupId = cluster.clusterSecurityGroupId; +``` + +### Cluster Encryption Configuration + +When you create an Amazon EKS cluster, envelope encryption of +Kubernetes secrets using the AWS Key Management Service (AWS KMS) can be enabled. The documentation +on [creating a cluster](https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html) +can provide more details about the customer master key (CMK) that can be used for the encryption. + +The Amazon Resource Name (ARN) for that CMK can be retrieved. + +```ts +const clusterEncryptionConfigKeyArn = cluster.clusterEncryptionConfigKeyArn; +``` + ### Node ssh Access If you want to be able to SSH into your worker nodes, you must already @@ -486,7 +513,8 @@ cluster.addChart('NginxIngress', { }); ``` -Helm charts will be installed and updated using `helm upgrade --install`. +Helm charts will be installed and updated using `helm upgrade --install`, where a few parameters +are being passed down (such as `repo`, `values`, `version`, `namespace`, `wait`, `timeout`, etc). This means that if the chart is added to CDK with the same release name, it will try to update the chart in the cluster. The chart will exists as CloudFormation resource. diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts index 8733463cce31b..f20ddd85c5704 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts @@ -131,11 +131,21 @@ export class ClusterResourceHandler extends ResourceHandler { } if (updates.updateLogging || updates.updateAccess) { - const updateResponse = await this.eks.updateClusterConfig({ + const config: aws.EKS.UpdateClusterConfigRequest = { name: this.clusterName, logging: this.newProps.logging, - resourcesVpcConfig: this.newProps.resourcesVpcConfig, - }); + }; + if (updates.updateAccess) { + // Updating the cluster with securityGroupIds and subnetIds (as specified in the warning here: + // https://awscli.amazonaws.com/v2/documentation/api/latest/reference/eks/update-cluster-config.html) + // will fail, therefore we take only the access fields explicitly + config.resourcesVpcConfig = { + endpointPrivateAccess: this.newProps.resourcesVpcConfig.endpointPrivateAccess, + endpointPublicAccess: this.newProps.resourcesVpcConfig.endpointPublicAccess, + publicAccessCidrs: this.newProps.resourcesVpcConfig.publicAccessCidrs, + }; + } + const updateResponse = await this.eks.updateClusterConfig(config); return { EksUpdateId: updateResponse.update?.id }; } @@ -197,9 +207,20 @@ export class ClusterResourceHandler extends ResourceHandler { Name: cluster.name, Endpoint: cluster.endpoint, Arn: cluster.arn, - CertificateAuthorityData: cluster.certificateAuthority?.data, - OpenIdConnectIssuerUrl: cluster.identity?.oidc?.issuer, - OpenIdConnectIssuer: cluster.identity?.oidc?.issuer?.substring(8), // Strips off https:// from the issuer url + + // IMPORTANT: CFN expects that attributes will *always* have values, + // so return an empty string in case the value is not defined. + // Otherwise, CFN will throw with `Vendor response doesn't contain + // XXXX key`. + + CertificateAuthorityData: cluster.certificateAuthority?.data ?? '', + ClusterSecurityGroupId: cluster.resourcesVpcConfig?.clusterSecurityGroupId ?? '', + OpenIdConnectIssuerUrl: cluster.identity?.oidc?.issuer ?? '', + OpenIdConnectIssuer: cluster.identity?.oidc?.issuer?.substring(8) ?? '', // Strips off https:// from the issuer url + + // We can safely return the first item from encryption configuration array, because it has a limit of 1 item + // https://docs.aws.amazon.com/eks/latest/APIReference/API_CreateCluster.html#AmazonEKS-CreateCluster-request-encryptionConfig + EncryptionConfigKeyArn: cluster.encryptionConfig?.shift()?.provider?.keyArn ?? '', }, }; } diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts index 52557776c97e8..745dcc799b954 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts @@ -18,16 +18,18 @@ export class ClusterResource extends Construct { public readonly attrEndpoint: string; public readonly attrArn: string; public readonly attrCertificateAuthorityData: string; + public readonly attrClusterSecurityGroupId: string; + public readonly attrEncryptionConfigKeyArn: string; 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) { @@ -117,6 +119,13 @@ export class ClusterResource extends Construct { properties: { Config: props, AssumeRoleArn: this.creationRole.roleArn, + + // IMPORTANT: increment this number when you add new attributes to the + // resource. Otherwise, CloudFormation will error with "Vendor response + // doesn't contain XXX key in object" (see #8276) by incrementing this + // number, you will effectively cause a "no-op update" to the cluster + // which will return the new set of attribute. + AttributesRevision: 2, }, }); @@ -126,33 +135,30 @@ export class ClusterResource extends Construct { this.attrEndpoint = Token.asString(resource.getAtt('Endpoint')); this.attrArn = Token.asString(resource.getAtt('Arn')); this.attrCertificateAuthorityData = Token.asString(resource.getAtt('CertificateAuthorityData')); + this.attrClusterSecurityGroupId = Token.asString(resource.getAtt('ClusterSecurityGroupId')); + this.attrEncryptionConfigKeyArn = Token.asString(resource.getAtt('EncryptionConfigKeyArn')); this.attrOpenIdConnectIssuerUrl = Token.asString(resource.getAtt('OpenIdConnectIssuerUrl')); this.attrOpenIdConnectIssuer = Token.asString(resource.getAtt('OpenIdConnectIssuer')); } /** - * 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); } } @@ -162,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 d1fb2bf60352b..491d357387073 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -52,6 +52,18 @@ export interface ICluster extends IResource, ec2.IConnectable { * @attribute */ readonly clusterCertificateAuthorityData: string; + + /** + * The cluster security group that was created by Amazon EKS for the cluster. + * @attribute + */ + readonly clusterSecurityGroupId: string; + + /** + * Amazon Resource Name (ARN) or alias of the customer master key (CMK). + * @attribute + */ + readonly clusterEncryptionConfigKeyArn: string; } /** @@ -84,6 +96,16 @@ export interface ClusterAttributes { */ readonly clusterCertificateAuthorityData: string; + /** + * The cluster security group that was created by Amazon EKS for the cluster. + */ + readonly clusterSecurityGroupId: string; + + /** + * Amazon Resource Name (ARN) or alias of the customer master key (CMK). + */ + readonly clusterEncryptionConfigKeyArn: string; + /** * The security groups associated with this cluster. */ @@ -299,6 +321,16 @@ export class Cluster extends Resource implements ICluster { */ public readonly clusterCertificateAuthorityData: string; + /** + * The cluster security group that was created by Amazon EKS for the cluster. + */ + public readonly clusterSecurityGroupId: string; + + /** + * Amazon Resource Name (ARN) or alias of the customer master key (CMK). + */ + public readonly clusterEncryptionConfigKeyArn: string; + /** * Manages connection rules (Security Group Rules) for the cluster * @@ -331,6 +363,12 @@ export class Cluster extends Resource implements ICluster { */ public readonly defaultNodegroup?: Nodegroup; + /** + * If the cluster has one (or more) FargateProfiles associated, this array + * will hold a reference to each. + */ + private readonly _fargateProfiles: FargateProfile[] = []; + /** * If this cluster is kubectl-enabled, returns the `ClusterResource` object * that manages it. If this cluster is not kubectl-enabled (i.e. uses the @@ -414,6 +452,8 @@ export class Cluster extends Resource implements ICluster { this.clusterEndpoint = resource.attrEndpoint; this.clusterCertificateAuthorityData = resource.attrCertificateAuthorityData; + this.clusterSecurityGroupId = resource.attrClusterSecurityGroupId; + this.clusterEncryptionConfigKeyArn = resource.attrEncryptionConfigKeyArn; const updateConfigCommandPrefix = `aws eks update-kubeconfig --name ${this.clusterName}`; const getTokenCommandPrefix = `aws eks get-token --cluster-name ${this.clusterName}`; @@ -736,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; } /** @@ -754,7 +794,24 @@ 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; + } + + /** + * Internal API used by `FargateProfile` to keep inventory of Fargate profiles associated with + * this cluster, for the sake of ensuring the profiles are created sequentially. + * + * @returns the list of FargateProfiles attached to this cluster, including the one just attached. + * @internal + */ + public _attachFargateProfile(fargateProfile: FargateProfile): FargateProfile[] { + this._fargateProfiles.push(fargateProfile); + return this._fargateProfiles; } /** @@ -990,6 +1047,8 @@ export interface AutoScalingGroupOptions { class ImportedCluster extends Resource implements ICluster { public readonly vpc: ec2.IVpc; public readonly clusterCertificateAuthorityData: string; + public readonly clusterSecurityGroupId: string; + public readonly clusterEncryptionConfigKeyArn: string; public readonly clusterName: string; public readonly clusterArn: string; public readonly clusterEndpoint: string; @@ -1003,6 +1062,8 @@ class ImportedCluster extends Resource implements ICluster { this.clusterEndpoint = props.clusterEndpoint; this.clusterArn = props.clusterArn; this.clusterCertificateAuthorityData = props.clusterCertificateAuthorityData; + this.clusterSecurityGroupId = props.clusterSecurityGroupId; + this.clusterEncryptionConfigKeyArn = props.clusterEncryptionConfigKeyArn; let i = 1; for (const sgProps of props.securityGroups) { diff --git a/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts b/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts index 5a96731a7b17d..76a32929de190 100644 --- a/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts +++ b/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts @@ -129,16 +129,32 @@ 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); + // currently the custom resource requires a role to assume when interacting with the cluster + // and we only have this role when kubectl is enabled. + if (!props.cluster.kubectlEnabled) { + throw new Error('adding Faregate Profiles to clusters without kubectl enabled is currently unsupported'); + } + 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 }; @@ -159,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() }), @@ -173,5 +189,24 @@ export class FargateProfile extends Construct implements ITaggable { this.fargateProfileArn = resource.getAttString('fargateProfileArn'); this.fargateProfileName = resource.ref; + + // Fargate profiles must be created sequentially. If other profile(s) already + // exist on the same cluster, create a dependency to force sequential creation. + const clusterFargateProfiles = props.cluster._attachFargateProfile(this); + if (clusterFargateProfiles.length > 1) { + const previousProfile = clusterFargateProfiles[clusterFargateProfiles.length - 2]; + resource.node.addDependency(previousProfile); + } + + // 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(this.podExecutionRole, { + username: 'system:node:{{SessionName}}', + groups: [ + 'system:bootstrappers', + 'system:nodes', + 'system:node-proxier', + ], + }); } } diff --git a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts index 59cbc0f3e7aa0..8d5b50d870151 100644 --- a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts +++ b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts @@ -1,4 +1,4 @@ -import { Construct, CustomResource, Stack } from '@aws-cdk/core'; +import { Construct, CustomResource, Duration, Stack } from '@aws-cdk/core'; import { Cluster } from './cluster'; /** @@ -47,6 +47,12 @@ export interface HelmChartOptions { * @default - Helm will not wait before marking release as successful */ readonly wait?: boolean; + + /** + * Amount of time to wait for any individual Kubernetes operation. Maximum 15 minutes. + * @default Duration.minutes(5) + */ + readonly timeout?: Duration; } /** @@ -68,7 +74,7 @@ export interface HelmChartProps extends HelmChartOptions { */ export class HelmChart extends Construct { /** - * The CloudFormation reosurce type. + * The CloudFormation resource type. */ public static readonly RESOURCE_TYPE = 'Custom::AWSCDK-EKS-HelmChart'; @@ -79,18 +85,24 @@ export class HelmChart extends Construct { const provider = props.cluster._kubectlProvider; + const timeout = props.timeout?.toSeconds(); + if (timeout && timeout > 900) { + throw new Error('Helm chart timeout cannot be higher than 15 minutes.'); + } + new CustomResource(this, 'Resource', { serviceToken: provider.serviceToken, 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/lib/kubectl-handler/helm/__init__.py b/packages/@aws-cdk/aws-eks/lib/kubectl-handler/helm/__init__.py index 05d0fbdaba614..57ea65a2fa3b7 100644 --- a/packages/@aws-cdk/aws-eks/lib/kubectl-handler/helm/__init__.py +++ b/packages/@aws-cdk/aws-eks/lib/kubectl-handler/helm/__init__.py @@ -25,6 +25,7 @@ def helm_handler(event, context): chart = props['Chart'] version = props.get('Version', None) wait = props.get('Wait', False) + timeout = props.get('Timeout', None) namespace = props.get('Namespace', None) repository = props.get('Repository', None) values_text = props.get('Values', None) @@ -45,14 +46,14 @@ def helm_handler(event, context): f.write(json.dumps(values, indent=2)) if request_type == 'Create' or request_type == 'Update': - helm('upgrade', release, chart, repository, values_file, namespace, version) + helm('upgrade', release, chart, repository, values_file, namespace, version, wait, timeout) elif request_type == "Delete": try: - helm('uninstall', release, namespace=namespace) + helm('uninstall', release, namespace=namespace, timeout=timeout) except Exception as e: logger.info("delete error: %s" % e) -def helm(verb, release, chart = None, repo = None, file = None, namespace = None, version = None, wait = False): +def helm(verb, release, chart = None, repo = None, file = None, namespace = None, version = None, wait = False, timeout = None): import subprocess cmnd = ['helm', verb, release] @@ -70,6 +71,8 @@ def helm(verb, release, chart = None, repo = None, file = None, namespace = None cmnd.extend(['--namespace', namespace]) if wait: cmnd.append('--wait') + if not timeout is None: + cmnd.extend(['--timeout', timeout]) cmnd.extend(['--kubeconfig', kubeconfig]) retry = 3 diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index 3fc137fce439f..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", @@ -98,13 +98,7 @@ }, "awslint": { "exclude": [ - "resource-attribute:@aws-cdk/aws-eks.FargateCluster.clusterSecurityGroupId", - "resource-attribute:@aws-cdk/aws-eks.FargateCluster.clusterEncryptionConfigKeyArn", - "resource-attribute:@aws-cdk/aws-eks.Cluster.clusterSecurityGroupId", - "resource-attribute:@aws-cdk/aws-eks.Cluster.clusterEncryptionConfigKeyArn", - "props-no-arn-refs:@aws-cdk/aws-eks.ClusterProps.outputMastersRoleArn", - "resource-attribute:@aws-cdk/aws-eks.Cluster.clusterSecurityGroupId", - "resource-attribute:@aws-cdk/aws-eks.Cluster.clusterSecurityGroupId" + "props-no-arn-refs:@aws-cdk/aws-eks.ClusterProps.outputMastersRoleArn" ] }, "stability": "experimental", 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 164377d944797..4278bee450139 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" @@ -888,7 +898,8 @@ "ClusterCreationRole360249B6", "Arn" ] - } + }, + "AttributesRevision": 2 }, "DependsOn": [ "ClusterCreationRoleDefaultPolicyE8BDFC7B", @@ -925,6 +936,13 @@ ] }, "\\\",\\\"groups\\\":[\\\"system:masters\\\"]},{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "ClusterfargateprofiledefaultPodExecutionRole09952CFF", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{SessionName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\",\\\"system:node-proxier\\\"]},{\\\"rolearn\\\":\\\"", { "Fn::GetAtt": [ "ClusterNodesInstanceRoleC3C01328", @@ -2348,7 +2366,7 @@ }, "/", { - "Ref": "AssetParameters7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6S3BucketB18DC500" + "Ref": "AssetParameters7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896S3BucketA0BEF119" }, "/", { @@ -2358,7 +2376,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6S3VersionKeyBE7DFF7A" + "Ref": "AssetParameters7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896S3VersionKey639A5314" } ] } @@ -2371,7 +2389,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6S3VersionKeyBE7DFF7A" + "Ref": "AssetParameters7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896S3VersionKey639A5314" } ] } @@ -2381,17 +2399,17 @@ ] }, "Parameters": { - "referencetoawscdkeksclustertestAssetParameters01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896S3Bucket35BE45A3Ref": { - "Ref": "AssetParameters01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896S3Bucket221B7FEE" + "referencetoawscdkeksclustertestAssetParametersc23ce59a47ffb1e28812148fb83f7dcb0d94f1f0286e122a2f1aa189c0b35d03S3BucketD5010C93Ref": { + "Ref": "AssetParametersc23ce59a47ffb1e28812148fb83f7dcb0d94f1f0286e122a2f1aa189c0b35d03S3Bucket2F8CA18B" }, - "referencetoawscdkeksclustertestAssetParameters01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896S3VersionKey60905A80Ref": { - "Ref": "AssetParameters01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896S3VersionKeyA8C9A018" + "referencetoawscdkeksclustertestAssetParametersc23ce59a47ffb1e28812148fb83f7dcb0d94f1f0286e122a2f1aa189c0b35d03S3VersionKeyAC8DDB71Ref": { + "Ref": "AssetParametersc23ce59a47ffb1e28812148fb83f7dcb0d94f1f0286e122a2f1aa189c0b35d03S3VersionKeyEFEE8BE5" }, - "referencetoawscdkeksclustertestAssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3BucketC7CBF350Ref": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" + "referencetoawscdkeksclustertestAssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket8FECC379Ref": { + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18" }, - "referencetoawscdkeksclustertestAssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKey7E2BE411Ref": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "referencetoawscdkeksclustertestAssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey0555F20FRef": { + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" } } } @@ -2409,7 +2427,7 @@ }, "/", { - "Ref": "AssetParameters36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbcS3Bucket2D824DEF" + "Ref": "AssetParametersc70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649eS3Bucket5515EF97" }, "/", { @@ -2419,7 +2437,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbcS3VersionKey45D8E8E4" + "Ref": "AssetParametersc70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649eS3VersionKeyF1C97F67" } ] } @@ -2432,7 +2450,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbcS3VersionKey45D8E8E4" + "Ref": "AssetParametersc70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649eS3VersionKeyF1C97F67" } ] } @@ -2442,17 +2460,17 @@ ] }, "Parameters": { - "referencetoawscdkeksclustertestAssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3Bucket6A8A7186Ref": { - "Ref": "AssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3Bucket0C3A00C2" + "referencetoawscdkeksclustertestAssetParametersca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9S3Bucket973804E9Ref": { + "Ref": "AssetParametersca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9S3BucketC1533EC8" }, - "referencetoawscdkeksclustertestAssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3VersionKeyA18C5C39Ref": { - "Ref": "AssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3VersionKeyBED95764" + "referencetoawscdkeksclustertestAssetParametersca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9S3VersionKey2F733777Ref": { + "Ref": "AssetParametersca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9S3VersionKey2C834492" }, - "referencetoawscdkeksclustertestAssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3BucketC7CBF350Ref": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" + "referencetoawscdkeksclustertestAssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket8FECC379Ref": { + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18" }, - "referencetoawscdkeksclustertestAssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKey7E2BE411Ref": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "referencetoawscdkeksclustertestAssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey0555F20FRef": { + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" } } } @@ -2484,7 +2502,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3BucketEF5DD638" + "Ref": "AssetParameters3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086S3Bucket57C0655B" }, "S3Key": { "Fn::Join": [ @@ -2497,7 +2515,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3VersionKey2EA0DB2E" + "Ref": "AssetParameters3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086S3VersionKey4BC65AD6" } ] } @@ -2510,7 +2528,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3VersionKey2EA0DB2E" + "Ref": "AssetParameters3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086S3VersionKey4BC65AD6" } ] } @@ -2583,7 +2601,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319S3Bucket718B603F" + "Ref": "AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3Bucket0C424907" }, "S3Key": { "Fn::Join": [ @@ -2596,7 +2614,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319S3VersionKey6B97A1A3" + "Ref": "AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3VersionKey6841F1F8" } ] } @@ -2609,7 +2627,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319S3VersionKey6B97A1A3" + "Ref": "AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3VersionKey6841F1F8" } ] } @@ -2700,6 +2718,22 @@ ] } }, + "ClusterSecurityGroupId": { + "Value": { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "ClusterSecurityGroupId" + ] + } + }, + "ClusterEncryptionConfigKeyArn": { + "Value": { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "EncryptionConfigKeyArn" + ] + } + }, "ClusterName": { "Value": { "Ref": "Cluster9EE0221C" @@ -2707,89 +2741,89 @@ } }, "Parameters": { - "AssetParameters01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896S3Bucket221B7FEE": { + "AssetParametersc23ce59a47ffb1e28812148fb83f7dcb0d94f1f0286e122a2f1aa189c0b35d03S3Bucket2F8CA18B": { "Type": "String", - "Description": "S3 bucket for asset \"01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896\"" + "Description": "S3 bucket for asset \"c23ce59a47ffb1e28812148fb83f7dcb0d94f1f0286e122a2f1aa189c0b35d03\"" }, - "AssetParameters01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896S3VersionKeyA8C9A018": { + "AssetParametersc23ce59a47ffb1e28812148fb83f7dcb0d94f1f0286e122a2f1aa189c0b35d03S3VersionKeyEFEE8BE5": { "Type": "String", - "Description": "S3 key for asset version \"01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896\"" + "Description": "S3 key for asset version \"c23ce59a47ffb1e28812148fb83f7dcb0d94f1f0286e122a2f1aa189c0b35d03\"" }, - "AssetParameters01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896ArtifactHashED8C0EF9": { + "AssetParametersc23ce59a47ffb1e28812148fb83f7dcb0d94f1f0286e122a2f1aa189c0b35d03ArtifactHashC187523A": { "Type": "String", - "Description": "Artifact hash for asset \"01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896\"" + "Description": "Artifact hash for asset \"c23ce59a47ffb1e28812148fb83f7dcb0d94f1f0286e122a2f1aa189c0b35d03\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C": { + "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18": { "Type": "String", - "Description": "S3 bucket for asset \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "S3 bucket for asset \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB": { + "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471": { "Type": "String", - "Description": "S3 key for asset version \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "S3 key for asset version \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1ArtifactHash251241BC": { + "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6ArtifactHash3651BF53": { "Type": "String", - "Description": "Artifact hash for asset \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "Artifact hash for asset \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" }, - "AssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3Bucket0C3A00C2": { + "AssetParametersca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9S3BucketC1533EC8": { "Type": "String", - "Description": "S3 bucket for asset \"a6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afb\"" + "Description": "S3 bucket for asset \"ca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9\"" }, - "AssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3VersionKeyBED95764": { + "AssetParametersca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9S3VersionKey2C834492": { "Type": "String", - "Description": "S3 key for asset version \"a6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afb\"" + "Description": "S3 key for asset version \"ca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9\"" }, - "AssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbArtifactHashBF08C2D7": { + "AssetParametersca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9ArtifactHash51A7CDC3": { "Type": "String", - "Description": "Artifact hash for asset \"a6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afb\"" + "Description": "Artifact hash for asset \"ca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9\"" }, - "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3BucketEF5DD638": { + "AssetParameters3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086S3Bucket57C0655B": { "Type": "String", - "Description": "S3 bucket for asset \"e02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57\"" + "Description": "S3 bucket for asset \"3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086\"" }, - "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3VersionKey2EA0DB2E": { + "AssetParameters3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086S3VersionKey4BC65AD6": { "Type": "String", - "Description": "S3 key for asset version \"e02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57\"" + "Description": "S3 key for asset version \"3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086\"" }, - "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57ArtifactHash95B71D2D": { + "AssetParameters3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086ArtifactHashD8D99435": { "Type": "String", - "Description": "Artifact hash for asset \"e02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57\"" + "Description": "Artifact hash for asset \"3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086\"" }, - "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319S3Bucket718B603F": { + "AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3Bucket0C424907": { "Type": "String", - "Description": "S3 bucket for asset \"4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319\"" + "Description": "S3 bucket for asset \"ea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161\"" }, - "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319S3VersionKey6B97A1A3": { + "AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3VersionKey6841F1F8": { "Type": "String", - "Description": "S3 key for asset version \"4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319\"" + "Description": "S3 key for asset version \"ea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161\"" }, - "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319ArtifactHash96BDDF33": { + "AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161ArtifactHash67B22EF2": { "Type": "String", - "Description": "Artifact hash for asset \"4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319\"" + "Description": "Artifact hash for asset \"ea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161\"" }, - "AssetParameters7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6S3BucketB18DC500": { + "AssetParameters7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896S3BucketA0BEF119": { "Type": "String", - "Description": "S3 bucket for asset \"7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6\"" + "Description": "S3 bucket for asset \"7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896\"" }, - "AssetParameters7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6S3VersionKeyBE7DFF7A": { + "AssetParameters7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896S3VersionKey639A5314": { "Type": "String", - "Description": "S3 key for asset version \"7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6\"" + "Description": "S3 key for asset version \"7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896\"" }, - "AssetParameters7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6ArtifactHash5F906FBC": { + "AssetParameters7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896ArtifactHash8FB2ECC5": { "Type": "String", - "Description": "Artifact hash for asset \"7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6\"" + "Description": "Artifact hash for asset \"7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896\"" }, - "AssetParameters36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbcS3Bucket2D824DEF": { + "AssetParametersc70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649eS3Bucket5515EF97": { "Type": "String", - "Description": "S3 bucket for asset \"36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbc\"" + "Description": "S3 bucket for asset \"c70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649e\"" }, - "AssetParameters36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbcS3VersionKey45D8E8E4": { + "AssetParametersc70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649eS3VersionKeyF1C97F67": { "Type": "String", - "Description": "S3 key for asset version \"36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbc\"" + "Description": "S3 key for asset version \"c70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649e\"" }, - "AssetParameters36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbcArtifactHash83AE269A": { + "AssetParametersc70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649eArtifactHash88EDABBF": { "Type": "String", - "Description": "Artifact hash for asset \"36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbc\"" + "Description": "Artifact hash for asset \"c70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649e\"" }, "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", @@ -2800,4 +2834,4 @@ "Default": "/aws/service/bottlerocket/aws-k8s-1.15/x86_64/latest/image_id" } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts index f6e883f773140..ff6d62e74c20f 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts @@ -72,6 +72,8 @@ class EksClusterStack extends TestStack { new CfnOutput(this, 'ClusterEndpoint', { value: cluster.clusterEndpoint }); new CfnOutput(this, 'ClusterArn', { value: cluster.clusterArn }); new CfnOutput(this, 'ClusterCertificateAuthorityData', { value: cluster.clusterCertificateAuthorityData }); + new CfnOutput(this, 'ClusterSecurityGroupId', { value: cluster.clusterSecurityGroupId }); + new CfnOutput(this, 'ClusterEncryptionConfigKeyArn', { value: cluster.clusterEncryptionConfigKeyArn }); new CfnOutput(this, 'ClusterName', { value: cluster.clusterName }); } } diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts b/packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts index 29dcfac4e89b6..e762d6c7abbd3 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts @@ -99,8 +99,10 @@ export = { Endpoint: 'http://endpoint', Arn: 'arn:cluster-arn', CertificateAuthorityData: 'certificateAuthority-data', - OpenIdConnectIssuerUrl: undefined, - OpenIdConnectIssuer: undefined, + ClusterSecurityGroupId: '', + EncryptionConfigKeyArn: '', + OpenIdConnectIssuerUrl: '', + OpenIdConnectIssuer: '', }, }); test.done(); @@ -272,7 +274,7 @@ export = { test.done(); }, - async '"roleArn" requires a replcement'(test: Test) { + async '"roleArn" requires a replacement'(test: Test) { const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', { roleArn: 'new-arn', }, { @@ -422,8 +424,10 @@ export = { Endpoint: 'http://endpoint', Arn: 'arn:cluster-arn', CertificateAuthorityData: 'certificateAuthority-data', - OpenIdConnectIssuerUrl: undefined, - OpenIdConnectIssuer: undefined, + ClusterSecurityGroupId: '', + EncryptionConfigKeyArn: '', + OpenIdConnectIssuerUrl: '', + OpenIdConnectIssuer: '', }, }); test.done(); @@ -496,7 +500,106 @@ export = { test.done(); }, }, + + 'logging or access change': { + async 'from undefined to partial logging enabled'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', { + logging: { + clusterLogging: [ + { + types: [ 'api' ], + enabled: true, + }, + ], + }, + }, { + logging: undefined, + })); + const resp = await handler.onEvent(); + test.deepEqual(resp, { EksUpdateId: mocks.MOCK_UPDATE_STATUS_ID }); + test.deepEqual(mocks.actualRequest.updateClusterConfigRequest!, { + name: 'physical-resource-id', + logging: { + clusterLogging: [ + { + types: [ 'api' ], + enabled: true, + }, + ], + }, + }); + test.equal(mocks.actualRequest.createClusterRequest, undefined); + test.done(); + }, + + async 'from partial vpc configuration to only private access enabled'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', { + resourcesVpcConfig: { + securityGroupIds: ['sg1', 'sg2', 'sg3'], + endpointPrivateAccess: true, + }, + }, { + resourcesVpcConfig: { + securityGroupIds: ['sg1', 'sg2', 'sg3'], + }, + })); + const resp = await handler.onEvent(); + test.deepEqual(resp, { EksUpdateId: mocks.MOCK_UPDATE_STATUS_ID }); + test.deepEqual(mocks.actualRequest.updateClusterConfigRequest!, { + name: 'physical-resource-id', + logging: undefined, + resourcesVpcConfig: { + endpointPrivateAccess: true, + endpointPublicAccess: undefined, + publicAccessCidrs: undefined, + }, + }); + test.equal(mocks.actualRequest.createClusterRequest, undefined); + test.done(); + }, + + async 'from undefined to both logging and access fully enabled'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', { + logging: { + clusterLogging: [ + { + types: [ 'api', 'audit', 'authenticator', 'controllerManager', 'scheduler' ], + enabled: true, + }, + ], + }, + resourcesVpcConfig: { + endpointPrivateAccess: true, + endpointPublicAccess: true, + publicAccessCidrs: [ '0.0.0.0/0' ], + }, + }, { + logging: undefined, + resourcesVpcConfig: undefined, + })); + + const resp = await handler.onEvent(); + test.deepEqual(resp, { EksUpdateId: mocks.MOCK_UPDATE_STATUS_ID }); + test.deepEqual(mocks.actualRequest.updateClusterConfigRequest!, { + name: 'physical-resource-id', + logging: { + clusterLogging: [ + { + types: [ 'api', 'audit', 'authenticator', 'controllerManager', 'scheduler' ], + enabled: true, + }, + ], + }, + resourcesVpcConfig: { + endpointPrivateAccess: true, + endpointPublicAccess: true, + publicAccessCidrs: [ '0.0.0.0/0' ], + }, + }); + test.equal(mocks.actualRequest.createClusterRequest, undefined); + test.done(); + }, + }, }, }, - }; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index daeded7e80743..4b00ac1ec6e36 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -306,6 +306,8 @@ export = { clusterName: cluster.clusterName, securityGroups: cluster.connections.securityGroups, clusterCertificateAuthorityData: cluster.clusterCertificateAuthorityData, + clusterSecurityGroupId: cluster.clusterSecurityGroupId, + clusterEncryptionConfigKeyArn: cluster.clusterEncryptionConfigKeyArn, }); // this should cause an export/import diff --git a/packages/@aws-cdk/aws-eks/test/test.fargate.ts b/packages/@aws-cdk/aws-eks/test/test.fargate.ts index d571576a4d0ab..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 } 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'; @@ -251,4 +251,169 @@ export = { })); test.done(); }, + + 'multiple Fargate profiles added to a cluster are processed sequentially'(test: Test) { + // GIVEN + const stack = new Stack(); + const cluster = new eks.Cluster(stack, 'MyCluster'); + + // WHEN + cluster.addFargateProfile('MyProfile1', { + selectors: [ { namespace: 'namespace1' } ], + }); + cluster.addFargateProfile('MyProfile2', { + selectors: [ { namespace: 'namespace2' } ], + }); + + // THEN + expect(stack).to(haveResource('Custom::AWSCDK-EKS-FargateProfile', { + Config: { + clusterName: { Ref: 'MyCluster8AD82BF8' }, + podExecutionRoleArn: { 'Fn::GetAtt': [ 'MyClusterfargateprofileMyProfile1PodExecutionRole794E9E37', 'Arn' ] }, + selectors: [ { namespace: 'namespace1' } ], + }, + })); + expect(stack).to(haveResource('Custom::AWSCDK-EKS-FargateProfile', { + Properties: { + ServiceToken: { 'Fn::GetAtt': [ + 'awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454', + 'Outputs.awscdkawseksClusterResourceProviderframeworkonEventEA97AA31Arn', + ]}, + AssumeRoleArn: { 'Fn::GetAtt': [ 'MyClusterCreationRoleB5FA4FF3', 'Arn' ] }, + Config: { + clusterName: { Ref: 'MyCluster8AD82BF8' }, + podExecutionRoleArn: { 'Fn::GetAtt': [ 'MyClusterfargateprofileMyProfile2PodExecutionRoleD1151CCF', 'Arn' ] }, + selectors: [ { namespace: 'namespace2' } ], + }, + }, + DependsOn: [ + 'MyClusterfargateprofileMyProfile1PodExecutionRole794E9E37', + 'MyClusterfargateprofileMyProfile1879D501A', + ], + }, ResourcePart.CompleteDefinition)); + + test.done(); + }, + + 'fargate role is added to RBAC'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + new eks.FargateCluster(stack, 'FargateCluster'); + + // THEN + expect(stack).to(haveResource('Custom::AWSCDK-EKS-KubernetesResource', { + Manifest: { + 'Fn::Join': [ + '', + [ + '[{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"aws-auth","namespace":"kube-system"},"data":{"mapRoles":"[{\\"rolearn\\":\\"', + { + 'Fn::GetAtt': [ + 'FargateClusterfargateprofiledefaultPodExecutionRole66F2610E', + 'Arn', + ], + }, + '\\",\\"username\\":\\"system:node:{{SessionName}}\\",\\"groups\\":[\\"system:bootstrappers\\",\\"system:nodes\\",\\"system:node-proxier\\"]}]","mapUsers":"[]","mapAccounts":"[]"}}]', + ], + ], + }, + })); + test.done(); + }, + + 'cannot be added to a cluster without kubectl enabled'(test: Test) { + // GIVEN + const stack = new Stack(); + const cluster = new eks.Cluster(stack, 'MyCluster', { kubectlEnabled: false }); + + // WHEN + test.throws(() => new eks.FargateProfile(stack, 'MyFargateProfile', { + cluster, + selectors: [ { namespace: 'default' } ], + }), /unsupported/); + + 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-eks/test/test.helm-chart.ts b/packages/@aws-cdk/aws-eks/test/test.helm-chart.ts index ea7828d18fdee..7fed84a2b6185 100644 --- a/packages/@aws-cdk/aws-eks/test/test.helm-chart.ts +++ b/packages/@aws-cdk/aws-eks/test/test.helm-chart.ts @@ -1,4 +1,5 @@ import { expect, haveResource } from '@aws-cdk/assert'; +import { Duration } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as eks from '../lib'; import { testFixtureCluster } from './util'; @@ -70,7 +71,18 @@ export = { new eks.HelmChart(stack, 'MyWaitingChart', { cluster, chart: 'chart' }); // THEN - expect(stack).to(haveResource(eks.HelmChart.RESOURCE_TYPE, { Wait: false})); + expect(stack).to(haveResource(eks.HelmChart.RESOURCE_TYPE, { Wait: false })); + test.done(); + }, + 'should timeout only after 10 minutes'(test: Test) { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + + // WHEN + new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart', timeout: Duration.minutes(10) }); + + // THEN + expect(stack).to(haveResource(eks.HelmChart.RESOURCE_TYPE, { Timeout: 600 })); 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-elasticloadbalancingv2/lib/nlb/network-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts index f122b7470ce63..691d2b100a64e 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts @@ -3,6 +3,7 @@ import { BaseListener } from '../shared/base-listener'; import { HealthCheck } from '../shared/base-target-group'; import { Protocol, SslPolicy } from '../shared/enums'; import { IListenerCertificate } from '../shared/listener-certificate'; +import { validateNetworkProtocol } from '../shared/util'; import { NetworkListenerAction } from './network-listener-action'; import { INetworkLoadBalancer } from './network-load-balancer'; import { INetworkLoadBalancerTarget, INetworkTargetGroup, NetworkTargetGroup } from './network-target-group'; @@ -43,7 +44,7 @@ export interface BaseNetworkListenerProps { readonly defaultAction?: NetworkListenerAction; /** - * Protocol for listener, expects TCP or TLS + * Protocol for listener, expects TCP, TLS, UDP, or TCP_UDP. * * @default - TLS if certificates are provided. TCP otherwise. */ @@ -110,9 +111,7 @@ export class NetworkListener extends BaseListener implements INetworkListener { const certs = props.certificates || []; const proto = props.protocol || (certs.length > 0 ? Protocol.TLS : Protocol.TCP); - if (NLB_PROTOCOLS.indexOf(proto) === -1) { - throw new Error(`The protocol must be one of ${NLB_PROTOCOLS.join(', ')}. Found ${props.protocol}`); - } + validateNetworkProtocol(proto); if (proto === Protocol.TLS && certs.filter(v => v != null).length === 0) { throw new Error('When the protocol is set to TLS, you must specify certificates'); @@ -285,5 +284,3 @@ export interface AddNetworkTargetsProps { */ readonly healthCheck?: HealthCheck; } - -const NLB_PROTOCOLS = [Protocol.TCP, Protocol.TLS, Protocol.UDP, Protocol.TCP_UDP]; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts index f8fa7cdb1f483..12e5ea3dd715f 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts @@ -3,6 +3,7 @@ import { BaseTargetGroupProps, HealthCheck, ITargetGroup, loadBalancerNameFromLi TargetGroupAttributes, TargetGroupBase, TargetGroupImportProps } from '../shared/base-target-group'; import { Protocol } from '../shared/enums'; import { ImportedTargetGroupBase } from '../shared/imported'; +import { validateNetworkProtocol } from '../shared/util'; import { INetworkListener } from './network-listener'; /** @@ -14,6 +15,13 @@ export interface NetworkTargetGroupProps extends BaseTargetGroupProps { */ readonly port: number; + /** + * Protocol for target group, expects TCP, TLS, UDP, or TCP_UDP. + * + * @default - TCP + */ + readonly protocol?: Protocol; + /** * Indicates whether Proxy Protocol version 2 is enabled. * @@ -56,8 +64,11 @@ export class NetworkTargetGroup extends TargetGroupBase implements INetworkTarge private readonly listeners: INetworkListener[]; constructor(scope: cdk.Construct, id: string, props: NetworkTargetGroupProps) { + const proto = props.protocol || Protocol.TCP; + validateNetworkProtocol(proto); + super(scope, id, props, { - protocol: Protocol.TCP, + protocol: proto, port: props.port, }); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts index b0150557b015a..3281aa8deb02f 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts @@ -1,4 +1,4 @@ -import { ApplicationProtocol } from './enums'; +import { ApplicationProtocol, Protocol } from './enums'; export type Attributes = {[key: string]: string | undefined}; @@ -68,3 +68,15 @@ export function determineProtocolAndPort(protocol: ApplicationProtocol | undefin export function ifUndefined(x: T | undefined, def: T) { return x !== undefined ? x : def; } + +/** + * Helper function for ensuring network listeners and target groups only accept valid + * protocols. + */ +export function validateNetworkProtocol(protocol: Protocol) { + const NLB_PROTOCOLS = [Protocol.TCP, Protocol.TLS, Protocol.UDP, Protocol.TCP_UDP]; + + if (NLB_PROTOCOLS.indexOf(protocol) === -1) { + throw new Error(`The protocol must be one of ${NLB_PROTOCOLS.join(', ')}. Found ${protocol}`); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.target-group.ts index e65ccd12aa1c1..08f7d8396b662 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.target-group.ts @@ -54,4 +54,52 @@ export = { test.done(); }, -}; + + 'Configure protocols for target group'(test: Test) { + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc'); + + new elbv2.NetworkTargetGroup(stack, 'Group', { + vpc, + port: 80, + protocol: elbv2.Protocol.UDP, + }); + + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::TargetGroup', { + Protocol: 'UDP', + })); + + test.done(); + }, + + 'Target group defaults to TCP'(test: Test) { + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc'); + + new elbv2.NetworkTargetGroup(stack, 'Group', { + vpc, + port: 80, + }); + + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::TargetGroup', { + Protocol: 'TCP', + })); + + test.done(); + }, + + 'Throws error for unacceptable protocol'(test: Test) { + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc'); + + test.throws(() => { + new elbv2.NetworkTargetGroup(stack, 'Group', { + vpc, + port: 80, + protocol: elbv2.Protocol.HTTPS, + }); + }); + + test.done(); + }, +}; \ No newline at end of file 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/README.md b/packages/@aws-cdk/aws-events-targets/README.md index 57d52739fe23f..686c26c270bbc 100644 --- a/packages/@aws-cdk/aws-events-targets/README.md +++ b/packages/@aws-cdk/aws-events-targets/README.md @@ -1,4 +1,4 @@ -# Event Targets for AWS CloudWatch Events +# Event Targets for Amazon EventBridge --- @@ -7,7 +7,7 @@ --- -This library contains integration classes to send AWS CloudWatch Events to any +This library contains integration classes to send Amazon EventBridge to any number of supported AWS Services. Instances of these classes should be passed to the `rule.addTarget()` method. @@ -25,6 +25,5 @@ Currently supported are: * Put a record to a Kinesis stream See the README of the `@aws-cdk/aws-events` library for more information on -CloudWatch Events. - +EventBridge. diff --git a/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts b/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts index b47f23e6e9a2b..9eade462aae13 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts @@ -78,7 +78,7 @@ export class AwsApi implements events.IRuleTarget { /** * Returns a RuleTarget that can be used to trigger this AwsApi as a - * result from a CloudWatch event. + * result from an EventBridge event. */ public bind(rule: events.IRule, id?: string): events.RuleTargetConfig { const handler = new lambda.SingletonFunction(rule as events.Rule, `${rule.node.id}${id}Handler`, { diff --git a/packages/@aws-cdk/aws-events-targets/lib/batch.ts b/packages/@aws-cdk/aws-events-targets/lib/batch.ts index e498483faf4c2..69f9a52fdbb35 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/batch.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/batch.ts @@ -13,7 +13,7 @@ export interface BatchJobProps { * * This will be the payload sent to the Lambda Function. * - * @default the entire CloudWatch event + * @default the entire EventBridge event */ readonly event?: events.RuleTargetInput; @@ -54,7 +54,7 @@ export class BatchJob implements events.IRuleTarget { /** * Returns a RuleTarget that can be used to trigger queue this batch job as a - * result from a CloudWatch event. + * result from an EventBridge event. */ public bind(rule: events.IRule, _id?: string): events.RuleTargetConfig { const batchParameters: events.CfnRule.BatchParametersProperty = { diff --git a/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts b/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts index 81cd756972aa0..e38287c0cdc07 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts @@ -12,13 +12,13 @@ export interface CodeBuildProjectProps { * * This will be the payload for the StartBuild API. * - * @default - the entire CloudWatch event + * @default - the entire EventBridge event */ readonly event?: events.RuleTargetInput; } /** - * Start a CodeBuild build when an AWS CloudWatch events rule is triggered. + * Start a CodeBuild build when an Amazon EventBridge rule is triggered. */ export class CodeBuildProject implements events.IRuleTarget { constructor( diff --git a/packages/@aws-cdk/aws-events-targets/lib/codepipeline.ts b/packages/@aws-cdk/aws-events-targets/lib/codepipeline.ts index 4d0fdb0ae8250..4ed12809fcc23 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/codepipeline.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/codepipeline.ts @@ -17,7 +17,7 @@ export interface CodePipelineTargetOptions { } /** - * Allows the pipeline to be used as a CloudWatch event rule target. + * Allows the pipeline to be used as an EventBridge rule target. */ export class CodePipeline implements events.IRuleTarget { constructor( diff --git a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts index 57cf46b6d4aeb..4565ee8b9b0f1 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts @@ -114,7 +114,7 @@ export class EcsTask implements events.IRuleTarget { } /** - * Allows using tasks as target of CloudWatch events + * Allows using tasks as target of EventBridge events */ public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig { const policyStatements = [new iam.PolicyStatement({ @@ -126,7 +126,7 @@ export class EcsTask implements events.IRuleTarget { })]; // If it so happens that a Task Execution Role was created for the TaskDefinition, - // then the CloudWatch Events Role must have permissions to pass it (otherwise it doesn't). + // then the EventBridge Role must have permissions to pass it (otherwise it doesn't). if (this.taskDefinition.executionRole !== undefined) { policyStatements.push(new iam.PolicyStatement({ actions: ['iam:PassRole'], diff --git a/packages/@aws-cdk/aws-events-targets/lib/lambda.ts b/packages/@aws-cdk/aws-events-targets/lib/lambda.ts index 84a78a663e405..780cd6d57162c 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/lambda.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/lambda.ts @@ -11,7 +11,7 @@ export interface LambdaFunctionProps { * * This will be the payload sent to the Lambda Function. * - * @default the entire CloudWatch event + * @default the entire EventBridge event */ readonly event?: events.RuleTargetInput; } @@ -26,7 +26,7 @@ export class LambdaFunction implements events.IRuleTarget { /** * Returns a RuleTarget that can be used to trigger this Lambda as a - * result from a CloudWatch event. + * result from an EventBridge event. */ public bind(rule: events.IRule, _id?: string): events.RuleTargetConfig { // Allow handler to be called from rule diff --git a/packages/@aws-cdk/aws-events-targets/lib/sns.ts b/packages/@aws-cdk/aws-events-targets/lib/sns.ts index 041310163f547..0584f8b924871 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/sns.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/sns.ts @@ -9,13 +9,13 @@ export interface SnsTopicProps { /** * The message to send to the topic * - * @default the entire CloudWatch event + * @default the entire EventBridge event */ readonly message?: events.RuleTargetInput; } /** - * Use an SNS topic as a target for AWS CloudWatch event rules. + * Use an SNS topic as a target for Amazon EventBridge rules. * * @example * @@ -30,9 +30,9 @@ export class SnsTopic implements events.IRuleTarget { /** * Returns a RuleTarget that can be used to trigger this SNS topic as a - * result from a CloudWatch event. + * result from an EventBridge event. * - * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/resource-based-policies-cwe.html#sns-permissions + * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/resource-based-policies-eventbridge.html#sns-permissions */ public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig { // deduplicated automatically diff --git a/packages/@aws-cdk/aws-events-targets/lib/sqs.ts b/packages/@aws-cdk/aws-events-targets/lib/sqs.ts index b18686db081d1..f19df1b01f136 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/sqs.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/sqs.ts @@ -21,14 +21,14 @@ export interface SqsQueueProps { * * Must be a valid JSON text passed to the target queue. * - * @default the entire CloudWatch event + * @default the entire EventBridge event */ readonly message?: events.RuleTargetInput; } /** - * Use an SQS Queue as a target for AWS CloudWatch event rules. + * Use an SQS Queue as a target for Amazon EventBridge rules. * * @example * @@ -47,9 +47,9 @@ export class SqsQueue implements events.IRuleTarget { /** * Returns a RuleTarget that can be used to trigger this SQS queue as a - * result from a CloudWatch event. + * result from an EventBridge event. * - * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/resource-based-policies-cwe.html#sqs-permissions + * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/resource-based-policies-eventbridge.html#sqs-permissions */ public bind(rule: events.IRule, _id?: string): events.RuleTargetConfig { // deduplicated automatically diff --git a/packages/@aws-cdk/aws-events-targets/lib/state-machine.ts b/packages/@aws-cdk/aws-events-targets/lib/state-machine.ts index 78dcbad9ba13c..5fa41ffae689f 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/state-machine.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/state-machine.ts @@ -10,13 +10,13 @@ export interface SfnStateMachineProps { /** * The input to the state machine execution * - * @default the entire CloudWatch event + * @default the entire EventBridge event */ readonly input?: events.RuleTargetInput; } /** - * Use a StepFunctions state machine as a target for AWS CloudWatch event rules. + * Use a StepFunctions state machine as a target for Amazon EventBridge rules. */ export class SfnStateMachine implements events.IRuleTarget { constructor(public readonly machine: sfn.IStateMachine, private readonly props: SfnStateMachineProps = {}) { @@ -25,7 +25,7 @@ export class SfnStateMachine implements events.IRuleTarget { /** * Returns a properties that are used in an Rule to trigger this State Machine * - * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/resource-based-policies-cwe.html#sns-permissions + * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/resource-based-policies-eventbridge.html#sns-permissions */ public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig { return { diff --git a/packages/@aws-cdk/aws-events-targets/lib/util.ts b/packages/@aws-cdk/aws-events-targets/lib/util.ts index b38bc72f21e11..ddcd83adb5f1b 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/util.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/util.ts @@ -4,7 +4,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import { Construct, IConstruct } from '@aws-cdk/core'; /** - * Obtain the Role for the CloudWatch event + * Obtain the Role for the EventBridge event * * If a role already exists, it will be returned. This ensures that if multiple * events have the same target, they will share a role. diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index 64b7060517815..08853301a4c63 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -1,7 +1,7 @@ { "name": "@aws-cdk/aws-events-targets", "version": "0.0.0", - "description": "Event targets for AWS CloudWatch Events", + "description": "Event targets for Amazon EventBridge", "main": "lib/index.js", "types": "lib/index.d.ts", "jsii": { @@ -56,7 +56,8 @@ "cdk", "constructs", "cloudwatch", - "events" + "events", + "eventbridge" ], "author": { "name": "Amazon Web Services", @@ -67,7 +68,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-targets/test/aws-api/integ.aws-api.expected.json b/packages/@aws-cdk/aws-events-targets/test/aws-api/integ.aws-api.expected.json index f7cb8a26cca19..5d4b2eaedfcb2 100644 --- a/packages/@aws-cdk/aws-events-targets/test/aws-api/integ.aws-api.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/aws-api/integ.aws-api.expected.json @@ -96,7 +96,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters4004cc41f85e7313875ae00610314ab53e83faa96c9696615bb8411b96377a4bS3Bucket386D1CAE" + "Ref": "AssetParameters4e52413f31cff0a335f5083fa6197a6cb61928644842d89026c42c2d2a98342eS3Bucket38E36746" }, "S3Key": { "Fn::Join": [ @@ -109,7 +109,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4004cc41f85e7313875ae00610314ab53e83faa96c9696615bb8411b96377a4bS3VersionKey633D63CD" + "Ref": "AssetParameters4e52413f31cff0a335f5083fa6197a6cb61928644842d89026c42c2d2a98342eS3VersionKeyFB07730C" } ] } @@ -122,7 +122,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4004cc41f85e7313875ae00610314ab53e83faa96c9696615bb8411b96377a4bS3VersionKey633D63CD" + "Ref": "AssetParameters4e52413f31cff0a335f5083fa6197a6cb61928644842d89026c42c2d2a98342eS3VersionKeyFB07730C" } ] } @@ -219,17 +219,17 @@ } }, "Parameters": { - "AssetParameters4004cc41f85e7313875ae00610314ab53e83faa96c9696615bb8411b96377a4bS3Bucket386D1CAE": { + "AssetParameters4e52413f31cff0a335f5083fa6197a6cb61928644842d89026c42c2d2a98342eS3Bucket38E36746": { "Type": "String", - "Description": "S3 bucket for asset \"4004cc41f85e7313875ae00610314ab53e83faa96c9696615bb8411b96377a4b\"" + "Description": "S3 bucket for asset \"4e52413f31cff0a335f5083fa6197a6cb61928644842d89026c42c2d2a98342e\"" }, - "AssetParameters4004cc41f85e7313875ae00610314ab53e83faa96c9696615bb8411b96377a4bS3VersionKey633D63CD": { + "AssetParameters4e52413f31cff0a335f5083fa6197a6cb61928644842d89026c42c2d2a98342eS3VersionKeyFB07730C": { "Type": "String", - "Description": "S3 key for asset version \"4004cc41f85e7313875ae00610314ab53e83faa96c9696615bb8411b96377a4b\"" + "Description": "S3 key for asset version \"4e52413f31cff0a335f5083fa6197a6cb61928644842d89026c42c2d2a98342e\"" }, - "AssetParameters4004cc41f85e7313875ae00610314ab53e83faa96c9696615bb8411b96377a4bArtifactHash5D181EB7": { + "AssetParameters4e52413f31cff0a335f5083fa6197a6cb61928644842d89026c42c2d2a98342eArtifactHash3C551617": { "Type": "String", - "Description": "Artifact hash for asset \"4004cc41f85e7313875ae00610314ab53e83faa96c9696615bb8411b96377a4b\"" + "Description": "Artifact hash for asset \"4e52413f31cff0a335f5083fa6197a6cb61928644842d89026c42c2d2a98342e\"" } } -} \ No newline at end of file +} 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-events/README.md b/packages/@aws-cdk/aws-events/README.md index adade2d158367..1bc319a0c64b8 100644 --- a/packages/@aws-cdk/aws-events/README.md +++ b/packages/@aws-cdk/aws-events/README.md @@ -1,4 +1,4 @@ -## Amazon CloudWatch Events Construct Library +## Amazon EventBridge Construct Library --- @@ -9,10 +9,10 @@ --- -Amazon CloudWatch Events delivers a near real-time stream of system events that +Amazon EventBridge delivers a near real-time stream of system events that describe changes in AWS resources. For example, an AWS CodePipeline emits the [State -Change](https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html#codepipeline_event_type) +Change](https://docs.aws.amazon.com/eventbridge/latest/userguide/event-types.html#codepipeline-event-type) event when the pipeline changes it's state. * __Events__: An event indicates a change in your AWS environment. AWS resources @@ -21,11 +21,11 @@ event when the pipeline changes it's state. running, and Amazon EC2 Auto Scaling generates events when it launches or terminates instances. AWS CloudTrail publishes events when you make API calls. You can generate custom application-level events and publish them to - CloudWatch Events. You can also set up scheduled events that are generated on + EventBridge. You can also set up scheduled events that are generated on a periodic basis. For a list of services that generate events, and sample - events from each service, see [CloudWatch Events Event Examples From Each + events from each service, see [EventBridge Event Examples From Each Supported - Service](https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html). + Service](https://docs.aws.amazon.com/eventbridge/latest/userguide/event-types.html). * __Targets__: A target processes events. Targets can include Amazon EC2 instances, AWS Lambda functions, Kinesis streams, Amazon ECS tasks, Step Functions state machines, Amazon SNS topics, Amazon SQS queues, and built-in @@ -42,9 +42,9 @@ event when the pipeline changes it's state. ## Rule -The `Rule` construct defines a CloudWatch events rule which monitors an +The `Rule` construct defines an EventBridge rule which monitors an event based on an [event -pattern](https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CloudWatchEventsandEventPatterns.html) +pattern](https://docs.aws.amazon.com/eventbridge/latest/userguide/filtering-examples-structure.html) and invoke __event targets__ when the pattern is matched against a triggered event. Event targets are objects that implement the `IRuleTarget` interface. @@ -64,7 +64,7 @@ const onCommitRule = repo.onCommit('OnCommit', { ``` You can add additional targets, with optional [input -transformer](https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_InputTransformer.html) +transformer](https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_InputTransformer.html) using `eventRule.addTarget(target[, input])`. For example, we can add a SNS topic target which formats a human-readable message for the commit. @@ -97,7 +97,7 @@ new Rule(this, 'ScheduleRule', { }); ``` -More details in [ScheduledEvents](https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html) documentation page. +More details in [ScheduledEvents](https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html) documentation page. ## Event Targets @@ -152,7 +152,7 @@ In this situation, the CDK will wire the 2 accounts together: and make sure its deployed before the source stack **Note**: while events can span multiple accounts, they _cannot_ span different regions -(that is a CloudWatch, not CDK, limitation). +(that is an EventBridge, not CDK, limitation). For more information, see the -[AWS documentation on cross-account events](https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CloudWatchEvents-CrossAccountEventDelivery.html). +[AWS documentation on cross-account events](https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-cross-account-event-delivery.html). diff --git a/packages/@aws-cdk/aws-events/lib/event-bus.ts b/packages/@aws-cdk/aws-events/lib/event-bus.ts index 4761503fab180..366c259685527 100644 --- a/packages/@aws-cdk/aws-events/lib/event-bus.ts +++ b/packages/@aws-cdk/aws-events/lib/event-bus.ts @@ -96,7 +96,7 @@ export interface EventBusAttributes { } /** - * Define a CloudWatch EventBus + * Define an EventBridge EventBus * * @resource AWS::Events::EventBus */ @@ -145,7 +145,7 @@ export class EventBus extends Resource implements IEventBus { */ public static grantPutEvents(grantee: iam.IGrantable): iam.Grant { // It's currently not possible to restrict PutEvents to specific resources. - // See https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/permissions-reference-cwe.html + // See https://docs.aws.amazon.com/eventbridge/latest/userguide/permissions-reference-eventbridge.html return iam.Grant.addToPrincipal({ grantee, actions: ['events:PutEvents'], diff --git a/packages/@aws-cdk/aws-events/lib/input.ts b/packages/@aws-cdk/aws-events/lib/input.ts index b712b5b743ddc..55a9cc667646e 100644 --- a/packages/@aws-cdk/aws-events/lib/input.ts +++ b/packages/@aws-cdk/aws-events/lib/input.ts @@ -116,7 +116,7 @@ class LiteralEventInput extends RuleTargetInput { * * One weird exception: if we're in object context, we MUST skip the quotes * around the placeholder. I assume this is so once a trivial string replace is - * done later on by CWE, numbers are still numbers. + * done later on by EventBridge, numbers are still numbers. * * So in string context: * diff --git a/packages/@aws-cdk/aws-events/lib/on-event-options.ts b/packages/@aws-cdk/aws-events/lib/on-event-options.ts index 428d01c11b8ce..57fad3514c2f8 100644 --- a/packages/@aws-cdk/aws-events/lib/on-event-options.ts +++ b/packages/@aws-cdk/aws-events/lib/on-event-options.ts @@ -36,7 +36,7 @@ export interface OnEventOptions { * @default - No additional filtering based on an event pattern. * * @see - * http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CloudWatchEventsandEventPatterns.html + * https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-and-event-patterns.html */ readonly eventPattern?: EventPattern; } diff --git a/packages/@aws-cdk/aws-events/lib/rule-ref.ts b/packages/@aws-cdk/aws-events/lib/rule-ref.ts index 0bea16726acf1..0e189a0508949 100644 --- a/packages/@aws-cdk/aws-events/lib/rule-ref.ts +++ b/packages/@aws-cdk/aws-events/lib/rule-ref.ts @@ -1,7 +1,7 @@ import { IResource } from '@aws-cdk/core'; /** - * Represents a CloudWatch Event Rule + * Represents an EventBridge Rule */ export interface IRule extends IResource { /** diff --git a/packages/@aws-cdk/aws-events/lib/rule.ts b/packages/@aws-cdk/aws-events/lib/rule.ts index 582cd570e8bc8..1b19b5f1174a9 100644 --- a/packages/@aws-cdk/aws-events/lib/rule.ts +++ b/packages/@aws-cdk/aws-events/lib/rule.ts @@ -8,7 +8,7 @@ import { IRuleTarget } from './target'; import { mergeEventPattern } from './util'; /** - * Properties for defining a CloudWatch Event Rule + * Properties for defining an EventBridge Rule */ export interface RuleProps { /** @@ -34,11 +34,11 @@ export interface RuleProps { readonly enabled?: boolean; /** - * The schedule or rate (frequency) that determines when CloudWatch Events + * The schedule or rate (frequency) that determines when EventBridge * runs the rule. For more information, see Schedule Expression Syntax for - * Rules in the Amazon CloudWatch User Guide. + * Rules in the Amazon EventBridge User Guide. * - * @see http://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html + * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html * * You must specify this property, the `eventPattern` property, or both. * @@ -47,12 +47,12 @@ export interface RuleProps { readonly schedule?: Schedule; /** - * Describes which events CloudWatch Events routes to the specified target. + * Describes which events EventBridge routes to the specified target. * These routed events are matched events. For more information, see Events - * and Event Patterns in the Amazon CloudWatch User Guide. + * and Event Patterns in the Amazon EventBridge User Guide. * * @see - * http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CloudWatchEventsandEventPatterns.html + * https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-and-event-patterns.html * * You must specify this property (either via props or via * `addEventPattern`), the `scheduleExpression` property, or both. The @@ -82,14 +82,14 @@ export interface RuleProps { } /** - * Defines a CloudWatch Event Rule in this stack. + * Defines an EventBridge Rule in this stack. * * @resource AWS::Events::Rule */ export class Rule extends Resource implements IRule { /** - * Import an existing CloudWatch Event Rule provided an ARN + * Import an existing EventBridge Rule provided an ARN * * @param scope The parent creating construct (usually `this`). * @param id The construct's name. @@ -184,7 +184,7 @@ export class Rule extends Resource implements IRule { if (targetAccount !== sourceAccount) { // cross-account event - strap in, this works differently than regular events! // based on: - // https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CloudWatchEvents-CrossAccountEventDelivery.html + // https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-cross-account-event-delivery.html // for cross-account events, we require concrete accounts if (Token.isUnresolved(targetAccount)) { @@ -218,7 +218,7 @@ export class Rule extends Resource implements IRule { // Grant the source account permissions to publish events to the event bus of the target account. // Do it in a separate stack instead of the target stack (which seems like the obvious place to put it), // because it needs to be deployed before the rule containing the above event-bus target in the source stack - // (CloudWatch verifies whether you have permissions to the targets on rule creation), + // (EventBridge verifies whether you have permissions to the targets on rule creation), // but it's common for the target stack to depend on the source stack // (that's the case with CodePipeline, for example) const sourceApp = this.node.root; diff --git a/packages/@aws-cdk/aws-events/lib/schedule.ts b/packages/@aws-cdk/aws-events/lib/schedule.ts index 719862f9ab009..1e0a391e2b911 100644 --- a/packages/@aws-cdk/aws-events/lib/schedule.ts +++ b/packages/@aws-cdk/aws-events/lib/schedule.ts @@ -7,7 +7,7 @@ export abstract class Schedule { /** * Construct a schedule from a literal schedule expression * - * @param expression The expression to use. Must be in a format that Cloudwatch Events will recognize + * @param expression The expression to use. Must be in a format that EventBridge will recognize */ public static expression(expression: string): Schedule { return new LiteralSchedule(expression); @@ -62,7 +62,7 @@ export abstract class Schedule { * All fields are strings so you can use complex expressions. Absence of * a field implies '*' or '?', whichever one is appropriate. * - * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions + * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html#cron-expressions */ export interface CronOptions { /** diff --git a/packages/@aws-cdk/aws-events/lib/target.ts b/packages/@aws-cdk/aws-events/lib/target.ts index 3287d04b120e5..319cf3d4f14da 100644 --- a/packages/@aws-cdk/aws-events/lib/target.ts +++ b/packages/@aws-cdk/aws-events/lib/target.ts @@ -12,7 +12,7 @@ export interface IRuleTarget { * Returns the rule target specification. * NOTE: Do not use the various `inputXxx` options. They can be set in a call to `addTarget`. * - * @param rule The CloudWatch Event Rule that would trigger this target. + * @param rule The EventBridge Rule that would trigger this target. * @param id The id of the target that will be attached to the rule. */ bind(rule: IRule, id?: string): RuleTargetConfig; @@ -87,7 +87,7 @@ export interface RuleTargetConfig { * if so, we generate a more complex setup, * including an additional stack containing the EventBusPolicy. * - * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CloudWatchEvents-CrossAccountEventDelivery.html + * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-cross-account-event-delivery.html * @default the target is not backed by any resource */ readonly targetResource?: IConstruct; diff --git a/packages/@aws-cdk/aws-events/package.json b/packages/@aws-cdk/aws-events/package.json index b8160a0a2d398..112095403d69d 100644 --- a/packages/@aws-cdk/aws-events/package.json +++ b/packages/@aws-cdk/aws-events/package.json @@ -1,7 +1,7 @@ { "name": "@aws-cdk/aws-events", "version": "0.0.0", - "description": "AWS CloudWatch Events Construct Library", + "description": "Amazon EventBridge Construct Library", "main": "lib/index.js", "types": "lib/index.d.ts", "jsii": { @@ -54,7 +54,8 @@ "cdk", "constructs", "cloudwatch", - "events" + "events", + "eventbridge" ], "author": { "name": "Amazon Web Services", 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-iam/test/integ.condition-with-ref.expected.json b/packages/@aws-cdk/aws-iam/test/integ.condition-with-ref.expected.json index db82b0544e2bd..66957c3979200 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.condition-with-ref.expected.json +++ b/packages/@aws-cdk/aws-iam/test/integ.condition-with-ref.expected.json @@ -4,17 +4,17 @@ "Type": "String", "Default": "developer" }, - "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3BucketEF5DD638": { + "AssetParameters3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086S3Bucket57C0655B": { "Type": "String", - "Description": "S3 bucket for asset \"e02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57\"" + "Description": "S3 bucket for asset \"3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086\"" }, - "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3VersionKey2EA0DB2E": { + "AssetParameters3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086S3VersionKey4BC65AD6": { "Type": "String", - "Description": "S3 key for asset version \"e02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57\"" + "Description": "S3 key for asset version \"3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086\"" }, - "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57ArtifactHash95B71D2D": { + "AssetParameters3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086ArtifactHashD8D99435": { "Type": "String", - "Description": "Artifact hash for asset \"e02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57\"" + "Description": "Artifact hash for asset \"3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086\"" } }, "Resources": { @@ -70,7 +70,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3BucketEF5DD638" + "Ref": "AssetParameters3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086S3Bucket57C0655B" }, "S3Key": { "Fn::Join": [ @@ -83,7 +83,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3VersionKey2EA0DB2E" + "Ref": "AssetParameters3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086S3VersionKey4BC65AD6" } ] } @@ -96,7 +96,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3VersionKey2EA0DB2E" + "Ref": "AssetParameters3b28f4ee261986c158a160900e3042a61238f644fe502199d60bcea592128086S3VersionKey4BC65AD6" } ] } @@ -162,4 +162,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-iam/test/integ.oidc-provider.expected.json b/packages/@aws-cdk/aws-iam/test/integ.oidc-provider.expected.json index 11c2bd0f23a4c..4b04dd157155e 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.oidc-provider.expected.json +++ b/packages/@aws-cdk/aws-iam/test/integ.oidc-provider.expected.json @@ -62,7 +62,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319S3Bucket718B603F" + "Ref": "AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3Bucket0C424907" }, "S3Key": { "Fn::Join": [ @@ -75,7 +75,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319S3VersionKey6B97A1A3" + "Ref": "AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3VersionKey6841F1F8" } ] } @@ -88,7 +88,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319S3VersionKey6B97A1A3" + "Ref": "AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3VersionKey6841F1F8" } ] } @@ -151,17 +151,17 @@ } }, "Parameters": { - "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319S3Bucket718B603F": { + "AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3Bucket0C424907": { "Type": "String", - "Description": "S3 bucket for asset \"4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319\"" + "Description": "S3 bucket for asset \"ea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161\"" }, - "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319S3VersionKey6B97A1A3": { + "AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3VersionKey6841F1F8": { "Type": "String", - "Description": "S3 key for asset version \"4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319\"" + "Description": "S3 key for asset version \"ea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161\"" }, - "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319ArtifactHash96BDDF33": { + "AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161ArtifactHash67B22EF2": { "Type": "String", - "Description": "Artifact hash for asset \"4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319\"" + "Description": "Artifact hash for asset \"ea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161\"" } } -} \ No newline at end of file +} 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-nodejs/test/integ.function.expected.json b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.function.expected.json index dc69e03b48c13..6633fb1402bf7 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/integ.function.expected.json +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.function.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersa736580e4382e6d67a7acaedbbd271f559ed918382808ae1431364ae70286543S3BucketD096DC83" + "Ref": "AssetParameters20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9S3Bucket7F316AC2" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersa736580e4382e6d67a7acaedbbd271f559ed918382808ae1431364ae70286543S3VersionKeyA76287A5" + "Ref": "AssetParameters20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9S3VersionKeyEEDC3772" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersa736580e4382e6d67a7acaedbbd271f559ed918382808ae1431364ae70286543S3VersionKeyA76287A5" + "Ref": "AssetParameters20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9S3VersionKeyEEDC3772" } ] } @@ -121,7 +121,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters0d445d366e339b4344917ba638a4cb2dcddfd0e063b10fb909340fd1cc51c278S3BucketDBD288E6" + "Ref": "AssetParameters39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990S3Bucket88C76A86" }, "S3Key": { "Fn::Join": [ @@ -134,7 +134,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters0d445d366e339b4344917ba638a4cb2dcddfd0e063b10fb909340fd1cc51c278S3VersionKey81058FB7" + "Ref": "AssetParameters39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990S3VersionKey1E5E4562" } ] } @@ -147,7 +147,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters0d445d366e339b4344917ba638a4cb2dcddfd0e063b10fb909340fd1cc51c278S3VersionKey81058FB7" + "Ref": "AssetParameters39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990S3VersionKey1E5E4562" } ] } @@ -172,29 +172,29 @@ } }, "Parameters": { - "AssetParametersa736580e4382e6d67a7acaedbbd271f559ed918382808ae1431364ae70286543S3BucketD096DC83": { + "AssetParameters20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9S3Bucket7F316AC2": { "Type": "String", - "Description": "S3 bucket for asset \"a736580e4382e6d67a7acaedbbd271f559ed918382808ae1431364ae70286543\"" + "Description": "S3 bucket for asset \"20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9\"" }, - "AssetParametersa736580e4382e6d67a7acaedbbd271f559ed918382808ae1431364ae70286543S3VersionKeyA76287A5": { + "AssetParameters20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9S3VersionKeyEEDC3772": { "Type": "String", - "Description": "S3 key for asset version \"a736580e4382e6d67a7acaedbbd271f559ed918382808ae1431364ae70286543\"" + "Description": "S3 key for asset version \"20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9\"" }, - "AssetParametersa736580e4382e6d67a7acaedbbd271f559ed918382808ae1431364ae70286543ArtifactHashB7337532": { + "AssetParameters20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9ArtifactHash8387A82E": { "Type": "String", - "Description": "Artifact hash for asset \"a736580e4382e6d67a7acaedbbd271f559ed918382808ae1431364ae70286543\"" + "Description": "Artifact hash for asset \"20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9\"" }, - "AssetParameters0d445d366e339b4344917ba638a4cb2dcddfd0e063b10fb909340fd1cc51c278S3BucketDBD288E6": { + "AssetParameters39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990S3Bucket88C76A86": { "Type": "String", - "Description": "S3 bucket for asset \"0d445d366e339b4344917ba638a4cb2dcddfd0e063b10fb909340fd1cc51c278\"" + "Description": "S3 bucket for asset \"39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990\"" }, - "AssetParameters0d445d366e339b4344917ba638a4cb2dcddfd0e063b10fb909340fd1cc51c278S3VersionKey81058FB7": { + "AssetParameters39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990S3VersionKey1E5E4562": { "Type": "String", - "Description": "S3 key for asset version \"0d445d366e339b4344917ba638a4cb2dcddfd0e063b10fb909340fd1cc51c278\"" + "Description": "S3 key for asset version \"39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990\"" }, - "AssetParameters0d445d366e339b4344917ba638a4cb2dcddfd0e063b10fb909340fd1cc51c278ArtifactHashF5C1730B": { + "AssetParameters39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990ArtifactHash37B9CB08": { "Type": "String", - "Description": "Artifact hash for asset \"0d445d366e339b4344917ba638a4cb2dcddfd0e063b10fb909340fd1cc51c278\"" + "Description": "Artifact hash for asset \"39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990\"" } } -} \ No newline at end of file +} 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.current-version.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json index 96d17d375e9c1..1a3ea891ebc6d 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dS3Bucket34E3DBD0" + "Ref": "AssetParameters8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34S3BucketB47CCF1E" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dS3VersionKey585C4BED" + "Ref": "AssetParameters8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34S3VersionKey80D7B84B" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dS3VersionKey585C4BED" + "Ref": "AssetParameters8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34S3VersionKey80D7B84B" } ] } @@ -85,7 +85,7 @@ "MyLambdaServiceRole4539ECB6" ] }, - "MyLambdaCurrentVersionE7A382CC1a5358ec9d2d5ef45baeba2fbb9fa9bd": { + "MyLambdaCurrentVersionE7A382CC86b18af374d6e380aa07074d2490e2df": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -103,7 +103,7 @@ }, "Qualifier": { "Fn::GetAtt": [ - "MyLambdaCurrentVersionE7A382CC1a5358ec9d2d5ef45baeba2fbb9fa9bd", + "MyLambdaCurrentVersionE7A382CC86b18af374d6e380aa07074d2490e2df", "Version" ] }, @@ -118,7 +118,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaCurrentVersionE7A382CC1a5358ec9d2d5ef45baeba2fbb9fa9bd", + "MyLambdaCurrentVersionE7A382CC86b18af374d6e380aa07074d2490e2df", "Version" ] }, @@ -127,17 +127,17 @@ } }, "Parameters": { - "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dS3Bucket34E3DBD0": { + "AssetParameters8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34S3BucketB47CCF1E": { "Type": "String", - "Description": "S3 bucket for asset \"45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2d\"" + "Description": "S3 bucket for asset \"8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34\"" }, - "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dS3VersionKey585C4BED": { + "AssetParameters8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34S3VersionKey80D7B84B": { "Type": "String", - "Description": "S3 key for asset version \"45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2d\"" + "Description": "S3 key for asset version \"8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34\"" }, - "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dArtifactHash20CDD3D4": { + "AssetParameters8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34ArtifactHash70E274C4": { "Type": "String", - "Description": "Artifact hash for asset \"45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2d\"" + "Description": "Artifact hash for asset \"8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34\"" } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-lambda/test/integ.layer-version.lit.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.layer-version.lit.expected.json index 3a3aad036b5c6..93687882379f7 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.layer-version.lit.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.layer-version.lit.expected.json @@ -5,7 +5,7 @@ "Properties": { "Content": { "S3Bucket": { - "Ref": "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dS3Bucket34E3DBD0" + "Ref": "AssetParameters8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34S3BucketB47CCF1E" }, "S3Key": { "Fn::Join": [ @@ -18,7 +18,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dS3VersionKey585C4BED" + "Ref": "AssetParameters8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34S3VersionKey80D7B84B" } ] } @@ -31,7 +31,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dS3VersionKey585C4BED" + "Ref": "AssetParameters8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34S3VersionKey80D7B84B" } ] } @@ -117,17 +117,17 @@ } }, "Parameters": { - "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dS3Bucket34E3DBD0": { + "AssetParameters8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34S3BucketB47CCF1E": { "Type": "String", - "Description": "S3 bucket for asset \"45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2d\"" + "Description": "S3 bucket for asset \"8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34\"" }, - "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dS3VersionKey585C4BED": { + "AssetParameters8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34S3VersionKey80D7B84B": { "Type": "String", - "Description": "S3 key for asset version \"45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2d\"" + "Description": "S3 key for asset version \"8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34\"" }, - "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dArtifactHash20CDD3D4": { + "AssetParameters8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34ArtifactHash70E274C4": { "Type": "String", - "Description": "Artifact hash for asset \"45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2d\"" + "Description": "Artifact hash for asset \"8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34\"" } } -} \ No newline at end of file +} 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..f123d24edf60a 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": "AssetParameters11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847S3Bucket46EF559D" }, "S3Key": { "Fn::Join": [ @@ -146,7 +146,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3VersionKey3194A583" + "Ref": "AssetParameters11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847S3VersionKey68B7BF84" } ] } @@ -159,7 +159,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3VersionKey3194A583" + "Ref": "AssetParameters11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847S3VersionKey68B7BF84" } ] } @@ -331,17 +331,17 @@ } }, "Parameters": { - "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3Bucket7046E6CE": { + "AssetParameters11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847S3Bucket46EF559D": { "Type": "String", - "Description": "S3 bucket for asset \"82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4e\"" + "Description": "S3 bucket for asset \"11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847\"" }, - "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3VersionKey3194A583": { + "AssetParameters11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847S3VersionKey68B7BF84": { "Type": "String", - "Description": "S3 key for asset version \"82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4e\"" + "Description": "S3 key for asset version \"11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847\"" }, - "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eArtifactHashB967D42A": { + "AssetParameters11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847ArtifactHash27BA7171": { "Type": "String", - "Description": "Artifact hash for asset \"82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4e\"" + "Description": "Artifact hash for asset \"11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847\"" } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-lambda/test/test.layers.ts b/packages/@aws-cdk/aws-lambda/test/test.layers.ts index 705cd6628a617..3ec35f172f382 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.layers.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.layers.ts @@ -87,7 +87,7 @@ export = testCase({ // THEN expect(stack).to(haveResource('AWS::Lambda::LayerVersion', { Metadata: { - 'aws:asset:path': 'asset.45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2d', + 'aws:asset:path': 'asset.8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34', 'aws:asset:property': 'Content', }, }, ResourcePart.CompleteDefinition)); 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-logs/README.md b/packages/@aws-cdk/aws-logs/README.md index 478e981010d63..8d86dfa65e1a7 100644 --- a/packages/@aws-cdk/aws-logs/README.md +++ b/packages/@aws-cdk/aws-logs/README.md @@ -84,6 +84,31 @@ Will extract the value of `jsonField` wherever it occurs in JSON-structed log records in the LogGroup, and emit them to CloudWatch Metrics under the name `Namespace/MetricName`. +#### Exposing Metric on a Metric Filter + +You can expose a metric on a metric filter by calling the `MetricFilter.metric()` API. +This has a default of `statistic = 'avg'` if the statistic is not set in the `props`. + +```ts +const mf = new MetricFilter(this, 'MetricFilter', { + logGroup, + metricNamespace: 'MyApp', + metricName: 'Latency', + filterPattern: FilterPattern.exists('$.latency'), + metricValue: '$.latency', +}); + +//expose a metric from the metric filter +const metric = mf.metric(); + +//you can use the metric to create a new alarm +new Alarm(this, 'alarm from metric filter', { + metric, + threshold: 100, + evaluationPeriods: 2, +}); +``` + ### Patterns Patterns describe which log events match a subscription or metric filter. There diff --git a/packages/@aws-cdk/aws-logs/lib/metric-filter.ts b/packages/@aws-cdk/aws-logs/lib/metric-filter.ts index 4338fcfc6013c..3b8fe826d053e 100644 --- a/packages/@aws-cdk/aws-logs/lib/metric-filter.ts +++ b/packages/@aws-cdk/aws-logs/lib/metric-filter.ts @@ -1,3 +1,4 @@ +import { Metric, MetricOptions } from '@aws-cdk/aws-cloudwatch'; import { Construct, Resource } from '@aws-cdk/core'; import { ILogGroup, MetricFilterOptions } from './log-group'; import { CfnMetricFilter } from './logs.generated'; @@ -16,9 +17,16 @@ export interface MetricFilterProps extends MetricFilterOptions { * A filter that extracts information from CloudWatch Logs and emits to CloudWatch Metrics */ export class MetricFilter extends Resource { + + private readonly metricName: string; + private readonly metricNamespace: string; + constructor(scope: Construct, id: string, props: MetricFilterProps) { super(scope, id); + this.metricName = props.metricName; + this.metricNamespace = props.metricNamespace; + // It looks odd to map this object to a singleton list, but that's how // we're supposed to do it according to the docs. // @@ -38,4 +46,18 @@ export class MetricFilter extends Resource { }], }); } + + /** + * Return the given named metric for this Metric Filter + * + * @default avg over 5 minutes + */ + public metric(props?: MetricOptions): Metric { + return new Metric({ + metricName: this.metricName, + namespace: this.metricNamespace, + statistic: 'avg', + ...props, + }).attachTo(this); + } } diff --git a/packages/@aws-cdk/aws-logs/test/integ.expose-metric.expected.json b/packages/@aws-cdk/aws-logs/test/integ.expose-metric.expected.json new file mode 100644 index 0000000000000..95e3532d70b25 --- /dev/null +++ b/packages/@aws-cdk/aws-logs/test/integ.expose-metric.expected.json @@ -0,0 +1,40 @@ +{ + "Resources": { + "LogGroupF5B46931": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731 + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "MetricFilter1B93B6E5": { + "Type": "AWS::Logs::MetricFilter", + "Properties": { + "FilterPattern": "{ $.latency = \"*\" }", + "LogGroupName": { + "Ref": "LogGroupF5B46931" + }, + "MetricTransformations": [ + { + "MetricName": "Latency", + "MetricNamespace": "MyApp", + "MetricValue": "$.latency" + } + ] + } + }, + "alarmfrommetricfilterF546D67D": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 2, + "MetricName": "Latency", + "Namespace": "MyApp", + "Period": 300, + "Statistic": "Average", + "Threshold": 100 + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-logs/test/integ.expose-metric.ts b/packages/@aws-cdk/aws-logs/test/integ.expose-metric.ts new file mode 100644 index 0000000000000..dc442923a5822 --- /dev/null +++ b/packages/@aws-cdk/aws-logs/test/integ.expose-metric.ts @@ -0,0 +1,41 @@ +import { Alarm } from '@aws-cdk/aws-cloudwatch'; +import { App, RemovalPolicy, Stack, StackProps } from '@aws-cdk/core'; +import { FilterPattern, LogGroup, MetricFilter } from '../lib'; + +/* + * Stack verification steps: + * + * -- aws cloudwatch describe-alarms --alarm-name-prefix aws-cdk-expose-metric-integ + * has Namespace of `MyApp` and Statistic of `Average` + */ + +class ExposeMetricIntegStack extends Stack { + constructor(scope: App, id: string, props?: StackProps) { + super(scope, id, props); + + const logGroup = new LogGroup(this, 'LogGroup', { + removalPolicy: RemovalPolicy.DESTROY, + }); + + /// !show + const mf = new MetricFilter(this, 'MetricFilter', { + logGroup, + metricNamespace: 'MyApp', + metricName: 'Latency', + filterPattern: FilterPattern.exists('$.latency'), + metricValue: '$.latency', + }); + + new Alarm(this, 'alarm from metric filter', { + metric: mf.metric(), + threshold: 100, + evaluationPeriods: 2, + }); + + /// !hide + } +} + +const app = new App(); +new ExposeMetricIntegStack(app, 'aws-cdk-expose-metric-integ'); +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-logs/test/test.metricfilter.ts b/packages/@aws-cdk/aws-logs/test/test.metricfilter.ts index 429bce4503106..5b98dcb797ec7 100644 --- a/packages/@aws-cdk/aws-logs/test/test.metricfilter.ts +++ b/packages/@aws-cdk/aws-logs/test/test.metricfilter.ts @@ -1,4 +1,5 @@ import { expect, haveResource } from '@aws-cdk/assert'; +import { Metric } from '@aws-cdk/aws-cloudwatch'; import { Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import { FilterPattern, LogGroup, MetricFilter } from '../lib'; @@ -31,4 +32,56 @@ export = { test.done(); }, + + 'metric filter exposes metric'(test: Test) { + // GIVEN + const stack = new Stack(); + const logGroup = new LogGroup(stack, 'LogGroup'); + + // WHEN + const mf = new MetricFilter(stack, 'Subscription', { + logGroup, + metricNamespace: 'AWS/Test', + metricName: 'Latency', + metricValue: '$.latency', + filterPattern: FilterPattern.exists('$.latency'), + }); + + const metric = mf.metric(); + + // THEN + test.deepEqual(metric, new Metric({ + metricName: 'Latency', + namespace: 'AWS/Test', + statistic: 'avg', + })); + + test.done(); + }, + + 'metric filter exposes metric with custom statistic'(test: Test) { + // GIVEN + const stack = new Stack(); + const logGroup = new LogGroup(stack, 'LogGroup'); + + // WHEN + const mf = new MetricFilter(stack, 'Subscription', { + logGroup, + metricNamespace: 'AWS/Test', + metricName: 'Latency', + metricValue: '$.latency', + filterPattern: FilterPattern.exists('$.latency'), + }); + + const metric = mf.metric({statistic: 'maximum'}); + + // THEN + test.deepEqual(metric, new Metric({ + metricName: 'Latency', + namespace: 'AWS/Test', + statistic: 'maximum', + })); + + test.done(); + }, }; 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..9f591e2399a62 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": "AssetParameters11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847S3Bucket46EF559D" }, "S3Key": { "Fn::Join": [ @@ -980,7 +980,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3VersionKey3194A583" + "Ref": "AssetParameters11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847S3VersionKey68B7BF84" } ] } @@ -993,7 +993,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3VersionKey3194A583" + "Ref": "AssetParameters11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847S3VersionKey68B7BF84" } ] } @@ -1108,17 +1108,17 @@ } }, "Parameters": { - "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3Bucket7046E6CE": { + "AssetParameters11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847S3Bucket46EF559D": { "Type": "String", - "Description": "S3 bucket for asset \"82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4e\"" + "Description": "S3 bucket for asset \"11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847\"" }, - "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eS3VersionKey3194A583": { + "AssetParameters11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847S3VersionKey68B7BF84": { "Type": "String", - "Description": "S3 key for asset version \"82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4e\"" + "Description": "S3 key for asset version \"11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847\"" }, - "AssetParameters82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4eArtifactHashB967D42A": { + "AssetParameters11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847ArtifactHash27BA7171": { "Type": "String", - "Description": "Artifact hash for asset \"82c54bfa7c42ba410d6d18dad983ba51c93a5ea940818c5c20230f8b59c19d4e\"" + "Description": "Artifact hash for asset \"11aa2ce8971716ca7c8d28d472ab5e937131e78e136d0de8f4997fb11c4de847\"" } } } 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/README.md b/packages/@aws-cdk/aws-s3-assets/README.md index 07d3a88bb0208..3833f750ebe02 100644 --- a/packages/@aws-cdk/aws-s3-assets/README.md +++ b/packages/@aws-cdk/aws-s3-assets/README.md @@ -50,9 +50,6 @@ The following examples grants an IAM group read permissions on an asset: [Example of granting read access to an asset](./test/integ.assets.permissions.lit.ts) -The following example uses custom asset bundling to convert a markdown file to html: -[Example of using asset bundling](./test/integ.assets.bundling.lit.ts) - ## How does it work? When an asset is defined in a construct, a construct metadata entry @@ -73,6 +70,26 @@ the asset store, it is uploaded during deployment. Now, when the toolkit deploys the stack, it will set the relevant CloudFormation Parameters to point to the actual bucket and key for each asset. +## Asset Bundling + +When defining an asset, you can use the `bundling` option to specify a command +to run inside a docker container. The command can read the contents of the asset +source from `/asset-input` and is expected to write files under `/asset-output` +(directories mapped inside the container). The files under `/asset-output` will +be zipped and uploaded to S3 as the asset. + +The following example uses custom asset bundling to convert a markdown file to html: + +[Example of using asset bundling](./test/integ.assets.bundling.lit.ts). + +The bundling docker image (`image`) can either come from a registry (`BundlingDockerImage.fromRegistry`) +or it can be built from a `Dockerfile` located inside your project (`BundlingDockerImage.fromAsset`). + +You can set the `CDK_DOCKER` environment variable in order to provide a custom +docker program to execute. This may sometime be needed when building in +environments where the standard docker cannot be executed (see +https://github.com/aws/aws-cdk/issues/8460 for details). + ## CloudFormation Resource Metadata > NOTE: This section is relevant for authors of AWS Resource Constructs. 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/build.sh b/packages/@aws-cdk/aws-s3-deployment/lambda/build.sh index ddcbf4807ab0d..cc9df52d130df 100755 --- a/packages/@aws-cdk/aws-s3-deployment/lambda/build.sh +++ b/packages/@aws-cdk/aws-s3-deployment/lambda/build.sh @@ -27,7 +27,7 @@ cd ${staging} # install python requirements # Must use --prefix to because --target cannot be used on # platforms that have a default --prefix set. -pip3 install --ignore-installed --prefix ${piptemp} -r ${staging}/requirements.txt +pip3 install --ignore-installed --prefix ${piptemp} --no-user -r ${staging}/requirements.txt mv ${piptemp}/lib/python*/*-packages/* . [ -d ${piptemp}/lib64 ] && mv ${piptemp}/lib64/python*/*-packages/* . rm -fr ./awscli/examples 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-secretsmanager/lib/secret-rotation.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts index 706894935299a..16ff1056534bc 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts @@ -210,7 +210,9 @@ export class SecretRotation extends Construct { throw new Error('The `masterSecret` must be specified for application using the multi user scheme.'); } - const rotationFunctionName = this.node.uniqueId; + // Max length of 64 chars, get the last 64 chars + const uniqueId = this.node.uniqueId; + const rotationFunctionName = uniqueId.substring(Math.max(uniqueId.length - 64, 0), uniqueId.length); const securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc, diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.secret-rotation.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.secret-rotation.ts index bb1d7b435a46e..73eed329f232d 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/test.secret-rotation.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/test.secret-rotation.ts @@ -291,4 +291,68 @@ export = { test.done(); }, + + 'rotation function name does not exceed 64 chars'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const secret = new secretsmanager.Secret(stack, 'Secret'); + const target = new ec2.Connections({ + defaultPort: ec2.Port.tcp(3306), + securityGroups: [new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc })], + }); + + // WHEN + const id = 'SecretRotation'.repeat(5); + new secretsmanager.SecretRotation(stack, id, { + application: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, + secret, + target, + vpc, + }); + + // THEN + expect(stack).to(haveResource('AWS::Serverless::Application', { + Parameters: { + endpoint: { + 'Fn::Join': [ + '', + [ + 'https://secretsmanager.', + { + Ref: 'AWS::Region', + }, + '.', + { + Ref: 'AWS::URLSuffix', + }, + ], + ], + }, + functionName: 'RotationSecretRotationSecretRotationSecretRotationSecretRotation', + vpcSecurityGroupIds: { + 'Fn::GetAtt': [ + 'SecretRotationSecretRotationSecretRotationSecretRotationSecretRotationSecurityGroupBFCB171A', + 'GroupId', + ], + }, + vpcSubnetIds: { + 'Fn::Join': [ + '', + [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', + }, + ',', + { + Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', + }, + ], + ], + }, + }, + })); + + test.done(); + }, }; 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..6ed90de00c40c 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, }); ``` @@ -394,15 +393,13 @@ autoScalingRole.assumeRolePolicy?.addStatements( }); ) -new sfn.Task(stack, 'Create Cluster', { - task: new tasks.EmrCreateCluster({ - instances: {}, - clusterRole, - name: sfn.TaskInput.fromDataAt('$.ClusterName').value, - serviceRole, - autoScalingRole, - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }), +new tasks.EmrCreateCluster(stack, 'Create Cluster', { + instances: {}, + clusterRole, + name: sfn.TaskInput.fromDataAt('$.ClusterName').value, + serviceRole, + autoScalingRole, + integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, }); ``` @@ -414,11 +411,9 @@ terminated by user intervention, an API call, or a job-flow error. Corresponds to the [`setTerminationProtection`](https://docs.aws.amazon.com/step-functions/latest/dg/connect-emr.html) API in EMR. ```ts -new sfn.Task(stack, 'Task', { - task: new tasks.EmrSetClusterTerminationProtection({ - clusterId: 'ClusterId', - terminationProtected: false, - }), +new tasks.EmrSetClusterTerminationProtection(stack, 'Task', { + clusterId: 'ClusterId', + terminationProtected: false, }); ``` @@ -428,10 +423,8 @@ Shuts down a cluster (job flow). Corresponds to the [`terminateJobFlows`](https://docs.aws.amazon.com/emr/latest/APIReference/API_TerminateJobFlows.html) API in EMR. ```ts -new sfn.Task(stack, 'Task', { - task: new tasks.EmrTerminateCluster({ - clusterId: 'ClusterId' - }), +new tasks.EmrTerminateCluster(stack, 'Task', { + clusterId: 'ClusterId' }); ``` @@ -441,13 +434,11 @@ Adds a new step to a running cluster. Corresponds to the [`addJobFlowSteps`](https://docs.aws.amazon.com/emr/latest/APIReference/API_AddJobFlowSteps.html) API in EMR. ```ts -new sfn.Task(stack, 'Task', { - task: new tasks.EmrAddStep({ +new tasks.EmrAddStep(stack, 'Task', { clusterId: 'ClusterId', name: 'StepName', jar: 'Jar', actionOnFailure: tasks.ActionOnFailure.CONTINUE, - }), }); ``` @@ -457,11 +448,9 @@ Cancels a pending step in a running cluster. Corresponds to the [`cancelSteps`](https://docs.aws.amazon.com/emr/latest/APIReference/API_CancelSteps.html) API in EMR. ```ts -new sfn.Task(stack, 'Task', { - task: new tasks.EmrCancelStep({ - clusterId: 'ClusterId', - stepId: 'StepId', - }), +new tasks.EmrCancelStep(stack, 'Task', { + clusterId: 'ClusterId', + stepId: 'StepId', }); ``` @@ -473,13 +462,11 @@ fleet with the specified InstanceFleetName. Corresponds to the [`modifyInstanceFleet`](https://docs.aws.amazon.com/emr/latest/APIReference/API_ModifyInstanceFleet.html) API in EMR. ```ts -new sfn.Task(stack, 'Task', { - task: new tasks.EmrModifyInstanceFleetByName({ - clusterId: 'ClusterId', - instanceFleetName: 'InstanceFleetName', - targetOnDemandCapacity: 2, - targetSpotCapacity: 0, - }), +new sfn.EmrModifyInstanceFleetByName(stack, 'Task', { + clusterId: 'ClusterId', + instanceFleetName: 'InstanceFleetName', + targetOnDemandCapacity: 2, + targetSpotCapacity: 0, }); ``` @@ -490,14 +477,12 @@ Modifies the number of nodes and configuration settings of an instance group. Corresponds to the [`modifyInstanceGroups`](https://docs.aws.amazon.com/emr/latest/APIReference/API_ModifyInstanceGroups.html) API in EMR. ```ts -new sfn.Task(stack, 'Task', { - task: new tasks.EmrModifyInstanceGroupByName({ - clusterId: 'ClusterId', - instanceGroupName: sfn.Data.stringAt('$.InstanceGroupName'), - instanceGroup: { - instanceCount: 1, - }, - }), +new tasks.EmrModifyInstanceGroupByName(stack, 'Task', { + clusterId: 'ClusterId', + instanceGroupName: sfn.Data.stringAt('$.InstanceGroupName'), + instanceGroup: { + instanceCount: 1, + }, }); ``` diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-add-step.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-add-step.ts index 2da1871ebfd83..2faef1a7c978e 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-add-step.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-add-step.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; -import { Aws, Stack } from '@aws-cdk/core'; -import { getResourceArn } from '../resource-arn-suffix'; +import { Aws, Construct, Stack } from '@aws-cdk/core'; +import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; /** * The action to take when the cluster step fails. @@ -35,7 +35,7 @@ export enum ActionOnFailure { * * @experimental */ -export interface EmrAddStepProps { +export interface EmrAddStepProps extends sfn.TaskStateBaseProps { /** * The ClusterId to add the Step to. */ @@ -53,7 +53,7 @@ export interface EmrAddStepProps { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_StepConfig.html * - * @default CONTINUE + * @default ActionOnFailure.CONTINUE */ readonly actionOnFailure?: ActionOnFailure; @@ -69,7 +69,7 @@ export interface EmrAddStepProps { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_HadoopJarStepConfig.html * - * @default No mainClass + * @default - No mainClass */ readonly mainClass?: string; @@ -78,7 +78,7 @@ export interface EmrAddStepProps { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_HadoopJarStepConfig.html * - * @default No args + * @default - No args */ readonly args?: string[]; @@ -87,18 +87,9 @@ export interface EmrAddStepProps { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_HadoopJarStepConfig.html * - * @default No properties + * @default - No properties */ readonly properties?: { [key: string]: string }; - - /** - * The service integration pattern indicates different ways to call AddStep. - * - * The valid value is either FIRE_AND_FORGET or SYNC. - * - * @default SYNC - */ - readonly integrationPattern?: sfn.ServiceIntegrationPattern; } /** @@ -110,30 +101,31 @@ export interface EmrAddStepProps { * * @experimental */ -export class EmrAddStep implements sfn.IStepFunctionsTask { +export class EmrAddStep extends sfn.TaskStateBase { + private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ + sfn.IntegrationPattern.REQUEST_RESPONSE, + sfn.IntegrationPattern.RUN_JOB, + ]; - private readonly actionOnFailure: ActionOnFailure; - private readonly integrationPattern: sfn.ServiceIntegrationPattern; + protected readonly taskPolicies?: iam.PolicyStatement[]; + protected readonly taskMetrics?: sfn.TaskMetricsConfig; - constructor(private readonly props: EmrAddStepProps) { - this.actionOnFailure = props.actionOnFailure || ActionOnFailure.CONTINUE; - this.integrationPattern = props.integrationPattern || sfn.ServiceIntegrationPattern.SYNC; + private readonly actionOnFailure: ActionOnFailure; + private readonly integrationPattern: sfn.IntegrationPattern; - const supportedPatterns = [ - sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - sfn.ServiceIntegrationPattern.SYNC, - ]; + constructor(scope: Construct, id: string, private readonly props: EmrAddStepProps) { + super(scope, id, props); + this.actionOnFailure = props.actionOnFailure ?? ActionOnFailure.CONTINUE; + this.integrationPattern = props.integrationPattern ?? sfn.IntegrationPattern.RUN_JOB; - if (!supportedPatterns.includes(this.integrationPattern)) { - throw new Error(`Invalid Service Integration Pattern: ${this.integrationPattern} is not supported to call AddStep.`); - } + validatePatternSupported(this.integrationPattern, EmrAddStep.SUPPORTED_INTEGRATION_PATTERNS); + this.taskPolicies = this.createPolicyStatements(); } - public bind(_task: sfn.Task): sfn.StepFunctionsTaskConfig { + protected renderTask(): any { return { - resourceArn: getResourceArn('elasticmapreduce', 'addStep', this.integrationPattern), - policyStatements: this.createPolicyStatements(_task), - parameters: { + Resource: integrationResourceArn('elasticmapreduce', 'addStep', this.integrationPattern), + Parameters: sfn.FieldUtils.renderObject({ ClusterId: this.props.clusterId, Step: { Name: this.props.name, @@ -152,15 +144,15 @@ export class EmrAddStep implements sfn.IStepFunctionsTask { ), }, }, - }, + }), }; } /** * This generates the PolicyStatements required by the Task to call AddStep. */ - private createPolicyStatements(task: sfn.Task): iam.PolicyStatement[] { - const stack = Stack.of(task); + private createPolicyStatements(): iam.PolicyStatement[] { + const stack = Stack.of(this); const policyStatements = [ new iam.PolicyStatement({ @@ -173,7 +165,7 @@ export class EmrAddStep implements sfn.IStepFunctionsTask { }), ]; - if (this.integrationPattern === sfn.ServiceIntegrationPattern.SYNC) { + if (this.integrationPattern === sfn.IntegrationPattern.RUN_JOB) { policyStatements.push(new iam.PolicyStatement({ actions: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'], resources: [stack.formatArn({ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-cancel-step.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-cancel-step.ts index 456ae68777b9e..4252056e1cb50 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-cancel-step.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-cancel-step.ts @@ -1,14 +1,14 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; -import { Aws } from '@aws-cdk/core'; -import { getResourceArn } from '../resource-arn-suffix'; +import { Aws, Construct } from '@aws-cdk/core'; +import { integrationResourceArn } from '../private/task-utils'; /** * Properties for EmrCancelStep * * @experimental */ -export interface EmrCancelStepProps { +export interface EmrCancelStepProps extends sfn.TaskStateBaseProps { /** * The ClusterId to update. */ @@ -25,24 +25,28 @@ export interface EmrCancelStepProps { * * @experimental */ -export class EmrCancelStep implements sfn.IStepFunctionsTask { +export class EmrCancelStep extends sfn.TaskStateBase { - constructor(private readonly props: EmrCancelStepProps) {} + protected readonly taskPolicies?: iam.PolicyStatement[]; + protected readonly taskMetrics?: sfn.TaskMetricsConfig; - public bind(_task: sfn.Task): sfn.StepFunctionsTaskConfig { + constructor(scope: Construct, id: string, private readonly props: EmrCancelStepProps) { + super(scope, id, props); + this.taskPolicies = [ + new iam.PolicyStatement({ + actions: ['elasticmapreduce:CancelSteps'], + resources: [`arn:aws:elasticmapreduce:${Aws.REGION}:${Aws.ACCOUNT_ID}:cluster/*`], + }), + ]; + } + + protected renderTask(): any { return { - resourceArn: getResourceArn('elasticmapreduce', 'cancelStep', - sfn.ServiceIntegrationPattern.FIRE_AND_FORGET), - policyStatements: [ - new iam.PolicyStatement({ - actions: ['elasticmapreduce:CancelSteps'], - resources: [`arn:aws:elasticmapreduce:${Aws.REGION}:${Aws.ACCOUNT_ID}:cluster/*`], - }), - ], - parameters: { + Resource: integrationResourceArn('elasticmapreduce', 'cancelStep', sfn.IntegrationPattern.REQUEST_RESPONSE), + Parameters: sfn.FieldUtils.renderObject({ ClusterId: this.props.clusterId, StepId: this.props.stepId, - }, + }), }; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-create-cluster.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-create-cluster.ts index 3832a821b7b2c..b33f12e5d8863 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-create-cluster.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-create-cluster.ts @@ -1,7 +1,14 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; -import { getResourceArn } from '../resource-arn-suffix'; +import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; +import { + ApplicationConfigPropertyToJson, + BootstrapActionConfigToJson, + ConfigurationPropertyToJson, + InstancesConfigPropertyToJson, + KerberosAttributesPropertyToJson, +} from './private/cluster-utils'; /** * Properties for EmrCreateCluster @@ -12,7 +19,7 @@ import { getResourceArn } from '../resource-arn-suffix'; * * @experimental */ -export interface EmrCreateClusterProps { +export interface EmrCreateClusterProps extends sfn.TaskStateBaseProps { /** * A specification of the number and type of Amazon EC2 instances. */ @@ -22,9 +29,8 @@ export interface EmrCreateClusterProps { * Also called instance profile and EC2 role. An IAM role for an EMR cluster. The EC2 instances of the cluster assume this role. * * This attribute has been renamed from jobFlowRole to clusterRole to align with other ERM/StepFunction integration parameters. - * A Role will be created if one is not provided. * - * @default No clusterRole + * @default - * A Role will be created */ readonly clusterRole?: iam.IRole; @@ -34,103 +40,102 @@ export interface EmrCreateClusterProps { readonly name: string; /** - * The IAM role that will be assumed by the Amazon EMR service to access AWS resources on your behalf. A Role will be created if - * one is not provided. + * The IAM role that will be assumed by the Amazon EMR service to access AWS resources on your behalf. * - * @default No serviceRole + * @default - A role will be created that Amazon EMR service can assume. */ readonly serviceRole?: iam.IRole; /** * A JSON string for selecting additional features. * - * @default No additionalInfo + * @default - None */ readonly additionalInfo?: string; /** * A case-insensitive list of applications for Amazon EMR to install and configure when launching the cluster. * - * @default EMR selected default + * @default - EMR selected default */ readonly applications?: EmrCreateCluster.ApplicationConfigProperty[]; /** - * An IAM role for automatic scaling policies. A Role will be created if one is not provided. + * An IAM role for automatic scaling policies. * - * @default No autoScalingRole + * @default - A role will be created. */ readonly autoScalingRole?: iam.IRole; /** * A list of bootstrap actions to run before Hadoop starts on the cluster nodes. * - * @default No bootstrapActions + * @default - None */ readonly bootstrapActions?: EmrCreateCluster.BootstrapActionConfigProperty[]; /** * The list of configurations supplied for the EMR cluster you are creating. * - * @default No configurations + * @default - None */ readonly configurations?: EmrCreateCluster.ConfigurationProperty[]; /** * The ID of a custom Amazon EBS-backed Linux AMI. * - * @default No customAmiId + * @default - None */ readonly customAmiId?: string; /** - * The size, in GiB, of the EBS root device volume of the Linux AMI that is used for each EC2 instance. + * The size of the EBS root device volume of the Linux AMI that is used for each EC2 instance. * - * @default EMR selected default + * @default - EMR selected default */ - readonly ebsRootVolumeSize?: number; + readonly ebsRootVolumeSize?: cdk.Size; /** * Attributes for Kerberos configuration when Kerberos authentication is enabled using a security configuration. * - * @default No kerberosAttributes + * @default - None */ readonly kerberosAttributes?: EmrCreateCluster.KerberosAttributesProperty; /** * The location in Amazon S3 to write the log files of the job flow. * - * @default No logUri + * @default - None */ readonly logUri?: string; /** * The Amazon EMR release label, which determines the version of open-source application packages installed on the cluster. * - * @default EMR selected default + * @default - EMR selected default */ readonly releaseLabel?: string; /** * Specifies the way that individual Amazon EC2 instances terminate when an automatic scale-in activity occurs or an instance group is resized. * - * @default EMR selected default + * @default - EMR selected default */ readonly scaleDownBehavior?: EmrCreateCluster.EmrClusterScaleDownBehavior; /** * The name of a security configuration to apply to the cluster. * - * @default No securityConfiguration + * @default - None */ readonly securityConfiguration?: string; /** * A list of tags to associate with a cluster and propagate to Amazon EC2 instances. * - * @default No Tags + * @default - None */ - readonly tags?: cdk.CfnTag[]; + readonly tags?: { [key: string]: string }; /** * A value of true indicates that all IAM users in the AWS account can perform cluster actions if they have the proper IAM policy permissions. @@ -138,15 +143,6 @@ export interface EmrCreateClusterProps { * @default true */ readonly visibleToAllUsers?: boolean; - - /** - * The service integration pattern indicates different ways to call CreateCluster. - * - * The valid value is either FIRE_AND_FORGET or SYNC. - * - * @default SYNC - */ - readonly integrationPattern?: sfn.ServiceIntegrationPattern; } /** @@ -158,31 +154,44 @@ export interface EmrCreateClusterProps { * * @experimental */ -export class EmrCreateCluster implements sfn.IStepFunctionsTask { +export class EmrCreateCluster extends sfn.TaskStateBase { + private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ + sfn.IntegrationPattern.REQUEST_RESPONSE, + sfn.IntegrationPattern.RUN_JOB, + ]; + + protected readonly taskPolicies?: iam.PolicyStatement[]; + protected readonly taskMetrics?: sfn.TaskMetricsConfig; private readonly visibleToAllUsers: boolean; - private readonly integrationPattern: sfn.ServiceIntegrationPattern; + private readonly integrationPattern: sfn.IntegrationPattern; - private _serviceRole?: iam.IRole; - private _clusterRole?: iam.IRole; + private _serviceRole: iam.IRole; + private _clusterRole: iam.IRole; private _autoScalingRole?: iam.IRole; - constructor(private readonly props: EmrCreateClusterProps) { - this.visibleToAllUsers = (this.props.visibleToAllUsers !== undefined) ? this.props.visibleToAllUsers : true; - this.integrationPattern = props.integrationPattern || sfn.ServiceIntegrationPattern.SYNC; + constructor(scope: cdk.Construct, id: string, private readonly props: EmrCreateClusterProps) { + super(scope, id, props); + this.visibleToAllUsers = this.props.visibleToAllUsers !== undefined ? this.props.visibleToAllUsers : true; + this.integrationPattern = props.integrationPattern || sfn.IntegrationPattern.RUN_JOB; + validatePatternSupported(this.integrationPattern, EmrCreateCluster.SUPPORTED_INTEGRATION_PATTERNS); - this._serviceRole = this.props.serviceRole; - this._clusterRole = this.props.clusterRole; this._autoScalingRole = this.props.autoScalingRole; - const supportedPatterns = [ - sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - sfn.ServiceIntegrationPattern.SYNC, - ]; + // If the Roles are undefined then they weren't provided, so create them + this._serviceRole = this.props.serviceRole ?? this.createServiceRole(); + this._clusterRole = this.props.clusterRole ?? this.createClusterRole(); - if (!supportedPatterns.includes(this.integrationPattern)) { - throw new Error(`Invalid Service Integration Pattern: ${this.integrationPattern} is not supported to call CreateCluster.`); + // AutoScaling roles are not valid with InstanceFleet clusters. + // Attempt to create only if .instances.instanceFleets is undefined or empty + if (this.props.instances.instanceFleets === undefined || this.props.instances.instanceFleets.length === 0) { + this._autoScalingRole = this._autoScalingRole || this.createAutoScalingRole(); + // If InstanceFleets are used and an AutoScaling Role is specified, throw an error + } else if (this._autoScalingRole !== undefined) { + throw new Error('Auto Scaling roles can not be specified with instance fleets.'); } + + this.taskPolicies = this.createPolicyStatements(this._serviceRole, this._clusterRole, this._autoScalingRole); } /** @@ -221,91 +230,78 @@ export class EmrCreateCluster implements sfn.IStepFunctionsTask { return this._autoScalingRole; } - public bind(task: sfn.Task): sfn.StepFunctionsTaskConfig { - // If the Roles are undefined then they weren't provided, so create them - this._serviceRole = this._serviceRole || this.createServiceRole(task); - this._clusterRole = this._clusterRole || this.createClusterRole(task); - - // AutoScaling roles are not valid with InstanceFleet clusters. - // Attempt to create only if .instances.instanceFleets is undefined or empty - if (this.props.instances.instanceFleets === undefined || this.props.instances.instanceFleets.length === 0) { - this._autoScalingRole = this._autoScalingRole || this.createAutoScalingRole(task); - // If InstanceFleets are used and an AutoScaling Role is specified, throw an error - } else if (this._autoScalingRole !== undefined) { - throw new Error('Auto Scaling roles can not be specified with instance fleets.'); - } - + protected renderTask(): any { return { - resourceArn: getResourceArn('elasticmapreduce', 'createCluster', this.integrationPattern), - policyStatements: this.createPolicyStatements(task, this._serviceRole, this._clusterRole, this._autoScalingRole), - parameters: { - Instances: EmrCreateCluster.InstancesConfigPropertyToJson(this.props.instances), + Resource: integrationResourceArn('elasticmapreduce', 'createCluster', this.integrationPattern), + Parameters: sfn.FieldUtils.renderObject({ + Instances: InstancesConfigPropertyToJson(this.props.instances), JobFlowRole: cdk.stringToCloudFormation(this._clusterRole.roleName), Name: cdk.stringToCloudFormation(this.props.name), ServiceRole: cdk.stringToCloudFormation(this._serviceRole.roleName), AdditionalInfo: cdk.stringToCloudFormation(this.props.additionalInfo), - Applications: cdk.listMapper(EmrCreateCluster.ApplicationConfigPropertyToJson)(this.props.applications), + Applications: cdk.listMapper(ApplicationConfigPropertyToJson)(this.props.applications), AutoScalingRole: cdk.stringToCloudFormation(this._autoScalingRole?.roleName), - BootstrapActions: cdk.listMapper(EmrCreateCluster.BootstrapActionConfigToJson)(this.props.bootstrapActions), - Configurations: cdk.listMapper(EmrCreateCluster.ConfigurationPropertyToJson)(this.props.configurations), + BootstrapActions: cdk.listMapper(BootstrapActionConfigToJson)(this.props.bootstrapActions), + Configurations: cdk.listMapper(ConfigurationPropertyToJson)(this.props.configurations), CustomAmiId: cdk.stringToCloudFormation(this.props.customAmiId), - EbsRootVolumeSize: cdk.numberToCloudFormation(this.props.ebsRootVolumeSize), - KerberosAttributes: (this.props.kerberosAttributes === undefined) ? - this.props.kerberosAttributes : - EmrCreateCluster.KerberosAttributesPropertyToJson(this.props.kerberosAttributes), + EbsRootVolumeSize: this.props.ebsRootVolumeSize?.toGibibytes(), + KerberosAttributes: this.props.kerberosAttributes ? KerberosAttributesPropertyToJson(this.props.kerberosAttributes) : undefined, LogUri: cdk.stringToCloudFormation(this.props.logUri), ReleaseLabel: cdk.stringToCloudFormation(this.props.releaseLabel), ScaleDownBehavior: cdk.stringToCloudFormation(this.props.scaleDownBehavior?.valueOf()), SecurityConfiguration: cdk.stringToCloudFormation(this.props.securityConfiguration), - Tags: cdk.listMapper(cdk.cfnTagToCloudFormation)(this.props.tags), + ...(this.props.tags ? this.renderTags(this.props.tags) : undefined), VisibleToAllUsers: cdk.booleanToCloudFormation(this.visibleToAllUsers), - }, + }), }; } + private renderTags(tags: { [key: string]: any } | undefined): { [key: string]: any } { + return tags ? { Tags: Object.keys(tags).map((key) => ({ Key: key, Value: tags[key] })) } : {}; + } + /** * This generates the PolicyStatements required by the Task to call CreateCluster. */ - private createPolicyStatements( - task: sfn.Task, serviceRole: iam.IRole, clusterRole: iam.IRole, - autoScalingRole?: iam.IRole): iam.PolicyStatement[] { - const stack = cdk.Stack.of(task); + private createPolicyStatements(serviceRole: iam.IRole, clusterRole: iam.IRole, autoScalingRole?: iam.IRole): iam.PolicyStatement[] { + const stack = cdk.Stack.of(this); const policyStatements = [ new iam.PolicyStatement({ - actions: [ - 'elasticmapreduce:RunJobFlow', - 'elasticmapreduce:DescribeCluster', - 'elasticmapreduce:TerminateJobFlows', - ], + actions: ['elasticmapreduce:RunJobFlow', 'elasticmapreduce:DescribeCluster', 'elasticmapreduce:TerminateJobFlows'], resources: ['*'], }), ]; // Allow the StateMachine to PassRole to Cluster roles - policyStatements.push(new iam.PolicyStatement({ - actions: ['iam:PassRole'], - resources: [ - serviceRole.roleArn, - clusterRole.roleArn, - ], - })); - if (autoScalingRole !== undefined) { - policyStatements.push(new iam.PolicyStatement({ + policyStatements.push( + new iam.PolicyStatement({ actions: ['iam:PassRole'], - resources: [ autoScalingRole.roleArn ], - })); + resources: [serviceRole.roleArn, clusterRole.roleArn], + }), + ); + if (autoScalingRole !== undefined) { + policyStatements.push( + new iam.PolicyStatement({ + actions: ['iam:PassRole'], + resources: [autoScalingRole.roleArn], + }), + ); } - if (this.integrationPattern === sfn.ServiceIntegrationPattern.SYNC) { - policyStatements.push(new iam.PolicyStatement({ - actions: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'], - resources: [stack.formatArn({ - service: 'events', - resource: 'rule', - resourceName: 'StepFunctionsGetEventForEMRRunJobFlowRule', - })], - })); + if (this.integrationPattern === sfn.IntegrationPattern.RUN_JOB) { + policyStatements.push( + new iam.PolicyStatement({ + actions: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'], + resources: [ + stack.formatArn({ + service: 'events', + resource: 'rule', + resourceName: 'StepFunctionsGetEventForEMRRunJobFlowRule', + }), + ], + }), + ); } return policyStatements; @@ -314,12 +310,10 @@ export class EmrCreateCluster implements sfn.IStepFunctionsTask { /** * Generate the Role used by the EMR Service */ - private createServiceRole(task: sfn.Task): iam.IRole { - return new iam.Role(task, 'ServiceRole', { + private createServiceRole(): iam.IRole { + return new iam.Role(this, 'ServiceRole', { assumedBy: new iam.ServicePrincipal('elasticmapreduce.amazonaws.com'), - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonElasticMapReduceRole'), - ], + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonElasticMapReduceRole')], }); } @@ -328,13 +322,13 @@ export class EmrCreateCluster implements sfn.IStepFunctionsTask { * * Data access permissions will need to be updated by the user */ - private createClusterRole(task: sfn.Task): iam.IRole { - const role = new iam.Role(task, 'InstanceRole', { + private createClusterRole(): iam.IRole { + const role = new iam.Role(this, 'InstanceRole', { assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), }); - new iam.CfnInstanceProfile(task, 'InstanceProfile', { - roles: [ role.roleName ], + new iam.CfnInstanceProfile(this, 'InstanceProfile', { + roles: [role.roleName], instanceProfileName: role.roleName, }); @@ -344,24 +338,18 @@ export class EmrCreateCluster implements sfn.IStepFunctionsTask { /** * Generate the Role used to AutoScale the Cluster */ - private createAutoScalingRole(task: sfn.Task): iam.IRole { - const role = new iam.Role(task, 'AutoScalingRole', { + private createAutoScalingRole(): iam.IRole { + const role = new iam.Role(this, 'AutoScalingRole', { assumedBy: new iam.ServicePrincipal('elasticmapreduce.amazonaws.com'), - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonElasticMapReduceforAutoScalingRole'), - ], + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonElasticMapReduceforAutoScalingRole')], }); role.assumeRolePolicy?.addStatements( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, - principals: [ - new iam.ServicePrincipal('application-autoscaling.amazonaws.com'), - ], - actions: [ - 'sts:AssumeRole', - ], - }) + principals: [new iam.ServicePrincipal('application-autoscaling.amazonaws.com')], + actions: ['sts:AssumeRole'], + }), ); return role; @@ -385,7 +373,7 @@ export namespace EmrCreateCluster { * Indicates that Amazon EMR blacklists and drains tasks from nodes before terminating the Amazon EC2 instances, regardless of the * instance-hour boundary. */ - TERMINATE_AT_TASK_COMPLETION = 'TERMINATE_AT_TASK_COMPLETION' + TERMINATE_AT_TASK_COMPLETION = 'TERMINATE_AT_TASK_COMPLETION', } /** @@ -405,7 +393,7 @@ export namespace EmrCreateCluster { /** * Task Node */ - TASK = 'TASK' + TASK = 'TASK', } /** @@ -425,7 +413,7 @@ export namespace EmrCreateCluster { /** * Standard Volume Type */ - STANDARD = 'standard' + STANDARD = 'standard', } /** @@ -440,19 +428,20 @@ export namespace EmrCreateCluster { /** * The number of I/O operations per second (IOPS) that the volume supports. * - * @default EMR selected default + * @default - EMR selected default */ readonly iops?: number; /** - * The volume size, in gibibytes (GiB). This can be a number from 1 - 1024. If the volume type is EBS-optimized, the minimum value is 10. + * The volume size. If the volume type is EBS-optimized, the minimum value is 10GiB. + * Maximum size is 1TiB */ - readonly sizeInGB: number; + readonly volumeSize: cdk.Size; /** * The volume type. Volume types supported are gp2, io1, standard. */ - readonly volumeType: EbsBlockDeviceVolumeType + readonly volumeType: EbsBlockDeviceVolumeType; } /** @@ -478,22 +467,6 @@ export namespace EmrCreateCluster { readonly volumesPerInstance?: number; } - /** - * Render the EbsBlockDeviceConfigProperty as JSON - * - * @param property - */ - export function EbsBlockDeviceConfigPropertyToJson(property: EbsBlockDeviceConfigProperty) { - return { - VolumeSpecification: { - Iops: cdk.numberToCloudFormation(property.volumeSpecification.iops), - SizeInGB: cdk.numberToCloudFormation(property.volumeSpecification.sizeInGB), - VolumeType: cdk.stringToCloudFormation(property.volumeSpecification.volumeType?.valueOf()), - }, - VolumesPerInstance: cdk.numberToCloudFormation(property.volumesPerInstance), - }; - } - /** * The Amazon EBS configuration of a cluster instance. * @@ -505,30 +478,18 @@ export namespace EmrCreateCluster { /** * An array of Amazon EBS volume specifications attached to a cluster instance. * - * @default No ebsBlockDeviceConfigs + * @default - None */ readonly ebsBlockDeviceConfigs?: EbsBlockDeviceConfigProperty[]; /** * Indicates whether an Amazon EBS volume is EBS-optimized. * - * @default EMR selected default + * @default - EMR selected default */ readonly ebsOptimized?: boolean; } - /** - * Render the EbsConfigurationProperty to JSON - * - * @param property - */ - export function EbsConfigurationPropertyToJson(property: EbsConfigurationProperty) { - return { - EbsBlockDeviceConfigs: cdk.listMapper(EbsBlockDeviceConfigPropertyToJson)(property.ebsBlockDeviceConfigs), - EbsOptimized: cdk.booleanToCloudFormation(property.ebsOptimized), - }; - } - /** * An instance type configuration for each instance type in an instance fleet, which determines the EC2 instances Amazon EMR attempts to * provision to fulfill On-Demand and Spot target capacities. @@ -541,14 +502,14 @@ export namespace EmrCreateCluster { /** * The bid price for each EC2 Spot instance type as defined by InstanceType. Expressed in USD. * - * @default No bidPrice + * @default - None */ readonly bidPrice?: string; /** * The bid price, as a percentage of On-Demand price. * - * @default No bidPriceAsPercentageOfOnDemandPrice + * @default - None */ readonly bidPriceAsPercentageOfOnDemandPrice?: number; @@ -556,14 +517,14 @@ export namespace EmrCreateCluster { * A configuration classification that applies when provisioning cluster instances, which can include configurations for applications * and software that run on the cluster. * - * @default No configurations + * @default - None */ readonly configurations?: ConfigurationProperty[]; /** * The configuration of Amazon Elastic Block Storage (EBS) attached to each instance as defined by InstanceType. * - * @default No ebsConfiguration + * @default - None */ readonly ebsConfiguration?: EbsConfigurationProperty; @@ -576,29 +537,11 @@ export namespace EmrCreateCluster { * The number of units that a provisioned instance of this type provides toward fulfilling the target capacities defined * in the InstanceFleetConfig. * - * @default No weightedCapacity + * @default - None */ readonly weightedCapacity?: number; } - /** - * Render the InstanceTypeConfigProperty to JSON] - * - * @param property - */ - export function InstanceTypeConfigPropertyToJson(property: InstanceTypeConfigProperty) { - return { - BidPrice: cdk.stringToCloudFormation(property.bidPrice), - BidPriceAsPercentageOfOnDemandPrice: cdk.numberToCloudFormation(property.bidPriceAsPercentageOfOnDemandPrice), - Configurations: cdk.listMapper(ConfigurationPropertyToJson)(property.configurations), - EbsConfiguration: (property.ebsConfiguration === undefined) ? - property.ebsConfiguration : - EbsConfigurationPropertyToJson(property.ebsConfiguration), - InstanceType: cdk.stringToCloudFormation(property.instanceType?.valueOf()), - WeightedCapacity: cdk.numberToCloudFormation(property.weightedCapacity), - }; - } - /** * Spot Timeout Actions * @@ -612,7 +555,7 @@ export namespace EmrCreateCluster { /** * TERMINATE_CLUSTER */ - TERMINATE_CLUSTER = 'TERMINATE_CLUSTER' + TERMINATE_CLUSTER = 'TERMINATE_CLUSTER', } /** @@ -655,21 +598,6 @@ export namespace EmrCreateCluster { readonly spotSpecification: SpotProvisioningSpecificationProperty; } - /** - * Render the InstanceFleetProvisioningSpecificationsProperty to JSON - * - * @param property - */ - export function InstanceFleetProvisioningSpecificationsPropertyToJson(property: InstanceFleetProvisioningSpecificationsProperty) { - return { - SpotSpecification: { - BlockDurationMinutes: cdk.numberToCloudFormation(property.spotSpecification.blockDurationMinutes), - TimeoutAction: cdk.stringToCloudFormation(property.spotSpecification.timeoutAction?.valueOf()), - TimeoutDurationMinutes: cdk.numberToCloudFormation(property.spotSpecification.timeoutDurationMinutes), - }, - }; - } - /** * The configuration that defines an instance fleet. * @@ -719,24 +647,6 @@ export namespace EmrCreateCluster { readonly targetSpotCapacity?: number; } - /** - * Render the InstanceFleetConfigProperty as JSON - * - * @param property - */ - export function InstanceFleetConfigPropertyToJson(property: InstanceFleetConfigProperty) { - return { - InstanceFleetType: cdk.stringToCloudFormation(property.instanceFleetType?.valueOf()), - InstanceTypeConfigs: cdk.listMapper(InstanceTypeConfigPropertyToJson)(property.instanceTypeConfigs), - LaunchSpecifications: (property.launchSpecifications === undefined) ? - property.launchSpecifications : - InstanceFleetProvisioningSpecificationsPropertyToJson(property.launchSpecifications), - Name: cdk.stringToCloudFormation(property.name), - TargetOnDemandCapacity: cdk.numberToCloudFormation(property.targetOnDemandCapacity), - TargetSpotCapacity: cdk.numberToCloudFormation(property.targetSpotCapacity), - }; - } - /** * CloudWatch Alarm Comparison Operators * @@ -758,7 +668,7 @@ export namespace EmrCreateCluster { /** * LESS_THAN_OR_EQUAL */ - LESS_THAN_OR_EQUAL = 'LESS_THAN_OR_EQUAL' + LESS_THAN_OR_EQUAL = 'LESS_THAN_OR_EQUAL', } /** @@ -786,7 +696,7 @@ export namespace EmrCreateCluster { /** * MAXIMUM */ - MAXIMUM = 'MAXIMUM' + MAXIMUM = 'MAXIMUM', } /** @@ -902,7 +812,7 @@ export namespace EmrCreateCluster { /** * COUNT_PER_SECOND */ - COUNT_PER_SECOND = 'COUNT_PER_SECOND' + COUNT_PER_SECOND = 'COUNT_PER_SECOND', } /** @@ -926,18 +836,6 @@ export namespace EmrCreateCluster { readonly value: string; } - /** - * Render the MetricDimensionProperty as JSON - * - * @param property - */ - export function MetricDimensionPropertyToJson(property: MetricDimensionProperty) { - return { - Key: cdk.stringToCloudFormation(property.key), - Value: cdk.stringToCloudFormation(property.value), - }; - } - /** * The definition of a CloudWatch metric alarm, which determines when an automatic scaling activity is triggered. When the defined alarm conditions * are satisfied, scaling activity begins. @@ -955,17 +853,17 @@ export namespace EmrCreateCluster { /** * A CloudWatch metric dimension * - * @default No dimensions + * @default - No dimensions */ readonly dimensions?: MetricDimensionProperty[]; /** * The number of periods, in five-minute increments, during which the alarm condition must exist before the alarm triggers automatic - * scaling activity. The default value is 1. + * scaling activity. * - * @default No evaluationPeriods + * @default 1 */ - readonly evalutionPeriods?: number; + readonly evaluationPeriods?: number; /** * The name of the CloudWatch metric that is watched to determine an alarm condition. @@ -973,9 +871,9 @@ export namespace EmrCreateCluster { readonly metricName: string; /** - * The namespace for the CloudWatch metric. The default is AWS/ElasticMapReduce. + * The namespace for the CloudWatch metric. * - * @default No nampespace + * @default 'AWS/ElasticMapReduce' */ readonly namespace?: string; @@ -986,16 +884,16 @@ export namespace EmrCreateCluster { readonly period: cdk.Duration; /** - * The statistic to apply to the metric associated with the alarm. The default is AVERAGE. + * The statistic to apply to the metric associated with the alarm. * - * @default No statistic + * @default CloudWatchAlarmStatistic.AVERAGE */ readonly statistic?: CloudWatchAlarmStatistic; /** * The value against which the specified statistic is compared. * - * @default No threshold + * @default - None */ readonly threshold?: number; @@ -1003,7 +901,7 @@ export namespace EmrCreateCluster { * The unit of measure associated with the CloudWatch metric being watched. The value specified for Unit must correspond to the units * specified in the CloudWatch metric. * - * @default No unit + * @default CloudWatchAlarmUnit.NONE */ readonly unit?: CloudWatchAlarmUnit; } @@ -1024,27 +922,6 @@ export namespace EmrCreateCluster { readonly cloudWatchAlarmDefinition: CloudWatchAlarmDefinitionProperty; } - /** - * Render the ScalingTriggerProperty to JSON - * - * @param property - */ - export function ScalingTriggerPropertyToJson(property: ScalingTriggerProperty) { - return { - CloudWatchAlarmDefinition: { - ComparisonOperator: cdk.stringToCloudFormation(property.cloudWatchAlarmDefinition.comparisonOperator?.valueOf()), - Dimensions: cdk.listMapper(MetricDimensionPropertyToJson)(property.cloudWatchAlarmDefinition.dimensions), - EvaluationPeriods: cdk.numberToCloudFormation(property.cloudWatchAlarmDefinition.evalutionPeriods), - MetricName: cdk.stringToCloudFormation(property.cloudWatchAlarmDefinition.metricName), - Namespace: cdk.stringToCloudFormation(property.cloudWatchAlarmDefinition.namespace), - Period: cdk.numberToCloudFormation(property.cloudWatchAlarmDefinition.period.toSeconds()), - Statistic: cdk.stringToCloudFormation(property.cloudWatchAlarmDefinition.statistic?.valueOf()), - Threshold: cdk.numberToCloudFormation(property.cloudWatchAlarmDefinition.threshold), - Unit: cdk.stringToCloudFormation(property.cloudWatchAlarmDefinition.unit?.valueOf()), - }, - }; - } - /** * EC2 Instance Market * @@ -1058,7 +935,7 @@ export namespace EmrCreateCluster { /** * Spot Instance */ - SPOT = 'SPOT' + SPOT = 'SPOT', } /** @@ -1078,7 +955,7 @@ export namespace EmrCreateCluster { /** * EXACT_CAPACITY */ - EXACT_CAPACITY = 'EXACT_CAPACITY' + EXACT_CAPACITY = 'EXACT_CAPACITY', } /** @@ -1094,15 +971,14 @@ export namespace EmrCreateCluster { * The way in which EC2 instances are added (if ScalingAdjustment is a positive number) or terminated (if ScalingAdjustment is a negative * number) each time the scaling activity is triggered. * - * @default No adjustmentType + * @default - None */ readonly adjustmentType?: ScalingAdjustmentType; /** * The amount of time, in seconds, after a scaling activity completes before any further trigger-related scaling activities can start. - * The default value is 0. * - * @default No coolDown + * @default 0 */ readonly coolDown?: number; @@ -1127,7 +1003,7 @@ export namespace EmrCreateCluster { /** * Not available for instance groups. Instance groups use the market type specified for the group. * - * @default EMR selected default + * @default - EMR selected default */ readonly market?: InstanceMarket; @@ -1137,22 +1013,6 @@ export namespace EmrCreateCluster { readonly simpleScalingPolicyConfiguration: SimpleScalingPolicyConfigurationProperty; } - /** - * Render the ScalingActionPropety to JSON - * - * @param property - */ - export function ScalingActionPropertyToJson(property: ScalingActionProperty) { - return { - Market: cdk.stringToCloudFormation(property.market?.valueOf()), - SimpleScalingPolicyConfiguration: { - AdjustmentType: cdk.stringToCloudFormation(property.simpleScalingPolicyConfiguration.adjustmentType), - CoolDown: cdk.numberToCloudFormation(property.simpleScalingPolicyConfiguration.coolDown), - ScalingAdjustment: cdk.numberToCloudFormation(property.simpleScalingPolicyConfiguration.scalingAdjustment), - }, - }; - } - /** * A scale-in or scale-out rule that defines scaling activity, including the CloudWatch metric alarm that triggers activity, how EC2 * instances are added or removed, and the periodicity of adjustments. @@ -1170,7 +1030,7 @@ export namespace EmrCreateCluster { /** * A friendly, more verbose description of the automatic scaling rule. * - * @default No description + * @default - None */ readonly description?: string; @@ -1185,20 +1045,6 @@ export namespace EmrCreateCluster { readonly trigger: ScalingTriggerProperty; } - /** - * Render the ScalingRuleProperty to JSON - * - * @param property - */ - export function ScalingRulePropertyToJson(property: ScalingRuleProperty) { - return { - Action: ScalingActionPropertyToJson(property.action), - Description: cdk.stringToCloudFormation(property.description), - Name: cdk.stringToCloudFormation(property.name), - Trigger: ScalingTriggerPropertyToJson(property.trigger), - }; - } - /** * The upper and lower EC2 instance limits for an automatic scaling policy. Automatic scaling activities triggered by automatic scaling * rules will not cause an instance group to grow above or below these limits. @@ -1241,21 +1087,6 @@ export namespace EmrCreateCluster { readonly rules: ScalingRuleProperty[]; } - /** - * Render the AutoScalingPolicyProperty to JSON - * - * @param property - */ - export function AutoScalingPolicyPropertyToJson(property: AutoScalingPolicyProperty) { - return { - Constraints: { - MaxCapacity: cdk.numberToCloudFormation(property.constraints.maxCapacity), - MinCapacity: cdk.numberToCloudFormation(property.constraints.minCapacity), - }, - Rules: cdk.listMapper(ScalingRulePropertyToJson)(property.rules), - }; - } - /** * Configuration defining a new instance group. * @@ -1267,28 +1098,28 @@ export namespace EmrCreateCluster { /** * An automatic scaling policy for a core instance group or task instance group in an Amazon EMR cluster. * - * @default No autoScalingPolicy + * @default - None */ readonly autoScalingPolicy?: AutoScalingPolicyProperty; /** * The bid price for each EC2 Spot instance type as defined by InstanceType. Expressed in USD. * - * @default No bidPrice + * @default - None */ readonly bidPrice?: string; /** * The list of configurations supplied for an EMR cluster instance group. * - * @default No configurations + * @default - None */ readonly configurations?: ConfigurationProperty[]; /** * EBS configurations that will be attached to each EC2 instance in the instance group. * - * @default No ebsConfiguration + * @default - None */ readonly ebsConfiguration?: EbsConfigurationProperty; @@ -1310,41 +1141,18 @@ export namespace EmrCreateCluster { /** * Market type of the EC2 instances used to create a cluster node. * - * @default EMR selected default + * @default - EMR selected default */ readonly market?: InstanceMarket; /** * Friendly name given to the instance group. * - * @default No name + * @default - None */ readonly name?: string; } - /** - * Render the InstanceGroupConfigProperty to JSON - * - * @param property - */ - export function InstanceGroupConfigPropertyToJson(property: InstanceGroupConfigProperty) { - return { - AutoScalingPolicy: (property.autoScalingPolicy === undefined) ? - property.autoScalingPolicy : - AutoScalingPolicyPropertyToJson(property.autoScalingPolicy), - BidPrice: cdk.numberToCloudFormation(property.bidPrice), - Configurations: cdk.listMapper(ConfigurationPropertyToJson)(property.configurations), - EbsConfiguration: (property.ebsConfiguration === undefined) ? - property.ebsConfiguration : - EbsConfigurationPropertyToJson(property.ebsConfiguration), - InstanceCount: cdk.numberToCloudFormation(property.instanceCount), - InstanceRole: cdk.stringToCloudFormation(property.instanceRole?.valueOf()), - InstanceType: cdk.stringToCloudFormation(property.instanceType), - Market: cdk.stringToCloudFormation(property.market?.valueOf()), - Name: cdk.stringToCloudFormation(property.name), - }; - } - /** * The Amazon EC2 Availability Zone configuration of the cluster (job flow). * @@ -1357,7 +1165,7 @@ export namespace EmrCreateCluster { * The Amazon EC2 Availability Zone for the cluster. AvailabilityZone is used for uniform instance groups, while AvailabilityZones * (plural) is used for instance fleets. * - * @default EMR selected default + * @default - EMR selected default */ readonly availabilityZone?: string; @@ -1365,23 +1173,11 @@ export namespace EmrCreateCluster { * When multiple Availability Zones are specified, Amazon EMR evaluates them and launches instances in the optimal Availability Zone. * AvailabilityZones is used for instance fleets, while AvailabilityZone (singular) is used for uniform instance groups. * - * @default EMR selected default + * @default - EMR selected default */ readonly availabilityZones?: string[]; } - /** - * Render the PlacementTypeProperty to JSON - * - * @param property - */ - export function PlacementTypePropertyToJson(property: PlacementTypeProperty) { - return { - AvailabilityZone: cdk.stringToCloudFormation(property.availabilityZone), - AvailabilityZones: cdk.listMapper(cdk.stringToCloudFormation)(property.availabilityZones), - }; - } - /** * A specification of the number and type of Amazon EC2 instances. * @@ -1395,21 +1191,21 @@ export namespace EmrCreateCluster { /** * A list of additional Amazon EC2 security group IDs for the master node. * - * @default No additionalMasterSecurityGroups + * @default - None */ readonly additionalMasterSecurityGroups?: string[]; /** * A list of additional Amazon EC2 security group IDs for the core and task nodes. * - * @default No additionalSlaveSecurityGroups + * @default - None */ readonly additionalSlaveSecurityGroups?: string[]; /** * The name of the EC2 key pair that can be used to ssh to the master node as the user called "hadoop." * - * @default No ec2KeyName + * @default - None */ readonly ec2KeyName?: string; @@ -1432,70 +1228,71 @@ export namespace EmrCreateCluster { /** * The identifier of the Amazon EC2 security group for the master node. * - * @default No emrManagedMasterSecurityGroup + * @default - None */ readonly emrManagedMasterSecurityGroup?: string; /** * The identifier of the Amazon EC2 security group for the core and task nodes. * - * @default No emrManagedSlaveSecurityGroup + * @default - None */ readonly emrManagedSlaveSecurityGroup?: string; /** * Applies only to Amazon EMR release versions earlier than 4.0. The Hadoop version for the cluster. * - * @default No hadoopVersion + * @default - 0.18 if the AmiVersion parameter is not set. If AmiVersion is set, the version of Hadoop for that AMI version is used. */ readonly hadoopVersion?: string; /** * The number of EC2 instances in the cluster. * - * @default No instanceCount + * @default 0 */ readonly instanceCount?: number; /** * Describes the EC2 instances and instance configurations for clusters that use the instance fleet configuration. + * The instance fleet configuration is available only in Amazon EMR versions 4.8.0 and later, excluding 5.0.x versions. * - * @default No instanceFleets + * @default - None */ readonly instanceFleets?: InstanceFleetConfigProperty[]; /** * Configuration for the instance groups in a cluster. * - * @default No instanceGroups + * @default - None */ readonly instanceGroups?: InstanceGroupConfigProperty[]; /** * The EC2 instance type of the master node. * - * @default No masterInstanceType + * @default - None */ readonly masterInstanceType?: string; /** * The Availability Zone in which the cluster runs. * - * @default EMR selected default + * @default - EMR selected default */ readonly placement?: PlacementTypeProperty; /** * The identifier of the Amazon EC2 security group for the Amazon EMR service to access clusters in VPC private subnets. * - * @default No serviceAccessSecurityGroup + * @default - None */ readonly serviceAccessSecurityGroup?: string; /** * The EC2 instance type of the core and task nodes. * - * @default No slaveInstanceThpe + * @default - None */ readonly slaveInstanceType?: string; @@ -1503,40 +1300,11 @@ export namespace EmrCreateCluster { * Specifies whether to lock the cluster to prevent the Amazon EC2 instances from being terminated by API call, user intervention, * or in the event of a job-flow error. * - * @default EMR selected default (false) + * @default false */ readonly terminationProtected?: boolean; } - /** - * Render the InstancesConfigProperty to JSON - * - * @param property - */ - export function InstancesConfigPropertyToJson(property: InstancesConfigProperty) { - return { - AdditionalMasterSecurityGroups: cdk.listMapper(cdk.stringToCloudFormation)(property.additionalMasterSecurityGroups), - AdditionalSlaveSecurityGroups: cdk.listMapper(cdk.stringToCloudFormation)(property.additionalSlaveSecurityGroups), - Ec2KeyName: cdk.stringToCloudFormation(property.ec2KeyName), - Ec2SubnetId: cdk.stringToCloudFormation(property.ec2SubnetId), - Ec2SubnetIds: cdk.listMapper(cdk.stringToCloudFormation)(property.ec2SubnetIds), - EmrManagedMasterSecurityGroup: cdk.stringToCloudFormation(property.emrManagedMasterSecurityGroup), - EmrManagedSlaveSecurityGroup: cdk.stringToCloudFormation(property.emrManagedSlaveSecurityGroup), - HadoopVersion: cdk.stringToCloudFormation(property.hadoopVersion), - InstanceCount: cdk.numberToCloudFormation(property.instanceCount), - InstanceFleets: cdk.listMapper(InstanceFleetConfigPropertyToJson)(property.instanceFleets), - InstanceGroups: cdk.listMapper(InstanceGroupConfigPropertyToJson)(property.instanceGroups), - KeepJobFlowAliveWhenNoSteps: true, - MasterInstanceType: cdk.stringToCloudFormation(property.masterInstanceType), - Placement: (property.placement === undefined) ? - property.placement : - PlacementTypePropertyToJson(property.placement), - ServiceAccessSecurityGroup: cdk.stringToCloudFormation(property.serviceAccessSecurityGroup), - SlaveInstanceType: cdk.stringToCloudFormation(property.slaveInstanceType), - TerminationProtected: cdk.booleanToCloudFormation(property.terminationProtected), - }; - } - /** * Properties for the EMR Cluster Applications * @@ -1556,7 +1324,7 @@ export namespace EmrCreateCluster { * * @default No additionalInfo */ - readonly additionalInfo?: {[key: string]: string}; + readonly additionalInfo?: { [key: string]: string }; /** * Arguments for Amazon EMR to pass to the application. @@ -1578,20 +1346,6 @@ export namespace EmrCreateCluster { readonly version?: string; } - /** - * Render the ApplicationConfigProperty as JSON - * - * @param property - */ - export function ApplicationConfigPropertyToJson(property: ApplicationConfigProperty) { - return { - Name: cdk.stringToCloudFormation(property.name), - Args: cdk.listMapper(cdk.stringToCloudFormation)(property.args), - Version: cdk.stringToCloudFormation(property.version), - AdditionalInfo: cdk.objectToCloudFormation(property.additionalInfo), - }; - } - /** * Configuration of the script to run during a bootstrap action. * @@ -1631,22 +1385,7 @@ export namespace EmrCreateCluster { /** * The script run by the bootstrap action */ - readonly scriptBootstrapAction: ScriptBootstrapActionConfigProperty - } - - /** - * Render the BootstrapActionProperty as JSON - * - * @param property - */ - export function BootstrapActionConfigToJson(property: BootstrapActionConfigProperty) { - return { - Name: cdk.stringToCloudFormation(property.name), - ScriptBootstrapAction: { - Path: cdk.stringToCloudFormation(property.scriptBootstrapAction.path), - Args: cdk.listMapper(cdk.stringToCloudFormation)(property.scriptBootstrapAction.args), - }, - }; + readonly scriptBootstrapAction: ScriptBootstrapActionConfigProperty; } /** @@ -1672,7 +1411,7 @@ export namespace EmrCreateCluster { * * @default No properties */ - readonly properties?: {[key: string]: string}; + readonly properties?: { [key: string]: string }; /** * A list of additional configurations to apply within a configuration object. @@ -1682,19 +1421,6 @@ export namespace EmrCreateCluster { readonly configurations?: ConfigurationProperty[]; } - /** - * Render the ConfigurationProperty as JSON - * - * @param property - */ - export function ConfigurationPropertyToJson(property: ConfigurationProperty) { - return { - Classification: cdk.stringToCloudFormation(property.classification), - Properties: cdk.objectToCloudFormation(property.properties), - Configurations: cdk.listMapper(ConfigurationPropertyToJson)(property.configurations), - }; - } - /** * Attributes for Kerberos configuration when Kerberos authentication is enabled using a security configuration. * @@ -1741,19 +1467,4 @@ export namespace EmrCreateCluster { */ readonly realm: string; } - - /** - * Render the KerberosAttributesProperty as JSON - * - * @param property - */ - export function KerberosAttributesPropertyToJson(property: KerberosAttributesProperty) { - return { - ADDomainJoinPassword: cdk.stringToCloudFormation(property.adDomainJoinPassword), - ADDomainJoinUser: cdk.stringToCloudFormation(property.adDomainJoinUser), - CrossRealmTrustPrincipalPassword: cdk.stringToCloudFormation(property.crossRealmTrustPrincipalPassword), - KdcAdminPassword: cdk.stringToCloudFormation(property.kdcAdminPassword), - Realm: cdk.stringToCloudFormation(property.realm), - }; - } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-fleet-by-name.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-fleet-by-name.ts index 871e133db7f3c..cdb6bc8f3f4fd 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-fleet-by-name.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-fleet-by-name.ts @@ -1,14 +1,14 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; -import { Aws } from '@aws-cdk/core'; -import { getResourceArn } from '../resource-arn-suffix'; +import { Aws, Construct } from '@aws-cdk/core'; +import { integrationResourceArn } from '../private/task-utils'; /** * Properties for EmrModifyInstanceFleetByName * * @experimental */ -export interface EmrModifyInstanceFleetByNameProps { +export interface EmrModifyInstanceFleetByNameProps extends sfn.TaskStateBaseProps { /** * The ClusterId to update. */ @@ -24,7 +24,7 @@ export interface EmrModifyInstanceFleetByNameProps { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_InstanceFleetModifyConfig.html * - * @default None + * @default - None */ readonly targetOnDemandCapacity: number; @@ -33,7 +33,7 @@ export interface EmrModifyInstanceFleetByNameProps { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_InstanceFleetModifyConfig.html * - * @default None + * @default - None */ readonly targetSpotCapacity: number; } @@ -43,31 +43,37 @@ export interface EmrModifyInstanceFleetByNameProps { * * @experimental */ -export class EmrModifyInstanceFleetByName implements sfn.IStepFunctionsTask { +export class EmrModifyInstanceFleetByName extends sfn.TaskStateBase { - constructor(private readonly props: EmrModifyInstanceFleetByNameProps) {} + protected readonly taskPolicies?: iam.PolicyStatement[]; + protected readonly taskMetrics?: sfn.TaskMetricsConfig; - public bind(_task: sfn.Task): sfn.StepFunctionsTaskConfig { + constructor(scope: Construct, id: string, private readonly props: EmrModifyInstanceFleetByNameProps) { + super(scope, id, props); + + this.taskPolicies = [ + new iam.PolicyStatement({ + actions: [ + 'elasticmapreduce:ModifyInstanceFleet', + 'elasticmapreduce:ListInstanceFleets', + ], + resources: [`arn:aws:elasticmapreduce:${Aws.REGION}:${Aws.ACCOUNT_ID}:cluster/*`], + }), + ]; + } + + protected renderTask(): any { return { - resourceArn: getResourceArn('elasticmapreduce', 'modifyInstanceFleetByName', - sfn.ServiceIntegrationPattern.FIRE_AND_FORGET), - policyStatements: [ - new iam.PolicyStatement({ - actions: [ - 'elasticmapreduce:ModifyInstanceFleet', - 'elasticmapreduce:ListInstanceFleets', - ], - resources: [`arn:aws:elasticmapreduce:${Aws.REGION}:${Aws.ACCOUNT_ID}:cluster/*`], - }), - ], - parameters: { + Resource: integrationResourceArn('elasticmapreduce', 'modifyInstanceFleetByName', + sfn.IntegrationPattern.REQUEST_RESPONSE), + Parameters: sfn.FieldUtils.renderObject({ ClusterId: this.props.clusterId, InstanceFleetName: this.props.instanceFleetName, InstanceFleet: { TargetOnDemandCapacity: this.props.targetOnDemandCapacity, TargetSpotCapacity: this.props.targetSpotCapacity, }, - }, + }), }; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-group-by-name.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-group-by-name.ts index 95ce218d1c476..30cfc572b262e 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-group-by-name.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-group-by-name.ts @@ -1,15 +1,16 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; -import { getResourceArn } from '../resource-arn-suffix'; +import { integrationResourceArn } from '../private/task-utils'; import { EmrCreateCluster } from './emr-create-cluster'; +import { InstanceGroupModifyConfigPropertyToJson } from './private/cluster-utils'; /** * Properties for EmrModifyInstanceGroupByName * * @experimental */ -export interface EmrModifyInstanceGroupByNameProps { +export interface EmrModifyInstanceGroupByNameProps extends sfn.TaskStateBaseProps { /** * The ClusterId to update. */ @@ -35,28 +36,28 @@ export interface EmrModifyInstanceGroupByNameProps { * * @experimental */ -export class EmrModifyInstanceGroupByName implements sfn.IStepFunctionsTask { - - constructor(private readonly props: EmrModifyInstanceGroupByNameProps) {} +export class EmrModifyInstanceGroupByName extends sfn.TaskStateBase { + protected readonly taskPolicies?: iam.PolicyStatement[]; + protected readonly taskMetrics?: sfn.TaskMetricsConfig; + + constructor(scope: cdk.Construct, id: string, private readonly props: EmrModifyInstanceGroupByNameProps) { + super(scope, id, props); + this.taskPolicies = [ + new iam.PolicyStatement({ + actions: ['elasticmapreduce:ModifyInstanceGroups', 'elasticmapreduce:ListInstanceGroups'], + resources: [`arn:aws:elasticmapreduce:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:cluster/*`], + }), + ]; + } - public bind(_task: sfn.Task): sfn.StepFunctionsTaskConfig { + protected renderTask(): any { return { - resourceArn: getResourceArn('elasticmapreduce', 'modifyInstanceGroupByName', - sfn.ServiceIntegrationPattern.FIRE_AND_FORGET), - policyStatements: [ - new iam.PolicyStatement({ - actions: [ - 'elasticmapreduce:ModifyInstanceGroups', - 'elasticmapreduce:ListInstanceGroups', - ], - resources: [`arn:aws:elasticmapreduce:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:cluster/*`], - }), - ], - parameters: { + Resource: integrationResourceArn('elasticmapreduce', 'modifyInstanceGroupByName', sfn.IntegrationPattern.REQUEST_RESPONSE), + Parameters: sfn.FieldUtils.renderObject({ ClusterId: this.props.clusterId, InstanceGroupName: this.props.instanceGroupName, - InstanceGroup: EmrModifyInstanceGroupByName.InstanceGroupModifyConfigPropertyToJson(this.props.instanceGroup), - }, + InstanceGroup: InstanceGroupModifyConfigPropertyToJson(this.props.instanceGroup), + }), }; } } @@ -73,38 +74,25 @@ export namespace EmrModifyInstanceGroupByName { /** * Specific list of instances to be protected when shrinking an instance group. * - * @default No instancesToProtect + * @default - No instances will be protected when shrinking an instance group */ readonly instancesToProtect?: string[]; /** * Specific list of instances to be terminated when shrinking an instance group. * - * @default No instancesToTerminate + * @default - No instances will be terminated when shrinking an instance group. */ readonly instancesToTerminate?: string[]; /** * Decommissioning timeout override for the specific list of instances to be terminated. * - * @default EMR selected default + * @default cdk.Duration.seconds */ readonly instanceTerminationTimeout?: cdk.Duration; } - /** - * Render the InstanceResizePolicyProperty to JSON - * - * @param property - */ - export function InstanceResizePolicyPropertyToJson(property: InstanceResizePolicyProperty) { - return { - InstancesToProtect: cdk.listMapper(cdk.stringToCloudFormation)(property.instancesToProtect), - InstancesToTerminate: cdk.listMapper(cdk.stringToCloudFormation)(property.instancesToTerminate), - InstanceTerminationTimeout: cdk.numberToCloudFormation(property.instanceTerminationTimeout?.toSeconds()), - }; - } - /** * Policy for customizing shrink operations. Allows configuration of decommissioning timeout and targeted instance shrinking. * @@ -116,32 +104,18 @@ export namespace EmrModifyInstanceGroupByName { /** * The desired timeout for decommissioning an instance. Overrides the default YARN decommissioning timeout. * - * @default EMR selected default + * @default - EMR selected default */ readonly decommissionTimeout?: cdk.Duration; /** * Custom policy for requesting termination protection or termination of specific instances when shrinking an instance group. * - * @default No instanceResizePolicy + * @default - None */ readonly instanceResizePolicy?: InstanceResizePolicyProperty; } - /** - * Render the ShrinkPolicyProperty to JSON - * - * @param property - */ - export function ShrinkPolicyPropertyToJson(property: ShrinkPolicyProperty) { - return { - DecommissionTimeout: cdk.numberToCloudFormation(property.decommissionTimeout?.toSeconds()), - InstanceResizePolicy: (property.instanceResizePolicy === undefined) ? - property.instanceResizePolicy : - InstanceResizePolicyPropertyToJson(property.instanceResizePolicy), - }; - } - /** * Modify the size or configurations of an instance group. * @@ -153,21 +127,21 @@ export namespace EmrModifyInstanceGroupByName { /** * A list of new or modified configurations to apply for an instance group. * - * @default No configurations + * @default - None */ readonly configurations?: EmrCreateCluster.ConfigurationProperty[]; /** * The EC2 InstanceIds to terminate. After you terminate the instances, the instance group will not return to its original requested size. * - * @default No eC2InstanceIdsToTerminate + * @default - None */ readonly eC2InstanceIdsToTerminate?: string[]; /** * Target size for the instance group. * - * @default No instanceCount + * @default - None */ readonly instanceCount?: number; @@ -176,24 +150,8 @@ export namespace EmrModifyInstanceGroupByName { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_ShrinkPolicy.html * - * @default No shrinkPolicy + * @default - None */ readonly shrinkPolicy?: ShrinkPolicyProperty; } - - /** - * Render the InstanceGroupModifyConfigPropety to JSON - * - * @param property - */ - export function InstanceGroupModifyConfigPropertyToJson(property: InstanceGroupModifyConfigProperty) { - return { - Configurations: cdk.listMapper(EmrCreateCluster.ConfigurationPropertyToJson)(property.configurations), - EC2InstanceIdsToTerminate: cdk.listMapper(cdk.stringToCloudFormation)(property.eC2InstanceIdsToTerminate), - InstanceCount: cdk.numberToCloudFormation(property.instanceCount), - ShrinkPolicy: (property.shrinkPolicy === undefined) ? - property.shrinkPolicy : - ShrinkPolicyPropertyToJson(property.shrinkPolicy), - }; - } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-set-cluster-termination-protection.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-set-cluster-termination-protection.ts index 6a6e951fffc0a..c2b52f0cc9a4f 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-set-cluster-termination-protection.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-set-cluster-termination-protection.ts @@ -1,14 +1,14 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; -import { Aws } from '@aws-cdk/core'; -import { getResourceArn } from '../resource-arn-suffix'; +import { Aws, Construct } from '@aws-cdk/core'; +import { integrationResourceArn } from '../private/task-utils'; /** * Properties for EmrSetClusterTerminationProtection * * @experimental */ -export interface EmrSetClusterTerminationProtectionProps { +export interface EmrSetClusterTerminationProtectionProps extends sfn.TaskStateBaseProps { /** * The ClusterId to update. */ @@ -25,24 +25,30 @@ export interface EmrSetClusterTerminationProtectionProps { * * @experimental */ -export class EmrSetClusterTerminationProtection implements sfn.IStepFunctionsTask { +export class EmrSetClusterTerminationProtection extends sfn.TaskStateBase { - constructor(private readonly props: EmrSetClusterTerminationProtectionProps) {} + protected readonly taskPolicies?: iam.PolicyStatement[]; + protected readonly taskMetrics?: sfn.TaskMetricsConfig; - public bind(_task: sfn.Task): sfn.StepFunctionsTaskConfig { + constructor(scope: Construct, id: string, private readonly props: EmrSetClusterTerminationProtectionProps) { + super(scope, id, props); + + this.taskPolicies = [ + new iam.PolicyStatement({ + actions: ['elasticmapreduce:SetTerminationProtection'], + resources: [`arn:aws:elasticmapreduce:${Aws.REGION}:${Aws.ACCOUNT_ID}:cluster/*`], + }), + ]; + } + + protected renderTask(): any { return { - resourceArn: getResourceArn('elasticmapreduce', 'setClusterTerminationProtection', - sfn.ServiceIntegrationPattern.FIRE_AND_FORGET), - policyStatements: [ - new iam.PolicyStatement({ - actions: ['elasticmapreduce:SetTerminationProtection'], - resources: [`arn:aws:elasticmapreduce:${Aws.REGION}:${Aws.ACCOUNT_ID}:cluster/*`], - }), - ], - parameters: { + Resource: integrationResourceArn('elasticmapreduce', 'setClusterTerminationProtection', + sfn.IntegrationPattern.REQUEST_RESPONSE), + Parameters: sfn.FieldUtils.renderObject({ ClusterId: this.props.clusterId, TerminationProtected: this.props.terminationProtected, - }, + }), }; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-terminate-cluster.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-terminate-cluster.ts index 1de355888f830..3aa9d1dc5a138 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-terminate-cluster.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-terminate-cluster.ts @@ -1,27 +1,18 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; -import { Aws, Stack } from '@aws-cdk/core'; -import { getResourceArn } from '../resource-arn-suffix'; +import { Aws, Construct, Stack } from '@aws-cdk/core'; +import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; /** * Properties for EmrTerminateCluster * * @experimental */ -export interface EmrTerminateClusterProps { +export interface EmrTerminateClusterProps extends sfn.TaskStateBaseProps { /** * The ClusterId to terminate. */ readonly clusterId: string; - - /** - * The service integration pattern indicates different ways to call TerminateCluster. - * - * The valid value is either FIRE_AND_FORGET or SYNC. - * - * @default SYNC - */ - readonly integrationPattern?: sfn.ServiceIntegrationPattern; } /** @@ -29,38 +20,39 @@ export interface EmrTerminateClusterProps { * * @experimental */ -export class EmrTerminateCluster implements sfn.IStepFunctionsTask { +export class EmrTerminateCluster extends sfn.TaskStateBase { + private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ + sfn.IntegrationPattern.REQUEST_RESPONSE, + sfn.IntegrationPattern.RUN_JOB, + ]; - private readonly integrationPattern: sfn.ServiceIntegrationPattern; + protected readonly taskPolicies?: iam.PolicyStatement[]; + protected readonly taskMetrics?: sfn.TaskMetricsConfig; - constructor(private readonly props: EmrTerminateClusterProps) { - this.integrationPattern = props.integrationPattern || sfn.ServiceIntegrationPattern.SYNC; + private readonly integrationPattern: sfn.IntegrationPattern; - const supportedPatterns = [ - sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - sfn.ServiceIntegrationPattern.SYNC, - ]; + constructor(scope: Construct, id: string, private readonly props: EmrTerminateClusterProps) { + super(scope, id, props); + this.integrationPattern = props.integrationPattern ?? sfn.IntegrationPattern.RUN_JOB; + validatePatternSupported(this.integrationPattern, EmrTerminateCluster.SUPPORTED_INTEGRATION_PATTERNS); - if (!supportedPatterns.includes(this.integrationPattern)) { - throw new Error(`Invalid Service Integration Pattern: ${this.integrationPattern} is not supported to call TerminateCluster.`); - } + this.taskPolicies = this.createPolicyStatements(); } - public bind(_task: sfn.Task): sfn.StepFunctionsTaskConfig { + protected renderTask(): any { return { - resourceArn: getResourceArn('elasticmapreduce', 'terminateCluster', this.integrationPattern), - policyStatements: this.createPolicyStatements(_task), - parameters: { + Resource: integrationResourceArn('elasticmapreduce', 'terminateCluster', this.integrationPattern), + Parameters: sfn.FieldUtils.renderObject({ ClusterId: this.props.clusterId, - }, + }), }; } /** * This generates the PolicyStatements required by the Task to call TerminateCluster. */ - private createPolicyStatements(task: sfn.Task): iam.PolicyStatement[] { - const stack = Stack.of(task); + private createPolicyStatements(): iam.PolicyStatement[] { + const stack = Stack.of(this); const policyStatements = [ new iam.PolicyStatement({ @@ -72,7 +64,7 @@ export class EmrTerminateCluster implements sfn.IStepFunctionsTask { }), ]; - if (this.integrationPattern === sfn.ServiceIntegrationPattern.SYNC) { + if (this.integrationPattern === sfn.IntegrationPattern.RUN_JOB) { policyStatements.push(new iam.PolicyStatement({ actions: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'], resources: [stack.formatArn({ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/private/cluster-utils.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/private/cluster-utils.ts new file mode 100644 index 0000000000000..37cf474c83ea0 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/private/cluster-utils.ts @@ -0,0 +1,314 @@ +import * as cdk from '@aws-cdk/core'; +import { EmrCreateCluster } from '../emr-create-cluster'; +import { EmrModifyInstanceGroupByName } from '../emr-modify-instance-group-by-name'; + +/** + * Render the KerberosAttributesProperty as JSON + * + * @param property + */ +export function KerberosAttributesPropertyToJson(property: EmrCreateCluster.KerberosAttributesProperty) { + return { + ADDomainJoinPassword: cdk.stringToCloudFormation(property.adDomainJoinPassword), + ADDomainJoinUser: cdk.stringToCloudFormation(property.adDomainJoinUser), + CrossRealmTrustPrincipalPassword: cdk.stringToCloudFormation(property.crossRealmTrustPrincipalPassword), + KdcAdminPassword: cdk.stringToCloudFormation(property.kdcAdminPassword), + Realm: cdk.stringToCloudFormation(property.realm), + }; +} + +/** + * Render the InstancesConfigProperty to JSON + * + * @param property + */ +export function InstancesConfigPropertyToJson(property: EmrCreateCluster.InstancesConfigProperty) { + return { + AdditionalMasterSecurityGroups: cdk.listMapper(cdk.stringToCloudFormation)(property.additionalMasterSecurityGroups), + AdditionalSlaveSecurityGroups: cdk.listMapper(cdk.stringToCloudFormation)(property.additionalSlaveSecurityGroups), + Ec2KeyName: cdk.stringToCloudFormation(property.ec2KeyName), + Ec2SubnetId: cdk.stringToCloudFormation(property.ec2SubnetId), + Ec2SubnetIds: cdk.listMapper(cdk.stringToCloudFormation)(property.ec2SubnetIds), + EmrManagedMasterSecurityGroup: cdk.stringToCloudFormation(property.emrManagedMasterSecurityGroup), + EmrManagedSlaveSecurityGroup: cdk.stringToCloudFormation(property.emrManagedSlaveSecurityGroup), + HadoopVersion: cdk.stringToCloudFormation(property.hadoopVersion), + InstanceCount: cdk.numberToCloudFormation(property.instanceCount), + InstanceFleets: cdk.listMapper(InstanceFleetConfigPropertyToJson)(property.instanceFleets), + InstanceGroups: cdk.listMapper(InstanceGroupConfigPropertyToJson)(property.instanceGroups), + KeepJobFlowAliveWhenNoSteps: true, + MasterInstanceType: cdk.stringToCloudFormation(property.masterInstanceType), + Placement: property.placement === undefined ? property.placement : PlacementTypePropertyToJson(property.placement), + ServiceAccessSecurityGroup: cdk.stringToCloudFormation(property.serviceAccessSecurityGroup), + SlaveInstanceType: cdk.stringToCloudFormation(property.slaveInstanceType), + TerminationProtected: cdk.booleanToCloudFormation(property.terminationProtected), + }; +} + +/** + * Render the ApplicationConfigProperty as JSON + * + * @param property + */ +export function ApplicationConfigPropertyToJson(property: EmrCreateCluster.ApplicationConfigProperty) { + return { + Name: cdk.stringToCloudFormation(property.name), + Args: cdk.listMapper(cdk.stringToCloudFormation)(property.args), + Version: cdk.stringToCloudFormation(property.version), + AdditionalInfo: cdk.objectToCloudFormation(property.additionalInfo), + }; +} + +/** + * Render the ConfigurationProperty as JSON + * + * @param property + */ +export function ConfigurationPropertyToJson(property: EmrCreateCluster.ConfigurationProperty) { + return { + Classification: cdk.stringToCloudFormation(property.classification), + Properties: cdk.objectToCloudFormation(property.properties), + Configurations: cdk.listMapper(ConfigurationPropertyToJson)(property.configurations), + }; +} + +/** + * Render the EbsBlockDeviceConfigProperty as JSON + * + * @param property + */ +export function EbsBlockDeviceConfigPropertyToJson(property: EmrCreateCluster.EbsBlockDeviceConfigProperty) { + return { + VolumeSpecification: { + Iops: cdk.numberToCloudFormation(property.volumeSpecification.iops), + SizeInGB: property.volumeSpecification.volumeSize?.toGibibytes(), + VolumeType: cdk.stringToCloudFormation(property.volumeSpecification.volumeType?.valueOf()), + }, + VolumesPerInstance: cdk.numberToCloudFormation(property.volumesPerInstance), + }; +} + +/** + * Render the EbsConfigurationProperty to JSON + * + * @param property + */ +export function EbsConfigurationPropertyToJson(property: EmrCreateCluster.EbsConfigurationProperty) { + return { + EbsBlockDeviceConfigs: cdk.listMapper(EbsBlockDeviceConfigPropertyToJson)(property.ebsBlockDeviceConfigs), + EbsOptimized: cdk.booleanToCloudFormation(property.ebsOptimized), + }; +} + +/** + * Render the InstanceTypeConfigProperty to JSON] + * + * @param property + */ +export function InstanceTypeConfigPropertyToJson(property: EmrCreateCluster.InstanceTypeConfigProperty) { + return { + BidPrice: cdk.stringToCloudFormation(property.bidPrice), + BidPriceAsPercentageOfOnDemandPrice: cdk.numberToCloudFormation(property.bidPriceAsPercentageOfOnDemandPrice), + Configurations: cdk.listMapper(ConfigurationPropertyToJson)(property.configurations), + EbsConfiguration: property.ebsConfiguration === undefined ? property.ebsConfiguration : EbsConfigurationPropertyToJson(property.ebsConfiguration), + InstanceType: cdk.stringToCloudFormation(property.instanceType?.valueOf()), + WeightedCapacity: cdk.numberToCloudFormation(property.weightedCapacity), + }; +} + +/** + * Render the InstanceFleetProvisioningSpecificationsProperty to JSON + * + * @param property + */ +export function InstanceFleetProvisioningSpecificationsPropertyToJson(property: EmrCreateCluster.InstanceFleetProvisioningSpecificationsProperty) { + return { + SpotSpecification: { + BlockDurationMinutes: cdk.numberToCloudFormation(property.spotSpecification.blockDurationMinutes), + TimeoutAction: cdk.stringToCloudFormation(property.spotSpecification.timeoutAction?.valueOf()), + TimeoutDurationMinutes: cdk.numberToCloudFormation(property.spotSpecification.timeoutDurationMinutes), + }, + }; +} + +/** + * Render the InstanceFleetConfigProperty as JSON + * + * @param property + */ +export function InstanceFleetConfigPropertyToJson(property: EmrCreateCluster.InstanceFleetConfigProperty) { + return { + InstanceFleetType: cdk.stringToCloudFormation(property.instanceFleetType?.valueOf()), + InstanceTypeConfigs: cdk.listMapper(InstanceTypeConfigPropertyToJson)(property.instanceTypeConfigs), + LaunchSpecifications: + property.launchSpecifications === undefined + ? property.launchSpecifications + : InstanceFleetProvisioningSpecificationsPropertyToJson(property.launchSpecifications), + Name: cdk.stringToCloudFormation(property.name), + TargetOnDemandCapacity: cdk.numberToCloudFormation(property.targetOnDemandCapacity), + TargetSpotCapacity: cdk.numberToCloudFormation(property.targetSpotCapacity), + }; +} + +/** + * Render the MetricDimensionProperty as JSON + * + * @param property + */ +export function MetricDimensionPropertyToJson(property: EmrCreateCluster.MetricDimensionProperty) { + return { + Key: cdk.stringToCloudFormation(property.key), + Value: cdk.stringToCloudFormation(property.value), + }; +} + +/** + * Render the ScalingTriggerProperty to JSON + * + * @param property + */ +export function ScalingTriggerPropertyToJson(property: EmrCreateCluster.ScalingTriggerProperty) { + return { + CloudWatchAlarmDefinition: { + ComparisonOperator: cdk.stringToCloudFormation(property.cloudWatchAlarmDefinition.comparisonOperator?.valueOf()), + Dimensions: cdk.listMapper(MetricDimensionPropertyToJson)(property.cloudWatchAlarmDefinition.dimensions), + EvaluationPeriods: cdk.numberToCloudFormation(property.cloudWatchAlarmDefinition.evaluationPeriods), + MetricName: cdk.stringToCloudFormation(property.cloudWatchAlarmDefinition.metricName), + Namespace: cdk.stringToCloudFormation(property.cloudWatchAlarmDefinition.namespace), + Period: cdk.numberToCloudFormation(property.cloudWatchAlarmDefinition.period.toSeconds()), + Statistic: cdk.stringToCloudFormation(property.cloudWatchAlarmDefinition.statistic?.valueOf()), + Threshold: cdk.numberToCloudFormation(property.cloudWatchAlarmDefinition.threshold), + Unit: cdk.stringToCloudFormation(property.cloudWatchAlarmDefinition.unit?.valueOf()), + }, + }; +} + +/** + * Render the ScalingActionProperty to JSON + * + * @param property + */ +export function ScalingActionPropertyToJson(property: EmrCreateCluster.ScalingActionProperty) { + return { + Market: cdk.stringToCloudFormation(property.market?.valueOf()), + SimpleScalingPolicyConfiguration: { + AdjustmentType: cdk.stringToCloudFormation(property.simpleScalingPolicyConfiguration.adjustmentType), + CoolDown: cdk.numberToCloudFormation(property.simpleScalingPolicyConfiguration.coolDown), + ScalingAdjustment: cdk.numberToCloudFormation(property.simpleScalingPolicyConfiguration.scalingAdjustment), + }, + }; +} + +/** + * Render the ScalingRuleProperty to JSON + * + * @param property + */ +export function ScalingRulePropertyToJson(property: EmrCreateCluster.ScalingRuleProperty) { + return { + Action: ScalingActionPropertyToJson(property.action), + Description: cdk.stringToCloudFormation(property.description), + Name: cdk.stringToCloudFormation(property.name), + Trigger: ScalingTriggerPropertyToJson(property.trigger), + }; +} + +/** + * Render the AutoScalingPolicyProperty to JSON + * + * @param property + */ +export function AutoScalingPolicyPropertyToJson(property: EmrCreateCluster.AutoScalingPolicyProperty) { + return { + Constraints: { + MaxCapacity: cdk.numberToCloudFormation(property.constraints.maxCapacity), + MinCapacity: cdk.numberToCloudFormation(property.constraints.minCapacity), + }, + Rules: cdk.listMapper(ScalingRulePropertyToJson)(property.rules), + }; +} + +/** + * Render the InstanceGroupConfigProperty to JSON + * + * @param property + */ +export function InstanceGroupConfigPropertyToJson(property: EmrCreateCluster.InstanceGroupConfigProperty) { + return { + AutoScalingPolicy: + property.autoScalingPolicy === undefined ? property.autoScalingPolicy : AutoScalingPolicyPropertyToJson(property.autoScalingPolicy), + BidPrice: cdk.numberToCloudFormation(property.bidPrice), + Configurations: cdk.listMapper(ConfigurationPropertyToJson)(property.configurations), + EbsConfiguration: property.ebsConfiguration === undefined ? property.ebsConfiguration : EbsConfigurationPropertyToJson(property.ebsConfiguration), + InstanceCount: cdk.numberToCloudFormation(property.instanceCount), + InstanceRole: cdk.stringToCloudFormation(property.instanceRole?.valueOf()), + InstanceType: cdk.stringToCloudFormation(property.instanceType), + Market: cdk.stringToCloudFormation(property.market?.valueOf()), + Name: cdk.stringToCloudFormation(property.name), + }; +} + +/** + * Render the PlacementTypeProperty to JSON + * + * @param property + */ +export function PlacementTypePropertyToJson(property: EmrCreateCluster.PlacementTypeProperty) { + return { + AvailabilityZone: cdk.stringToCloudFormation(property.availabilityZone), + AvailabilityZones: cdk.listMapper(cdk.stringToCloudFormation)(property.availabilityZones), + }; +} + +/** + * Render the BootstrapActionProperty as JSON + * + * @param property + */ +export function BootstrapActionConfigToJson(property: EmrCreateCluster.BootstrapActionConfigProperty) { + return { + Name: cdk.stringToCloudFormation(property.name), + ScriptBootstrapAction: { + Path: cdk.stringToCloudFormation(property.scriptBootstrapAction.path), + Args: cdk.listMapper(cdk.stringToCloudFormation)(property.scriptBootstrapAction.args), + }, + }; +} + +/** + * Render the InstanceGroupModifyConfigProperty to JSON + * + * @param property + */ +export function InstanceGroupModifyConfigPropertyToJson(property: EmrModifyInstanceGroupByName.InstanceGroupModifyConfigProperty) { + return { + Configurations: cdk.listMapper(ConfigurationPropertyToJson)(property.configurations), + EC2InstanceIdsToTerminate: cdk.listMapper(cdk.stringToCloudFormation)(property.eC2InstanceIdsToTerminate), + InstanceCount: cdk.numberToCloudFormation(property.instanceCount), + ShrinkPolicy: property.shrinkPolicy === undefined ? property.shrinkPolicy : ShrinkPolicyPropertyToJson(property.shrinkPolicy), + }; +} + +/** + * Render the ShrinkPolicyProperty to JSON + * + * @param property + */ +function ShrinkPolicyPropertyToJson(property: EmrModifyInstanceGroupByName.ShrinkPolicyProperty) { + return { + DecommissionTimeout: cdk.numberToCloudFormation(property.decommissionTimeout?.toSeconds()), + InstanceResizePolicy: property.instanceResizePolicy ? InstanceResizePolicyPropertyToJson(property.instanceResizePolicy) : undefined, + }; +} + +/** + * Render the InstanceResizePolicyProperty to JSON + * + * @param property + */ +function InstanceResizePolicyPropertyToJson(property: EmrModifyInstanceGroupByName.InstanceResizePolicyProperty) { + return { + InstancesToProtect: cdk.listMapper(cdk.stringToCloudFormation)(property.instancesToProtect), + InstancesToTerminate: cdk.listMapper(cdk.stringToCloudFormation)(property.instancesToTerminate), + InstanceTerminationTimeout: cdk.numberToCloudFormation(property.instanceTerminationTimeout?.toSeconds()), + }; +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-add-step.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-add-step.test.ts index 4d3474e7e71fc..63633412a4933 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-add-step.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-add-step.test.ts @@ -11,14 +11,12 @@ beforeEach(() => { test('Add Step with static ClusterId and Step configuration', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrAddStep({ - clusterId: 'ClusterId', - name: 'StepName', - jar: 'Jar', - actionOnFailure: tasks.ActionOnFailure.CONTINUE, - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - }), + const task = new tasks.EmrAddStep(stack, 'Task', { + clusterId: 'ClusterId', + name: 'StepName', + jar: 'Jar', + actionOnFailure: tasks.ActionOnFailure.CONTINUE, + integrationPattern: sfn.IntegrationPattern.RUN_JOB, }); // THEN @@ -52,14 +50,12 @@ test('Add Step with static ClusterId and Step configuration', () => { test('Terminate cluster with ClusterId from payload and static Step configuration', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrAddStep({ - clusterId: sfn.Data.stringAt('$.ClusterId'), - name: 'StepName', - jar: 'Jar', - actionOnFailure: tasks.ActionOnFailure.CONTINUE, - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - }), + const task = new tasks.EmrAddStep(stack, 'Task', { + clusterId: sfn.Data.stringAt('$.ClusterId'), + name: 'StepName', + jar: 'Jar', + actionOnFailure: tasks.ActionOnFailure.CONTINUE, + integrationPattern: sfn.IntegrationPattern.RUN_JOB, }); // THEN @@ -93,14 +89,12 @@ test('Terminate cluster with ClusterId from payload and static Step configuratio test('Add Step with static ClusterId and Step Name from payload', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrAddStep({ - clusterId: 'ClusterId', - name: sfn.Data.stringAt('$.StepName'), - jar: 'Jar', - actionOnFailure: tasks.ActionOnFailure.CONTINUE, - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - }), + const task = new tasks.EmrAddStep(stack, 'Task', { + clusterId: 'ClusterId', + name: sfn.Data.stringAt('$.StepName'), + jar: 'Jar', + actionOnFailure: tasks.ActionOnFailure.CONTINUE, + integrationPattern: sfn.IntegrationPattern.RUN_JOB, }); // THEN @@ -134,14 +128,12 @@ test('Add Step with static ClusterId and Step Name from payload', () => { test('Add Step with static ClusterId and Step configuration and FIRE_AND_FORGET integrationPattern', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrAddStep({ - clusterId: 'ClusterId', - name: 'StepName', - jar: 'Jar', - actionOnFailure: tasks.ActionOnFailure.CONTINUE, - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }), + const task = new tasks.EmrAddStep(stack, 'Task', { + clusterId: 'ClusterId', + name: 'StepName', + jar: 'Jar', + actionOnFailure: tasks.ActionOnFailure.CONTINUE, + integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE, }); // THEN @@ -175,14 +167,12 @@ test('Add Step with static ClusterId and Step configuration and FIRE_AND_FORGET test('Add Step with static ClusterId and Step configuration with TERMINATE_CLUSTER', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrAddStep({ - clusterId: 'ClusterId', - name: 'StepName', - jar: 'Jar', - actionOnFailure: tasks.ActionOnFailure.TERMINATE_CLUSTER, - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - }), + const task = new tasks.EmrAddStep(stack, 'Task', { + clusterId: 'ClusterId', + name: 'StepName', + jar: 'Jar', + actionOnFailure: tasks.ActionOnFailure.TERMINATE_CLUSTER, + integrationPattern: sfn.IntegrationPattern.RUN_JOB, }); // THEN @@ -216,15 +206,13 @@ test('Add Step with static ClusterId and Step configuration with TERMINATE_CLUST test('Add Step with static ClusterId and Step configuration with Args', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrAddStep({ - clusterId: 'ClusterId', - name: 'StepName', - jar: 'Jar', - args: ['Arg1'], - actionOnFailure: tasks.ActionOnFailure.CONTINUE, - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - }), + const task = new tasks.EmrAddStep(stack, 'Task', { + clusterId: 'ClusterId', + name: 'StepName', + jar: 'Jar', + args: ['Arg1'], + actionOnFailure: tasks.ActionOnFailure.CONTINUE, + integrationPattern: sfn.IntegrationPattern.RUN_JOB, }); // THEN @@ -259,17 +247,15 @@ test('Add Step with static ClusterId and Step configuration with Args', () => { test('Add Step with static ClusterId and Step configuration with Properties', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrAddStep({ - clusterId: 'ClusterId', - name: 'StepName', - jar: 'Jar', - properties: { - PropertyKey: 'PropertyValue', - }, - actionOnFailure: tasks.ActionOnFailure.CONTINUE, - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - }), + const task = new tasks.EmrAddStep(stack, 'Task', { + clusterId: 'ClusterId', + name: 'StepName', + jar: 'Jar', + properties: { + PropertyKey: 'PropertyValue', + }, + actionOnFailure: tasks.ActionOnFailure.CONTINUE, + integrationPattern: sfn.IntegrationPattern.RUN_JOB, }); // THEN @@ -307,14 +293,12 @@ test('Add Step with static ClusterId and Step configuration with Properties', () test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.EmrAddStep({ - clusterId: 'ClusterId', - name: 'StepName', - jar: 'Jar', - actionOnFailure: tasks.ActionOnFailure.CONTINUE, - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - }), + new tasks.EmrAddStep(stack, 'Task', { + clusterId: 'ClusterId', + name: 'StepName', + jar: 'Jar', + actionOnFailure: tasks.ActionOnFailure.CONTINUE, + integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, }); - }).toThrow(/Invalid Service Integration Pattern: WAIT_FOR_TASK_TOKEN is not supported to call AddStep./i); + }).toThrow(/Unsupported service integration pattern. Supported Patterns: REQUEST_RESPONSE,RUN_JOB. Received: WAIT_FOR_TASK_TOKEN/); }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-cancel-step.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-cancel-step.test.ts index 75dd92d85085b..e7a1cf2194708 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-cancel-step.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-cancel-step.test.ts @@ -11,11 +11,9 @@ beforeEach(() => { test('Cancel a Step with static ClusterId and StepId', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrCancelStep({ - clusterId: 'ClusterId', - stepId: 'StepId', - }), + const task = new tasks.EmrCancelStep(stack, 'Task', { + clusterId: 'ClusterId', + stepId: 'StepId', }); // THEN @@ -43,11 +41,9 @@ test('Cancel a Step with static ClusterId and StepId', () => { test('Cancel a Step with static ClusterId and StepId from payload', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrCancelStep({ - clusterId: 'ClusterId', - stepId: sfn.TaskInput.fromDataAt('$.StepId').value, - }), + const task = new tasks.EmrCancelStep(stack, 'Task', { + clusterId: 'ClusterId', + stepId: sfn.TaskInput.fromDataAt('$.StepId').value, }); // THEN @@ -75,11 +71,9 @@ test('Cancel a Step with static ClusterId and StepId from payload', () => { test('Cancel a Step with ClusterId from payload and static StepId', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrCancelStep({ - clusterId: sfn.TaskInput.fromDataAt('$.ClusterId').value, - stepId: 'StepId', - }), + const task = new tasks.EmrCancelStep(stack, 'Task', { + clusterId: sfn.TaskInput.fromDataAt('$.ClusterId').value, + stepId: 'StepId', }); // THEN diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-create-cluster.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-create-cluster.test.ts index 6993543b9e1d6..06d1a2da8d34b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-create-cluster.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-create-cluster.test.ts @@ -2,7 +2,7 @@ import '@aws-cdk/assert/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; -import * as tasks from '../../lib'; +import { EmrCreateCluster } from '../../lib'; let stack: cdk.Stack; let clusterRole: iam.Role; @@ -36,14 +36,14 @@ beforeEach(() => { test('Create Cluster with FIRE_AND_FORGET integrationPattern', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { task: new tasks.EmrCreateCluster({ + const task = new EmrCreateCluster(stack, 'Task', { instances: {}, clusterRole, name: 'Cluster', serviceRole, autoScalingRole, - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }) }); + integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE, + }); // THEN expect(stack.resolve(task.toStateJson())).toEqual({ @@ -82,14 +82,14 @@ test('Create Cluster with FIRE_AND_FORGET integrationPattern', () => { test('Create Cluster with SYNC integrationPattern', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { task: new tasks.EmrCreateCluster({ + const task = new EmrCreateCluster(stack, 'Task', { instances: {}, clusterRole, name: 'Cluster', serviceRole, autoScalingRole, - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - }) }); + integrationPattern: sfn.IntegrationPattern.RUN_JOB, + }); // THEN expect(stack.resolve(task.toStateJson())).toEqual({ @@ -128,14 +128,14 @@ test('Create Cluster with SYNC integrationPattern', () => { test('Create Cluster with clusterConfiguration Name from payload', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { task: new tasks.EmrCreateCluster({ + const task = new EmrCreateCluster(stack, 'Task', { instances: {}, clusterRole, name: sfn.TaskInput.fromDataAt('$.ClusterName').value, serviceRole, autoScalingRole, - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }) }); + integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE, + }); // THEN expect(stack.resolve(task.toStateJson())).toEqual({ @@ -174,18 +174,17 @@ test('Create Cluster with clusterConfiguration Name from payload', () => { test('Create Cluster with Tags', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { task: new tasks.EmrCreateCluster({ + const task = new EmrCreateCluster(stack, 'Task', { instances: {}, clusterRole, name: 'Cluster', serviceRole, autoScalingRole, - tags: [{ - key: 'Key', - value: 'Value', - }], - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }) }); + tags: { + key: 'value', + }, + integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE, + }); // THEN expect(stack.resolve(task.toStateJson())).toEqual({ @@ -219,8 +218,8 @@ test('Create Cluster with Tags', () => { Ref: 'AutoScalingRole015ADA0A', }, Tags: [{ - Key: 'Key', - Value: 'Value', + Key: 'key', + Value: 'value', }], }, }); @@ -228,7 +227,7 @@ test('Create Cluster with Tags', () => { test('Create Cluster with Applications', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { task: new tasks.EmrCreateCluster({ + const task = new EmrCreateCluster(stack, 'Task', { instances: {}, clusterRole, name: 'Cluster', @@ -238,8 +237,8 @@ test('Create Cluster with Applications', () => { { name: 'Hive', version: '0.0' }, { name: 'Spark', version: '0.0' }, ], - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }) }); + integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE, + }); // THEN expect(stack.resolve(task.toStateJson())).toEqual({ @@ -282,7 +281,7 @@ test('Create Cluster with Applications', () => { test('Create Cluster with Bootstrap Actions', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { task: new tasks.EmrCreateCluster({ + const task = new EmrCreateCluster(stack, 'Task', { instances: {}, clusterRole, name: 'Cluster', @@ -295,8 +294,8 @@ test('Create Cluster with Bootstrap Actions', () => { args: [ 'Arg' ], }, }], - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }) }); + integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE, + }); // THEN expect(stack.resolve(task.toStateJson())).toEqual({ @@ -342,7 +341,7 @@ test('Create Cluster with Bootstrap Actions', () => { test('Create Cluster with Configurations', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { task: new tasks.EmrCreateCluster({ + const task = new EmrCreateCluster(stack, 'Task', { instances: {}, clusterRole, name: 'Cluster', @@ -354,8 +353,8 @@ test('Create Cluster with Configurations', () => { Key: 'Value', }, }], - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }) }); + integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE, + }); // THEN expect(stack.resolve(task.toStateJson())).toEqual({ @@ -400,7 +399,7 @@ test('Create Cluster with Configurations', () => { test('Create Cluster with KerberosAttributes', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { task: new tasks.EmrCreateCluster({ + const task = new EmrCreateCluster(stack, 'Task', { instances: {}, clusterRole, name: 'Cluster', @@ -413,8 +412,8 @@ test('Create Cluster with KerberosAttributes', () => { crossRealmTrustPrincipalPassword: 'password2', kdcAdminPassword: 'password3', }, - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }) }); + integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE, + }); // THEN expect(stack.resolve(task.toStateJson())).toEqual({ @@ -460,12 +459,11 @@ test('Create Cluster with KerberosAttributes', () => { test('Create Cluster without Roles', () => { // WHEN - const createClusterTask = new tasks.EmrCreateCluster({ + const task = new EmrCreateCluster(stack, 'Task', { instances: {}, name: 'Cluster', - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + integrationPattern: sfn.IntegrationPattern.RUN_JOB, }); - const task = new sfn.Task(stack, 'Task', { task: createClusterTask}); // THEN expect(stack.resolve(task.toStateJson())).toEqual({ @@ -563,7 +561,7 @@ test('Create Cluster without Roles', () => { test('Create Cluster with Instances configuration', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { task: new tasks.EmrCreateCluster({ + const task = new EmrCreateCluster(stack, 'Task', { instances: { additionalMasterSecurityGroups: ['MasterGroup'], additionalSlaveSecurityGroups: ['SlaveGroup'], @@ -587,8 +585,8 @@ test('Create Cluster with Instances configuration', () => { name: 'Cluster', serviceRole, autoScalingRole, - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }) }); + integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE, + }); // THEN expect(stack.resolve(task.toStateJson())).toEqual({ @@ -644,10 +642,10 @@ test('Create Cluster with Instances configuration', () => { test('Create Cluster with InstanceFleet', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { task: new tasks.EmrCreateCluster({ + const task = new EmrCreateCluster(stack, 'Task', { instances: { instanceFleets: [{ - instanceFleetType: tasks.EmrCreateCluster.InstanceRoleType.MASTER, + instanceFleetType: EmrCreateCluster.InstanceRoleType.MASTER, instanceTypeConfigs: [{ bidPrice: '1', bidPriceAsPercentageOfOnDemandPrice: 1, @@ -661,8 +659,8 @@ test('Create Cluster with InstanceFleet', () => { ebsBlockDeviceConfigs: [{ volumeSpecification: { iops: 1, - sizeInGB: 1, - volumeType: tasks.EmrCreateCluster.EbsBlockDeviceVolumeType.STANDARD, + volumeSize: cdk.Size.gibibytes(1), + volumeType: EmrCreateCluster.EbsBlockDeviceVolumeType.STANDARD, }, volumesPerInstance: 1, }], @@ -674,7 +672,7 @@ test('Create Cluster with InstanceFleet', () => { launchSpecifications: { spotSpecification: { blockDurationMinutes: 1, - timeoutAction: tasks.EmrCreateCluster.SpotTimeoutAction.TERMINATE_CLUSTER, + timeoutAction: EmrCreateCluster.SpotTimeoutAction.TERMINATE_CLUSTER, timeoutDurationMinutes: 1, }, }, @@ -686,8 +684,8 @@ test('Create Cluster with InstanceFleet', () => { clusterRole, name: 'Cluster', serviceRole, - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }) }); + integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE, + }); // THEN expect(stack.resolve(task.toStateJson())).toEqual({ @@ -759,7 +757,7 @@ test('Create Cluster with InstanceFleet', () => { test('Create Cluster with InstanceGroup', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { task: new tasks.EmrCreateCluster({ + const task = new EmrCreateCluster(stack, 'Task', { instances: { instanceGroups: [{ autoScalingPolicy: { @@ -769,9 +767,9 @@ test('Create Cluster with InstanceGroup', () => { }, rules: [{ action: { - market: tasks.EmrCreateCluster.InstanceMarket.ON_DEMAND, + market: EmrCreateCluster.InstanceMarket.ON_DEMAND, simpleScalingPolicyConfiguration: { - adjustmentType: tasks.EmrCreateCluster.ScalingAdjustmentType.CHANGE_IN_CAPACITY, + adjustmentType: EmrCreateCluster.ScalingAdjustmentType.CHANGE_IN_CAPACITY, coolDown: 1, scalingAdjustment: 1, }, @@ -780,25 +778,25 @@ test('Create Cluster with InstanceGroup', () => { name: 'Name', trigger: { cloudWatchAlarmDefinition: { - comparisonOperator: tasks.EmrCreateCluster.CloudWatchAlarmComparisonOperator.GREATER_THAN, + comparisonOperator: EmrCreateCluster.CloudWatchAlarmComparisonOperator.GREATER_THAN, dimensions: [{ key: 'Key', value: 'Value', }], - evalutionPeriods: 1, + evaluationPeriods: 1, metricName: 'Name', namespace: 'Namespace', period: cdk.Duration.seconds(300), - statistic: tasks.EmrCreateCluster.CloudWatchAlarmStatistic.AVERAGE, + statistic: EmrCreateCluster.CloudWatchAlarmStatistic.AVERAGE, threshold: 1, - unit: tasks.EmrCreateCluster.CloudWatchAlarmUnit.NONE, + unit: EmrCreateCluster.CloudWatchAlarmUnit.NONE, }, }, }, { action: { - market: tasks.EmrCreateCluster.InstanceMarket.ON_DEMAND, + market: EmrCreateCluster.InstanceMarket.ON_DEMAND, simpleScalingPolicyConfiguration: { - adjustmentType: tasks.EmrCreateCluster.ScalingAdjustmentType.CHANGE_IN_CAPACITY, + adjustmentType: EmrCreateCluster.ScalingAdjustmentType.CHANGE_IN_CAPACITY, coolDown: 1, scalingAdjustment: 1, }, @@ -807,18 +805,18 @@ test('Create Cluster with InstanceGroup', () => { name: 'Name', trigger: { cloudWatchAlarmDefinition: { - comparisonOperator: tasks.EmrCreateCluster.CloudWatchAlarmComparisonOperator.GREATER_THAN, + comparisonOperator: EmrCreateCluster.CloudWatchAlarmComparisonOperator.GREATER_THAN, dimensions: [{ key: 'Key', value: 'Value', }], - evalutionPeriods: 1, + evaluationPeriods: 1, metricName: 'Name', namespace: 'Namespace', period: cdk.Duration.seconds(sfn.Data.numberAt('$.CloudWatchPeriod')), - statistic: tasks.EmrCreateCluster.CloudWatchAlarmStatistic.AVERAGE, + statistic: EmrCreateCluster.CloudWatchAlarmStatistic.AVERAGE, threshold: 1, - unit: tasks.EmrCreateCluster.CloudWatchAlarmUnit.NONE, + unit: EmrCreateCluster.CloudWatchAlarmUnit.NONE, }, }, }], @@ -834,17 +832,17 @@ test('Create Cluster with InstanceGroup', () => { ebsBlockDeviceConfigs: [{ volumeSpecification: { iops: 1, - sizeInGB: 1, - volumeType: tasks.EmrCreateCluster.EbsBlockDeviceVolumeType.STANDARD, + volumeSize: cdk.Size.gibibytes(1), + volumeType: EmrCreateCluster.EbsBlockDeviceVolumeType.STANDARD, }, volumesPerInstance: 1, }], ebsOptimized: true, }, instanceCount: 1, - instanceRole: tasks.EmrCreateCluster.InstanceRoleType.MASTER, + instanceRole: EmrCreateCluster.InstanceRoleType.MASTER, instanceType: 'm5.xlarge', - market: tasks.EmrCreateCluster.InstanceMarket.ON_DEMAND, + market: EmrCreateCluster.InstanceMarket.ON_DEMAND, name: 'Name', }], }, @@ -852,8 +850,8 @@ test('Create Cluster with InstanceGroup', () => { name: 'Cluster', serviceRole, autoScalingRole, - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }) }); + integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE, + }); // THEN expect(stack.resolve(task.toStateJson())).toEqual({ @@ -978,15 +976,13 @@ test('Create Cluster with InstanceGroup', () => { test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.EmrCreateCluster({ - instances: {}, - clusterRole, - name: 'Cluster', - serviceRole, - autoScalingRole, - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - }), + new EmrCreateCluster(stack, 'Task', { + instances: {}, + clusterRole, + name: 'Cluster', + serviceRole, + autoScalingRole, + integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, }); - }).toThrow(/Invalid Service Integration Pattern: WAIT_FOR_TASK_TOKEN is not supported to call CreateCluster./i); + }).toThrow(/Unsupported service integration pattern. Supported Patterns: REQUEST_RESPONSE,RUN_JOB. Received: WAIT_FOR_TASK_TOKEN/); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-fleet-by-name.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-fleet-by-name.test.ts index db62542123224..e6612d92b1cf4 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-fleet-by-name.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-fleet-by-name.test.ts @@ -11,13 +11,11 @@ beforeEach(() => { test('Modify an InstanceFleet with static ClusterId, InstanceFleetName, and InstanceFleetConfiguration', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrModifyInstanceFleetByName({ - clusterId: 'ClusterId', - instanceFleetName: 'InstanceFleetName', - targetOnDemandCapacity: 2, - targetSpotCapacity: 0, - }), + const task = new tasks.EmrModifyInstanceFleetByName(stack, 'Task', { + clusterId: 'ClusterId', + instanceFleetName: 'InstanceFleetName', + targetOnDemandCapacity: 2, + targetSpotCapacity: 0, }); // THEN @@ -49,13 +47,11 @@ test('Modify an InstanceFleet with static ClusterId, InstanceFleetName, and Inst test('Modify an InstanceFleet with ClusterId from payload and static InstanceFleetName and InstanceFleetConfiguration', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrModifyInstanceFleetByName({ - clusterId: sfn.Data.stringAt('$.ClusterId'), - instanceFleetName: 'InstanceFleetName', - targetOnDemandCapacity: 2, - targetSpotCapacity: 0, - }), + const task = new tasks.EmrModifyInstanceFleetByName(stack, 'Task', { + clusterId: sfn.Data.stringAt('$.ClusterId'), + instanceFleetName: 'InstanceFleetName', + targetOnDemandCapacity: 2, + targetSpotCapacity: 0, }); // THEN @@ -87,13 +83,11 @@ test('Modify an InstanceFleet with ClusterId from payload and static InstanceFle test('Modify an InstanceFleet with static ClusterId and InstanceFleetConfigurateion and InstanceFleetName from payload', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrModifyInstanceFleetByName({ - clusterId: 'ClusterId', - instanceFleetName: sfn.Data.stringAt('$.InstanceFleetName'), - targetOnDemandCapacity: 2, - targetSpotCapacity: 0, - }), + const task = new tasks.EmrModifyInstanceFleetByName(stack, 'Task', { + clusterId: 'ClusterId', + instanceFleetName: sfn.Data.stringAt('$.InstanceFleetName'), + targetOnDemandCapacity: 2, + targetSpotCapacity: 0, }); // THEN @@ -125,13 +119,11 @@ test('Modify an InstanceFleet with static ClusterId and InstanceFleetConfigurate test('Modify an InstanceFleet with static ClusterId and InstanceFleetName and Target Capacities from payload', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrModifyInstanceFleetByName({ - clusterId: 'ClusterId', - instanceFleetName: 'InstanceFleetName', - targetOnDemandCapacity: sfn.Data.numberAt('$.TargetOnDemandCapacity'), - targetSpotCapacity: sfn.Data.numberAt('$.TargetSpotCapacity'), - }), + const task = new tasks.EmrModifyInstanceFleetByName(stack, 'Task', { + clusterId: 'ClusterId', + instanceFleetName: 'InstanceFleetName', + targetOnDemandCapacity: sfn.Data.numberAt('$.TargetOnDemandCapacity'), + targetSpotCapacity: sfn.Data.numberAt('$.TargetSpotCapacity'), }); // THEN diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-group-by-name.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-group-by-name.test.ts index ca0219f652d48..f9bc8886de0d3 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-group-by-name.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-group-by-name.test.ts @@ -11,29 +11,27 @@ beforeEach(() => { test('Modify an InstanceGroup with static ClusterId, InstanceGroupName, and InstanceGroup', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrModifyInstanceGroupByName({ - clusterId: 'ClusterId', - instanceGroupName: 'InstanceGroupName', - instanceGroup: { - configurations: [{ - classification: 'Classification', - properties: { - Key: 'Value', - }, - }], - eC2InstanceIdsToTerminate: ['InstanceToTerminate'], - instanceCount: 1, - shrinkPolicy: { - decommissionTimeout: cdk.Duration.seconds(1), - instanceResizePolicy: { - instanceTerminationTimeout: cdk.Duration.seconds(1), - instancesToProtect: ['InstanceToProtect'], - instancesToTerminate: ['InstanceToTerminate'], - }, + const task = new tasks.EmrModifyInstanceGroupByName(stack, 'Task', { + clusterId: 'ClusterId', + instanceGroupName: 'InstanceGroupName', + instanceGroup: { + configurations: [{ + classification: 'Classification', + properties: { + Key: 'Value', + }, + }], + eC2InstanceIdsToTerminate: ['InstanceToTerminate'], + instanceCount: 1, + shrinkPolicy: { + decommissionTimeout: cdk.Duration.seconds(1), + instanceResizePolicy: { + instanceTerminationTimeout: cdk.Duration.seconds(1), + instancesToProtect: ['InstanceToProtect'], + instancesToTerminate: ['InstanceToTerminate'], }, }, - }), + }, }); // THEN @@ -79,14 +77,12 @@ test('Modify an InstanceGroup with static ClusterId, InstanceGroupName, and Inst test('Modify an InstanceGroup with ClusterId from payload and static InstanceGroupName and InstanceGroupConfiguration', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrModifyInstanceGroupByName({ - clusterId: sfn.Data.stringAt('$.ClusterId'), - instanceGroupName: 'InstanceGroupName', - instanceGroup: { - instanceCount: 1, - }, - }), + const task = new tasks.EmrModifyInstanceGroupByName(stack, 'Task', { + clusterId: sfn.Data.stringAt('$.ClusterId'), + instanceGroupName: 'InstanceGroupName', + instanceGroup: { + instanceCount: 1, + }, }); // THEN @@ -117,14 +113,12 @@ test('Modify an InstanceGroup with ClusterId from payload and static InstanceGro test('Modify an InstanceGroup with static ClusterId and InstanceGroupConfigurateion and InstanceGroupName from payload', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrModifyInstanceGroupByName({ - clusterId: 'ClusterId', - instanceGroupName: sfn.Data.stringAt('$.InstanceGroupName'), - instanceGroup: { - instanceCount: 1, - }, - }), + const task = new tasks.EmrModifyInstanceGroupByName(stack, 'Task', { + clusterId: 'ClusterId', + instanceGroupName: sfn.Data.stringAt('$.InstanceGroupName'), + instanceGroup: { + instanceCount: 1, + }, }); // THEN @@ -155,14 +149,12 @@ test('Modify an InstanceGroup with static ClusterId and InstanceGroupConfigurate test('Modify an InstanceGroup with static ClusterId and InstanceGroupName and InstanceCount from payload', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrModifyInstanceGroupByName({ - clusterId: 'ClusterId', - instanceGroupName: 'InstanceGroupName', - instanceGroup: { - instanceCount: sfn.Data.numberAt('$.InstanceCount'), - }, - }), + const task = new tasks.EmrModifyInstanceGroupByName(stack, 'Task', { + clusterId: 'ClusterId', + instanceGroupName: 'InstanceGroupName', + instanceGroup: { + instanceCount: sfn.Data.numberAt('$.InstanceCount'), + }, }); // THEN diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-set-cluster-termination-protection.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-set-cluster-termination-protection.test.ts index 0581e6bdd8494..6b68837837b7b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-set-cluster-termination-protection.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-set-cluster-termination-protection.test.ts @@ -11,11 +11,9 @@ beforeEach(() => { test('Set termination protection with static ClusterId and TerminationProtected', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrSetClusterTerminationProtection({ - clusterId: 'ClusterId', - terminationProtected: false, - }), + const task = new tasks.EmrSetClusterTerminationProtection(stack, 'Task', { + clusterId: 'ClusterId', + terminationProtected: false, }); // THEN @@ -43,11 +41,9 @@ test('Set termination protection with static ClusterId and TerminationProtected' test('Set termination protection with static ClusterId and TerminationProtected from payload', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrSetClusterTerminationProtection({ - clusterId: 'ClusterId', - terminationProtected: sfn.TaskInput.fromDataAt('$.TerminationProtected').value, - }), + const task = new tasks.EmrSetClusterTerminationProtection(stack, 'Task', { + clusterId: 'ClusterId', + terminationProtected: sfn.TaskInput.fromDataAt('$.TerminationProtected').value, }); // THEN @@ -75,11 +71,9 @@ test('Set termination protection with static ClusterId and TerminationProtected test('Set termination protection with ClusterId from payload and static TerminationProtected', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrSetClusterTerminationProtection({ - clusterId: sfn.TaskInput.fromDataAt('$.ClusterId').value, - terminationProtected: false, - }), + const task = new tasks.EmrSetClusterTerminationProtection(stack, 'Task', { + clusterId: sfn.TaskInput.fromDataAt('$.ClusterId').value, + terminationProtected: false, }); // THEN diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-terminate-cluster.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-terminate-cluster.test.ts index aac9d83baf242..8df767949a7b5 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-terminate-cluster.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-terminate-cluster.test.ts @@ -11,11 +11,9 @@ beforeEach(() => { test('Terminate cluster with static ClusterId', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrTerminateCluster({ - clusterId: 'ClusterId', - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - }), + const task = new tasks.EmrTerminateCluster(stack, 'Task', { + clusterId: 'ClusterId', + integrationPattern: sfn.IntegrationPattern.RUN_JOB, }); // THEN @@ -42,11 +40,9 @@ test('Terminate cluster with static ClusterId', () => { test('Terminate cluster with ClusterId from payload', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrTerminateCluster({ - clusterId: sfn.TaskInput.fromDataAt('$.ClusterId').value, - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - }), + const task = new tasks.EmrTerminateCluster(stack, 'Task', { + clusterId: sfn.TaskInput.fromDataAt('$.ClusterId').value, + integrationPattern: sfn.IntegrationPattern.RUN_JOB, }); // THEN @@ -73,11 +69,9 @@ test('Terminate cluster with ClusterId from payload', () => { test('Terminate cluster with static ClusterId and SYNC integrationPattern', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EmrTerminateCluster({ - clusterId: 'ClusterId', - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }), + const task = new tasks.EmrTerminateCluster(stack, 'Task', { + clusterId: 'ClusterId', + integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE, }); // THEN @@ -104,11 +98,9 @@ test('Terminate cluster with static ClusterId and SYNC integrationPattern', () = test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.EmrTerminateCluster({ - clusterId: 'ClusterId', - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - }), + new tasks.EmrTerminateCluster(stack, 'Task', { + clusterId: 'ClusterId', + integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, }); - }).toThrow(/Invalid Service Integration Pattern: WAIT_FOR_TASK_TOKEN is not supported to call TerminateCluster./i); + }).toThrow(/Unsupported service integration pattern. Supported Patterns: REQUEST_RESPONSE,RUN_JOB. Received: WAIT_FOR_TASK_TOKEN/); }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.evaluate-expression.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.evaluate-expression.expected.json index 14b88ea6e252c..7c44d5f356e85 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.evaluate-expression.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.evaluate-expression.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters26ff0ffbfcb72e0fa136547addc06082857c59c734b1dd8c4294b58f03f1d26cS3Bucket0CC7D0FC" + "Ref": "AssetParameters640b7d3e1a6ff78c1cad25c2d7396d04c74d6eee31b116f4c86d910338d480d0S3BucketA16CB30E" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters26ff0ffbfcb72e0fa136547addc06082857c59c734b1dd8c4294b58f03f1d26cS3VersionKeyEDCE07B4" + "Ref": "AssetParameters640b7d3e1a6ff78c1cad25c2d7396d04c74d6eee31b116f4c86d910338d480d0S3VersionKey102DBBD9" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters26ff0ffbfcb72e0fa136547addc06082857c59c734b1dd8c4294b58f03f1d26cS3VersionKeyEDCE07B4" + "Ref": "AssetParameters640b7d3e1a6ff78c1cad25c2d7396d04c74d6eee31b116f4c86d910338d480d0S3VersionKey102DBBD9" } ] } @@ -185,17 +185,17 @@ } }, "Parameters": { - "AssetParameters26ff0ffbfcb72e0fa136547addc06082857c59c734b1dd8c4294b58f03f1d26cS3Bucket0CC7D0FC": { + "AssetParameters640b7d3e1a6ff78c1cad25c2d7396d04c74d6eee31b116f4c86d910338d480d0S3BucketA16CB30E": { "Type": "String", - "Description": "S3 bucket for asset \"26ff0ffbfcb72e0fa136547addc06082857c59c734b1dd8c4294b58f03f1d26c\"" + "Description": "S3 bucket for asset \"640b7d3e1a6ff78c1cad25c2d7396d04c74d6eee31b116f4c86d910338d480d0\"" }, - "AssetParameters26ff0ffbfcb72e0fa136547addc06082857c59c734b1dd8c4294b58f03f1d26cS3VersionKeyEDCE07B4": { + "AssetParameters640b7d3e1a6ff78c1cad25c2d7396d04c74d6eee31b116f4c86d910338d480d0S3VersionKey102DBBD9": { "Type": "String", - "Description": "S3 key for asset version \"26ff0ffbfcb72e0fa136547addc06082857c59c734b1dd8c4294b58f03f1d26c\"" + "Description": "S3 key for asset version \"640b7d3e1a6ff78c1cad25c2d7396d04c74d6eee31b116f4c86d910338d480d0\"" }, - "AssetParameters26ff0ffbfcb72e0fa136547addc06082857c59c734b1dd8c4294b58f03f1d26cArtifactHash52172C25": { + "AssetParameters640b7d3e1a6ff78c1cad25c2d7396d04c74d6eee31b116f4c86d910338d480d0ArtifactHash43D553D7": { "Type": "String", - "Description": "Artifact hash for asset \"26ff0ffbfcb72e0fa136547addc06082857c59c734b1dd8c4294b58f03f1d26c\"" + "Description": "Artifact hash for asset \"640b7d3e1a6ff78c1cad25c2d7396d04c74d6eee31b116f4c86d910338d480d0\"" } }, "Outputs": { @@ -205,4 +205,4 @@ } } } -} \ No newline at end of file +} 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-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index b7560ad80883e..0cb349358ba0f 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -508,6 +508,23 @@ new stepfunctions.StateMachine(stack, 'MyStateMachine', { }); ``` +## Import + +Any Step Functions state machine that has been created outside the stack can be imported +into your CDK stack. + +State machines can be imported by their ARN via the `StateMachine.fromStateMachineArn()` API + +```ts +import * as sfn from 'aws-stepfunctions'; + +const stack = new Stack(app, 'MyStack'); +sfn.StateMachine.fromStateMachineArn( + stack, + 'ImportedStateMachine', + 'arn:aws:states:us-east-1:123456789012:stateMachine:StateMachine2E01A3A5-N5TJppzoevKQ'); +``` + ## Future work Contributions welcome: 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..4023f2b214069 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 @@ -136,16 +136,16 @@ All items unchecked below are currently not supported. - [x] Fn::GetAtt - [x] Fn::Join - [x] Fn::If -- [ ] Fn::And +- [x] Fn::And - [x] Fn::Equals -- [ ] Fn::Not -- [ ] Fn::Or -- [ ] Fn::Base64 -- [ ] Fn::Cidr -- [ ] Fn::FindInMap -- [ ] Fn::GetAZs -- [ ] Fn::ImportValue -- [ ] Fn::Select -- [ ] Fn::Split +- [x] Fn::Not +- [x] Fn::Or +- [x] Fn::Base64 +- [x] Fn::Cidr +- [x] Fn::FindInMap +- [x] Fn::GetAZs +- [x] Fn::ImportValue +- [x] Fn::Select +- [x] Fn::Split - [ ] Fn::Sub -- [ ] Fn::Transform +- [x] Fn::Transform 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..f6e1c2100365a 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,18 @@ describe('CDK Include', () => { includeTestTemplate(stack, 'non-existent-condition.json'); }).toThrow(/Resource 'Bucket' uses Condition 'AlwaysFalseCond' that doesn't exist/); }); + + test("throws an exception when encountering a CFN function it doesn't support", () => { + expect(() => { + includeTestTemplate(stack, 'only-codecommit-repo-using-cfn-functions.json'); + }).toThrow(/Unsupported CloudFormation function 'Fn::DoesNotExist'/); + }); + + 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/functions-and-conditions.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json new file mode 100644 index 0000000000000..ce57b3e1a8b9f --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json @@ -0,0 +1,160 @@ +{ + "Mappings": { + "RegionMap": { + "region-1": { + "HVM64": "name1", + "HVMG2": "name2" + } + } + }, + "Conditions": { + "AlwaysTrueCond": { + "Fn::Not": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "completely-made-up-region" + ] + } + ] + }, + "AndCond": { + "Fn::And": [ + { + "Condition": "AlwaysTrueCond" + }, + { + "Fn::Or": [ + { + "Condition": "AlwaysTrueCond" + }, + { + "Condition": "AlwaysTrueCond" + } + ] + } + ] + } + }, + "Resources": { + "Vpc": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": { + "Fn::If": [ + "AlwaysTrueCond", + { + "Fn::Cidr": [ + "192.168.1.1/24", + 2, + 5 + ] + }, + { + "Fn::Cidr": [ + "10.0.0.0/24", + "6", + "5" + ] + } + ] + } + } + }, + "Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::If": [ + "AndCond", + { + "Fn::FindInMap": [ + "RegionMap", + "region-1", + "HVM64" + ] + }, + "Unreachable" + ] + } + } + }, + "Subnet1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Fn::If": [ + "AlwaysTrueCond", + { + "Fn::Split": [ + ",", + { + "Fn::ImportValue": "ImportedVpcId" + } + ] + }, + "Unreachable" + ] + }, + "CidrBlock": "10.0.0.0/24", + "AvailabilityZone": { + "Fn::Select": [ + "0", + { + "Fn::GetAZs": "" + } + ] + } + } + }, + "Subnet2": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Fn::Select": [ + 0, + { + "Fn::Cidr": [ + "10.0.0.0/24", + 5, + 2 + ] + } + ] + }, + "CidrBlock": "10.0.0.0/24", + "AvailabilityZone": { + "Fn::Select": [ + "0", + { + "Fn::GetAZs": "eu-west-2" + } + ] + } + } + }, + "TransformBucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::If": [ + "AndCond", + { + "Fn::Transform": { + "Name": "AWS::Include", + "Parameters": { + "Location": "location", + "AnotherParameter": { + "Fn:Base64": "AnotherValue" + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file 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/only-codecommit-repo-using-cfn-functions.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/only-codecommit-repo-using-cfn-functions.json similarity index 77% rename from packages/@aws-cdk/cloudformation-include/test/test-templates/only-codecommit-repo-using-cfn-functions.json rename to packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/only-codecommit-repo-using-cfn-functions.json index a1037dbf2402a..10e907c0ea753 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/only-codecommit-repo-using-cfn-functions.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/only-codecommit-repo-using-cfn-functions.json @@ -5,7 +5,7 @@ "Properties": { "RepositoryName": "my-repository", "RepositoryDescription": { - "Fn::Base64": "my description, in base-64!" + "Fn::DoesNotExist": "my description, in base-64!" } } } 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..1855c46acab5a 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -184,6 +184,14 @@ describe('CDK Include', () => { ); }); + test('can ingest a template with intrinsic functions and conditions, and output it unchanged', () => { + includeTestTemplate(stack, 'functions-and-conditions.json'); + + expect(stack).toMatchTemplate( + loadTestFileToJsObject('functions-and-conditions.json'), + ); + }); + test('can ingest a template with a Ref expression for an array value, and output it unchanged', () => { includeTestTemplate(stack, 'ref-array-property.json'); @@ -264,28 +272,32 @@ describe('CDK Include', () => { }); }); - test("throws an exception when encountering a Resource type it doesn't recognize", () => { - expect(() => { - includeTestTemplate(stack, 'non-existent-resource-type.json'); - }).toThrow(/Unrecognized CloudFormation resource type: 'AWS::FakeService::DoesNotExist'/); - }); + test('correctly handles the CreationPolicy resource attribute', () => { + const cfnTemplate = includeTestTemplate(stack, 'resource-attribute-creation-policy.json'); + const cfnBucket = cfnTemplate.getResource('Bucket'); - test("throws an exception when encountering a CFN function it doesn't support", () => { - expect(() => { - includeTestTemplate(stack, 'only-codecommit-repo-using-cfn-functions.json'); - }).toThrow(/Unsupported CloudFormation function 'Fn::Base64'/); + expect(cfnBucket.cfnOptions.creationPolicy).toBeDefined(); + + expect(stack).toMatchTemplate( + loadTestFileToJsObject('resource-attribute-creation-policy.json'), + ); }); - 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('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 the UpdatePolicy attribute in a resource', () => { + test("throws an exception when encountering a Resource type it doesn't recognize", () => { expect(() => { - includeTestTemplate(stack, 'resource-attribute-update-policy.json'); - }).toThrow(/The UpdatePolicy resource attribute is not supported by cloudformation-include yet/); + includeTestTemplate(stack, 'non-existent-resource-type.json'); + }).toThrow(/Unrecognized CloudFormation resource type: 'AWS::FakeService::DoesNotExist'/); }); }); 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 c37d8d441d7c0..e25994beeb821 100644 --- a/packages/@aws-cdk/core/lib/asset-staging.ts +++ b/packages/@aws-cdk/core/lib/asset-staging.ts @@ -1,12 +1,15 @@ 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'; -import { BUNDLING_INPUT_DIR, BUNDLING_OUTPUT_DIR, BundlingOptions } from './bundling'; +import { BundlingOptions } from './bundling'; import { Construct, ISynthesisSession } from './construct-compat'; import { FileSystem, FingerprintOptions } from './fs'; +const STAGING_TMP = '.cdk.staging'; + /** * Initialization properties for `AssetStaging`. */ @@ -36,6 +39,18 @@ export interface AssetStagingProps extends FingerprintOptions, AssetOptions { * means that only if content was changed, copy will happen. */ export class AssetStaging extends Construct { + /** + * The directory inside the bundling container into which the asset sources will be mounted. + * @experimental + */ + public static readonly BUNDLING_INPUT_DIR = '/asset-input'; + + /** + * The directory inside the bundling container into which the bundled output should be written. + * @experimental + */ + public static readonly BUNDLING_OUTPUT_DIR = '/asset-output'; + /** * The path to the asset (stringinfied token). * @@ -105,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 @@ -136,18 +138,32 @@ export class AssetStaging extends Construct { } private bundle(options: BundlingOptions): string { - // Create temporary directory for bundling - const bundleDir = fs.mkdtempSync(path.resolve(path.join(os.tmpdir(), '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) { + user = options.user; + } else { // Default to current user + const userInfo = os.userInfo(); + user = userInfo.uid !== -1 // uid is -1 on Windows + ? `${userInfo.uid}:${userInfo.gid}` + : '1000:1000'; + } // Always mount input and output dir const volumes = [ { hostPath: this.sourcePath, - containerPath: BUNDLING_INPUT_DIR, + containerPath: AssetStaging.BUNDLING_INPUT_DIR, }, { hostPath: bundleDir, - containerPath: BUNDLING_OUTPUT_DIR, + containerPath: AssetStaging.BUNDLING_OUTPUT_DIR, }, ...options.volumes ?? [], ]; @@ -155,16 +171,17 @@ export class AssetStaging extends Construct { try { options.image._run({ command: options.command, + user, volumes, environment: options.environment, - workingDirectory: options.workingDirectory ?? BUNDLING_INPUT_DIR, + workingDirectory: options.workingDirectory ?? AssetStaging.BUNDLING_INPUT_DIR, }); } catch (err) { throw new Error(`Failed to run bundling Docker image for asset ${this.node.path}: ${err}`); } if (FileSystem.isEmpty(bundleDir)) { - throw new Error(`Bundling did not produce any output. Check that your container writes content to ${BUNDLING_OUTPUT_DIR}.`); + throw new Error(`Bundling did not produce any output. Check that your container writes content to ${AssetStaging.BUNDLING_OUTPUT_DIR}.`); } return bundleDir; @@ -196,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/bundling.ts b/packages/@aws-cdk/core/lib/bundling.ts index bfff68b40f5cd..1034517534f10 100644 --- a/packages/@aws-cdk/core/lib/bundling.ts +++ b/packages/@aws-cdk/core/lib/bundling.ts @@ -1,8 +1,5 @@ import { spawnSync } from 'child_process'; -export const BUNDLING_INPUT_DIR = '/asset-input'; -export const BUNDLING_OUTPUT_DIR = '/asset-output'; - /** * Bundling options * @@ -45,6 +42,17 @@ export interface BundlingOptions { * @default /asset-input */ readonly workingDirectory?: string; + + /** + * The user to use when running the container. + * + * user | user:group | uid | uid:gid | user:gid | uid:group + * + * @see https://docs.docker.com/engine/reference/run/#user + * + * @default - uid:gid of the current user or 1000:1000 on Windows + */ + readonly user?: string; } /** @@ -75,7 +83,7 @@ export class BundlingDockerImage { path, ]; - const docker = exec('docker', dockerArgs); + const docker = dockerExec(dockerArgs); const match = docker.stdout.toString().match(/Successfully built ([a-z0-9]+)/); @@ -101,6 +109,9 @@ export class BundlingDockerImage { const dockerArgs: string[] = [ 'run', '--rm', + ...options.user + ? ['-u', options.user] + : [], ...flatten(volumes.map(v => ['-v', `${v.hostPath}:${v.containerPath}`])), ...flatten(Object.entries(environment).map(([k, v]) => ['--env', `${k}=${v}`])), ...options.workingDirectory @@ -110,7 +121,7 @@ export class BundlingDockerImage { ...command, ]; - exec('docker', dockerArgs); + dockerExec(dockerArgs); } } @@ -160,6 +171,13 @@ interface DockerRunOptions { * @default - image default */ readonly workingDirectory?: string; + + /** + * The user to use when running the container. + * + * @default - root or image default + */ + readonly user?: string; } /** @@ -178,8 +196,9 @@ function flatten(x: string[][]) { return Array.prototype.concat([], ...x); } -function exec(cmd: string, args: string[]) { - const proc = spawnSync(cmd, args); +function dockerExec(args: string[]) { + const prog = process.env.CDK_DOCKER ?? 'docker'; + const proc = spawnSync(prog, args); if (proc.error) { throw proc.error; diff --git a/packages/@aws-cdk/core/lib/cfn-fn.ts b/packages/@aws-cdk/core/lib/cfn-fn.ts index 30fb6cf435bec..73d0f61c03236 100644 --- a/packages/@aws-cdk/core/lib/cfn-fn.ts +++ b/packages/@aws-cdk/core/lib/cfn-fn.ts @@ -172,6 +172,17 @@ export class Fn { return new FnFindInMap(mapName, topLevelKey, secondLevelKey).toString(); } + /** + * Creates a token representing the ``Fn::Transform`` expression + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-transform.html + * @param macroName The name of the macro to perform the processing + * @param parameters The parameters to be passed to the macro + * @returns a token representing the transform expression + */ + public static transform(macroName: string, parameters: { [name: string]: any }): IResolvable { + return new FnTransform(macroName, parameters); + } + /** * Returns true if all the specified conditions evaluate to true, or returns * false if any one of the conditions evaluates to false. ``Fn::And`` acts as @@ -356,6 +367,20 @@ class FnFindInMap extends FnBase { } } +/** + * The intrinsic function ``Fn::Transform`` specifies a macro to perform custom processing on part of a stack template. + */ +class FnTransform extends FnBase { + /** + * creates an ``Fn::Transform`` function. + * @param macroName The name of the macro to be invoked + * @param parameters the parameters to pass to it + */ + constructor(macroName: string, parameters: { [name: string]: any }) { + super('Fn::Transform', { Name: macroName, Parameters: parameters }); + } +} + /** * The ``Fn::GetAtt`` intrinsic function returns the value of an attribute from a resource in the template. */ diff --git a/packages/@aws-cdk/core/lib/cfn-parse.ts b/packages/@aws-cdk/core/lib/cfn-parse.ts index 67e2d2390ef62..e0844da24d22b 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; @@ -179,6 +267,38 @@ function parseIfCfnIntrinsic(object: any): any { const value = parseCfnValueToCdkValue(object[key]); return Fn.join(value[0], value[1]); } + case 'Fn::Cidr': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.cidr(value[0], value[1], value[2]); + } + case 'Fn::FindInMap': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.findInMap(value[0], value[1], value[2]); + } + case 'Fn::Select': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.select(value[0], value[1]); + } + case 'Fn::GetAZs': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.getAzs(value); + } + case 'Fn::ImportValue': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.importValue(value); + } + case 'Fn::Split': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.split(value[0], value[1]); + } + case 'Fn::Transform': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.transform(value.Name, value.Parameters); + } + case 'Fn::Base64': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.base64(value); + } case 'Fn::If': { // Fn::If takes a 3-element list as its argument // ToDo the first argument is the name of the condition, @@ -191,6 +311,18 @@ function parseIfCfnIntrinsic(object: any): any { const value = parseCfnValueToCdkValue(object[key]); return Fn.conditionEquals(value[0], value[1]); } + case 'Fn::And': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.conditionAnd(...value); + } + case 'Fn::Not': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.conditionNot(value[0]); + } + case 'Fn::Or': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.conditionOr(...value); + } default: throw new Error(`Unsupported CloudFormation function '${key}'`); } @@ -220,3 +352,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/construct-compat.ts b/packages/@aws-cdk/core/lib/construct-compat.ts index 78e57266fe768..e689d3a837619 100644 --- a/packages/@aws-cdk/core/lib/construct-compat.ts +++ b/packages/@aws-cdk/core/lib/construct-compat.ts @@ -79,9 +79,15 @@ export class Construct extends constructs.Construct implements IConstruct { Object.defineProperty(this, CONSTRUCT_SYMBOL, { value: true }); this.node = ConstructNode._unwrap(constructs.Node.of(this)); - const disableTrace = this.node.tryGetContext(cxapi.DISABLE_METADATA_STACK_TRACE); + const disableTrace = + this.node.tryGetContext(cxapi.DISABLE_METADATA_STACK_TRACE) || + this.node.tryGetContext(constructs.ConstructMetadata.DISABLE_STACK_TRACE_IN_METADATA) || + process.env.CDK_DISABLE_STACK_TRACE; + if (disableTrace) { + this.node.setContext(cxapi.DISABLE_METADATA_STACK_TRACE, true); this.node.setContext(constructs.ConstructMetadata.DISABLE_STACK_TRACE_IN_METADATA, true); + process.env.CDK_DISABLE_STACK_TRACE = '1'; } } diff --git a/packages/@aws-cdk/core/lib/fs/index.ts b/packages/@aws-cdk/core/lib/fs/index.ts index 01c6d132956e2..4ecfea7c2471c 100644 --- a/packages/@aws-cdk/core/lib/fs/index.ts +++ b/packages/@aws-cdk/core/lib/fs/index.ts @@ -1,4 +1,6 @@ import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; import { copyDirectory } from './copy'; import { fingerprint } from './fingerprint'; import { CopyOptions, FingerprintOptions } from './options'; @@ -43,4 +45,27 @@ export class FileSystem { public static isEmpty(dir: string): boolean { return fs.readdirSync(dir).length === 0; } + + /** + * The real path of the system temp directory + */ + public static get tmpdir(): string { + if (FileSystem._tmpdir) { + return FileSystem._tmpdir; + } + FileSystem._tmpdir = fs.realpathSync(os.tmpdir()); + return FileSystem._tmpdir; + } + + /** + * Creates a unique temporary directory in the **system temp directory**. + * + * @param prefix A prefix for the directory name. Six random characters + * will be generated and appended behind this prefix. + */ + public static mkdtemp(prefix: string): string { + return fs.mkdtempSync(path.join(FileSystem.tmpdir, prefix)); + } + + private static _tmpdir?: string; } diff --git a/packages/@aws-cdk/core/lib/private/runtime-info.ts b/packages/@aws-cdk/core/lib/private/runtime-info.ts index e18fabc5ecaa1..dce0ac508d71f 100644 --- a/packages/@aws-cdk/core/lib/private/runtime-info.ts +++ b/packages/@aws-cdk/core/lib/private/runtime-info.ts @@ -3,7 +3,7 @@ import { basename, dirname } from 'path'; import { major as nodeMajorVersion } from './node-version'; // list of NPM scopes included in version reporting e.g. @aws-cdk and @aws-solutions-konstruk -const WHITELIST_SCOPES = ['@aws-cdk', '@aws-solutions-konstruk']; +const WHITELIST_SCOPES = ['@aws-cdk', '@aws-solutions-konstruk', '@aws-solutions-constructs']; /** * Returns a list of loaded modules and their versions. 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/lib/stack-trace.ts b/packages/@aws-cdk/core/lib/stack-trace.ts index 0901666309875..47caefa22f4a8 100644 --- a/packages/@aws-cdk/core/lib/stack-trace.ts +++ b/packages/@aws-cdk/core/lib/stack-trace.ts @@ -1,5 +1,9 @@ // tslint:disable-next-line:ban-types export function captureStackTrace(below?: Function): string[] { + if (process.env.CDK_DISABLE_STACK_TRACE) { + return [ 'stack traces disabled' ]; + } + below = below || captureStackTrace; // hide myself if nothing else const object = { stack: '' }; const previousLimit = Error.stackTraceLimit; 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/custom-resource-provider/test.custom-resource-provider.ts b/packages/@aws-cdk/core/test/custom-resource-provider/test.custom-resource-provider.ts index 99c8fb12d4e70..47bdbce65e852 100644 --- a/packages/@aws-cdk/core/test/custom-resource-provider/test.custom-resource-provider.ts +++ b/packages/@aws-cdk/core/test/custom-resource-provider/test.custom-resource-provider.ts @@ -49,7 +49,7 @@ export = { Properties: { Code: { S3Bucket: { - Ref: 'AssetParametersd46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7aS3Bucket1D703CB8', + Ref: 'AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226S3Bucket8B4D0E9E', }, S3Key: { 'Fn::Join': [ @@ -62,7 +62,7 @@ export = { 'Fn::Split': [ '||', { - Ref: 'AssetParametersd46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7aS3VersionKey01A97AE3', + Ref: 'AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226S3VersionKeyDECB34FE', }, ], }, @@ -75,7 +75,7 @@ export = { 'Fn::Split': [ '||', { - Ref: 'AssetParametersd46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7aS3VersionKey01A97AE3', + Ref: 'AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226S3VersionKeyDECB34FE', }, ], }, @@ -102,17 +102,17 @@ export = { }, }, Parameters: { - AssetParametersd46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7aS3Bucket1D703CB8: { + AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226S3Bucket8B4D0E9E: { Type: 'String', - Description: 'S3 bucket for asset "d46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7a"', + Description: 'S3 bucket for asset "925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226"', }, - AssetParametersd46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7aS3VersionKey01A97AE3: { + AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226S3VersionKeyDECB34FE: { Type: 'String', - Description: 'S3 key for asset version "d46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7a"', + Description: 'S3 key for asset version "925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226"', }, - AssetParametersd46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7aArtifactHash16A571C9: { + AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226ArtifactHashEEC400F2: { Type: 'String', - Description: 'Artifact hash for asset "d46d1ebe2c1958c6352664721f77acb9c78131013956eb82d3d36cf503098e7a"', + Description: 'Artifact hash for asset "925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226"', }, }, }); diff --git a/packages/@aws-cdk/core/test/docker-stub.sh b/packages/@aws-cdk/core/test/docker-stub.sh new file mode 100755 index 0000000000000..45a78ef881ebd --- /dev/null +++ b/packages/@aws-cdk/core/test/docker-stub.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -euo pipefail + +# stub for the `docker` executable. it is used as CDK_DOCKER when executing unit +# tests in `test.staging.ts` It outputs the command line to +# `/tmp/docker-stub.input` and accepts one of 3 commands that impact it's +# behavior. + +echo "$@" > /tmp/docker-stub.input + +if echo "$@" | grep "DOCKER_STUB_SUCCESS_NO_OUTPUT"; then + exit 0 +fi + +if echo "$@" | grep "DOCKER_STUB_FAIL"; then + echo "A HUGE FAILING DOCKER STUFF" + exit 1 +fi + +if echo "$@" | grep "DOCKER_STUB_SUCCESS"; then + outdir=$(echo "$@" | xargs -n1 | grep "/asset-output" | head -n1 | cut -d":" -f1) + touch ${outdir}/test.txt + exit 0 +fi + +echo "Docker mock only supports one of the following commands: DOCKER_STUB_SUCCESS_NO_OUTPUT,DOCKER_STUB_FAIL,DOCKER_STUB_SUCCESS" +exit 1 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/fs/test.fs.ts b/packages/@aws-cdk/core/test/fs/test.fs.ts new file mode 100644 index 0000000000000..cc6d4898c922e --- /dev/null +++ b/packages/@aws-cdk/core/test/fs/test.fs.ts @@ -0,0 +1,48 @@ +import * as fs from 'fs'; +import { Test } from 'nodeunit'; +import * as os from 'os'; +import * as path from 'path'; +import * as sinon from 'sinon'; +import { FileSystem } from '../../lib/fs'; + +export = { + 'tearDown'(callback: any) { + sinon.restore(); + callback(); + }, + + 'tmpdir returns a real path and is cached'(test: Test) { + // Create symlink that points to /tmp + const symlinkTmp = path.join(__dirname, 'tmp-link'); + fs.symlinkSync(os.tmpdir(), symlinkTmp); + + // Now stub os.tmpdir() to return this link instead of /tmp + const tmpdirStub = sinon.stub(os, 'tmpdir').returns(symlinkTmp); + + test.ok(path.isAbsolute(FileSystem.tmpdir)); + + const p = path.join(FileSystem.tmpdir, 'tmpdir-test.txt'); + fs.writeFileSync(p, 'tmpdir-test'); + + test.equal(p, fs.realpathSync(p)); + test.equal(fs.readFileSync(p, 'utf8'), 'tmpdir-test'); + + test.ok(tmpdirStub.calledOnce); // cached result + + fs.unlinkSync(p); + fs.unlinkSync(symlinkTmp); + + test.done(); + }, + + 'mkdtemp creates a temporary directory in the system temp'(test: Test) { + const tmpdir = FileSystem.mkdtemp('cdk-mkdtemp-'); + + test.equal(path.dirname(tmpdir), FileSystem.tmpdir); + test.ok(fs.existsSync(tmpdir)); + + fs.rmdirSync(tmpdir); + + test.done(); + }, +}; 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.bundling.ts b/packages/@aws-cdk/core/test/test.bundling.ts index 658aa99901bb6..2ba23a83ffce9 100644 --- a/packages/@aws-cdk/core/test/test.bundling.ts +++ b/packages/@aws-cdk/core/test/test.bundling.ts @@ -28,10 +28,12 @@ export = { }, volumes: [{ hostPath: '/host-path', containerPath: '/container-path' }], workingDirectory: '/working-directory', + user: 'user:group', }); test.ok(spawnSyncStub.calledWith('docker', [ 'run', '--rm', + '-u', 'user:group', '-v', '/host-path:/container-path', '--env', 'VAR1=value1', '--env', 'VAR2=value2', 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.construct.ts b/packages/@aws-cdk/core/test/test.construct.ts index 4850c5d0493ad..56ef04e1d1d5e 100644 --- a/packages/@aws-cdk/core/test/test.construct.ts +++ b/packages/@aws-cdk/core/test/test.construct.ts @@ -1,6 +1,7 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { Test } from 'nodeunit'; import { App as Root, Aws, Construct, ConstructNode, ConstructOrder, IConstruct, Lazy, ValidationError } from '../lib'; +import { reEnableStackTraceCollection, restoreStackTraceColection } from './util'; // tslint:disable:variable-name @@ -246,6 +247,7 @@ export = { }, 'addMetadata(type, data) can be used to attach metadata to constructs FIND_ME'(test: Test) { + const previousValue = reEnableStackTraceCollection(); const root = new Root(); const con = new Construct(root, 'MyConstruct'); test.deepEqual(con.node.metadata, [], 'starts empty'); @@ -253,6 +255,7 @@ export = { con.node.addMetadata('key', 'value'); con.node.addMetadata('number', 103); con.node.addMetadata('array', [ 123, 456 ]); + restoreStackTraceColection(previousValue); test.deepEqual(con.node.metadata[0].type, 'key'); test.deepEqual(con.node.metadata[0].data, 'value'); @@ -282,9 +285,12 @@ export = { }, 'addWarning(message) can be used to add a "WARNING" message entry to the construct'(test: Test) { + const previousValue = reEnableStackTraceCollection(); const root = new Root(); const con = new Construct(root, 'MyConstruct'); con.node.addWarning('This construct is deprecated, use the other one instead'); + restoreStackTraceColection(previousValue); + test.deepEqual(con.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); test.deepEqual(con.node.metadata[0].data, 'This construct is deprecated, use the other one instead'); test.ok(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0); @@ -292,9 +298,12 @@ export = { }, 'addError(message) can be used to add a "ERROR" message entry to the construct'(test: Test) { + const previousValue = reEnableStackTraceCollection(); const root = new Root(); const con = new Construct(root, 'MyConstruct'); con.node.addError('Stop!'); + restoreStackTraceColection(previousValue); + test.deepEqual(con.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.ERROR); test.deepEqual(con.node.metadata[0].data, 'Stop!'); test.ok(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0); @@ -302,9 +311,12 @@ export = { }, 'addInfo(message) can be used to add an "INFO" message entry to the construct'(test: Test) { + const previousValue = reEnableStackTraceCollection(); const root = new Root(); const con = new Construct(root, 'MyConstruct'); con.node.addInfo('Hey there, how do you do?'); + restoreStackTraceColection(previousValue); + test.deepEqual(con.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.INFO); test.deepEqual(con.node.metadata[0].data, 'Hey there, how do you do?'); test.ok(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0); diff --git a/packages/@aws-cdk/core/test/test.staging.ts b/packages/@aws-cdk/core/test/test.staging.ts index 5d5ab521eba59..81766f8a34879 100644 --- a/packages/@aws-cdk/core/test/test.staging.ts +++ b/packages/@aws-cdk/core/test/test.staging.ts @@ -1,10 +1,35 @@ 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'; + +enum DockerStubCommand { + SUCCESS = 'DOCKER_STUB_SUCCESS', + FAIL = 'DOCKER_STUB_FAIL', + SUCCESS_NO_OUTPUT = 'DOCKER_STUB_SUCCESS_NO_OUTPUT' +} + +const userInfo = os.userInfo(); +const USER_ARG = `-u ${userInfo.uid}:${userInfo.gid}`; + +// this is a way to provide a custom "docker" command for staging. +process.env.CDK_DOCKER = `${__dirname}/docker-stub.sh`; + export = { + + 'tearDown'(cb: any) { + if (fs.existsSync(STUB_INPUT_FILE)) { + fs.unlinkSync(STUB_INPUT_FILE); + } + cb(); + sinon.restore(); + }, + 'base case'(test: Test) { // GIVEN const stack = new Stack(); @@ -80,18 +105,24 @@ 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', { sourcePath: directory, bundling: { image: BundlingDockerImage.fromRegistry('alpine'), - command: ['touch', '/asset-output/test.txt'], + command: [ DockerStubCommand.SUCCESS ], }, }); // THEN const assembly = app.synth(); + test.deepEqual( + readDockerStubInput(), + `run --rm ${USER_ARG} -v /input:/asset-input -v /output:/asset-output -w /asset-input alpine DOCKER_STUB_SUCCESS`, + ); test.deepEqual(fs.readdirSync(assembly.directory), [ 'asset.2f37f937c51e2c191af66acf9b09f548926008ec68c575bd2ee54b6e997c0e00', 'cdk.out', @@ -100,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(); }, @@ -114,9 +150,14 @@ export = { sourcePath: directory, bundling: { image: BundlingDockerImage.fromRegistry('alpine'), + command: [ DockerStubCommand.SUCCESS_NO_OUTPUT ], }, }), /Bundling did not produce any output/); + test.equal( + readDockerStubInput(), + `run --rm ${USER_ARG} -v /input:/asset-input -v /output:/asset-output -w /asset-input alpine DOCKER_STUB_SUCCESS_NO_OUTPUT`, + ); test.done(); }, @@ -131,11 +172,16 @@ export = { sourcePath: directory, bundling: { image: BundlingDockerImage.fromRegistry('alpine'), - command: ['touch', '/asset-output/test.txt'], + command: [ DockerStubCommand.SUCCESS ], }, assetHashType: AssetHashType.BUNDLE, }); + // THEN + test.equal( + readDockerStubInput(), + `run --rm ${USER_ARG} -v /input:/asset-input -v /output:/asset-output -w /asset-input alpine DOCKER_STUB_SUCCESS`, + ); test.equal(asset.assetHash, '33cbf2cae5432438e0f046bc45ba8c3cef7b6afcf47b59d1c183775c1918fb1f'); test.done(); @@ -153,7 +199,9 @@ export = { assetHash: 'my-custom-hash', }); - test.equal(asset.assetHash, 'my-custom-hash'); + // THEN + test.equal(fs.existsSync(STUB_INPUT_FILE), false); + test.equal(asset.assetHash, 'b9c77053f5b83bbe5ba343bc18e92db939a49017010813225fea91fa892c4823'); // hash of 'my-custom-hash' test.done(); }, @@ -169,11 +217,15 @@ export = { sourcePath: directory, bundling: { image: BundlingDockerImage.fromRegistry('alpine'), - command: ['touch', '/asset-output/test.txt'], + command: [ DockerStubCommand.SUCCESS ], }, assetHash: 'my-custom-hash', assetHashType: AssetHashType.BUNDLE, }), /Cannot specify `bundle` for `assetHashType`/); + test.equal( + readDockerStubInput(), + `run --rm ${USER_ARG} -v /input:/asset-input -v /output:/asset-output -w /asset-input alpine DOCKER_STUB_SUCCESS`, + ); test.done(); }, @@ -189,6 +241,7 @@ export = { sourcePath: directory, assetHashType: AssetHashType.BUNDLE, }), /Cannot use `AssetHashType.BUNDLE` when `bundling` is not specified/); + test.equal(fs.existsSync(STUB_INPUT_FILE), false); test.done(); }, @@ -204,6 +257,7 @@ export = { sourcePath: directory, assetHashType: AssetHashType.CUSTOM, }), /`assetHash` must be specified when `assetHashType` is set to `AssetHashType.CUSTOM`/); + test.equal(fs.existsSync(STUB_INPUT_FILE), false); // "docker" not executed test.done(); }, @@ -219,9 +273,21 @@ export = { sourcePath: directory, bundling: { image: BundlingDockerImage.fromRegistry('this-is-an-invalid-docker-image'), + command: [ DockerStubCommand.FAIL ], }, }), /Failed to run bundling Docker image for asset stack\/Asset/); + test.equal( + readDockerStubInput(), + `run --rm ${USER_ARG} -v /input:/asset-input -v /output:/asset-output -w /asset-input this-is-an-invalid-docker-image DOCKER_STUB_FAIL`, + ); test.done(); }, }; + +function readDockerStubInput() { + const out = fs.readFileSync(STUB_INPUT_FILE, 'utf-8').trim(); + return out + .replace(/-v ([^:]+):\/asset-input/, '-v /input:/asset-input') + .replace(/-v ([^:]+):\/asset-output/, '-v /output:/asset-output'); +} diff --git a/packages/@aws-cdk/core/test/test.tokens.ts b/packages/@aws-cdk/core/test/test.tokens.ts index c8e7751c7b750..3dfe56ff07c8d 100644 --- a/packages/@aws-cdk/core/test/test.tokens.ts +++ b/packages/@aws-cdk/core/test/test.tokens.ts @@ -5,6 +5,7 @@ import { Intrinsic } from '../lib/private/intrinsic'; import { findTokens } from '../lib/private/resolve'; import { IResolvable } from '../lib/resolvable'; import { evaluateCFN } from './evaluate-cfn'; +import { reEnableStackTraceCollection, restoreStackTraceColection } from './util'; export = { 'resolve a plain old object should just return the object'(test: Test) { @@ -487,7 +488,9 @@ export = { return fn2(); } + const previousValue = reEnableStackTraceCollection(); const token = fn1(); + restoreStackTraceColection(previousValue); test.ok(token.creationTrace.find(x => x.includes('fn1'))); test.ok(token.creationTrace.find(x => x.includes('fn2'))); test.done(); @@ -509,7 +512,10 @@ export = { } return fn2(); } + + const previousValue = reEnableStackTraceCollection(); const token = fn1(); + restoreStackTraceColection(previousValue); test.throws(() => token.throwError('message!'), /Token created:/); test.done(); }, @@ -588,12 +594,15 @@ export = { return Lazy.stringValue({ produce: () => { throw new Error('fooError'); } }); } + const previousValue = reEnableStackTraceCollection(); const x = showMeInTheStackTrace(); let message; try { resolve(x); } catch (e) { message = e.message; + } finally { + restoreStackTraceColection(previousValue); } test.ok(message && message.includes('showMeInTheStackTrace')); diff --git a/packages/@aws-cdk/core/test/util.ts b/packages/@aws-cdk/core/test/util.ts index d029d31e495a7..b10c547de6f65 100644 --- a/packages/@aws-cdk/core/test/util.ts +++ b/packages/@aws-cdk/core/test/util.ts @@ -3,3 +3,13 @@ import { ConstructNode, Stack } from '../lib'; export function toCloudFormation(stack: Stack): any { return ConstructNode.synth(stack.node, { skipValidation: true }).getStackByName(stack.stackName).template; } + +export function reEnableStackTraceCollection(): any { + const previousValue = process.env.CDK_DISABLE_STACK_TRACE; + process.env.CDK_DISABLE_STACK_TRACE = ''; + return previousValue; +} + +export function restoreStackTraceColection(previousValue: any): void { + process.env.CDK_DISABLE_STACK_TRACE = previousValue; +} 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/lib/provider-framework/runtime/outbound.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/outbound.ts index 9b15ec01864f7..682632fd1a40a 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/outbound.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/outbound.ts @@ -1,8 +1,19 @@ /* istanbul ignore file */ // eslint-disable-next-line import/no-extraneous-dependencies import * as AWS from 'aws-sdk'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { ConfigurationOptions } from 'aws-sdk/lib/config'; import * as https from 'https'; +const FRAMEWORK_HANDLER_TIMEOUT = 900000; // 15 minutes + +// In order to honor the overall maximum timeout set for the target process, +// the default 2 minutes from AWS SDK has to be overriden: +// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#httpOptions-property +const awsSdkConfig: ConfigurationOptions = { + httpOptions: { timeout: FRAMEWORK_HANDLER_TIMEOUT }, +}; + async function defaultHttpRequest(options: https.RequestOptions, responseBody: string) { return new Promise((resolve, reject) => { try { @@ -21,7 +32,7 @@ let lambda: AWS.Lambda; async function defaultStartExecution(req: AWS.StepFunctions.StartExecutionInput): Promise { if (!sfn) { - sfn = new AWS.StepFunctions(); + sfn = new AWS.StepFunctions(awsSdkConfig); } return await sfn.startExecution(req).promise(); @@ -29,7 +40,7 @@ async function defaultStartExecution(req: AWS.StepFunctions.StartExecutionInput) async function defaultInvokeFunction(req: AWS.Lambda.InvocationRequest): Promise { if (!lambda) { - lambda = new AWS.Lambda(); + lambda = new AWS.Lambda(awsSdkConfig); } return await lambda.invoke(req).promise(); 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/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json index 3d91d15aff6ac..9e3d73e284694 100644 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json @@ -113,7 +113,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bS3BucketA3488101" + "Ref": "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4S3BucketC6CBC09E" }, "S3Key": { "Fn::Join": [ @@ -126,7 +126,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bS3VersionKey23A2E46C" + "Ref": "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4S3VersionKeyB194AB23" } ] } @@ -139,7 +139,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bS3VersionKey23A2E46C" + "Ref": "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4S3VersionKeyB194AB23" } ] } @@ -242,17 +242,17 @@ } }, "Parameters": { - "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bS3BucketA3488101": { + "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4S3BucketC6CBC09E": { "Type": "String", - "Description": "S3 bucket for asset \"0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5b\"" + "Description": "S3 bucket for asset \"a75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4\"" }, - "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bS3VersionKey23A2E46C": { + "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4S3VersionKeyB194AB23": { "Type": "String", - "Description": "S3 key for asset version \"0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5b\"" + "Description": "S3 key for asset version \"a75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4\"" }, - "AssetParameters0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5bArtifactHashF6409D44": { + "AssetParametersa75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4ArtifactHashBE5BD63C": { "Type": "String", - "Description": "Artifact hash for asset \"0317970d7c7695dbb9076b70f5eaa0a840dabe9a56c3389439ae5018b5a4cc5b\"" + "Description": "Artifact hash for asset \"a75563f489fb6bc4064bc85b91ef607f671326e647bcd9d9bcab0731de62edd4\"" } }, "Outputs": { @@ -281,4 +281,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json b/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json index 9907ab690dd70..38ac259c2254a 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json @@ -88,7 +88,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersf465f835a93a93413d7d25f5572670bbb6379304f4cdbad718d4f6a5562d1368S3Bucket3674ED2B" + "Ref": "AssetParametersfd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2S3BucketD831F708" }, "S3Key": { "Fn::Join": [ @@ -101,7 +101,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf465f835a93a93413d7d25f5572670bbb6379304f4cdbad718d4f6a5562d1368S3VersionKey1CFAF1A8" + "Ref": "AssetParametersfd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2S3VersionKeyEAC17D61" } ] } @@ -114,7 +114,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf465f835a93a93413d7d25f5572670bbb6379304f4cdbad718d4f6a5562d1368S3VersionKey1CFAF1A8" + "Ref": "AssetParametersfd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2S3VersionKeyEAC17D61" } ] } @@ -200,7 +200,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18" }, "S3Key": { "Fn::Join": [ @@ -213,7 +213,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" } ] } @@ -226,7 +226,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" } ] } @@ -579,7 +579,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18" }, "S3Key": { "Fn::Join": [ @@ -592,7 +592,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" } ] } @@ -605,7 +605,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" } ] } @@ -721,7 +721,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18" }, "S3Key": { "Fn::Join": [ @@ -734,7 +734,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" } ] } @@ -747,7 +747,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" } ] } @@ -860,7 +860,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18" }, "S3Key": { "Fn::Join": [ @@ -873,7 +873,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" } ] } @@ -886,7 +886,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" } ] } @@ -1030,29 +1030,29 @@ } }, "Parameters": { - "AssetParametersf465f835a93a93413d7d25f5572670bbb6379304f4cdbad718d4f6a5562d1368S3Bucket3674ED2B": { + "AssetParametersfd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2S3BucketD831F708": { "Type": "String", - "Description": "S3 bucket for asset \"f465f835a93a93413d7d25f5572670bbb6379304f4cdbad718d4f6a5562d1368\"" + "Description": "S3 bucket for asset \"fd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2\"" }, - "AssetParametersf465f835a93a93413d7d25f5572670bbb6379304f4cdbad718d4f6a5562d1368S3VersionKey1CFAF1A8": { + "AssetParametersfd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2S3VersionKeyEAC17D61": { "Type": "String", - "Description": "S3 key for asset version \"f465f835a93a93413d7d25f5572670bbb6379304f4cdbad718d4f6a5562d1368\"" + "Description": "S3 key for asset version \"fd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2\"" }, - "AssetParametersf465f835a93a93413d7d25f5572670bbb6379304f4cdbad718d4f6a5562d1368ArtifactHashD28BEA4C": { + "AssetParametersfd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2ArtifactHashDD841113": { "Type": "String", - "Description": "Artifact hash for asset \"f465f835a93a93413d7d25f5572670bbb6379304f4cdbad718d4f6a5562d1368\"" + "Description": "Artifact hash for asset \"fd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C": { + "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18": { "Type": "String", - "Description": "S3 bucket for asset \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "S3 bucket for asset \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB": { + "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471": { "Type": "String", - "Description": "S3 key for asset version \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "S3 key for asset version \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1ArtifactHash251241BC": { + "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6ArtifactHash3651BF53": { "Type": "String", - "Description": "Artifact hash for asset \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "Artifact hash for asset \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" }, "AssetParameters4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8S3Bucket0DB889DF": { "Type": "String", @@ -1085,4 +1085,4 @@ } } } -} \ No newline at end of file +} 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/bin/rewrite-imports.ts b/packages/@monocdk-experiment/rewrite-imports/bin/rewrite-imports.ts index 360e526fbe175..a79f833d5e71c 100644 --- a/packages/@monocdk-experiment/rewrite-imports/bin/rewrite-imports.ts +++ b/packages/@monocdk-experiment/rewrite-imports/bin/rewrite-imports.ts @@ -3,10 +3,9 @@ import * as fs from 'fs'; import * as _glob from 'glob'; import { promisify } from 'util'; -import { rewriteFile } from '../lib/rewrite'; +import { rewriteImports } from '../lib/rewrite'; + const glob = promisify(_glob); -const readFile = promisify(fs.readFile); -const writeFile = promisify(fs.writeFile); async function main() { if (!process.argv[2]) { @@ -21,10 +20,10 @@ async function main() { const files = await glob(process.argv[2], { ignore, matchBase: true }); for (const file of files) { - const input = await readFile(file, 'utf-8'); - const output = rewriteFile(input); + const input = await fs.promises.readFile(file, { encoding: 'utf8' }); + const output = rewriteImports(input, file); if (output.trim() !== input.trim()) { - await writeFile(file, output); + await fs.promises.writeFile(file, output); } } } diff --git a/packages/@monocdk-experiment/rewrite-imports/lib/rewrite.ts b/packages/@monocdk-experiment/rewrite-imports/lib/rewrite.ts index 35b78943f6444..f6dad5edbd89b 100644 --- a/packages/@monocdk-experiment/rewrite-imports/lib/rewrite.ts +++ b/packages/@monocdk-experiment/rewrite-imports/lib/rewrite.ts @@ -1,24 +1,113 @@ -const exclude = [ - '@aws-cdk/cloudformation-diff', - '@aws-cdk/assert', -]; +import * as ts from 'typescript'; + +/** + * Re-writes "hyper-modular" CDK imports (most packages in `@aws-cdk/*`) to the + * relevant "mono" CDK import path. The re-writing will only modify the imported + * library path, presrving the existing quote style, etc... + * + * Syntax errors in the source file being processed may cause some import + * statements to not be re-written. + * + * Supported import statement forms are: + * - `import * as lib from '@aws-cdk/lib';` + * - `import { Type } from '@aws-cdk/lib';` + * - `import '@aws-cdk/lib';` + * - `import lib = require('@aws-cdk/lib');` + * - `import { Type } = require('@aws-cdk/lib'); + * - `require('@aws-cdk/lib'); + * + * @param sourceText the source code where imports should be re-written. + * @param fileName a customized file name to provide the TypeScript processor. + * + * @returns the updated source code. + */ +export function rewriteImports(sourceText: string, fileName: string = 'index.ts'): string { + const sourceFile = ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.ES2018); + + const replacements = new Array<{ original: ts.Node, updatedLocation: string }>(); + + const visitor = (node: T): ts.VisitResult => { + const moduleSpecifier = getModuleSpecifier(node); + const newTarget = moduleSpecifier && updatedLocationOf(moduleSpecifier.text); + + if (moduleSpecifier != null && newTarget != null) { + replacements.push({ original: moduleSpecifier, updatedLocation: newTarget }); + } -export function rewriteFile(source: string) { - const output = new Array(); - for (const line of source.split('\n')) { - output.push(rewriteLine(line)); + return node; + }; + + sourceFile.statements.forEach(node => ts.visitNode(node, visitor)); + + let updatedSourceText = sourceText; + // Applying replacements in reverse order, so node positions remain valid. + for (const replacement of replacements.sort(({ original: l }, { original: r }) => r.getStart(sourceFile) - l.getStart(sourceFile))) { + const prefix = updatedSourceText.substring(0, replacement.original.getStart(sourceFile) + 1); + const suffix = updatedSourceText.substring(replacement.original.getEnd() - 1); + + updatedSourceText = prefix + replacement.updatedLocation + suffix; } - return output.join('\n'); -} -export function rewriteLine(line: string) { - for (const skip of exclude) { - if (line.includes(skip)) { - return line; + return updatedSourceText; + + function getModuleSpecifier(node: ts.Node): ts.StringLiteral | undefined { + if (ts.isImportDeclaration(node)) { + // import style + const moduleSpecifier = node.moduleSpecifier; + if (ts.isStringLiteral(moduleSpecifier)) { + // import from 'location'; + // import * as name from 'location'; + return moduleSpecifier; + } else if (ts.isBinaryExpression(moduleSpecifier) && ts.isCallExpression(moduleSpecifier.right)) { + // import { Type } = require('location'); + return getModuleSpecifier(moduleSpecifier.right); + } + } else if ( + ts.isImportEqualsDeclaration(node) + && ts.isExternalModuleReference(node.moduleReference) + && ts.isStringLiteral(node.moduleReference.expression) + ) { + // import name = require('location'); + return node.moduleReference.expression; + } else if ( + (ts.isCallExpression(node)) + && ts.isIdentifier(node.expression) + && node.expression.escapedText === 'require' + && node.arguments.length === 1 + ) { + // require('location'); + const argument = node.arguments[0]; + if (ts.isStringLiteral(argument)) { + return argument; + } + } else if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression)) { + // require('location'); // This is an alternate AST version of it + return getModuleSpecifier(node.expression); } + return undefined; } - return line - .replace(/(["'])@aws-cdk\/assert(["'])/g, '$1@monocdk-experiment/assert$2') // @aws-cdk/assert => @monocdk-experiment/assert - .replace(/(["'])@aws-cdk\/core(["'])/g, '$1monocdk-experiment$2') // @aws-cdk/core => monocdk-experiment - .replace(/(["'])@aws-cdk\/(.+)(["'])/g, '$1monocdk-experiment/$2$3'); // @aws-cdk/* => monocdk-experiment/*; +} + +const EXEMPTIONS = new Set([ + '@aws-cdk/cloudformation-diff', +]); + +function updatedLocationOf(modulePath: string): string | undefined { + if (!modulePath.startsWith('@aws-cdk/') || EXEMPTIONS.has(modulePath)) { + return undefined; + } + + if (modulePath === '@aws-cdk/core') { + return 'monocdk-experiment'; + } + + if (modulePath === '@aws-cdk/assert') { + return '@monocdk-experiment/assert'; + } + + if (modulePath === '@aws-cdk/assert/jest') { + return '@monocdk-experiment/assert/jest'; + } + + return `monocdk-experiment/${modulePath.substring(9)}`; } diff --git a/packages/@monocdk-experiment/rewrite-imports/package.json b/packages/@monocdk-experiment/rewrite-imports/package.json index 1c1a44d1758a0..a9932328b681c 100644 --- a/packages/@monocdk-experiment/rewrite-imports/package.json +++ b/packages/@monocdk-experiment/rewrite-imports/package.json @@ -31,12 +31,13 @@ }, "license": "Apache-2.0", "dependencies": { - "glob": "^7.1.6" + "glob": "^7.1.6", + "typescript": "~3.9.5" }, "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/@monocdk-experiment/rewrite-imports/test/rewrite.test.ts b/packages/@monocdk-experiment/rewrite-imports/test/rewrite.test.ts index d282338f66c4b..689efb72ef79b 100644 --- a/packages/@monocdk-experiment/rewrite-imports/test/rewrite.test.ts +++ b/packages/@monocdk-experiment/rewrite-imports/test/rewrite.test.ts @@ -1,47 +1,75 @@ -import { rewriteFile, rewriteLine } from '../lib/rewrite'; +import { rewriteImports } from '../lib/rewrite'; -describe('rewriteLine', () => { - test('quotes', () => { - expect(rewriteLine('import * as s3 from \'@aws-cdk/aws-s3\'')) - .toEqual('import * as s3 from \'monocdk-experiment/aws-s3\''); - }); +describe(rewriteImports, () => { + test('correctly rewrites naked "import"', () => { + const output = rewriteImports(` + // something before + import '@aws-cdk/assert/jest'; + // something after - test('double quotes', () => { - expect(rewriteLine('import * as s3 from "@aws-cdk/aws-s3"')) - .toEqual('import * as s3 from "monocdk-experiment/aws-s3"'); - }); + console.log('Look! I did something!');`, 'subhect.ts'); + + expect(output).toBe(` + // something before + import '@monocdk-experiment/assert/jest'; + // something after - test('@aws-cdk/core', () => { - expect(rewriteLine('import * as s3 from "@aws-cdk/core"')) - .toEqual('import * as s3 from "monocdk-experiment"'); - expect(rewriteLine('import * as s3 from \'@aws-cdk/core\'')) - .toEqual('import * as s3 from \'monocdk-experiment\''); + console.log('Look! I did something!');`); }); - test('non-jsii modules are ignored', () => { - expect(rewriteLine('import * as cfndiff from \'@aws-cdk/cloudformation-diff\'')) - .toEqual('import * as cfndiff from \'@aws-cdk/cloudformation-diff\''); - expect(rewriteLine('import * as cfndiff from \'@aws-cdk/assert')) - .toEqual('import * as cfndiff from \'@aws-cdk/assert'); + test('correctly rewrites naked "require"', () => { + const output = rewriteImports(` + // something before + require('@aws-cdk/assert/jest'); + // something after + + console.log('Look! I did something!');`, 'subhect.ts'); + + expect(output).toBe(` + // something before + require('@monocdk-experiment/assert/jest'); + // something after + + console.log('Look! I did something!');`); }); -}); -describe('rewriteFile', () => { - const output = rewriteFile(` + test('correctly rewrites "import from"', () => { + const output = rewriteImports(` // something before import * as s3 from '@aws-cdk/aws-s3'; import * as cfndiff from '@aws-cdk/cloudformation-diff'; - import * as s3 from '@aws-cdk/core'; + import { Construct } from "@aws-cdk/core"; // something after - // hello`); + console.log('Look! I did something!');`, 'subject.ts'); - expect(output).toEqual(` + expect(output).toBe(` // something before import * as s3 from 'monocdk-experiment/aws-s3'; import * as cfndiff from '@aws-cdk/cloudformation-diff'; - import * as s3 from 'monocdk-experiment'; + import { Construct } from "monocdk-experiment"; + // something after + + console.log('Look! I did something!');`); + }); + + test('correctly rewrites "import = require"', () => { + const output = rewriteImports(` + // something before + import s3 = require('@aws-cdk/aws-s3'); + import cfndiff = require('@aws-cdk/cloudformation-diff'); + import { Construct } = require("@aws-cdk/core"); // something after - // hello`); -}); \ No newline at end of file + console.log('Look! I did something!');`, 'subject.ts'); + + expect(output).toBe(` + // something before + import s3 = require('monocdk-experiment/aws-s3'); + import cfndiff = require('@aws-cdk/cloudformation-diff'); + import { Construct } = require("monocdk-experiment"); + // something after + + console.log('Look! I did something!');`); + }); +}); 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/README.md b/packages/aws-cdk/README.md index 1d3b1ca3b85db..ad31e14b4214a 100644 --- a/packages/aws-cdk/README.md +++ b/packages/aws-cdk/README.md @@ -239,7 +239,8 @@ $ cdk destroy --app='node bin/main.js' MyStackName #### `cdk bootstrap` Deploys a `CDKToolkit` CloudFormation stack into the specified environment(s), that provides an S3 bucket that `cdk deploy` will use to store synthesized templates and the related assets, before triggering a CloudFormation stack -update. The name of the deployed stack can be configured using the `--toolkit-stack-name` argument. +update. The name of the deployed stack can be configured using the `--toolkit-stack-name` argument. The S3 Bucket +Public Access Block Configuration can be configured using the `--public-access-block-configuration` argument. ```console $ # Deploys to all environments @@ -279,6 +280,6 @@ Some of the interesting keys that can be used in the JSON configuration files: }, "toolkitStackName": "foo", // Customize 'bootstrap' stack name (--toolkit-stack-name=foo) "toolkitBucketName": "fooBucket", // Customize 'bootstrap' bucket name (--toolkit-bucket-name=fooBucket) - "versionReporting": false // Opt-out of version reporting (--no-version-reporting) + "versionReporting": false, // Opt-out of version reporting (--no-version-reporting) } ``` diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 37c5a0887d52c..b30b6346ada84 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -67,6 +67,7 @@ async function parseCommandLineArguments() { .option('bootstrap-bucket-name', { type: 'string', alias: ['b', 'toolkit-bucket-name'], desc: 'The name of the CDK toolkit bucket; bucket will be created and must not exist', default: undefined }) .option('bootstrap-kms-key-id', { type: 'string', desc: 'AWS KMS master key ID used for the SSE-KMS encryption', default: undefined }) .option('qualifier', { type: 'string', desc: 'Unique string to distinguish multiple bootstrap stacks', default: undefined }) + .option('public-access-block-configuration', { type: 'boolean', desc: 'Block public access configuration on CDK toolkit bucket (enabled by default) ', default: true }) .option('tags', { type: 'array', alias: 't', desc: 'Tags to add for the stack (KEY=VALUE)', nargs: 1, requiresArg: true, default: [] }) .option('execute', {type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)', default: true}) .option('trust', { type: 'array', desc: 'The AWS account IDs that should be trusted to perform deployments into this environment (may be repeated)', default: [], nargs: 1, requiresArg: true, hidden: true }) @@ -233,6 +234,7 @@ async function initCommandLine() { bucketName: configuration.settings.get(['toolkitBucket', 'bucketName']), kmsKeyId: configuration.settings.get(['toolkitBucket', 'kmsKeyId']), qualifier: args.qualifier, + publicAccessBlockConfiguration: args.publicAccessBlockConfiguration, tags: configuration.settings.get(['tags']), execute: args.execute, trustedAccounts: args.trust, diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts index 2bb024d8abea1..71e85d77e9d69 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts @@ -62,6 +62,7 @@ export async function bootstrapEnvironment2( TrustedAccounts: params.trustedAccounts?.join(','), CloudFormationExecutionPolicies: params.cloudFormationExecutionPolicies?.join(','), Qualifier: params.qualifier, + PublicAccessBlockConfiguration: params.publicAccessBlockConfiguration || params.publicAccessBlockConfiguration === undefined ? 'true' : 'false', }, environment, sdkProvider, diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts index fd284a4fec83b..2a90bd9033908 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts @@ -70,4 +70,11 @@ export interface BootstrappingParameters { * @default - Default qualifier */ readonly qualifier?: string; + + /** + * Whether or not to enable S3 Staging Bucket Public Access Block Configuration + * + * @default true + */ + readonly publicAccessBlockConfiguration?: boolean; } \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml index 5b61c2e99e7dd..c3b744c83d9e2 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml @@ -28,6 +28,11 @@ Parameters: Description: An identifier to distinguish multiple bootstrap stacks in the same environment Default: hnb659fds Type: String + PublicAccessBlockConfiguration: + Description: Whether or not to enable S3 Staging Bucket Public Access Block Configuration + Default: 'true' + Type: 'String' + AllowedValues: ['true', 'false'] Conditions: HasTrustedAccounts: Fn::Not: @@ -57,6 +62,10 @@ Conditions: - Fn::Equals: - '' - Ref: ContainerAssetsRepositoryName + UsePublicAccessBlockConfiguration: + Fn::Equals: + - 'true' + - Ref: PublicAccessBlockConfiguration Resources: FileAssetsBucketEncryptionKey: Type: AWS::KMS::Key @@ -128,10 +137,13 @@ Resources: - Fn::Sub: "${FileAssetsBucketEncryptionKey.Arn}" - Fn::Sub: "${FileAssetsBucketKmsKeyId}" PublicAccessBlockConfiguration: - BlockPublicAcls: true - BlockPublicPolicy: true - IgnorePublicAcls: true - RestrictPublicBuckets: true + Fn::If: + - UsePublicAccessBlockConfiguration + - BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + - Ref: AWS::NoValue UpdateReplacePolicy: Retain StagingBucketPolicy: Type: 'AWS::S3::BucketPolicy' @@ -347,6 +359,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 +376,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/bootstrap/legacy-template.ts b/packages/aws-cdk/lib/api/bootstrap/legacy-template.ts index 9bd99a8792836..67bed3406b07a 100644 --- a/packages/aws-cdk/lib/api/bootstrap/legacy-template.ts +++ b/packages/aws-cdk/lib/api/bootstrap/legacy-template.ts @@ -3,6 +3,13 @@ import { BootstrappingParameters, BUCKET_DOMAIN_NAME_OUTPUT, BUCKET_NAME_OUTPUT export function legacyBootstrapTemplate(params: BootstrappingParameters): any { return { Description: 'The CDK Toolkit Stack. It was created by `cdk bootstrap` and manages resources necessary for managing your Cloud Applications with AWS CDK.', + Conditions: { + UsePublicAccessBlockConfiguration: { + 'Fn::Equals' : [ + params.publicAccessBlockConfiguration || params.publicAccessBlockConfiguration === undefined ? 'true' : 'false', + 'true', + ]}, + }, Resources: { StagingBucket: { Type: 'AWS::S3::Bucket', @@ -18,11 +25,16 @@ export function legacyBootstrapTemplate(params: BootstrappingParameters): any { }], }, PublicAccessBlockConfiguration: { - BlockPublicAcls: true, - BlockPublicPolicy: true, - IgnorePublicAcls: true, - RestrictPublicBuckets: true, - }, + 'Fn::If' : [ + 'UsePublicAccessBlockConfiguration', + { + BlockPublicAcls: true, + BlockPublicPolicy: true, + IgnorePublicAcls: true, + RestrictPublicBuckets: true, + }, + 'AWS::NoValue', + ]}, }, }, StagingBucketPolicy: { 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..adf36daa5eb4c 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); }); @@ -55,6 +60,7 @@ test('do bootstrap', async () => { expect(bucketProperties.BucketName).toBeUndefined(); expect(bucketProperties.BucketEncryption.ServerSideEncryptionConfiguration[0].ServerSideEncryptionByDefault.KMSMasterKeyID) .toBeUndefined(); + expect(changeSetTemplate.Conditions.UsePublicAccessBlockConfiguration['Fn::Equals'][0]).toBe('true'); expect(ret.noOp).toBeFalsy(); expect(executed).toBeTruthy(); }); @@ -73,6 +79,7 @@ test('do bootstrap using custom bucket name', async () => { expect(bucketProperties.BucketName).toBe('foobar'); expect(bucketProperties.BucketEncryption.ServerSideEncryptionConfiguration[0].ServerSideEncryptionByDefault.KMSMasterKeyID) .toBeUndefined(); + expect(changeSetTemplate.Conditions.UsePublicAccessBlockConfiguration['Fn::Equals'][0]).toBe('true'); expect(ret.noOp).toBeFalsy(); expect(executed).toBeTruthy(); }); @@ -91,6 +98,26 @@ test('do bootstrap using KMS CMK', async () => { expect(bucketProperties.BucketName).toBeUndefined(); expect(bucketProperties.BucketEncryption.ServerSideEncryptionConfiguration[0].ServerSideEncryptionByDefault.KMSMasterKeyID) .toBe('myKmsKey'); + expect(changeSetTemplate.Conditions.UsePublicAccessBlockConfiguration['Fn::Equals'][0]).toBe('true'); + expect(ret.noOp).toBeFalsy(); + expect(executed).toBeTruthy(); +}); + +test('bootstrap disable bucket Public Access Block Configuration', async () => { + // WHEN + const ret = await bootstrapEnvironment(env, sdk, { + toolkitStackName: 'mockStack', + parameters: { + publicAccessBlockConfiguration: false, + }, + }); + + // THEN + const bucketProperties = changeSetTemplate.Resources.StagingBucket.Properties; + expect(bucketProperties.BucketName).toBeUndefined(); + expect(bucketProperties.BucketEncryption.ServerSideEncryptionConfiguration[0].ServerSideEncryptionByDefault.KMSMasterKeyID) + .toBeUndefined(); + expect(changeSetTemplate.Conditions.UsePublicAccessBlockConfiguration['Fn::Equals'][0]).toBe('false'); expect(ret.noOp).toBeFalsy(); expect(executed).toBeTruthy(); }); @@ -109,6 +136,7 @@ test('do bootstrap with custom tags for toolkit stack', async () => { expect(bucketProperties.BucketName).toBeUndefined(); expect(bucketProperties.BucketEncryption.ServerSideEncryptionConfiguration[0].ServerSideEncryptionByDefault.KMSMasterKeyID) .toBeUndefined(); + expect(changeSetTemplate.Conditions.UsePublicAccessBlockConfiguration['Fn::Equals'][0]).toBe('true'); expect(ret.noOp).toBeFalsy(); expect(executed).toBeTruthy(); }); @@ -134,3 +162,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..8409a60cf2945 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, { @@ -36,6 +41,7 @@ describe('Bootstrapping v2', () => { expect(mockDeployStack).toHaveBeenCalledWith(expect.objectContaining({ parameters: { FileAssetsBucketName: 'my-bucket-name', + PublicAccessBlockConfiguration: 'true', }, })); }); @@ -50,6 +56,21 @@ describe('Bootstrapping v2', () => { expect(mockDeployStack).toHaveBeenCalledWith(expect.objectContaining({ parameters: { FileAssetsBucketKmsKeyId: 'my-kms-key-id', + PublicAccessBlockConfiguration: 'true', + }, + })); + }); + + test('passes false to PublicAccessBlockConfiguration', async () => { + await bootstrapEnvironment2(env, sdk, { + parameters: { + publicAccessBlockConfiguration: false, + }, + }); + + expect(mockDeployStack).toHaveBeenCalledWith(expect.objectContaining({ + parameters: { + PublicAccessBlockConfiguration: 'false', }, })); }); @@ -74,6 +95,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/tools/awslint/.gitignore b/packages/awslint/.gitignore similarity index 100% rename from tools/awslint/.gitignore rename to packages/awslint/.gitignore diff --git a/tools/awslint/.npmignore b/packages/awslint/.npmignore similarity index 100% rename from tools/awslint/.npmignore rename to packages/awslint/.npmignore diff --git a/tools/awslint/LICENSE b/packages/awslint/LICENSE similarity index 100% rename from tools/awslint/LICENSE rename to packages/awslint/LICENSE diff --git a/tools/awslint/NOTICE b/packages/awslint/NOTICE similarity index 100% rename from tools/awslint/NOTICE rename to packages/awslint/NOTICE diff --git a/tools/awslint/README.md b/packages/awslint/README.md similarity index 100% rename from tools/awslint/README.md rename to packages/awslint/README.md diff --git a/tools/awslint/bin/awslint b/packages/awslint/bin/awslint similarity index 100% rename from tools/awslint/bin/awslint rename to packages/awslint/bin/awslint diff --git a/tools/awslint/bin/awslint.ts b/packages/awslint/bin/awslint.ts similarity index 100% rename from tools/awslint/bin/awslint.ts rename to packages/awslint/bin/awslint.ts diff --git a/tools/awslint/lib/index.ts b/packages/awslint/lib/index.ts similarity index 100% rename from tools/awslint/lib/index.ts rename to packages/awslint/lib/index.ts diff --git a/tools/awslint/lib/linter.ts b/packages/awslint/lib/linter.ts similarity index 100% rename from tools/awslint/lib/linter.ts rename to packages/awslint/lib/linter.ts diff --git a/tools/awslint/lib/rules/api.ts b/packages/awslint/lib/rules/api.ts similarity index 100% rename from tools/awslint/lib/rules/api.ts rename to packages/awslint/lib/rules/api.ts diff --git a/tools/awslint/lib/rules/attributes.ts b/packages/awslint/lib/rules/attributes.ts similarity index 100% rename from tools/awslint/lib/rules/attributes.ts rename to packages/awslint/lib/rules/attributes.ts diff --git a/tools/awslint/lib/rules/cfn-resource.ts b/packages/awslint/lib/rules/cfn-resource.ts similarity index 100% rename from tools/awslint/lib/rules/cfn-resource.ts rename to packages/awslint/lib/rules/cfn-resource.ts diff --git a/tools/awslint/lib/rules/cloudwatch-events.ts b/packages/awslint/lib/rules/cloudwatch-events.ts similarity index 100% rename from tools/awslint/lib/rules/cloudwatch-events.ts rename to packages/awslint/lib/rules/cloudwatch-events.ts diff --git a/tools/awslint/lib/rules/construct.ts b/packages/awslint/lib/rules/construct.ts similarity index 100% rename from tools/awslint/lib/rules/construct.ts rename to packages/awslint/lib/rules/construct.ts diff --git a/tools/awslint/lib/rules/core-types.ts b/packages/awslint/lib/rules/core-types.ts similarity index 100% rename from tools/awslint/lib/rules/core-types.ts rename to packages/awslint/lib/rules/core-types.ts diff --git a/tools/awslint/lib/rules/docs.ts b/packages/awslint/lib/rules/docs.ts similarity index 100% rename from tools/awslint/lib/rules/docs.ts rename to packages/awslint/lib/rules/docs.ts diff --git a/tools/awslint/lib/rules/durations.ts b/packages/awslint/lib/rules/durations.ts similarity index 100% rename from tools/awslint/lib/rules/durations.ts rename to packages/awslint/lib/rules/durations.ts diff --git a/tools/awslint/lib/rules/exports.ts b/packages/awslint/lib/rules/exports.ts similarity index 100% rename from tools/awslint/lib/rules/exports.ts rename to packages/awslint/lib/rules/exports.ts diff --git a/tools/awslint/lib/rules/imports.ts b/packages/awslint/lib/rules/imports.ts similarity index 100% rename from tools/awslint/lib/rules/imports.ts rename to packages/awslint/lib/rules/imports.ts diff --git a/tools/awslint/lib/rules/index.ts b/packages/awslint/lib/rules/index.ts similarity index 100% rename from tools/awslint/lib/rules/index.ts rename to packages/awslint/lib/rules/index.ts diff --git a/tools/awslint/lib/rules/integrations.ts b/packages/awslint/lib/rules/integrations.ts similarity index 100% rename from tools/awslint/lib/rules/integrations.ts rename to packages/awslint/lib/rules/integrations.ts diff --git a/tools/awslint/lib/rules/module.ts b/packages/awslint/lib/rules/module.ts similarity index 100% rename from tools/awslint/lib/rules/module.ts rename to packages/awslint/lib/rules/module.ts diff --git a/tools/awslint/lib/rules/no-unused-type.ts b/packages/awslint/lib/rules/no-unused-type.ts similarity index 100% rename from tools/awslint/lib/rules/no-unused-type.ts rename to packages/awslint/lib/rules/no-unused-type.ts diff --git a/tools/awslint/lib/rules/public-static-properties.ts b/packages/awslint/lib/rules/public-static-properties.ts similarity index 100% rename from tools/awslint/lib/rules/public-static-properties.ts rename to packages/awslint/lib/rules/public-static-properties.ts diff --git a/tools/awslint/lib/rules/resource.ts b/packages/awslint/lib/rules/resource.ts similarity index 100% rename from tools/awslint/lib/rules/resource.ts rename to packages/awslint/lib/rules/resource.ts diff --git a/tools/awslint/lib/rules/util.ts b/packages/awslint/lib/rules/util.ts similarity index 100% rename from tools/awslint/lib/rules/util.ts rename to packages/awslint/lib/rules/util.ts diff --git a/tools/awslint/load.sh b/packages/awslint/load.sh similarity index 100% rename from tools/awslint/load.sh rename to packages/awslint/load.sh diff --git a/tools/awslint/package.json b/packages/awslint/package.json similarity index 81% rename from tools/awslint/package.json rename to packages/awslint/package.json index 959ba1f0a4697..67f81ed960c6f 100644 --- a/tools/awslint/package.json +++ b/packages/awslint/package.json @@ -1,33 +1,32 @@ { "name": "awslint", - "private": true, "version": "0.0.0", "description": "Enforces the AWS Construct Library guidelines", - "main": "index.js", "scripts": { "build": "tsc -b && chmod +x bin/awslint", "lint": "tslint -p . && pkglint", "test": "echo ok", "watch": "tsc -b -w", - "build+test+package": "npm run build+test", + "package": "mkdir -p dist/js && mv $( npm pack ) dist/js/", + "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test" }, "bin": { "awslint": "bin/awslint" }, "dependencies": { - "@jsii/spec": "^1.6.0", + "@jsii/spec": "^1.7.0", "camelcase": "^6.0.0", "colors": "^1.4.0", "fs-extra": "^9.0.1", - "jsii-reflect": "^1.6.0", + "jsii-reflect": "^1.7.0", "yargs": "^15.3.1" }, "devDependencies": { "@types/fs-extra": "^8.1.0", "@types/yargs": "^15.0.5", "tslint": "^5.20.1", - "typescript": "~3.8.3" + "typescript": "~3.9.5" }, "repository": { "type": "git", diff --git a/tools/awslint/tsconfig.json b/packages/awslint/tsconfig.json similarity index 100% rename from tools/awslint/tsconfig.json rename to packages/awslint/tsconfig.json diff --git a/tools/awslint/tslint.yaml b/packages/awslint/tslint.yaml similarity index 100% rename from tools/awslint/tslint.yaml rename to packages/awslint/tslint.yaml 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..7554e7bee6180 100644 --- a/packages/cdk-dasm/package.json +++ b/packages/cdk-dasm/package.json @@ -26,11 +26,11 @@ }, "license": "Apache-2.0", "dependencies": { - "codemaker": "^1.6.0", + "codemaker": "^1.7.0", "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..b89bb874eae53 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -179,18 +179,18 @@ "@aws-cdk/region-info": "0.0.0", "constructs": "^3.0.2", "fs-extra": "^9.0.1", - "jsii-reflect": "^1.6.0", + "jsii-reflect": "^1.7.0", "jsonschema": "^1.2.6", "yaml": "1.9.2", "yargs": "^15.3.1" }, "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", - "jsii": "^1.6.0" + "jsii": "^1.7.0" }, "keywords": [ "aws", 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/build-tools/gen.ts b/packages/monocdk-experiment/build-tools/gen.ts index 96e7de8d874a1..3cc90564f320f 100644 --- a/packages/monocdk-experiment/build-tools/gen.ts +++ b/packages/monocdk-experiment/build-tools/gen.ts @@ -273,6 +273,13 @@ async function copyOrTransformFiles(from: string, to: string, libraries: readonl const promises = (await fs.readdir(from)).map(async name => { if (shouldIgnoreFile(name)) { return; } + if (name.endsWith(".d.ts") || name.endsWith(".js")) { + if (await fs.pathExists(path.join(from, name.replace(/\.(d\.ts|js)$/, ".ts")))) { + // We won't copy .d.ts and .js files with a corresponding .ts file + return; + } + } + const source = path.join(from, name); const destination = path.join(to, name); 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/bin/cdk-test.ts b/tools/cdk-build-tools/bin/cdk-test.ts index 4a237ad6324ac..4e89f9a562570 100644 --- a/tools/cdk-build-tools/bin/cdk-test.ts +++ b/tools/cdk-build-tools/bin/cdk-test.ts @@ -30,8 +30,15 @@ async function main() { const options = cdkBuildOptions(); + const defaultShellOptions = { + timers, + env: { + CDK_DISABLE_STACK_TRACE: '1', + }, + }; + if (options.test) { - await shell(options.test, { timers }); + await shell(options.test, defaultShellOptions); } const testFiles = await unitTestFiles(); @@ -41,7 +48,7 @@ async function main() { if (testFiles.length > 0) { throw new Error(`Jest is enabled, but ${testFiles.length} nodeunit tests were found!`); } - await shell([args.jest], { timers }); + await shell([args.jest], defaultShellOptions); } else if (testFiles.length > 0) { const testCommand: string[] = []; @@ -65,12 +72,12 @@ async function main() { testCommand.push(args.nodeunit); testCommand.push(...testFiles.map(f => f.path)); - await shell(testCommand, { timers }); + await shell(testCommand, defaultShellOptions); } // Run integration test if the package has integ test files if (await hasIntegTests()) { - await shell(['cdk-integ-assert'], { timers }); + await shell(['cdk-integ-assert'], defaultShellOptions); } } diff --git a/tools/cdk-build-tools/lib/os.ts b/tools/cdk-build-tools/lib/os.ts index f224ee67b2208..01103239cf721 100644 --- a/tools/cdk-build-tools/lib/os.ts +++ b/tools/cdk-build-tools/lib/os.ts @@ -6,6 +6,7 @@ import { Timers } from './timer'; interface ShellOptions { timers?: Timers; + env?: child_process.SpawnOptions['env']; } /** @@ -22,6 +23,10 @@ export async function shell(command: string[], options: ShellOptions = {}): Prom // Need this for Windows where we want .cmd and .bat to be found as well. shell: true, stdio: [ 'ignore', 'pipe', 'pipe' ], + env: { + ...process.env, + ...options.env, + }, }); const makeRed = process.stderr.isTTY ? colors.red : (x: string) => x; diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index b8ff407bc883f..6cab04c888862 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -34,28 +34,28 @@ "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", - "jsii-pacmak": "^1.6.0", + "jsii": "^1.7.0", + "jsii-pacmak": "^1.7.0", "nodeunit": "^0.11.3", - "nyc": "^15.0.1", + "nyc": "^15.1.0", "ts-jest": "^26.1.0", "tslint": "^5.20.1", - "typescript": "~3.8.3", + "typescript": "~3.9.5", "yargs": "^15.3.1", "yarn-cling": "0.0.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/bin/cdk-integ-assert.ts b/tools/cdk-integ-tools/bin/cdk-integ-assert.ts index f500416c690bd..300c5ddc75bf4 100644 --- a/tools/cdk-integ-tools/bin/cdk-integ-assert.ts +++ b/tools/cdk-integ-tools/bin/cdk-integ-assert.ts @@ -16,17 +16,9 @@ async function main() { throw new Error(`No such file: ${test.expectedFileName}. Run 'npm run integ'.`); } - const stackToDeploy = await test.determineTestStack(); const expected = await test.readExpected(); - const args = new Array(); - args.push('--no-path-metadata'); - args.push('--no-asset-metadata'); - args.push('--no-staging'); - const actual = await test.invoke(['--json', ...args, 'synth', ...stackToDeploy], { - json: true, - ...DEFAULT_SYNTH_OPTIONS, - }); + const actual = await test.cdkSynthFast(DEFAULT_SYNTH_OPTIONS); const diff = diffTemplate(expected, actual); diff --git a/tools/cdk-integ-tools/bin/cdk-integ.ts b/tools/cdk-integ-tools/bin/cdk-integ.ts index 7637f5754b6eb..e39d738c55c53 100644 --- a/tools/cdk-integ-tools/bin/cdk-integ.ts +++ b/tools/cdk-integ-tools/bin/cdk-integ.ts @@ -35,7 +35,7 @@ async function main() { try { // tslint:disable-next-line:max-line-length - await test.invoke([ ...args, 'deploy', '--require-approval', 'never', ...stackToDeploy ], { + await test.invokeCli([ ...args, 'deploy', '--require-approval', 'never', ...stackToDeploy ], { verbose: argv.verbose, // Note: no "context" and "env", so use default user settings! }); @@ -43,17 +43,13 @@ async function main() { console.error('Success! Writing out reference synth.'); // If this all worked, write the new expectation file - const actual = await test.invoke([ ...args, '--json', 'synth', ...stackToDeploy ], { - json: true, - verbose: argv.verbose, - ...DEFAULT_SYNTH_OPTIONS, - }); + const actual = await test.cdkSynthFast(DEFAULT_SYNTH_OPTIONS); await test.writeExpected(actual); } finally { if (argv.clean) { console.error('Cleaning up.'); - await test.invoke(['destroy', '--force', ...stackToDeploy ]); + await test.invokeCli(['destroy', '--force', ...stackToDeploy ]); } else { console.error('Skipping clean up (--no-clean).'); } diff --git a/tools/cdk-integ-tools/lib/integ-helpers.ts b/tools/cdk-integ-tools/lib/integ-helpers.ts index 75a7c56edc813..d264f545acde8 100644 --- a/tools/cdk-integ-tools/lib/integ-helpers.ts +++ b/tools/cdk-integ-tools/lib/integ-helpers.ts @@ -64,6 +64,11 @@ export class IntegrationTests { } } +export interface SynthOptions { + readonly context?: Record; + readonly env?: Record; +} + export class IntegrationTest { public readonly expectedFileName: string; private readonly expectedFilePath: string; @@ -78,7 +83,77 @@ export class IntegrationTest { this.cdkContextPath = path.join(this.directory, 'cdk.context.json'); } - public async invoke(args: string[], options: { json?: boolean, context?: any, verbose?: boolean, env?: any } = { }): Promise { + /** + * Do a CDK synth, mimicking the CLI (without actually using it) + * + * The CLI has a pretty slow startup time because of all the modules it needs to load, + * and we are running this in a tight loop. Bypass it to be quicker! + * + * Return the "main" template or a concatenation of all listed templates in the pragma + */ + public async cdkSynthFast(options: SynthOptions = {}): Promise { + const context = { + ...options.context, + }; + await exec(['node', `${this.name}`], { + cwd: this.directory, + env: { + ...options.env, + CDK_CONTEXT_JSON: JSON.stringify(context), + CDK_DEFAULT_ACCOUNT: '12345678', + CDK_DEFAULT_REGION: 'test-region', + CDK_OUTDIR: 'cdk.out', + CDK_CLI_ASM_VERSION: '5.0.0', + }, + }); + + // Interpret the cloud assembly directly here. Not great, but I'm wary + // adding dependencies on the libraries that model it. + // + // FIXME: Refactor later if it doesn't introduce dependency cycles + const cloudManifest = await fs.readJSON(path.resolve(this.directory, 'cdk.out', 'manifest.json')); + const stacks: Record = {}; + for (const [artifactId, artifact] of Object.entries(cloudManifest.artifacts ?? {}) as Array<[string, any]>) { + if (artifact.type !== 'aws:cloudformation:stack') { continue; } + + const template = await fs.readJSON(path.resolve(this.directory, 'cdk.out', artifact.properties.templateFile)); + stacks[artifactId] = template; + } + + const stacksToDiff = await this.readStackPragma(); + + if (stacksToDiff.length > 0) { + // This is a monster. I'm sorry. :( + const templates = stacksToDiff.length === 1 && stacksToDiff[0] === '*' + ? Object.values(stacks) + : stacksToDiff.map(templateForStackName); + + // We're supposed to just return *a* template (which is an object), but there's a crazy + // case in which we diff multiple templates at once and then they're an array. And it works somehow. + return templates.length === 1 ? templates[0] : templates; + } else { + const names = Object.keys(stacks); + if (names.length !== 1) { + throw new Error('"cdk-integ" can only operate on apps with a single stack.\n\n' + + ' If your app has multiple stacks, specify which stack to select by adding this to your test source:\n\n' + + ` ${CDK_INTEG_STACK_PRAGMA} STACK ...\n\n` + + ` Available stacks: ${names.join(' ')} (wildcards are also supported)\n`); + } + return stacks[names[0]]; + } + + function templateForStackName(name: string) { + if (!stacks[name]) { + throw new Error(`No such stack in output: ${name}`); + } + return stacks[name]; + } + } + + /** + * Invoke the CDK CLI with some options + */ + public async invokeCli(args: string[], options: { json?: boolean, context?: any, verbose?: boolean, env?: any } = { }): Promise { // Write context to cdk.json, afterwards delete. We need to do this because there is no way // to pass structured context data from the command-line, currently. if (options.context) { @@ -131,7 +206,7 @@ export class IntegrationTest { return pragma; } - const stacks = (await this.invoke([ 'ls' ], { ...DEFAULT_SYNTH_OPTIONS })).split('\n'); + const stacks = (await this.invokeCli([ 'ls' ], { ...DEFAULT_SYNTH_OPTIONS })).split('\n'); if (stacks.length !== 1) { throw new Error('"cdk-integ" can only operate on apps with a single stack.\n\n' + ' If your app has multiple stacks, specify which stack to select by adding this to your test source:\n\n' + @@ -170,7 +245,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..04fa146c2a4ec 100644 --- a/tools/cfn2ts/package.json +++ b/tools/cfn2ts/package.json @@ -30,14 +30,14 @@ "license": "Apache-2.0", "dependencies": { "@aws-cdk/cfnspec": "0.0.0", - "codemaker": "^1.6.0", + "codemaker": "^1.7.0", "fast-json-patch": "^3.0.0-1", "fs-extra": "^9.0.1", "yargs": "^15.3.1" }, "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/.gitignore b/tools/pkglint/.gitignore index b6e91e22a0f5c..82521bd0dc9a1 100644 --- a/tools/pkglint/.gitignore +++ b/tools/pkglint/.gitignore @@ -3,3 +3,4 @@ *.d.ts dist lib/generated/resources.ts +!jest.config.js diff --git a/tools/pkglint/jest.config.js b/tools/pkglint/jest.config.js new file mode 100644 index 0000000000000..896018e27914b --- /dev/null +++ b/tools/pkglint/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + testMatch: [ + "**/?(*.)+(test).js", + ], +}; \ No newline at end of file diff --git a/tools/pkglint/lib/aws-service-official-names.json b/tools/pkglint/lib/aws-service-official-names.json index 17e741cca18c5..61ac88c6f3e6c 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", @@ -42,7 +42,7 @@ "AWS::ElasticLoadBalancing": "Amazon Elastic Load Balancing", "AWS::ElasticLoadBalancingV2": "Amazon Elastic Load Balancing V2", "AWS::Elasticsearch": "Amazon Elasticsearch Service", - "AWS::Events": "Amazon CloudWatch Events", + "AWS::Events": "Amazon EventBridge", "AWS::FSx": "Amazon FSx", "AWS::GameLift": "Amazon GameLift", "AWS::Glue": "AWS Glue", diff --git a/tools/pkglint/lib/banners/README.md b/tools/pkglint/lib/banners/README.md deleted file mode 100644 index 1ff39234bf079..0000000000000 --- a/tools/pkglint/lib/banners/README.md +++ /dev/null @@ -1,10 +0,0 @@ -README maturity banners. - -L1 banners will only be included if the package contains a -`lib/*.generated.ts` file. - -L2 banners will be included if the package contains a non-generated, -non-index ts file. - -Some banners files are the same, or empty on purpose, because the messaging -is designed to be combined between the L1 and L2 layers. diff --git a/tools/pkglint/lib/banners/features-cfn-stable.md b/tools/pkglint/lib/banners/features-cfn-stable.md new file mode 100644 index 0000000000000..a72b593f64508 --- /dev/null +++ b/tools/pkglint/lib/banners/features-cfn-stable.md @@ -0,0 +1 @@ +> **CFN Resources:** 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. \ No newline at end of file diff --git a/tools/pkglint/lib/banners/features-developer-preview.md b/tools/pkglint/lib/banners/features-developer-preview.md new file mode 100644 index 0000000000000..f60d5d7cbd00f --- /dev/null +++ b/tools/pkglint/lib/banners/features-developer-preview.md @@ -0,0 +1 @@ +> **Developer Preview:** Higher level constructs in this module that are marked as developer preview have completed their phase of active development and are looking for adoption and feedback. While the same caveats around non-backward compatible as Experimental constructs apply, they will undergo fewer breaking changes. Just as with Experimental constructs, these are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. \ No newline at end of file diff --git a/tools/pkglint/lib/banners/features-experimental.md b/tools/pkglint/lib/banners/features-experimental.md new file mode 100644 index 0000000000000..3d56c6c4890e9 --- /dev/null +++ b/tools/pkglint/lib/banners/features-experimental.md @@ -0,0 +1 @@ +> **Experimental:** Higher level constructs in this module that are marked as experimental are 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. \ No newline at end of file diff --git a/tools/pkglint/lib/banners/features-stable.md b/tools/pkglint/lib/banners/features-stable.md new file mode 100644 index 0000000000000..d104c3e06d282 --- /dev/null +++ b/tools/pkglint/lib/banners/features-stable.md @@ -0,0 +1 @@ +> **Stable:** Higher level constructs in this module that are marked stable will not undergo any breaking changes. They will strictly follow the [Semantic Versioning](https://semver.org/) model. \ No newline at end of file diff --git a/tools/pkglint/lib/rules.ts b/tools/pkglint/lib/rules.ts index 53bb5e5a894b3..439d13ad358f8 100644 --- a/tools/pkglint/lib/rules.ts +++ b/tools/pkglint/lib/rules.ts @@ -183,6 +183,11 @@ export class MaturitySetting extends ValidationRule { return; } + if (pkg.json.features) { + // Skip this in favour of the FeatureStabilityRule. + return; + } + let maturity = pkg.json.maturity as string | undefined; const stability = pkg.json.stability as string | undefined; if (!maturity) { @@ -227,7 +232,7 @@ export class MaturitySetting extends ValidationRule { } const readmeContent = fs.readFileSync(readmeFile, { encoding: 'utf8' }); - const badgeRegex = new RegExp(badge.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\w+/g, '\\w+')); + const badgeRegex = toRegExp(badge); if (!badgeRegex.test(readmeContent)) { // Removing a possible old, now invalid stability indication from the README.md before adding a new one const [title, ...body] = readmeContent.replace(/(?:.|\n)+\n+/m, '').split('\n'); @@ -300,6 +305,11 @@ export class StabilitySetting extends ValidationRule { return; } + if (pkg.json.features) { + // Skip this in favour of the FeatureStabilityRule. + return; + } + const maturity = pkg.json.maturity as string | undefined; const stability = pkg.json.stability as string | undefined; @@ -314,6 +324,84 @@ export class StabilitySetting extends ValidationRule { } } +export class FeatureStabilityRule extends ValidationRule { + public readonly name = 'package-info/feature-stability'; + private readonly badges: { [key: string]: string } = { + 'Not Implemented': 'https://img.shields.io/badge/not--implemented-black.svg?style=for-the-badge', + 'Experimental': 'https://img.shields.io/badge/experimental-important.svg?style=for-the-badge', + 'Developer Preview': 'https://img.shields.io/badge/developer--preview-informational.svg?style=for-the-badge', + 'Stable': 'https://img.shields.io/badge/stable-success.svg?style=for-the-badge', + }; + + public validate(pkg: PackageJson): void { + if (pkg.json.private || !pkg.json.features) { + return; + } + + const stabilityBanner: string = [ + '', + '---', + '', + '| Features | Stability |', + '| --- | --- |', + ...this.featureEntries(pkg), + '', + ...this.bannerNotices(pkg), + '---', + '', + ].join('\n'); + + const readmeFile = path.join(pkg.packageRoot, 'README.md'); + if (!fs.existsSync(readmeFile)) { + // Presence of the file is asserted by another rule + return; + } + const readmeContent = fs.readFileSync(readmeFile, { encoding: 'utf8' }); + const stabilityRegex = toRegExp(stabilityBanner); + if (!stabilityRegex.test(readmeContent)) { + const [title, ...body] = readmeContent.replace(/(?:.|\n)+\n+/m, '').split('\n'); + pkg.report({ + ruleName: this.name, + message: 'Stability banner does not match as expected', + fix: () => fs.writeFileSync(readmeFile, [title, stabilityBanner, ...body].join('\n')), + }); + } + } + + private featureEntries(pkg: PackageJson): string[] { + const entries: string[] = []; + if (pkg.json['cdk-build']?.cloudformation) { + entries.push(`| CFN Resources | ![Stable](${this.badges.Stable}) |`); + } + pkg.json.features.forEach((feature: { [key: string]: string }) => { + const badge = this.badges[feature.stability]; + if (!badge) { + throw new Error(`Unknown stability - ${feature.stability}`); + } + entries.push(`| ${feature.name} | ![${feature.stability}](${badge}) |`); + }); + return entries; + } + + private bannerNotices(pkg: PackageJson): string[] { + const notices: string[] = []; + if (pkg.json['cdk-build']?.cloudformation) { + notices.push(readBannerFile('features-cfn-stable.md')); + notices.push(''); + } + + const noticeOrder = [ 'Experimental', 'Developer Preview', 'Stable' ]; + const stabilities = pkg.json.features.map((f: { [k: string]: string }) => f.stability); + const filteredNotices = noticeOrder.filter(v => stabilities.includes(v)); + filteredNotices.map((notice) => { + const lowerTrainCase = notice.toLowerCase().replace(/\s/g, '-'); + notices.push(readBannerFile(`features-${lowerTrainCase}.md`)); + notices.push(''); + }); + return notices; + } +} + /** * Keywords must contain CDK keywords and be sorted */ @@ -1289,3 +1377,11 @@ function repoRoot(dir: string) { } return root; } + +function toRegExp(str: string): RegExp { + return new RegExp(str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\w+/g, '\\w+')); +} + +function readBannerFile(file: string): string { + return fs.readFileSync(path.join(__dirname, 'banners', file), { encoding: 'utf-8' }); +} diff --git a/tools/pkglint/package.json b/tools/pkglint/package.json index 2118a2c677bb6..88d09027159b0 100644 --- a/tools/pkglint/package.json +++ b/tools/pkglint/package.json @@ -17,8 +17,9 @@ }, "scripts": { "build": "tsc -b && tslint -p . && chmod +x bin/pkglint", - "build+test": "npm run build", - "build+test+package": "npm run build", + "test": "jest", + "build+test": "npm run build && npm test", + "build+test+package": "npm run build && npm test", "watch": "tsc -b -w", "lint": "tsc -b && tslint -p . --force" }, @@ -37,7 +38,8 @@ "@types/fs-extra": "^8.1.0", "@types/semver": "^7.2.0", "@types/yargs": "^15.0.5", - "typescript": "~3.8.3" + "jest": "^25.5.4", + "typescript": "~3.9.5" }, "dependencies": { "case": "^1.6.3", diff --git a/tools/pkglint/test/rules.test.ts b/tools/pkglint/test/rules.test.ts new file mode 100644 index 0000000000000..3494db23467df --- /dev/null +++ b/tools/pkglint/test/rules.test.ts @@ -0,0 +1,166 @@ +import * as fs from 'fs-extra'; +import * as os from 'os'; +import * as path from 'path'; +import { PackageJson } from '../lib/packagejson'; +import * as rules from '../lib/rules'; + +describe('FeatureStabilityRule', () => { + test('feature table is rendered', async () => { + const dirPath = await fakeModuleDir({ + features: [ + { name: 'Experimental Feature', stability: 'Experimental' }, + { name: 'Stable Feature', stability: 'Stable' }, + { name: 'Dev Preview Feature', stability: 'Developer Preview' }, + { name: 'Not Implemented Feature', stability: 'Not Implemented' }, + ] + }); + const rule = new rules.FeatureStabilityRule(); + + const pkgJson = new PackageJson(path.join(dirPath, 'package.json')); + rule.validate(pkgJson); + + expect(pkgJson.hasReports).toBe(true); + pkgJson.applyFixes(); + const fixedContents = await fs.readFile(path.join(dirPath, 'README.md'), { encoding: 'utf8' }); + expect(fixedContents).toMatch(/Experimental Feature \| \!\[Experimental\]/); + expect(fixedContents).toMatch(/Dev Preview Feature \| \!\[Developer Preview\]/); + expect(fixedContents).toMatch(/Stable Feature \| \!\[Stable\]/); + expect(fixedContents).toMatch(/Not Implemented Feature \| \!\[Not Implemented\]/); + expect(fixedContents).not.toMatch(/CFN Resources/); + }); + + test('CFN Resources is rendered', async () => { + const dirPath = await fakeModuleDir({ + 'cdk-build': { cloudformation: 'Foo::Bar' }, + 'features': [], + }); + + const rule = new rules.FeatureStabilityRule(); + const pkgJson = new PackageJson(path.join(dirPath, 'package.json')); + rule.validate(pkgJson); + expect(pkgJson.hasReports).toBe(true); + pkgJson.applyFixes(); + const fixedContents = await fs.readFile(path.join(dirPath, 'README.md'), { encoding: 'utf8' }); + + expect(fixedContents).toMatch(/CFN Resources | \!\[Stable\]/); + }); + + describe('banner notices', () => { + test('CFN Resources', async () => { + const dirPath = await fakeModuleDir({ + 'cdk-build': { cloudformation: 'Foo::Bar' }, + 'features': [], + }); + const rule = new rules.FeatureStabilityRule(); + + const pkgJson = new PackageJson(path.join(dirPath, 'package.json')); + rule.validate(pkgJson); + pkgJson.applyFixes(); + + const fixedContents = await fs.readFile(path.join(dirPath, 'README.md'), { encoding: 'utf8' }); + expect(fixedContents).toMatch(/> \*\*CFN Resources:\*\*/); + }); + + test('experimental', async () => { + const dirPath = await fakeModuleDir({ + features: [ + { name: 'Feature', stability: 'Experimental' }, + ] + }); + const rule = new rules.FeatureStabilityRule(); + + const pkgJson = new PackageJson(path.join(dirPath, 'package.json')); + rule.validate(pkgJson); + pkgJson.applyFixes(); + + const fixedContents = await fs.readFile(path.join(dirPath, 'README.md'), { encoding: 'utf8' }); + expect(fixedContents).toMatch(/> \*\*Experimental:\*\*/); + expect(fixedContents).not.toMatch(/> \*\*Developer Preview:\*\*/); + expect(fixedContents).not.toMatch(/> \*\*Stable:\*\*/); + }); + + test('developer preview', async () => { + const dirPath = await fakeModuleDir({ + features: [ + { name: 'Feature', stability: 'Developer Preview' }, + ] + }); + const rule = new rules.FeatureStabilityRule(); + + const pkgJson = new PackageJson(path.join(dirPath, 'package.json')); + rule.validate(pkgJson); + pkgJson.applyFixes(); + + const fixedContents = await fs.readFile(path.join(dirPath, 'README.md'), { encoding: 'utf8' }); + expect(fixedContents).toMatch(/> \*\*Developer Preview:\*\*/); + expect(fixedContents).not.toMatch(/> \*\*Experimental:\*\*/); + expect(fixedContents).not.toMatch(/> \*\*Stable:\*\*/); + }); + + test('stable', async () => { + const dirPath = await fakeModuleDir({ + features: [ + { name: 'Feature', stability: 'Stable' }, + ] + }); + const rule = new rules.FeatureStabilityRule(); + + const pkgJson = new PackageJson(path.join(dirPath, 'package.json')); + rule.validate(pkgJson); + pkgJson.applyFixes(); + + const fixedContents = await fs.readFile(path.join(dirPath, 'README.md'), { encoding: 'utf8' }); + expect(fixedContents).toMatch(/> \*\*Stable:\*\*/); + expect(fixedContents).not.toMatch(/> \*\*Experimental:\*\*/); + expect(fixedContents).not.toMatch(/> \*\*Developer Preview:\*\*/); + }); + }); + + test('skip if package private', async () => { + const dirPath = await fakeModuleDir({ + private: true, + features: [ + { name: 'Experimental Feature', stability: 'Experimental' }, + ] + }); + const rule = new rules.FeatureStabilityRule(); + + const pkgJson = new PackageJson(path.join(dirPath, 'package.json')); + rule.validate(pkgJson); + + expect(pkgJson.hasReports).toBe(false); + }); + + test('skip if features is not specified', async () => { + const dirPath = await fakeModuleDir({}); + const rule = new rules.FeatureStabilityRule(); + + const pkgJson = new PackageJson(path.join(dirPath, 'package.json')); + rule.validate(pkgJson); + + expect(pkgJson.hasReports).toBe(false); + }); + + test('skip if README.md is missing', async () => { + const dirPath = await fakeModuleDir({ + features: [ + { name: 'Experimental Feature', stability: 'Experimental' }, + ] + }, false); + const rule = new rules.FeatureStabilityRule(); + + const pkgJson = new PackageJson(path.join(dirPath, 'package.json')); + rule.validate(pkgJson); + + expect(pkgJson.hasReports).toBe(false); + }); +}); + +async function fakeModuleDir(json: { [key: string]: any }, createReadme: boolean = true): Promise { + const tmpdir = await fs.mkdtemp(path.join(os.tmpdir(), 'pkglint-rules-test-')); + await fs.writeFile(path.join(tmpdir, 'package.json'), JSON.stringify(json)); + if (createReadme) { + await fs.createFile(path.join(tmpdir, 'README.md')); + } + return tmpdir; +} \ No newline at end of file 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..e372356bec847 100644 --- a/tools/yarn-cling/package.json +++ b/tools/yarn-cling/package.json @@ -39,10 +39,10 @@ }, "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", - "typescript": "~3.8.3", + "@types/node": "^10.17.26", + "typescript": "~3.9.5", "pkglint": "0.0.0" }, "dependencies": { diff --git a/yarn.lock b/yarn.lock index 3434b584d847c..70c63ca45eb21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -529,10 +529,10 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" -"@jsii/spec@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.6.0.tgz#a93fa8eb22684a2263f70c1ae2b7143e43548149" - integrity sha512-6S863f3YQCLG00236OOT29EOqZZRFQEQcfACZ5f3Ph1PApRRndeZLsELm23MS6cCktdgdptRzaYR0HCupajBHQ== +"@jsii/spec@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.7.0.tgz#2a70ee5753aab1711a5e65161a1988845eb91043" + integrity sha512-gvj0vEvKWSo89ywclzb0OfFDSOqwTpvk0VQp2F3UEHewvR+hjJMgLjo7+ycpQF2bTLLni99KLmapMg/huxfshA== dependencies: jsonschema "^1.2.6" @@ -1042,10 +1042,10 @@ inquirer "^6.2.0" npmlog "^4.1.2" -"@lerna/publish@3.22.0": - version "3.22.0" - resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-3.22.0.tgz#7a3fb61026d3b7425f3b9a1849421f67d795c55d" - integrity sha512-8LBeTLBN8NIrCrLGykRu+PKrfrCC16sGCVY0/bzq9TDioR7g6+cY0ZAw653Qt/0Kr7rg3J7XxVNdzj3fvevlwA== +"@lerna/publish@3.22.1": + version "3.22.1" + resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-3.22.1.tgz#b4f7ce3fba1e9afb28be4a1f3d88222269ba9519" + integrity sha512-PG9CM9HUYDreb1FbJwFg90TCBQooGjj+n/pb3gw/eH5mEDq0p8wKdLFe0qkiqUkm/Ub5C8DbVFertIo0Vd0zcw== dependencies: "@evocateur/libnpmaccess" "^3.1.2" "@evocateur/npm-registry-fetch" "^4.0.0" @@ -1068,7 +1068,7 @@ "@lerna/run-lifecycle" "3.16.2" "@lerna/run-topologically" "3.18.5" "@lerna/validation-error" "3.13.0" - "@lerna/version" "3.22.0" + "@lerna/version" "3.22.1" figgy-pudding "^3.5.1" fs-extra "^8.1.0" npm-package-arg "^6.1.0" @@ -1181,10 +1181,10 @@ dependencies: npmlog "^4.1.2" -"@lerna/version@3.22.0": - version "3.22.0" - resolved "https://registry.yarnpkg.com/@lerna/version/-/version-3.22.0.tgz#67e1340c1904e9b339becd66429f32dd8ad65a55" - integrity sha512-6uhL6RL7/FeW6u1INEgyKjd5dwO8+IsbLfkfC682QuoVLS7VG6OOB+JmTpCvnuyYWI6fqGh1bRk9ww8kPsj+EA== +"@lerna/version@3.22.1": + version "3.22.1" + resolved "https://registry.yarnpkg.com/@lerna/version/-/version-3.22.1.tgz#9805a9247a47ee62d6b81bd9fa5fb728b24b59e2" + integrity sha512-PSGt/K1hVqreAFoi3zjD0VEDupQ2WZVlVIwesrE5GbrL2BjXowjCsTDPqblahDUPy0hp6h7E2kG855yLTp62+g== dependencies: "@lerna/check-working-tree" "3.16.5" "@lerna/child-process" "3.16.5" @@ -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" @@ -2679,14 +2678,14 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= -codemaker@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/codemaker/-/codemaker-1.6.0.tgz#5fa6cf121bfb4476908666b46cf9ff34a72ef49a" - integrity sha512-B8FcGhBVMfQs+a8i8VnAWZLUgsM8IU3Q+V2hrLnBXd82Tlp/uUm5K5melOJeSKCoHHaTU8y1kNLaNo6qq47etw== +codemaker@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/codemaker/-/codemaker-1.7.0.tgz#bcad7226740319dcdc99c23ba908a06c0e05ea35" + integrity sha512-TypUtZ56Au+BEvf0lSs37S/H5p2tpYfzF1FwE7hPNNasBffrAkqfryz0GFyOKPK7Svla5h5qTxRXFQWJ+g9Ciw== dependencies: camelcase "^6.0.0" decamelize "^1.2.0" - fs-extra "^9.0.0" + fs-extra "^9.0.1" collect-v8-coverage@^1.0.0: version "1.0.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" @@ -4408,7 +4408,7 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.0, fs-extra@^9.0.1: +fs-extra@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== @@ -4502,6 +4502,11 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + get-pkg-repo@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz#c73b489c06d80cc5536c2c853f9e05232056972d" @@ -5916,9 +5921,9 @@ js-tokens@^4.0.0: integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1, js-yaml@^3.2.7: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -5965,75 +5970,75 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -jsii-diff@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/jsii-diff/-/jsii-diff-1.6.0.tgz#a8b2cd56fd1fd77de37061c38a6434c70c235a41" - integrity sha512-m/xS549AtR/dK6crArmJeYHaJACwv+tj/koLsn2cKmPqfK2z6FcSgKjOnQH+Q2PlgsJWVUlyaVvhQlnj+W78kw== +jsii-diff@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/jsii-diff/-/jsii-diff-1.7.0.tgz#f63a36ebcb0c948ef2480015a5cc6137de6d451d" + integrity sha512-EqZWSnHL6Qe7p3rfMIIV2h4a2bdx8Q1mB48uHa9ORZPEPiXwKjmrmY5TKk7XRcR9GSamEoKEcxILmC1ld4CfdQ== dependencies: - "@jsii/spec" "^1.6.0" - fs-extra "^9.0.0" - jsii-reflect "^1.6.0" + "@jsii/spec" "^1.7.0" + fs-extra "^9.0.1" + jsii-reflect "^1.7.0" log4js "^6.3.0" - typescript "~3.8.3" + typescript "~3.9.5" yargs "^15.3.1" -jsii-pacmak@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/jsii-pacmak/-/jsii-pacmak-1.6.0.tgz#d25e162b16328b50e89c3927d996a277b6acb865" - integrity sha512-Ces8X36Ccyq5AZjzpznFUfV5wd0Ol0hiprJwtGHhs5vug5uJFLZxdS0hPFBFFPLiXQWwsEToWyM7PQ+xakTTpg== +jsii-pacmak@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/jsii-pacmak/-/jsii-pacmak-1.7.0.tgz#4e9da8cf33916a149d7e3c90088df9f9c9fd228d" + integrity sha512-HFAR4/ySCqUAkDu++tEjIK5c5eiRoqUa43KahAy0nBqcyg3TrPqqKiK2m6nUbB+A6tQ0n4zO1EWm8yE0WlvY0w== dependencies: - "@jsii/spec" "^1.6.0" + "@jsii/spec" "^1.7.0" clone "^2.1.2" - codemaker "^1.6.0" + codemaker "^1.7.0" commonmark "^0.29.1" escape-string-regexp "^4.0.0" - fs-extra "^9.0.0" - jsii-reflect "^1.6.0" - jsii-rosetta "^1.6.0" + fs-extra "^9.0.1" + jsii-reflect "^1.7.0" + jsii-rosetta "^1.7.0" semver "^7.3.2" spdx-license-list "^6.2.0" xmlbuilder "^15.1.1" yargs "^15.3.1" -jsii-reflect@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/jsii-reflect/-/jsii-reflect-1.6.0.tgz#dae9ea3aa04bc95a1c244051a4c4adf691849c01" - integrity sha512-JsVGJCcezNdnR4OukLNs7p6T6f3rKbGWNByE8Omvi7GfDf9c/YiVG4LggxEQaWyIZiYYqeEtBw6JtIKj3Qme5w== +jsii-reflect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/jsii-reflect/-/jsii-reflect-1.7.0.tgz#4e01f803d80babb4a5125a72793e4dc48316bbd4" + integrity sha512-2r/GC+Ka0rghcCDGjlcg2RUjLTtYvT5z5GpyewCRP2Ss/5wwHyCo8xc/MjpDulzFzFozVa2xBtrckcux1seSKA== dependencies: - "@jsii/spec" "^1.6.0" + "@jsii/spec" "^1.7.0" colors "^1.4.0" - fs-extra "^9.0.0" - oo-ascii-tree "^1.6.0" + fs-extra "^9.0.1" + oo-ascii-tree "^1.7.0" yargs "^15.3.1" -jsii-rosetta@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/jsii-rosetta/-/jsii-rosetta-1.6.0.tgz#49cf48328f29c0b88e2bec23372696b7c4eba006" - integrity sha512-eDaaIyvFcnB07j4aRS/xWBxenHE+OEW8gWLwSnv72+BsPifcS1QOkYcz/p/fTtPjNDyjtO8dcG5V4NUsy3QKdw== +jsii-rosetta@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/jsii-rosetta/-/jsii-rosetta-1.7.0.tgz#eabcb159a2230a1e69cc1503c2afae18316513e8" + integrity sha512-DFoWSaVYtHJuxUmBohabsBtmdVPyRAaHkKRXOerRLEwdnS/WIix+FgbtxeVTeWMOHuw7C928DjW6Gwbo+lJu8w== dependencies: - "@jsii/spec" "^1.6.0" + "@jsii/spec" "^1.7.0" commonmark "^0.29.1" - fs-extra "^9.0.0" - typescript "~3.8.3" + fs-extra "^9.0.1" + typescript "~3.9.5" xmldom "^0.3.0" yargs "^15.3.1" -jsii@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/jsii/-/jsii-1.6.0.tgz#35a60fed491bb3e4fa2c35965f9f2bb8593f2165" - integrity sha512-g9L2xBnKCrzfMPkaioYSz8lYATYGt8SWimycq9HxfszaI0/QjKv+68E5pgTimy6EZil+2O/KguiYqlK9jNQE7A== +jsii@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/jsii/-/jsii-1.7.0.tgz#a9e8267fcb7f964f60ed400851a78d47543eee5e" + integrity sha512-isvI0v39OGzPYK3TYA0tg6H8FOKoq7GS+bYYWrhI3YWXNrVl9ZZNQW0hz6sUWN1cla3W9KqMT7HFcRf3ttt0Sg== dependencies: - "@jsii/spec" "^1.6.0" + "@jsii/spec" "^1.7.0" case "^1.6.3" colors "^1.4.0" deep-equal "^2.0.3" - fs-extra "^9.0.0" + fs-extra "^9.0.1" log4js "^6.3.0" semver "^7.3.2" semver-intersect "^1.4.0" sort-json "^2.0.0" spdx-license-list "^6.2.0" - typescript "~3.8.3" + typescript "~3.9.5" yargs "^15.3.1" json-diff@^0.5.4: @@ -6132,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" @@ -6195,10 +6200,10 @@ lcov-parse@^1.0.0: resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-1.0.0.tgz#eb0d46b54111ebc561acb4c408ef9363bdc8f7e0" integrity sha1-6w1GtUER68VhrLTECO+TY73I9+A= -lerna@^3.22.0: - version "3.22.0" - resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.22.0.tgz#da14d08f183ffe6eec566a4ef3f0e11afa621183" - integrity sha512-xWlHdAStcqK/IjKvjsSMHPZjPkBV1lS60PmsIeObU8rLljTepc4Sg/hncw4HWfQxPIewHAUTqhrxPIsqf9L2Eg== +lerna@^3.22.1: + version "3.22.1" + resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.22.1.tgz#82027ac3da9c627fd8bf02ccfeff806a98e65b62" + integrity sha512-vk1lfVRFm+UuEFA7wkLKeSF7Iz13W+N/vFd48aW2yuS7Kv0RbNm2/qcDPV863056LMfkRlsEe+QYOw3palj5Lg== dependencies: "@lerna/add" "3.21.0" "@lerna/bootstrap" "3.21.0" @@ -6213,9 +6218,9 @@ lerna@^3.22.0: "@lerna/init" "3.21.0" "@lerna/link" "3.21.0" "@lerna/list" "3.21.0" - "@lerna/publish" "3.22.0" + "@lerna/publish" "3.22.1" "@lerna/run" "3.21.0" - "@lerna/version" "3.22.0" + "@lerna/version" "3.22.1" import-local "^2.0.0" npmlog "^4.1.2" @@ -7148,10 +7153,10 @@ nyc@^14.0.0: yargs "^13.2.2" yargs-parser "^13.0.0" -nyc@^15.0.1: - version "15.0.1" - resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.0.1.tgz#bd4d5c2b17f2ec04370365a5ca1fc0ed26f9f93d" - integrity sha512-n0MBXYBYRqa67IVt62qW1r/d9UH/Qtr7SF1w/nQLJ9KxvWF6b2xCHImRAixHN9tnMMYHC2P14uo6KddNGwMgGg== +nyc@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02" + integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A== dependencies: "@istanbuljs/load-nyc-config" "^1.0.0" "@istanbuljs/schema" "^0.1.2" @@ -7161,6 +7166,7 @@ nyc@^15.0.1: find-cache-dir "^3.2.0" find-up "^4.1.0" foreground-child "^2.0.0" + get-package-type "^0.1.0" glob "^7.1.6" istanbul-lib-coverage "^3.0.0" istanbul-lib-hook "^3.0.0" @@ -7249,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== @@ -7285,10 +7291,10 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -oo-ascii-tree@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/oo-ascii-tree/-/oo-ascii-tree-1.6.0.tgz#afc53c12d9bc33e658bfd3a4b128f8aeb2c97196" - integrity sha512-3JNvbe7r+qHPHbJhnQ8R8GzgSdF5sAA49gNKnJDWD/bQ9cZzSKG8qtbGPBBnwQ2wX/YCaJ4rUTs1c2Rz2sx1+w== +oo-ascii-tree@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/oo-ascii-tree/-/oo-ascii-tree-1.7.0.tgz#6d804ffd0971105900379e6a1091c0fa58a545ae" + integrity sha512-Kfz5r6vEtUTZV1J8jIQVOIsfNujk/Rk2ngUgHKDwDOliycLytI9Bg55iCUxUoeiuy9NCefx7ZaLAbzM0CkjaOA== opener@^1.5.1: version "1.5.1" @@ -8281,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" @@ -9474,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" @@ -9589,6 +9593,11 @@ typescript@^3.3.3, typescript@^3.5.3, typescript@~3.8.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== +typescript@~3.9.5: + version "3.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" + integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== + uglify-js@^3.1.4: version "3.9.1" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.9.1.tgz#a56a71c8caa2d36b5556cc1fd57df01ae3491539" @@ -9943,6 +9952,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"