diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 69581271f774d..d263a2a35e27e 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -1,7 +1,7 @@ --- name: "\U0001F41B Bug Report" about: Report a bug -title: "(module): " +title: "[module] " labels: bug, needs-triage --- diff --git a/.github/ISSUE_TEMPLATE/doc.md b/.github/ISSUE_TEMPLATE/doc.md index 53cacd3ae3bfd..022c04c22ed80 100644 --- a/.github/ISSUE_TEMPLATE/doc.md +++ b/.github/ISSUE_TEMPLATE/doc.md @@ -1,7 +1,7 @@ --- name: "📕 Documentation Issue" about: Issue in the reference documentation or developer guide -title: "(module): " +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 20a51e351f0e4..5fecac4f7ce57 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -1,7 +1,7 @@ --- name: "\U0001F680 Feature Request" about: Request a new feature -title: "(module): " +title: "[module] " labels: feature-request, needs-triage --- diff --git a/.github/ISSUE_TEMPLATE/general-issues.md b/.github/ISSUE_TEMPLATE/general-issues.md index 8cb4679f0889d..1f2d2b6ff707f 100644 --- a/.github/ISSUE_TEMPLATE/general-issues.md +++ b/.github/ISSUE_TEMPLATE/general-issues.md @@ -1,7 +1,7 @@ --- name: "\U00002753 General Issue" about: Create a new issue -title: "(module): " +title: "[module] " labels: needs-triage --- diff --git a/.github/workflows/issue-label-assign.yml b/.github/workflows/issue-label-assign.yml index 64b8fd2e08019..6529fc960b41f 100644 --- a/.github/workflows/issue-label-assign.yml +++ b/.github/workflows/issue-label-assign.yml @@ -1,9 +1,7 @@ name: "Set Issue Label and Assignee" on: issues: - types: [opened] - pull_request: - types: [opened] + types: [opened, edited] jobs: test: @@ -15,157 +13,156 @@ jobs: 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"]} + {"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]", "[vpc]"],"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]", "[ecrassets]"],"labels":["@aws-cdk/aws-ecr-assets"],"assignees":["eladb"]}, + {"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"]}, + {"keywords":["(@aws-cdk/aws-macie)","(aws-macie)","(macie)"],"labels":["@aws-cdk/aws-macie"],"assignees":["rix0rrr"]} ] diff --git a/.gitignore b/.gitignore index 2cb016405e016..36d6e15a83ab0 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ cdk.out/ # Yarn error log yarn-error.log + +# Parcel default cache directory +.parcel-cache diff --git a/CHANGELOG.md b/CHANGELOG.md index 7862a9041888f..28e25c02d359f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ 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.47.0](https://github.com/aws/aws-cdk/compare/v1.46.0...v1.47.0) (2020-06-24) + + +### ⚠ BREAKING CHANGES + +* **stepfunctions-tasks:** `Dynamo*` tasks no longer implement`IStepFunctionsTask` and have been replaced by constructs that can be instantiated directly. See README for examples + +### Features + +* **cfn-include:** add support for retrieving parameter objects ([#8658](https://github.com/aws/aws-cdk/issues/8658)) ([52dc123](https://github.com/aws/aws-cdk/commit/52dc123ba8696abcfad99d8093e98cd39b5b104f)), closes [#8657](https://github.com/aws/aws-cdk/issues/8657) +* **cfn-include:** support logical id overrides ([#8529](https://github.com/aws/aws-cdk/issues/8529)) ([d9c4f5e](https://github.com/aws/aws-cdk/commit/d9c4f5e67c54e1a2a436978fbc28fffd92b24cd6)), closes [#7375](https://github.com/aws/aws-cdk/issues/7375) +* **cloudwatch:** CompositeAlarm ([#8498](https://github.com/aws/aws-cdk/issues/8498)) ([1e6d293](https://github.com/aws/aws-cdk/commit/1e6d293f4c445318b11bd6fe998325688a675807)) +* **efs:** access point ([#8631](https://github.com/aws/aws-cdk/issues/8631)) ([dde0ef5](https://github.com/aws/aws-cdk/commit/dde0ef52cc0cdbc40fd212f518f3cee4f30450b9)) +* **stepfunctions:** grant APIs for state machine construct ([#8486](https://github.com/aws/aws-cdk/issues/8486)) ([fe71364](https://github.com/aws/aws-cdk/commit/fe71364b6cd8274e937cc2dc9185249dcbbb9388)), closes [#5933](https://github.com/aws/aws-cdk/issues/5933) +* **stepfunctions-tasks:** task constructs to call DynamoDB APIs ([#8466](https://github.com/aws/aws-cdk/issues/8466)) ([a7cb3b7](https://github.com/aws/aws-cdk/commit/a7cb3b7633c433ecb0619c030914bfa497ee39bc)), closes [#8108](https://github.com/aws/aws-cdk/issues/8108) + + +### Bug Fixes + +* **appsync:** Not to throw an Error even if 'additionalAuthorizationModes' is undefined ([#8673](https://github.com/aws/aws-cdk/issues/8673)) ([6b5d77b](https://github.com/aws/aws-cdk/commit/6b5d77b452bccb35564d6acee118112156149eb0)), closes [#8666](https://github.com/aws/aws-cdk/issues/8666) [#8668](https://github.com/aws/aws-cdk/issues/8668) +* **cli:** cannot change policies or trust after initial bootstrap ([#8677](https://github.com/aws/aws-cdk/issues/8677)) ([6e6b23e](https://github.com/aws/aws-cdk/commit/6e6b23e329d8a1b6455210768371a5ab9de478ef)), closes [#6581](https://github.com/aws/aws-cdk/issues/6581) +* **cli:** crash on tiny reported terminal width ([#8675](https://github.com/aws/aws-cdk/issues/8675)) ([a186c24](https://github.com/aws/aws-cdk/commit/a186c24918fddc697270b794b6603add5a47e947)), closes [#8667](https://github.com/aws/aws-cdk/issues/8667) +* **toolkit:** CLI tool fails on CloudFormation Throttling ([#8711](https://github.com/aws/aws-cdk/issues/8711)) ([e512a40](https://github.com/aws/aws-cdk/commit/e512a4057b21d32432d4dc7ac14ae7caa812265d)), closes [#5637](https://github.com/aws/aws-cdk/issues/5637) + ## [1.46.0](https://github.com/aws/aws-cdk/compare/v1.45.0...v1.46.0) (2020-06-19) diff --git a/lerna.json b/lerna.json index cc8251a51f0a9..f416a842add0e 100644 --- a/lerna.json +++ b/lerna.json @@ -10,5 +10,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.46.0" + "version": "1.47.0" } diff --git a/package.json b/package.json index 65f78e85c6d42..6442b77109e93 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,8 @@ "@aws-cdk/aws-codepipeline-actions/case/**", "@aws-cdk/aws-ecr-assets/minimatch", "@aws-cdk/aws-ecr-assets/minimatch/**", + "@aws-cdk/aws-eks/yaml", + "@aws-cdk/aws-eks/yaml/**", "@aws-cdk/aws-lambda-nodejs/parcel-bundler", "@aws-cdk/aws-lambda-nodejs/parcel-bundler/**", "@aws-cdk/cloud-assembly-schema/jsonschema", diff --git a/packages/@aws-cdk/assert/package.json b/packages/@aws-cdk/assert/package.json index 815abfab9ddc1..8eefc0da1c185 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": "^26.0.0", + "@types/jest": "^26.0.3", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", "pkglint": "0.0.0", diff --git a/packages/@aws-cdk/aws-backup/README.md b/packages/@aws-cdk/aws-backup/README.md index 531a324855f26..57e47aee1081a 100644 --- a/packages/@aws-cdk/aws-backup/README.md +++ b/packages/@aws-cdk/aws-backup/README.md @@ -6,6 +6,10 @@ > 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. + --- diff --git a/packages/@aws-cdk/aws-backup/lib/vault.ts b/packages/@aws-cdk/aws-backup/lib/vault.ts index 849f7dfa5cfa5..6f109306ce1c3 100644 --- a/packages/@aws-cdk/aws-backup/lib/vault.ts +++ b/packages/@aws-cdk/aws-backup/lib/vault.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as sns from '@aws-cdk/aws-sns'; -import { Aws, Construct, IResource, RemovalPolicy, Resource } from '@aws-cdk/core'; +import { Construct, IResource, RemovalPolicy, Resource } from '@aws-cdk/core'; import { CfnBackupVault } from './backup.generated'; /** @@ -21,7 +21,9 @@ export interface IBackupVault extends IResource { */ export interface BackupVaultProps { /** - * The name of a logical container where backups are stored. + * The name of a logical container where backups are stored. Backup vaults + * are identified by names that are unique to the account used to create + * them and the AWS Region where they are created. * * @default - A CDK generated name */ @@ -158,7 +160,7 @@ export class BackupVault extends Resource implements IBackupVault { private uniqueVaultName() { // Max length of 50 chars, get the last 50 chars - const id = `${this.node.uniqueId}${Aws.STACK_NAME}`; + const id = this.node.uniqueId; return id.substring(Math.max(id.length - 50, 0), id.length); } } diff --git a/packages/@aws-cdk/aws-backup/package.json b/packages/@aws-cdk/aws-backup/package.json index ce47e6dc2b249..43461ce79359f 100644 --- a/packages/@aws-cdk/aws-backup/package.json +++ b/packages/@aws-cdk/aws-backup/package.json @@ -99,7 +99,7 @@ "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-backup/test/integ.backup.expected.json b/packages/@aws-cdk/aws-backup/test/integ.backup.expected.json index bad27ac4ec97f..2ba1356211377 100644 --- a/packages/@aws-cdk/aws-backup/test/integ.backup.expected.json +++ b/packages/@aws-cdk/aws-backup/test/integ.backup.expected.json @@ -29,17 +29,7 @@ "Vault23237E5B": { "Type": "AWS::Backup::BackupVault", "Properties": { - "BackupVaultName": { - "Fn::Join": [ - "", - [ - "cdkbackupVaultC2A6D3CB", - { - "Ref": "AWS::StackName" - } - ] - ] - } + "BackupVaultName": "cdkbackupVaultC2A6D3CB" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" diff --git a/packages/@aws-cdk/aws-backup/test/vault.test.ts b/packages/@aws-cdk/aws-backup/test/vault.test.ts index 31e202a16b163..b8291be0e74ff 100644 --- a/packages/@aws-cdk/aws-backup/test/vault.test.ts +++ b/packages/@aws-cdk/aws-backup/test/vault.test.ts @@ -16,17 +16,7 @@ test('create a vault', () => { // THEN expect(stack).toHaveResource('AWS::Backup::BackupVault', { - BackupVaultName: { - 'Fn::Join': [ - '', - [ - 'Vault', - { - Ref: 'AWS::StackName', - }, - ], - ], - }, + BackupVaultName: 'Vault', }); }); diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index 50148bd7eb8af..9daff3e256225 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.703.0", + "aws-sdk": "^2.706.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/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index c0f699140ba69..158b50fc11d40 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.703.0", + "aws-sdk": "^2.706.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index f72cf8072fa80..a3ebadc8e8cee 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.703.0", + "aws-sdk": "^2.706.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/package.json b/packages/@aws-cdk/aws-codecommit/package.json index 39f382e390a3f..1fb64aef65eaf 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.703.0", + "aws-sdk": "^2.706.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package.json b/packages/@aws-cdk/aws-codepipeline-actions/package.json index 557f8079b9b34..b34c52fc4c7ec 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-cloudtrail": "0.0.0", - "@types/lodash": "^4.14.156", + "@types/lodash": "^4.14.157", "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-domain.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-domain.ts index 3566acf7c7aee..b910cc24a41f0 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-domain.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-domain.ts @@ -83,6 +83,8 @@ export class UserPoolDomain extends Resource implements IUserPoolDomain { public readonly domainName: string; private isCognitoDomain: boolean; + private cloudFrontCustomResource?: AwsCustomResource; + constructor(scope: Construct, id: string, props: UserPoolDomainProps) { super(scope, id); @@ -113,25 +115,27 @@ export class UserPoolDomain extends Resource implements IUserPoolDomain { * The domain name of the CloudFront distribution associated with the user pool domain. */ public get cloudFrontDomainName(): string { - const sdkCall: AwsSdkCall = { - service: 'CognitoIdentityServiceProvider', - action: 'describeUserPoolDomain', - parameters: { - Domain: this.domainName, - }, - physicalResourceId: PhysicalResourceId.of(this.domainName), - }; - const customResource = new AwsCustomResource(this, 'CloudFrontDomainName', { - resourceType: 'Custom::UserPoolCloudFrontDomainName', - onCreate: sdkCall, - onUpdate: sdkCall, - policy: AwsCustomResourcePolicy.fromSdkCalls({ - // DescribeUserPoolDomain only supports access level '*' - // https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazoncognitouserpools.html#amazoncognitouserpools-actions-as-permissions - resources: [ '*' ], - }), - }); - return customResource.getResponseField('DomainDescription.CloudFrontDistribution'); + if (!this.cloudFrontCustomResource) { + const sdkCall: AwsSdkCall = { + service: 'CognitoIdentityServiceProvider', + action: 'describeUserPoolDomain', + parameters: { + Domain: this.domainName, + }, + physicalResourceId: PhysicalResourceId.of(this.domainName), + }; + this.cloudFrontCustomResource = new AwsCustomResource(this, 'CloudFrontDomainName', { + resourceType: 'Custom::UserPoolCloudFrontDomainName', + onCreate: sdkCall, + onUpdate: sdkCall, + policy: AwsCustomResourcePolicy.fromSdkCalls({ + // DescribeUserPoolDomain only supports access level '*' + // https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazoncognitouserpools.html#amazoncognitouserpools-actions-as-permissions + resources: [ '*' ], + }), + }); + } + return this.cloudFrontCustomResource.getResponseField('DomainDescription.CloudFrontDistribution'); } /** diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-domain.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-domain.test.ts index 41407985c8ed1..cb281e11f369a 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-domain.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-domain.test.ts @@ -103,7 +103,7 @@ describe('User Pool Client', () => { })).not.toThrow(); }); - test('custom resource is added when cloudFrontDistribution method is called', () => { + test('custom resource is added when cloudFrontDomainName property is used', () => { // GIVEN const stack = new Stack(); const pool = new UserPool(stack, 'Pool'); @@ -137,6 +137,21 @@ describe('User Pool Client', () => { }); }); + test('cloudFrontDomainName property can be called multiple times', () => { + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + const domain = pool.addDomain('Domain', { + cognitoDomain: { + domainPrefix: 'cognito-domain-prefix', + }, + }); + + const cfDomainNameFirst = domain.cloudFrontDomainName; + const cfDomainNameSecond = domain.cloudFrontDomainName; + + expect(cfDomainNameSecond).toEqual(cfDomainNameFirst); + }); + describe('signInUrl', () => { test('returns the expected URL', () => { // GIVEN diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index 8be1059887c3a..833c699d5d80e 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": "^26.0.0", - "aws-sdk": "^2.703.0", + "@types/jest": "^26.0.3", + "aws-sdk": "^2.706.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 318ffb55a1274..d3d9e070684f8 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -584,7 +584,6 @@ EBS volume for the bastion host can be encrypted like: }); ``` - ## Block Devices To add EBS block device mappings, specify the `blockDeviceMappings` property. The follow example sets the EBS-backed @@ -608,6 +607,74 @@ new ec2.Instance(this, 'Instance', { ``` +## Volumes + +Whereas a `BlockDeviceVolume` is an EBS volume that is created and destroyed as part of the creation and destruction of a specific instance. A `Volume` is for when you want an EBS volume separate from any particular instance. A `Volume` is an EBS block device that can be attached to, or detached from, any instance at any time. Some types of `Volume`s can also be attached to multiple instances at the same time to allow you to have shared storage between those instances. + +A notable restriction is that a Volume can only be attached to instances in the same availability zone as the Volume itself. + +The following demonstrates how to create a 500 GiB encrypted Volume in the `us-west-2a` availability zone, and give a role the ability to attach that Volume to a specific instance: + +```ts +const instance = new ec2.Instance(this, 'Instance', { + // ... +}); +const role = new iam.Role(stack, 'SomeRole', { + assumedBy: new iam.AccountRootPrincipal(), +}); +const volume = new ec2.Volume(this, 'Volume', { + availabilityZone: 'us-west-2a', + size: cdk.Size.gibibytes(500), + encrypted: true, +}); + +volume.grantAttachVolume(role, [instance]); +``` + +### Instances Attaching Volumes to Themselves + +If you need to grant an instance the ability to attach/detach an EBS volume to/from itself, then using `grantAttachVolume` and `grantDetachVolume` as outlined above +will lead to an unresolvable circular reference between the instance role and the instance. In this case, use `grantAttachVolumeByResourceTag` and `grantDetachVolumeByResourceTag` as follows: + +```ts +const instance = new ec2.Instance(this, 'Instance', { + // ... +}); +const volume = new ec2.Volume(this, 'Volume', { + // ... +}); + +const attachGrant = volume.grantAttachVolumeByResourceTag(instance.grantPrincipal, [instance]); +const detachGrant = volume.grantDetachVolumeByResourceTag(instance.grantPrincipal, [instance]); +``` + +### Attaching Volumes + +The Amazon EC2 documentation for +[Linux Instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AmazonEBS.html) and +[Windows Instances](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ebs-volumes.html) contains information on how +to attach and detach your Volumes to/from instances, and how to format them for use. + +The following is a sample skeleton of EC2 UserData that can be used to attach a Volume to the Linux instance that it is running on: + +```ts +const volume = new ec2.Volume(this, 'Volume', { + // ... +}); +const instance = new ec2.Instance(this, 'Instance', { + // ... +}); +volume.grantAttachVolumeByResourceTag(instance.grantPrincipal, [instance]); +const targetDevice = '/dev/xvdz'; +instance.userData.addCommands( + // Attach the volume to /dev/xvdz + `aws --region ${Stack.of(this).region} ec2 attach-volume --volume-id ${volume.volumeId} --instance-id ${instance.instanceId} --device ${targetDevice}`, + // Wait until the volume has attached + `while ! test -e ${targetDevice}; do sleep 1; done` + // The volume will now be mounted. You may have to add additional code to format the volume if it has not been prepared. +); +``` + ## VPC Flow Logs VPC Flow Logs is a feature that enables you to capture information about the IP traffic going to and from network interfaces in your VPC. Flow log data can be published to Amazon CloudWatch Logs and Amazon S3. After you've created a flow log, you can retrieve and view its data in the chosen destination. (https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html). diff --git a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts index 8f27607a85f4a..28b8c9ee46554 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts @@ -383,23 +383,96 @@ export enum InstanceClass { * What size of instance to use */ export enum InstanceSize { + /** + * Instance size NANO (nano) + */ NANO = 'nano', + + /** + * Instance size MICRO (micro) + */ MICRO = 'micro', + + /** + * Instance size SMALL (small) + */ SMALL = 'small', + + /** + * Instance size MEDIUM (medium) + */ MEDIUM = 'medium', + + /** + * Instance size LARGE (large) + */ LARGE = 'large', + + /** + * Instance size XLARGE (xlarge) + */ XLARGE = 'xlarge', + + /** + * Instance size XLARGE2 (2xlarge) + */ XLARGE2 = '2xlarge', + + /** + * Instance size XLARGE4 (4xlarge) + */ XLARGE4 = '4xlarge', + + /** + * Instance size XLARGE6 (6xlarge) + */ + XLARGE6 = '6xlarge', + + /** + * Instance size XLARGE8 (8xlarge) + */ XLARGE8 = '8xlarge', + + /** + * Instance size XLARGE9 (9xlarge) + */ XLARGE9 = '9xlarge', + + /** + * Instance size XLARGE10 (10xlarge) + */ XLARGE10 = '10xlarge', + + /** + * Instance size XLARGE12 (12xlarge) + */ XLARGE12 = '12xlarge', + + /** + * Instance size XLARGE16 (16xlarge) + */ XLARGE16 = '16xlarge', + + /** + * Instance size XLARGE18 (18xlarge) + */ XLARGE18 = '18xlarge', + + /** + * Instance size XLARGE24 (24xlarge) + */ XLARGE24 = '24xlarge', + + /** + * Instance size XLARGE32 (32xlarge) + */ XLARGE32 = '32xlarge', + + /** + * Instance size METAL (metal) + */ METAL = 'metal', + } /** diff --git a/packages/@aws-cdk/aws-ec2/lib/volume.ts b/packages/@aws-cdk/aws-ec2/lib/volume.ts index 5108ea2effff1..6a6445dc87379 100644 --- a/packages/@aws-cdk/aws-ec2/lib/volume.ts +++ b/packages/@aws-cdk/aws-ec2/lib/volume.ts @@ -1,5 +1,10 @@ -import { Construct } from '@aws-cdk/core'; -import { CfnInstance } from './ec2.generated'; +import * as crypto from 'crypto'; + +import { AccountRootPrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam'; +import { IKey, ViaServicePrincipal } from '@aws-cdk/aws-kms'; +import { Construct, IResource, Resource, Size, SizeRoundingBehavior, Stack, Tag, Token } from '@aws-cdk/core'; +import { CfnInstance, CfnVolume } from './ec2.generated'; +import { IInstance } from './instance'; /** * Block device @@ -210,4 +215,482 @@ export enum EbsDeviceVolumeType { * Cold HDD */ SC1 = 'sc1', -} \ No newline at end of file + + /** + * General purpose SSD volume that balances price and performance for a wide variety of workloads. + */ + GENERAL_PURPOSE_SSD = GP2, + + /** + * Highest-performance SSD volume for mission-critical low-latency or high-throughput workloads. + */ + PROVISIONED_IOPS_SSD = IO1, + + /** + * Low-cost HDD volume designed for frequently accessed, throughput-intensive workloads. + */ + THROUGHPUT_OPTIMIZED_HDD = ST1, + + /** + * Lowest cost HDD volume designed for less frequently accessed workloads. + */ + COLD_HDD = SC1, + + /** + * Magnetic volumes are backed by magnetic drives and are suited for workloads where data is accessed infrequently, and scenarios where low-cost + * storage for small volume sizes is important. + */ + MAGNETIC = STANDARD, +} + +/** + * An EBS Volume in AWS EC2. + */ +export interface IVolume extends IResource { + /** + * The EBS Volume's ID + * + * @attribute + */ + readonly volumeId: string; + + /** + * The availability zone that the EBS Volume is contained within (ex: us-west-2a) + */ + readonly availabilityZone: string; + + /** + * The customer-managed encryption key that is used to encrypt the Volume. + * + * @attribute + */ + readonly encryptionKey?: IKey; + + /** + * Grants permission to attach this Volume to an instance. + * CAUTION: Granting an instance permission to attach to itself using this method will lead to + * an unresolvable circular reference between the instance role and the instance. + * Use {@link IVolume.grantAttachVolumeToSelf} to grant an instance permission to attach this + * volume to itself. + * + * @param grantee the principal being granted permission. + * @param instances the instances to which permission is being granted to attach this + * volume to. If not specified, then permission is granted to attach + * to all instances in this account. + */ + grantAttachVolume(grantee: IGrantable, instances?: IInstance[]): Grant; + + /** + * Grants permission to attach the Volume by a ResourceTag condition. If you are looking to + * grant an Instance, AutoScalingGroup, EC2-Fleet, SpotFleet, ECS host, etc the ability to attach + * this volume to **itself** then this is the method you want to use. + * + * This is implemented by adding a Tag with key `VolumeGrantAttach-` to the given + * constructs and this Volume, and then conditioning the Grant such that the grantee is only + * given the ability to AttachVolume if both the Volume and the destination Instance have that + * tag applied to them. + * + * If you need to call this method multiple times on different sets of constructs, then provide a + * unique `tagKeySuffix` for each call; failure to do so will result in an inability to attach this + * volume to some of the grants because it will overwrite the tag. + * + * @param grantee the principal being granted permission. + * @param constructs The list of constructs that will have the generated resource tag applied to them. + * @param tagKeySuffix A suffix to use on the generated Tag key in place of the generated hash value. + * Defaults to a hash calculated from this volume. + */ + grantAttachVolumeByResourceTag(grantee: IGrantable, constructs: Construct[], tagKeySuffix?: string): Grant; + + /** + * Grants permission to detach this Volume from an instance + * CAUTION: Granting an instance permission to detach from itself using this method will lead to + * an unresolvable circular reference between the instance role and the instance. + * Use {@link IVolume.grantDetachVolumeFromSelf} to grant an instance permission to detach this + * volume from itself. + * + * @param grantee the principal being granted permission. + * @param instances the instances to which permission is being granted to detach this + * volume from. If not specified, then permission is granted to detach + * from all instances in this account. + */ + grantDetachVolume(grantee: IGrantable, instances?: IInstance[]): Grant; + + /** + * Grants permission to detach the Volume by a ResourceTag condition. + * + * This is implemented via the same mechanism as {@link IVolume.grantAttachVolumeByResourceTag}, + * and is subject to the same conditions. + * + * @param grantee the principal being granted permission. + * @param constructs The list of constructs that will have the generated resource tag applied to them. + * @param tagKeySuffix A suffix to use on the generated Tag key in place of the generated hash value. + * Defaults to a hash calculated from this volume. + */ + grantDetachVolumeByResourceTag(grantee: IGrantable, constructs: Construct[], tagKeySuffix?: string): Grant; +} + +/** + * Properties of an EBS Volume + */ +export interface VolumeProps { + /** + * The value of the physicalName property of this resource. + * + * @default The physical name will be allocated by CloudFormation at deployment time + */ + readonly volumeName?: string; + + /** + * The Availability Zone in which to create the volume. + */ + readonly availabilityZone: string; + + /** + * The size of the volume, in GiBs. You must specify either a snapshot ID or a volume size. + * See {@link https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html#ebs-volume-characteristics|Volume Characteristics} + * for details on the allowable size for each type of volume. + * + * @default If you're creating the volume from a snapshot and don't specify a volume size, the default is the snapshot size. + */ + readonly size?: Size; + + /** + * The snapshot from which to create the volume. You must specify either a snapshot ID or a volume size. + * + * @default The EBS volume is not created from a snapshot. + */ + readonly snapshotId?: string; + + /** + * Indicates whether Amazon EBS Multi-Attach is enabled. + * See {@link https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volumes-multi.html#considerations|Considerations and limitations} + * for the constraints of multi-attach. + * + * @default false + */ + readonly enableMultiAttach?: boolean; + + /** + * Specifies whether the volume should be encrypted. The effect of setting the encryption state to true depends on the volume origin + * (new or from a snapshot), starting encryption state, ownership, and whether encryption by default is enabled. For more information, + * see {@link https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html#encryption-by-default|Encryption by Default} + * in the Amazon Elastic Compute Cloud User Guide. + * + * Encrypted Amazon EBS volumes must be attached to instances that support Amazon EBS encryption. For more information, see + * {@link https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html#EBSEncryption_supported_instances|Supported Instance Types.} + * + * @default false + */ + readonly encrypted?: boolean; + + /** + * The customer-managed encryption key that is used to encrypt the Volume. The encrypted property must + * be true if this is provided. + * + * Note: If using an {@link aws-kms.IKey} created from a {@link aws-kms.Key.fromKeyArn()} here, + * then the KMS key **must** have the following in its Key policy; otherwise, the Volume + * will fail to create. + * + * { + * "Effect": "Allow", + * "Principal": { "AWS": " ex: arn:aws:iam::00000000000:root" }, + * "Resource": "*", + * "Action": [ + * "kms:DescribeKey", + * "kms:GenerateDataKeyWithoutPlainText", + * ], + * "Condition": { + * "StringEquals": { + * "kms:ViaService": "ec2..amazonaws.com", (eg: ec2.us-east-1.amazonaws.com) + * "kms:CallerAccount": "0000000000" (your account ID) + * } + * } + * } + * + * @default The default KMS key for the account, region, and EC2 service is used. + */ + readonly encryptionKey?: IKey; + + /** + * Indicates whether the volume is auto-enabled for I/O operations. By default, Amazon EBS disables I/O to the volume from attached EC2 + * instances when it determines that a volume's data is potentially inconsistent. If the consistency of the volume is not a concern, and + * you prefer that the volume be made available immediately if it's impaired, you can configure the volume to automatically enable I/O. + * + * @default false + */ + readonly autoEnableIo?: boolean; + + /** + * The type of the volume; what type of storage to use to form the EBS Volume. + * + * @default {@link EbsDeviceVolumeType.GENERAL_PURPOSE_SSD} + */ + readonly volumeType?: EbsDeviceVolumeType; + + /** + * The number of I/O operations per second (IOPS) to provision for the volume, with a maximum ratio of 50 IOPS/GiB. + * See {@link https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html#EBSVolumeTypes_piops|Provisioned IOPS SSD (io1) volumes} + * for more information. + * + * This parameter is valid only for PROVISIONED_IOPS_SSD volumes. + * + * @default None -- Required for {@link EbsDeviceVolumeType.PROVISIONED_IOPS_SSD} + */ + readonly iops?: number; +} + +/** + * Attributes required to import an existing EBS Volume into the Stack. + */ +export interface VolumeAttributes { + /** + * The EBS Volume's ID + */ + readonly volumeId: string; + + /** + * The availability zone that the EBS Volume is contained within (ex: us-west-2a) + */ + readonly availabilityZone: string; + + /** + * The customer-managed encryption key that is used to encrypt the Volume. + * + * @default None -- The EBS Volume is not using a customer-managed KMS key for encryption. + */ + readonly encryptionKey?: IKey; +} + +/** + * Common behavior of Volumes. Users should not use this class directly, and instead use ``Volume``. + */ +abstract class VolumeBase extends Resource implements IVolume { + public abstract readonly volumeId: string; + public abstract readonly availabilityZone: string; + public abstract readonly encryptionKey?: IKey; + + public grantAttachVolume(grantee: IGrantable, instances?: IInstance[]): Grant { + const result = Grant.addToPrincipal({ + grantee, + actions: [ 'ec2:AttachVolume' ], + resourceArns : this.collectGrantResourceArns(instances), + }); + + if (this.encryptionKey) { + // When attaching a volume, the EC2 Service will need to grant to itself permission + // to be able to decrypt the encryption key. We restrict the CreateGrant for principle + // of least privilege, in accordance with best practices. + // See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html#ebs-encryption-permissions + const kmsGrant: Grant = this.encryptionKey.grant(grantee, 'kms:CreateGrant'); + kmsGrant.principalStatement!.addConditions( + { + Bool: { 'kms:GrantIsForAWSResource': true }, + StringEquals: { + 'kms:ViaService': `ec2.${Stack.of(this).region}.amazonaws.com`, + 'kms:GrantConstraintType': 'EncryptionContextSubset', + }, + }, + ); + } + + return result; + } + + public grantAttachVolumeByResourceTag(grantee: IGrantable, constructs: Construct[], tagKeySuffix?: string): Grant { + const tagKey = `VolumeGrantAttach-${tagKeySuffix ?? this.stringHash(this.node.uniqueId)}`; + const tagValue = this.calculateResourceTagValue(constructs); + const grantCondition: { [key: string]: string } = {}; + grantCondition[`ec2:ResourceTag/${tagKey}`] = tagValue; + + const result = this.grantAttachVolume(grantee); + result.principalStatement!.addCondition( + 'ForAnyValue:StringEquals', grantCondition, + ); + + // The ResourceTag condition requires that all resources involved in the operation have + // the given tag, so we tag this and all constructs given. + Tag.add(this, tagKey, tagValue); + constructs.forEach(construct => Tag.add(construct, tagKey, tagValue)); + + return result; + } + + public grantDetachVolume(grantee: IGrantable, instances?: IInstance[]): Grant { + const result = Grant.addToPrincipal({ + grantee, + actions: [ 'ec2:DetachVolume' ], + resourceArns : this.collectGrantResourceArns(instances), + }); + // Note: No encryption key permissions are required to detach an encrypted volume. + return result; + } + + public grantDetachVolumeByResourceTag(grantee: IGrantable, constructs: Construct[], tagKeySuffix?: string): Grant { + const tagKey = `VolumeGrantDetach-${tagKeySuffix ?? this.stringHash(this.node.uniqueId)}`; + const tagValue = this.calculateResourceTagValue(constructs); + const grantCondition: { [key: string]: string } = {}; + grantCondition[`ec2:ResourceTag/${tagKey}`] = tagValue; + + const result = this.grantDetachVolume(grantee); + result.principalStatement!.addCondition( + 'ForAnyValue:StringEquals', grantCondition, + ); + + // The ResourceTag condition requires that all resources involved in the operation have + // the given tag, so we tag this and all constructs given. + Tag.add(this, tagKey, tagValue); + constructs.forEach(construct => Tag.add(construct, tagKey, tagValue)); + + return result; + } + + private collectGrantResourceArns(instances?: IInstance[]): string[] { + const stack = Stack.of(this); + const resourceArns: string[] = [ + `arn:${stack.partition}:ec2:${stack.region}:${stack.account}:volume/${this.volumeId}`, + ]; + const instanceArnPrefix = `arn:${stack.partition}:ec2:${stack.region}:${stack.account}:instance`; + if (instances) { + instances.forEach(instance => resourceArns.push(`${instanceArnPrefix}/${instance?.instanceId}`)); + } else { + resourceArns.push(`${instanceArnPrefix}/*`); + } + return resourceArns; + } + + private stringHash(value: string): string { + const md5 = crypto.createHash('md5').update(value).digest('hex'); + return md5.slice(0, 8).toUpperCase(); + } + + private calculateResourceTagValue(constructs: Construct[]): string { + const md5 = crypto.createHash('md5'); + constructs.forEach(construct => md5.update(construct.node.uniqueId)); + return md5.digest('hex'); + } +} + +/** + * Creates a new EBS Volume in AWS EC2. + */ +export class Volume extends VolumeBase { + /** + * Import an existing EBS Volume into the Stack. + * + * @param scope the scope of the import. + * @param id the ID of the imported Volume in the construct tree. + * @param attrs the attributes of the imported Volume + */ + public static fromVolumeAttributes(scope: Construct, id: string, attrs: VolumeAttributes): IVolume { + class Import extends VolumeBase { + public readonly volumeId = attrs.volumeId; + public readonly availabilityZone = attrs.availabilityZone; + public readonly encryptionKey = attrs.encryptionKey; + } + // Check that the provided volumeId looks like it could be valid. + if (!Token.isUnresolved(attrs.volumeId) && !/^vol-[0-9a-fA-F]+$/.test(attrs.volumeId)) { + throw new Error('`volumeId` does not match expected pattern. Expected `vol-` (ex: `vol-05abe246af`) or a Token'); + } + return new Import(scope, id); + } + + public readonly volumeId: string; + public readonly availabilityZone: string; + public readonly encryptionKey?: IKey; + + constructor(scope: Construct, id: string, props: VolumeProps) { + super(scope, id, { + physicalName: props.volumeName, + }); + + this.validateProps(props); + + const resource = new CfnVolume(this, 'Resource', { + availabilityZone: props.availabilityZone, + autoEnableIo: props.autoEnableIo, + encrypted: props.encrypted, + kmsKeyId: props.encryptionKey?.keyArn, + iops: props.iops, + multiAttachEnabled: props.enableMultiAttach ?? false, + size: props.size?.toGibibytes({rounding: SizeRoundingBehavior.FAIL}), + snapshotId: props.snapshotId, + volumeType: props.volumeType ?? EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, + }); + + this.volumeId = resource.ref; + this.availabilityZone = props.availabilityZone; + this.encryptionKey = props.encryptionKey; + + if (this.encryptionKey) { + // Per: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html#ebs-encryption-requirements + const principal = + new ViaServicePrincipal(`ec2.${Stack.of(this).region}.amazonaws.com`, new AccountRootPrincipal()).withConditions({ + StringEquals: { + 'kms:CallerAccount': Stack.of(this).account, + }, + }); + const grant = this.encryptionKey.grant(principal, + // Describe & Generate are required to be able to create the CMK-encrypted Volume. + 'kms:DescribeKey', + 'kms:GenerateDataKeyWithoutPlainText', + ); + if (props.snapshotId) { + // ReEncrypt is required for when re-encrypting from an encrypted snapshot. + grant.principalStatement?.addActions('kms:ReEncrypt*'); + } + } + } + + protected validateProps(props: VolumeProps) { + if (!Token.isUnresolved(props.availabilityZone) && !/^[a-z]{2}-[a-z]+-[1-9]+[a-z]$/.test(props.availabilityZone)) { + throw new Error('`availabilityZone` is a region followed by a letter (ex: `us-east-1a`), or a token'); + } + + if (!(props.size || props.snapshotId)) { + throw new Error('Must provide at least one of `size` or `snapshotId`'); + } + + if (props.snapshotId && !Token.isUnresolved(props.snapshotId) && !/^snap-[0-9a-fA-F]+$/.test(props.snapshotId)) { + throw new Error('`snapshotId` does match expected pattern. Expected `snap-` (ex: `snap-05abe246af`) or Token'); + } + + if (props.encryptionKey && !props.encrypted) { + throw new Error('`encrypted` must be true when providing an `encryptionKey`.'); + } + + if (props.iops) { + if (props.volumeType !== EbsDeviceVolumeType.PROVISIONED_IOPS_SSD) { + throw new Error('`iops` may only be specified if the `volumeType` is `PROVISIONED_IOPS_SSD`/`IO1`'); + } + + if (props.iops < 100 || props.iops > 64000) { + throw new Error('`iops` must be in the range 100 to 64,000, inclusive.'); + } + + if (props.size && (props.iops > 50 * props.size.toGibibytes({rounding: SizeRoundingBehavior.FAIL}))) { + throw new Error('`iops` has a maximum ratio of 50 IOPS/GiB.'); + } + } + + if (props.enableMultiAttach && props.volumeType !== EbsDeviceVolumeType.PROVISIONED_IOPS_SSD) { + throw new Error('multi-attach is supported exclusively on `PROVISIONED_IOPS_SSD` volumes.'); + } + + if (props.size) { + const size = props.size.toGibibytes({rounding: SizeRoundingBehavior.FAIL}); + // Enforce maximum volume size: + // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html#ebs-volume-characteristics + const sizeRanges: { [key: string]: { Min: number, Max: number } } = {}; + sizeRanges[EbsDeviceVolumeType.GENERAL_PURPOSE_SSD] = { Min: 1, Max: 16000 }; + sizeRanges[EbsDeviceVolumeType.PROVISIONED_IOPS_SSD] = { Min: 4, Max: 16000 }; + sizeRanges[EbsDeviceVolumeType.THROUGHPUT_OPTIMIZED_HDD] = { Min: 500, Max: 16000 }; + sizeRanges[EbsDeviceVolumeType.COLD_HDD] = { Min: 500, Max: 16000 }; + sizeRanges[EbsDeviceVolumeType.MAGNETIC] = { Min: 1, Max: 1000 }; + const volumeType = props.volumeType ?? EbsDeviceVolumeType.GENERAL_PURPOSE_SSD; + const { Min, Max } = sizeRanges[volumeType]; + if (size < Min || size > Max) { + throw new Error(`\`${volumeType}\` volumes must be between ${Min} GiB and ${Max} GiB in size.`); + } + } + } +} diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index 458f2d1b4bc2e..eadb31460c21d 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -74,6 +74,7 @@ "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-ssm": "0.0.0", @@ -87,6 +88,7 @@ "peerDependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-ssm": "0.0.0", @@ -289,23 +291,6 @@ "docs-public-apis:@aws-cdk/aws-ec2.VpnConnectionProps", "docs-public-apis:@aws-cdk/aws-ec2.VpnTunnelOption", "docs-public-apis:@aws-cdk/aws-ec2.AmazonLinuxStorage", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.NANO", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.MICRO", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.SMALL", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.MEDIUM", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.LARGE", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.XLARGE", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.XLARGE2", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.XLARGE4", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.XLARGE8", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.XLARGE9", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.XLARGE10", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.XLARGE12", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.XLARGE16", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.XLARGE18", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.XLARGE24", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.XLARGE32", - "docs-public-apis:@aws-cdk/aws-ec2.InstanceSize.METAL", "docs-public-apis:@aws-cdk/aws-ec2.OperatingSystemType.LINUX", "docs-public-apis:@aws-cdk/aws-ec2.OperatingSystemType.WINDOWS", "docs-public-apis:@aws-cdk/aws-ec2.Protocol.ALL", diff --git a/packages/@aws-cdk/aws-ec2/test/test.volume.ts b/packages/@aws-cdk/aws-ec2/test/test.volume.ts new file mode 100644 index 0000000000000..dbef8b6ecfebe --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/test.volume.ts @@ -0,0 +1,1487 @@ +import { + expect as cdkExpect, + haveResource, + haveResourceLike, + ResourcePart, +} from '@aws-cdk/assert'; +import { + AccountRootPrincipal, + Role, +} from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import { + AmazonLinuxGeneration, + EbsDeviceVolumeType, + Instance, + InstanceType, + MachineImage, + Volume, + Vpc, +} from '../lib'; + +export = { + 'basic volume'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + }); + + // THEN + cdkExpect(stack).to(haveResource('AWS::EC2::Volume', { + AvailabilityZone: 'us-east-1a', + MultiAttachEnabled: false, + Size: 8, + VolumeType: 'gp2', + }, ResourcePart.Properties)); + + test.done(); + }, + + 'fromVolumeAttributes'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const encryptionKey = new kms.Key(stack, 'Key'); + const volumeId = 'vol-000000'; + const availabilityZone = 'us-east-1a'; + + // WHEN + const volume = Volume.fromVolumeAttributes(stack, 'Volume', { + volumeId, + availabilityZone, + encryptionKey, + }); + + // THEN + test.strictEqual(volume.volumeId, volumeId); + test.strictEqual(volume.availabilityZone, availabilityZone); + test.strictEqual(volume.encryptionKey, encryptionKey); + test.done(); + }, + + 'tagged volume'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const volume = new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + }); + + // WHEN + cdk.Tag.add(volume, 'TagKey', 'TagValue'); + + // THEN + cdkExpect(stack).to(haveResource('AWS::EC2::Volume', { + AvailabilityZone: 'us-east-1a', + MultiAttachEnabled: false, + Size: 8, + VolumeType: 'gp2', + Tags: [{ + Key: 'TagKey', + Value: 'TagValue', + }], + }, ResourcePart.Properties)); + + test.done(); + }, + + 'autoenableIO'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(500), + autoEnableIo: true, + }); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Volume', { + AutoEnableIO: true, + }, ResourcePart.Properties)); + + test.done(); + }, + + 'encryption'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(500), + encrypted: true, + }); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Volume', { + Encrypted: true, + }, ResourcePart.Properties)); + + test.done(); + }, + + 'encryption with kms'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const encryptionKey = new kms.Key(stack, 'Key'); + + // WHEN + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(500), + encrypted: true, + encryptionKey, + }); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Volume', { + Encrypted: true, + KmsKeyId: { + 'Fn::GetAtt': [ + 'Key961B73FD', + 'Arn', + ], + }, + }, ResourcePart.Properties)); + cdkExpect(stack).to(haveResourceLike('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + {}, + { + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + Action: [ + 'kms:DescribeKey', + 'kms:GenerateDataKeyWithoutPlainText', + ], + Condition: { + StringEquals: { + 'kms:ViaService': { + 'Fn::Join': [ + '', + [ + 'ec2.', + { + Ref: 'AWS::Region', + }, + '.amazonaws.com', + ], + ], + }, + 'kms:CallerAccount': { + Ref: 'AWS::AccountId', + }, + }, + }, + }, + ], + }, + })); + + test.done(); + }, + + 'encryption with kms from snapshot'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const encryptionKey = new kms.Key(stack, 'Key'); + + // WHEN + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(500), + encrypted: true, + encryptionKey, + snapshotId: 'snap-1234567890', + }); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + {}, + { + Action: [ + 'kms:DescribeKey', + 'kms:GenerateDataKeyWithoutPlainText', + 'kms:ReEncrypt*', + ], + }, + ], + }, + })); + + test.done(); + }, + + 'iops'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(500), + iops: 500, + volumeType: EbsDeviceVolumeType.PROVISIONED_IOPS_SSD, + }); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Volume', { + Iops: 500, + VolumeType: 'io1', + }, ResourcePart.Properties)); + + test.done(); + }, + + 'multi-attach'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(500), + iops: 500, + volumeType: EbsDeviceVolumeType.PROVISIONED_IOPS_SSD, + enableMultiAttach: true, + }); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Volume', { + MultiAttachEnabled: true, + }, ResourcePart.Properties)); + + test.done(); + }, + + 'snapshotId'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + snapshotId: 'snap-00000000', + }); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Volume', { + SnapshotId: 'snap-00000000', + }, ResourcePart.Properties)); + + test.done(); + }, + + 'volume: standard'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(500), + volumeType: EbsDeviceVolumeType.MAGNETIC, + }); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Volume', { + VolumeType: 'standard', + }, ResourcePart.Properties)); + + test.done(); + }, + + 'volume: io1'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(500), + volumeType: EbsDeviceVolumeType.PROVISIONED_IOPS_SSD, + }); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Volume', { + VolumeType: 'io1', + }, ResourcePart.Properties)); + + test.done(); + }, + + 'volume: gp2'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(500), + volumeType: EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, + }); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Volume', { + VolumeType: 'gp2', + }, ResourcePart.Properties)); + + test.done(); + }, + + 'volume: st1'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(500), + volumeType: EbsDeviceVolumeType.THROUGHPUT_OPTIMIZED_HDD, + }); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Volume', { + VolumeType: 'st1', + }, ResourcePart.Properties)); + + test.done(); + }, + + 'volume: sc1'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(500), + volumeType: EbsDeviceVolumeType.COLD_HDD, + }); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Volume', { + VolumeType: 'sc1', + }, ResourcePart.Properties)); + + test.done(); + }, + + 'grantAttachVolume to any instance'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const role = new Role(stack, 'Role', { assumedBy: new AccountRootPrincipal() }); + const volume = new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + }); + + // WHEN + volume.grantAttachVolume(role); + + // THEN + cdkExpect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: 'ec2:AttachVolume', + Effect: 'Allow', + Resource: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ec2:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':volume/', + { + Ref: 'VolumeA92988D3', + }, + ], + ], + }, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ec2:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':instance/*', + ], + ], + }, + ], + }], + }, + })); + test.done(); + }, + + 'grantAttachVolume to any instance with encryption'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const role = new Role(stack, 'Role', { assumedBy: new AccountRootPrincipal() }); + const encryptionKey = new kms.Key(stack, 'Key'); + const volume = new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + encrypted: true, + encryptionKey, + }); + + // WHEN + volume.grantAttachVolume(role); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + {}, + {}, + { + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::GetAtt': [ + 'Role1ABCC5F0', + 'Arn', + ], + }, + }, + Action: 'kms:CreateGrant', + Condition: { + Bool: { + 'kms:GrantIsForAWSResource': true, + }, + StringEquals: { + 'kms:ViaService': { + 'Fn::Join': [ + '', + [ + 'ec2.', + { + Ref: 'AWS::Region', + }, + '.amazonaws.com', + ], + ], + }, + 'kms:GrantConstraintType': 'EncryptionContextSubset', + }, + }, + Resource: '*', + }, + ], + }, + })); + + test.done(); + }, + + 'grantAttachVolume to any instance with KMS.fromKeyArn() encryption'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const role = new Role(stack, 'Role', { assumedBy: new AccountRootPrincipal() }); + const kmsKey = new kms.Key(stack, 'Key'); + // kmsKey policy is not strictly necessary for the test. + // Demonstrating how to properly construct the Key. + const principal = + new kms.ViaServicePrincipal(`ec2.${stack.region}.amazonaws.com`, new AccountRootPrincipal()).withConditions({ + StringEquals: { + 'kms:CallerAccount': stack.account, + }, + }); + kmsKey.grant(principal, + // Describe & Generate are required to be able to create the CMK-encrypted Volume. + 'kms:DescribeKey', + 'kms:GenerateDataKeyWithoutPlainText', + // ReEncrypt is required for when the CMK is rotated. + 'kms:ReEncrypt*', + ); + + const encryptionKey = kms.Key.fromKeyArn(stack, 'KeyArn', kmsKey.keyArn); + const volume = new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + encrypted: true, + encryptionKey, + }); + + // WHEN + volume.grantAttachVolume(role); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + {}, + { + Effect: 'Allow', + Action: 'kms:CreateGrant', + Resource: { + 'Fn::GetAtt': [ + 'Key961B73FD', + 'Arn', + ], + }, + Condition: { + Bool: { + 'kms:GrantIsForAWSResource': true, + }, + StringEquals: { + 'kms:ViaService': { + 'Fn::Join': [ + '', + [ + 'ec2.', + { + Ref: 'AWS::Region', + }, + '.amazonaws.com', + ], + ], + }, + 'kms:GrantConstraintType': 'EncryptionContextSubset', + }, + }, + }, + ], + }, + })); + + test.done(); + }, + + 'grantAttachVolume to specific instances'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const role = new Role(stack, 'Role', { assumedBy: new AccountRootPrincipal() }); + const vpc = new Vpc(stack, 'Vpc'); + const instance1 = new Instance(stack, 'Instance1', { + vpc, + instanceType: new InstanceType('t3.small'), + machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2 }), + availabilityZone: 'us-east-1a', + }); + const instance2 = new Instance(stack, 'Instance2', { + vpc, + instanceType: new InstanceType('t3.small'), + machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2 }), + availabilityZone: 'us-east-1a', + }); + const volume = new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + }); + + // WHEN + volume.grantAttachVolume(role, [instance1, instance2]); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: 'ec2:AttachVolume', + Effect: 'Allow', + Resource: [ + {}, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ec2:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':instance/', + { + Ref: 'Instance14BC3991D', + }, + ], + ], + }, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ec2:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':instance/', + { + Ref: 'Instance255F35265', + }, + ], + ], + }, + ], + }], + }, + })); + + test.done(); + }, + + 'grantAttachVolume to instance self'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new Vpc(stack, 'Vpc'); + const instance = new Instance(stack, 'Instance', { + vpc, + instanceType: new InstanceType('t3.small'), + machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2 }), + availabilityZone: 'us-east-1a', + }); + const volume = new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + }); + + // WHEN + volume.grantAttachVolumeByResourceTag(instance.grantPrincipal, [instance]); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: 'ec2:AttachVolume', + Effect: 'Allow', + Resource: [ + {}, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ec2:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':instance/*', + ], + ], + }, + ], + Condition: { + 'ForAnyValue:StringEquals': { + 'ec2:ResourceTag/VolumeGrantAttach-BD7A9717': 'd9a17c1c9e8ef6866e4dbeef41c741b2', + }, + }, + }], + }, + })); + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Volume', { + Tags: [ + { + Key: 'VolumeGrantAttach-BD7A9717', + Value: 'd9a17c1c9e8ef6866e4dbeef41c741b2', + }, + ], + }, ResourcePart.Properties)); + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Instance', { + Tags: [ + {}, + { + Key: 'VolumeGrantAttach-BD7A9717', + Value: 'd9a17c1c9e8ef6866e4dbeef41c741b2', + }, + ], + }, ResourcePart.Properties)); + + test.done(); + }, + + 'grantAttachVolume to instance self with suffix'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new Vpc(stack, 'Vpc'); + const instance = new Instance(stack, 'Instance', { + vpc, + instanceType: new InstanceType('t3.small'), + machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2 }), + availabilityZone: 'us-east-1a', + }); + const volume = new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + }); + + // WHEN + volume.grantAttachVolumeByResourceTag(instance.grantPrincipal, [instance], 'TestSuffix'); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: 'ec2:AttachVolume', + Effect: 'Allow', + Resource: [ + {}, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ec2:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':instance/*', + ], + ], + }, + ], + Condition: { + 'ForAnyValue:StringEquals': { + 'ec2:ResourceTag/VolumeGrantAttach-TestSuffix': 'd9a17c1c9e8ef6866e4dbeef41c741b2', + }, + }, + }], + }, + })); + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Volume', { + Tags: [ + { + Key: 'VolumeGrantAttach-TestSuffix', + Value: 'd9a17c1c9e8ef6866e4dbeef41c741b2', + }, + ], + }, ResourcePart.Properties)); + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Instance', { + Tags: [ + {}, + { + Key: 'VolumeGrantAttach-TestSuffix', + Value: 'd9a17c1c9e8ef6866e4dbeef41c741b2', + }, + ], + }, ResourcePart.Properties)); + + test.done(); + }, + + 'grantDetachVolume to any instance'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const role = new Role(stack, 'Role', { assumedBy: new AccountRootPrincipal() }); + const volume = new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + }); + + // WHEN + volume.grantDetachVolume(role); + + // THEN + cdkExpect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: 'ec2:DetachVolume', + Effect: 'Allow', + Resource: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ec2:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':volume/', + { + Ref: 'VolumeA92988D3', + }, + ], + ], + }, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ec2:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':instance/*', + ], + ], + }, + ], + }], + }, + })); + test.done(); + }, + + 'grantDetachVolume from specific instance'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const role = new Role(stack, 'Role', { assumedBy: new AccountRootPrincipal() }); + const vpc = new Vpc(stack, 'Vpc'); + const instance1 = new Instance(stack, 'Instance1', { + vpc, + instanceType: new InstanceType('t3.small'), + machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2 }), + availabilityZone: 'us-east-1a', + }); + const instance2 = new Instance(stack, 'Instance2', { + vpc, + instanceType: new InstanceType('t3.small'), + machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2 }), + availabilityZone: 'us-east-1a', + }); + const volume = new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + }); + + // WHEN + volume.grantDetachVolume(role, [instance1, instance2]); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: 'ec2:DetachVolume', + Effect: 'Allow', + Resource: [ + {}, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ec2:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':instance/', + { + Ref: 'Instance14BC3991D', + }, + ], + ], + }, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ec2:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':instance/', + { + Ref: 'Instance255F35265', + }, + ], + ], + }, + ], + }], + }, + })); + + test.done(); + }, + + 'grantDetachVolume from instance self'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new Vpc(stack, 'Vpc'); + const instance = new Instance(stack, 'Instance', { + vpc, + instanceType: new InstanceType('t3.small'), + machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2 }), + availabilityZone: 'us-east-1a', + }); + const volume = new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + }); + + // WHEN + volume.grantDetachVolumeByResourceTag(instance.grantPrincipal, [instance]); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: 'ec2:DetachVolume', + Effect: 'Allow', + Resource: [ + {}, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ec2:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':instance/*', + ], + ], + }, + ], + Condition: { + 'ForAnyValue:StringEquals': { + 'ec2:ResourceTag/VolumeGrantDetach-BD7A9717': 'd9a17c1c9e8ef6866e4dbeef41c741b2', + }, + }, + }], + }, + })); + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Volume', { + Tags: [ + { + Key: 'VolumeGrantDetach-BD7A9717', + Value: 'd9a17c1c9e8ef6866e4dbeef41c741b2', + }, + ], + }, ResourcePart.Properties)); + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Instance', { + Tags: [ + {}, + { + Key: 'VolumeGrantDetach-BD7A9717', + Value: 'd9a17c1c9e8ef6866e4dbeef41c741b2', + }, + ], + }, ResourcePart.Properties)); + + test.done(); + }, + + 'grantDetachVolume from instance self with suffix'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new Vpc(stack, 'Vpc'); + const instance = new Instance(stack, 'Instance', { + vpc, + instanceType: new InstanceType('t3.small'), + machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2 }), + availabilityZone: 'us-east-1a', + }); + const volume = new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + }); + + // WHEN + volume.grantDetachVolumeByResourceTag(instance.grantPrincipal, [instance], 'TestSuffix'); + + // THEN + cdkExpect(stack).to(haveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: 'ec2:DetachVolume', + Effect: 'Allow', + Resource: [ + {}, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ec2:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':instance/*', + ], + ], + }, + ], + Condition: { + 'ForAnyValue:StringEquals': { + 'ec2:ResourceTag/VolumeGrantDetach-TestSuffix': 'd9a17c1c9e8ef6866e4dbeef41c741b2', + }, + }, + }], + }, + })); + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Volume', { + Tags: [ + { + Key: 'VolumeGrantDetach-TestSuffix', + Value: 'd9a17c1c9e8ef6866e4dbeef41c741b2', + }, + ], + }, ResourcePart.Properties)); + cdkExpect(stack).to(haveResourceLike('AWS::EC2::Instance', { + Tags: [ + {}, + { + Key: 'VolumeGrantDetach-TestSuffix', + Value: 'd9a17c1c9e8ef6866e4dbeef41c741b2', + }, + ], + }, ResourcePart.Properties)); + + test.done(); + }, + + 'validation fromVolumeAttributes'(test: Test) { + // GIVEN + let idx: number = 0; + const stack = new cdk.Stack(); + const volume = new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + }); + + // THEN + test.doesNotThrow(() => { + Volume.fromVolumeAttributes(stack, `Volume${idx++}`, { + volumeId: volume.volumeId, + availabilityZone: volume.availabilityZone, + }); + }); + test.doesNotThrow(() => { + Volume.fromVolumeAttributes(stack, `Volume${idx++}`, { + volumeId: 'vol-0123456789abcdefABCDEF', + availabilityZone: 'us-east-1a', + }); + }); + test.throws(() => { + Volume.fromVolumeAttributes(stack, `Volume${idx++}`, { + volumeId: ' vol-0123456789abcdefABCDEF', // leading invalid character(s) + availabilityZone: 'us-east-1a', + }); + }, Error, '`volumeId` does not match expected pattern. Expected `vol-` (ex: `vol-05abe246af`) or a Token'); + test.throws(() => { + Volume.fromVolumeAttributes(stack, `Volume${idx++}`, { + volumeId: 'vol-0123456789abcdefABCDEF ', // trailing invalid character(s) + availabilityZone: 'us-east-1a', + }); + }, Error, '`volumeId` does not match expected pattern. Expected `vol-` (ex: `vol-05abe246af`) or a Token'); + test.done(); + }, + + 'validation required props'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const key = new kms.Key(stack, 'Key'); + let idx: number = 0; + + // THEN + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + }); + }, Error, 'Must provide at least one of `size` or `snapshotId`'); + test.doesNotThrow(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + }); + }); + test.doesNotThrow(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + snapshotId: 'snap-000000000', + }); + }); + test.doesNotThrow(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + snapshotId: 'snap-000000000', + }); + }); + + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + encryptionKey: key, + }); + }, Error, '`encrypted` must be true when providing an `encryptionKey`.'); + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + encrypted: false, + encryptionKey: key, + }); + }, Error, '`encrypted` must be true when providing an `encryptionKey`.'); + test.doesNotThrow(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + encrypted: true, + encryptionKey: key, + }); + }); + + test.done(); + }, + + 'validation availabilityZone'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const volume = new Volume(stack, 'ForToken', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + }); + let idx: number = 0; + + // THEN + test.doesNotThrow(() => { + // Should not throw if we provide a token for the AZ + new Volume(stack, `Volume${idx++}`, { + availabilityZone: volume.volumeId, + size: cdk.Size.gibibytes(8), + }); + }); + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1', + }); + }, Error, '`availabilityZone` is a region followed by a letter (ex: `us-east-1a`), or a token'); + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'Virginia', + }); + }, Error, '`availabilityZone` is a region followed by a letter (ex: `us-east-1a`), or a token'); + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: ' us-east-1a', // leading character(s) + }); + }, Error, '`availabilityZone` is a region followed by a letter (ex: `us-east-1a`), or a token'); + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a ', // trailing character(s) + }); + }, Error, '`availabilityZone` is a region followed by a letter (ex: `us-east-1a`), or a token'); + + test.done(); + }, + + 'validation snapshotId'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const volume = new Volume(stack, 'ForToken', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(8), + }); + let idx: number = 0; + + // THEN + test.doesNotThrow(() => { + // Should not throw if we provide a Token for the snapshotId + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + snapshotId: volume.volumeId, + }); + }); + test.doesNotThrow(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + snapshotId: 'snap-0123456789abcdefABCDEF', + }); + }); + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + snapshotId: ' snap-1234', // leading extra character(s) + }); + }, Error, '`snapshotId` does match expected pattern. Expected `snap-` (ex: `snap-05abe246af`) or Token'); + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + snapshotId: 'snap-1234 ', // trailing extra character(s) + }); + }, Error, '`snapshotId` does match expected pattern. Expected `snap-` (ex: `snap-05abe246af`) or Token'); + + test.done(); + }, + + 'validation iops'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + let idx: number = 0; + + // THEN + // Test: Type of volume + for (const volumeType of [ + EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, + EbsDeviceVolumeType.THROUGHPUT_OPTIMIZED_HDD, + EbsDeviceVolumeType.COLD_HDD, + EbsDeviceVolumeType.MAGNETIC, + ]) { + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(500), + iops: 100, + volumeType, + }); + }, Error, '`iops` may only be specified if the `volumeType` is `PROVISIONED_IOPS_SSD`/`IO1`'); + } + + // Test: iops in range + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(10), + iops: 99, + volumeType: EbsDeviceVolumeType.PROVISIONED_IOPS_SSD, + }); + }, Error, '`iops` must be in the range 100 to 64,000, inclusive.'); + test.doesNotThrow(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(10), + iops: 100, + volumeType: EbsDeviceVolumeType.PROVISIONED_IOPS_SSD, + }); + }); + test.doesNotThrow(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(1300), + iops: 64000, + volumeType: EbsDeviceVolumeType.PROVISIONED_IOPS_SSD, + }); + }); + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(1300), + iops: 64001, + volumeType: EbsDeviceVolumeType.PROVISIONED_IOPS_SSD, + }); + }, Error, '`iops` must be in the range 100 to 64,000, inclusive.'); + + // Test: iops ratio + test.doesNotThrow(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(10), + iops: 500, + volumeType: EbsDeviceVolumeType.PROVISIONED_IOPS_SSD, + }); + }); + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(10), + iops: 501, + volumeType: EbsDeviceVolumeType.PROVISIONED_IOPS_SSD, + }); + }, Error, '`iops` has a maximum ratio of 50 IOPS/GiB.'); + + test.done(); + }, + + 'validation multi-attach'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + let idx: number = 0; + + // THEN + for (const volumeType of [ + EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, + EbsDeviceVolumeType.THROUGHPUT_OPTIMIZED_HDD, + EbsDeviceVolumeType.COLD_HDD, + EbsDeviceVolumeType.MAGNETIC, + ]) { + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(500), + iops: 100, + enableMultiAttach: true, + volumeType, + }); + }, Error, 'multi-attach is supported exclusively on `PROVISIONED_IOPS_SSD` volumes.'); + } + + test.done(); + }, + + 'validation size in range'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + let idx: number = 0; + + // THEN + for (const testData of [ + [EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, 1, 16000], + [EbsDeviceVolumeType.PROVISIONED_IOPS_SSD, 4, 16000], + [EbsDeviceVolumeType.THROUGHPUT_OPTIMIZED_HDD, 500, 16000], + [EbsDeviceVolumeType.COLD_HDD, 500, 16000], + [EbsDeviceVolumeType.MAGNETIC, 1, 1000], + ]) { + const volumeType = testData[0] as EbsDeviceVolumeType; + const min = testData[1] as number; + const max = testData[2] as number; + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(min - 1), + volumeType, + }); + }, Error, `\`${volumeType}\` volumes must be between ${min} GiB and ${max} GiB in size.`); + test.doesNotThrow(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(min), + volumeType, + }); + }); + test.doesNotThrow(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(max), + volumeType, + }); + }); + test.throws(() => { + new Volume(stack, `Volume${idx++}`, { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(max + 1), + volumeType, + }); + }, Error, `\`${volumeType}\` volumes must be between ${min} GiB and ${max} GiB in size.`); + } + + test.done(); + }, + +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts index 17693856ed8fd..09f786c2ea760 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts @@ -436,7 +436,7 @@ export = { Type: 'A', AliasTarget: { HostedZoneId: { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'CanonicalHostedZoneID'] }, - DNSName: { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'DNSName'] }, + DNSName: { 'Fn::Join': ['', [ 'dualstack.', { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'DNSName'] } ] ] }, }, })); @@ -501,7 +501,7 @@ export = { Type: 'A', AliasTarget: { HostedZoneId: { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'CanonicalHostedZoneID'] }, - DNSName: { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'DNSName'] }, + DNSName: { 'Fn::Join': [ '', [ 'dualstack.', { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'DNSName'] } ] ] }, }, })); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.expected.json index 3f560edd9c37a..e7434c8e2eafc 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.expected.json @@ -618,9 +618,18 @@ "Type": "A", "AliasTarget": { "DNSName": { - "Fn::GetAtt": [ - "myServiceLB168895E1", - "DNSName" + "Fn::Join": + [ + "", + [ + "dualstack.", + { + "Fn::GetAtt": [ + "myServiceLB168895E1", + "DNSName" + ] + } + ] ] }, "HostedZoneId": { diff --git a/packages/@aws-cdk/aws-efs/README.md b/packages/@aws-cdk/aws-efs/README.md index ec2ff3352755a..01d766117a6b1 100644 --- a/packages/@aws-cdk/aws-efs/README.md +++ b/packages/@aws-cdk/aws-efs/README.md @@ -45,12 +45,10 @@ and group override any identity information provided by the NFS client. The file access point's root directory. Applications using the access point can only access data in its own directory and below. To learn more, see [Mounting a File System Using EFS Access Points](https://docs.aws.amazon.com/efs/latest/ug/efs-access-points.html). -Use `AccessPoint` to create an access point: +Use `addAccessPoint` to create an access point from a fileSystem: ```ts -new AccessPoint(stack, 'AccessPoint', { - fileSystem -}); +fileSystem.addAccessPoint('AccessPoint'); ``` By default, when you create an access point, the root(`/`) directory is exposed to the client connecting to diff --git a/packages/@aws-cdk/aws-efs/lib/access-point.ts b/packages/@aws-cdk/aws-efs/lib/access-point.ts index 39c9fabb68a04..6fd4b6e927bce 100644 --- a/packages/@aws-cdk/aws-efs/lib/access-point.ts +++ b/packages/@aws-cdk/aws-efs/lib/access-point.ts @@ -65,14 +65,9 @@ export interface PosixUser { } /** - * Properties for the AccessPoint + * Options to create an AccessPoint */ -export interface AccessPointProps { - /** - * The efs filesystem - */ - readonly fileSystem: IFileSystem; - +export interface AccessPointOptions { /** * Specifies the POSIX IDs and permissions to apply when creating the access point's root directory. If the * root directory specified by `path` does not exist, EFS creates the root directory and applies the @@ -103,6 +98,16 @@ export interface AccessPointProps { readonly posixUser?: PosixUser; } +/** + * Properties for the AccessPoint + */ +export interface AccessPointProps extends AccessPointOptions { + /** + * The efs filesystem + */ + readonly fileSystem: IFileSystem; +} + /** * Represents the AccessPoint */ @@ -137,7 +142,7 @@ export class AccessPoint extends Resource implements IAccessPoint { constructor(scope: Construct, id: string, props: AccessPointProps) { super(scope, id); - const resource = new CfnAccessPoint(scope, 'Resource', { + const resource = new CfnAccessPoint(this, 'Resource', { fileSystemId: props.fileSystem.fileSystemId, rootDirectory: { creationInfo: props.createAcl ? { 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 aa42acb2b6372..35c6ef381a8d5 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,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import { Construct, IResource, RemovalPolicy, Resource, Size, Tag } from '@aws-cdk/core'; +import { AccessPoint, AccessPointOptions } from './access-point'; import { CfnFileSystem, CfnMountTarget } from './efs.generated'; // tslint:disable:max-line-length @@ -272,4 +273,14 @@ export class FileSystem extends Resource implements IFileSystem { }); }); } + + /** + * create access point from this filesystem + */ + public addAccessPoint(id: string, accessPointOptions: AccessPointOptions = {}): AccessPoint { + return new AccessPoint(this, id, { + fileSystem: this, + ...accessPointOptions, + }); + } } diff --git a/packages/@aws-cdk/aws-efs/test/access-point.test.ts b/packages/@aws-cdk/aws-efs/test/access-point.test.ts index 6b7343b1e5f52..761594507c779 100644 --- a/packages/@aws-cdk/aws-efs/test/access-point.test.ts +++ b/packages/@aws-cdk/aws-efs/test/access-point.test.ts @@ -15,7 +15,14 @@ beforeEach(() => { }); }); -test('default access point is created correctly', () => { +test('addAccessPoint correctly', () => { + // WHEN + fileSystem.addAccessPoint('MyAccessPoint'); + // THEN + expectCDK(stack).to(haveResource('AWS::EFS::AccessPoint')); +}); + +test('new AccessPoint correctly', () => { // WHEN new AccessPoint(stack, 'MyAccessPoint', { fileSystem, diff --git a/packages/@aws-cdk/aws-efs/test/integ.efs.expected.json b/packages/@aws-cdk/aws-efs/test/integ.efs.expected.json index f5eeff3153703..8c1fa47948791 100644 --- a/packages/@aws-cdk/aws-efs/test/integ.efs.expected.json +++ b/packages/@aws-cdk/aws-efs/test/integ.efs.expected.json @@ -542,7 +542,7 @@ } } }, - "Resource": { + "FileSystemAccessPointF8178182": { "Type": "AWS::EFS::AccessPoint", "Properties": { "FileSystemId": { diff --git a/packages/@aws-cdk/aws-efs/test/integ.efs.ts b/packages/@aws-cdk/aws-efs/test/integ.efs.ts index 3906e374b8130..0afad3e6f4a29 100644 --- a/packages/@aws-cdk/aws-efs/test/integ.efs.ts +++ b/packages/@aws-cdk/aws-efs/test/integ.efs.ts @@ -1,6 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; -import { AccessPoint, FileSystem } from '../lib'; +import { FileSystem } from '../lib'; const app = new cdk.App(); const stack = new cdk.Stack(app, 'test-efs-integ'); @@ -11,8 +11,7 @@ const fileSystem = new FileSystem(stack, 'FileSystem', { vpc, }); -new AccessPoint(stack, 'AccessPoint', { - fileSystem, +fileSystem.addAccessPoint('AccessPoint', { createAcl: { ownerGid: '1000', ownerUid: '1000', @@ -23,4 +22,4 @@ new AccessPoint(stack, 'AccessPoint', { gid: '1000', uid: '1000', }, -}); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/lib/addons/neuron-device-plugin.yaml b/packages/@aws-cdk/aws-eks/lib/addons/neuron-device-plugin.yaml new file mode 100644 index 0000000000000..48d66be1c8343 --- /dev/null +++ b/packages/@aws-cdk/aws-eks/lib/addons/neuron-device-plugin.yaml @@ -0,0 +1,66 @@ +# source: https://github.com/aws/aws-neuron-sdk/blob/master/docs/neuron-container-tools/k8s-neuron-device-plugin.yml +# https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: neuron-device-plugin-daemonset + namespace: kube-system +spec: + selector: + matchLabels: + name: neuron-device-plugin-ds + updateStrategy: + type: RollingUpdate + template: + metadata: + annotations: + scheduler.alpha.kubernetes.io/critical-pod: "" + labels: + name: neuron-device-plugin-ds + spec: + tolerations: + - key: CriticalAddonsOnly + operator: Exists + - key: aws.amazon.com/neuron + operator: Exists + effect: NoSchedule + # Mark this pod as a critical add-on; when enabled, the critical add-on + # scheduler reserves resources for critical add-on pods so that they can + # be rescheduled after a failure. + # See https://kubernetes.io/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods/ + priorityClassName: "system-node-critical" + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: "beta.kubernetes.io/instance-type" + operator: In + values: + - inf1.xlarge + - inf1.2xlarge + - inf1.6xlarge + - inf1.4xlarge + - matchExpressions: + - key: "node.kubernetes.io/instance-type" + operator: In + values: + - inf1.xlarge + - inf1.2xlarge + - inf1.6xlarge + - inf1.24xlarge + containers: + - image: 790709498068.dkr.ecr.us-west-2.amazonaws.com/neuron-device-plugin:1.0.9043.0 + imagePullPolicy: Always + name: k8s-neuron-device-plugin-ctr + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + volumeMounts: + - name: device-plugin + mountPath: /var/lib/kubelet/device-plugins + volumes: + - name: device-plugin + hostPath: + path: /var/lib/kubelet/device-plugins \ 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 491d357387073..5cf344f067f75 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -3,6 +3,9 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as ssm from '@aws-cdk/aws-ssm'; import { CfnOutput, Construct, IResource, Resource, Stack, Tag, Token } from '@aws-cdk/core'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as YAML from 'yaml'; import { AwsAuth } from './aws-auth'; import { clusterArnComponents, ClusterResource } from './cluster-resource'; import { CfnCluster, CfnClusterProps } from './eks.generated'; @@ -385,6 +388,8 @@ export class Cluster extends Resource implements ICluster { private _spotInterruptHandler?: HelmChart; + private _neuronDevicePlugin?: KubernetesResource; + private readonly version: string | undefined; /** @@ -537,6 +542,10 @@ export class Cluster extends Resource implements ICluster { machineImageType: options.machineImageType, }); + if (nodeTypeForInstanceType(options.instanceType) === NodeType.INFERENTIA) { + this.addNeuronDevicePlugin(); + } + return asg; } @@ -834,6 +843,20 @@ export class Cluster extends Resource implements ICluster { return this._spotInterruptHandler; } + /** + * Installs the Neuron device plugin on the cluster if it's not + * already added. + */ + private addNeuronDevicePlugin() { + if (!this._neuronDevicePlugin) { + const fileContents = fs.readFileSync(path.join(__dirname, 'addons/neuron-device-plugin.yaml'), 'utf8'); + const sanitized = YAML.parse(fileContents); + this._neuronDevicePlugin = this.addResource('NeuronDevicePlugin', sanitized); + } + + return this._neuronDevicePlugin; + } + /** * Opportunistically tag subnets with the required tags. * @@ -1112,6 +1135,7 @@ export class EksOptimizedImage implements ec2.IMachineImage { this.amiParameterName = `/aws/service/eks/optimized-ami/${this.kubernetesVersion}/` + ( this.nodeType === NodeType.STANDARD ? 'amazon-linux-2/' : '' ) + ( this.nodeType === NodeType.GPU ? 'amazon-linux-2-gpu/' : '' ) + + (this.nodeType === NodeType.INFERENTIA ? 'amazon-linux-2-gpu/' : '') + 'recommended/image_id'; } @@ -1176,6 +1200,11 @@ export enum NodeType { * GPU instances */ GPU = 'GPU', + + /** + * Inferentia instances + */ + INFERENTIA = 'INFERENTIA', } /** @@ -1222,7 +1251,10 @@ export enum MachineImageType { } const GPU_INSTANCETYPES = ['p2', 'p3', 'g4']; +const INFERENTIA_INSTANCETYPES = ['inf1']; function nodeTypeForInstanceType(instanceType: ec2.InstanceType) { - return GPU_INSTANCETYPES.includes(instanceType.toString().substring(0, 2)) ? NodeType.GPU : NodeType.STANDARD; + return GPU_INSTANCETYPES.includes(instanceType.toString().substring(0, 2)) ? NodeType.GPU : + INFERENTIA_INSTANCETYPES.includes(instanceType.toString().substring(0, 4)) ? NodeType.INFERENTIA : + NodeType.STANDARD; } diff --git a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts index 8d5b50d870151..4761e71b0c7a9 100644 --- a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts +++ b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts @@ -100,7 +100,7 @@ export class HelmChart extends Construct { Chart: props.chart, Version: props.version, Wait: props.wait ?? false, - Timeout: timeout, + Timeout: timeout ? `${timeout.toString()}s` : undefined, // Helm v3 expects duration instead of integer Values: (props.values ? stack.toJsonString(props.values) : undefined), Namespace: props.namespace ?? 'default', Repository: props.repository, diff --git a/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts b/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts index dd4f2e27f910a..5c144e42abf97 100644 --- a/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts +++ b/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts @@ -24,7 +24,7 @@ export class KubectlProvider extends NestedStack { runtime: lambda.Runtime.PYTHON_3_7, handler: 'index.handler', timeout: Duration.minutes(15), - layers: [ KubectlLayer.getOrCreate(this, { version: '2.0.0-beta2' }) ], + layers: [ KubectlLayer.getOrCreate(this, { version: '2.0.0' }) ], memorySize: 256, }); diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index 9a7d145f01c93..df01d4bdbe553 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -64,7 +64,8 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.703.0", + "@types/yaml": "1.2.0", + "aws-sdk": "^2.706.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", @@ -80,8 +81,12 @@ "@aws-cdk/aws-ssm": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/custom-resources": "0.0.0", - "constructs": "^3.0.2" + "constructs": "^3.0.2", + "yaml": "1.10.0" }, + "bundledDependencies": [ + "yaml" + ], "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { "@aws-cdk/aws-autoscaling": "0.0.0", diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json index 4278bee450139..dd43e4cf0204d 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 @@ -28,7 +28,7 @@ } } }, - "ClusterDefaultVpcFA9F2722": { + "Vpc8378EB38": { "Type": "AWS::EC2::VPC", "Properties": { "CidrBlock": "10.0.0.0/16", @@ -38,17 +38,17 @@ "Tags": [ { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc" + "Value": "aws-cdk-eks-cluster-test/Vpc" } ] } }, - "ClusterDefaultVpcPublicSubnet1Subnet3BFE1BDA": { + "VpcPublicSubnet1Subnet5C2D37C4": { "Type": "AWS::EC2::Subnet", "Properties": { "CidrBlock": "10.0.0.0/19", "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" }, "AvailabilityZone": "test-region-1a", "MapPublicIpOnLaunch": true, @@ -67,16 +67,16 @@ }, { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PublicSubnet1" + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet1" } ] } }, - "ClusterDefaultVpcPublicSubnet1RouteTable1DCCDD98": { + "VpcPublicSubnet1RouteTable6C95E38E": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" }, "Tags": [ { @@ -85,38 +85,38 @@ }, { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PublicSubnet1" + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet1" } ] } }, - "ClusterDefaultVpcPublicSubnet1RouteTableAssociationAFBE6789": { + "VpcPublicSubnet1RouteTableAssociation97140677": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "ClusterDefaultVpcPublicSubnet1RouteTable1DCCDD98" + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" }, "SubnetId": { - "Ref": "ClusterDefaultVpcPublicSubnet1Subnet3BFE1BDA" + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" } } }, - "ClusterDefaultVpcPublicSubnet1DefaultRouteCF22EF6E": { + "VpcPublicSubnet1DefaultRoute3DA9E72A": { "Type": "AWS::EC2::Route", "Properties": { "RouteTableId": { - "Ref": "ClusterDefaultVpcPublicSubnet1RouteTable1DCCDD98" + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { - "Ref": "ClusterDefaultVpcIGW756BE43E" + "Ref": "VpcIGWD7BA715C" } }, "DependsOn": [ - "ClusterDefaultVpcVPCGWC1D00388" + "VpcVPCGWBF912B6E" ] }, - "ClusterDefaultVpcPublicSubnet1EIP498E2BD2": { + "VpcPublicSubnet1EIPD7E02669": { "Type": "AWS::EC2::EIP", "Properties": { "Domain": "vpc", @@ -127,22 +127,22 @@ }, { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PublicSubnet1" + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet1" } ] } }, - "ClusterDefaultVpcPublicSubnet1NATGateway6E21013E": { + "VpcPublicSubnet1NATGateway4D7517AA": { "Type": "AWS::EC2::NatGateway", "Properties": { "AllocationId": { "Fn::GetAtt": [ - "ClusterDefaultVpcPublicSubnet1EIP498E2BD2", + "VpcPublicSubnet1EIPD7E02669", "AllocationId" ] }, "SubnetId": { - "Ref": "ClusterDefaultVpcPublicSubnet1Subnet3BFE1BDA" + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" }, "Tags": [ { @@ -151,17 +151,17 @@ }, { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PublicSubnet1" + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet1" } ] } }, - "ClusterDefaultVpcPublicSubnet2SubnetC4E9A966": { + "VpcPublicSubnet2Subnet691E08A3": { "Type": "AWS::EC2::Subnet", "Properties": { "CidrBlock": "10.0.32.0/19", "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" }, "AvailabilityZone": "test-region-1b", "MapPublicIpOnLaunch": true, @@ -180,16 +180,16 @@ }, { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PublicSubnet2" + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet2" } ] } }, - "ClusterDefaultVpcPublicSubnet2RouteTable6F1F5F47": { + "VpcPublicSubnet2RouteTable94F7E489": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" }, "Tags": [ { @@ -198,83 +198,43 @@ }, { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PublicSubnet2" + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet2" } ] } }, - "ClusterDefaultVpcPublicSubnet2RouteTableAssociationA8539C50": { + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "ClusterDefaultVpcPublicSubnet2RouteTable6F1F5F47" + "Ref": "VpcPublicSubnet2RouteTable94F7E489" }, "SubnetId": { - "Ref": "ClusterDefaultVpcPublicSubnet2SubnetC4E9A966" + "Ref": "VpcPublicSubnet2Subnet691E08A3" } } }, - "ClusterDefaultVpcPublicSubnet2DefaultRoute1FA8621E": { + "VpcPublicSubnet2DefaultRoute97F91067": { "Type": "AWS::EC2::Route", "Properties": { "RouteTableId": { - "Ref": "ClusterDefaultVpcPublicSubnet2RouteTable6F1F5F47" + "Ref": "VpcPublicSubnet2RouteTable94F7E489" }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { - "Ref": "ClusterDefaultVpcIGW756BE43E" + "Ref": "VpcIGWD7BA715C" } }, "DependsOn": [ - "ClusterDefaultVpcVPCGWC1D00388" + "VpcVPCGWBF912B6E" ] }, - "ClusterDefaultVpcPublicSubnet2EIP265F4810": { - "Type": "AWS::EC2::EIP", - "Properties": { - "Domain": "vpc", - "Tags": [ - { - "Key": "kubernetes.io/role/elb", - "Value": "1" - }, - { - "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PublicSubnet2" - } - ] - } - }, - "ClusterDefaultVpcPublicSubnet2NATGateway4AF4B728": { - "Type": "AWS::EC2::NatGateway", - "Properties": { - "AllocationId": { - "Fn::GetAtt": [ - "ClusterDefaultVpcPublicSubnet2EIP265F4810", - "AllocationId" - ] - }, - "SubnetId": { - "Ref": "ClusterDefaultVpcPublicSubnet2SubnetC4E9A966" - }, - "Tags": [ - { - "Key": "kubernetes.io/role/elb", - "Value": "1" - }, - { - "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PublicSubnet2" - } - ] - } - }, - "ClusterDefaultVpcPublicSubnet3Subnet1A46184A": { + "VpcPublicSubnet3SubnetBE12F0B6": { "Type": "AWS::EC2::Subnet", "Properties": { "CidrBlock": "10.0.64.0/19", "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" }, "AvailabilityZone": "test-region-1c", "MapPublicIpOnLaunch": true, @@ -293,16 +253,16 @@ }, { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PublicSubnet3" + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet3" } ] } }, - "ClusterDefaultVpcPublicSubnet3RouteTableC81F99EF": { + "VpcPublicSubnet3RouteTable93458DBB": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" }, "Tags": [ { @@ -311,83 +271,43 @@ }, { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PublicSubnet3" + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet3" } ] } }, - "ClusterDefaultVpcPublicSubnet3RouteTableAssociation7C5D21CC": { + "VpcPublicSubnet3RouteTableAssociation1F1EDF02": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "ClusterDefaultVpcPublicSubnet3RouteTableC81F99EF" + "Ref": "VpcPublicSubnet3RouteTable93458DBB" }, "SubnetId": { - "Ref": "ClusterDefaultVpcPublicSubnet3Subnet1A46184A" + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" } } }, - "ClusterDefaultVpcPublicSubnet3DefaultRouteB6080504": { + "VpcPublicSubnet3DefaultRoute4697774F": { "Type": "AWS::EC2::Route", "Properties": { "RouteTableId": { - "Ref": "ClusterDefaultVpcPublicSubnet3RouteTableC81F99EF" + "Ref": "VpcPublicSubnet3RouteTable93458DBB" }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { - "Ref": "ClusterDefaultVpcIGW756BE43E" + "Ref": "VpcIGWD7BA715C" } }, "DependsOn": [ - "ClusterDefaultVpcVPCGWC1D00388" + "VpcVPCGWBF912B6E" ] }, - "ClusterDefaultVpcPublicSubnet3EIP0CBF6D05": { - "Type": "AWS::EC2::EIP", - "Properties": { - "Domain": "vpc", - "Tags": [ - { - "Key": "kubernetes.io/role/elb", - "Value": "1" - }, - { - "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PublicSubnet3" - } - ] - } - }, - "ClusterDefaultVpcPublicSubnet3NATGatewayEF4BA49A": { - "Type": "AWS::EC2::NatGateway", - "Properties": { - "AllocationId": { - "Fn::GetAtt": [ - "ClusterDefaultVpcPublicSubnet3EIP0CBF6D05", - "AllocationId" - ] - }, - "SubnetId": { - "Ref": "ClusterDefaultVpcPublicSubnet3Subnet1A46184A" - }, - "Tags": [ - { - "Key": "kubernetes.io/role/elb", - "Value": "1" - }, - { - "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PublicSubnet3" - } - ] - } - }, - "ClusterDefaultVpcPrivateSubnet1Subnet03F39409": { + "VpcPrivateSubnet1Subnet536B997A": { "Type": "AWS::EC2::Subnet", "Properties": { "CidrBlock": "10.0.96.0/19", "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" }, "AvailabilityZone": "test-region-1a", "MapPublicIpOnLaunch": false, @@ -406,16 +326,16 @@ }, { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PrivateSubnet1" + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet1" } ] } }, - "ClusterDefaultVpcPrivateSubnet1RouteTable7844020C": { + "VpcPrivateSubnet1RouteTableB2C5B500": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" }, "Tags": [ { @@ -424,40 +344,40 @@ }, { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PrivateSubnet1" + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet1" } ] } }, - "ClusterDefaultVpcPrivateSubnet1RouteTableAssociationF8A67D95": { + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "ClusterDefaultVpcPrivateSubnet1RouteTable7844020C" + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" }, "SubnetId": { - "Ref": "ClusterDefaultVpcPrivateSubnet1Subnet03F39409" + "Ref": "VpcPrivateSubnet1Subnet536B997A" } } }, - "ClusterDefaultVpcPrivateSubnet1DefaultRouteD624C8BD": { + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { "Type": "AWS::EC2::Route", "Properties": { "RouteTableId": { - "Ref": "ClusterDefaultVpcPrivateSubnet1RouteTable7844020C" + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" }, "DestinationCidrBlock": "0.0.0.0/0", "NatGatewayId": { - "Ref": "ClusterDefaultVpcPublicSubnet1NATGateway6E21013E" + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" } } }, - "ClusterDefaultVpcPrivateSubnet2SubnetA526AEA7": { + "VpcPrivateSubnet2Subnet3788AAA1": { "Type": "AWS::EC2::Subnet", "Properties": { "CidrBlock": "10.0.128.0/19", "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" }, "AvailabilityZone": "test-region-1b", "MapPublicIpOnLaunch": false, @@ -476,16 +396,16 @@ }, { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PrivateSubnet2" + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet2" } ] } }, - "ClusterDefaultVpcPrivateSubnet2RouteTable1F9A5298": { + "VpcPrivateSubnet2RouteTableA678073B": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" }, "Tags": [ { @@ -494,40 +414,40 @@ }, { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PrivateSubnet2" + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet2" } ] } }, - "ClusterDefaultVpcPrivateSubnet2RouteTableAssociationE1240DF2": { + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "ClusterDefaultVpcPrivateSubnet2RouteTable1F9A5298" + "Ref": "VpcPrivateSubnet2RouteTableA678073B" }, "SubnetId": { - "Ref": "ClusterDefaultVpcPrivateSubnet2SubnetA526AEA7" + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" } } }, - "ClusterDefaultVpcPrivateSubnet2DefaultRouteAB55737C": { + "VpcPrivateSubnet2DefaultRoute060D2087": { "Type": "AWS::EC2::Route", "Properties": { "RouteTableId": { - "Ref": "ClusterDefaultVpcPrivateSubnet2RouteTable1F9A5298" + "Ref": "VpcPrivateSubnet2RouteTableA678073B" }, "DestinationCidrBlock": "0.0.0.0/0", "NatGatewayId": { - "Ref": "ClusterDefaultVpcPublicSubnet2NATGateway4AF4B728" + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" } } }, - "ClusterDefaultVpcPrivateSubnet3SubnetB64BC839": { + "VpcPrivateSubnet3SubnetF258B56E": { "Type": "AWS::EC2::Subnet", "Properties": { "CidrBlock": "10.0.160.0/19", "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" }, "AvailabilityZone": "test-region-1c", "MapPublicIpOnLaunch": false, @@ -546,16 +466,16 @@ }, { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PrivateSubnet3" + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet3" } ] } }, - "ClusterDefaultVpcPrivateSubnet3RouteTableF71314D0": { + "VpcPrivateSubnet3RouteTableD98824C7": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" }, "Tags": [ { @@ -564,53 +484,53 @@ }, { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc/PrivateSubnet3" + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet3" } ] } }, - "ClusterDefaultVpcPrivateSubnet3RouteTableAssociation3007DC36": { + "VpcPrivateSubnet3RouteTableAssociation16BDDC43": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "ClusterDefaultVpcPrivateSubnet3RouteTableF71314D0" + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" }, "SubnetId": { - "Ref": "ClusterDefaultVpcPrivateSubnet3SubnetB64BC839" + "Ref": "VpcPrivateSubnet3SubnetF258B56E" } } }, - "ClusterDefaultVpcPrivateSubnet3DefaultRoute932EDFF0": { + "VpcPrivateSubnet3DefaultRoute94B74F0D": { "Type": "AWS::EC2::Route", "Properties": { "RouteTableId": { - "Ref": "ClusterDefaultVpcPrivateSubnet3RouteTableF71314D0" + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" }, "DestinationCidrBlock": "0.0.0.0/0", "NatGatewayId": { - "Ref": "ClusterDefaultVpcPublicSubnet3NATGatewayEF4BA49A" + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" } } }, - "ClusterDefaultVpcIGW756BE43E": { + "VpcIGWD7BA715C": { "Type": "AWS::EC2::InternetGateway", "Properties": { "Tags": [ { "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Cluster/DefaultVpc" + "Value": "aws-cdk-eks-cluster-test/Vpc" } ] } }, - "ClusterDefaultVpcVPCGWC1D00388": { + "VpcVPCGWBF912B6E": { "Type": "AWS::EC2::VPCGatewayAttachment", "Properties": { "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" }, "InternetGatewayId": { - "Ref": "ClusterDefaultVpcIGW756BE43E" + "Ref": "VpcIGWD7BA715C" } } }, @@ -657,7 +577,7 @@ } ], "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" } } }, @@ -724,6 +644,27 @@ "ToPort": 443 } }, + "ClusterControlPlaneSecurityGroupfromawscdkeksclustertestClusterInferenceInstancesInstanceSecurityGroup42C57C51443E3176F85": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from awscdkeksclustertestClusterInferenceInstancesInstanceSecurityGroup42C57C51:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "ClusterControlPlaneSecurityGroupD274242C", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "ClusterInferenceInstancesInstanceSecurityGroupECB3FC45", + "GroupId" + ] + }, + "ToPort": 443 + } + }, "ClusterCreationRole360249B6": { "Type": "AWS::IAM::Role", "Properties": { @@ -873,22 +814,22 @@ ], "subnetIds": [ { - "Ref": "ClusterDefaultVpcPublicSubnet1Subnet3BFE1BDA" + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" }, { - "Ref": "ClusterDefaultVpcPublicSubnet2SubnetC4E9A966" + "Ref": "VpcPublicSubnet2Subnet691E08A3" }, { - "Ref": "ClusterDefaultVpcPublicSubnet3Subnet1A46184A" + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" }, { - "Ref": "ClusterDefaultVpcPrivateSubnet1Subnet03F39409" + "Ref": "VpcPrivateSubnet1Subnet536B997A" }, { - "Ref": "ClusterDefaultVpcPrivateSubnet2SubnetA526AEA7" + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" }, { - "Ref": "ClusterDefaultVpcPrivateSubnet3SubnetB64BC839" + "Ref": "VpcPrivateSubnet3SubnetF258B56E" } ] } @@ -963,6 +904,13 @@ "Arn" ] }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]},{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "ClusterInferenceInstancesInstanceRole59AC6F56", + "Arn" + ] + }, "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" ] ] @@ -1059,13 +1007,13 @@ }, "Subnets": [ { - "Ref": "ClusterDefaultVpcPrivateSubnet1Subnet03F39409" + "Ref": "VpcPrivateSubnet1Subnet536B997A" }, { - "Ref": "ClusterDefaultVpcPrivateSubnet2SubnetA526AEA7" + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" }, { - "Ref": "ClusterDefaultVpcPrivateSubnet3SubnetB64BC839" + "Ref": "VpcPrivateSubnet3SubnetF258B56E" } ], "ForceUpdateEnabled": true, @@ -1177,7 +1125,7 @@ } ], "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" } } }, @@ -1406,13 +1354,13 @@ ], "VPCZoneIdentifier": [ { - "Ref": "ClusterDefaultVpcPrivateSubnet1Subnet03F39409" + "Ref": "VpcPrivateSubnet1Subnet536B997A" }, { - "Ref": "ClusterDefaultVpcPrivateSubnet2SubnetA526AEA7" + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" }, { - "Ref": "ClusterDefaultVpcPrivateSubnet3SubnetB64BC839" + "Ref": "VpcPrivateSubnet3SubnetF258B56E" } ] }, @@ -1465,7 +1413,7 @@ } ], "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" } } }, @@ -1708,13 +1656,13 @@ ], "VPCZoneIdentifier": [ { - "Ref": "ClusterDefaultVpcPrivateSubnet1Subnet03F39409" + "Ref": "VpcPrivateSubnet1Subnet536B997A" }, { - "Ref": "ClusterDefaultVpcPrivateSubnet2SubnetA526AEA7" + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" }, { - "Ref": "ClusterDefaultVpcPrivateSubnet3SubnetB64BC839" + "Ref": "VpcPrivateSubnet3SubnetF258B56E" } ] }, @@ -1767,7 +1715,7 @@ } ], "VpcId": { - "Ref": "ClusterDefaultVpcFA9F2722" + "Ref": "Vpc8378EB38" } } }, @@ -1997,13 +1945,13 @@ ], "VPCZoneIdentifier": [ { - "Ref": "ClusterDefaultVpcPrivateSubnet1Subnet03F39409" + "Ref": "VpcPrivateSubnet1Subnet536B997A" }, { - "Ref": "ClusterDefaultVpcPrivateSubnet2SubnetA526AEA7" + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" }, { - "Ref": "ClusterDefaultVpcPrivateSubnet3SubnetB64BC839" + "Ref": "VpcPrivateSubnet3SubnetF258B56E" } ] }, @@ -2053,6 +2001,317 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "ClusterInferenceInstancesInstanceSecurityGroupECB3FC45": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-cdk-eks-cluster-test/Cluster/InferenceInstances/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "Cluster9EE0221C" + } + ] + ] + }, + "Value": "owned" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Cluster/InferenceInstances" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "ClusterInferenceInstancesInstanceSecurityGroupfromawscdkeksclustertestClusterInferenceInstancesInstanceSecurityGroup42C57C51ALLTRAFFICB6138869": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "-1", + "Description": "from awscdkeksclustertestClusterInferenceInstancesInstanceSecurityGroup42C57C51:ALL TRAFFIC", + "GroupId": { + "Fn::GetAtt": [ + "ClusterInferenceInstancesInstanceSecurityGroupECB3FC45", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "ClusterInferenceInstancesInstanceSecurityGroupECB3FC45", + "GroupId" + ] + } + } + }, + "ClusterInferenceInstancesInstanceSecurityGroupfromawscdkeksclustertestClusterControlPlaneSecurityGroup2F1301344437B48FD33": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from awscdkeksclustertestClusterControlPlaneSecurityGroup2F130134:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "ClusterInferenceInstancesInstanceSecurityGroupECB3FC45", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "ClusterControlPlaneSecurityGroupD274242C", + "GroupId" + ] + }, + "ToPort": 443 + } + }, + "ClusterInferenceInstancesInstanceSecurityGroupfromawscdkeksclustertestClusterControlPlaneSecurityGroup2F130134102565535A460F673": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from awscdkeksclustertestClusterControlPlaneSecurityGroup2F130134:1025-65535", + "FromPort": 1025, + "GroupId": { + "Fn::GetAtt": [ + "ClusterInferenceInstancesInstanceSecurityGroupECB3FC45", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "ClusterControlPlaneSecurityGroupD274242C", + "GroupId" + ] + }, + "ToPort": 65535 + } + }, + "ClusterInferenceInstancesInstanceRole59AC6F56": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSWorkerNodePolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKS_CNI_Policy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + ] + } + ], + "Tags": [ + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "Cluster9EE0221C" + } + ] + ] + }, + "Value": "owned" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Cluster/InferenceInstances" + } + ] + } + }, + "ClusterInferenceInstancesInstanceProfile5A1209B4": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "ClusterInferenceInstancesInstanceRole59AC6F56" + } + ] + } + }, + "ClusterInferenceInstancesLaunchConfig03BF48FE": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": { + "Ref": "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2gpurecommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "inf1.2xlarge", + "IamInstanceProfile": { + "Ref": "ClusterInferenceInstancesInstanceProfile5A1209B4" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ClusterInferenceInstancesInstanceSecurityGroupECB3FC45", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ", + { + "Ref": "Cluster9EE0221C" + }, + " --kubelet-extra-args \"--node-labels lifecycle=OnDemand\" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack aws-cdk-eks-cluster-test --resource ClusterInferenceInstancesASGE90717C7 --region test-region" + ] + ] + } + } + }, + "DependsOn": [ + "ClusterInferenceInstancesInstanceRole59AC6F56" + ] + }, + "ClusterInferenceInstancesASGE90717C7": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "1", + "MinSize": "1", + "LaunchConfigurationName": { + "Ref": "ClusterInferenceInstancesLaunchConfig03BF48FE" + }, + "Tags": [ + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "Cluster9EE0221C" + } + ] + ] + }, + "PropagateAtLaunch": true, + "Value": "owned" + }, + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "aws-cdk-eks-cluster-test/Cluster/InferenceInstances" + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ] + }, + "UpdatePolicy": { + "AutoScalingRollingUpdate": { + "WaitOnResourceSignals": false, + "PauseTime": "PT0S", + "SuspendProcesses": [ + "HealthCheck", + "ReplaceUnhealthy", + "AZRebalance", + "AlarmNotification", + "ScheduledActions" + ] + }, + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "ClustermanifestNeuronDevicePlugin0B3E0D17": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B", + "Outputs.awscdkeksclustertestawscdkawseksKubectlProviderframeworkonEventC681B49AArn" + ] + }, + "Manifest": "[{\"apiVersion\":\"apps/v1\",\"kind\":\"DaemonSet\",\"metadata\":{\"name\":\"neuron-device-plugin-daemonset\",\"namespace\":\"kube-system\"},\"spec\":{\"selector\":{\"matchLabels\":{\"name\":\"neuron-device-plugin-ds\"}},\"updateStrategy\":{\"type\":\"RollingUpdate\"},\"template\":{\"metadata\":{\"annotations\":{\"scheduler.alpha.kubernetes.io/critical-pod\":\"\"},\"labels\":{\"name\":\"neuron-device-plugin-ds\"}},\"spec\":{\"tolerations\":[{\"key\":\"CriticalAddonsOnly\",\"operator\":\"Exists\"},{\"key\":\"aws.amazon.com/neuron\",\"operator\":\"Exists\",\"effect\":\"NoSchedule\"}],\"priorityClassName\":\"system-node-critical\",\"affinity\":{\"nodeAffinity\":{\"requiredDuringSchedulingIgnoredDuringExecution\":{\"nodeSelectorTerms\":[{\"matchExpressions\":[{\"key\":\"beta.kubernetes.io/instance-type\",\"operator\":\"In\",\"values\":[\"inf1.xlarge\",\"inf1.2xlarge\",\"inf1.6xlarge\",\"inf1.4xlarge\"]}]},{\"matchExpressions\":[{\"key\":\"node.kubernetes.io/instance-type\",\"operator\":\"In\",\"values\":[\"inf1.xlarge\",\"inf1.2xlarge\",\"inf1.6xlarge\",\"inf1.24xlarge\"]}]}]}}},\"containers\":[{\"image\":\"790709498068.dkr.ecr.us-west-2.amazonaws.com/neuron-device-plugin:1.0.9043.0\",\"imagePullPolicy\":\"Always\",\"name\":\"k8s-neuron-device-plugin-ctr\",\"securityContext\":{\"allowPrivilegeEscalation\":false,\"capabilities\":{\"drop\":[\"ALL\"]}},\"volumeMounts\":[{\"name\":\"device-plugin\",\"mountPath\":\"/var/lib/kubelet/device-plugins\"}]}],\"volumes\":[{\"name\":\"device-plugin\",\"hostPath\":{\"path\":\"/var/lib/kubelet/device-plugins\"}}]}}}}]", + "ClusterName": { + "Ref": "Cluster9EE0221C" + }, + "RoleArn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "ClusterNodegroupextrangNodeGroupRole23AE23D0": { "Type": "AWS::IAM::Role", "Properties": { @@ -2132,13 +2391,13 @@ }, "Subnets": [ { - "Ref": "ClusterDefaultVpcPrivateSubnet1Subnet03F39409" + "Ref": "VpcPrivateSubnet1Subnet536B997A" }, { - "Ref": "ClusterDefaultVpcPrivateSubnet2SubnetA526AEA7" + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" }, { - "Ref": "ClusterDefaultVpcPrivateSubnet3SubnetB64BC839" + "Ref": "VpcPrivateSubnet3SubnetF258B56E" } ], "ForceUpdateEnabled": true, @@ -2197,7 +2456,7 @@ "Chart": "kubernetes-dashboard", "Wait": false, "Namespace": "default", - "Repository": "https://kubernetes-charts.storage.googleapis.com" + "Repository": "https://kubernetes.github.io/dashboard/" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" @@ -2222,7 +2481,8 @@ }, "Release": "awscdkeksclustertestclusterchartnginxingressa7f70129", "Chart": "nginx-ingress", - "Wait": false, + "Wait": true, + "Timeout": "900s", "Namespace": "kube-system", "Repository": "https://helm.nginx.com/stable" }, @@ -2427,7 +2687,7 @@ }, "/", { - "Ref": "AssetParametersc70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649eS3Bucket5515EF97" + "Ref": "AssetParameters5b580ce3f53b1ee1551ac2e759433f10ef9a6a312bf7a501a0753fddcc8a3042S3BucketAE64A22C" }, "/", { @@ -2437,7 +2697,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersc70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649eS3VersionKeyF1C97F67" + "Ref": "AssetParameters5b580ce3f53b1ee1551ac2e759433f10ef9a6a312bf7a501a0753fddcc8a3042S3VersionKeyAFF70CF1" } ] } @@ -2450,7 +2710,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersc70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649eS3VersionKeyF1C97F67" + "Ref": "AssetParameters5b580ce3f53b1ee1551ac2e759433f10ef9a6a312bf7a501a0753fddcc8a3042S3VersionKeyAFF70CF1" } ] } @@ -2813,17 +3073,17 @@ "Type": "String", "Description": "Artifact hash for asset \"7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896\"" }, - "AssetParametersc70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649eS3Bucket5515EF97": { + "AssetParameters5b580ce3f53b1ee1551ac2e759433f10ef9a6a312bf7a501a0753fddcc8a3042S3BucketAE64A22C": { "Type": "String", - "Description": "S3 bucket for asset \"c70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649e\"" + "Description": "S3 bucket for asset \"5b580ce3f53b1ee1551ac2e759433f10ef9a6a312bf7a501a0753fddcc8a3042\"" }, - "AssetParametersc70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649eS3VersionKeyF1C97F67": { + "AssetParameters5b580ce3f53b1ee1551ac2e759433f10ef9a6a312bf7a501a0753fddcc8a3042S3VersionKeyAFF70CF1": { "Type": "String", - "Description": "S3 key for asset version \"c70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649e\"" + "Description": "S3 key for asset version \"5b580ce3f53b1ee1551ac2e759433f10ef9a6a312bf7a501a0753fddcc8a3042\"" }, - "AssetParametersc70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649eArtifactHash88EDABBF": { + "AssetParameters5b580ce3f53b1ee1551ac2e759433f10ef9a6a312bf7a501a0753fddcc8a3042ArtifactHashD8735DDD": { "Type": "String", - "Description": "Artifact hash for asset \"c70286a3d87ecc95aea2ffe7d7723ba303222280fb87f0128b3e83dcdd1d649e\"" + "Description": "Artifact hash for asset \"5b580ce3f53b1ee1551ac2e759433f10ef9a6a312bf7a501a0753fddcc8a3042\"" }, "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", @@ -2832,6 +3092,10 @@ "SsmParameterValueawsservicebottlerocketawsk8s115x8664latestimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", "Default": "/aws/service/bottlerocket/aws-k8s-1.15/x86_64/latest/image_id" + }, + "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2gpurecommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/eks/optimized-ami/1.16/amazon-linux-2-gpu/recommended/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 ff6d62e74c20f..ddee987d81d35 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts @@ -1,6 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { App, CfnOutput } from '@aws-cdk/core'; +import { App, CfnOutput, Duration } from '@aws-cdk/core'; import * as eks from '../lib'; import * as hello from './hello-k8s'; import { TestStack } from './util'; @@ -14,14 +14,18 @@ class EksClusterStack extends TestStack { assumedBy: new iam.AccountRootPrincipal(), }); + // just need one nat gateway to simplify the test + const vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 3, natGateways: 1 }); + // create the cluster with a default nodegroup capacity const cluster = new eks.Cluster(this, 'Cluster', { + vpc, mastersRole, defaultCapacity: 2, version: '1.16', }); - // // fargate profile for resources in the "default" namespace + // fargate profile for resources in the "default" namespace cluster.addFargateProfile('default', { selectors: [{ namespace: 'default' }], }); @@ -51,6 +55,12 @@ class EksClusterStack extends TestStack { }, }); + // inference instances + cluster.addCapacity('InferenceInstances', { + instanceType: new ec2.InstanceType('inf1.2xlarge'), + minCapacity: 1, + }); + // add a extra nodegroup cluster.addNodegroup('extra-ng', { instanceType: new ec2.InstanceType('t3.small'), @@ -59,12 +69,22 @@ class EksClusterStack extends TestStack { nodeRole: cluster.defaultCapacity ? cluster.defaultCapacity.role : undefined, }); - // // apply a kubernetes manifest + // apply a kubernetes manifest cluster.addResource('HelloApp', ...hello.resources); - // // add two Helm charts to the cluster. This will be the Kubernetes dashboard and the Nginx Ingress Controller - cluster.addChart('dashboard', { chart: 'kubernetes-dashboard', repository: 'https://kubernetes-charts.storage.googleapis.com' }); - cluster.addChart('nginx-ingress', { chart: 'nginx-ingress', repository: 'https://helm.nginx.com/stable', namespace: 'kube-system' }); + // add two Helm charts to the cluster. This will be the Kubernetes dashboard and the Nginx Ingress Controller + cluster.addChart('dashboard', { + chart: 'kubernetes-dashboard', + repository: 'https://kubernetes.github.io/dashboard/', + }); + + cluster.addChart('nginx-ingress', { + chart: 'nginx-ingress', + repository: 'https://helm.nginx.com/stable', + namespace: 'kube-system', + wait: true, + timeout: Duration.minutes(15), + }); // add a service account connected to a IAM role cluster.addServiceAccount('MyServiceAccount'); diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index 4b00ac1ec6e36..c7a09d794e7cc 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -2,7 +2,10 @@ import { countResources, expect, haveResource, haveResourceLike, not } from '@aw import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; +import * as fs from 'fs'; import { Test } from 'nodeunit'; +import * as path from 'path'; +import * as YAML from 'yaml'; import * as eks from '../lib'; import { KubectlLayer } from '../lib/kubectl-layer'; import { testFixture, testFixtureNoVpc } from './util'; @@ -1156,4 +1159,23 @@ export = { })); test.done(); }, + 'inference instances are supported'(test: Test) { + // GIVEN + const { stack } = testFixtureNoVpc(); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0 }); + + // WHEN + cluster.addCapacity('InferenceInstances', { + instanceType: new ec2.InstanceType('inf1.2xlarge'), + minCapacity: 1, + }); + const fileContents = fs.readFileSync(path.join(__dirname, '../lib', 'addons/neuron-device-plugin.yaml'), 'utf8'); + const sanitized = YAML.parse(fileContents); + + // THEN + expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { + Manifest: JSON.stringify([sanitized]), + })); + 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 7fed84a2b6185..0528bb27aac54 100644 --- a/packages/@aws-cdk/aws-eks/test/test.helm-chart.ts +++ b/packages/@aws-cdk/aws-eks/test/test.helm-chart.ts @@ -82,7 +82,7 @@ export = { new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart', timeout: Duration.minutes(10) }); // THEN - expect(stack).to(haveResource(eks.HelmChart.RESOURCE_TYPE, { Timeout: 600 })); + expect(stack).to(haveResource(eks.HelmChart.RESOURCE_TYPE, { Timeout: '600s' })); test.done(); }, }, diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index 15152b7fc89a5..a51e40c0df0db 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -205,11 +205,13 @@ const ELBV2_ACCOUNTS: { [region: string]: string } = { 'us-east-2': '033677994240', 'us-west-1': '027434742980', 'us-west-2': '797873946194', + 'af-south-1': '098369216593', 'ca-central-1': '985666609251', 'eu-central-1': '054676820928', 'eu-west-1': '156460612806', 'eu-west-2': '652711504416', 'eu-west-3': '009996457667', + 'eu-south-1': '635631232127', 'eu-north-1': '897822967062', 'ap-east-1': '754344448648', 'ap-northeast-1': '582318560864', diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index 18f0665c69a02..01ab709164870 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -68,7 +68,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", - "aws-sdk": "^2.703.0", + "aws-sdk": "^2.706.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-nodejs/.gitignore b/packages/@aws-cdk/aws-lambda-nodejs/.gitignore index 7be76e8fa94c1..ce811676a5cb2 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/.gitignore +++ b/packages/@aws-cdk/aws-lambda-nodejs/.gitignore @@ -14,10 +14,6 @@ nyc.config.js .LAST_PACKAGE *.snk -# Parcel -.build -.cache - !test/integ-handlers/js-handler.js !test/function.test.handler2.js !.eslintrc.js diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index 84b938347b6f1..b4e4579991f9e 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -53,10 +53,41 @@ new lambda.NodejsFunction(this, 'my-handler', { ``` ### Configuring Parcel -The `NodejsFunction` construct exposes some [Parcel](https://parceljs.org/) options via properties: `minify`, `sourceMaps`, -`buildDir` and `cacheDir`. +The `NodejsFunction` construct exposes some [Parcel](https://parceljs.org/) options via properties: `minify`, `sourceMaps` and `cacheDir`. Parcel transpiles your code (every internal module) with [@babel/preset-env](https://babeljs.io/docs/en/babel-preset-env) and uses the runtime version of your Lambda function as target. Configuring Babel with Parcel is possible via a `.babelrc` or a `babel` config in `package.json`. + +### Working with modules + +#### Externals +By default, all node modules are bundled except for `aws-sdk`. This can be configured by specifying +the `externalModules` prop. + +```ts +new lambda.NodejsFunction(this, 'my-handler', { + externalModules: [ + 'aws-sdk', // Use the 'aws-sdk' available in the Lambda runtime + 'cool-module', // 'cool-module' is already available in a Layer + ], +}); +``` + +#### Install modules +By default, all node modules referenced in your Lambda code will be bundled by Parcel. +Use the `nodeModules` prop to specify a list of modules that should not be bundled +but instead included in the `node_modules` folder of the Lambda package. This is useful +when working with native dependencies or when Parcel fails to bundle a module. + +```ts +new lambda.NodejsFunction(this, 'my-handler', { + nodeModules: ['native-module', 'other-module'] +}); +``` + +The modules listed in `nodeModules` must be present in the `package.json`'s dependencies. The +same version will be used for installation. If a lock file is detected (`package-lock.json` or +`yarn.lock`) it will be used along with the right installer (`npm` or `yarn`). The modules are +installed in a [Lambda compatible Docker container](https://github.com/lambci/docker-lambda). diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 15457bcf45869..274e043bf11a3 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -2,136 +2,213 @@ 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'; +import { PackageJsonManager } from './package-json-manager'; +import { findClosestPathContaining } from './util'; /** - * Options for Parcel bundling + * Base options for Parcel bundling */ -export interface ParcelOptions { +export interface ParcelBaseOptions { /** - * Entry file + * Whether to minify files when bundling. + * + * @default false */ - readonly entry: string; + readonly minify?: boolean; /** - * Expose modules as UMD under this name + * Whether to include source maps when bundling. + * + * @default false */ - readonly global: string; + readonly sourceMaps?: boolean; /** - * Minify + * The cache directory + * + * Parcel uses a filesystem cache for fast rebuilds. + * + * @default - `.cache` in the root directory */ - readonly minify?: boolean; + readonly cacheDir?: string; /** - * Include source maps + * The root of the project. This will be used as the source for the volume + * mounted in the Docker container. If you specify this prop, ensure that + * this path includes `entry` and any module/dependencies used by your + * function otherwise bundling will not be possible. + * + * @default - the closest path containing a .git folder */ - readonly sourceMaps?: boolean; + readonly projectRoot?: string; /** - * The cache directory + * Environment variables defined when Parcel runs. + * + * @default - no environment variables are defined. */ - readonly cacheDir?: string; + readonly parcelEnvironment?: { [key: string]: string; }; /** - * The node version to use as target for Babel + * A list of modules that should be considered as externals (already available + * in the runtime). + * + * @default ['aws-sdk'] */ - readonly nodeVersion: string; + readonly externalModules?: string[]; /** - * The docker tag of the node base image to use in the parcel-bundler docker image + * A list of modules that should be installed instead of bundled. Modules are + * installed in a Lambda compatible environnment. * - * @see https://hub.docker.com/_/node/?tab=tags + * @default - all modules are bundled */ - readonly nodeDockerTag: string; + readonly nodeModules?: string[]; /** - * The root of the project. This will be used as the source for the volume - * mounted in the Docker container. + * The version of Parcel to use. + * + * @default - 2.0.0-beta.1 + */ + readonly parcelVersion?: string; +} + +/** + * Options for Parcel bundling + */ +export interface ParcelOptions extends ParcelBaseOptions { + /** + * Entry file */ - readonly projectRoot: string; + readonly entry: string; /** - * The environment variables to pass to the container running Parcel. - * - * @default - no environment variables are passed to the container + * The runtime of the lambda function */ - readonly environment?: { [key: string]: string; }; + readonly runtime: lambda.Runtime; } /** - * Parcel code + * Bundling */ export class Bundling { + /** + * Parcel bundled Lambda asset code + */ 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.'); + // Find project root + const projectRoot = options.projectRoot ?? findClosestPathContaining(`.git${path.sep}`); + if (!projectRoot) { + throw new Error('Cannot find project root. Please specify it with `projectRoot`.'); } - 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); + // Bundling image derived from runtime bundling image (lambci) + const image = cdk.BundlingDockerImage.fromAsset(path.join(__dirname, '../parcel'), { + buildArgs: { + IMAGE: options.runtime.bundlingDockerImage.image, + PARCEL_VERSION: options.parcelVersion ?? '2.0.0-beta.1', + }, + }); + + const packageJsonManager = new PackageJsonManager(); + + // Collect external and install modules + let includeNodeModules: { [key: string]: boolean } | undefined; + let dependencies: { [key: string]: string } | undefined; + const externalModules = options.externalModules ?? ['aws-sdk']; + if (externalModules || options.nodeModules) { + const modules = [...externalModules, ...options.nodeModules ?? []]; + includeNodeModules = {}; + for (const mod of modules) { + includeNodeModules[mod] = false; + } + if (options.nodeModules) { + dependencies = packageJsonManager.getVersions(options.nodeModules); + } + } - // 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, + // Configure target in package.json for Parcel + packageJsonManager.update({ + 'cdk-lambda': `${cdk.AssetStaging.BUNDLING_OUTPUT_DIR}/index.js`, + 'targets': { + 'cdk-lambda': { + context: 'node', + includeNodeModules: includeNodeModules ?? true, + sourceMap: options.sourceMaps ?? false, + minify: options.minify ?? false, + engines: { + node: `>= ${runtimeVersion(options.runtime)}`, + }, }, - }); - } finally { - restorePkg(pkgPath, originalPkg); + }, + }); + + // Entry file path relative to container path + const containerEntryPath = path.join(cdk.AssetStaging.BUNDLING_INPUT_DIR, path.relative(projectRoot, path.resolve(options.entry))); + const parcelCommand = `parcel build ${containerEntryPath.replace(/\\/g, '/')} --target cdk-lambda${options.cacheDir ? ' --cache-dir /parcel-cache' : ''}`; + + let installer = Installer.NPM; + let lockfile: string | undefined; + let depsCommand = ''; + + if (dependencies) { + // Create a dummy package.json for dependencies that we need to install + fs.writeFileSync( + path.join(projectRoot, '.package.json'), + JSON.stringify({ dependencies }), + ); + + // Use npm unless we have a yarn.lock. + if (fs.existsSync(path.join(projectRoot, LockFile.YARN))) { + installer = Installer.YARN; + lockfile = LockFile.YARN; + } else if (fs.existsSync(path.join(projectRoot, LockFile.NPM))) { + lockfile = LockFile.NPM; + } + + // Move dummy package.json and lock file then install + depsCommand = chain([ + `mv ${cdk.AssetStaging.BUNDLING_INPUT_DIR}/.package.json ${cdk.AssetStaging.BUNDLING_OUTPUT_DIR}/package.json`, + lockfile ? `cp ${cdk.AssetStaging.BUNDLING_INPUT_DIR}/${lockfile} ${cdk.AssetStaging.BUNDLING_OUTPUT_DIR}/${lockfile}` : '', + `cd ${cdk.AssetStaging.BUNDLING_OUTPUT_DIR} && ${installer} install`, + ]); } + + return lambda.Code.fromAsset(projectRoot, { + assetHashType: cdk.AssetHashType.BUNDLE, + bundling: { + image, + command: ['bash', '-c', chain([parcelCommand, depsCommand])], + environment: options.parcelEnvironment, + volumes: options.cacheDir + ? [{ containerPath: '/parcel-cache', hostPath: options.cacheDir }] + : [], + workingDirectory: path.dirname(containerEntryPath).replace(/\\/g, '/'), // Always use POSIX paths in the container + }, + }); } } -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)); +enum Installer { + NPM = 'npm', + YARN = 'yarn', +} + +enum LockFile { + NPM = 'package-lock.json', + YARN = 'yarn.lock' +} + +function runtimeVersion(runtime: lambda.Runtime): string { + const match = runtime.name.match(/nodejs(\d+)/); + + if (!match) { + throw new Error('Cannot extract version from runtime.'); } + + return match[1]; } -function restorePkg(pkgPath: string, originalPkg: Buffer): void { - fs.writeFileSync(pkgPath, originalPkg); +function chain(commands: string[]): string { + return commands.filter(c => !!c).join(' && '); } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts index c231eb06d7f84..70cfb8f19b130 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts @@ -2,13 +2,14 @@ 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 { Bundling } from './bundling'; -import { findGitPath, nodeMajorVersion, parseStackTrace } from './util'; +import { Bundling, ParcelBaseOptions } from './bundling'; +import { PackageJsonManager } from './package-json-manager'; +import { nodeMajorVersion, parseStackTrace } from './util'; /** * Properties for a NodejsFunction */ -export interface NodejsFunctionProps extends lambda.FunctionOptions { +export interface NodejsFunctionProps extends lambda.FunctionOptions, ParcelBaseOptions { /** * Path to the entry file (JavaScript or TypeScript). * @@ -34,62 +35,6 @@ export interface NodejsFunctionProps extends lambda.FunctionOptions { * `NODEJS_10_X` otherwise. */ readonly runtime?: lambda.Runtime; - - /** - * Whether to minify files when bundling. - * - * @default false - */ - readonly minify?: boolean; - - /** - * Whether to include source maps when bundling. - * - * @default false - */ - readonly sourceMaps?: boolean; - - /** - * The build directory - * - * @default - `.build` in the entry file directory - */ - readonly buildDir?: string; - - /** - * The cache directory - * - * Parcel uses a filesystem cache for fast rebuilds. - * - * @default - `.cache` in the root directory - */ - readonly cacheDir?: 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 - * - * @default - the `process.versions.node` alpine image - */ - readonly nodeDockerTag?: string; - - /** - * The root of the project. This will be used as the source for the volume - * mounted in the Docker container. If you specify this prop, ensure that - * this path includes `entry` and any module/dependencies used by your - * function otherwise bundling will not be possible. - * - * @default - the closest path containing a .git folder - */ - readonly projectRoot?: string; - - /** - * The environment variables to pass to the container running Parcel. - * - * @default - no environment variables are passed to the container - */ - readonly containerEnvironment?: { [key: string]: string; }; } /** @@ -101,34 +46,32 @@ export class NodejsFunction extends lambda.Function { throw new Error('Only `NODEJS` runtimes are supported.'); } + // Entry and defaults const entry = findEntry(id, props.entry); 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 projectRoot = props.projectRoot ?? findGitPath(); - if (!projectRoot) { - throw new Error('Cannot find project root. Please specify it with `projectRoot`.'); - } - const nodeDockerTag = props.nodeDockerTag ?? `${process.versions.node}-alpine`; - super(scope, id, { - ...props, - runtime, - 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}`, - }); + // We need to restore the package.json after bundling + const packageJsonManager = new PackageJsonManager(); + + try { + super(scope, id, { + ...props, + runtime, + code: Bundling.parcel({ + entry, + runtime, + ...props, + }), + handler: `index.${handler}`, + }); + } finally { + // We can only restore after the code has been bound to the function + packageJsonManager.restore(); + } } } @@ -178,16 +121,3 @@ function findDefiningFile(): string { return stackTrace[functionIndex + 1].file; } - -/** - * Extracts the version from the runtime - */ -function extractVersion(runtime: lambda.Runtime): string { - const match = runtime.name.match(/nodejs(\d+)/); - - if (!match) { - throw new Error('Cannot extract version from runtime.'); - } - - return match[1]; -} diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/index.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/index.ts index 2653adb2a89e8..bcda298c2f5fe 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/index.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/index.ts @@ -1 +1,2 @@ export * from './function'; +export * from './bundling'; diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/package-json-manager.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/package-json-manager.ts new file mode 100644 index 0000000000000..c43c4b0963351 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/package-json-manager.ts @@ -0,0 +1,71 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { findClosestPathContaining } from './util'; + +/** + * A package.json manager to act on the closest package.json file. + * + * Configuring the bundler requires to manipulate the package.json and then + * restore it. + */ +export class PackageJsonManager { + private readonly pkgPath: string; + private readonly pkg: Buffer; + private readonly pkgJson: PackageJson; + + constructor() { + const pkgPath = findClosestPathContaining('package.json'); + if (!pkgPath) { + throw new Error('Cannot find a `package.json` in this project.'); + } + this.pkgPath = path.join(pkgPath, 'package.json'); + this.pkg = fs.readFileSync(this.pkgPath); + this.pkgJson = JSON.parse(this.pkg.toString()); + } + + /** + * Update the package.json + */ + public update(data: any) { + fs.writeFileSync(this.pkgPath, JSON.stringify({ + ...this.pkgJson, + ...data, + }, null, 2)); + } + + /** + * Restore the package.json to the original + */ + public restore() { + fs.writeFileSync(this.pkgPath, this.pkg); + } + + /** + * Extract versions for a list of modules + */ + public getVersions(modules: string[]): { [key: string]: string } { + const dependencies: { [key: string]: string } = {}; + + const allDependencies = { + ...this.pkgJson.dependencies ?? {}, + ...this.pkgJson.devDependencies ?? {}, + ...this.pkgJson.peerDependencies ?? {}, + }; + + for (const mod of modules) { + if (!allDependencies[mod]) { + throw new Error(`Cannot extract version for ${mod} in package.json`); + } + dependencies[mod] = allDependencies[mod]; + } + + return dependencies; + + } +} + +interface PackageJson { + dependencies?: { [key: string]: string }; + devDependencies?: { [key: string]: string }; + peerDependencies?: { [key: string]: string }; +} diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts index c54b0e3ed56c4..92d61e7f76e1a 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts @@ -53,7 +53,7 @@ export function nodeMajorVersion(): number { /** * Finds the closest path containg a path */ -function findClosestPathContaining(p: string): string | undefined { +export function findClosestPathContaining(p: string): string | undefined { for (const nodeModulesPath of module.paths) { if (fs.existsSync(path.join(path.dirname(nodeModulesPath), p))) { return path.dirname(nodeModulesPath); @@ -62,17 +62,3 @@ function findClosestPathContaining(p: string): string | undefined { return undefined; } - -/** - * Finds closest package.json path - */ -export function findPkgPath(): string | undefined { - return findClosestPathContaining('package.json'); -} - -/** - * Finds closest .git/ - */ -export function findGitPath(): string | undefined { - return findClosestPathContaining(`.git${path.sep}`); -} diff --git a/packages/@aws-cdk/aws-lambda-nodejs/package.json b/packages/@aws-cdk/aws-lambda-nodejs/package.json index 31995f757c849..48c612067a32b 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/package.json +++ b/packages/@aws-cdk/aws-lambda-nodejs/package.json @@ -61,7 +61,7 @@ "@aws-cdk/assert": "0.0.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "fs-extra": "^9.0.1", + "delay": "4.3.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-lambda-nodejs/parcel-bundler/Dockerfile b/packages/@aws-cdk/aws-lambda-nodejs/parcel-bundler/Dockerfile deleted file mode 100644 index 0a92746c6464c..0000000000000 --- a/packages/@aws-cdk/aws-lambda-nodejs/parcel-bundler/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -# runs the parcel-bundler npm package to package and install dependencies of nodejs lambda functions -ARG NODE_TAG -FROM node:${NODE_TAG} - -RUN yarn global add parcel-bundler@^1 - -# add the global node_modules folder to NODE_PATH so that plugins can find parcel-bundler -ENV NODE_PATH /usr/local/share/.config/yarn/global/node_modules - -CMD [ "parcel" ] diff --git a/packages/@aws-cdk/aws-lambda-nodejs/parcel/Dockerfile b/packages/@aws-cdk/aws-lambda-nodejs/parcel/Dockerfile new file mode 100644 index 0000000000000..0b91c0df6f600 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/parcel/Dockerfile @@ -0,0 +1,19 @@ +# The correct lambci build image based on the runtime of the function will be +# passed as build arg. The default allows to do `docker build .` when testing. +ARG IMAGE=lambci/lambda:build-nodejs12.x +FROM $IMAGE + +# Ensure npm cache is not owned by root because the image will not run +# as root and can potentially run `npm install`. +RUN mkdir /npm-cache && \ + chown -R 1000:1000 /npm-cache && \ + npm config --global set cache /npm-cache + +# Install yarn +RUN npm install --global yarn + +# Install parcel 2 (fix the version since it's still in beta) +ARG PARCEL_VERSION=2.0.0-beta.1 +RUN yarn global add parcel@$PARCEL_VERSION + +CMD [ "parcel" ] diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index 9a58350934516..e7129cf5da0c8 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -1,11 +1,14 @@ -import { Code } from '@aws-cdk/aws-lambda'; +import { Code, Runtime } from '@aws-cdk/aws-lambda'; import { AssetHashType } from '@aws-cdk/core'; +import { version as delayVersion } from 'delay/package.json'; import * as fs from 'fs'; import { Bundling } from '../lib/bundling'; jest.mock('@aws-cdk/aws-lambda'); -const writeFileSyncMock = jest.spyOn(fs, 'writeFileSync'); +const writeFileSyncMock = jest.spyOn(fs, 'writeFileSync').mockReturnValue(); +const existsSyncOriginal = fs.existsSync; +const existsSyncMock = jest.spyOn(fs, 'existsSync'); beforeEach(() => { jest.clearAllMocks(); @@ -14,12 +17,10 @@ beforeEach(() => { test('Parcel bundling', () => { Bundling.parcel({ entry: '/project/folder/entry.ts', - global: 'handler', + runtime: Runtime.NODEJS_12_X, cacheDir: '/cache-dir', - nodeDockerTag: 'lts-alpine', - nodeVersion: '12', projectRoot: '/project', - environment: { + parcelEnvironment: { KEY: 'value', }, }); @@ -34,42 +35,113 @@ test('Parcel bundling', () => { 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', + 'bash', '-c', 'parcel build /asset-input/folder/entry.ts --target cdk-lambda --cache-dir /parcel-cache', ], }), }); // Correctly updates package.json - expect(writeFileSyncMock).toHaveBeenCalledWith( - expect.stringContaining('package.json'), - expect.stringContaining('"node": ">= 12"'), - ); + const call = writeFileSyncMock.mock.calls[0]; + expect(call[0]).toMatch('package.json'); + expect(JSON.parse(call[1])).toEqual(expect.objectContaining({ + 'cdk-lambda': '/asset-output/index.js', + 'targets': { + 'cdk-lambda': { + context: 'node', + includeNodeModules: { + 'aws-sdk': false, + }, + sourceMap: false, + minify: false, + engines: { + node: '>= 12', + }, + }, + }, + })); }); test('Parcel with Windows paths', () => { Bundling.parcel({ entry: 'C:\\my-project\\lib\\entry.ts', - global: 'handler', + runtime: Runtime.NODEJS_12_X, 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'), + expect.stringContaining('/lib/entry.ts'), ]), }), })); }); + +test('Parcel bundling with externals and dependencies', () => { + Bundling.parcel({ + entry: '/project/folder/entry.ts', + runtime: Runtime.NODEJS_12_X, + projectRoot: '/project', + externalModules: ['abc'], + nodeModules: ['delay'], + }); + + // Correctly bundles with parcel + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.BUNDLE, + bundling: expect.objectContaining({ + command: [ + 'bash', '-c', + 'parcel build /asset-input/folder/entry.ts --target cdk-lambda && mv /asset-input/.package.json /asset-output/package.json && cd /asset-output && npm install', + ], + }), + }); + + // Correctly updates package.json + const call = writeFileSyncMock.mock.calls[0]; + expect(call[0]).toMatch('package.json'); + expect(JSON.parse(call[1])).toEqual(expect.objectContaining({ + targets: expect.objectContaining({ + 'cdk-lambda': expect.objectContaining({ + includeNodeModules: { + delay: false, + abc: false, + }, + }), + }), + })); + + // Correctly writes dummy package.json + expect(writeFileSyncMock).toHaveBeenCalledWith('/project/.package.json', JSON.stringify({ + dependencies: { + delay: delayVersion, + }, + })); +}); + +test('Detects yarn.lock', () => { + existsSyncMock.mockImplementation((p: fs.PathLike) => { + if (/yarn.lock/.test(p.toString())) { + return true; + } + return existsSyncOriginal(p); + }); + + Bundling.parcel({ + entry: '/project/folder/entry.ts', + runtime: Runtime.NODEJS_12_X, + projectRoot: '/project', + nodeModules: ['delay'], + }); + + // Correctly bundles with parcel + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.BUNDLE, + bundling: expect.objectContaining({ + command: expect.arrayContaining([ + expect.stringMatching(/yarn\.lock.+yarn install/), + ]), + }), + }); +}); 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 25f5589e9e388..200200fdeb1ce 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts @@ -29,7 +29,6 @@ test('NodejsFunction with .ts handler', () => { expect(Bundling.parcel).toHaveBeenCalledWith(expect.objectContaining({ entry: expect.stringContaining('function.test.handler1.ts'), // Automatically finds .ts handler file - global: 'handler', })); expect(stack).toHaveResource('AWS::Lambda::Function', { @@ -50,13 +49,13 @@ test('NodejsFunction with .js handler', () => { test('NodejsFunction with container env vars', () => { // WHEN new NodejsFunction(stack, 'handler1', { - containerEnvironment: { + parcelEnvironment: { KEY: 'VALUE', }, }); expect(Bundling.parcel).toHaveBeenCalledWith(expect.objectContaining({ - environment: { + parcelEnvironment: { KEY: 'VALUE', }, })); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/dependencies.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/dependencies.ts new file mode 100644 index 0000000000000..31abbb04b4cfe --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/dependencies.ts @@ -0,0 +1,10 @@ +// tslint:disable:no-console +import { S3 } from 'aws-sdk'; // eslint-disable-line import/no-extraneous-dependencies +import * as delay from 'delay'; + +const s3 = new S3(); + +export async function handler() { + console.log(s3); + await delay(5); +} diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/integ.dependencies.expected.json b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.dependencies.expected.json new file mode 100644 index 0000000000000..d67505788c16c --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.dependencies.expected.json @@ -0,0 +1,103 @@ +{ + "Resources": { + "externalServiceRole85A00A90": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "external068F12D1": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersf3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4S3BucketC81DD688" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersf3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4S3VersionKeyDA9CBF67" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersf3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4S3VersionKeyDA9CBF67" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "externalServiceRole85A00A90", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + }, + "DependsOn": [ + "externalServiceRole85A00A90" + ] + } + }, + "Parameters": { + "AssetParametersf3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4S3BucketC81DD688": { + "Type": "String", + "Description": "S3 bucket for asset \"f3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4\"" + }, + "AssetParametersf3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4S3VersionKeyDA9CBF67": { + "Type": "String", + "Description": "S3 key for asset version \"f3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4\"" + }, + "AssetParametersf3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4ArtifactHash0E6684C0": { + "Type": "String", + "Description": "Artifact hash for asset \"f3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/integ.dependencies.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.dependencies.ts new file mode 100644 index 0000000000000..acd55e7f6e8c7 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.dependencies.ts @@ -0,0 +1,25 @@ +import { Runtime } from '@aws-cdk/aws-lambda'; +import { App, Construct, Stack, StackProps } from '@aws-cdk/core'; +import * as path from 'path'; +import * as lambda from '../lib'; + +class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + // This function uses aws-sdk but it will not be included + new lambda.NodejsFunction(this, 'external', { + entry: path.join(__dirname, 'integ-handlers/dependencies.ts'), + runtime: Runtime.NODEJS_12_X, + minify: true, + // Will be installed, not bundled + // (delay is a zero dependency package and its version is fixed + // in the package.json to ensure a stable hash for this integ test) + nodeModules: ['delay'], + }); + } +} + +const app = new App(); +new TestStack(app, 'cdk-integ-lambda-nodejs-dependencies'); +app.synth(); 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 6633fb1402bf7..b88a7e8503886 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": "AssetParameters20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9S3Bucket7F316AC2" + "Ref": "AssetParameters9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88S3BucketD344F833" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9S3VersionKeyEEDC3772" + "Ref": "AssetParameters9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88S3VersionKeyEB3332E0" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9S3VersionKeyEEDC3772" + "Ref": "AssetParameters9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88S3VersionKeyEB3332E0" } ] } @@ -121,7 +121,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990S3Bucket88C76A86" + "Ref": "AssetParameters0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0S3Bucket3B0DF548" }, "S3Key": { "Fn::Join": [ @@ -134,7 +134,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990S3VersionKey1E5E4562" + "Ref": "AssetParameters0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0S3VersionKey1D84CC0E" } ] } @@ -147,7 +147,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990S3VersionKey1E5E4562" + "Ref": "AssetParameters0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0S3VersionKey1D84CC0E" } ] } @@ -172,29 +172,29 @@ } }, "Parameters": { - "AssetParameters20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9S3Bucket7F316AC2": { + "AssetParameters9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88S3BucketD344F833": { "Type": "String", - "Description": "S3 bucket for asset \"20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9\"" + "Description": "S3 bucket for asset \"9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88\"" }, - "AssetParameters20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9S3VersionKeyEEDC3772": { + "AssetParameters9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88S3VersionKeyEB3332E0": { "Type": "String", - "Description": "S3 key for asset version \"20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9\"" + "Description": "S3 key for asset version \"9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88\"" }, - "AssetParameters20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9ArtifactHash8387A82E": { + "AssetParameters9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88ArtifactHash079EA103": { "Type": "String", - "Description": "Artifact hash for asset \"20afe351e391b62b260d621490f511b5a25bc8bceb71127d3784c5d8e62aa5e9\"" + "Description": "Artifact hash for asset \"9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88\"" }, - "AssetParameters39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990S3Bucket88C76A86": { + "AssetParameters0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0S3Bucket3B0DF548": { "Type": "String", - "Description": "S3 bucket for asset \"39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990\"" + "Description": "S3 bucket for asset \"0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0\"" }, - "AssetParameters39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990S3VersionKey1E5E4562": { + "AssetParameters0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0S3VersionKey1D84CC0E": { "Type": "String", - "Description": "S3 key for asset version \"39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990\"" + "Description": "S3 key for asset version \"0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0\"" }, - "AssetParameters39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990ArtifactHash37B9CB08": { + "AssetParameters0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0ArtifactHash7545DAEB": { "Type": "String", - "Description": "Artifact hash for asset \"39c8a0dc659dd89e6876d7d8447b17176396320962e88fb69ea7d7feb9a23990\"" + "Description": "Artifact hash for asset \"0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0\"" } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index 6b79895bf02c6..1fa918db69d5c 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -68,16 +68,16 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/aws-lambda": "^8.10.39", - "@types/lodash": "^4.14.156", + "@types/lodash": "^4.14.157", "@types/nodeunit": "^0.0.31", "@types/sinon": "^9.0.4", - "aws-sdk": "^2.703.0", + "aws-sdk": "^2.706.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", "lodash": "^4.17.15", - "nock": "^12.0.3", + "nock": "^13.0.0", "nodeunit": "^0.11.3", "pkglint": "0.0.0", "sinon": "^9.0.2" diff --git a/packages/@aws-cdk/aws-route53-targets/README.md b/packages/@aws-cdk/aws-route53-targets/README.md index e3e8f99a0e5ba..3bd36f68ba46f 100644 --- a/packages/@aws-cdk/aws-route53-targets/README.md +++ b/packages/@aws-cdk/aws-route53-targets/README.md @@ -13,7 +13,7 @@ This library contains Route53 Alias Record targets for: new route53.ARecord(this, 'AliasRecord', { zone, target: route53.RecordTarget.fromAlias(new alias.ApiGateway(restApi)), - // or - route53.RecordTarget.fromAlias(new alias.ApiGatewayDomainName(domainName)), + // or - route53.RecordTarget.fromAlias(new alias.ApiGatewayDomain(domainName)), }); ``` * CloudFront distributions @@ -28,7 +28,7 @@ This library contains Route53 Alias Record targets for: new route53.ARecord(this, 'AliasRecord', { zone, target: route53.RecordTarget.fromAlias(new alias.LoadBalancerTarget(elbv2)), - // or - route53.RecordTarget.fromAlias(new alias.ApiGatewayDomainName(domainName)), + // or - route53.RecordTarget.fromAlias(new alias.ApiGatewayDomain(domainName)), }); ``` * Classic load balancers @@ -36,9 +36,14 @@ This library contains Route53 Alias Record targets for: new route53.ARecord(this, 'AliasRecord', { zone, target: route53.RecordTarget.fromAlias(new alias.ClassicLoadBalancerTarget(elb)), - // or - route53.RecordTarget.fromAlias(new alias.ApiGatewayDomainName(domainName)), + // or - route53.RecordTarget.fromAlias(new alias.ApiGatewayDomain(domainName)), }); ``` + +**Important:** Based on [AWS documentation](https://aws.amazon.com/de/premiumsupport/knowledge-center/alias-resource-record-set-route53-cli/), all alias record in Route 53 that points to a Elastic Load Balancer will always include *dualstack* for the DNSName to resolve IPv4/IPv6 addresses (without *dualstack* IPv6 will not resolve). + +For example, if the Amazon-provided DNS for the load balancer is `ALB-xxxxxxx.us-west-2.elb.amazonaws.com`, CDK will create alias target in Route 53 will be `dualstack.ALB-xxxxxxx.us-west-2.elb.amazonaws.com`. + * InterfaceVpcEndpoints **Important:** Based on the CFN docs for VPCEndpoints - [see here](attrDnsEntries) - the attributes returned for DnsEntries in CloudFormation is a combination of the hosted zone ID and the DNS name. The entries are ordered as follows: regional public DNS, zonal public DNS, private DNS, and wildcard DNS. This order is not enforced for AWS Marketplace services, and therefore this CDK construct is ONLY guaranteed to work with non-marketplace services. diff --git a/packages/@aws-cdk/aws-route53-targets/lib/classic-load-balancer-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/classic-load-balancer-target.ts index 076c390e8f704..8b25fbe4f331e 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/classic-load-balancer-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/classic-load-balancer-target.ts @@ -11,7 +11,7 @@ export class ClassicLoadBalancerTarget implements route53.IAliasRecordTarget { public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { return { hostedZoneId: this.loadBalancer.loadBalancerCanonicalHostedZoneNameId, - dnsName: this.loadBalancer.loadBalancerDnsName, + dnsName: `dualstack.${this.loadBalancer.loadBalancerDnsName}`, }; } } diff --git a/packages/@aws-cdk/aws-route53-targets/lib/load-balancer-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/load-balancer-target.ts index c8902b98c0daa..d398256c57f8b 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/load-balancer-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/load-balancer-target.ts @@ -11,7 +11,7 @@ export class LoadBalancerTarget implements route53.IAliasRecordTarget { public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { return { hostedZoneId: this.loadBalancer.loadBalancerCanonicalHostedZoneId, - dnsName: this.loadBalancer.loadBalancerDnsName, + dnsName: `dualstack.${this.loadBalancer.loadBalancerDnsName}`, }; } } diff --git a/packages/@aws-cdk/aws-route53-targets/test/classic-load-balancer-target.test.ts b/packages/@aws-cdk/aws-route53-targets/test/classic-load-balancer-target.test.ts index 6aea222305741..59e9326e7b23e 100644 --- a/packages/@aws-cdk/aws-route53-targets/test/classic-load-balancer-target.test.ts +++ b/packages/@aws-cdk/aws-route53-targets/test/classic-load-balancer-target.test.ts @@ -28,7 +28,7 @@ test('use classic ELB as record target', () => { // THEN expect(stack).toHaveResource('AWS::Route53::RecordSet', { AliasTarget: { - DNSName: { 'Fn::GetAtt': [ 'LB8A12904C', 'DNSName' ] }, + DNSName: { 'Fn::Join': [ '', [ 'dualstack.', { 'Fn::GetAtt': [ 'LB8A12904C', 'DNSName' ] } ] ] }, HostedZoneId: { 'Fn::GetAtt': [ 'LB8A12904C', 'CanonicalHostedZoneNameID' ] }, }, }); diff --git a/packages/@aws-cdk/aws-route53-targets/test/integ.alb-alias-target.expected.json b/packages/@aws-cdk/aws-route53-targets/test/integ.alb-alias-target.expected.json index 828c0d7f4b6a5..746f62cc9acc7 100644 --- a/packages/@aws-cdk/aws-route53-targets/test/integ.alb-alias-target.expected.json +++ b/packages/@aws-cdk/aws-route53-targets/test/integ.alb-alias-target.expected.json @@ -413,9 +413,17 @@ "Type": "A", "AliasTarget": { "DNSName": { - "Fn::GetAtt": [ - "LB8A12904C", - "DNSName" + "Fn::Join": [ + "", + [ + "dualstack.", + { + "Fn::GetAtt": [ + "LB8A12904C", + "DNSName" + ] + } + ] ] }, "HostedZoneId": { diff --git a/packages/@aws-cdk/aws-route53-targets/test/load-balancer-target.test.ts b/packages/@aws-cdk/aws-route53-targets/test/load-balancer-target.test.ts index 50ff50a1a122e..396d2d457bb85 100644 --- a/packages/@aws-cdk/aws-route53-targets/test/load-balancer-target.test.ts +++ b/packages/@aws-cdk/aws-route53-targets/test/load-balancer-target.test.ts @@ -28,7 +28,7 @@ test('use ALB as record target', () => { // THEN expect(stack).toHaveResource('AWS::Route53::RecordSet', { AliasTarget: { - DNSName: { 'Fn::GetAtt': [ 'LB8A12904C', 'DNSName' ] }, + DNSName: { 'Fn::Join': [ '', [ 'dualstack.', { 'Fn::GetAtt': [ 'LB8A12904C', 'DNSName' ] } ] ] }, HostedZoneId: { 'Fn::GetAtt': [ 'LB8A12904C', 'CanonicalHostedZoneID' ] }, }, }); diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 60b155b6bcd7f..b6d65266506ec 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.703.0", + "aws-sdk": "^2.706.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts b/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts index 1ca13bb1e763d..a1ea5ac5ac42c 100644 --- a/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts +++ b/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts @@ -13,7 +13,7 @@ export class LambdaDestination implements s3.IBucketNotificationDestination { public bind(_scope: Construct, bucket: s3.IBucket): s3.BucketNotificationDestinationConfig { const permissionId = `AllowBucketNotificationsFrom${bucket.node.uniqueId}`; - if (this.fn.node.tryFindChild(permissionId) === undefined) { + if (this.fn.permissionsNode.tryFindChild(permissionId) === undefined) { this.fn.addPermission(permissionId, { sourceAccount: Stack.of(bucket).account, principal: new iam.ServicePrincipal('s3.amazonaws.com'), @@ -23,7 +23,7 @@ export class LambdaDestination implements s3.IBucketNotificationDestination { // if we have a permission resource for this relationship, add it as a dependency // to the bucket notifications resource, so it will be created first. - const permission = this.fn.node.tryFindChild(permissionId) as CfnResource | undefined; + const permission = this.fn.permissionsNode.tryFindChild(permissionId) as CfnResource | undefined; return { type: s3.BucketNotificationDestinationType.LAMBDA, diff --git a/packages/@aws-cdk/aws-s3-notifications/test/lambda/lambda.test.ts b/packages/@aws-cdk/aws-s3-notifications/test/lambda/lambda.test.ts index bc36b562776fd..76563c2b4cccc 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/lambda/lambda.test.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/lambda/lambda.test.ts @@ -70,3 +70,52 @@ test('lambda as notification target specified by function arn', () => { }, }); }); + +test('permissions are added as a dependency to the notifications resource when using singleton function', () => { + + const stack = new Stack(); + const bucket = new s3.Bucket(stack, 'MyBucket'); + const fn = new lambda.SingletonFunction(stack, 'MyFunction', { + uuid: 'uuid', + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.fromInline('foo'), + }); + + const lambdaDestination = new s3n.LambdaDestination(fn); + + bucket.addEventNotification(s3.EventType.OBJECT_CREATED, lambdaDestination, { prefix: 'v1/'}); + + const notifications = stack.node.findAll().filter(c => c.node.id === 'Notifications')[0]; + const dependencies = notifications!.node.dependencies; + + expect(dependencies[0].target.node.id).toEqual('AllowBucketNotificationsFromMyBucket'); + +}); + +test('add multiple event notifications using a singleton function', () => { + + const stack = new Stack(); + const bucket = new s3.Bucket(stack, 'MyBucket'); + const fn = new lambda.SingletonFunction(stack, 'MyFunction', { + uuid: 'uuid', + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.fromInline('foo'), + }); + + const lambdaDestination = new s3n.LambdaDestination(fn); + + bucket.addEventNotification(s3.EventType.OBJECT_CREATED, lambdaDestination, { prefix: 'v1/'}); + bucket.addEventNotification(s3.EventType.OBJECT_CREATED, lambdaDestination, { prefix: 'v2/'}); + + expect(stack).toHaveResourceLike('Custom::S3BucketNotifications', { + NotificationConfiguration: { + LambdaFunctionConfigurations: [ + { Filter: { Key: { FilterRules: [{ Name: 'prefix', Value: 'v1/'}]}}}, + { Filter: { Key: { FilterRules: [{ Name: 'prefix', Value: 'v2/'}]}}}, + ], + }, + }); + +}); diff --git a/packages/@aws-cdk/aws-sam/package.json b/packages/@aws-cdk/aws-sam/package.json index f0c48b343da21..f49d0c41dc349 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": "^26.0.0", + "@types/jest": "^26.0.3", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", "jest": "^25.5.4", diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/service.ts b/packages/@aws-cdk/aws-servicediscovery/lib/service.ts index f81d9fd043376..c41625bf68a7e 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/service.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/service.ts @@ -123,7 +123,7 @@ export interface DnsServiceProps extends BaseServiceProps { export interface ServiceProps extends DnsServiceProps { /** - * The ID of the namespace that you want to use for DNS configuration. + * The namespace that you want to use for DNS configuration. */ readonly namespace: INamespace; } diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index f2dc1fc22533f..ebc4924d0ce86 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.703.0", + "aws-sdk": "^2.706.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md index 907fd5f5b76a1..239c943674c2e 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md @@ -140,7 +140,7 @@ Most tasks take parameters. Parameter values can either be static, supplied dire in the workflow definition (by specifying their values), or a value available at runtime in the state machine's execution (either as its input or an output of a prior state). Parameter values available at runtime can be specified via the `Data` class, -using methods such as `Data.stringAt()`. +using methods such as `JsonPath.stringAt()`. The following example provides the field named `input` as the input to the Lambda function and invokes it asynchronously. @@ -148,7 +148,7 @@ and invokes it asynchronously. ```ts const submitJob = new tasks.LambdaInvoke(stack, 'Invoke Handler', { lambdaFunction: submitJobLambda, - payload: sfn.Data.StringAt('$.input'), + payload: sfn.JsonPath.StringAt('$.input'), invocationType: tasks.InvocationType.EVENT, }); ``` @@ -164,25 +164,22 @@ Use the `EvaluateExpression` to perform simple operations referencing state path Example: convert a wait time from milliseconds to seconds, concat this in a message and wait: ```ts -const convertToSeconds = new sfn.Task(this, 'Convert to seconds', { - task: new tasks.EvaluateExpression({ expression: '$.waitMilliseconds / 1000' }), - resultPath: '$.waitSeconds' +const convertToSeconds = new tasks.EvaluateExpression(this, 'Convert to seconds', { + expression: '$.waitMilliseconds / 1000', + resultPath: '$.waitSeconds', }); -const createMessage = new sfn.Task(this, 'Create message', { +const createMessage = new tasks.EvaluateExpression(this, 'Create message', { // Note: this is a string inside a string. - task: new tasks.EvaluateExpression({ expression: '`Now waiting ${$.waitSeconds} seconds...`', runtime: lambda.Runtime.NODEJS_10_X, - }), - resultPath: '$.message' + resultPath: '$.message', }); -const publishMessage = new sfn.Task(this, 'Publish message', { - task: new tasks.PublishToTopic(topic, { - message: sfn.TaskInput.fromDataAt('$.message'), - }), - resultPath: '$.sns' +const publishMessage = new tasks.SnsPublish(this, 'Publish message', { + topic, + message: sfn.TaskInput.fromDataAt('$.message'), + resultPath: '$.sns', }); const wait = new sfn.Wait(this, 'Wait', { @@ -261,7 +258,7 @@ The [PutItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API new tasks.DynamoPutItem(this, 'PutItem', { item: { MessageId: tasks.DynamoAttributeValue.fromString('message-007'), - Text: tasks.DynamoAttributeValue.fromString(sfn.Data.stringAt('$.bar')), + Text: tasks.DynamoAttributeValue.fromString(sfn.JsonPath.stringAt('$.bar')), TotalCount: tasks.DynamoAttributeValue.fromNumber(10), }, table, @@ -273,10 +270,13 @@ new tasks.DynamoPutItem(this, 'PutItem', { The [DeleteItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html) operation deletes a single item in a table by primary key. ```ts +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as tasks from '@aws-cdk/aws-stepfunctions-tasks'; + new tasks.DynamoDeleteItem(this, 'DeleteItem', { key: { MessageId: tasks.DynamoAttributeValue.fromString('message-007') }, table, - resultPath: 'DISCARD', + resultPath: sfn.JsonPath.DISCARD, }); ``` @@ -290,7 +290,7 @@ new tasks.DynamoUpdateItem(this, 'UpdateItem', { key: { MessageId: tasks.DynamoAttributeValue.fromString('message-007') }, table, expressionAttributeValues: { - ':val': tasks.DynamoAttributeValue.numberFromString(sfn.Data.stringAt('$.Item.TotalCount.N')), + ':val': tasks.DynamoAttributeValue.numberFromString(sfn.JsonPath.stringAt('$.Item.TotalCount.N')), ':rand': tasks.DynamoAttributeValue.fromNumber(20), }, updateExpression: 'SET TotalCount = :val + :rand', @@ -319,7 +319,7 @@ new ecs.RunEcsFargateTask({ environment: [ { name: 'CONTAINER_INPUT', - value: Data.stringAt('$.valueFromStateData'), + value: JsonPath.stringAt('$.valueFromStateData'), } ] } @@ -458,7 +458,7 @@ Corresponds to the [`modifyInstanceGroups`](https://docs.aws.amazon.com/emr/late ```ts new tasks.EmrModifyInstanceGroupByName(stack, 'Task', { clusterId: 'ClusterId', - instanceGroupName: sfn.Data.stringAt('$.InstanceGroupName'), + instanceGroupName: sfn.JsonPath.stringAt('$.InstanceGroupName'), instanceGroup: { instanceCount: 1, }, @@ -562,8 +562,8 @@ new tasks.LambdaInvoke(stack, 'Invoke with callback', { lambdaFunction: myLambda, integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, payload: sfn.TaskInput.fromObject({ - token: sfn.Context.taskToken, - input: sfn.Data.stringAt('$.someField'), + token: sfn.JsonPath.taskToken, + input: sfn.JsonPath.stringAt('$.someField'), }), }); ``` @@ -582,7 +582,7 @@ You can call the [`CreateTrainingJob`](https://docs.aws.amazon.com/sagemaker/lat ```ts new sfn.SagemakerTrainTask(this, 'TrainSagemaker', { - trainingJobName: sfn.Data.stringAt('$.JobName'), + trainingJobName: sfn.JsonPath.stringAt('$.JobName'), role, algorithmSpecification: { algorithmName: 'BlazingText', @@ -667,7 +667,7 @@ const task2 = new tasks.SnsPublish(this, 'Publish2', { topic, message: sfn.TaskInput.fromObject({ field1: 'somedata', - field2: sfn.Data.stringAt('$.field2'), + field2: sfn.JsonPath.stringAt('$.field2'), }) }); ``` @@ -689,7 +689,7 @@ const task = new StepFunctionsStartExecution(stack, 'ChildTask', { stateMachine: child, integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, input: sfn.TaskInput.fromObject({ - token: sfn.Context.taskToken, + token: sfn.JsonPath.taskToken, foo: 'bar' }), name: 'MyExecutionName' @@ -729,7 +729,7 @@ const task2 = new tasks.SqsSendMessage(this, 'Send2', { queue, messageBody: sfn.TaskInput.fromObject({ field1: 'somedata', - field2: sfn.Data.stringAt('$.field2'), + field2: sfn.JsonPath.stringAt('$.field2'), }), }); ``` diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/submit-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/submit-job.ts index ee9577cbd6ac1..c376a4ce91d26 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/submit-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/submit-job.ts @@ -213,7 +213,10 @@ export class BatchSubmitJob extends sfn.TaskStateBase { this.taskPolicies = this.configurePolicyStatements(); } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: integrationResourceArn('batch', 'submitJob', this.integrationPattern), Parameters: sfn.FieldUtils.renderObject({ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/delete-item.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/delete-item.ts index 174698241a1f8..f79c40d46f964 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/delete-item.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/delete-item.ts @@ -105,7 +105,10 @@ export class DynamoDeleteItem extends sfn.TaskStateBase { ]; } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: getDynamoResourceArn(DynamoMethod.DELETE), Parameters: sfn.FieldUtils.renderObject({ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/get-item.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/get-item.ts index 3cef6bd994fdc..ac361b8c9cb1b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/get-item.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/get-item.ts @@ -87,7 +87,10 @@ export class DynamoGetItem extends sfn.TaskStateBase { ]; } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: getDynamoResourceArn(DynamoMethod.GET), Parameters: sfn.FieldUtils.renderObject({ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/put-item.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/put-item.ts index c23842bf5d98f..4d238765f2247 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/put-item.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/put-item.ts @@ -103,7 +103,10 @@ export class DynamoPutItem extends sfn.TaskStateBase { ]; } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: getDynamoResourceArn(DynamoMethod.PUT), Parameters: sfn.FieldUtils.renderObject({ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/update-item.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/update-item.ts index 63c424beb7b20..5e5f871737221 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/update-item.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/update-item.ts @@ -115,7 +115,10 @@ export class DynamoUpdateItem extends sfn.TaskStateBase { ]; } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: getDynamoResourceArn(DynamoMethod.UPDATE), Parameters: sfn.FieldUtils.renderObject({ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts index 1bc456d7c47c4..fc4d5f6efc448 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts @@ -83,7 +83,7 @@ export class EcsRunTaskBase implements ec2.IConnectable, sfn.IStepFunctionsTask if (this.integrationPattern === sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN && !sfn.FieldUtils.containsTaskToken(props.containerOverrides)) { - throw new Error('Task Token is missing in containerOverrides (pass Context.taskToken somewhere in containerOverrides)'); + throw new Error('Task Token is missing in containerOverrides (pass JsonPath.taskToken somewhere in containerOverrides)'); } for (const override of this.props.containerOverrides || []) { 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 2faef1a7c978e..a745bfccd6205 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 @@ -122,7 +122,10 @@ export class EmrAddStep extends sfn.TaskStateBase { this.taskPolicies = this.createPolicyStatements(); } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: integrationResourceArn('elasticmapreduce', 'addStep', this.integrationPattern), Parameters: sfn.FieldUtils.renderObject({ 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 4252056e1cb50..bf14e86085118 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 @@ -40,7 +40,10 @@ export class EmrCancelStep extends sfn.TaskStateBase { ]; } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: integrationResourceArn('elasticmapreduce', 'cancelStep', sfn.IntegrationPattern.REQUEST_RESPONSE), Parameters: sfn.FieldUtils.renderObject({ 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 b33f12e5d8863..e291448e92019 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 @@ -230,7 +230,10 @@ export class EmrCreateCluster extends sfn.TaskStateBase { return this._autoScalingRole; } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: integrationResourceArn('elasticmapreduce', 'createCluster', this.integrationPattern), Parameters: sfn.FieldUtils.renderObject({ 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 cdb6bc8f3f4fd..d4ed7436adba7 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 @@ -62,7 +62,10 @@ export class EmrModifyInstanceFleetByName extends sfn.TaskStateBase { ]; } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: integrationResourceArn('elasticmapreduce', 'modifyInstanceFleetByName', sfn.IntegrationPattern.REQUEST_RESPONSE), 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 30cfc572b262e..805f59dc52518 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 @@ -50,7 +50,10 @@ export class EmrModifyInstanceGroupByName extends sfn.TaskStateBase { ]; } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: integrationResourceArn('elasticmapreduce', 'modifyInstanceGroupByName', sfn.IntegrationPattern.REQUEST_RESPONSE), Parameters: sfn.FieldUtils.renderObject({ 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 c2b52f0cc9a4f..ff983869b8b67 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 @@ -41,7 +41,10 @@ export class EmrSetClusterTerminationProtection extends sfn.TaskStateBase { ]; } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: integrationResourceArn('elasticmapreduce', 'setClusterTerminationProtection', sfn.IntegrationPattern.REQUEST_RESPONSE), 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 3aa9d1dc5a138..9e23ee1bdc8b1 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 @@ -39,7 +39,10 @@ export class EmrTerminateCluster extends sfn.TaskStateBase { this.taskPolicies = this.createPolicyStatements(); } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: integrationResourceArn('elasticmapreduce', 'terminateCluster', this.integrationPattern), Parameters: sfn.FieldUtils.renderObject({ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts index b2a630681bb83..e07da1a16493b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts @@ -9,7 +9,7 @@ import * as path from 'path'; * * @experimental */ -export interface EvaluateExpressionProps { +export interface EvaluateExpressionProps extends sfn.TaskStateBaseProps { /** * The expression to evaluate. The expression may contain state paths. * @@ -49,11 +49,29 @@ export interface Event { * * @experimental */ -export class EvaluateExpression implements sfn.IStepFunctionsTask { - constructor(private readonly props: EvaluateExpressionProps) { +export class EvaluateExpression extends sfn.TaskStateBase { + protected readonly taskMetrics?: sfn.TaskMetricsConfig; + protected readonly taskPolicies?: iam.PolicyStatement[]; + + private readonly evalFn: lambda.SingletonFunction; + + constructor(scope: cdk.Construct, id: string, private readonly props: EvaluateExpressionProps) { + super(scope, id, props); + + this.evalFn = createEvalFn(this.props.runtime || lambda.Runtime.NODEJS_10_X, this); + + this.taskPolicies = [ + new iam.PolicyStatement({ + resources: [this.evalFn.functionArn], + actions: ['lambda:InvokeFunction'], + }), + ]; } - public bind(task: sfn.Task): sfn.StepFunctionsTaskConfig { + /** + * @internal + */ + protected _renderTask(): any { const matches = this.props.expression.match(/\$[.\[][.a-zA-Z[\]0-9]+/g); let expressionAttributeValues = {}; @@ -61,25 +79,19 @@ export class EvaluateExpression implements sfn.IStepFunctionsTask { expressionAttributeValues = matches.reduce( (acc, m) => ({ ...acc, - [m]: sfn.Data.stringAt(m), // It's okay to always use `stringAt` here + [m]: sfn.JsonPath.stringAt(m), // It's okay to always use `stringAt` here }), {}, ); } - const evalFn = createEvalFn(this.props.runtime || lambda.Runtime.NODEJS_10_X, task); - const parameters: Event = { expression: this.props.expression, expressionAttributeValues, }; return { - resourceArn: evalFn.functionArn, - policyStatements: [new iam.PolicyStatement({ - resources: [evalFn.functionArn], - actions: ['lambda:InvokeFunction'], - })], - parameters, + Resource: this.evalFn.functionArn, + Parameters: sfn.FieldUtils.renderObject(parameters), }; } } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/glue/start-job-run.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/glue/start-job-run.ts index 9df1a6a5ed852..0618e413ccb9e 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/glue/start-job-run.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/glue/start-job-run.ts @@ -77,7 +77,10 @@ export class GlueStartJobRun extends sfn.TaskStateBase { }; } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { const notificationProperty = this.props.notifyDelayAfter ? { NotifyDelayAfter: this.props.notifyDelayAfter.toMinutes() } : null; return { Resource: integrationResourceArn('glue', 'startJobRun', this.integrationPattern), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/lambda/invoke.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/lambda/invoke.ts index ce7cd397f48c8..90ac0c2f0ab33 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/lambda/invoke.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/lambda/invoke.ts @@ -73,7 +73,7 @@ export class LambdaInvoke extends sfn.TaskStateBase { if (this.integrationPattern === sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN && !sfn.FieldUtils.containsTaskToken(props.payload)) { - throw new Error('Task Token is required in `payload` for callback. Use Context.taskToken to set the token.'); + throw new Error('Task Token is required in `payload` for callback. Use JsonPath.taskToken to set the token.'); } this.taskMetrics = { @@ -96,7 +96,10 @@ export class LambdaInvoke extends sfn.TaskStateBase { /** * Provides the Lambda Invoke service integration task configuration */ - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: integrationResourceArn('lambda', 'invoke', this.integrationPattern), Parameters: sfn.FieldUtils.renderObject({ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/lambda/run-lambda-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/lambda/run-lambda-task.ts index c3ed91e724ca2..cf240ccbfa86f 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/lambda/run-lambda-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/lambda/run-lambda-task.ts @@ -22,7 +22,7 @@ export interface RunLambdaTaskProps { * The valid value for Lambda is either FIRE_AND_FORGET or WAIT_FOR_TASK_TOKEN, * it determines whether to pause the workflow until a task token is returned. * - * If this is set to WAIT_FOR_TASK_TOKEN, the Context.taskToken value must be included + * If this is set to WAIT_FOR_TASK_TOKEN, the JsonPath.taskToken value must be included * somewhere in the payload and the Lambda must call * `SendTaskSuccess/SendTaskFailure` using that token. * @@ -79,7 +79,7 @@ export class RunLambdaTask implements sfn.IStepFunctionsTask { if (this.integrationPattern === sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN && !sfn.FieldUtils.containsTaskToken(props.payload)) { - throw new Error('Task Token is missing in payload (pass Context.taskToken somewhere in payload)'); + throw new Error('Task Token is missing in payload (pass JsonPath.taskToken somewhere in payload)'); } } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts index 6f1c5f03dcc37..f701f9e20d6e9 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts @@ -308,7 +308,7 @@ export abstract class S3Location { * @param expression the JSON expression resolving to an S3 location URI. */ public static fromJsonExpression(expression: string): S3Location { - return new StandardS3Location({ uri: sfn.Data.stringAt(expression) }); + return new StandardS3Location({ uri: sfn.JsonPath.stringAt(expression) }); } /** diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts index f541a0e692a4f..f8b4c73d00320 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts @@ -204,7 +204,10 @@ export class SageMakerCreateTrainingJob extends sfn.TaskStateBase implements iam this.securityGroups.push(securityGroup); } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: integrationResourceArn('sagemaker', 'createTrainingJob', this.integrationPattern), Parameters: sfn.FieldUtils.renderObject(this.renderParameters()), @@ -374,7 +377,7 @@ export class SageMakerCreateTrainingJob extends sfn.TaskStateBase implements iam service: 'sagemaker', resource: 'training-job', // If the job name comes from input, we cannot target the policy to a particular ARN prefix reliably... - resourceName: sfn.Data.isJsonPathString(this.props.trainingJobName) ? '*' : `${this.props.trainingJobName}*`, + resourceName: sfn.JsonPath.isEncodedJsonPath(this.props.trainingJobName) ? '*' : `${this.props.trainingJobName}*`, }), ], }), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-transform-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-transform-job.ts index 111a15500443e..b9bdcbf940d3d 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-transform-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-transform-job.ts @@ -134,7 +134,10 @@ export class SageMakerCreateTransformJob extends sfn.TaskStateBase { this.taskPolicies = this.makePolicyStatements(); } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: integrationResourceArn('sagemaker', 'createTransformJob', this.integrationPattern), Parameters: sfn.FieldUtils.renderObject(this.renderParameters()), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sns/publish-to-topic.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sns/publish-to-topic.ts index 3473984ada2fa..bc07f33b2bc9b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sns/publish-to-topic.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sns/publish-to-topic.ts @@ -71,7 +71,7 @@ export class PublishToTopic implements sfn.IStepFunctionsTask { if (this.integrationPattern === sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN) { if (!sfn.FieldUtils.containsTaskToken(props.message)) { - throw new Error('Task Token is missing in message (pass Context.taskToken somewhere in message)'); + throw new Error('Task Token is missing in message (pass JsonPath.taskToken somewhere in message)'); } } } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sns/publish.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sns/publish.ts index 273257f98743c..051b217b6d042 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sns/publish.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sns/publish.ts @@ -73,7 +73,7 @@ export class SnsPublish extends sfn.TaskStateBase { if (this.integrationPattern === sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN) { if (!sfn.FieldUtils.containsTaskToken(props.message)) { - throw new Error('Task Token is required in `message` Use Context.taskToken to set the token.'); + throw new Error('Task Token is required in `message` Use JsonPath.taskToken to set the token.'); } } @@ -88,7 +88,10 @@ export class SnsPublish extends sfn.TaskStateBase { /** * Provides the SNS Publish service integration task configuration */ - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: integrationResourceArn('sns', 'publish', this.integrationPattern), Parameters: sfn.FieldUtils.renderObject({ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sqs/send-message.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sqs/send-message.ts index c7ec4adb18517..e1a2ae6d5118e 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sqs/send-message.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sqs/send-message.ts @@ -73,7 +73,7 @@ export class SqsSendMessage extends sfn.TaskStateBase { if (props.integrationPattern === sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN) { if (!sfn.FieldUtils.containsTaskToken(props.messageBody)) { - throw new Error('Task Token is required in `messageBody` Use Context.taskToken to set the token.'); + throw new Error('Task Token is required in `messageBody` Use JsonPath.taskToken to set the token.'); } } @@ -88,7 +88,10 @@ export class SqsSendMessage extends sfn.TaskStateBase { /** * Provides the SQS SendMessage service integration task configuration */ - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: integrationResourceArn('sqs', 'sendMessage', this.integrationPattern), Parameters: sfn.FieldUtils.renderObject({ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sqs/send-to-queue.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sqs/send-to-queue.ts index 4d7dce33f9cf6..36c1d1e7ac6be 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sqs/send-to-queue.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sqs/send-to-queue.ts @@ -77,7 +77,7 @@ export class SendToQueue implements sfn.IStepFunctionsTask { if (props.integrationPattern === sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN) { if (!sfn.FieldUtils.containsTaskToken(props.messageBody)) { - throw new Error('Task Token is missing in messageBody (pass Context.taskToken somewhere in messageBody)'); + throw new Error('Task Token is missing in messageBody (pass JsonPath.taskToken somewhere in messageBody)'); } } } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/start-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/start-execution.ts index 9ec81c367e4df..0572459f1e4a3 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/start-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/start-execution.ts @@ -62,7 +62,7 @@ export class StartExecution implements sfn.IStepFunctionsTask { if (this.integrationPattern === sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN && !sfn.FieldUtils.containsTaskToken(props.input)) { - throw new Error('Task Token is missing in input (pass Context.taskToken somewhere in input)'); + throw new Error('Task Token is missing in input (pass JsonPath.taskToken somewhere in input)'); } } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts index 5677d5d89021f..405e3be6a74fc 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts @@ -55,13 +55,16 @@ export class StepFunctionsStartExecution extends sfn.TaskStateBase { validatePatternSupported(this.integrationPattern, StepFunctionsStartExecution.SUPPORTED_INTEGRATION_PATTERNS); if (this.integrationPattern === sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN && !sfn.FieldUtils.containsTaskToken(props.input)) { - throw new Error('Task Token is required in `input` for callback. Use Context.taskToken to set the token.'); + throw new Error('Task Token is required in `input` for callback. Use JsonPath.taskToken to set the token.'); } this.taskPolicies = this.createScopedAccessPolicy(); } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { // suffix of ':2' indicates that the output of the nested state machine should be JSON // suffix is only applicable when waiting for a nested state machine to complete (RUN_JOB) // https://docs.aws.amazon.com/step-functions/latest/dg/connect-stepfunctions.html diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.ts index b1c53f9a80710..944039ed40bde 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.ts @@ -51,7 +51,7 @@ class RunBatchStack extends cdk.Stack { vcpus: 1, }, payload: { - foo: sfn.Data.stringAt('$.bar'), + foo: sfn.JsonPath.stringAt('$.bar'), }, attempts: 3, timeout: cdk.Duration.seconds(60), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.ts index 86e891d4331ed..5a89db1913334 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.ts @@ -50,7 +50,7 @@ class RunBatchStack extends cdk.Stack { vcpus: 1, }, payload: sfn.TaskInput.fromObject({ - foo: sfn.Data.stringAt('$.bar'), + foo: sfn.JsonPath.stringAt('$.bar'), }), attempts: 3, timeout: cdk.Duration.seconds(60), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/run-batch-job.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/run-batch-job.test.ts index f6be966c1cb85..a9b93a3dd2f2b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/run-batch-job.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/run-batch-job.test.ts @@ -86,7 +86,7 @@ test('Task with all the parameters', () => { }, dependsOn: [{ jobId: '1234', type: 'some_type' }], payload: { - foo: sfn.Data.stringAt('$.bar'), + foo: sfn.JsonPath.stringAt('$.bar'), }, attempts: 3, timeout: cdk.Duration.seconds(60), @@ -136,11 +136,11 @@ test('supports tokens', () => { const task = new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ jobDefinition: batchJobDefinition, - jobName: sfn.Data.stringAt('$.jobName'), + jobName: sfn.JsonPath.stringAt('$.jobName'), jobQueue: batchJobQueue, - arraySize: sfn.Data.numberAt('$.arraySize'), - timeout: cdk.Duration.seconds(sfn.Data.numberAt('$.timeout')), - attempts: sfn.Data.numberAt('$.attempts'), + arraySize: sfn.JsonPath.numberAt('$.arraySize'), + timeout: cdk.Duration.seconds(sfn.JsonPath.numberAt('$.timeout')), + attempts: sfn.JsonPath.numberAt('$.attempts'), }), }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/submit-job.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/submit-job.test.ts index 6538e8bc1733e..92c8ebeb597b8 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/submit-job.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/submit-job.test.ts @@ -83,7 +83,7 @@ test('Task with all the parameters', () => { }, dependsOn: [{ jobId: '1234', type: 'some_type' }], payload: sfn.TaskInput.fromObject({ - foo: sfn.Data.stringAt('$.bar'), + foo: sfn.JsonPath.stringAt('$.bar'), }), attempts: 3, timeout: cdk.Duration.seconds(60), @@ -131,11 +131,11 @@ test('supports tokens', () => { // WHEN const task = new BatchSubmitJob(stack, 'Task', { jobDefinition: batchJobDefinition, - jobName: sfn.Data.stringAt('$.jobName'), + jobName: sfn.JsonPath.stringAt('$.jobName'), jobQueue: batchJobQueue, - arraySize: sfn.Data.numberAt('$.arraySize'), - timeout: cdk.Duration.seconds(sfn.Data.numberAt('$.timeout')), - attempts: sfn.Data.numberAt('$.attempts'), + arraySize: sfn.JsonPath.numberAt('$.arraySize'), + timeout: cdk.Duration.seconds(sfn.JsonPath.numberAt('$.timeout')), + attempts: sfn.JsonPath.numberAt('$.attempts'), }); // THEN @@ -175,7 +175,7 @@ test('supports passing task input into payload', () => { // WHEN const task = new BatchSubmitJob(stack, 'Task', { jobDefinition: batchJobDefinition, - jobName: sfn.Data.stringAt('$.jobName'), + jobName: sfn.JsonPath.stringAt('$.jobName'), jobQueue: batchJobQueue, payload: sfn.TaskInput.fromDataAt('$.foo'), }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/delete-item.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/delete-item.test.ts index 031055d1d0f8c..4a278249f4585 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/delete-item.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/delete-item.test.ts @@ -26,7 +26,7 @@ test('DeleteItem task', () => { conditionExpression: 'ForumName <> :f and Subject <> :s', expressionAttributeNames: { OTHER_KEY: '#OK' }, expressionAttributeValues: { - ':val': tasks.DynamoAttributeValue.numberFromString(sfn.Data.stringAt('$.Item.TotalCount.N')), + ':val': tasks.DynamoAttributeValue.numberFromString(sfn.JsonPath.stringAt('$.Item.TotalCount.N')), }, returnConsumedCapacity: tasks.DynamoConsumedCapacity.TOTAL, returnItemCollectionMetrics: tasks.DynamoItemCollectionMetrics.SIZE, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/get-item.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/get-item.test.ts index b09664737a4ef..2991c762c025d 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/get-item.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/get-item.test.ts @@ -68,12 +68,12 @@ test('supports tokens', () => { // WHEN const task = new tasks.DynamoGetItem(stack, 'GetItem', { key: { - SOME_KEY: tasks.DynamoAttributeValue.fromString(sfn.Data.stringAt('$.partitionKey')), - OTHER_KEY: tasks.DynamoAttributeValue.numberFromString(sfn.Data.stringAt('$.sortKey')), + SOME_KEY: tasks.DynamoAttributeValue.fromString(sfn.JsonPath.stringAt('$.partitionKey')), + OTHER_KEY: tasks.DynamoAttributeValue.numberFromString(sfn.JsonPath.stringAt('$.sortKey')), }, table, consistentRead: true, - expressionAttributeNames: { OTHER_KEY: sfn.Data.stringAt('$.otherKey') }, + expressionAttributeNames: { OTHER_KEY: sfn.JsonPath.stringAt('$.otherKey') }, projectionExpression: [ new tasks.DynamoProjectionExpression().withAttribute('Messages').atIndex(1).withAttribute('Tags'), new tasks.DynamoProjectionExpression().withAttribute('ID'), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.ts index 3728a64295122..07e05b7414a9f 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.ts @@ -34,7 +34,7 @@ class CallDynamoDBStack extends cdk.Stack { const putItemTask = new tasks.DynamoPutItem(this, 'PutItem', { item: { MessageId: tasks.DynamoAttributeValue.fromString(MESSAGE_ID), - Text: tasks.DynamoAttributeValue.fromString(sfn.Data.stringAt('$.bar')), + Text: tasks.DynamoAttributeValue.fromString(sfn.JsonPath.stringAt('$.bar')), TotalCount: tasks.DynamoAttributeValue.fromNumber(firstNumber), }, table, @@ -49,7 +49,7 @@ class CallDynamoDBStack extends cdk.Stack { key: { MessageId: tasks.DynamoAttributeValue.fromString(MESSAGE_ID) }, table, expressionAttributeValues: { - ':val': tasks.DynamoAttributeValue.numberFromString(sfn.Data.stringAt('$.Item.TotalCount.N')), + ':val': tasks.DynamoAttributeValue.numberFromString(sfn.JsonPath.stringAt('$.Item.TotalCount.N')), ':rand': tasks.DynamoAttributeValue.fromNumber(secondNumber), }, updateExpression: 'SET TotalCount = :val + :rand', @@ -58,13 +58,13 @@ class CallDynamoDBStack extends cdk.Stack { const getItemTaskAfterUpdate = new tasks.DynamoGetItem(this, 'GetItemAfterUpdate', { key: { MessageId: tasks.DynamoAttributeValue.fromString(MESSAGE_ID) }, table, - outputPath: sfn.Data.stringAt('$.Item.TotalCount.N'), + outputPath: sfn.JsonPath.stringAt('$.Item.TotalCount.N'), }); const deleteItemTask = new tasks.DynamoDeleteItem(this, 'DeleteItem', { key: { MessageId: tasks.DynamoAttributeValue.fromString(MESSAGE_ID) }, table, - resultPath: 'DISCARD', + resultPath: sfn.JsonPath.DISCARD, }); const definition = new sfn.Pass(this, 'Start', { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/put-item.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/put-item.test.ts index bfa6840abe287..a21a4decc0b30 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/put-item.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/put-item.test.ts @@ -26,7 +26,7 @@ test('PutItem task', () => { conditionExpression: 'ForumName <> :f and Subject <> :s', expressionAttributeNames: { OTHER_KEY: '#OK' }, expressionAttributeValues: { - ':val': tasks.DynamoAttributeValue.numberFromString(sfn.Data.stringAt('$.Item.TotalCount.N')), + ':val': tasks.DynamoAttributeValue.numberFromString(sfn.JsonPath.stringAt('$.Item.TotalCount.N')), }, returnConsumedCapacity: tasks.DynamoConsumedCapacity.TOTAL, returnItemCollectionMetrics: tasks.DynamoItemCollectionMetrics.SIZE, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/shared-types.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/shared-types.test.ts index 111be02b15027..d848ef9035132 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/shared-types.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/shared-types.test.ts @@ -43,7 +43,7 @@ describe('DynamoAttributeValue', () => { const s = '$.string'; // WHEN - const attribute = tasks.DynamoAttributeValue.fromString(sfn.Data.stringAt(s)); + const attribute = tasks.DynamoAttributeValue.fromString(sfn.JsonPath.stringAt(s)); // THEN expect(sfn.FieldUtils.renderObject(attribute)).toEqual({ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/update-item.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/update-item.test.ts index fcd38f03f384c..9a4eb3cbcb66a 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/update-item.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/update-item.test.ts @@ -26,7 +26,7 @@ test('UpdateItem task', () => { conditionExpression: 'ForumName <> :f and Subject <> :s', expressionAttributeNames: { OTHER_KEY: '#OK' }, expressionAttributeValues: { - ':val': tasks.DynamoAttributeValue.numberFromString(sfn.Data.stringAt('$.Item.TotalCount.N')), + ':val': tasks.DynamoAttributeValue.numberFromString(sfn.JsonPath.stringAt('$.Item.TotalCount.N')), }, returnConsumedCapacity: tasks.DynamoConsumedCapacity.TOTAL, returnItemCollectionMetrics: tasks.DynamoItemCollectionMetrics.SIZE, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/ecs-tasks.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/ecs-tasks.test.ts index 611339b3fe08a..a644363a9c077 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/ecs-tasks.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/ecs-tasks.test.ts @@ -64,7 +64,7 @@ test('Running a Fargate Task', () => { { containerName: 'TheContainer', environment: [ - {name: 'SOME_KEY', value: sfn.Data.stringAt('$.SomeKey')}, + {name: 'SOME_KEY', value: sfn.JsonPath.stringAt('$.SomeKey')}, ], }, ], @@ -173,7 +173,7 @@ test('Running an EC2 Task with bridge network', () => { { containerName: 'TheContainer', environment: [ - {name: 'SOME_KEY', value: sfn.Data.stringAt('$.SomeKey')}, + {name: 'SOME_KEY', value: sfn.JsonPath.stringAt('$.SomeKey')}, ], }, ], @@ -331,9 +331,9 @@ test('Running an EC2 Task with overridden number values', () => { containerOverrides: [ { containerName: 'TheContainer', - command: sfn.Data.listAt('$.TheCommand'), + command: sfn.JsonPath.listAt('$.TheCommand'), cpu: 5, - memoryLimit: sfn.Data.numberAt('$.MemoryLimit'), + memoryLimit: sfn.JsonPath.numberAt('$.MemoryLimit'), }, ], }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.ec2-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.ec2-task.ts index aa4238f85a218..dc0a42c1d7bd8 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.ec2-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.ec2-task.ts @@ -44,7 +44,7 @@ const definition = new sfn.Pass(stack, 'Start', { environment: [ { name: 'SOME_KEY', - value: sfn.Data.stringAt('$.SomeKey'), + value: sfn.JsonPath.stringAt('$.SomeKey'), }, ], }, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.fargate-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.fargate-task.ts index 8c05adb3dbcba..4f981dfaa45b8 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.fargate-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/integ.fargate-task.ts @@ -43,7 +43,7 @@ const definition = new sfn.Pass(stack, 'Start', { environment: [ { name: 'SOME_KEY', - value: sfn.Data.stringAt('$.SomeKey'), + value: sfn.JsonPath.stringAt('$.SomeKey'), }, ], }, 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 63633412a4933..489c5612b5f2f 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 @@ -51,7 +51,7 @@ test('Add Step with static ClusterId and Step configuration', () => { test('Terminate cluster with ClusterId from payload and static Step configuration', () => { // WHEN const task = new tasks.EmrAddStep(stack, 'Task', { - clusterId: sfn.Data.stringAt('$.ClusterId'), + clusterId: sfn.JsonPath.stringAt('$.ClusterId'), name: 'StepName', jar: 'Jar', actionOnFailure: tasks.ActionOnFailure.CONTINUE, @@ -91,7 +91,7 @@ test('Add Step with static ClusterId and Step Name from payload', () => { // WHEN const task = new tasks.EmrAddStep(stack, 'Task', { clusterId: 'ClusterId', - name: sfn.Data.stringAt('$.StepName'), + name: sfn.JsonPath.stringAt('$.StepName'), jar: 'Jar', actionOnFailure: tasks.ActionOnFailure.CONTINUE, integrationPattern: sfn.IntegrationPattern.RUN_JOB, 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 06d1a2da8d34b..54fdf8a83e63d 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 @@ -813,7 +813,7 @@ test('Create Cluster with InstanceGroup', () => { evaluationPeriods: 1, metricName: 'Name', namespace: 'Namespace', - period: cdk.Duration.seconds(sfn.Data.numberAt('$.CloudWatchPeriod')), + period: cdk.Duration.seconds(sfn.JsonPath.numberAt('$.CloudWatchPeriod')), statistic: EmrCreateCluster.CloudWatchAlarmStatistic.AVERAGE, threshold: 1, unit: EmrCreateCluster.CloudWatchAlarmUnit.NONE, 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 e6612d92b1cf4..bd8fe53e7ff51 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 @@ -48,7 +48,7 @@ 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 tasks.EmrModifyInstanceFleetByName(stack, 'Task', { - clusterId: sfn.Data.stringAt('$.ClusterId'), + clusterId: sfn.JsonPath.stringAt('$.ClusterId'), instanceFleetName: 'InstanceFleetName', targetOnDemandCapacity: 2, targetSpotCapacity: 0, @@ -85,7 +85,7 @@ test('Modify an InstanceFleet with static ClusterId and InstanceFleetConfigurate // WHEN const task = new tasks.EmrModifyInstanceFleetByName(stack, 'Task', { clusterId: 'ClusterId', - instanceFleetName: sfn.Data.stringAt('$.InstanceFleetName'), + instanceFleetName: sfn.JsonPath.stringAt('$.InstanceFleetName'), targetOnDemandCapacity: 2, targetSpotCapacity: 0, }); @@ -122,8 +122,8 @@ test('Modify an InstanceFleet with static ClusterId and InstanceFleetName and Ta const task = new tasks.EmrModifyInstanceFleetByName(stack, 'Task', { clusterId: 'ClusterId', instanceFleetName: 'InstanceFleetName', - targetOnDemandCapacity: sfn.Data.numberAt('$.TargetOnDemandCapacity'), - targetSpotCapacity: sfn.Data.numberAt('$.TargetSpotCapacity'), + targetOnDemandCapacity: sfn.JsonPath.numberAt('$.TargetOnDemandCapacity'), + targetSpotCapacity: sfn.JsonPath.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 f9bc8886de0d3..51bd3600147c1 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 @@ -78,7 +78,7 @@ 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 tasks.EmrModifyInstanceGroupByName(stack, 'Task', { - clusterId: sfn.Data.stringAt('$.ClusterId'), + clusterId: sfn.JsonPath.stringAt('$.ClusterId'), instanceGroupName: 'InstanceGroupName', instanceGroup: { instanceCount: 1, @@ -115,7 +115,7 @@ test('Modify an InstanceGroup with static ClusterId and InstanceGroupConfigurate // WHEN const task = new tasks.EmrModifyInstanceGroupByName(stack, 'Task', { clusterId: 'ClusterId', - instanceGroupName: sfn.Data.stringAt('$.InstanceGroupName'), + instanceGroupName: sfn.JsonPath.stringAt('$.InstanceGroupName'), instanceGroup: { instanceCount: 1, }, @@ -153,7 +153,7 @@ test('Modify an InstanceGroup with static ClusterId and InstanceGroupName and In clusterId: 'ClusterId', instanceGroupName: 'InstanceGroupName', instanceGroup: { - instanceCount: sfn.Data.numberAt('$.InstanceCount'), + instanceCount: sfn.JsonPath.numberAt('$.InstanceCount'), }, }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts index d1235d66a16aa..0bcc3cc3e98c3 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts @@ -10,10 +10,8 @@ beforeEach(() => { test('Eval with Node.js', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EvaluateExpression({ - expression: '$.a + $.b', - }), + const task = new tasks.EvaluateExpression(stack, 'Task', { + expression: '$.a + $.b', }); new sfn.StateMachine(stack, 'SM', { definition: task, @@ -25,14 +23,11 @@ test('Eval with Node.js', () => { 'Fn::Join': [ '', [ - '{"StartAt":"Task","States":{"Task":{"End":true,"Parameters":{"expression":"$.a + $.b","expressionAttributeValues":{"$.a.$":"$.a","$.b.$":"$.b"}},"Type":"Task","Resource":"', + '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', { - 'Fn::GetAtt': [ - 'Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1', - 'Arn', - ], + 'Fn::GetAtt': ['Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1', 'Arn'], }, - '"}}}', + '","Parameters":{"expression":"$.a + $.b","expressionAttributeValues":{"$.a.$":"$.a","$.b.$":"$.b"}}}}}', ], ], }, @@ -45,10 +40,8 @@ test('Eval with Node.js', () => { test('expression does not contain paths', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.EvaluateExpression({ - expression: '2 + 2', - }), + const task = new tasks.EvaluateExpression(stack, 'Task', { + expression: '2 + 2', }); new sfn.StateMachine(stack, 'SM', { definition: task, @@ -59,14 +52,11 @@ test('expression does not contain paths', () => { 'Fn::Join': [ '', [ - '{"StartAt":"Task","States":{"Task":{"End":true,"Parameters":{"expression":"2 + 2",\"expressionAttributeValues\":{}},"Type":"Task","Resource":"', + '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', { - 'Fn::GetAtt': [ - 'Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1', - 'Arn', - ], + 'Fn::GetAtt': ['Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1', 'Arn'], }, - '"}}}', + '","Parameters":{"expression":"2 + 2","expressionAttributeValues":{}}}}}', ], ], }, 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 7c44d5f356e85..efdf3878e67e2 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 @@ -142,40 +142,40 @@ "StateMachine2E01A3A5": { "Type": "AWS::StepFunctions::StateMachine", "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, "DefinitionString": { "Fn::Join": [ "", [ - "{\"StartAt\":\"Sum\",\"States\":{\"Sum\":{\"Next\":\"Multiply\",\"Parameters\":{\"expression\":\"$.a + $.b\",\"expressionAttributeValues\":{\"$.a.$\":\"$.a\",\"$.b.$\":\"$.b\"}},\"Type\":\"Task\",\"Resource\":\"", + "{\"StartAt\":\"Sum\",\"States\":{\"Sum\":{\"Next\":\"Multiply\",\"Type\":\"Task\",\"ResultPath\":\"$.c\",\"Resource\":\"", { "Fn::GetAtt": [ "Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1", "Arn" ] }, - "\",\"ResultPath\":\"$.c\"},\"Multiply\":{\"Next\":\"Wait\",\"Parameters\":{\"expression\":\"$.c * 2\",\"expressionAttributeValues\":{\"$.c.$\":\"$.c\"}},\"Type\":\"Task\",\"Resource\":\"", + "\",\"Parameters\":{\"expression\":\"$.a + $.b\",\"expressionAttributeValues\":{\"$.a.$\":\"$.a\",\"$.b.$\":\"$.b\"}}},\"Multiply\":{\"Next\":\"Wait\",\"Type\":\"Task\",\"ResultPath\":\"$.d\",\"Resource\":\"", { "Fn::GetAtt": [ "Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1", "Arn" ] }, - "\",\"ResultPath\":\"$.d\"},\"Wait\":{\"Type\":\"Wait\",\"SecondsPath\":\"$.d\",\"Next\":\"Now\"},\"Now\":{\"End\":true,\"Parameters\":{\"expression\":\"(new Date()).toUTCString()\",\"expressionAttributeValues\":{}},\"Type\":\"Task\",\"Resource\":\"", + "\",\"Parameters\":{\"expression\":\"$.c * 2\",\"expressionAttributeValues\":{\"$.c.$\":\"$.c\"}}},\"Wait\":{\"Type\":\"Wait\",\"SecondsPath\":\"$.d\",\"Next\":\"Now\"},\"Now\":{\"End\":true,\"Type\":\"Task\",\"ResultPath\":\"$.now\",\"Resource\":\"", { "Fn::GetAtt": [ "Evala0d2ce44871b4e7487a1f5e63d7c3bdc4DAC06E1", "Arn" ] }, - "\",\"ResultPath\":\"$.now\"}}}" + "\",\"Parameters\":{\"expression\":\"(new Date()).toUTCString()\",\"expressionAttributeValues\":{}}}}}" ] ] - }, - "RoleArn": { - "Fn::GetAtt": [ - "StateMachineRoleB840431D", - "Arn" - ] } }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.evaluate-expression.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.evaluate-expression.ts index 0dac6789aeb7c..8034635352b32 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.evaluate-expression.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.evaluate-expression.ts @@ -12,33 +12,29 @@ import * as tasks from '../lib'; const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-stepfunctions-integ'); -const sum = new sfn.Task(stack, 'Sum', { - task: new tasks.EvaluateExpression({ - expression: '$.a + $.b', - }), +const sum = new tasks.EvaluateExpression(stack, 'Sum', { + expression: '$.a + $.b', resultPath: '$.c', }); -const multiply = new sfn.Task(stack, 'Multiply', { - task: new tasks.EvaluateExpression({ - expression: '$.c * 2', - }), +const multiply = new tasks.EvaluateExpression(stack, 'Multiply', { + expression: '$.c * 2', resultPath: '$.d', }); -const now = new sfn.Task(stack, 'Now', { - task: new tasks.EvaluateExpression({ - expression: '(new Date()).toUTCString()', - }), +const now = new tasks.EvaluateExpression(stack, 'Now', { + expression: '(new Date()).toUTCString()', resultPath: '$.now', }); const statemachine = new sfn.StateMachine(stack, 'StateMachine', { definition: sum .next(multiply) - .next(new sfn.Wait(stack, 'Wait', { - time: sfn.WaitTime.secondsPath('$.d'), - })) + .next( + new sfn.Wait(stack, 'Wait', { + time: sfn.WaitTime.secondsPath('$.d'), + }), + ) .next(now), }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.start-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.start-execution.ts index 8624c601f8c83..865e42e9033c5 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.start-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.start-execution.ts @@ -21,7 +21,7 @@ class TestStack extends Stack { definition: new sfn.Task(this, 'Task', { task: new tasks.StartExecution(child, { input: { - hello: sfn.Data.stringAt('$.hello'), + hello: sfn.JsonPath.stringAt('$.hello'), }, integrationPattern: sfn.ServiceIntegrationPattern.SYNC, }), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke-function.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke-function.ts index d3791f59782ed..7ff38d135a72d 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke-function.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke-function.ts @@ -27,7 +27,7 @@ const taskTokenHandler = new sfn.Task(stack, 'Invoke Handler with task token', { task: new tasks.RunLambdaTask(callbackHandler, { integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, payload: sfn.TaskInput.fromObject({ - token: sfn.Context.taskToken, + token: sfn.JsonPath.taskToken, }), }), inputPath: '$.guid', diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts index 2c5b51d4a347e..eb5341281e6b2 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts @@ -39,7 +39,7 @@ test('Lambda function payload ends up in Parameters', () => { definition: new sfn.Task(stack, 'Task', { task: new tasks.InvokeFunction(fn, { payload: { - foo: sfn.Data.stringAt('$.bar'), + foo: sfn.JsonPath.stringAt('$.bar'), }, }), }), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts index 62a6d4c632e23..5bb39f17385ff 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts @@ -106,7 +106,7 @@ describe('LambdaInvoke', () => { lambdaFunction, integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, payload: sfn.TaskInput.fromObject({ - token: sfn.Context.taskToken, + token: sfn.JsonPath.taskToken, }), qualifier: 'my-alias', }); @@ -183,7 +183,7 @@ describe('LambdaInvoke', () => { lambdaFunction, integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, }); - }).toThrow(/Task Token is required in `payload` for callback. Use Context.taskToken to set the token./); + }).toThrow(/Task Token is required in `payload` for callback. Use JsonPath.taskToken to set the token./); }); test('fails when RUN_JOB integration pattern is used', () => { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts index 6d46eeaf368cb..46538a2b41cd6 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts @@ -64,7 +64,7 @@ test('Lambda function can be used in a Task with Task Token', () => { task: new tasks.RunLambdaTask(fn, { integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, payload: sfn.TaskInput.fromObject({ - token: sfn.Context.taskToken, + token: sfn.JsonPath.taskToken, }), }), }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts index 4f02f9ac048a1..5fb84bfee9981 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts @@ -294,7 +294,7 @@ test('pass param to training job', () => { }); const task = new SageMakerCreateTrainingJob(stack, 'TrainSagemaker', { - trainingJobName: sfn.Data.stringAt('$.JobName'), + trainingJobName: sfn.JsonPath.stringAt('$.JobName'), role, algorithmSpecification: { algorithmName: 'BlazingText', diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-transform-job.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-transform-job.test.ts index c53233523cfa7..7777f31e70074 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-transform-job.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-transform-job.test.ts @@ -184,8 +184,8 @@ test('create complex transform job', () => { test('pass param to transform job', () => { // WHEN const task = new SageMakerCreateTransformJob(stack, 'TransformTask', { - transformJobName: sfn.Data.stringAt('$.TransformJobName'), - modelName: sfn.Data.stringAt('$.ModelName'), + transformJobName: sfn.JsonPath.stringAt('$.TransformJobName'), + modelName: sfn.JsonPath.stringAt('$.ModelName'), role, transformInput: { transformDataSource: { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish-to-topic.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish-to-topic.test.ts index 162b3b3eee1f6..38da096791e54 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish-to-topic.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish-to-topic.test.ts @@ -46,7 +46,7 @@ test('Publish JSON to SNS topic with task token', () => { integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, message: sfn.TaskInput.fromObject({ Input: 'Publish this message', - Token: sfn.Context.taskToken, + Token: sfn.JsonPath.taskToken, }), }) }); @@ -93,7 +93,7 @@ test('Task throws if WAIT_FOR_TASK_TOKEN is supplied but task token is not inclu test('Publish to topic with ARN from payload', () => { // GIVEN const stack = new cdk.Stack(); - const topic = sns.Topic.fromTopicArn(stack, 'Topic', sfn.Data.stringAt('$.topicArn')); + const topic = sns.Topic.fromTopicArn(stack, 'Topic', sfn.JsonPath.stringAt('$.topicArn')); // WHEN const pub = new sfn.Task(stack, 'Publish', { task: new tasks.PublishToTopic(topic, { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish.test.ts index c63fb72fb719a..384c578641560 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish.test.ts @@ -50,7 +50,7 @@ describe('Publish', () => { integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, message: sfn.TaskInput.fromObject({ Input: 'Publish this message', - Token: sfn.Context.taskToken, + Token: sfn.JsonPath.taskToken, }), }); @@ -128,7 +128,7 @@ describe('Publish', () => { test('topic ARN supplied through the task input', () => { // GIVEN const stack = new cdk.Stack(); - const topic = sns.Topic.fromTopicArn(stack, 'Topic', sfn.Data.stringAt('$.topicArn')); + const topic = sns.Topic.fromTopicArn(stack, 'Topic', sfn.JsonPath.stringAt('$.topicArn')); // WHEN const task = new SnsPublish(stack, 'Publish', { @@ -172,7 +172,7 @@ describe('Publish', () => { message: sfn.TaskInput.fromText('Publish this message'), }); // THEN - }).toThrow(/Task Token is required in `message` Use Context.taskToken to set the token./); + }).toThrow(/Task Token is required in `message` Use JsonPath.taskToken to set the token./); }); test('fails when RUN_JOB integration pattern is used', () => { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-message.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-message.test.ts index 3ce5cd3cc6a1b..ee1ff021571e8 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-message.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-message.test.ts @@ -48,7 +48,7 @@ describe('SqsSendMessage', () => { const task = new SqsSendMessage(stack, 'Send', { queue, messageBody: sfn.TaskInput.fromText('Send this message'), - messageDeduplicationId: sfn.Data.stringAt('$.deduping'), + messageDeduplicationId: sfn.JsonPath.stringAt('$.deduping'), comment: 'sending a message to my SQS queue', delay: cdk.Duration.seconds(30), }); @@ -86,7 +86,7 @@ describe('SqsSendMessage', () => { integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, messageBody: sfn.TaskInput.fromObject({ Input: 'Send this message', - Token: sfn.Context.taskToken, + Token: sfn.JsonPath.taskToken, }), }); @@ -152,7 +152,7 @@ describe('SqsSendMessage', () => { queue, messageBody: sfn.TaskInput.fromObject({ literal: 'literal', - SomeInput: sfn.Data.stringAt('$.theMessage'), + SomeInput: sfn.JsonPath.stringAt('$.theMessage'), }), }); @@ -223,7 +223,7 @@ describe('SqsSendMessage', () => { integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, messageBody: sfn.TaskInput.fromText('Send this message'), }); - }).toThrow(/Task Token is required in `messageBody` Use Context.taskToken to set the token./); + }).toThrow(/Task Token is required in `messageBody` Use JsonPath.taskToken to set the token./); }); test('fails when RUN_JOB integration pattern is used', () => { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-to-queue.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-to-queue.test.ts index 7b77ba34f2592..69e051c903a55 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-to-queue.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-to-queue.test.ts @@ -16,7 +16,7 @@ test('Send message to queue', () => { // WHEN const task = new sfn.Task(stack, 'Send', { task: new tasks.SendToQueue(queue, { messageBody: sfn.TaskInput.fromText('Send this message'), - messageDeduplicationId: sfn.Data.stringAt('$.deduping'), + messageDeduplicationId: sfn.JsonPath.stringAt('$.deduping'), }) }); // THEN @@ -49,7 +49,7 @@ test('Send message to SQS queue with task token', () => { integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, messageBody: sfn.TaskInput.fromObject({ Input: 'Send this message', - Token: sfn.Context.taskToken, + Token: sfn.JsonPath.taskToken, }), }) }); @@ -127,7 +127,7 @@ test('Message body can be an object', () => { task: new tasks.SendToQueue(queue, { messageBody: sfn.TaskInput.fromObject({ literal: 'literal', - SomeInput: sfn.Data.stringAt('$.theMessage'), + SomeInput: sfn.JsonPath.stringAt('$.theMessage'), }), }), }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/start-execution.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/start-execution.test.ts index 5d1ef3171debc..ee6a252ab09c3 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/start-execution.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/start-execution.test.ts @@ -181,7 +181,7 @@ test('Execute State Machine - Wait For Task Token', () => { task: new tasks.StartExecution(child, { integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, input: { - token: sfn.Context.taskToken, + token: sfn.JsonPath.taskToken, }, }), }); @@ -223,5 +223,5 @@ test('Execute State Machine - Wait For Task Token - Missing Task Token', () => { integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, }), }); - }).toThrow('Task Token is missing in input (pass Context.taskToken somewhere in input'); + }).toThrow('Task Token is missing in input (pass JsonPath.taskToken somewhere in input'); }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.start-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.start-execution.ts index 012189950cecd..f103d41da4a7c 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.start-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.start-execution.ts @@ -21,7 +21,7 @@ class TestStack extends Stack { definition: new StepFunctionsStartExecution(this, 'Task', { stateMachine: child, input: sfn.TaskInput.fromObject({ - hello: sfn.Data.stringAt('$.hello'), + hello: sfn.JsonPath.stringAt('$.hello'), }), integrationPattern: sfn.IntegrationPattern.RUN_JOB, }), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/start-execution.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/start-execution.test.ts index 21d2546af8681..99228dc1491d5 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/start-execution.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/start-execution.test.ts @@ -173,7 +173,7 @@ test('Execute State Machine - Wait For Task Token', () => { stateMachine: child, integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, input: sfn.TaskInput.fromObject({ - token: sfn.Context.taskToken, + token: sfn.JsonPath.taskToken, }), }); @@ -213,5 +213,5 @@ test('Execute State Machine - Wait For Task Token - Missing Task Token', () => { stateMachine: child, integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, }); - }).toThrow('Task Token is required in `input` for callback. Use Context.taskToken to set the token.'); + }).toThrow('Task Token is required in `input` for callback. Use JsonPath.taskToken to set the token.'); }); diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index e8ed1e4fffef1..22e847ed058d9 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -277,7 +277,7 @@ execute the same steps for multiple entries of an array in the state input. ```ts const map = new stepfunctions.Map(this, 'Map State', { maxConcurrency: 1, - itemsPath: stepfunctions.Data.stringAt('$.inputForMap') + itemsPath: stepfunctions.JsonPath.stringAt('$.inputForMap') }); map.iterator(new stepfunctions.Pass(this, 'Pass State')); ``` diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts b/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts index ab4ddfb070483..407f93cd3d162 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts @@ -1,8 +1,90 @@ import { Token } from '@aws-cdk/core'; import { findReferencedPaths, jsonPathString, JsonPathToken, renderObject } from './json-path'; +/** + * Extract a field from the State Machine data or context + * that gets passed around between states + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-paths.html + */ +export class JsonPath { + /** + * Special string value to discard state input, output or result + */ + public static readonly DISCARD = 'DISCARD'; + + /** + * Instead of using a literal string, get the value from a JSON path + */ + public static stringAt(path: string): string { + validateJsonPath(path); + return new JsonPathToken(path).toString(); + } + + /** + * Instead of using a literal string list, get the value from a JSON path + */ + public static listAt(path: string): string[] { + // does not apply to task context + validateDataPath(path); + return Token.asList(new JsonPathToken(path)); + } + + /** + * Instead of using a literal number, get the value from a JSON path + */ + public static numberAt(path: string): number { + validateJsonPath(path); + return Token.asNumber(new JsonPathToken(path)); + } + + /** + * Use the entire data structure + * + * Will be an object at invocation time, but is represented in the CDK + * application as a string. + */ + public static get entirePayload(): string { + return new JsonPathToken('$').toString(); + } + + /** + * Determines if the indicated string is an encoded JSON path + * + * @param value string to be evaluated + */ + public static isEncodedJsonPath(value: string): boolean { + return !!jsonPathString(value); + } + + /** + * Return the Task Token field + * + * External actions will need this token to report step completion + * back to StepFunctions using the `SendTaskSuccess` or `SendTaskFailure` + * calls. + */ + public static get taskToken(): string { + return new JsonPathToken('$$.Task.Token').toString(); + } + + /** + * Use the entire context data structure + * + * Will be an object at invocation time, but is represented in the CDK + * application as a string. + */ + public static get entireContext(): string { + return new JsonPathToken('$$').toString(); + } + + private constructor() {} +} + /** * Extract a field from the State Machine data that gets passed around between states + * + * @deprecated replaced by `JsonPath` */ export class Data { /** @@ -48,14 +130,15 @@ export class Data { return !!jsonPathString(value); } - private constructor() { - } + private constructor() {} } /** * Extract a field from the State Machine Context data * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html#wait-token-contextobject + * + * @deprecated replaced by `JsonPath` */ export class Context { /** @@ -95,26 +178,24 @@ export class Context { return new JsonPathToken('$$').toString(); } - private constructor() { - } + private constructor() {} } /** * Helper functions to work with structures containing fields */ export class FieldUtils { - /** * Render a JSON structure containing fields to the right StepFunctions structure */ - public static renderObject(obj?: {[key: string]: any}): {[key: string]: any} | undefined { + public static renderObject(obj?: { [key: string]: any }): { [key: string]: any } | undefined { return renderObject(obj); } /** * Return all JSON paths used in the given structure */ - public static findReferencedPaths(obj?: {[key: string]: any}): string[] { + public static findReferencedPaths(obj?: { [key: string]: any }): string[] { return Array.from(findReferencedPaths(obj)).sort(); } @@ -124,12 +205,17 @@ export class FieldUtils { * The field is considered included if the field itself or one of its containing * fields occurs anywhere in the payload. */ - public static containsTaskToken(obj?: {[key: string]: any}): boolean { + public static containsTaskToken(obj?: { [key: string]: any }): boolean { const paths = findReferencedPaths(obj); return paths.has('$$.Task.Token') || paths.has('$$.Task') || paths.has('$$'); } - private constructor() { + private constructor() {} +} + +function validateJsonPath(path: string) { + if (path !== '$' && !path.startsWith('$.') && path !== '$$' && !path.startsWith('$$.')) { + throw new Error(`JSON path values must be exactly '$', '$$', start with '$.' or start with '$$.' Received: ${path}`); } } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/input.ts b/packages/@aws-cdk/aws-stepfunctions/lib/input.ts index 9fdf42c0ef3f4..c72b1c01670e7 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/input.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/input.ts @@ -1,4 +1,4 @@ -import { Context, Data } from './fields'; +import { JsonPath } from './fields'; /** * Type union for task classes that accept multiple types of payload @@ -19,10 +19,21 @@ export class TaskInput { * This object may contain Data and Context fields * as object values, if desired. */ - public static fromObject(obj: {[key: string]: any}) { + public static fromObject(obj: { [key: string]: any }) { return new TaskInput(InputType.OBJECT, obj); } + /** + * Use a part of the execution data or task context as task input + * + * Use this when you want to use a subobject or string from + * the current state machine execution or the current task context + * as complete payload to a task. + */ + public static fromJsonPathAt(path: string) { + return new TaskInput(InputType.TEXT, JsonPath.stringAt(path)); + } + /** * Use a part of the execution data as task input * @@ -31,7 +42,7 @@ export class TaskInput { * to a task. */ public static fromDataAt(path: string) { - return new TaskInput(InputType.TEXT, Data.stringAt(path)); + return new TaskInput(InputType.TEXT, JsonPath.stringAt(path)); } /** @@ -42,7 +53,7 @@ export class TaskInput { * to a task. */ public static fromContextAt(path: string) { - return new TaskInput(InputType.TEXT, Context.stringAt(path)); + return new TaskInput(InputType.TEXT, JsonPath.stringAt(path)); } /** @@ -51,8 +62,7 @@ export class TaskInput { * @param value payload for the corresponding input type. * It can be a JSON-encoded object, context, data, etc. */ - private constructor(public readonly type: InputType, public readonly value: any) { - } + private constructor(public readonly type: InputType, public readonly value: any) {} } /** @@ -75,11 +85,11 @@ export enum InputType { * example: * { * literal: 'literal', - * SomeInput: sfn.Data.stringAt('$.someField') + * SomeInput: sfn.JsonPath.stringAt('$.someField') * } * * @see https://docs.aws.amazon.com/step-functions/latest/dg/concepts-state-machine-data.html * @see https://docs.aws.amazon.com/step-functions/latest/dg/input-output-contextobject.html */ - OBJECT -} \ No newline at end of file + OBJECT, +} diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts index 2721b975d43d0..14d65ccdc541a 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts @@ -19,7 +19,7 @@ export interface MapProps { /** * JSONPath expression to select part of the state to be the input to this state. * - * May also be the special value DISCARD, which will cause the effective + * May also be the special value JsonPath.DISCARD, which will cause the effective * input to be the empty object {}. * * @default $ @@ -29,7 +29,7 @@ export interface MapProps { /** * JSONPath expression to select part of the state to be the output to this state. * - * May also be the special value DISCARD, which will cause the effective + * May also be the special value JsonPath.DISCARD, which will cause the effective * output to be the empty object {}. * * @default $ @@ -39,7 +39,7 @@ export interface MapProps { /** * JSONPath expression to indicate where to inject the state's output * - * May also be the special value DISCARD, which will cause the state's + * May also be the special value JsonPath.DISCARD, which will cause the state's * input to become its output. * * @default $ diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts index 281660036e4db..4bce7313377cf 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts @@ -19,7 +19,7 @@ export interface ParallelProps { /** * JSONPath expression to select part of the state to be the input to this state. * - * May also be the special value DISCARD, which will cause the effective + * May also be the special value JsonPath.DISCARD, which will cause the effective * input to be the empty object {}. * * @default $ @@ -29,7 +29,7 @@ export interface ParallelProps { /** * JSONPath expression to select part of the state to be the output to this state. * - * May also be the special value DISCARD, which will cause the effective + * May also be the special value JsonPath.DISCARD, which will cause the effective * output to be the empty object {}. * * @default $ @@ -39,7 +39,7 @@ export interface ParallelProps { /** * JSONPath expression to indicate where to inject the state's output * - * May also be the special value DISCARD, which will cause the state's + * May also be the special value JsonPath.DISCARD, which will cause the state's * input to become its output. * * @default $ diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts index 982980456caaa..6d98608853b68 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts @@ -66,7 +66,7 @@ export interface PassProps { /** * JSONPath expression to select part of the state to be the input to this state. * - * May also be the special value DISCARD, which will cause the effective + * May also be the special value JsonPath.DISCARD, which will cause the effective * input to be the empty object {}. * * @default $ @@ -76,7 +76,7 @@ export interface PassProps { /** * JSONPath expression to select part of the state to be the output to this state. * - * May also be the special value DISCARD, which will cause the effective + * May also be the special value JsonPath.DISCARD, which will cause the effective * output to be the empty object {}. * * @default $ @@ -86,7 +86,7 @@ export interface PassProps { /** * JSONPath expression to indicate where to inject the state's output * - * May also be the special value DISCARD, which will cause the state's + * May also be the special value JsonPath.DISCARD, which will cause the state's * input to become its output. * * @default $ diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts index 5f69d2af2d80b..8d82eecd398d7 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts @@ -1,7 +1,8 @@ import * as cdk from '@aws-cdk/core'; import { Condition } from '../condition'; +import { JsonPath } from '../fields'; import { StateGraph } from '../state-graph'; -import { CatchProps, DISCARD, Errors, IChainable, INextable, RetryProps } from '../types'; +import { CatchProps, Errors, IChainable, INextable, RetryProps } from '../types'; /** * Properties shared by all states @@ -17,7 +18,7 @@ export interface StateProps { /** * JSONPath expression to select part of the state to be the input to this state. * - * May also be the special value DISCARD, which will cause the effective + * May also be the special value JsonPath.DISCARD, which will cause the effective * input to be the empty object {}. * * @default $ @@ -37,7 +38,7 @@ export interface StateProps { /** * JSONPath expression to select part of the state to be the output to this state. * - * May also be the special value DISCARD, which will cause the effective + * May also be the special value JsonPath.DISCARD, which will cause the effective * output to be the empty object {}. * * @default $ @@ -47,7 +48,7 @@ export interface StateProps { /** * JSONPath expression to indicate where to inject the state's output * - * May also be the special value DISCARD, which will cause the state's + * May also be the special value JsonPath.DISCARD, which will cause the state's * input to become its output. * * @default $ @@ -512,7 +513,7 @@ export function renderList(xs: T[], fn: (x: T) => any): any { */ export function renderJsonPath(jsonPath?: string): undefined | null | string { if (jsonPath === undefined) { return undefined; } - if (jsonPath === DISCARD) { return null; } + if (jsonPath === JsonPath.DISCARD) { return null; } if (!jsonPath.startsWith('$')) { throw new Error(`Expected JSON path to start with '$', got: ${jsonPath}`); diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/succeed.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/succeed.ts index b4f4da1d38c2b..75d51840e0234 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/succeed.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/succeed.ts @@ -17,7 +17,7 @@ export interface SucceedProps { /** * JSONPath expression to select part of the state to be the input to this state. * - * May also be the special value DISCARD, which will cause the effective + * May also be the special value JsonPath.DISCARD, which will cause the effective * input to be the empty object {}. * * @default $ @@ -27,7 +27,7 @@ export interface SucceedProps { /** * JSONPath expression to select part of the state to be the output to this state. * - * May also be the special value DISCARD, which will cause the effective + * May also be the special value JsonPath.DISCARD, which will cause the effective * output to be the empty object {}. * * @default $ diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts index 4617ae15adb41..23b0c4acbb1b3 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts @@ -21,7 +21,7 @@ export interface TaskStateBaseProps { /** * JSONPath expression to select part of the state to be the input to this state. * - * May also be the special value DISCARD, which will cause the effective + * May also be the special value JsonPath.DISCARD, which will cause the effective * input to be the empty object {}. * * @default - The entire task input (JSON path '$') @@ -32,7 +32,7 @@ export interface TaskStateBaseProps { * JSONPath expression to select select a portion of the state output to pass * to the next state. * - * May also be the special value DISCARD, which will cause the effective + * May also be the special value JsonPath.DISCARD, which will cause the effective * output to be the empty object {}. * * @default - The entire JSON node determined by the state input, the task result, @@ -43,7 +43,7 @@ export interface TaskStateBaseProps { /** * JSONPath expression to indicate where to inject the state's output * - * May also be the special value DISCARD, which will cause the state's + * May also be the special value JsonPath.DISCARD, which will cause the state's * input to become its output. * * @default - Replaces the entire input with the result (JSON path '$') @@ -140,7 +140,7 @@ export abstract class TaskStateBase extends State implements INextable { ...this.renderNextEnd(), ...this.renderRetryCatch(), ...this.renderTaskBase(), - ...this.renderTask(), + ...this._renderTask(), }; } @@ -247,7 +247,10 @@ export abstract class TaskStateBase extends State implements INextable { } } - protected abstract renderTask(): any; + /** + * @internal + */ + protected abstract _renderTask(): any; private taskMetric(prefix: string | undefined, suffix: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { if (prefix === undefined) { diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts index 243f6b520b41c..1d275d3fb25f8 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -28,7 +28,7 @@ export interface TaskProps { /** * JSONPath expression to select part of the state to be the input to this state. * - * May also be the special value DISCARD, which will cause the effective + * May also be the special value JsonPath.DISCARD, which will cause the effective * input to be the empty object {}. * * @default $ @@ -38,7 +38,7 @@ export interface TaskProps { /** * JSONPath expression to select part of the state to be the output to this state. * - * May also be the special value DISCARD, which will cause the effective + * May also be the special value JsonPath.DISCARD, which will cause the effective * output to be the empty object {}. * * @default $ @@ -48,7 +48,7 @@ export interface TaskProps { /** * JSONPath expression to indicate where to inject the state's output * - * May also be the special value DISCARD, which will cause the state's + * May also be the special value JsonPath.DISCARD, which will cause the state's * input to become its output. * * @default $ diff --git a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts index 618ae4a6f5bb0..ef6d15fd86519 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts @@ -1,5 +1,5 @@ import '@aws-cdk/assert/jest'; -import { Context, Data, FieldUtils } from '../lib'; +import { FieldUtils, JsonPath } from '../lib'; describe('Fields', () => { test('deep replace correctly handles fields in arrays', () => { @@ -8,12 +8,12 @@ describe('Fields', () => { unknown: undefined, bool: true, literal: 'literal', - field: Data.stringAt('$.stringField'), - listField: Data.listAt('$.listField'), + field: JsonPath.stringAt('$.stringField'), + listField: JsonPath.listAt('$.listField'), deep: [ 'literal', { - deepField: Data.numberAt('$.numField'), + deepField: JsonPath.numberAt('$.numField'), }, ], }), @@ -33,10 +33,10 @@ describe('Fields', () => { test('exercise contextpaths', () => { expect( FieldUtils.renderObject({ - str: Context.stringAt('$$.Execution.StartTime'), - count: Context.numberAt('$$.State.RetryCount'), - token: Context.taskToken, - entire: Context.entireContext, + str: JsonPath.stringAt('$$.Execution.StartTime'), + count: JsonPath.numberAt('$$.State.RetryCount'), + token: JsonPath.taskToken, + entire: JsonPath.entireContext, }), ).toStrictEqual({ 'str.$': '$$.Execution.StartTime', @@ -50,13 +50,13 @@ describe('Fields', () => { FieldUtils.findReferencedPaths({ bool: false, literal: 'literal', - field: Data.stringAt('$.stringField'), - listField: Data.listAt('$.listField'), + field: JsonPath.stringAt('$.stringField'), + listField: JsonPath.listAt('$.listField'), deep: [ 'literal', { - field: Data.stringAt('$.stringField'), - deepField: Data.numberAt('$.numField'), + field: JsonPath.stringAt('$.stringField'), + deepField: JsonPath.numberAt('$.numField'), }, ], }), @@ -64,39 +64,39 @@ describe('Fields', () => { }), test('cannot have JsonPath fields in arrays', () => { expect(() => FieldUtils.renderObject({ - deep: [Data.stringAt('$.hello')], + deep: [JsonPath.stringAt('$.hello')], })).toThrowError(/Cannot use JsonPath fields in an array/); }), test('datafield path must be correct', () => { - expect(Data.stringAt('$')).toBeDefined(); + expect(JsonPath.stringAt('$')).toBeDefined(); - expect(() => Data.stringAt('$hello')).toThrowError(/exactly equal to '\$' or start with '\$.'/); + expect(() => JsonPath.stringAt('$hello')).toThrowError(/exactly '\$', '\$\$', start with '\$.' or start with '\$\$.'/); - expect(() => Data.stringAt('hello')).toThrowError(/exactly equal to '\$' or start with '\$.'/); + expect(() => JsonPath.stringAt('hello')).toThrowError(/exactly '\$', '\$\$', start with '\$.' or start with '\$\$.'/); }), test('context path must be correct', () => { - expect(Context.stringAt('$$')).toBeDefined(); + expect(JsonPath.stringAt('$$')).toBeDefined(); - expect(() => Context.stringAt('$$hello')).toThrowError(/exactly equal to '\$\$' or start with '\$\$.'/); + expect(() => JsonPath.stringAt('$$hello')).toThrowError(/exactly '\$', '\$\$', start with '\$.' or start with '\$\$.'/); - expect(() => Context.stringAt('hello')).toThrowError(/exactly equal to '\$\$' or start with '\$\$.'/); + expect(() => JsonPath.stringAt('hello')).toThrowError(/exactly '\$', '\$\$', start with '\$.' or start with '\$\$.'/); }), test('test contains task token', () => { expect(true).toEqual( FieldUtils.containsTaskToken({ - field: Context.taskToken, + field: JsonPath.taskToken, }), ); expect(true).toEqual( FieldUtils.containsTaskToken({ - field: Context.stringAt('$$.Task'), + field: JsonPath.stringAt('$$.Task'), }), ); expect(true).toEqual( FieldUtils.containsTaskToken({ - field: Context.entireContext, + field: JsonPath.entireContext, }), ); @@ -108,7 +108,7 @@ describe('Fields', () => { expect(false).toEqual( FieldUtils.containsTaskToken({ - oops: Context.stringAt('$$.Execution.StartTime'), + oops: JsonPath.stringAt('$$.Execution.StartTime'), }), ); }), @@ -123,7 +123,7 @@ describe('Fields', () => { }), test('fields cannot be used somewhere in a string interpolation', () => { expect(() => FieldUtils.renderObject({ - field: `contains ${Data.stringAt('$.hello')}`, + field: `contains ${JsonPath.stringAt('$.hello')}`, })).toThrowError(/Field references must be the entire string/); }); }); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts index e70765e96c502..c5ad0bb6d5697 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts @@ -10,10 +10,10 @@ describe('Map State', () => { // WHEN const map = new stepfunctions.Map(stack, 'Map State', { maxConcurrency: 1, - itemsPath: stepfunctions.Data.stringAt('$.inputForMap'), + itemsPath: stepfunctions.JsonPath.stringAt('$.inputForMap'), parameters: { foo: 'foo', - bar: stepfunctions.Data.stringAt('$.bar'), + bar: stepfunctions.JsonPath.stringAt('$.bar'), }, }); map.iterator(new stepfunctions.Pass(stack, 'Pass State')); @@ -41,13 +41,11 @@ describe('Map State', () => { }, }); }), - test('synth is successful', () => { - const app = createAppWithMap((stack) => { const map = new stepfunctions.Map(stack, 'Map State', { maxConcurrency: 1, - itemsPath: stepfunctions.Data.stringAt('$.inputForMap'), + itemsPath: stepfunctions.JsonPath.stringAt('$.inputForMap'), }); map.iterator(new stepfunctions.Pass(stack, 'Pass State')); return map; @@ -55,13 +53,11 @@ describe('Map State', () => { app.synth(); }), - test('fails in synthesis if iterator is missing', () => { - const app = createAppWithMap((stack) => { const map = new stepfunctions.Map(stack, 'Map State', { maxConcurrency: 1, - itemsPath: stepfunctions.Data.stringAt('$.inputForMap'), + itemsPath: stepfunctions.JsonPath.stringAt('$.inputForMap'), }); return map; @@ -69,13 +65,11 @@ describe('Map State', () => { expect(() => app.synth()).toThrow(/Map state must have a non-empty iterator/); }), - test('fails in synthesis when maxConcurrency is a float', () => { - const app = createAppWithMap((stack) => { const map = new stepfunctions.Map(stack, 'Map State', { maxConcurrency: 1.2, - itemsPath: stepfunctions.Data.stringAt('$.inputForMap'), + itemsPath: stepfunctions.JsonPath.stringAt('$.inputForMap'), }); map.iterator(new stepfunctions.Pass(stack, 'Pass State')); @@ -84,13 +78,11 @@ describe('Map State', () => { expect(() => app.synth()).toThrow(/maxConcurrency has to be a positive integer/); }), - test('fails in synthesis when maxConcurrency is a negative integer', () => { - const app = createAppWithMap((stack) => { const map = new stepfunctions.Map(stack, 'Map State', { maxConcurrency: -1, - itemsPath: stepfunctions.Data.stringAt('$.inputForMap'), + itemsPath: stepfunctions.JsonPath.stringAt('$.inputForMap'), }); map.iterator(new stepfunctions.Pass(stack, 'Pass State')); @@ -99,13 +91,11 @@ describe('Map State', () => { expect(() => app.synth()).toThrow(/maxConcurrency has to be a positive integer/); }), - test('fails in synthesis when maxConcurrency is too big to be an integer', () => { - const app = createAppWithMap((stack) => { const map = new stepfunctions.Map(stack, 'Map State', { maxConcurrency: Number.MAX_VALUE, - itemsPath: stepfunctions.Data.stringAt('$.inputForMap'), + itemsPath: stepfunctions.JsonPath.stringAt('$.inputForMap'), }); map.iterator(new stepfunctions.Pass(stack, 'Pass State')); @@ -114,28 +104,22 @@ describe('Map State', () => { expect(() => app.synth()).toThrow(/maxConcurrency has to be a positive integer/); }), - test('isPositiveInteger is false with negative number', () => { expect(stepfunctions.isPositiveInteger(-1)).toEqual(false); }), - test('isPositiveInteger is false with decimal number', () => { expect(stepfunctions.isPositiveInteger(1.2)).toEqual(false); }), - test('isPositiveInteger is false with a value greater than safe integer', () => { const valueToTest = Number.MAX_SAFE_INTEGER + 1; expect(stepfunctions.isPositiveInteger(valueToTest)).toEqual(false); }), - test('isPositiveInteger is true with 0', () => { expect(stepfunctions.isPositiveInteger(0)).toEqual(true); }), - test('isPositiveInteger is true with 10', () => { expect(stepfunctions.isPositiveInteger(10)).toEqual(true); }), - test('isPositiveInteger is true with max integer value', () => { expect(stepfunctions.isPositiveInteger(Number.MAX_SAFE_INTEGER)).toEqual(true); }); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts index 67fcd36752f80..dc016b87012df 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts @@ -619,7 +619,7 @@ describe('State Machine Resources', () => { const stack = new cdk.Stack(); const task = new stepfunctions.Pass(stack, 'Pass', { parameters: { - input: stepfunctions.Data.stringAt('$.myField'), + input: stepfunctions.JsonPath.stringAt('$.myField'), }, }); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts index cfda2f109780a..3778eb6716b30 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts @@ -298,7 +298,10 @@ class FakeTask extends sfn.TaskStateBase { this.taskMetrics = props.metrics; } - protected renderTask(): any { + /** + * @internal + */ + protected _renderTask(): any { return { Resource: 'my-resource', Parameters: sfn.FieldUtils.renderObject({ diff --git a/packages/@aws-cdk/cdk-assets-schema/package.json b/packages/@aws-cdk/cdk-assets-schema/package.json index 279b10397a6db..8d65ed30aecd4 100644 --- a/packages/@aws-cdk/cdk-assets-schema/package.json +++ b/packages/@aws-cdk/cdk-assets-schema/package.json @@ -46,7 +46,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^26.0.0", + "@types/jest": "^26.0.3", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", "pkglint": "0.0.0" diff --git a/packages/@aws-cdk/cloud-assembly-schema/package.json b/packages/@aws-cdk/cloud-assembly-schema/package.json index b39ddd629873b..eac2654ff030b 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": "^26.0.0", + "@types/jest": "^26.0.3", "@types/mock-fs": "^4.10.0", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", diff --git a/packages/@aws-cdk/cloudformation-diff/package.json b/packages/@aws-cdk/cloudformation-diff/package.json index 4d128ebfc9579..3196b202c7f27 100644 --- a/packages/@aws-cdk/cloudformation-diff/package.json +++ b/packages/@aws-cdk/cloudformation-diff/package.json @@ -29,7 +29,7 @@ "table": "^5.4.6" }, "devDependencies": { - "@types/jest": "^26.0.0", + "@types/jest": "^26.0.3", "@types/string-width": "^4.0.1", "@types/table": "^4.0.7", "cdk-build-tools": "0.0.0", diff --git a/packages/@aws-cdk/cloudformation-include/package.json b/packages/@aws-cdk/cloudformation-include/package.json index cad1c8249b4fe..de407f258d640 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": "^26.0.0", + "@types/jest": "^26.0.3", "@types/yaml": "1.2.0", "cdk-build-tools": "0.0.0", "jest": "^25.4.0", diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index bc6acb2a443dc..4b660f4e83c42 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -151,7 +151,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/lodash": "^4.14.156", + "@types/lodash": "^4.14.157", "@types/node": "^10.17.26", "@types/nodeunit": "^0.0.31", "@types/minimatch": "^3.0.3", diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index 79b0fd59e5ee6..7907d4568c796 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -73,13 +73,13 @@ "@types/aws-lambda": "^8.10.39", "@types/fs-extra": "^8.1.0", "@types/sinon": "^9.0.4", - "aws-sdk": "^2.703.0", + "aws-sdk": "^2.706.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", "fs-extra": "^9.0.1", - "nock": "^12.0.3", + "nock": "^13.0.0", "pkglint": "0.0.0", "sinon": "^9.0.2" }, diff --git a/packages/@aws-cdk/cx-api/package.json b/packages/@aws-cdk/cx-api/package.json index 766e671454da6..371aebc1f65fd 100644 --- a/packages/@aws-cdk/cx-api/package.json +++ b/packages/@aws-cdk/cx-api/package.json @@ -53,9 +53,9 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^26.0.0", + "@types/jest": "^26.0.3", "@types/mock-fs": "^4.10.0", - "@types/semver": "^7.2.0", + "@types/semver": "^7.3.1", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", "mock-fs": "^4.12.0", diff --git a/packages/@monocdk-experiment/assert/package.json b/packages/@monocdk-experiment/assert/package.json index a83382da131d0..6a923bd5971a3 100644 --- a/packages/@monocdk-experiment/assert/package.json +++ b/packages/@monocdk-experiment/assert/package.json @@ -36,7 +36,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^26.0.0", + "@types/jest": "^26.0.3", "@types/node": "^10.17.26", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", diff --git a/packages/@monocdk-experiment/rewrite-imports/bin/rewrite-imports.ts b/packages/@monocdk-experiment/rewrite-imports/bin/rewrite-imports.ts index a79f833d5e71c..6193a04f5715e 100644 --- a/packages/@monocdk-experiment/rewrite-imports/bin/rewrite-imports.ts +++ b/packages/@monocdk-experiment/rewrite-imports/bin/rewrite-imports.ts @@ -18,12 +18,15 @@ async function main() { 'node_modules/**', ]; - const files = await glob(process.argv[2], { ignore, matchBase: true }); - for (const file of files) { - const input = await fs.promises.readFile(file, { encoding: 'utf8' }); - const output = rewriteImports(input, file); - if (output.trim() !== input.trim()) { - await fs.promises.writeFile(file, output); + const args = process.argv.slice(2); + for (const arg of args) { + const files = await glob(arg, { ignore, matchBase: true }); + for (const file of files) { + const input = await fs.promises.readFile(file, { encoding: 'utf8' }); + const output = rewriteImports(input, file); + if (output.trim() !== input.trim()) { + await fs.promises.writeFile(file, output); + } } } } diff --git a/packages/@monocdk-experiment/rewrite-imports/package.json b/packages/@monocdk-experiment/rewrite-imports/package.json index a9932328b681c..a159df11280b2 100644 --- a/packages/@monocdk-experiment/rewrite-imports/package.json +++ b/packages/@monocdk-experiment/rewrite-imports/package.json @@ -36,7 +36,7 @@ }, "devDependencies": { "@types/glob": "^7.1.2", - "@types/jest": "^26.0.0", + "@types/jest": "^26.0.3", "@types/node": "^10.17.26", "cdk-build-tools": "0.0.0", "pkglint": "0.0.0" diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index b30b6346ada84..c40c914714187 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -13,7 +13,7 @@ import { execProgram } from '../lib/api/cxapp/exec'; import { CdkToolkit } from '../lib/cdk-toolkit'; import { RequireApproval } from '../lib/diff'; import { availableInitLanguages, cliInit, printAvailableTemplates } from '../lib/init'; -import { data, debug, error, setVerbose } from '../lib/logging'; +import { data, debug, error, setLogLevel } from '../lib/logging'; import { PluginHost } from '../lib/plugin'; import { serializeStructure } from '../lib/serialize'; import { Configuration, Settings } from '../lib/settings'; @@ -45,7 +45,8 @@ async function parseCommandLineArguments() { .option('strict', { type: 'boolean', desc: 'Do not construct stacks with warnings' }) .option('ignore-errors', { type: 'boolean', default: false, desc: 'Ignores synthesis errors, which will likely produce an invalid output' }) .option('json', { type: 'boolean', alias: 'j', desc: 'Use JSON output instead of YAML when templates are printed to STDOUT', default: false }) - .option('verbose', { type: 'boolean', alias: 'v', desc: 'Show debug logs', default: false }) + .option('verbose', { type: 'boolean', alias: 'v', desc: 'Show debug logs (specify multiple times to increase verbosity)', default: false }) + .count('verbose') .option('profile', { type: 'string', desc: 'Use the indicated AWS profile as the default environment', requiresArg: true }) .option('proxy', { type: 'string', desc: 'Use the indicated proxy. Will read from HTTPS_PROXY environment variable if not specified.', requiresArg: true }) .option('ca-bundle-path', { type: 'string', desc: 'Path to CA certificate to use when validating HTTPS requests. Will read from AWS_CA_BUNDLE environment variable if not specified.', requiresArg: true }) @@ -122,7 +123,7 @@ if (!process.stdout.isTTY) { async function initCommandLine() { const argv = await parseCommandLineArguments(); if (argv.verbose) { - setVerbose(); + setLogLevel(argv.verbose); } debug('CDK toolkit version:', version.DISPLAY_VERSION); debug('Command line arguments:', argv); @@ -203,7 +204,7 @@ async function initCommandLine() { const cli = new CdkToolkit({ cloudExecutable, cloudFormation, - verbose: argv.trace || argv.verbose, + verbose: argv.trace || argv.verbose > 0, ignoreErrors: argv['ignore-errors'], strict: argv.strict, configuration, diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk.ts b/packages/aws-cdk/lib/api/aws-auth/sdk.ts index 871b36c6002d3..d735231836e27 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk.ts @@ -1,6 +1,6 @@ import * as AWS from 'aws-sdk'; import { ConfigurationOptions } from 'aws-sdk/lib/config'; -import { debug } from '../../logging'; +import { debug, trace } from '../../logging'; import { cached } from '../../util/functions'; import { AccountAccessKeyCache } from './account-cache'; import { Account } from './sdk-provider'; @@ -60,7 +60,7 @@ export class SDK implements ISDK { ...this.retryOptions, credentials, region, - logger: { log: (...messages) => messages.forEach(m => debug('%s', m)) }, + logger: { log: (...messages) => messages.forEach(m => trace('%s', m)) }, }; this.currentRegion = region; } diff --git a/packages/aws-cdk/lib/api/bootstrap/legacy-template.ts b/packages/aws-cdk/lib/api/bootstrap/legacy-template.ts index 67bed3406b07a..0b98ea3a3fbe2 100644 --- a/packages/aws-cdk/lib/api/bootstrap/legacy-template.ts +++ b/packages/aws-cdk/lib/api/bootstrap/legacy-template.ts @@ -33,7 +33,7 @@ export function legacyBootstrapTemplate(params: BootstrappingParameters): any { IgnorePublicAcls: true, RestrictPublicBuckets: true, }, - 'AWS::NoValue', + { Ref: 'AWS::NoValue' }, ]}, }, }, 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 c1ac8a8194120..7df4680c7fb3c 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,7 +3,7 @@ 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, isVerbose, setVerbose } from '../../../logging'; +import { error, logLevel, LogLevel, setLogLevel } from '../../../logging'; import { RewritableBlock } from '../display'; interface StackActivity { @@ -28,13 +28,13 @@ export interface StackActivityMonitorProps { readonly resourcesTotal?: number; /** - * Whether 'verbose' was requested in the CLI + * The log level that was requested in the CLI * - * If verbose is requested, we'll always use the full history printer. + * If verbose or trace is requested, we'll always use the full history printer. * - * @default - Use value from logging.isVerbose + * @default - Use value from logging.logLevel */ - readonly verbose?: boolean; + readonly logLevel?: LogLevel; } export class StackActivityMonitor { @@ -78,7 +78,7 @@ export class StackActivityMonitor { }; const isWindows = process.platform === 'win32'; - const verbose = options.verbose ?? isVerbose; + const verbose = options.logLevel ?? logLevel; const fancyOutputAvailable = !isWindows && stream.isTTY; this.printer = fancyOutputAvailable && !verbose @@ -479,7 +479,7 @@ export class CurrentActivityPrinter extends ActivityPrinterBase { */ public readonly updateSleep: number = 2_000; - private oldVerbose: boolean = false; + private oldLogLevel: LogLevel = LogLevel.DEFAULT; private block = new RewritableBlock(this.stream); constructor(props: PrinterProps) { @@ -520,12 +520,12 @@ export class CurrentActivityPrinter extends ActivityPrinterBase { 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); + this.oldLogLevel = logLevel; + setLogLevel(LogLevel.DEFAULT); } public stop() { - setVerbose(this.oldVerbose); + setLogLevel(this.oldLogLevel); // Print failures at the end const lines = new Array(); @@ -623,4 +623,4 @@ function shorten(maxWidth: number, p: string) { } const TIMESTAMP_WIDTH = 12; -const STATUS_WIDTH = 20; \ No newline at end of file +const STATUS_WIDTH = 20; diff --git a/packages/aws-cdk/lib/init-templates/app/javascript/.template.gitignore b/packages/aws-cdk/lib/init-templates/app/javascript/.template.gitignore index 599776985b7ff..a2da1bef05b07 100644 --- a/packages/aws-cdk/lib/init-templates/app/javascript/.template.gitignore +++ b/packages/aws-cdk/lib/init-templates/app/javascript/.template.gitignore @@ -4,6 +4,5 @@ node_modules .cdk.staging cdk.out -# Parcel build directories -.cache -.build +# Parcel default cache directory +.parcel-cache diff --git a/packages/aws-cdk/lib/init-templates/app/typescript/.template.gitignore b/packages/aws-cdk/lib/init-templates/app/typescript/.template.gitignore index 96eba04a818de..305c7fbcc4d89 100644 --- a/packages/aws-cdk/lib/init-templates/app/typescript/.template.gitignore +++ b/packages/aws-cdk/lib/init-templates/app/typescript/.template.gitignore @@ -7,6 +7,5 @@ node_modules .cdk.staging cdk.out -# Parcel build directories -.cache -.build +# Parcel default cache directory +.parcel-cache diff --git a/packages/aws-cdk/lib/init-templates/lib/typescript/.template.gitignore b/packages/aws-cdk/lib/init-templates/lib/typescript/.template.gitignore index 96eba04a818de..305c7fbcc4d89 100644 --- a/packages/aws-cdk/lib/init-templates/lib/typescript/.template.gitignore +++ b/packages/aws-cdk/lib/init-templates/lib/typescript/.template.gitignore @@ -7,6 +7,5 @@ node_modules .cdk.staging cdk.out -# Parcel build directories -.cache -.build +# Parcel default cache directory +.parcel-cache diff --git a/packages/aws-cdk/lib/init-templates/sample-app/javascript/.template.gitignore b/packages/aws-cdk/lib/init-templates/sample-app/javascript/.template.gitignore index 599776985b7ff..a2da1bef05b07 100644 --- a/packages/aws-cdk/lib/init-templates/sample-app/javascript/.template.gitignore +++ b/packages/aws-cdk/lib/init-templates/sample-app/javascript/.template.gitignore @@ -4,6 +4,5 @@ node_modules .cdk.staging cdk.out -# Parcel build directories -.cache -.build +# Parcel default cache directory +.parcel-cache diff --git a/packages/aws-cdk/lib/init-templates/sample-app/typescript/.template.gitignore b/packages/aws-cdk/lib/init-templates/sample-app/typescript/.template.gitignore index 96eba04a818de..305c7fbcc4d89 100644 --- a/packages/aws-cdk/lib/init-templates/sample-app/typescript/.template.gitignore +++ b/packages/aws-cdk/lib/init-templates/sample-app/typescript/.template.gitignore @@ -7,6 +7,5 @@ node_modules .cdk.staging cdk.out -# Parcel build directories -.cache -.build +# Parcel default cache directory +.parcel-cache diff --git a/packages/aws-cdk/lib/init.ts b/packages/aws-cdk/lib/init.ts index 07ec97181f551..cff039fbb9cee 100644 --- a/packages/aws-cdk/lib/init.ts +++ b/packages/aws-cdk/lib/init.ts @@ -241,15 +241,16 @@ async function initializeProject(template: InitTemplate, language: string, canUs await assertIsEmptyDirectory(workDir); print(`Applying project template ${colors.green(template.name)} for ${colors.blue(language)}`); await template.install(language, workDir); + if (await fs.pathExists('README.md')) { + print(colors.green(await fs.readFile('README.md', { encoding: 'utf-8' }))); + } + if (!generateOnly) { await initializeGitRepository(workDir); await postInstall(language, canUseNetwork, workDir); } - if (await fs.pathExists('README.md')) { - print(colors.green(await fs.readFile('README.md', { encoding: 'utf-8' }))); - } else { - print('✅ All done!'); - } + + print('✅ All done!'); } async function assertIsEmptyDirectory(workDir: string) { @@ -292,7 +293,7 @@ async function postInstallTypescript(canUseNetwork: boolean, cwd: string) { const command = 'npm'; if (!canUseNetwork) { - print(`Please run ${colors.green(`${command} install`)}!`); + warning(`Please run '${command} install'!`); return; } @@ -300,28 +301,36 @@ async function postInstallTypescript(canUseNetwork: boolean, cwd: string) { try { await execute(command, ['install'], { cwd }); } catch (e) { - throw new Error(`${colors.green(`${command} install`)} failed: ` + e.message); + warning(`${command} install failed: ` + e.message); } } async function postInstallJava(canUseNetwork: boolean, cwd: string) { + const mvnPackageWarning = 'Please run \'mvn package\'!'; if (!canUseNetwork) { - print(`Please run ${colors.green('mvn package')}!`); + warning(mvnPackageWarning); return; } - print(`Executing ${colors.green('mvn package')}...`); - await execute('mvn', ['package'], { cwd }); + print('Executing \'mvn package\''); + try { + await execute('mvn', ['package'], { cwd }); + } catch (e) { + warning('Unable to package compiled code as JAR'); + warning(mvnPackageWarning); + } + } async function postInstallPython(cwd: string) { const python = pythonExecutable(); + warning(`Please run ${python} -m venv .env'!`); print(`Executing ${colors.green('Creating virtualenv...')}`); try { await execute(python, ['-m venv', '.env'], { cwd }); } catch (e) { - print('Unable to create virtualenv automatically'); - print(`Please run ${colors.green(python + ' -m venv .env')}!`); + warning('Unable to create virtualenv automatically'); + warning(`Please run '${python} -m venv .env'!`); } } diff --git a/packages/aws-cdk/lib/logging.ts b/packages/aws-cdk/lib/logging.ts index 5c23131d1a451..b85881886a84f 100644 --- a/packages/aws-cdk/lib/logging.ts +++ b/packages/aws-cdk/lib/logging.ts @@ -13,15 +13,20 @@ const logger = (stream: Writable, styles?: StyleFn[]) => (fmt: string, ...args: stream.write(str + '\n'); }; -export let isVerbose = false; +export let logLevel = LogLevel.DEFAULT; -export function setVerbose(enabled = true) { - isVerbose = enabled; +export function setLogLevel(newLogLevel: LogLevel) { + logLevel = newLogLevel; +} + +export function increaseVerbosity() { + logLevel += 1; } const _debug = logger(stderr, [colors.gray]); -export const debug = (fmt: string, ...args: any[]) => isVerbose && _debug(fmt, ...args); +export const trace = (fmt: string, ...args: any) => logLevel >= LogLevel.TRACE && _debug(fmt, ...args); +export const debug = (fmt: string, ...args: any[]) => logLevel >= LogLevel.DEBUG && _debug(fmt, ...args); export const error = logger(stderr, [colors.red]); export const warning = logger(stderr, [colors.yellow]); export const success = logger(stderr, [colors.green]); @@ -42,3 +47,12 @@ export type LoggerFunction = (fmt: string, ...args: any[]) => void; export function prefix(prefixString: string, fn: LoggerFunction): LoggerFunction { return (fmt: string, ...args: any[]) => fn(`%s ${fmt}`, prefixString, ...args); } + +export const enum LogLevel { + /** Not verbose at all */ + DEFAULT = 0, + /** Pretty verbose */ + DEBUG = 1, + /** Extremely verbose */ + TRACE = 2 +} diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 5895dc62b74e1..886b797adf972 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -44,12 +44,12 @@ "@types/archiver": "^3.1.0", "@types/fs-extra": "^8.1.0", "@types/glob": "^7.1.2", - "@types/jest": "^26.0.0", + "@types/jest": "^26.0.3", "@types/minimatch": "^3.0.3", "@types/mockery": "^1.4.29", "@types/node": "^10.17.26", "@types/promptly": "^3.0.0", - "@types/semver": "^7.2.0", + "@types/semver": "^7.3.1", "@types/sinon": "^9.0.4", "@types/table": "^4.0.7", "@types/uuid": "^8.0.0", @@ -71,7 +71,7 @@ "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/region-info": "0.0.0", "archiver": "^4.0.1", - "aws-sdk": "^2.703.0", + "aws-sdk": "^2.706.0", "camelcase": "^6.0.0", "cdk-assets": "0.0.0", "colors": "^1.4.0", diff --git a/packages/aws-cdk/test/api/exec.test.ts b/packages/aws-cdk/test/api/exec.test.ts index ec653da6d6a77..13c5cf9a5f222 100644 --- a/packages/aws-cdk/test/api/exec.test.ts +++ b/packages/aws-cdk/test/api/exec.test.ts @@ -5,18 +5,18 @@ import * as semver from 'semver'; import * as sinon from 'sinon'; import { ImportMock } from 'ts-mock-imports'; import { execProgram } from '../../lib/api/cxapp/exec'; -import { setVerbose } from '../../lib/logging'; +import { LogLevel, setLogLevel } from '../../lib/logging'; import { Configuration } from '../../lib/settings'; import * as bockfs from '../bockfs'; import { testAssembly } from '../util'; import { mockSpawn } from '../util/mock-child_process'; import { MockSdkProvider } from '../util/mock-sdk'; -setVerbose(true); - let sdkProvider: MockSdkProvider; let config: Configuration; beforeEach(() => { + setLogLevel(LogLevel.DEBUG); + sdkProvider = new MockSdkProvider(); config = new Configuration(); @@ -34,6 +34,8 @@ beforeEach(() => { }); afterEach(() => { + setLogLevel(LogLevel.DEFAULT); + sinon.restore(); bockfs.restore(); }); diff --git a/packages/aws-cdk/test/api/sdk-provider.test.ts b/packages/aws-cdk/test/api/sdk-provider.test.ts index 6e6a4f91511c4..0d50a3936053c 100644 --- a/packages/aws-cdk/test/api/sdk-provider.test.ts +++ b/packages/aws-cdk/test/api/sdk-provider.test.ts @@ -9,7 +9,6 @@ import * as logging from '../../lib/logging'; import * as bockfs from '../bockfs'; SDKMock.setSDKInstance(AWS); -logging.setVerbose(true); type AwsCallback = (err: Error | null, val: T) => void; @@ -26,6 +25,8 @@ let defaultEnv: cxapi.Environment; beforeEach(() => { uid = `(${uuid.v4()})`; + logging.setLogLevel(logging.LogLevel.TRACE); + bockfs({ '/home/me/.bxt/credentials': dedent(` [default] @@ -98,6 +99,8 @@ beforeEach(() => { }); afterEach(() => { + logging.setLogLevel(logging.LogLevel.DEFAULT); + SDKMock.restore(); bockfs.restore(); }); @@ -264,4 +267,4 @@ function commonPrefix(a: string, b: string): string { if (a[i] !== b[i]) { return a.substring(0, i); } } return a.substr(N); -} \ No newline at end of file +} diff --git a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts index 93f9a0974aa2a..7c7947acdcae6 100644 --- a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts @@ -152,6 +152,17 @@ integTest('deploying new style synthesis to old style bootstrap fails', async () })).rejects.toThrow('exited with error'); }); +integTest('can create a legacy bootstrap stack with --public-access-block-configuration=false', async () => { + const bootstrapStackName = fullStackName('bootstrap-stack-1'); + + await cdk(['bootstrap', '-v', '--toolkit-stack-name', bootstrapStackName, '--public-access-block-configuration', 'false', '--tags', 'Foo=Bar']); + + const response = await cloudFormation('describeStacks', { StackName: bootstrapStackName }); + expect(response.Stacks?.[0].Tags).toEqual([ + { Key: 'Foo', Value: 'Bar' }, + ]); +}); + integTest('can create multiple legacy bootstrap stacks', async () => { const bootstrapStackName1 = fullStackName('bootstrap-stack-1'); const bootstrapStackName2 = fullStackName('bootstrap-stack-2'); diff --git a/packages/cdk-assets/package.json b/packages/cdk-assets/package.json index 859e64f9df617..138a686cc6afb 100644 --- a/packages/cdk-assets/package.json +++ b/packages/cdk-assets/package.json @@ -32,7 +32,7 @@ "devDependencies": { "@types/archiver": "^3.1.0", "@types/glob": "^7.1.2", - "@types/jest": "^26.0.0", + "@types/jest": "^26.0.3", "@types/mock-fs": "^4.10.0", "@types/node": "^10.17.26", "@types/yargs": "^15.0.5", @@ -47,7 +47,7 @@ "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "archiver": "^4.0.1", - "aws-sdk": "^2.703.0", + "aws-sdk": "^2.706.0", "glob": "^7.1.6", "yargs": "^15.3.1" }, diff --git a/packages/cdk-dasm/package.json b/packages/cdk-dasm/package.json index 7554e7bee6180..c5e6b34be451b 100644 --- a/packages/cdk-dasm/package.json +++ b/packages/cdk-dasm/package.json @@ -30,7 +30,7 @@ "yaml": "1.10.0" }, "devDependencies": { - "@types/jest": "^26.0.0", + "@types/jest": "^26.0.3", "@types/yaml": "1.9.7", "jest": "^25.5.4" }, diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 76fb6b76b2ed7..59b0ad777f07e 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -185,7 +185,7 @@ }, "devDependencies": { "@types/fs-extra": "^8.1.0", - "@types/jest": "^26.0.0", + "@types/jest": "^26.0.3", "@types/yaml": "1.9.7", "@types/yargs": "^15.0.5", "jest": "^25.5.4", diff --git a/scripts/buildup b/scripts/buildup index 59aeff93dcf50..3fea629af9976 100755 --- a/scripts/buildup +++ b/scripts/buildup @@ -19,7 +19,7 @@ else fi fi -${scriptdir}/foreach.sh --up yarn build+test +${scriptdir}/foreach.sh --up yarn build ${scriptdir}/foreach.sh --reset echo "************************************************************" diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index 0305be2513a6b..7913098fa397a 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -34,7 +34,7 @@ "license": "Apache-2.0", "devDependencies": { "@types/fs-extra": "^8.1.0", - "@types/jest": "^26.0.0", + "@types/jest": "^26.0.3", "@types/yargs": "^15.0.5", "pkglint": "0.0.0" }, @@ -46,7 +46,7 @@ "eslint": "^6.8.0", "eslint-import-resolver-node": "^0.3.4", "eslint-import-resolver-typescript": "^2.0.0", - "eslint-plugin-import": "^2.21.2", + "eslint-plugin-import": "^2.22.0", "fs-extra": "^9.0.1", "jest": "^25.5.4", "jsii": "^1.7.0", diff --git a/tools/cfn2ts/package.json b/tools/cfn2ts/package.json index 04fa146c2a4ec..d302cd2aa01be 100644 --- a/tools/cfn2ts/package.json +++ b/tools/cfn2ts/package.json @@ -37,7 +37,7 @@ }, "devDependencies": { "@types/fs-extra": "^8.1.0", - "@types/jest": "^26.0.0", + "@types/jest": "^26.0.3", "@types/yargs": "^15.0.5", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", diff --git a/tools/pkglint/lib/packagejson.ts b/tools/pkglint/lib/packagejson.ts index 3707a2a29a8ab..1ec46e6b2bdc8 100644 --- a/tools/pkglint/lib/packagejson.ts +++ b/tools/pkglint/lib/packagejson.ts @@ -2,6 +2,9 @@ import * as colors from 'colors/safe'; import * as fs from 'fs-extra'; import * as path from 'path'; +// do not descend into these directories when searching for `package.json` files. +export const PKGLINT_IGNORES = ['node_modules', 'cdk.out', '.cdk.staging']; + /** * Return all package JSONs in the root directory */ @@ -24,8 +27,8 @@ export function findPackageJsons(root: string): PackageJson[] { ret.push(new PackageJson(fullPath)); } - // Recurse into all dirs except node_modules - if (file !== 'node_modules' && (fs.lstatSync(fullPath)).isDirectory()) { + // Recurse into all dirs except ignored dirs + if (!PKGLINT_IGNORES.includes(file) && (fs.lstatSync(fullPath)).isDirectory()) { recurse(fullPath); } } diff --git a/tools/pkglint/lib/util.ts b/tools/pkglint/lib/util.ts index 85f33afb2e7b0..35966d3809b22 100644 --- a/tools/pkglint/lib/util.ts +++ b/tools/pkglint/lib/util.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; -import { PackageJson } from "./packagejson"; +import { PackageJson, PKGLINT_IGNORES } from "./packagejson"; /** * Expect a particular JSON key to be a given value @@ -168,7 +168,7 @@ export function* findInnerPackages(dir: string): IterableIterator { if (e.code !== 'ENOENT') { throw e; } continue; } - if (fname === 'node_modules') { continue; } + if (PKGLINT_IGNORES.includes(fname)) { continue; } if (fs.existsSync(path.join(dir, fname, 'package.json'))) { yield path.join(dir, fname); diff --git a/tools/pkglint/package.json b/tools/pkglint/package.json index 88d09027159b0..1c2037573215e 100644 --- a/tools/pkglint/package.json +++ b/tools/pkglint/package.json @@ -36,7 +36,7 @@ "license": "Apache-2.0", "devDependencies": { "@types/fs-extra": "^8.1.0", - "@types/semver": "^7.2.0", + "@types/semver": "^7.3.1", "@types/yargs": "^15.0.5", "jest": "^25.5.4", "typescript": "~3.9.5" diff --git a/tools/yarn-cling/package.json b/tools/yarn-cling/package.json index e372356bec847..fe1b5490e0967 100644 --- a/tools/yarn-cling/package.json +++ b/tools/yarn-cling/package.json @@ -39,7 +39,7 @@ }, "devDependencies": { "@types/yarnpkg__lockfile": "^1.1.3", - "@types/jest": "^26.0.0", + "@types/jest": "^26.0.3", "jest": "^25.5.4", "@types/node": "^10.17.26", "typescript": "~3.9.5", diff --git a/yarn.lock b/yarn.lock index ceb77319b9efd..3ad380188332a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1476,10 +1476,10 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" -"@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== +"@types/jest@^26.0.3": + version "26.0.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.3.tgz#79534e0e94857171c0edc596db0ebe7cb7863251" + integrity sha512-v89ga1clpVL/Y1+YI0eIu1VMW+KU7Xl8PhylVtDKVWaSUHBHYPLXMQGBdrpHewaKoTvlXkksbYqPgz8b4cmRZg== dependencies: jest-diff "^25.2.1" pretty-format "^25.2.1" @@ -1501,10 +1501,10 @@ dependencies: jszip "*" -"@types/lodash@^4.14.156": - version "4.14.156" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.156.tgz#cbe30909c89a1feeb7c60803e785344ea0ec82d1" - integrity sha512-l2AgHXcKUwx2DsvP19wtRPqZ4NkONjmorOdq4sMcxIjqdIuuV/ULo2ftuv4NUpevwfW7Ju/UKLqo0ZXuEt/8lQ== +"@types/lodash@^4.14.157": + version "4.14.157" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.157.tgz#fdac1c52448861dfde1a2e1515dbc46e54926dc8" + integrity sha512-Ft5BNFmv2pHDgxV5JDsndOWTRJ+56zte0ZpYLowp03tW+K+t8u8YMOzAnpuqPgzX6WO1XpDIUm7u04M8vdDiVQ== "@types/md5@^2.2.0": version "2.2.0" @@ -1572,10 +1572,10 @@ resolved "https://registry.yarnpkg.com/@types/proxyquire/-/proxyquire-1.3.28.tgz#05a647bb0d8fe48fc8edcc193e43cc79310faa7d" integrity sha512-SQaNzWQ2YZSr7FqAyPPiA3FYpux2Lqh3HWMZQk47x3xbMCqgC/w0dY3dw9rGqlweDDkrySQBcaScXWeR+Yb11Q== -"@types/semver@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.2.0.tgz#0d72066965e910531e1db4621c15d0ca36b8d83b" - integrity sha512-TbB0A8ACUWZt3Y6bQPstW9QNbhNeebdgLX4T/ZfkrswAfUzRiXrgd9seol+X379Wa589Pu4UEx9Uok0D4RjRCQ== +"@types/semver@^7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.1.tgz#7a9a5d595b6d873f338c867dcef64df289468cfa" + integrity sha512-ooD/FJ8EuwlDKOI6D9HWxgIgJjMg2cuziXm/42npDC8y4NjxplBUn9loewZiBNCt44450lHAU0OSb51/UqXeag== dependencies: "@types/node" "*" @@ -2124,10 +2124,10 @@ aws-sdk-mock@^5.1.0: sinon "^9.0.1" traverse "^0.6.6" -aws-sdk@^2.637.0, aws-sdk@^2.703.0: - version "2.703.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.703.0.tgz#b22a65094c65109ce52c206e5f46e05247c8aaf4" - integrity sha512-iMJueMVDp2fqopgpjPfejyFaxaksYYdRJ7bxzWEYSxR1UoSf6V9zgcrgkF+SgoxiKJ2rxsbPxhoPu2MV//b9xA== +aws-sdk@^2.637.0, aws-sdk@^2.706.0: + version "2.706.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.706.0.tgz#09f65e9a91ecac5a635daf934082abae30eca953" + integrity sha512-7GT+yrB5Wb/zOReRdv/Pzkb2Qt+hz6B/8FGMVaoysX3NryHvQUdz7EQWi5yhg9CxOjKxdw5lFwYSs69YlSp1KA== dependencies: buffer "4.9.2" events "1.1.1" @@ -3528,6 +3528,11 @@ degenerator@^1.0.4: escodegen "1.x.x" esprima "3.x.x" +delay@4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/delay/-/delay-4.3.0.tgz#efeebfb8f545579cb396b3a722443ec96d14c50e" + integrity sha512-Lwaf3zVFDMBop1yDuFZ19F9WyGcZcGacsbdlZtWjQmM50tOcMntm1njF/Nb/Vjij3KaSvCF+sEYGKrrjObu2NA== + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -3850,10 +3855,10 @@ eslint-module-utils@^2.6.0: debug "^2.6.9" pkg-dir "^2.0.0" -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== +eslint-plugin-import@^2.22.0: + version "2.22.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e" + integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg== dependencies: array-includes "^3.1.1" array.prototype.flat "^1.2.3" @@ -6918,14 +6923,14 @@ nise@^4.0.1: just-extend "^4.0.2" path-to-regexp "^1.7.0" -nock@^12.0.3: - version "12.0.3" - resolved "https://registry.yarnpkg.com/nock/-/nock-12.0.3.tgz#83f25076dbc4c9aa82b5cdf54c9604c7a778d1c9" - integrity sha512-QNb/j8kbFnKCiyqi9C5DD0jH/FubFGj5rt9NQFONXwQm3IPB0CULECg/eS3AU1KgZb/6SwUa4/DTRKhVxkGABw== +nock@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.0.0.tgz#e07276d558245386a2872cebf4d5570583c0d225" + integrity sha512-FiW8t91Je5yG5MVT1r+go1Z9bX3rCYIEjenUYeZrEl2v8aTWdIX336itrmQaKUO8Ske5Z7RHR7OIzr/9p0Ujjg== dependencies: debug "^4.1.0" json-stringify-safe "^5.0.1" - lodash "^4.17.13" + lodash.set "^4.3.2" propagate "^2.0.0" node-fetch-npm@^2.0.2: