diff --git a/.gitallowed b/.gitallowed index c04347c9bf4a6..ed8c612466b3d 100644 --- a/.gitallowed +++ b/.gitallowed @@ -5,6 +5,7 @@ account: '234567890123' # Account patterns used in the README account: '000000000000' account: '111111111111' +account: '222222222222' account: '333333333333' # used in physical names tests in @aws-cdk/core diff --git a/.github/ISSUE_TEMPLATE/general-issues.md b/.github/ISSUE_TEMPLATE/general-issues.md index 1f2d2b6ff707f..64cdaee0da43a 100644 --- a/.github/ISSUE_TEMPLATE/general-issues.md +++ b/.github/ISSUE_TEMPLATE/general-issues.md @@ -2,7 +2,7 @@ name: "\U00002753 General Issue" about: Create a new issue title: "[module] " -labels: needs-triage +labels: needs-triage, guidance --- diff --git a/.github/workflows/issue-label-assign.yml b/.github/workflows/issue-label-assign.yml index 6529fc960b41f..6353942674880 100644 --- a/.github/workflows/issue-label-assign.yml +++ b/.github/workflows/issue-label-assign.yml @@ -18,151 +18,152 @@ jobs: {"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-accessanalyzer]","[aws-accessanalyzer]","[accessanalyzer]","[access analyzer]"],"labels":["@aws-cdk/aws-accessanalyzer"],"assignees":["ericzbeard"]}, {"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-amazonmq]","[aws-amazonmq]","[amazonmq]","[amazon mq]","[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-apigateway]","[aws-apigateway]","[apigateway]","[api gateway]","[api-gateway]"],"labels":["@aws-cdk/aws-apigateway"],"assignees":["nija-at"]}, + {"keywords":["[@aws-cdk/aws-apigatewayv2]","[aws-apigatewayv2]","[apigatewayv2]","[apigateway v2]","[api-gateway-v2]"],"labels":["@aws-cdk/aws-apigatewayv2"],"assignees":["nija-at"]}, + {"keywords":["[@aws-cdk/aws-appconfig]","[aws-appconfig]","[appconfig]","[app config]","[app-config]"],"labels":["@aws-cdk/aws-appconfig"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["[@aws-cdk/aws-applicationautoscaling]","[aws-applicationautoscaling]","[applicationautoscaling]","[application autoscaling]","[application-autoscaling]"],"labels":["@aws-cdk/aws-applicationautoscaling"],"assignees":["NetaNir"]}, + {"keywords":["[@aws-cdk/aws-appmesh]","[aws-appmesh]","[appmesh]","[app mesh]","[app-mesh]"],"labels":["@aws-cdk/aws-appmesh"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["[@aws-cdk/aws-appstream]","[aws-appstream]","[appstream]","[app stream]","[app-stream]"],"labels":["@aws-cdk/aws-appstream"],"assignees":["NetaNir"]}, + {"keywords":["[@aws-cdk/aws-appsync]","[aws-appsync]","[appsync]","[app sync]","[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]","[aws-autoscaling]","[autoscaling]","[auto scaling]","[auto-scaling]"],"labels":["@aws-cdk/aws-autoscaling"],"assignees":["NetaNir"]}, + {"keywords":["[@aws-cdk/aws-autoscaling-api]","[aws-autoscaling-api]","[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]","[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-autoscalingplans]","[aws-autoscalingplans]","[autoscalingplans]","[autoscaling plans]", "[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-certificatemanager]","[aws-certificatemanager]","[certificatemanager]","[certificate manager]","[certificate-manager]","[acm]"],"labels":["@aws-cdk/aws-certificatemanager"],"assignees":["njlynch"]}, {"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-cloudtrail]","[aws-cloudtrail]","[cloudtrail]","[cloud trail]"],"labels":["@aws-cdk/aws-cloudtrail"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-cloudwatch]","[aws-cloudwatch]","[cloudwatch]","[cloud watch]"],"labels":["@aws-cdk/aws-cloudwatch"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-cloudwatch-actions]","[aws-cloudwatch-actions]","[cloudwatch-actions]","[cloudwatch actions]"],"labels":["@aws-cdk/aws-cloudwatch-actions"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-codebuild]","[aws-codebuild]","[codebuild]","[code build]","[code-build]"],"labels":["@aws-cdk/aws-codebuild"],"assignees":["skinny85"]}, + {"keywords":["[@aws-cdk/aws-codecommit]","[aws-codecommit]","[codecommit]","[code commit]", "[code-commit]"],"labels":["@aws-cdk/aws-codecommit"],"assignees":["skinny85"]}, + {"keywords":["[@aws-cdk/aws-codedeploy]","[aws-codedeploy]","[codedeploy]","[code deploy]","[code-deploy]"],"labels":["@aws-cdk/aws-codedeploy"],"assignees":["skinny85"]}, + {"keywords":["[@aws-cdk/aws-codeguruprofiler]","[aws-codeguruprofiler]","[codeguruprofiler]","[codeguru profiler]","[codeguru-profiler]"],"labels":["@aws-cdk/aws-codeguruprofiler"],"assignees":["skinny85"]}, + {"keywords":["[@aws-cdk/aws-codepipeline]","[aws-codepipeline]","[codepipeline]","[code pipeline]","[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-codestarconnections]","[aws-codestarconnections]","[codestarconnections]","[codestar connections]","[codestar-connections]"],"labels":["@aws-cdk/aws-codestarconnections"],"assignees":["skinny85"]}, + {"keywords":["[@aws-cdk/aws-codestarnotifications]","[aws-codestarnotifications]","[codestarnotifications]","[codestar notifications]","[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-datapipeline]","[aws-datapipeline]","[datapipeline]","[data pipeline]","[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-detective]","[aws-detective]","[detective]"],"labels":["@aws-cdk/aws-detective"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-directoryservice]","[aws-directoryservice]","[directoryservice]","[directory service]","[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-docdb]","[aws-docdb]","[docdb]","[doc db]","[doc-db]"],"labels":["@aws-cdk/aws-docdb"],"assignees":["iliapolo"]}, + {"keywords":["[@aws-cdk/aws-dynamodb]","[aws-dynamodb]","[dynamodb]","[dynamo db]","[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-ec2]","[aws-ec2]","[ec2]","[vpc]"],"labels":["@aws-cdk/aws-ec2"],"assignees":["ericzbeard"]}, {"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-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":["ericzbeard"]}, {"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-elasticache]","[aws-elasticache]","[elasticache]","[elastic cache]","[elastic-cache]"],"labels":["@aws-cdk/aws-elasticache"],"assignees":["iliapolo"]}, + {"keywords":["[@aws-cdk/aws-elasticbeanstalk]","[aws-elasticbeanstalk]","[elasticbeanstalk]","[elastic beanstalk]","[elastic-beanstalk]"],"labels":["@aws-cdk/aws-elasticbeanstalk"],"assignees":["skinny85"]}, + {"keywords":["[@aws-cdk/aws-elasticloadbalancing]","[aws-elasticloadbalancing]","[elasticloadbalancing]","[elastic loadbalancing]","[elastic-loadbalancing]","[elb]"],"labels":["@aws-cdk/aws-elasticloadbalancing"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-elasticloadbalancingv2]","[aws-elasticloadbalancingv2]","[elasticloadbalancingv2]","[elastic-loadbalancing-v2]","[elbv2]","[elb v2]"],"labels":["@aws-cdk/aws-elasticloadbalancingv2"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-elasticloadbalancingv2-targets]","[aws-elasticloadbalancingv2-targets]","[elasticloadbalancingv2-targets]","[elasticloadbalancingv2 targets]","[elbv2 targets]"],"labels":["@aws-cdk/aws-elasticloadbalancingv2-targets"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-elasticsearch]","[aws-elasticsearch]","[elasticsearch]","[elastic search]","[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-events]","[aws-events]","[events]","[eventbridge]","pevent-bridge]"],"labels":["@aws-cdk/aws-events"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-events-targets]","[aws-events-targets]","[events-targets]","[events targets]"],"labels":["@aws-cdk/aws-events-targets"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-eventschemas]","[aws-eventschemas]","[eventschemas]","[event schemas]"],"labels":["@aws-cdk/aws-eventschemas"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-fms]","[aws-fms]","[fms]"],"labels":["@aws-cdk/aws-fms"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-fsx]","[aws-fsx]","[fsx]"],"labels":["@aws-cdk/aws-fsx"],"assignees":["ericzbeard"]}, {"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-globalaccelerator]","[aws-globalaccelerator]","[globalaccelerator]","[global accelerator]","[global-accelerator]"],"labels":["@aws-cdk/aws-globalaccelerator"],"assignees":["ericzbeard"]}, {"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-greengrass]","[aws-greengrass]","[greengrass]","[green grass]","[green-grass]"],"labels":["@aws-cdk/aws-greengrass"],"assignees":["shivlaks"]}, + {"keywords":["[@aws-cdk/aws-guardduty]","[aws-guardduty]","[guardduty]","[guard duty]","[guard-duty]"],"labels":["@aws-cdk/aws-guardduty"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-iam]","[aws-iam]","[iam]"],"labels":["@aws-cdk/aws-iam"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-imagebuilder]","[aws-imagebuilder]","[imagebuilder]","[image builder]","[image-builder]"],"labels":["@aws-cdk/aws-imagebuilder"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-inspector]","[aws-inspector]","[inspector]"],"labels":["@aws-cdk/aws-inspector"],"assignees":["ericzbeard"]}, {"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-iotanalytics]","[aws-iotanalytics]","[iotanalytics]","[iot analytics]","[iot-analytics]"],"labels":["@aws-cdk/aws-iotanalytics"],"assignees":["shivlaks"]}, + {"keywords":["[@aws-cdk/aws-iotevents]","[aws-iotevents]","[iotevents]","[iot events]","[iot-events]"],"labels":["@aws-cdk/aws-iotevents"],"assignees":["shivlaks"]}, + {"keywords":["[@aws-cdk/aws-iotthingsgraph]","[aws-iotthingsgraph]","[iotthingsgraph]","[iot things graph]","[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-kinesisanalytics]","[aws-kinesisanalytics]","[kinesisanalytics]","[kinesis analytics]","[kinesis-analytics]"],"labels":["@aws-cdk/aws-kinesisanalytics"],"assignees":["iliapolo"]}, + {"keywords":["[@aws-cdk/aws-kinesisfirehose]","[aws-kinesisfirehose]","[kinesisfirehose]","[kinesis firehose]","[kinesis-firehose]"],"labels":["@aws-cdk/aws-kinesisfirehose"],"assignees":["iliapolo"]}, + {"keywords":["[@aws-cdk/aws-kms]","[aws-kms]","[kms]"],"labels":["@aws-cdk/aws-kms"],"assignees":["njlynch"]}, + {"keywords":["[@aws-cdk/aws-lakeformation]","[aws-lakeformation]","[lakeformation]","[lake formation]","[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-logs]","[aws-logs]","[logs]"],"labels":["@aws-cdk/aws-logs"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-logs-destinations]","[aws-logs-destinations]","[logs-destinations]","[logs destinations]"],"labels":["@aws-cdk/aws-logs-destinations"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-managedblockchain]","[aws-managedblockchain]","[managedblockchain]","[managed blockchain]","[managed-blockchain]"],"labels":["@aws-cdk/aws-managedblockchain"],"assignees":["shivlaks"]}, + {"keywords":["[@aws-cdk/aws-mediaconvert]","[aws-mediaconvert]","[mediaconvert]","[media convert]","[media-convert]"],"labels":["@aws-cdk/aws-mediaconvert"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-medialive]","[aws-medialive]","[medialive]","[media live]","[media-live]"],"labels":["@aws-cdk/aws-medialive"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-mediastore]","[aws-mediastore]","[mediastore]","[media store]","[media-store]"],"labels":["@aws-cdk/aws-mediastore"],"assignees":["ericzbeard"]}, {"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-networkmanager]","[aws-networkmanager]","[networkmanager]","[network manager]","[network-manager]"],"labels":["@aws-cdk/aws-networkmanager"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-opsworks]","[aws-opsworks]","[opsworks]","[ops works]","[ops-works]"],"labels":["@aws-cdk/aws-opsworks"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["[@aws-cdk/aws-opsworkscm]","[aws-opsworkscm]","[opsworkscm]","[opsworks cm]","[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-pinpointemail]","[aws-pinpointemail]","[pinpointemail]","[pinpoint email]","[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-rds]","[aws-rds]","[rds]"],"labels":["@aws-cdk/aws-rds"],"assignees":["skinny85"]}, + {"keywords":["[@aws-cdk/aws-redshift]","[aws-redshift]","[redshift]","[red shift]","[red-shift]"],"labels":["@aws-cdk/aws-redshift"],"assignees":["nija-at"]}, + {"keywords":["[@aws-cdk/aws-resourcegroups]","[aws-resourcegroups]","[resourcegroups]","[resource groups]","[resource-groups]"],"labels":["@aws-cdk/aws-resourcegroups"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["[@aws-cdk/aws-robomaker]","[aws-robomaker]","[robomaker]","[robo maker]","[robo-maker]"],"labels":["@aws-cdk/aws-robomaker"],"assignees":["NetaNir"]}, + {"keywords":["[@aws-cdk/aws-route53]","[aws-route53]","[route53]","[route 53]","[route-53]"],"labels":["@aws-cdk/aws-route53"],"assignees":["shivlaks"]}, + {"keywords":["[@aws-cdk/aws-route53-patterns]","[aws-route53-patterns]","[route53-patterns]","[route53 patterns]","[route-53-patterns]"],"labels":["@aws-cdk/aws-route53-patterns"],"assignees":["shivlaks"]}, + {"keywords":["[@aws-cdk/aws-route53-targets]","[aws-route53-targets]","[route53-targets]","[route53 targets]","[route-53-targets]"],"labels":["@aws-cdk/aws-route53-targets"],"assignees":["shivlaks"]}, + {"keywords":["[@aws-cdk/aws-route53resolver]","[aws-route53resolver]","[route53resolver]","[route53 resolver]","[route-53-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-sagemaker]","[aws-sagemaker]","[sagemaker]","[sage maker]","[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-secretsmanager]","[aws-secretsmanager]","[secretsmanager]","[secrets manager]","[secrets-manager]"],"labels":["@aws-cdk/aws-secretsmanager"],"assignees":["njlynch"]}, + {"keywords":["[@aws-cdk/aws-securityhub]","[aws-securityhub]","[securityhub]","[security hub]","[security-hub]"],"labels":["@aws-cdk/aws-securityhub"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-servicecatalog]","[aws-servicecatalog]","[servicecatalog]","[service catalog]","[service-catalog]"],"labels":["@aws-cdk/aws-servicecatalog"],"assignees":["MrArnoldPalmer"]}, + {"keywords":["[@aws-cdk/aws-servicediscovery]","[aws-servicediscovery]","[servicediscovery]","[service discovery]","[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]","[aws-stepfunctions]","[stepfunctions]","[step functions]","[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-synthetics]","[aws-synthetics]","[synthetics]"],"labels":["@aws-cdk/aws-synthetics"],"assignees":["ericzbeard"]}, {"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-waf]","[aws-waf]","[waf]"],"labels":["@aws-cdk/aws-waf"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-wafregional]","[aws-wafregional]","[wafregional]","[waf regional]","[waf-regional]"],"labels":["@aws-cdk/aws-wafregional"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/aws-wafv2]","[aws-wafv2]","[wafv2]","[waf v2]","[waf-v2]"],"labels":["@aws-cdk/aws-wafv2"],"assignees":["ericzbeard"]}, {"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/cfnspec]","[cfnspec]","[cfn spec]","[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/cloudformation-include]","[cloudformation-include]","[cloudformation include]","[cfn 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"]} + {"keywords":["[@aws-cdk/aws-macie]","[aws-macie]","[macie]"],"labels":["@aws-cdk/aws-macie"],"assignees":["ericzbeard"]}, + {"keywords":["[@aws-cdk/pipelines]","[pipelines]","[cdk pipelines]","[cdk-pipelines]"],"labels":["@aws-cdk/pipelines"],"assignees":["ericzbeard"]} ] diff --git a/.gitignore b/.gitignore index 36d6e15a83ab0..74312eef24f20 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ coverage/ *.sw[a-z] *~ .idea +junit.xml # We don't want tsconfig at the root /tsconfig.json @@ -38,3 +39,4 @@ yarn-error.log # Parcel default cache directory .parcel-cache + diff --git a/.gitpod.yml b/.gitpod.yml index 5491cbd291d9e..92cf51b5ccdd3 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,6 +1,6 @@ image: jsii/superchain tasks: - - init: yarn build --skip-test --no-bail + - init: yarn build --skip-test --no-bail --skip-prereqs vscode: extensions: diff --git a/CHANGELOG.md b/CHANGELOG.md index ce0459cfaf7c8..7411a92e30a23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ 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.51.0](https://github.com/aws/aws-cdk/compare/v1.50.0...v1.51.0) (2020-07-09) + + +### Features + +* **cloudfront:** Add connectionAttempts, connectionTimeout in origin configuration ([#8573](https://github.com/aws/aws-cdk/issues/8573)) ([84b923f](https://github.com/aws/aws-cdk/commit/84b923fb853d674e0a07f4296f2b23800d139366)), closes [#8572](https://github.com/aws/aws-cdk/issues/8572) +* Developer Preview of CDK Pipelines ([#8868](https://github.com/aws/aws-cdk/issues/8868)) ([d2609bd](https://github.com/aws/aws-cdk/commit/d2609bdbd0ba0347ff617267e928a2b54482e78a)), closes [aws/aws-cdk-rfcs#49](https://github.com/aws/aws-cdk-rfcs/issues/49) + + +### Bug Fixes + +* **appmesh:** Update enums for appmesh ([#8716](https://github.com/aws/aws-cdk/issues/8716)) ([64e3d88](https://github.com/aws/aws-cdk/commit/64e3d888a66da84c066298564ad2875cb93bfd27)) +* **cli:** Python sample app template does not follow PEP8 ([#8936](https://github.com/aws/aws-cdk/issues/8936)) ([0717919](https://github.com/aws/aws-cdk/commit/07179194d8fc4e3beaeafbe6cf04a2f3d1addd2c)) +* **codepipeline:** set correct header assignment in S3 deployment cache control ([#8864](https://github.com/aws/aws-cdk/issues/8864)) ([be1094b](https://github.com/aws/aws-cdk/commit/be1094b4f4ef1eb194333faaf804db610535fea1)), closes [#8774](https://github.com/aws/aws-cdk/issues/8774) +* **ec2:** VpcEndpoint AZ lookup fails for AWS services ([#8386](https://github.com/aws/aws-cdk/issues/8386)) ([54e5c36](https://github.com/aws/aws-cdk/commit/54e5c3658241320244ae3055ec3ef7ca18926001)) +* **iam:** cannot import service role with a principal in its path ([#8692](https://github.com/aws/aws-cdk/issues/8692)) ([55eb7d7](https://github.com/aws/aws-cdk/commit/55eb7d794450702e540246819f622a2bba22380e)), closes [#8691](https://github.com/aws/aws-cdk/issues/8691) + ## [1.50.0](https://github.com/aws/aws-cdk/compare/v1.49.1...v1.50.0) (2020-07-07) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index edaef65689c87..21469506518de 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,26 +77,25 @@ you need to have the following SDKs and tools locally: - [Node.js >= 10.13.0](https://nodejs.org/download/release/latest-v10.x/) - We recommend using a version in [Active LTS](https://nodejs.org/en/about/releases/) - ⚠️ versions `13.0.0` to `13.6.0` are not supported due to compatibility issues with our dependencies. -- [Yarn >= 1.19.1](https://yarnpkg.com/lang/en/docs/install) -- [Java OpenJDK 8](https://docs.aws.amazon.com/corretto/latest/corretto-8-ug/downloads-list.html) -- [Apache Maven](http://maven.apache.org/install.html) -- [.NET Core SDK 3.1](https://www.microsoft.com/net/download) -- [Python 3.6.5](https://www.python.org/downloads/release/python-365/) -- [Ruby 2.5.1](https://www.ruby-lang.org/en/news/2018/03/28/ruby-2-5-1-released/) +- [Yarn >= 1.19.1, < 1.3](https://yarnpkg.com/lang/en/docs/install) +- [Java >= OpenJDK 8, 11, 14](https://docs.aws.amazon.com/corretto/latest/corretto-8-ug/downloads-list.html) +- [Apache Maven >= 3.6.0, < 4.0](http://maven.apache.org/install.html) +- [.NET Core SDK 3.1.x](https://www.microsoft.com/net/download) +- [Python >= 3.6.5, < 4.0](https://www.python.org/downloads/release/python-365/) +- [Ruby >= 2.5.1, < 3.0](https://www.ruby-lang.org/en/news/2018/03/28/ruby-2-5-1-released/) +- [Docker 19.03](https://docs.docker.com/get-docker/) The basic commands to get the repository cloned and built locally follow: ```console $ git clone https://github.com/aws/aws-cdk.git $ cd aws-cdk -$ yarn install $ yarn build ``` -If you get compiler errors when building, a common cause is globally installed tools like tslint and typescript. Try uninstalling them. +If you get compiler errors when building, a common cause is a globally installed typescript. Try uninstalling it. ``` -npm uninstall -g tslint npm uninstall -g typescript ``` @@ -143,6 +142,9 @@ Sometimes, the GitHub issue is sufficient for such discussions, and can be suffi clarity on what you plan to do. Sometimes, a design document would work better, so people can provide iterative feedback. +Before starting on a design, read through the [design guidelines](DESIGN_GUIDELINES.md) for general +patterns and tips. + In such cases, use the GitHub issue description to collect **requirements** and **use cases** for your feature. @@ -277,7 +279,7 @@ However, in many cases, you can probably get away with just building a portion o want to work on. We recommend that you use [Visual Studio Code](https://code.visualstudio.com/) to work on the CDK. Be sure to install -the [tslint extension](https://marketplace.visualstudio.com/items?itemName=eg2.tslint) for it as well, since we have +the [eslint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) for it as well, since we have strict linting rules that will prevent your code from compiling, but with VSCode and this extension can be automatically fixed for you by hitting `Ctrl-.` when your cursor is on a red underline. @@ -332,9 +334,6 @@ The following linters are used - #### eslint -Historically, the CDK has used tslint for linting its typescript source code. With [tslint's deprecation in -2019](https://medium.com/palantir/tslint-in-2019-1a144c2317a9), we are slowly moving over to using eslint. - All packages in the repo use a standard base configuration found at [eslintrc.js](tools/cdk-build-tools/config/eslintrc.js). This can be customized for any package by modifying the `.eslintrc` file found at its root. diff --git a/DESIGN_GUIDELINES.md b/DESIGN_GUIDELINES.md new file mode 100644 index 0000000000000..822965e702bf4 --- /dev/null +++ b/DESIGN_GUIDELINES.md @@ -0,0 +1,1314 @@ +# AWS Construct Library Design Guidelines + +The AWS Construct Library is a rich class library of CDK constructs which +represent all resources offered by the AWS Cloud and higher-level constructs for +achieving common tasks. + +The purpose of this document is to provide guidelines for designing the APIs in +the AWS Construct Library in order to ensure a consistent and integrated +experience across the entire AWS surface area. + +As much as possible, the guidelines in this document are enforced using the +**awslint** tool which reflects on the APIs and verifies that the APIs adhere to +the guidelines. When a guideline is backed by a linter rule, the rule name will +be referenced like this: _[awslint:resource-class-is-construct]_. + +For the purpose of this document we will use "Foo" to denote the official name +of the resource as defined in the AWS CloudFormation resource specification +(i.e. "Bucket", "Queue", "Topic", etc). This notation allows deriving names from +the official name. For example, `FooProps` would be `BucketProps`, `TopicProps`, +etc, `IFoo` would be `IBucket`, `ITopic` and so forth. + +The guidelines in this document use TypeScript (and npm package names) since +this is the source programming language used to author the library, which is +later packaged and published to all programming languages through +[jsii](https://github.com/awslabs/jsii). + +When designing APIs for the AWS Construct Library (and these guidelines), we +follow the tenets of the AWS CDK: + +* **Meet developers where they are**: our APIs are based on the mental model of +the user, and not the mental model of the service APIs, which are normally +designed against the constraints of the backend system and the fact that these +APIs are used through network requests. It's okay to enable multiple ways to +achieve the same thing, in order to make it more natural for users who come from +different mental models. +* **Full coverage**: the AWS Construct Library exposes the full surface area of +AWS. It is not opinionated about which parts of the service API should be +used. However, it offers sensible defaults to allow users to get started quickly +with best practices, but allows them to fully customize this behavior. We use a +layered architecture so that users can choose the level of abstraction that fits +their needs. +* **Designed for the CDK**: the AWS Construct Library is primarily optimized for +AWS customers who use the CDK idiomatically and natively. As much as possible, +the APIs are non-leaky and do not require that users understand how AWS +CloudFormation works. If users wish to “escape” from the abstraction, the APIs +offer explicit ways to do that, so that users won't be blocked by missing +capabilities or issues. +* **Open**: the AWS Construct Library is an open and extensible framework. It is +also open source. It heavily relies on interfaces to allow developers to extend +its behavior and provide their own custom implementations. Anyone should be able +to publish constructs that look & feel exactly like any construct in the AWS +Construct Library. +* **Designed for jsii**: the AWS Construct Library is built with jsii. This +allows the library to be used from all supported programming languages. jsii +poses restrictions on language features that cannot be idiomatically represented +in target languages. + +## API Design + +### Modules + +AWS resources are organized into modules based on their AWS service. For +example, the "Bucket" resource, which is offered by the Amazon S3 service will +be available under the **@aws-cdk/aws-s3** module. We will use the “aws-” prefix +for all AWS services, regardless of whether their marketing name uses an +“Amazon” prefix (e.g. “Amazon S3”). Non-AWS services supported by AWS +CloudFormation (like the Alexa::ASK namespace) will be **@aws-cdk/alexa-ask**. + +The name of the module is based on the AWS namespace of this service, which is +consistent with the AWS SDKs and AWS CloudFormation _[awslint:module-name]_. + +All major versions of an AWS namespace will be mastered in the AWS Construct +Library under the root namespace. For example resources of the **ApiGatewayV2** +namespace will be available under the **@aws-cdk/aws-apigateway** module (and +not under “v2) _[awslint:module-v2]_. + +In some cases, it makes sense to introduce secondary modules for a certain +service (e.g. aws-s3-notifications, aws-lambda-event-sources, etc). The name of +the secondary module will be +**@aws-cdk/aws-xxx-\**_[awslint:module-secondary]_. + +Documentation for how to use secondary modules should be in the main module. The +README file should refer users to the central module +_[awslint:module-secondary-readme-redirect]_. + +### Construct Class + +Constructs are the basic building block of CDK applications. They represent +abstract cloud components of any complexity. Constructs in the AWS Construct +Library normally represent physical AWS resources (such as an SQS queue) but +they can also represent abstract composition of other constructs (such as +**LoadBalancedFargateService**). + +Most of the guidelines in this document apply to all constructs in the AWS +Construct Library, regardless of whether they represent concrete AWS resources +or abstractions. However, you will notice that some sections explicitly call out +guidelines that apply only to AWS resources (and in many cases +enforced/implemented by the **Resource** base class). + +AWS services are modeled around the concept of *resources*. Service normally +expose through their APIs one or more resources, which can be provisioned +through the APIs control plane or through AWS CloudFormation. + +Every resource available in the AWS platform will have a corresponding resource +construct class to represents it. For example, the **s3.Bucket** construct +represents Amazon S3 Buckets, the **dynamodb.Table** construct represents an +Amazon DynamoDB table. The name of resource constructs must be identical to the +name of the resource in the AWS API, which should be consistent with the +resource name in the AWS CloudFormation spec _[awslint:resource-class]_. + +> The _awslint:resource-class_ rule is a **warning** (instead of an error). This + allows us to gradually expand the coverage of the library. + +Classes which represent AWS resources are constructs and they must extend the +**cdk.Resource** class directly or indirectly +_[awslint:resource-class-extends-resource]_. + +> Resource constructs are normally implemented using low-level CloudFormation + (“CFN”) constructs, which are automatically generated from the AWS + CloudFormation resource specification. + +The signature (both argument names and types) of all construct initializers +(constructors) must be as follows _[awslint:construct-ctor]_: + +```ts +constructor(scope: cdk.Construct, id: string, props: FooProps) +``` + +The **props** argument must be of type FooProps +[_awslint:construct-ctor-props-type_]. + +If all props are optional, the `props` argument must also be optional +_[awslint:construct-ctor-props-optional]_. + +```ts +constructor(scope: cdk.Construct, id: string, props: FooProps = { }) +``` + +> Using `={}` as a default value is preferable to using an optional qualifier + (`?`) since it will ensure that props will never be `undefined` and therefore + easier to parse in the method body. + +As a rule of thumb, most constructs should directly extend the **Construct** or +**Resource** instead of another construct. Prefer representing polymorphic +behavior through interfaces and not through inheritance. + +Construct classes should extend only one of the following classes +[_awslint:construct-inheritence_]: + +* The **Resource** class (if it represents an AWS resource) The **Construct** +* class (if it represents an abstract component) The **XxxBase** class (which, +* in turn extends **Resource**) + +All constructs must define a static type check method called **isFoo** with the +following implementation [_awslint:static-type-check_]: + +```ts +const IS_FOO = Symbol.for('@aws-cdk/aws-foo.Foo'); + +export class Foo { + public static isFoo(x: any): x is Foo { + return IS_FOO in x; + } + + constructor(scope: Construct, id: string, props: FooProps) { + super(scope, id); + + Object.defineProperty(this, IS_FOO, { value: true }); + } +} +``` + +### Construct Interface + +One of the important tenets of the AWS Construct Library is to use strong-types +when referencing resources across the library. This is in contrast to how AWS +backend APIs (and, consequently, AWS CloudFormation) model reference via one of +their *runtime attributes* (such as the resource's ARN). Since the AWS CDK is a +client-side abstraction, we can offer developers a much richer experience by +using *object references* instead of *attribute references*. + +Using object references instead of attribute references allows consumers of +these objects to have a richer interaction with the consumed object. They can +reference runtime attributes such as the resource's ARN, but also utilize logic +encapsulated by the target object. + +Here's an example: when a user defines an S3 bucket, they can pass in a KMS key +that will be used for bucket encryption: + +```ts +new s3.Bucket(this, 'MyBucket', { encryptionKey: key }); +``` + +The **Bucket** class can now use **key.keyArn** to obtain the ARN for the key, +but it can also call the **key.grantEncrypt** method as a result of a call to +**bucket.grantWrite**. Separation of concerns is a basic OO design principle: +the fact that the Bucket class needs the ARN or that it needs to request +encryption permissions are not the user's concern, and the API of the Bucket +class should not “leak” these implementation details. In the future, the Bucket +class can decide to interact differently with the **key** and this won't require +expanding it's surface area. It also allows the **Key** class to change it's +behavior (i.e. add an IAM action to enable encryption of certain types of keys) +without affecting the API of the consumer. + +#### owned vs. unowned constructs + +Using object references instead of attribute references provides a richer API, +but also introduces an inherent challenge: how do we reference constructs that +are not defined inside the same app (“**owned**” by the app). These could be +resources that were created by some other AWS CDK app, via the AWS console, +etc. We call these “**unowned**”**constructs.** + +In order to model this concept of owned and unowned constructs, all constructs +in the AWS Construct Library should always have a corresponding **construct +interface**. This interface includes the API of the construct +_[awslint:construct-interface]_. + +Therefore, when constructs are referenced ***anywhere*** in the API (e.g. in +properties or methods of other resources or higher-level constructs), the +resource interface (`IFoo`) should be used over concrete resource classes +(`Foo`). This will allow users to supply either internal or external resources +_[awslint:ref-via-interface]_. + +Construct interfaces must extend the **IConstruct** interface in order to allow +consumers to take advantage of common resource capabilities such as unique IDs, +paths, scopes, etc _[awslint:construct-interface-extends-iconstruct]_. + +Constructs that directly represent AWS resources (most of the constructs in the +AWS Construct Library) should extend **IResource** (which, transitively, extends +**IConstruct**) _[awslint:resource-interface-extends-resource]_. + +#### Abstract Base + +It is recommended to implement an abstract base class **FooBase** for each +resource **Foo****. **The base class would normally implement the entire +construct interface and leave attributes as abstract properties. + +```ts +abstract class FooBase extends Resource implements IFoo { + public abstract fooName: string; + public abstract fooArn: string; + + // .. concrete implementation of IFoo (grants, metrics, factories), + // should only rely on "fooName" and "fooArn" theoretically +} +``` + +The construct base class can then be used to implement the various +deserialization and import methods by defining an ad-hoc local class which +simply provides an implementation for the attributes (see “Serialization” below +for an example). + +The abstract base class should be internal and not exported in the module's API +_[awslint:construct-base-is-private]_. This is only a recommended (linter +warning). + +### Props + +Constructs are defined by creating a new instance and passing it a set of +**props** to the constructor. Throughout this document, we will refer to these +as “props” (to distinguish them from JavaScript object properties). + + The props argument for the **Foo** construct should be a struct (interface with + only readonly properties) named **FooProps** _[awslint:props-struct-name]_. + +> Even if a construct props simply extends from some other Props struct and does + not add any new properties, you should still define it, so it will be + extensible in the future without breaking users in languages like Java where + the props struct name is explicitly named. + +Props are the most important aspect of designing a construct. Props are the +entry point of the construct. They should reflect the entire surface area of the +service through semantics that are intuitive to how developers perceive the +service and it's capabilities. + +When designing the props of an AWS resource, consult the AWS Console experience +for creating this resource. Service teams spend a lot of energy thinking about +this experience. This is a great resource for learning about the mental model of +the user. Aligning with the console also makes it easier for users to jump back +and forth between the AWS Console (the web frontend of AWS) and the CDK (the +“programmatic frontend” of AWS). + +AWS constructs should *not* have a “props” property +[_awslint:props-no-property_]. + +Construct props should expose the *full set* of capabilities of the AWS service +through a declarative interface [_awslint:props-coverage_]. + +This section describes guidelines for construct props. + +#### types + +Use **strong types** (and specifically, construct interfaces) instead of +physical attributes when referencing other resources. For example, instead of +**keyArn**, use **kms.IKey** [_awslint:props-no-arn-refs_]. + +Do not “leak” the details or types of the CFN layer when defining your construct +API. In almost all cases, a richer object-oriented API can be exposed to +encapsulate the low-level surface [_awslint:props-no-cfn-types_]. + +Do not use the **Token** type. It provides zero type safety, and is a functional +interface that may not translate cleanly in other JSII runtimes: ergo it should +be avoided wherever possible [_awslint:props-no-tokens_]. + +**deCDK** allows users to synthesize CDK stacks through a CloudFormation-like + template, similar to SAM. CDK constructs are represented in deCDK templates + like CloudFormation resources. Technically, this means that when a construct + is defined, users supply an ID, type and a set of properties.****In order to + allow users to instantiate all AWS Construct Library constructs through the + deCDK syntax, we pose restrictions on prop types _[awslint:props-decdk]_: + +* Primitives (string, number, boolean, date) Collections (list, map) Structs +* Enums Enum-like classes Union-like classes References to other constructs +* (through their construct interface) Integration interfaces (interfaces that +* have a “**bind**” method) + +#### Defaults + +A prop should be *required* only if there is no possible sensible default value +that can be provided *or calculated*. + +Sensible defaults have a tremendous impact on the developer experience. They +offer a quick way to get started with minimal cognitive, but do not limit users +from harnessing the full power of the resource, and customizing its behavior. + +> A good way to determine what's the right sensible default is to refer to the + AWS Console resource creation experience. In many cases, there will be + alignment. + +The **@default** documentation tag must be included on all optional properties +of interfaces. Since there are cases where the default behavior is not a +specific value but rather depends on circumstances/context, the default +documentation tag must always begin with a **“**-"**** and then include a +description of the default behavior _[awslint:props-default-doc]_. + +For example: + +```ts +/** + * External KMS key to use for bucket encryption. + * + * @default - if encryption is set to "Kms" and this property is undefined, a + * new KMS key will be created and associated with this bucket. + */ +encryptionKey?: kms.IEncryptionKey; +``` + +#### Flat + +Do not introduce artificial nesting for props. It hinders discoverability and +makes it cumbersome to use in some languages (like Java) [_awslint:props-flat_]. + +You can use a shared prefix for related properties to make them appear next to +each other in documentation and code completion: + +For example, instead of: + +```ts +new Bucket(this, 'MyBucket', { + bucketWebSiteConfiguration: { + errorDocument: '404.html', + indexDocument: 'index.html', + } +}); +``` + +Prefer: + +```ts +new Bucket(this, 'MyBucket', { + websiteErrorDocument: '404.html', + websiteIndexDocument: 'index.html' +}); +``` + +#### Concise + +Property names should be short and concise as possible and take into +consideration the ample context in which the property is used. Being concise +doesn't mean inventing new semantics. It just means that you can remove +redundant context from the property names. + +Being concise doesn't mean you should invent new service semantics (see next +item). It just means that you can remove redundant context from the property +names. For example, there is no need to repeat the resource type, the property +type or indicate that this is a "configuration". + +For example prefer “readCapacity” versus “readCapacityUnits”. + +#### Naming + +We prefer the terminology used by the official AWS service documentation over +new terminology, even if you think it's not ideal. It helps users diagnose +issues and map the mental model of the construct to the service APIs, +documentation and examples. For example don't be tempted to change SQS's +**dataKeyReusePeriod** with **keyRotation** because it will be hard for people +to diagnose problems. They won't be able to just search for “sqs dataKeyReuse” +and find topics on it. + +> We can relax this guidelines when this is about generic terms (like + `httpStatus` instead of `statusCode`). The important semantics to preserve are + for *service features*: I wouldn't want to rename "lambda layers" to "lambda + dependencies" just because it makes more sense because then users won't be + able to bind these terms to the service documentation. + +Indicate units of measurement in property names that don't use a strong +type. Use “milli”, “sec”, “min”, “hr”, “Bytes”, “KiB”, “MiB”, “GiB” (KiB=1024 +bytes, while KB=1000 bytes). + +#### Property Documentation + +Every prop must have detailed documentation. It is recommended to **copy** from +the official AWS documentation in English if possible so that language and style +will match the service. + +#### Enums + +When relevant, use enums to represent multiple choices. + +```ts +export enum MyEnum { + OPTION1 = 'op21', + OPTION2 = 'opt2', +} +``` + +A common pattern in AWS is to allow users to select from a predefined set of +common options, but also allow the user to provide their own customized values. + +A pattern for an "Enum-like Class" should be used in such cases: + +```ts +export interface MyProps { + option: MyOption; +} + +export class MyOption { + public static COMMON_OPTION_1 = new MyOption('common.option-1'); + public static COMMON_OPTION_2 = new MyOption('common.option-2'); + + public MyOption(public readonly customValue: string) { } +} +``` + +Then usage would be: + +```ts +new BoomBoom(this, 'Boom', { + option: MyOption.COMMON_OPTION_1 +}); +``` + +Suggestion for alternative syntax for custom options? Motivation: if we make +everything go through static factories, it will look more regular (I'm fine not +pursuing this, just popped into my head): + +```ts +export class MyOption { + public static COMMON_OPTION_1 = new MyOption('common.option-1'); + public static COMMON_OPTION_2 = new MyOption('common.option-2'); + + public static custom(value: string) { + return new MyOption(value); + } + + // 'protected' iso. 'private' so that someone that really wants to can still + // do subclassing. But maybe might as well be private. + protected MyOption(public readonly value: string) { } +} + +// Usage +new BoomBoom(this, 'Boom', { + option: MyOption.COMMON_OPTION_1 +}); + +new BoomBoom(this, 'Boom', { + option: MyOption.custom('my-value') +}); +``` + +#### Unions + +Do not use TypeScript union types in construct APIs (`string | number`) since +many of the target languages supported by the CDK cannot strongly-model such +types _[awslint:props-no-unions]_. + +Instead, use a class with static methods: + +```ts +new lambda.Function(this, 'MyFunction', { + code: lambda.Code.asset('/asset/path'), // or + code: lambda.Code.bucket(myBucket, 'bundle.zip'), // or + code: lambda.Code.inline('code') + // etc +} +``` + +### Attributes + +Every AWS resource has a set of "physical" runtime attributes such as ARN, +physical names, URLs, etc. These attributes are commonly late-bound, which means +they can only be resolved during deployment, when AWS CloudFormation actually +provisions the resource. + +AWS constructs must expose all resource attributes defined in the underlying +CloudFormation resource as readonly properties of the class +_[awslint:resource-attribute]_. + +All properties that represent resource attributes must include the JSDoc tag +**@attribute** _[awslint:attribute-tag]_. + +All attribute names must begin with the type name as a prefix +(e.g. ***bucket*Arn** instead of just **arn**) _[awslint:attribute-name]_. This +implies that if a property begins with the type name, it must have an +**@attribute** tag. + +All resource attributes must be represented as readonly properties of the +resource interface _[awslint:attribute-readonly]_. + +Resource attributes should use a type that corresponds to the resolved AWS +CloudFormation type (e.g. **string**, **string[]**) _[awslint:attribute-type]_. + +> Resource attributes almost always represent string values (URL, ARN, + name). Sometimes they might also represent a list of strings. Since attribute + values can either be late-bound ("a promise to a string") or concrete ("a + string"), the AWS CDK has a mechanism called "tokens" which allows codifying + late-bound values into strings or string arrays. This approach was chosen in + order to dramatically simplify the type-system and ergonomics of CDK code. As + long as users treat these attributes as opaque values (e.g. not try to parse + them or manipulate them), they can be used interchangeably. + +If needed, you can query whether an object includes unresolved tokens by using +the **Token.unresolved(x)** method. + +To ensure users are aware that the value returned by attribute properties should +be treated as an opaque token, the JSDoc “@returns” annotation should begin with +“**@returns a $token representing the xxxxx**” +[_awslint:attribute-doc-returns-token_]. + +### Configuration + +When an app defines a construct or resource, it specifies its provisioning +configuration upon initialization. For example, when an SQS queue is defined, +it's visibility timeout can be configured. + +Naturally, when constructs are imported (unowned), the importing app does not +have control over its configuration (e.g. you cannot change the visibility +timeout of an SQS queue that you didn't create). Therefore, construct interfaces +cannot include methods that require access/mutation of configuration. + +One of the problems with configuration mutation is that there could be a race +condition between two parts of the app, trying to set contradicting values. + +There are a number use cases where you'd want to provide APIs which expose or +mutate the construct's configuration. For example, +**lambda.Function.addEnvironment** is a useful method that adds an environment +variable to the function's runtime environment, and used occasionally to inject +dependencies. + +> Note that there are APIs that look like they mutate the construct, but in fact + they are **factories** (i.e. they define resources on the user's stack). Those + APIs _should_ be exposed on the construct interface and not on the construct + class. + +To help avoid the common mistake of exposing non-configuration APIs on the +construct class (versus the construct interface), we require that configuration +APIs (methods/properties) defined on the construct class will be annotated with +the **@config** jsdoc tag [_awslint:config-explicit_]. + +```ts +interface IFoo extends IConstruct { + bar(): void; +} + +class Foo extends Construct implements IFoo { + public bar() { } + + /** @mutating */ + public goo() { } + + public mutateMe() { } // ERROR! missing "@mutating" or missing on IFoo +} +``` + +#### Prefer Additions + +As a rule of thumb, “adding” items to configuration props of type unordered +array is normally considered safe as it will unlikely cause race conditions. If +the prop is a map (like in **addEnvironment**), write defensive code that will +throw if two values are assigned to the same key. + +#### Dropped Mutations + +Since all references across the library are done through a construct's +interface, methods that are only available on the concrete construct class will +not be accessible by code that uses the interface type. For example, code that +accepts a **lambda.IFunction** will not see the **addEnvironment** method. + +In most cases, this is desirable, as it ensures that only the code the owns the +construct (instantiated it), will be able to mutate its configuration. + +However, there are certain areas in the library, where, for the sake of +consistency and interoperability, we allow mutating methods to be exposed on the +interface. For example, **grant** methods are exposed on the construct interface +and not on the concrete class. In most cases, when you grant a permission on an +AWS resource, the *principal's* policy needs to be updated, which mutates the +consumer . However, there are certain cases where a *resource policy* must be +updated. However, if the resource is unowned, it doesn't make sense (or even +impossible) to update its policy (there is usually a 1:1 relationship between a +resource and a resource policy). In such a case, we decided that grant methods +will simply skip any changes to resource policies, but will issue attach a +**permission notice** to the app, which will be printed when the stack is +synthesized by the toolkit. + +### Factories + +In most AWS services, there's a one or more resource which can be referred to as +“primary resources” (normally one), while other resources exposed by the service +can be considered “secondary resources”. + +For example, the AWS Lambda service exposes the **Function** resource, which can +be considered the primary resource while **Layer**, **Permission**, **Alias** +are considered secondary. For API Gateway, the primary resource is **RestApi**, +and there are many secondary resources such as **Method**, **Resource**, +**Deployment**, **Authorizer**. + +Secondary resources are normally associated with the primary resource (i.e. a +reference to the primary resource must be supplied upon initialization). + +Users should be able to define secondary resources either by directly +instantiating their construct class (like any other construct), and passing in a +reference to the primary resource's construct interface *or* it is recommended +to implement convenience methods on the primary resource that will facilitate +defining secondary resources. This improves discoverability and ergonomics +_[awslint:factory-method]_. + +For example, **lambda.Function.addLayer** can be used to add a layer to the +function, **apigw.RestApi.addResource** can be used to add to an API. + +Methods for defining a secondary resource “Bar” associated with a primary +resource “Foo” should have the following signature: + +```ts +export interface IFoo { + addBar(...): Bar; +} +``` + +Notice that: + +* The method has an “add” prefix. It implies that users are adding something to +* their stack. The method is implemented on the construct interface (to allow +* adding secondary resources to unowned constructs). The method returns a “Bar” +* instance (owned). + +In order to reuse the set of props used to configure the secondary resource, +define a base interface for **FooProps** called **FooOptions** to allow +secondary resource factory methods to reuse props +_[awslint:factory-method-options]_: + +```ts +export interface LogStreamOptions { + logStreamName?: string; +} + +export interface LogStreamProps extends LogStreamOptions { + logGroup: ILogGroup; +} + +export interface ILogGroup { + addLogStream(id: string, options?: LogStreamOptions): LogStream; +} +``` + +### Imports + +Construct classes should expose a set of static factory methods with a +“**from**” prefix that will allow users to import *unowned* constructs into +their app. + +The signature of all “from” methods should adhere to the following rules +_[awslint:from-signature]_: + +* First argument must be **scope** of type **Construct** Second argument is a +* **string**. This string will be used to determine the ID of the new +* construct. If the import method uses some value that is promised to be unique +* within the stack scope (such as ARN, export name), this value can be reused as +* the construct ID. Returns an object that implements the construct interface +* (**IFoo**). + +#### “from” Methods + +Resource constructs should export static “from” methods for importing unowned +resources given one more of it's physical attributes such as ARN, name, etc. All +constructs should have at least one fromXxx method _[awslint:from-method]_: + +```ts +static fromFooArn(scope: Construct, id: string, bucketArn: string): IFoo; +static fromFooName(scope: Construct, id: string, bucketName: string): IFoo; +``` + +> Since AWS constructs usually export all resource attributes, the logic behind + the various “from\” methods would normally need to convert one + attribute to another. For example, given a name, it would need to render the + ARN of the resource. Therefore, if **from\** methods expect to be + able to parse their input, they must verify that the input (e.g. ARN, name) + doesn't have unresolved tokens (using **Token.unresolved**). Preferably, they + can use **Stack.parseArn** to achieve this purpose. + +If a resource has an ARN attribute it should implement at least a **fromFooArn** +import method [_awslint:from-arn_]. + +To implement **fromAttribute** methods, use the abstract base class construct as +follows: + + +```ts +class Foo { + static fromArn(scope: Construct, fooArn: string): IFoo { + class _Foo extends FooBase { + public get fooArn() { return fooArn; } + public get fooName() { return this.node.stack.parseArn(fooArn).resourceName; } + } + + return new _Foo(scope, fooArn); + } +} +``` + + +#### From-attributes + +If a resource has more than a single attribute (“ARN” and “name” are usually +considered a single attribute since it's usually possible to convert one to the +other), then the resource should provide a static **fromAttributes** method to +allow users to explicitly supply values to all resource attributes when they +import an external (unowned) resource [_awslint:from-attributes_]. + +```ts +static fromFooAttributes(scope: Construct, id: string, attrs: FooAttributes): IFoo; +``` + +### Roles + +If a CloudFormation resource has a **Role** property, it normally represents the +IAM role that will be used by the resource to perform operations on behalf of +the user. + +Constructs that represent such resources should conform to the following +guidelines. + +An optional prop called **role** of type **iam.IRole**should be exposed to allow +users to "bring their own role", and use either an owned or unowned role +_[awslint:role-config-prop]_. + +```ts +interface FooProps { + /** + * The role to associate with foo. + * @default - a role will be automatically created + */ + role?: iam.IRole; +} +``` + +The construct interface should expose a **role**property, and extends +**iam.IGrantable** _[awslint:role-property]_: + +```ts +interface IFoo extends iam.IGrantable { + /** + * The role associated with foo. If foo is imported, no role will be available. + */ + readonly role?: iam.IRole; +} +``` + +This property will be `undefined` if this is an unowned construct (e.g. was not +defined within the current app). + +An **addToRolePolicy** method must be exposed on the construct interface to +allow adding statements to the role's policy _[awslint:role-add-to-policy]_: + +```ts +interface IFoo { + addToRolePolicy(statement: iam.Statement): void; +} +``` + +If the construct is unowned this method should no-op and issue a **permissions +notice** (TODO) to the user indicating that they should ensure that the role of +this resource should have the specified permission. + +TODO: add a few sentences on grantable + +Implementing **IGrantable** brings an implementation burden of **grantPrincipal: +IPrincipal**. This property must be set to the **role** if available, or to a +new **iam.ImportedResourcePrincipal** if the resource is imported and the role +is not available. + +### Resource Policies + +Resource policies are IAM policies defined on the side of the resource (as +oppose to policies attached to the IAM principal). Different resources expose +different APIs for controlling their resource policy. For example, ECR +repositories have a **RepositoryPolicyText** prop, SQS queues offer a +**QueuePolicy** resource, etc. + +Constructs that represents resources with a resource policy should encapsulate +the details of how resource policies are created behind a uniform API as +described in this section. + +When a construct represents an AWS resource that supports a resource policy, it +should expose an optional prop that will allow initializing resource with a +specified policy _[awslint:resource-policy-prop]_: + +```ts +resourcePolicy?: iam.PolicyStatement[] +``` + +Furthermore, the construct *interface* should include a method that allows users +to add statements to the resource policy +_[awslint:resource-policy-add-to-policy]_: + +```ts +interface IFoo extends iam.IResourceWithPolicy { + addToResourcePolicy(statement: iam.PolicyStatement): void; +} +``` + +For some resources, such as ECR repositories, it is impossible (in +CloudFormation) to add a resource policy if the resource is unowned (the policy +is coupled with the resource creation). In such cases, the implementation of +`addToResourcePolicy` should add a **permission** **notice** to the construct +(using `node.addInfo`) indicating to the user that they must ensure that the +resource policy of that specified resource should include the specified +statement. + +### VPC + +Compute resources such as AWS Lambda functions, Amazon ECS clusters, AWS +CodeBuild projects normally allow users to specify the VPC configuration in +which they will be placed. The underlying CFN resources would normally have a +property or a set of properties that accept the set of subnets in which to place +the resources. + +In most cases, the preferred default behavior is to place the resource in all +private subnets available within the VPC. + +Props of such constructs should include the following properties +_[awslint:vpc-props]_: + +```ts +/** + * The VPC in which to run your CodeBuild project. + */ +vpc: ec2.IVpc; // usually this is required + +/** + * Which VPC subnets to use for your CodeBuild project. + * + * @default - uses all private subnets in your VPC + */ +vpcSubnetSelection?: ec2.SubnetSelection; +``` + +### Grants + +Grants are one of the most powerful concept in the AWS Construct Library. They +offer a higher level, intent-based, API for managing IAM permissions for AWS +resources. + +**Despite the fact that they may be mutating**, grants should be exposed on the + construct interface, and not on the concrete class + _[awslint:grants-on-interface]_. See discussion above about mutability for + reasoning. + +Grants are represented as a set of methods with the “**grant**” prefix. + +All constructs that represent AWS resources must have at least one grant method +called “**grant**” which can be used to grant a grantee (such as an IAM +principal) permission to perform a set of actions on the resource with the +following signature. This method is defined as an abstract method on the +**Resource** base class (and the **IResource** interface) +_[awslint:grants-grant-method]_: + +```ts +grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant; +``` + +The **iam.Grant** class has a rich API for implementing grants which implements +the desired behavior. + +Furthermore, resources should also include a set of grant methods for common use +cases. For example, **dynamodb.Table.grantPutItem**, +**s3.Bucket.grantReadWrite**, etc. In such cases, the signature of the grant +method should adhere to the following rules _[awslint:grant-signature]_: + +1. Name should have a “grant” prefix 2. Returns an **iam.Grant** object 3. First +argument must be **grantee: iam.IGrantable** + +```ts +grantXxx(grantee: iam.IGrantable): iam.Grant; +``` + +It makes sense for some AWS resources to also expose grant methods on all +resources in the account. To support such use cases, expose a set of static +grant methods on the construct class. For example, +**dynamodb.Table.grantAllListStreams**. The signature of static grants should be +similar _[awslint:grants-static-all]_. + +```ts +export class Table { + public static grantAll(grantee: iam.IGrantable, ...actions: string[]): iam.Grant; + public static grantAllListStreams(grantee: iam.IGrantable): iam.Grant; +} +``` + +### Metrics + +Almost all AWS resources emit CloudWatch metrics, which can be used with alarms +and dashboards. + +AWS construct interfaces should include a set of “metric” methods which +represent the CloudWatch metrics emitted from this resource +_[awslint:metrics-on-interface]_. + +At a minimum (and enforced by IResource), all resources should have a single +method called **metric**, which returns a **cloudwatch.Metric** object +associated with this instance (usually this method will simply set the right +metrics namespace and dimensions [_awslint:metrics-generic-method_]: + +```ts +metric(metricName: string, options?: cloudwatch.MetricOptions): cloudwatch.Metric; +``` + +> **Exclusion**: If a resource does not emit CloudWatch metrics, this rule may + be excluded + +Additional metric methods should be exposed with the official metric name as a +suffix and adhere to the following rules _[awslint:metrics-method-signature]:_ + +* Name should be “metricXxx” where “Xxx” is the official metric name Accepts a +* single “options” argument of type **MetricOptions** Returns a **Metric** +* object. + +```ts +interface IFunction { + metricDuration(options?: cloudwatch.MetricOptions): cloudwatch.Metric; + metricInvocations(options?: cloudwatch.MetricOptions): cloudwatch.Metric; + metricThrottles(options?: cloudwatch.MetricOptions): cloudwatch.Metric; +} +``` + +It is sometimes desirable to use a metric that applies to all resources of a +certain type within the account. To facilitate this, resources should expose a +static method called **metricAll** _[awslint:metrics-static-all]_. Additional +**metricAll** static methods can also be exposed +_[awslint:metrics-all-methods]_. + + +```ts +class Function extends Resource implements IFunction { + public static metricAll(metricName: string, options?: cloudwatch.MetricOptions): cloudwatch.Metric; + public static metricAllErrors(props?: cloudwatch.MetricOptions): cloudwatch.Metric; +} +``` + + +### Events + +Many AWS resource emit events to the CloudWatch event bus. Such resources should +have a set of “onXxx” methods available on their construct interface +_[awslint:events-in-interface]_. + +All “on” methods should have the following signature +[_awslint:events-method-signature_]: + +```ts +onXxx(id: string, target: events.IEventRuleTarget, options?: XxxOptions): cloudwatch.EventRule; +``` + +When a resource emits CloudWatch events, it should at least have a single +generic **onEvent** method to allow users to specify the event name +[_awslint:events-generic_]: + +```ts +onEvent(event: string, id: string, target: events.IEventRuleTarget): cloudwatch.EventRule +``` + +### Connections + +AWS resources that use EC2 security groups to manage network security should +implement the **connections API** interface by having the construct interface +extend **ec2.IConnectable** _[awslint:connectable-interface]_. + +### Integrations + +Many AWS services offer “integrations” to other services. For example, AWS +CodePipeline has actions that can trigger AWS Lambda functions, ECS tasks, +CodeBuild projects and more. AWS Lambda can be triggered by a variety of event +sources, AWS CloudWatch event rules can trigger many types of targets, SNS can +publish to SQS and Lambda, etc, etc. + +> See [aws-cdk#1743](https://github.com/awslabs/aws-cdk/issues/1743) for a + discussion on the various design options. + +AWS integrations normally have a single **central** service and a set of +**consumed** services. For example, AWS CodePipeline is the central service and +consumes multiple services that can be used as pipeline actions. AWS Lambda is +the central service and can be triggered by multiple event sources. + +Integrations are an abstract concept, not necessarily a specific mechanism. For +example, each AWS Lambda event source is implemented in a different way (SNS, +Bucket notifications, CloudWatch events, etc), but conceptually, *some*users +like to think about AWS Lambda as the “center”. It is also completely legitimate +to have multiple ways to connect two services on AWS. To trigger an AWS Lambda +function from an SNS topic, you could either use the integration or the SNS APIs +directly. + +Integrations should be modeled using an **interface** (i.e. **IEventSource**) +exported in the API of the central module (e.g. “aws-lambda”) and implemented by +classes in the integrations module (“aws-lambda-event-sources”) +[_awslint:integrations-interface_]. + +```ts +// aws-lambda +interface IEventSource { + bind(fn: IFunction): void; +} +``` + +A method “addXxx” should be defined on the construct interface and adhere to the +following rules _[awslint:integrations-add-method]:_ + +* Should accept any object that implements the integrations interface Should not +* return anything (void) Implementation should call “bind” on the integration +* object + +```ts +interface IFunction extends IResource { + public addEventSource(eventSource: IEventSource) { + eventSource.bind(this); + } +} +``` + +An optional array prop should allow declaratively applying integrations (sugar +to calling “addXxx”): + +```ts +interface FunctionProps { + eventSources?: IEventSource[]; +} +``` + +Lastly, to ease discoverability and maintain a sane dependency graphs, all +integrations for a certain service should be mastered in a single secondary +module named aws-*xxx*-*yyy* (where *xxx* is the service name and *yyy* is the +integration name). For example, **aws-s3-notifications**, +**aws-lambda-event-sources**, **aws-codepipeline-actions**. All implementations +of the integration interface should reside in a single module +_[awslint:integrations-in-single-module]_. + +```ts +// aws-lambda-event-sources +class DynamoEventSource implements IEventSource { + constructor(table: dynamodb.ITable, options?: DynamoEventSourceOptions) { ... } + + public bind(fn: IFunction) { + // ...do your magic + } +} +``` + +When integration classes define new constructs in **bind**, they should be aware +that they are adding into a scope they don't fully control. This means they +should find a way to ensure that construct IDs do not conflict. This is a +domain-specific problem. + +### State + +Persistent resources are AWS resource which hold persistent state, such as +databases, tables, buckets, etc. + +To make sure stateful resources can be easily identified, all resource +constructs must include the **@stateful** or **@stateless** JSDoc annotations at +the class level _[awslint:state-annotation]_. + +This annotation enables the following linting rules. + +```ts +/** + * @stateful + */ +export class Table { } +``` + +Persistent resources must have a **removalPolicy** prop, defaults to +**Orphan**_[awslint:state-removal-policy-prop]_: + +```ts +import { RemovalPolicy } from '@aws-cdk/cdk'; + +export interface TableProps { + /** + * @default ORPHAN + */ + readonly removalPolicy?: RemovalPolicy; +} +``` + +Removal policy is applied at the CFN resource level using the +**RemovalPolicy.apply(resource)**: + +```ts +RemovalPolicy.apply(cfnTable, props.removalPolicy); +``` + +The **IResource** interface requires that all resource constructs implement a +property **stateful** which returns **true** or **false** to allow runtime +checks query whether a resource is persistent +_[awslint:state-stateful-property]_. + +### Physical Names (NEW) - TODO + +See + +### Tags (NEW) + +The AWS platform has a powerful tagging system that can be used to tag resources +with key/values. The AWS CDK exposes this capability through the **Tag** +“aspect”, which can seamlessly tag all resources within a subtree: + +```ts +// add a tag to all taggable resource under "myConstruct" +myConstruct.node.apply(new cdk.Tag("myKey", "myValue")); +``` + +Constructs for AWS resources that can be tagged must have an optional **tags** +hash in their props [_awslint:tags-prop_]. + +### Secrets (NEW) + +If you expect a secret in your API (such as passwords, tokens), use the +**cdk.SecretValue** class to signal to users that they should not include +secrets in their CDK code or templates. + +If a property is named “password” it must use the **SecretValue** type +[_awslint:secret-password_]. If a property has the word “token” in it, it must +use the SecretValue type [_awslint:secret-token_]. + +## Project Structure + +### Code Organization + +* Code should be under `lib/` Entry point should be `lib/index.ts` and should +* only contain “imports” for other files. No need to put every class in a +* separate file. Try to think of a reader-friendly organization of your source +* files. + +## Implementation + +The following guidelines and recommendations apply are related to the +implementation of AWS constructs. + +### General Principles + +* Do not future proof No fluent APIs Good APIs “speak” in the language of the +* user. The terminology your API uses should be intuitive and represent the +* mental model your user brings over, not one that you made up and you force +* them to learn. Multiple ways of achieving the same thing is legitimate +* Constantly maintain the invariants Fewer “if statements” the better + +### Construct IDs + +Construct IDs (the second argument passed to all constructs when they are +defined) are used to formulate resource logical IDs which must be **stable** +across updates. The logical ID of a resource is calculated based on the **full +path** of it's construct in the construct scope hierarchy. This means that any +change to a logical ID in this path will invalidate all the logical IDs within +this scope. This will result in **replacements of all underlying resources** +within the next update, which is extremely undesirable. + +As described above, use the ID “**Resource**” for the primary resource of an AWS +construct. + +Therefore, when implementing constructs, you should treat the construct +hierarchy and all construct IDs as part of the **external contract** of the +construct. Any chance to either should be considered and called out as a +breaking change. + +There is no need to concatenate logical IDs. If you find yourself needing to +that to ensure uniqueness, it's an indication that you may be able to create +another abstraction, or even just a **Construct** instance to serve as a +namespace: + +```ts +const privateSubnets = new Construct(this, 'PrivateSubnets'); +const publicSubnets = new Construct(this, 'PublishSubnets'); + +for (const az of availabilityZones) { + new Subnet(privateSubnets, az); + new Subnet(publicSubnets, az, { public: true }); +} +``` + +### Errors + +#### input validation + +Prefer to validate input as early as it is passed into your code (ctor, methods, +etc) and bail out by throwing an **Error** (no need to create subclasses of +Error since all errors in the CDK are unrecoverable): + +* All lowercase sentences (usually they are printed after “Error: \”) +* Include a descriptive message Include the value provided Include the +* expected/allowed values No need to include information that can be obtained +* from the stack trace. No need to add a period at the end of error messages. + +#### avoid errors if possible + +Always prefer to do the right thing for the user instead of raising an +error. Only fail if the user has explicitly specified bad configuration. For +example, VPC has **enableDnsHostnames** and **enableDnsSupport**. DNS hostnames +*require* DNS support, so only fail if the user enabled DNS hostnames but +explicitly disabled DNS support. Otherwise, auto-enable DNS support for them. + +#### Never catch exceptions + +All CDK errors are unrecoverable. If a method wishes to signal a recoverable +error, this should be modeled in a return value and not through exceptions. + +#### Post Validation + +In the rare case where the integrity of your construct can only be checked right +before synthesis, override the **Construct.validate()** method and return +meaningful errors. Always prefer early input validation over post-validation. + +#### attached Errors/warnings + +You can also “attach” an error or a warning to a construct via +**node.addWarning(s)** or **node.addError(s)**. These methods will attach CDK +metadata to your construct, which will be displayed to the user by the toolchain +when the stack is deployed. + +Errors will not allow deployment and warnings will only be displayed in +highlight (unless **--strict** mode is used). + +### Tokens + +* Do not use FnSub + +## Documentation + +Documentation style (copy from official AWS docs) No need to Capitalize Resource +Names Like Buckets after they've been defined Stability (@stable, @experimental) +Use the word “define” to describe resources/constructs in your stack (instead of +“~~create~~”, “configure”, “provision”). Use a summary line and separate the +doc body from the summary line using an empty line. + +### Inline Documentation + +All public APIs must be documented when first introduced +[_awslint:docs-public-apis_]. + +Do not add documentation on overrides/implementations. The public reference +documentation will automatically copy the base documentation to the derived +APIs, so it's better to avoid the confusion [_awslint:docs-no-duplicates_]. + +Use the following JSDoc tags: **@param**, **@returns**, **@default**, **@see**, +**@example.** + +### Readme + +* Header should include maturity level Example for the simple use case should be +* almost the first thing If there are multiple common use cases, provide an +* example for each one and describe what happens under the hood at a high level +* (e.g. which resources are created). Reference docs are not needed Use +* literate (`.lit.ts`) integration tests into README file (see example in + +## Testing + +### Unit tests + +* Unit test utility functions and object models separately from constructs. If +* you want them to be “package-private”, just put them in a separate file and +* import `../lib/my-util` from your unit test code. Failing tests should be +* prefixed with “fails” + +### Integration tests + +* Integration tests should be under `test/integ.xxx.ts` and should basically + just be CDK apps that can be deployed using “cdk deploy” (in the meantime). + +### Versioning + +* Semantic versioning Construct ID changes or scope hierarchy + +## Naming & Style + +### Naming Conventions + +* **Class names**: PascalCase Properties**: camelCase Methods (static and +* **non-static)**: camelCase Interfaces** (“behavioral interface”) : +* **IMyInterface Structs** (“data interfaces”): MyDataStruct Enums**: +* **PascalCase,**Members**: SNAKE_UPPER + +### Coding Style + +* **Indentation**: 2 spaces Line length**: 150 String literals**: use +* **single-quotes (`'`) or backticks (```) Semicolons**: at the end of each code +* **statement and declaration (incl. properties and imports). Comments**: start +* **with lower-case, end with a period. diff --git a/build.sh b/build.sh index 6a90f93fbbcbe..754f7ce10111e 100755 --- a/build.sh +++ b/build.sh @@ -3,6 +3,7 @@ set -euo pipefail bail="--bail" runtarget="build+test" +check_prereqs="true" while [[ "${1:-}" != "" ]]; do case $1 in -h|--help) @@ -18,6 +19,9 @@ while [[ "${1:-}" != "" ]]; do --skip-test|--skip-tests) runtarget="build" ;; + --skip-prereqs) + check_prereqs="false" + ;; *) echo "Unrecognized parameter: $1" exit 1 @@ -38,8 +42,14 @@ fail() { exit 1 } +# Check for secrets that should not be committed /bin/bash ./git-secrets-scan.sh +# Verify all required tools are present before starting the build +if [ "$check_prereqs" == "true" ]; then + /bin/bash ./scripts/check-prerequisites.sh +fi + # Prepare for build with references /bin/bash scripts/generate-aggregate-tsconfig.sh > tsconfig.json diff --git a/buildspec.yaml b/buildspec.yaml index 59b95d64d8b13..95fd2f0194b85 100644 --- a/buildspec.yaml +++ b/buildspec.yaml @@ -25,3 +25,8 @@ artifacts: files: - "**/*" base-directory: dist +reports: + jest-tests: + files: + - "**/junit.xml" + file-format: JunitXml \ No newline at end of file diff --git a/design/aws-guidelines.md b/design/aws-guidelines.md deleted file mode 100644 index 85082cae278a3..0000000000000 --- a/design/aws-guidelines.md +++ /dev/null @@ -1,443 +0,0 @@ -# AWS Construct Library Design Guidelines - -The AWS Construct Library is a rich class library of CDK constructs which -represent all resources offered by the AWS Cloud. - -The purpose of this document is to describe common guidelines for designing the -APIs in the AWS Construct Library in order to ensure a consistent and integrated -experience across the entire AWS surface area. - -As much as possible, the guidelines in this document are enforced using the -__awslint__ tool which reflects on the APIs and verifies that the APIs adhere to -the guidelines. - -When a guideline is backed by a linter rule, the rule name will be referenced -like this: `awslint|resource-class-is-construct` -and anchored with the rule name. - -For the purpose of this document we will use "**Foo**" to denote the official -name of the resource as defined in the AWS CloudFormation resource specification -(i.e. "Bucket", "Queue", "Topic", etc). This notation allows deriving names from -the official name. For example, `FooProps` would be `BucketProps`, `TopicProps`, -etc, `IFoo` would be `IBucket`, `ITopic` and so forth. - -The guidelines in this document use TypeScript (and npm package names) since -this is the source programming language used to author the library, which is -later packaged and published to all programming languages through -[jsii](https://github.com/aws/jsii). - -## Modules - -> awslint: module-name - -AWS resources are organized into modules based on their AWS service. For -example, the "Bucket" resource, which is offered by the Amazon S3 service will -be available under the **@aws-cdk/aws-s3** module. - -## Constructs - -Constructs are the basic building block of CDK applications. They represent -abstract cloud resources of any complexity. - -> awslint: construct-ctor - -Construct initializer (constructor) signature should always be: - -```ts -constructor(scope: cdk.Construct, id: string, props: FooProps) -``` - -> __TODO__: awslint: construct-ctor-optional-props - -If all initialization properties are optional, the `props` argument must also be -optional. - -> NOTE: This rule breaks down for the case where there are two (or more) -> potential arguments, and exactly one (or at least one) of them is required. -> Then the props will be marked as optional, but the whole object is not -> optional since there is no valid use of not specifying anything. -> -> Ideally we rather not design APIs such as this, but there might be cases where -> this is the best approach. In those cases, it is okay to "exclude" this role:scope -> in `package.json`. - -```ts -constructor(scope: cdk.Construct, id: string, props: FooProps = { }) -``` - -Each module in the AWS Construct Library includes generated constructs which -represent the "raw" CloudFormation resources. - -1. These classes are named `CfnFoo` -2. They extend `cdk.Resource` (which extends `cdk.Construct`) -3. Their constructor accepts a `props` parameter of type `CfnFooProps` -4. `CfnFooProps` represents the exact set of properties as defined in the - AWS CloudFormation resource specification. -5. They have readonly properties which represent all the runtime attributes of - the resource. - -> NOTE: there are no linting rules against this section because this layer -> is entirely generated by the __cfn2ts__ tool according to these guidelines -> so there is no need to lint against it. - -## Resource Interface - -> awslint: resource-interface - -Every AWS resource should have a resource interface `IFoo`. - -This interface represents both resources defined within the same stack (aka -"internal" or "owned") or resources that are defined in different stack/app (aka -"imported", "existing", "external" or "unowned"). Throughout this document we -shall refer to these two types of resources as **"internal"** and -**"external"**. - -> awslint: resource-interface-extends-construct - -Resource interfaces should extend `cdk.IConstruct` in order to allow consumers -to take advantage of construct capabilities such as unique IDs, paths, scopes, etc. - -> TODO: awslint: resource-ref-interface - -When resources are referenced anywhere in the API (e.g. in properties or methods -of other resources or higher level constructs), the resource interface (`IFoo`) -should be preferred over the concrete resource class (`Foo`). This will allow -users to supply either internal or external resources. - -## Resource Attributes - -Every AWS resource has a set of "physical" runtime attributes such as ARN, -physical names, URLs, etc. These attributes are commonly late-bound, which means -they can only be resolved when AWS CloudFormation actually provisions the -resource. - -Resource attributes almost always represent **string** values (URL, ARN, name). -Sometimes they might also represent a **list of strings**. - -> awslint: resource-attribute
-> awslint: resource-attribute-immutable - -All resource attributes must be represented as readonly properties of the -resource interface. The names of the attributes must correspond to the -CloudFormation resource attribute name. - -> TODO: awslint: resource-attribute-type - -Since attribute values can either be late-bound ("a promise to a string") or -concrete ("a string"), the AWS CDK has a mechanism called "tokens" which allows -codifying late-bound values into strings or string arrays. This approach was -chosen in order to dramatically simplify the type-system and ergonomics of CDK -code. As long as users treat these attributes as **opaque values** (e.g. not try -to parse them or manipulate them), they can be used interchangeably. - -As long as attribute values are not manipulated, they can still be concatenated -idiomatically. For example: - -```ts -`This is my bucket name: ${bucket.bucketName} and bucket ARN: ${bucket.bucketArn}` -``` - -Even though `bucketName` and `bucketArn` will only be resolved during -deployment, the CDK will identify those as tokens and will convert this string -into an `{ "Fn::Join" }` expression which includes the relevant intrinsic -functions. - -If needed, you can query whether an object includes unresolved tokens by using -the `cdk.isToken(x)` function. - -Resource attributes should use a type that corresponds to the __resolved__ AWS -CloudFormation type (e.g. `string`, `string[]`). - -At the moment, attributes that represent strings, are represented as `string` in -the `CfnFoo` resource. However, other types of tokens (string arrays, numbers) -are still represented as `Token`. You can use `token.toList()` to represent a token as a -string array, and soon we will also have `toNumber()`. - -## Resource Class - -> awslint: resource-class - -Each `CfnFoo` resource must have a corresponding `Foo` high-level (L2) -class. - -> awslint: resource-class-is-construct - -Classes which represent AWS resources are constructs (they must extend the `cdk.Construct` class -directly or indirectly). - -## Resource Props - -> awslint: resource-props - -Resource constructs are initialized with a set of properties defined in an interface `FooProps`. - -Initialization properties should enable developers to define the resource in -their application. Generally, they should expose most of the surface -area of the resource. - -Initialization properties should be _required_ only if there is no sane default -that can be provided or calculated. By providing sensible and safe defaults (or -"smart defaults"), developers can get started quickly. - -## Imports - -In order to allow users to work with resources that are either internal or -external to their stack, AWS resources should provide an "import/export" -mechanism as described in this section. - -> awslint: import - -Every AWS resource class must include a static method called `import` with the -following signature: - -```ts -static import(scope: cdk.Construct, id: string, props: XxxImportProps): IXxx -``` - -This method returns an object that implements the resource interface (`IXxx`) -and represents an "imported resource". - -> awslint: import-props-interface - -The "props" argument is `XxxImportProps`, which is an interface that declares -properties that allow the user to specify an external resource identity, usually -by providing one or more resource attributes such as ARN, physical name, etc. - -The import interface should have the minimum required properties, that is: if it -is possible to parse the resource name from the ARN (using `cdk.Stack.parseArn`), -then only the ARN should be required. In cases where it -is not possible to parse the ARN (e.g. if it is a token and the resource name -might have use "/" characters), both the ARN and the name should be optional and -runtime-checks should be performed to require that at least one will be defined. -See `ecr.RepositoryAttributes` for an example. - -The recommended way to implement the `import` method is as follows: - -1. A public abstract base class called `XxxBase` which implements `IXxx` and - extends `cdk.Construct`. -2. The base class should provide as much of the implementation of `IXxx` as possible given the - context it has. In most cases, `grant` methods, `metric` methods, etc. can be implemented at - at that level. -5. A private class called `ImportedXxx` which extends `XxxBase` and implements - any remaining abstract members. -4. The `import` static method should be have the following implementation: - -```ts -public static import(scope: cdk.Construct, id: string, props: XxxImportProps): IXxx { - return new ImportedXxx(scope, id, props); -} -``` - -## Exports - -> awslint: export - -All resource interfaces (`IXxx`) must declare a method called `export` with the -following signature: - -```ts -export(): XxxImportProps -``` - -This method can be used to export this resource from the current stack and use -it in another stack through an `Xxx.import` call. - -The implementation of `export` is different between internal resources (`Xxx`) and -external imported resource (`ImportedXxx` as recommended above): - -For internal resources, the `export` method should produce a CloudFormation Output -for each resource attribute, and return a set of `{ "Fn::ImportValue" }` tokens -so they can be imported to another stack. - -```ts -class Xxx extends XxxBase { - public export(): XxxImportProps { - return { - attr1: new cdk.CfnOutput(this, 'Attr1', { value: this.attr1 }).makeImportValue().toString(), - attr2: new cdk.CfnOutput(this, 'Attr2', { value: this.attr2 }).makeImportValue().toString(), - } - } -} -``` - -For external resources, we know the actual values, so basically you would want to reflect -your `props` as is: - -```ts -class ImportedXxx extends XxxBase { - constructor(scope: cdk.Construct, id: string, private readonly props: XxxImportProps) { - // ... - } - - public export() { - return this.props; - } -} -``` - -The reason we are defining `export` on the resource interface and not on the resource -class is in order to allow "composite export" scenarios, where a higher-level construct -wants to implement `export` by composing the exports of multiple resources: - -```ts -interface MyCompositeProps { - bucket: s3.IBucket; - topic: sns.ITopic; -} - -class MyComposite extends cdk.Construct { - public export(): MyCompositeImportProps { - return { - bucket: this.bucket.export(), - topic: this.topic.export() - } - } -} -``` - -In this scenario, you don't know if `bucket` or `topic` are internal or external resources, -but you still want export to work. - -## General Guidelines - -### Defaults must be documented on optional interface properties - -> TODO: awslint: interface-defaults-docs - -The `@default` documentation tag must be included on all optional properties of -interfaces. - -## Complete Example - -Here's a complete "template" for the types required when defining a resource in the -AWS construct library: - -```ts -// must extend cdk.IConstruct -// extends all interfaces that are applicable for both internal -// and external resources of this type -export interface IFoo extends cdk.IConstruct, ISomething { - - // attributes - readonly fooArn: string; - readonly fooBoo: string[]; - - // security group connections (if applicable) - readonly connections: ec2.Connections; - - // permission grants (adds statements to the principal's policy) - grant(grantee?: iam.IGrantable, ...actions: string[]): void; - grantFoo(grantee?: iam.IGrantable): void; - grantBar(grantee?: iam.IGrantable): void; - - // resource policy (if applicable) - addToResourcePolicy(statement: iam.PolicyStatement): void; - - // role (if applicable) - addToRolePolicy(statement: iam.PolicyStatement): void; - - // pipeline (if applicable) - addToPipeline(stage: pipelineapi.IStage, name: string, props?: FooActionProps): FooAction; - - // metrics - metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric; - metricFoo(props?: cloudwatch.MetricOptions): cloudwatch.Metric; - metricBar(props?: cloudwatch.MetricOptions): cloudwatch.Metric; - - // export - export(): FooImportProps; - - // any other methods/properties that are applicable for both internal - // and external resources of this type. - // ... -} - -// base class to share implementation between internal/external resources -// it has to be public sadly. -export abstract class FooBase extends cdk.Construct implements IFoo { - - // attributes are usually still abstract at this level - public abstract readonly fooArn: string; - public abstract readonly fooBoo: string[]; - - // the "export" method is also still abstract - public abstract export(): FooAttributes; - - // grants can usually be shared - public grantYyy(grantee?: iam.IGrantable) { - // ... - } - - // metrics can usually be shared - public metricFoo(...) { ... } -} - -// extends the abstract base class and implement any interfaces that are not applicable -// for imported resources. This is quite rare usually, but can happen. -export class Foo extends FooBase implements IAnotherInterface { - - // the import method is always going to look like this. - public static import(scope: cdk.Construct, id: string, props: FooImportProps): IFoo { - return new ImportedFoo(scope, id, props); - } - - // implement resource attributes as readonly properties - public readonly fooArn: string; - public readonly fooBoo: string[]; - - // ctor's 3rd argument is always FooProps. It should be optional (`= { }`) in case - // there are no required properties. - constructor(scope: cdk.Construct, id: string, props: FooProps) { - super(scope, id); - - // you would usually add a `CfnFoo` resource at this point. - const resource = new CfnFoo(this, 'Resource', { - // ... - }); - - // proxy resource properties - this.fooArn = resource.fooArn; - this.fooBoo = resource.fooBoo; - } - - // this is how export() should be implemented on internal resources - // they would produce a stack export and return the "Fn::ImportValue" token - // for them so they can be imported to another stack. - public export(): FooAttributes { - return { - fooArn: new cdk.CfnOutput(this, 'Arn', { value: this.fooArn }).makeImportValue().toString(), // represent Fn::ImportValue as a string - fooBoo: new cdk.CfnOutput(this, 'Boo', { value: this.fooBoo }).makeImportValue().toList() // represent as string[] - // ... - } - } -} - -// an internal class (don't export it) representing the external (imported) resource -class ImportedFoo extends FooBase { - public readonly string fooArn; - public readonly string[] fooBoo; - - constructor(scope: cdk.Construct, id: string, private readonly props: FooImportProps) { - super(scope, id); - - this.fooArn = props.fooArn; - this.fooBoo = props.fooBoo; - } - - public export() { - return this.props; // just reflect props back - } -} -``` - -## Roadmap - -- [ ] IAM (`role`, `addToRolePolicy`, `addToResourcePolicy`) -- [ ] Grants (`grantXxx`) -- [ ] Metrics (`metricXxx`) -- [ ] Events (`onXxx`) -- [ ] Security Groups (`connections`) -- [ ] Pipeline Actions (`addToPipeline`) -- [ ] SNS Targets -- [ ] `_asFooTarget` -- [ ] TODO: other cross AWS patterns diff --git a/lerna.json b/lerna.json index 57f402c732b90..0a02acca2ae0c 100644 --- a/lerna.json +++ b/lerna.json @@ -10,5 +10,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.50.0" + "version": "1.51.0" } diff --git a/package.json b/package.json index 6442b77109e93..9b0a2f0772de1 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,13 @@ "conventional-changelog-cli": "^2.0.34", "fs-extra": "^9.0.1", "graceful-fs": "^4.2.4", - "jsii-diff": "^1.7.0", - "jsii-pacmak": "^1.7.0", - "jsii-rosetta": "^1.7.0", + "jsii-diff": "^1.9.0", + "jsii-pacmak": "^1.9.0", + "jsii-rosetta": "^1.9.0", + "jest-junit": "^11.0.1", "lerna": "^3.22.1", - "standard-version": "^8.0.0", - "typescript": "~3.9.5" + "standard-version": "^8.0.2", + "typescript": "~3.9.6" }, "resolutions-comment": "should be removed or reviewed when nodeunit dependency is dropped or adjusted", "resolutions": { diff --git a/packages/@aws-cdk/alexa-ask/.gitignore b/packages/@aws-cdk/alexa-ask/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/alexa-ask/.gitignore +++ b/packages/@aws-cdk/alexa-ask/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/alexa-ask/.npmignore b/packages/@aws-cdk/alexa-ask/.npmignore index b0edf81a01371..ab90672b1d91e 100644 --- a/packages/@aws-cdk/alexa-ask/.npmignore +++ b/packages/@aws-cdk/alexa-ask/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/alexa-ask/jest.config.js b/packages/@aws-cdk/alexa-ask/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/alexa-ask/jest.config.js +++ b/packages/@aws-cdk/alexa-ask/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/app-delivery/.gitignore b/packages/@aws-cdk/app-delivery/.gitignore index 478a3018be0d6..1ed331de29bb6 100644 --- a/packages/@aws-cdk/app-delivery/.gitignore +++ b/packages/@aws-cdk/app-delivery/.gitignore @@ -10,3 +10,5 @@ tsconfig.json *.snk coverage !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/app-delivery/.npmignore b/packages/@aws-cdk/app-delivery/.npmignore index 001499d011563..82e007c2c1c49 100644 --- a/packages/@aws-cdk/app-delivery/.npmignore +++ b/packages/@aws-cdk/app-delivery/.npmignore @@ -19,4 +19,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/app-delivery/README.md b/packages/@aws-cdk/app-delivery/README.md index fee307ffeb6fb..85e378002e5d1 100644 --- a/packages/@aws-cdk/app-delivery/README.md +++ b/packages/@aws-cdk/app-delivery/README.md @@ -2,9 +2,9 @@ --- -![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) +![Deprecated](https://img.shields.io/badge/deprecated-critical.svg?style=for-the-badge) -> The APIs of higher level constructs in this module are experimental and under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. +> This API may emit warnings. Backward compatibility is not guaranteed. --- @@ -13,6 +13,13 @@ This library includes a *CodePipeline* composite Action for deploying AWS CDK Ap This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. + +# Replacement recommended + +This library has been deprecated. We recommend you use the +[@aws-cdk/pipelines](https://docs.aws.amazon.com/cdk/api/latest/docs/pipelines.html) module instead. + + ### Limitations The construct library in it's current form has the following limitations: 1. It can only deploy stacks that are hosted in the same AWS account and region as the *CodePipeline* is. diff --git a/packages/@aws-cdk/app-delivery/package.json b/packages/@aws-cdk/app-delivery/package.json index d1f6bedf06839..016b2a151ff1e 100644 --- a/packages/@aws-cdk/app-delivery/package.json +++ b/packages/@aws-cdk/app-delivery/package.json @@ -57,7 +57,7 @@ "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "fast-check": "^1.25.1", + "fast-check": "^1.26.0", "nodeunit": "^0.11.3", "pkglint": "0.0.0" }, @@ -92,8 +92,8 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, - "stability": "experimental", - "maturity": "experimental", + "stability": "deprecated", + "maturity": "deprecated", "nyc": { "statements": 75 }, diff --git a/packages/@aws-cdk/assert/.gitignore b/packages/@aws-cdk/assert/.gitignore index 9d5b9f1ce1539..c9b9bcc8658a1 100644 --- a/packages/@aws-cdk/assert/.gitignore +++ b/packages/@aws-cdk/assert/.gitignore @@ -12,3 +12,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/assert/.npmignore b/packages/@aws-cdk/assert/.npmignore index f5e2864c265a7..582a6ea324723 100644 --- a/packages/@aws-cdk/assert/.npmignore +++ b/packages/@aws-cdk/assert/.npmignore @@ -17,4 +17,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/assert/README.md b/packages/@aws-cdk/assert/README.md index c81fac74562e9..cb953b02a7710 100644 --- a/packages/@aws-cdk/assert/README.md +++ b/packages/@aws-cdk/assert/README.md @@ -82,17 +82,24 @@ following values: - **Literal values**: the given property in the resource must match the given value *exactly*. - `ABSENT`: a magic value to assert that a particular key in an object is *not* set (or set to `undefined`). -- `arrayWith(...)`/`objectLike(...)`/`deepObjectLike(...)`/`exactValue()`: special matchers - for inexact matching. You can use these to match arrays where not all elements have to match, - just a single one, or objects where not all keys have to match. - -The difference between `haveResource` and `haveResourceLike` is the same as -between `objectLike` and `deepObjectLike`: the first allows -additional (unspecified) object keys only at the *first* level, while the -second one allows them in nested objects as well. - -If you want to escape from the "deep lenient matching" behavior, you can use -`exactValue()`. +- special matchers for inexact matching. You can use these to match values based on more lenient conditions + than the default (such as an array containing at least one element, ignoring the rest, or an inexact string + match). + +The following matchers exist: + +- `objectLike(O)` - the value has to be an object matching at least the keys in `O` (but may contain + more). The nested values must match exactly. +- `deepObjectLike(O)` - as `objectLike`, but nested objects are also treated as partial specifications. +- `exactValue(X)` - must match exactly the given value. Use this to escape from `deepObjectLike`'s leniency + back to exact value matching. +- `arrayWith(E, [F, ...])` - value must be an array containing the given elements (or matchers) in any order. +- `stringLike(S)` - value must be a string matching `S`. `S` may contain `*` as wildcard to match any number + of characters. +- `anything()` - matches any value. +- `notMatching(M)` - any value that does NOT match the given matcher (or exact value) given. +- `encodedJson(M)` - value must be a string which, when decoded as JSON, matches the given matcher or + exact value. Slightly more complex example with array matchers: diff --git a/packages/@aws-cdk/assert/jest.config.js b/packages/@aws-cdk/assert/jest.config.js index f81b80f39a2aa..ac8c47076506a 100644 --- a/packages/@aws-cdk/assert/jest.config.js +++ b/packages/@aws-cdk/assert/jest.config.js @@ -1,4 +1,4 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = { ...baseConfig, coverageThreshold: { diff --git a/packages/@aws-cdk/assert/lib/assertions/have-resource-matchers.ts b/packages/@aws-cdk/assert/lib/assertions/have-resource-matchers.ts new file mode 100644 index 0000000000000..13157b595751f --- /dev/null +++ b/packages/@aws-cdk/assert/lib/assertions/have-resource-matchers.ts @@ -0,0 +1,331 @@ +import { ABSENT, InspectionFailure, PropertyMatcher } from './have-resource'; + +/** + * A matcher for an object that contains at least the given fields with the given matchers (or literals) + * + * Only does lenient matching one level deep, at the next level all objects must declare the + * exact expected keys again. + */ +export function objectLike(pattern: A): PropertyMatcher { + return _objectContaining(pattern, false); +} + +/** + * A matcher for an object that contains at least the given fields with the given matchers (or literals) + * + * Switches to "deep" lenient matching. Nested objects also only need to contain declared keys. + */ +export function deepObjectLike(pattern: A): PropertyMatcher { + return _objectContaining(pattern, true); +} + +function _objectContaining(pattern: A, deep: boolean): PropertyMatcher { + const anno = { [deep ? '$deepObjectLike' : '$objectLike']: pattern }; + + return annotateMatcher(anno, (value: any, inspection: InspectionFailure): boolean => { + if (typeof value !== 'object' || !value) { + return failMatcher(inspection, `Expect an object but got '${typeof value}'`); + } + + const errors = new Array(); + + for (const [patternKey, patternValue] of Object.entries(pattern)) { + if (patternValue === ABSENT) { + if (value[patternKey] !== undefined) { errors.push(`Field ${patternKey} present, but shouldn't be`); } + continue; + } + + if (!(patternKey in value)) { + errors.push(`Field ${patternKey} missing`); + continue; + } + + // If we are doing DEEP objectLike, translate object literals in the pattern into + // more `deepObjectLike` matchers, even if they occur in lists. + const matchValue = deep ? deepMatcherFromObjectLiteral(patternValue) : patternValue; + + const innerInspection = { ...inspection, failureReason: '' }; + const valueMatches = match(value[patternKey], matchValue, innerInspection); + if (!valueMatches) { + errors.push(`Field ${patternKey} mismatch: ${innerInspection.failureReason}`); + } + } + + /** + * Transform nested object literals into more deep object matchers, if applicable + * + * Object literals in lists are also transformed. + */ + function deepMatcherFromObjectLiteral(nestedPattern: any): any { + if (isObject(nestedPattern)) { + return deepObjectLike(nestedPattern); + } + if (Array.isArray(nestedPattern)) { + return nestedPattern.map(deepMatcherFromObjectLiteral); + } + return nestedPattern; + } + + if (errors.length > 0) { + return failMatcher(inspection, errors.join(', ')); + } + return true; + }); +} + +/** + * Match exactly the given value + * + * This is the default, you only need this to escape from the deep lenient matching + * of `deepObjectLike`. + */ +export function exactValue(expected: any): PropertyMatcher { + const anno = { $exactValue: expected }; + return annotateMatcher(anno, (value: any, inspection: InspectionFailure): boolean => { + return matchLiteral(value, expected, inspection); + }); +} + +/** + * A matcher for a list that contains all of the given elements in any order + */ +export function arrayWith(...elements: any[]): PropertyMatcher { + if (elements.length === 0) { return anything(); } + + const anno = { $arrayContaining: elements.length === 1 ? elements[0] : elements }; + return annotateMatcher(anno, (value: any, inspection: InspectionFailure): boolean => { + if (!Array.isArray(value)) { + return failMatcher(inspection, `Expect an array but got '${typeof value}'`); + } + + for (const element of elements) { + const failure = longestFailure(value, element); + if (failure) { + return failMatcher(inspection, `Array did not contain expected element, closest match at index ${failure[0]}: ${failure[1]}`); + } + } + + return true; + + /** + * Return 'null' if the matcher matches anywhere in the array, otherwise the longest error and its index + */ + function longestFailure(array: any[], matcher: any): [number, string] | null { + let fail: [number, string] | null = null; + for (let i = 0; i < array.length; i++) { + const innerInspection = { ...inspection, failureReason: '' }; + if (match(array[i], matcher, innerInspection)) { + return null; + } + + if (fail === null || innerInspection.failureReason.length > fail[1].length) { + fail = [i, innerInspection.failureReason]; + } + } + return fail; + } + }); +} + +/** + * Whether a value is an object + */ +function isObject(x: any): x is object { + // Because `typeof null === 'object'`. + return x && typeof x === 'object'; +} + +/** + * Helper function to make matcher failure reporting a little easier + * + * Our protocol is weird (change a string on a passed-in object and return 'false'), + * but I don't want to change that right now. + */ +export function failMatcher(inspection: InspectionFailure, error: string): boolean { + inspection.failureReason = error; + return false; +} + +/** + * Match a given literal value against a matcher + * + * If the matcher is a callable, use that to evaluate the value. Otherwise, the values + * must be literally the same. + */ +export function match(value: any, matcher: any, inspection: InspectionFailure) { + if (isCallable(matcher)) { + // Custom matcher (this mostly looks very weird because our `InspectionFailure` signature is weird) + const innerInspection: InspectionFailure = { ...inspection, failureReason: '' }; + const result = matcher(value, innerInspection); + if (typeof result !== 'boolean') { + return failMatcher(inspection, `Predicate returned non-boolean return value: ${result}`); + } + if (!result && !innerInspection.failureReason) { + // Custom matcher neglected to return an error + return failMatcher(inspection, 'Predicate returned false'); + } + // Propagate inner error in case of failure + if (!result) { inspection.failureReason = innerInspection.failureReason; } + return result; + } + + return matchLiteral(value, matcher, inspection); +} + +/** + * Match a literal value at the top level. + * + * When recursing into arrays or objects, the nested values can be either matchers + * or literals. + */ +function matchLiteral(value: any, pattern: any, inspection: InspectionFailure) { + if (pattern == null) { return true; } + + const errors = new Array(); + + if (Array.isArray(value) !== Array.isArray(pattern)) { + return failMatcher(inspection, 'Array type mismatch'); + } + if (Array.isArray(value)) { + if (pattern.length !== value.length) { + return failMatcher(inspection, 'Array length mismatch'); + } + + // Recurse comparison for individual objects + for (let i = 0; i < pattern.length; i++) { + if (!match(value[i], pattern[i], { ...inspection })) { + errors.push(`Array element ${i} mismatch`); + } + } + + if (errors.length > 0) { + return failMatcher(inspection, errors.join(', ')); + } + return true; + } + if ((typeof value === 'object') !== (typeof pattern === 'object')) { + return failMatcher(inspection, 'Object type mismatch'); + } + if (typeof pattern === 'object') { + // Check that all fields in the pattern have the right value + const innerInspection = { ...inspection, failureReason: '' }; + const matcher = objectLike(pattern)(value, innerInspection); + if (!matcher) { + inspection.failureReason = innerInspection.failureReason; + return false; + } + + // Check no fields uncovered + const realFields = new Set(Object.keys(value)); + for (const key of Object.keys(pattern)) { realFields.delete(key); } + if (realFields.size > 0) { + return failMatcher(inspection, `Unexpected keys present in object: ${Array.from(realFields).join(', ')}`); + } + return true; + } + + if (value !== pattern) { + return failMatcher(inspection, 'Different values'); + } + + return true; +} + +/** + * Whether a value is a callable + */ +function isCallable(x: any): x is ((...args: any[]) => any) { + return x && {}.toString.call(x) === '[object Function]'; +} + +/** + * Do a glob-like pattern match (which only supports *s) + */ +export function stringLike(pattern: string): PropertyMatcher { + // Replace * with .* in the string, escape the rest and brace with ^...$ + const regex = new RegExp(`^${pattern.split('*').map(escapeRegex).join('.*')}$`); + + return annotateMatcher({ $stringContaining: pattern }, (value: any, failure: InspectionFailure) => { + if (typeof value !== 'string') { + failure.failureReason = `Expected a string, but got '${typeof value}'`; + return false; + } + + if (!regex.test(value)) { + failure.failureReason = 'String did not match pattern'; + return false; + } + + return true; + }); +} + +/** + * Matches any value + */ +export function anything(): PropertyMatcher { + return annotateMatcher({ $anything: true }, () => true); +} + +/** + * Negate an inner matcher + */ +export function notMatching(matcher: any): PropertyMatcher { + return annotateMatcher({ $notMatching: matcher }, (value: any, failure: InspectionFailure) => { + const result = matcherFrom(matcher)(value, failure); + if (result) { + failure.failureReason = 'Should not have matched, but did'; + return false; + } + return true; + }); +} + +/** + * Match on the innards of a JSON string, instead of the complete string + */ +export function encodedJson(matcher: any): PropertyMatcher { + return annotateMatcher({ $encodedJson: matcher }, (value: any, failure: InspectionFailure) => { + if (typeof value !== 'string') { + failure.failureReason = `Expected a string, but got '${typeof value}'`; + return false; + } + + let decoded; + try { + decoded = JSON.parse(value); + } catch (e) { + failure.failureReason = `String is not JSON: ${e}`; + return false; + } + + return matcherFrom(matcher)(decoded, failure); + }); +} + +function escapeRegex(s: string) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Make a matcher out of the given argument if it's not a matcher already + * + * If it's not a matcher, it will be treated as a literal. + */ +export function matcherFrom(matcher: any): PropertyMatcher { + return isCallable(matcher) ? matcher : exactValue(matcher); +} + +/** + * Annotate a matcher with toJSON + * + * We will JSON.stringify() values if we have a match failure, but for matchers this + * would show (in traditional JS fashion) something like '[function Function]', or more + * accurately nothing at all since functions cannot be JSONified. + * + * We override to JSON() in order to produce a readadable version of the matcher. + */ +export function annotateMatcher(how: A, matcher: PropertyMatcher): PropertyMatcher { + (matcher as any).toJSON = () => how; + return matcher; +} \ No newline at end of file diff --git a/packages/@aws-cdk/assert/lib/assertions/have-resource.ts b/packages/@aws-cdk/assert/lib/assertions/have-resource.ts index c44a4b176e6fd..2ea670b9c394d 100644 --- a/packages/@aws-cdk/assert/lib/assertions/have-resource.ts +++ b/packages/@aws-cdk/assert/lib/assertions/have-resource.ts @@ -1,5 +1,6 @@ import { Assertion, JestFriendlyAssertion } from '../assertion'; import { StackInspector } from '../inspector'; +import { anything, deepObjectLike, match, objectLike } from './have-resource-matchers'; /** * Magic value to signify that a certain key should be absent from the property bag. @@ -101,7 +102,7 @@ export class HaveResourceAssertion extends JestFriendlyAssertion } public get description(): string { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len return `resource '${this.resourceType}' with ${JSON.stringify(this.matcher, undefined, 2)}`; } } @@ -117,248 +118,27 @@ export interface InspectionFailure { } /** - * Match a given literal value against a matcher - * - * If the matcher is a callable, use that to evaluate the value. Otherwise, the values - * must be literally the same. - */ -function match(value: any, matcher: any, inspection: InspectionFailure) { - if (isCallable(matcher)) { - // Custom matcher (this mostly looks very weird because our `InspectionFailure` signature is weird) - const innerInspection: InspectionFailure = { ...inspection, failureReason: '' }; - const result = matcher(value, innerInspection); - if (typeof result !== 'boolean') { - return failMatcher(inspection, `Predicate returned non-boolean return value: ${result}`); - } - if (!result && !innerInspection.failureReason) { - // Custom matcher neglected to return an error - return failMatcher(inspection, 'Predicate returned false'); - } - // Propagate inner error in case of failure - if (!result) { inspection.failureReason = innerInspection.failureReason; } - return result; - } - - return matchLiteral(value, matcher, inspection); -} - -/** - * Match a literal value at the top level. - * - * When recursing into arrays or objects, the nested values can be either matchers - * or literals. - */ -function matchLiteral(value: any, pattern: any, inspection: InspectionFailure) { - if (pattern == null) { return true; } - - const errors = new Array(); - - if (Array.isArray(value) !== Array.isArray(pattern)) { - return failMatcher(inspection, 'Array type mismatch'); - } - if (Array.isArray(value)) { - if (pattern.length !== value.length) { - return failMatcher(inspection, 'Array length mismatch'); - } - - // Recurse comparison for individual objects - for (let i = 0; i < pattern.length; i++) { - if (!match(value[i], pattern[i], { ...inspection })) { - errors.push(`Array element ${i} mismatch`); - } - } - - if (errors.length > 0) { - return failMatcher(inspection, errors.join(', ')); - } - return true; - } - if ((typeof value === 'object') !== (typeof pattern === 'object')) { - return failMatcher(inspection, 'Object type mismatch'); - } - if (typeof pattern === 'object') { - // Check that all fields in the pattern have the right value - const innerInspection = { ...inspection, failureReason: '' }; - const matcher = objectLike(pattern)(value, innerInspection); - if (!matcher) { - inspection.failureReason = innerInspection.failureReason; - return false; - } - - // Check no fields uncovered - const realFields = new Set(Object.keys(value)); - for (const key of Object.keys(pattern)) { realFields.delete(key); } - if (realFields.size > 0) { - return failMatcher(inspection, `Unexpected keys present in object: ${Array.from(realFields).join(', ')}`); - } - return true; - } - - if (value !== pattern) { - return failMatcher(inspection, 'Different values'); - } - - return true; -} - -/** - * Helper function to make matcher failure reporting a little easier - * - * Our protocol is weird (change a string on a passed-in object and return 'false'), - * but I don't want to change that right now. - */ -function failMatcher(inspection: InspectionFailure, error: string): boolean { - inspection.failureReason = error; - return false; -} - -/** - * A matcher for an object that contains at least the given fields with the given matchers (or literals) - * - * Only does lenient matching one level deep, at the next level all objects must declare the - * exact expected keys again. - */ -export function objectLike(pattern: A): PropertyMatcher { - return _objectContaining(pattern, false); -} - -/** - * A matcher for an object that contains at least the given fields with the given matchers (or literals) - * - * Switches to "deep" lenient matching. Nested objects also only need to contain declared keys. - */ -export function deepObjectLike(pattern: A): PropertyMatcher { - return _objectContaining(pattern, true); -} - -export function _objectContaining(pattern: A, deep: boolean): PropertyMatcher { - const ret = (value: any, inspection: InspectionFailure): boolean => { - if (typeof value !== 'object' || !value) { - return failMatcher(inspection, `Expect an object but got '${typeof value}'`); - } - - const errors = new Array(); - - for (const [patternKey, patternValue] of Object.entries(pattern)) { - if (patternValue === ABSENT) { - if (value[patternKey] !== undefined) { errors.push(`Field ${patternKey} present, but shouldn't be`); } - continue; - } - - if (!(patternKey in value)) { - errors.push(`Field ${patternKey} missing`); - continue; - } - - // If we are doing DEEP objectLike, translate object literals in the pattern into - // more `deepObjectLike` matchers, even if they occur in lists. - const matchValue = deep ? deepMatcherFromObjectLiteral(patternValue) : patternValue; - - const innerInspection = { ...inspection, failureReason: '' }; - const valueMatches = match(value[patternKey], matchValue, innerInspection); - if (!valueMatches) { - errors.push(`Field ${patternKey} mismatch: ${innerInspection.failureReason}`); - } - } - - /** - * Transform nested object literals into more deep object matchers, if applicable - * - * Object literals in lists are also transformed. - */ - function deepMatcherFromObjectLiteral(nestedPattern: any): any { - if (isObject(nestedPattern)) { - return deepObjectLike(nestedPattern); - } - if (Array.isArray(nestedPattern)) { - return nestedPattern.map(deepMatcherFromObjectLiteral); - } - return nestedPattern; - } - - if (errors.length > 0) { - return failMatcher(inspection, errors.join(', ')); - } - return true; - }; - - // Override toJSON so that our error messages print an readable version of this matcher - // (which we produce by doing JSON.stringify() at some point in the future). - ret.toJSON = () => ({ [deep ? '$deepObjectLike' : '$objectLike']: pattern }); - return ret; -} - -/** - * Match exactly the given value - * - * This is the default, you only need this to escape from the deep lenient matching - * of `deepObjectLike`. - */ -export function exactValue(expected: any): PropertyMatcher { - const ret = (value: any, inspection: InspectionFailure): boolean => { - return matchLiteral(value, expected, inspection); - }; - - // Override toJSON so that our error messages print an readable version of this matcher - // (which we produce by doing JSON.stringify() at some point in the future). - ret.toJSON = () => ({ $exactValue: expected }); - return ret; -} - -/** - * A matcher for a list that contains all of the given elements in any order + * What part of the resource to compare */ -export function arrayWith(...elements: any[]): PropertyMatcher { - if (elements.length === 0) { return anything(); } - - const ret = (value: any, inspection: InspectionFailure): boolean => { - if (!Array.isArray(value)) { - return failMatcher(inspection, `Expect an array but got '${typeof value}'`); - } - - for (const element of elements) { - const failure = longestFailure(value, element); - if (failure) { - return failMatcher(inspection, `Array did not contain expected element, closest match at index ${failure[0]}: ${failure[1]}`); - } - } - - return true; - - /** - * Return 'null' if the matcher matches anywhere in the array, otherwise the longest error and its index - */ - function longestFailure(array: any[], matcher: any): [number, string] | null { - let fail: [number, string] | null = null; - for (let i = 0; i < array.length; i++) { - const innerInspection = { ...inspection, failureReason: '' }; - if (match(array[i], matcher, innerInspection)) { - return null; - } - - if (fail === null || innerInspection.failureReason.length > fail[1].length) { - fail = [i, innerInspection.failureReason]; - } - } - return fail; - } - }; +export enum ResourcePart { + /** + * Only compare the resource's properties + */ + Properties, - // Override toJSON so that our error messages print an readable version of this matcher - // (which we produce by doing JSON.stringify() at some point in the future). - ret.toJSON = () => ({ $arrayContaining: elements.length === 1 ? elements[0] : elements }); - return ret; + /** + * Check the entire CloudFormation config + * + * (including UpdateConfig, DependsOn, etc.) + */ + CompleteDefinition } /** - * Matches anything + * Whether a value is a callable */ -function anything() { - const ret = () => { - return true; - }; - ret.toJSON = () => ({ $anything: true }); - return ret; +function isCallable(x: any): x is ((...args: any[]) => any) { + return x && {}.toString.call(x) === '[object Function]'; } /** @@ -381,35 +161,3 @@ export function isSuperObject(superObj: any, pattern: any, errors: string[] = [] } return ret; } - -/** - * What part of the resource to compare - */ -export enum ResourcePart { - /** - * Only compare the resource's properties - */ - Properties, - - /** - * Check the entire CloudFormation config - * - * (including UpdateConfig, DependsOn, etc.) - */ - CompleteDefinition -} - -/** - * Whether a value is a callable - */ -function isCallable(x: any): x is ((...args: any[]) => any) { - return x && {}.toString.call(x) === '[object Function]'; -} - -/** - * Whether a value is an object - */ -function isObject(x: any): x is object { - // Because `typeof null === 'object'`. - return x && typeof x === 'object'; -} diff --git a/packages/@aws-cdk/assert/lib/canonicalize-assets.ts b/packages/@aws-cdk/assert/lib/canonicalize-assets.ts new file mode 100644 index 0000000000000..9cee3d4742b3c --- /dev/null +++ b/packages/@aws-cdk/assert/lib/canonicalize-assets.ts @@ -0,0 +1,71 @@ +/** + * Reduce template to a normal form where asset references have been normalized + * + * This makes it possible to compare templates if all that's different between + * them is the hashes of the asset values. + * + * Currently only handles parameterized assets, but can (and should) + * be adapted to handle convention-mode assets as well when we start using + * more of those. + */ +export function canonicalizeTemplate(template: any): any { + // For the weird case where we have an array of templates... + if (Array.isArray(template)) { + return template.map(canonicalizeTemplate); + } + + // Find assets via parameters + const stringSubstitutions = new Array<[RegExp, string]>(); + const paramRe = /^AssetParameters([a-zA-Z0-9]{64})(S3Bucket|S3VersionKey|ArtifactHash)([a-zA-Z0-9]{8})$/; + + const assetsSeen = new Set(); + for (const paramName of Object.keys(template?.Parameters || {})) { + const m = paramRe.exec(paramName); + if (!m) { continue; } + if (assetsSeen.has(m[1])) { continue; } + + assetsSeen.add(m[1]); + const ix = assetsSeen.size; + + // Full parameter reference + stringSubstitutions.push([ + new RegExp(`AssetParameters${m[1]}(S3Bucket|S3VersionKey|ArtifactHash)([a-zA-Z0-9]{8})`), + `Asset${ix}$1`, + ]); + // Substring asset hash reference + stringSubstitutions.push([ + new RegExp(`${m[1]}`), + `Asset${ix}Hash`, + ]); + } + + // Substitute them out + return substitute(template); + + function substitute(what: any): any { + if (Array.isArray(what)) { + return what.map(substitute); + } + + if (typeof what === 'object' && what !== null) { + const ret: any = {}; + for (const [k, v] of Object.entries(what)) { + ret[stringSub(k)] = substitute(v); + } + return ret; + } + + if (typeof what === 'string') { + return stringSub(what); + } + + return what; + } + + function stringSub(x: string) { + for (const [re, replacement] of stringSubstitutions) { + x = x.replace(re, replacement); + } + return x; + } +} diff --git a/packages/@aws-cdk/assert/lib/expect.ts b/packages/@aws-cdk/assert/lib/expect.ts index 392d374cb52a1..21dd7e011c826 100644 --- a/packages/@aws-cdk/assert/lib/expect.ts +++ b/packages/@aws-cdk/assert/lib/expect.ts @@ -3,8 +3,10 @@ import * as api from '@aws-cdk/cx-api'; import { StackInspector } from './inspector'; import { SynthUtils } from './synth-utils'; -export function expect(stack: api.CloudFormationStackArtifact | cdk.Stack, skipValidation = false): StackInspector { +export function expect(stack: api.CloudFormationStackArtifact | cdk.Stack | Record, skipValidation = false): StackInspector { // if this is already a synthesized stack, then just inspect it. - const artifact = stack instanceof api.CloudFormationStackArtifact ? stack : SynthUtils._synthesizeWithNested(stack, { skipValidation }); + const artifact = stack instanceof api.CloudFormationStackArtifact ? stack + : cdk.Stack.isStack(stack) ? SynthUtils._synthesizeWithNested(stack, { skipValidation }) + : stack; // This is a template already return new StackInspector(artifact); } diff --git a/packages/@aws-cdk/assert/lib/index.ts b/packages/@aws-cdk/assert/lib/index.ts index ff3516dc2f6fd..902a5c222f003 100644 --- a/packages/@aws-cdk/assert/lib/index.ts +++ b/packages/@aws-cdk/assert/lib/index.ts @@ -1,4 +1,5 @@ export * from './assertion'; +export * from './canonicalize-assets'; export * from './expect'; export * from './inspector'; export * from './synth-utils'; @@ -6,6 +7,7 @@ export * from './synth-utils'; export * from './assertions/exist'; export * from './assertions/have-output'; export * from './assertions/have-resource'; +export * from './assertions/have-resource-matchers'; export * from './assertions/have-type'; export * from './assertions/match-template'; export * from './assertions/and-assertion'; diff --git a/packages/@aws-cdk/assert/lib/synth-utils.ts b/packages/@aws-cdk/assert/lib/synth-utils.ts index 57d21926919b1..b66216362d363 100644 --- a/packages/@aws-cdk/assert/lib/synth-utils.ts +++ b/packages/@aws-cdk/assert/lib/synth-utils.ts @@ -1,7 +1,7 @@ -import * as core from '@aws-cdk/core'; -import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs'; import * as path from 'path'; +import * as core from '@aws-cdk/core'; +import * as cxapi from '@aws-cdk/cx-api'; export class SynthUtils { public static synthesize(stack: core.Stack, options: core.SynthesisOptions = { }): cxapi.CloudFormationStackArtifact { diff --git a/packages/@aws-cdk/assert/package.json b/packages/@aws-cdk/assert/package.json index 8eefc0da1c185..3ba471e665353 100644 --- a/packages/@aws-cdk/assert/package.json +++ b/packages/@aws-cdk/assert/package.json @@ -21,11 +21,11 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^26.0.3", + "@types/jest": "^26.0.4", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", "pkglint": "0.0.0", - "ts-jest": "^26.1.1" + "ts-jest": "^26.1.3" }, "dependencies": { "@aws-cdk/cloudformation-diff": "0.0.0", diff --git a/packages/@aws-cdk/assert/test/canonicalize-assets.test.ts b/packages/@aws-cdk/assert/test/canonicalize-assets.test.ts new file mode 100644 index 0000000000000..38ebd4b1e5b47 --- /dev/null +++ b/packages/@aws-cdk/assert/test/canonicalize-assets.test.ts @@ -0,0 +1,135 @@ +import { canonicalizeTemplate } from '../lib'; + +test('Canonicalize asset parameters and references to them', () => { + const template = { + Resources: { + AResource: { + Type: 'Some::Resource', + Properties: { + SomeValue: { Ref: 'AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3Bucket0C424907' }, + }, + }, + }, + Parameters: { + AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3Bucket0C424907: { + Type: 'String', + Description: 'S3 bucket for asset \'ea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161\'', + }, + AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3VersionKey6841F1F8: { + Type: 'String', + Description: 'S3 key for asset version \'ea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161\'', + }, + AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161ArtifactHash67B22EF2: { + Type: 'String', + Description: 'Artifact hash for asset \'ea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161\'', + }, + }, + }; + + const expected = { + Resources: { + AResource: { + Type: 'Some::Resource', + Properties: { + SomeValue: { Ref: 'Asset1S3Bucket' }, + }, + }, + }, + Parameters: { + Asset1S3Bucket: { + Type: 'String', + Description: 'S3 bucket for asset \'Asset1Hash\'', + }, + Asset1S3VersionKey: { + Type: 'String', + Description: 'S3 key for asset version \'Asset1Hash\'', + }, + Asset1ArtifactHash: { + Type: 'String', + Description: 'Artifact hash for asset \'Asset1Hash\'', + }, + }, + }; + + expect(canonicalizeTemplate(template)).toEqual(expected); +}); + +test('Distinguished 2 different assets', () => { + const template = { + Resources: { + AResource: { + Type: 'Some::Resource', + Properties: { + SomeValue: { Ref: 'AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3Bucket0C424907' }, + OtherValue: { Ref: 'AssetParameters1111111111111111111111111111111111122222222222222222222222222222ArtifactHash67B22EF2' }, + }, + }, + }, + Parameters: { + AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3Bucket0C424907: { + Type: 'String', + Description: 'S3 bucket for asset \'ea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161\'', + }, + AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161S3VersionKey6841F1F8: { + Type: 'String', + Description: 'S3 key for asset version \'ea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161\'', + }, + AssetParametersea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161ArtifactHash67B22EF2: { + Type: 'String', + Description: 'Artifact hash for asset \'ea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161\'', + }, + AssetParameters1111111111111111111111111111111111122222222222222222222222222222S3Bucket0C424907: { + Type: 'String', + Description: 'S3 bucket for asset \'1111111111111111111111111111111111122222222222222222222222222222\'', + }, + AssetParameters1111111111111111111111111111111111122222222222222222222222222222S3VersionKey6841F1F8: { + Type: 'String', + Description: 'S3 key for asset version \'1111111111111111111111111111111111122222222222222222222222222222\'', + }, + AssetParameters1111111111111111111111111111111111122222222222222222222222222222ArtifactHash67B22EF2: { + Type: 'String', + Description: 'Artifact hash for asset \'1111111111111111111111111111111111122222222222222222222222222222\'', + }, + }, + }; + + const expected = { + Resources: { + AResource: { + Type: 'Some::Resource', + Properties: { + SomeValue: { Ref: 'Asset1S3Bucket' }, + OtherValue: { Ref: 'Asset2ArtifactHash' }, + }, + }, + }, + Parameters: { + Asset1S3Bucket: { + Type: 'String', + Description: 'S3 bucket for asset \'Asset1Hash\'', + }, + Asset1S3VersionKey: { + Type: 'String', + Description: 'S3 key for asset version \'Asset1Hash\'', + }, + Asset1ArtifactHash: { + Type: 'String', + Description: 'Artifact hash for asset \'Asset1Hash\'', + }, + Asset2S3Bucket: { + Type: 'String', + Description: 'S3 bucket for asset \'Asset2Hash\'', + }, + Asset2S3VersionKey: { + Type: 'String', + Description: 'S3 key for asset version \'Asset2Hash\'', + }, + Asset2ArtifactHash: { + Type: 'String', + Description: 'Artifact hash for asset \'Asset2Hash\'', + }, + }, + }; + + expect(canonicalizeTemplate(template)).toEqual(expected); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/assert/test/have-output.test.ts b/packages/@aws-cdk/assert/test/have-output.test.ts index 250f30e038ce5..b7233d61de96a 100644 --- a/packages/@aws-cdk/assert/test/have-output.test.ts +++ b/packages/@aws-cdk/assert/test/have-output.test.ts @@ -1,7 +1,7 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as cxapi from '@aws-cdk/cx-api'; import { unlink, writeFileSync } from 'fs'; import { join } from 'path'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import * as cxapi from '@aws-cdk/cx-api'; import '../jest'; let templateFilePath: string; diff --git a/packages/@aws-cdk/assert/test/have-resource.test.ts b/packages/@aws-cdk/assert/test/have-resource.test.ts index 69ab649433350..ad114a485dead 100644 --- a/packages/@aws-cdk/assert/test/have-resource.test.ts +++ b/packages/@aws-cdk/assert/test/have-resource.test.ts @@ -1,7 +1,7 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as cxapi from '@aws-cdk/cx-api'; import { writeFileSync } from 'fs'; import { join } from 'path'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import * as cxapi from '@aws-cdk/cx-api'; import { ABSENT, arrayWith, exactValue, expect as cdkExpect, haveResource, haveResourceLike } from '../lib/index'; test('support resource with no properties', () => { diff --git a/packages/@aws-cdk/assets/.gitignore b/packages/@aws-cdk/assets/.gitignore index 84107ada8a317..7e3964a75701e 100644 --- a/packages/@aws-cdk/assets/.gitignore +++ b/packages/@aws-cdk/assets/.gitignore @@ -15,3 +15,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/assets/.npmignore b/packages/@aws-cdk/assets/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/assets/.npmignore +++ b/packages/@aws-cdk/assets/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-accessanalyzer/.gitignore b/packages/@aws-cdk/aws-accessanalyzer/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-accessanalyzer/.gitignore +++ b/packages/@aws-cdk/aws-accessanalyzer/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-accessanalyzer/.npmignore b/packages/@aws-cdk/aws-accessanalyzer/.npmignore index 683e3e0847e1f..a7c5b49852b3b 100644 --- a/packages/@aws-cdk/aws-accessanalyzer/.npmignore +++ b/packages/@aws-cdk/aws-accessanalyzer/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-accessanalyzer/jest.config.js b/packages/@aws-cdk/aws-accessanalyzer/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-accessanalyzer/jest.config.js +++ b/packages/@aws-cdk/aws-accessanalyzer/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-acmpca/.gitignore b/packages/@aws-cdk/aws-acmpca/.gitignore index 6d1b46f871517..38eb872eddcbe 100644 --- a/packages/@aws-cdk/aws-acmpca/.gitignore +++ b/packages/@aws-cdk/aws-acmpca/.gitignore @@ -16,3 +16,5 @@ coverage *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-acmpca/.npmignore b/packages/@aws-cdk/aws-acmpca/.npmignore index 2f1ff45adc883..c8874799485a3 100644 --- a/packages/@aws-cdk/aws-acmpca/.npmignore +++ b/packages/@aws-cdk/aws-acmpca/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-acmpca/jest.config.js b/packages/@aws-cdk/aws-acmpca/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-acmpca/jest.config.js +++ b/packages/@aws-cdk/aws-acmpca/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-amazonmq/.gitignore b/packages/@aws-cdk/aws-amazonmq/.gitignore index adcba106db8d1..7bdb507ae2cc7 100644 --- a/packages/@aws-cdk/aws-amazonmq/.gitignore +++ b/packages/@aws-cdk/aws-amazonmq/.gitignore @@ -14,3 +14,5 @@ tsconfig.json *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-amazonmq/.npmignore b/packages/@aws-cdk/aws-amazonmq/.npmignore index ac1c98567f9a5..3dffd1ce79a72 100644 --- a/packages/@aws-cdk/aws-amazonmq/.npmignore +++ b/packages/@aws-cdk/aws-amazonmq/.npmignore @@ -26,4 +26,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-amazonmq/jest.config.js b/packages/@aws-cdk/aws-amazonmq/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-amazonmq/jest.config.js +++ b/packages/@aws-cdk/aws-amazonmq/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-amplify/.gitignore b/packages/@aws-cdk/aws-amplify/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-amplify/.gitignore +++ b/packages/@aws-cdk/aws-amplify/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-amplify/.npmignore b/packages/@aws-cdk/aws-amplify/.npmignore index 5177fc0435bf1..917201c845418 100644 --- a/packages/@aws-cdk/aws-amplify/.npmignore +++ b/packages/@aws-cdk/aws-amplify/.npmignore @@ -26,4 +26,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-amplify/jest.config.js b/packages/@aws-cdk/aws-amplify/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-amplify/jest.config.js +++ b/packages/@aws-cdk/aws-amplify/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-amplify/lib/app.ts b/packages/@aws-cdk/aws-amplify/lib/app.ts index 640c9684625df..1d41c0e07ede3 100644 --- a/packages/@aws-cdk/aws-amplify/lib/app.ts +++ b/packages/@aws-cdk/aws-amplify/lib/app.ts @@ -210,7 +210,7 @@ export class App extends Resource implements IApp, iam.IGrantable { buildSpec: props.autoBranchCreation.buildSpec && props.autoBranchCreation.buildSpec.toBuildSpec(), enableAutoBranchCreation: true, enableAutoBuild: props.autoBranchCreation.autoBuild === undefined ? true : props.autoBranchCreation.autoBuild, - environmentVariables: Lazy.anyValue({ produce: () => renderEnvironmentVariables(this.autoBranchEnvironmentVariables )}, { omitEmptyArray: true }), // tslint:disable-line max-line-length + environmentVariables: Lazy.anyValue({ produce: () => renderEnvironmentVariables(this.autoBranchEnvironmentVariables )}, { omitEmptyArray: true }), // eslint-disable-line max-len enablePullRequestPreview: props.autoBranchCreation.pullRequestPreview === undefined ? true : props.autoBranchCreation.pullRequestPreview, pullRequestEnvironmentName: props.autoBranchCreation.pullRequestEnvironmentName, stage: props.autoBranchCreation.stage, diff --git a/packages/@aws-cdk/aws-apigateway/.gitignore b/packages/@aws-cdk/aws-apigateway/.gitignore index 0bd6133da4d09..018c65919d67c 100644 --- a/packages/@aws-cdk/aws-apigateway/.gitignore +++ b/packages/@aws-cdk/aws-apigateway/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/.npmignore b/packages/@aws-cdk/aws-apigateway/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-apigateway/.npmignore +++ b/packages/@aws-cdk/aws-apigateway/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/README.md b/packages/@aws-cdk/aws-apigateway/README.md index 6045d52aa95d3..89d2d721dc849 100644 --- a/packages/@aws-cdk/aws-apigateway/README.md +++ b/packages/@aws-cdk/aws-apigateway/README.md @@ -275,7 +275,7 @@ const integration = new LambdaIntegration(hello, { // We will set the response status code to 200 statusCode: "200", responseTemplates: { - // This template takes the "message" result from the Lambda function, adn embeds it in a JSON response + // This template takes the "message" result from the Lambda function, and embeds it in a JSON response // Check https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html 'application/json': JSON.stringify({ state: 'ok', greeting: '$util.escapeJavaScript($input.body)' }) }, diff --git a/packages/@aws-cdk/aws-apigateway/lib/apigatewayv2.ts b/packages/@aws-cdk/aws-apigateway/lib/apigatewayv2.ts index 0332859518b01..1ef0e5d29c246 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/apigatewayv2.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/apigatewayv2.ts @@ -1,7 +1,7 @@ // Copyright 2012-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // Originally generated from the AWS CloudFormation Resource Specification. Now, hand managed. -// tslint:disable:max-line-length +/* eslint-disable max-len */ import * as cdk from '@aws-cdk/core'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index 6e3ed26a0c6d9..172eb77cd1877 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -270,7 +270,7 @@ export class Method extends Resource { credentials = options.credentialsRole.roleArn; } else if (options.credentialsPassthrough) { // arn:aws:iam::*:user/* - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len credentials = Stack.of(this).formatArn({ service: 'iam', region: '', account: '*', resource: 'user', sep: '/', resourceName: '*' }); } diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 9ba3714aed915..cedf43099856e 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -67,7 +67,7 @@ export interface IRestApi extends IResourceBase { /** * Represents the props that all Rest APIs share */ -export interface RestApiOptions extends ResourceOptions { +export interface RestApiBaseProps { /** * Indicates if a Deployment should be automatically created for this API, * and recreated when the API model (resources, methods) changes. @@ -161,6 +161,13 @@ export interface RestApiOptions extends ResourceOptions { readonly endpointExportName?: string; } +/** + * Represents the props that all Rest APIs share. + * @deprecated - superceded by `RestApiBaseProps` + */ +export interface RestApiOptions extends RestApiBaseProps, ResourceOptions { +} + /** * Props to create a new instance of RestApi */ @@ -229,7 +236,7 @@ export interface RestApiProps extends RestApiOptions { * Props to instantiate a new SpecRestApi * @experimental */ -export interface SpecRestApiProps extends RestApiOptions { +export interface SpecRestApiProps extends RestApiBaseProps { /** * An OpenAPI definition compatible with API Gateway. * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-import-api.html @@ -296,7 +303,7 @@ export abstract class RestApiBase extends Resource implements IRestApi { private _latestDeployment?: Deployment; private _domainName?: DomainName; - constructor(scope: Construct, id: string, props: RestApiOptions = { }) { + constructor(scope: Construct, id: string, props: RestApiBaseProps = { }) { super(scope, id, { physicalName: props.restApiName || id, }); @@ -461,7 +468,7 @@ export class SpecRestApi extends RestApiBase { this.node.defaultChild = resource; this.restApiId = resource.ref; this.restApiRootResourceId = resource.attrRootResourceId; - this.root = new RootResource(this, props, this.restApiRootResourceId); + this.root = new RootResource(this, {}, this.restApiRootResourceId); this.configureDeployment(props); if (props.domainName) { diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.handler/index.ts b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.handler/index.ts index 981dc3d28994a..517b5fe0c3d4b 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.handler/index.ts +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.handler/index.ts @@ -1,4 +1,4 @@ -// tslint:disable:no-console +/* eslint-disable no-console */ export const handler = async (event: any, _context: any = {}): Promise => { const authToken: string = event.headers.Authorization; diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.ts b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.ts index 43746c4dee677..776d9d580c39e 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.ts +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as lambda from '@aws-cdk/aws-lambda'; import { App, Stack } from '@aws-cdk/core'; import * as path from 'path'; diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.ts b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.ts index 67bd522a81f9c..39a63e7da30b4 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.ts +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import { App, Stack } from '@aws-cdk/core'; diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.handler/index.ts b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.handler/index.ts index 7481578045036..4d1f1d9307e39 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.handler/index.ts +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.handler/index.ts @@ -1,4 +1,4 @@ -// tslint:disable:no-console +/* eslint-disable no-console */ export const handler = async (event: any, _context: any = {}): Promise => { const authToken: string = event.authorizationToken; diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.ts b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.ts index 7dbbcce503294..7e8c89ee799d5 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.ts +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as lambda from '@aws-cdk/aws-lambda'; import { App, Stack } from '@aws-cdk/core'; import * as path from 'path'; diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.cors.handler/index.ts b/packages/@aws-cdk/aws-apigateway/test/integ.cors.handler/index.ts index c2eb7a76a449b..dd19c03dd795f 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.cors.handler/index.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.cors.handler/index.ts @@ -1,5 +1,5 @@ exports.handler = async (evt: any) => { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.error(JSON.stringify(evt, undefined, 2)); return { statusCode: 200, diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.cors.ts b/packages/@aws-cdk/aws-apigateway/test/integ.cors.ts index 966e162acc228..bd847d46b326e 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.cors.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.cors.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as lambda from '@aws-cdk/aws-lambda'; import { App, Construct, Stack, StackProps } from '@aws-cdk/core'; import * as path from 'path'; diff --git a/packages/@aws-cdk/aws-apigateway/test/test.domains.ts b/packages/@aws-cdk/aws-apigateway/test/test.domains.ts index 197978922c5a1..9001e0b8ca31e 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.domains.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.domains.ts @@ -1,10 +1,11 @@ -// tslint:disable:object-literal-key-quotes import { ABSENT, expect, haveResource } from '@aws-cdk/assert'; import * as acm from '@aws-cdk/aws-certificatemanager'; import { Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as apigw from '../lib'; +/* eslint-disable quote-props */ + export = { 'can define either an EDGE or REGIONAL domain name'(test: Test) { // GIVEN @@ -365,7 +366,7 @@ export = { }); const testStage = new apigw.Stage(stack, 'test-stage', { - deployment : testDeploy, + deployment: testDeploy, }); // WHEN diff --git a/packages/@aws-cdk/aws-apigateway/test/test.lambda-api.ts b/packages/@aws-cdk/aws-apigateway/test/test.lambda-api.ts index 2c72ee3895835..a4fb4757aaba3 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.lambda-api.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.lambda-api.ts @@ -4,7 +4,7 @@ import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as apigw from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'LambdaRestApi defines a REST API with Lambda proxy integration'(test: Test) { diff --git a/packages/@aws-cdk/aws-apigateway/test/test.resource.ts b/packages/@aws-cdk/aws-apigateway/test/test.resource.ts index 93c28db5823e7..43db34771346f 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.resource.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.resource.ts @@ -3,7 +3,7 @@ import { Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as apigw from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'ProxyResource defines a "{proxy+}" resource with ANY method'(test: Test) { diff --git a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts index 4df5bd3fd2755..3b51f66a611e3 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts @@ -4,7 +4,7 @@ import { App, CfnElement, CfnResource, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as apigw from '../lib'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ export = { 'minimal setup'(test: Test) { @@ -956,7 +956,7 @@ export = { test.done(); }, - 'Import': { + Import: { 'fromRestApiId()'(test: Test) { // GIVEN const stack = new Stack(); @@ -995,7 +995,7 @@ export = { }, }, - 'SpecRestApi': { + SpecRestApi: { 'add Methods and Resources'(test: Test) { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-apigatewayv2/.gitignore b/packages/@aws-cdk/aws-apigatewayv2/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/.gitignore +++ b/packages/@aws-cdk/aws-apigatewayv2/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/.npmignore b/packages/@aws-cdk/aws-apigatewayv2/.npmignore index 4bed40b6573da..e9418d013ad1f 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/.npmignore +++ b/packages/@aws-cdk/aws-apigatewayv2/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/jest.config.js b/packages/@aws-cdk/aws-apigatewayv2/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/jest.config.js +++ b/packages/@aws-cdk/aws-apigatewayv2/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-appconfig/.gitignore b/packages/@aws-cdk/aws-appconfig/.gitignore index 094eab59919f8..2c62ebbec7aeb 100644 --- a/packages/@aws-cdk/aws-appconfig/.gitignore +++ b/packages/@aws-cdk/aws-appconfig/.gitignore @@ -17,3 +17,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appconfig/.npmignore b/packages/@aws-cdk/aws-appconfig/.npmignore index 2f1ff45adc883..c8874799485a3 100644 --- a/packages/@aws-cdk/aws-appconfig/.npmignore +++ b/packages/@aws-cdk/aws-appconfig/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appconfig/jest.config.js b/packages/@aws-cdk/aws-appconfig/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-appconfig/jest.config.js +++ b/packages/@aws-cdk/aws-appconfig/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-applicationautoscaling/.gitignore b/packages/@aws-cdk/aws-applicationautoscaling/.gitignore index 0bd6133da4d09..018c65919d67c 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/.gitignore +++ b/packages/@aws-cdk/aws-applicationautoscaling/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-applicationautoscaling/.npmignore b/packages/@aws-cdk/aws-applicationautoscaling/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/.npmignore +++ b/packages/@aws-cdk/aws-applicationautoscaling/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-applicationautoscaling/package.json b/packages/@aws-cdk/aws-applicationautoscaling/package.json index 64ede897c926b..51bc9b8813d45 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/package.json +++ b/packages/@aws-cdk/aws-applicationautoscaling/package.json @@ -66,7 +66,7 @@ "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", - "fast-check": "^1.25.1", + "fast-check": "^1.26.0", "nodeunit": "^0.11.3", "pkglint": "0.0.0" }, diff --git a/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts index f4a6d91c01793..6f6092b5452d9 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts @@ -74,7 +74,7 @@ export = { return steps.every(step => { return reportFalse(intervals.find(interval => { const acceptableLowerBounds = step.MetricIntervalLowerBound === -Infinity ? [undefined, 0] : [undefined, step.MetricIntervalLowerBound]; - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len const acceptableUpperBounds = step.MetricIntervalUpperBound === Infinity ? [undefined, Infinity] : [undefined, step.MetricIntervalUpperBound]; return (acceptableLowerBounds.includes(interval.lower) && acceptableUpperBounds.includes(interval.upper)); @@ -259,7 +259,7 @@ function apply(x: T | undefined, f: (x: T) => U | undefined): U | undefine */ function reportFalse(cond: boolean, ...repr: any[]) { if (!cond) { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.error('PROPERTY FAILS ON:', ...repr); } return cond; diff --git a/packages/@aws-cdk/aws-appmesh/.gitignore b/packages/@aws-cdk/aws-appmesh/.gitignore index d5c0c2743f469..65d2efc186f05 100644 --- a/packages/@aws-cdk/aws-appmesh/.gitignore +++ b/packages/@aws-cdk/aws-appmesh/.gitignore @@ -12,3 +12,5 @@ coverage dist tsconfig.json !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appmesh/.npmignore b/packages/@aws-cdk/aws-appmesh/.npmignore index af5cffcab06c1..f937500da09a6 100644 --- a/packages/@aws-cdk/aws-appmesh/.npmignore +++ b/packages/@aws-cdk/aws-appmesh/.npmignore @@ -24,4 +24,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts b/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts index 34f2001c62e36..c89d9b9f6635b 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts @@ -6,6 +6,8 @@ import { Duration } from '@aws-cdk/core'; export enum Protocol { HTTP = 'http', TCP = 'tcp', + HTTP2 = 'http2', + GRPC = 'grpc', } /** diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts index 63c97392473c0..f7a2548a48e7b 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts @@ -167,6 +167,10 @@ function renderHealthCheck(hc: HealthCheck | undefined, pm: PortMapping): CfnVir throw new Error('The path property cannot be set with Protocol.TCP'); } + if (hc.protocol === Protocol.GRPC && hc.path) { + throw new Error('The path property cannot be set with Protocol.GRPC'); + } + const healthCheck: CfnVirtualNode.HealthCheckProperty = { healthyThreshold: hc.healthyThreshold || 2, intervalMillis: (hc.interval || cdk.Duration.seconds(5)).toMilliseconds(), // min diff --git a/packages/@aws-cdk/aws-appmesh/package.json b/packages/@aws-cdk/aws-appmesh/package.json index 3bbc70c2941d8..d917e0ffccb98 100644 --- a/packages/@aws-cdk/aws-appmesh/package.json +++ b/packages/@aws-cdk/aws-appmesh/package.json @@ -134,7 +134,9 @@ "props-default-doc:@aws-cdk/aws-appmesh.VirtualRouterAttributes.meshName", "props-default-doc:@aws-cdk/aws-appmesh.VirtualRouterAttributes.virtualRouterArn", "props-default-doc:@aws-cdk/aws-appmesh.VirtualRouterAttributes.virtualRouterName", - "docs-public-apis:@aws-cdk/aws-appmesh.Protocol.HTTP" + "docs-public-apis:@aws-cdk/aws-appmesh.Protocol.HTTP", + "docs-public-apis:@aws-cdk/aws-appmesh.Protocol.HTTP2", + "docs-public-apis:@aws-cdk/aws-appmesh.Protocol.GRPC" ] }, "stability": "experimental", diff --git a/packages/@aws-cdk/aws-appmesh/test/test.health-check.ts b/packages/@aws-cdk/aws-appmesh/test/test.health-check.ts index 9f877fd26dd6a..a89ba66afb3db 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.health-check.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.health-check.ts @@ -128,4 +128,24 @@ export = { test.done(); }, + + 'throws if path and Protocol.GRPC'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const toThrow = (protocol: appmesh.Protocol) => getNode(stack).addListeners({ + healthCheck: { + protocol, + path: '/', + }, + }); + + // THEN + test.doesNotThrow(() => toThrow(appmesh.Protocol.HTTP)); + test.throws(() => toThrow(appmesh.Protocol.GRPC), /The path property cannot be set with Protocol.GRPC/); + + test.done(); + }, + }; diff --git a/packages/@aws-cdk/aws-appstream/.gitignore b/packages/@aws-cdk/aws-appstream/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-appstream/.gitignore +++ b/packages/@aws-cdk/aws-appstream/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appstream/.npmignore b/packages/@aws-cdk/aws-appstream/.npmignore index b0edf81a01371..ab90672b1d91e 100644 --- a/packages/@aws-cdk/aws-appstream/.npmignore +++ b/packages/@aws-cdk/aws-appstream/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appstream/jest.config.js b/packages/@aws-cdk/aws-appstream/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-appstream/jest.config.js +++ b/packages/@aws-cdk/aws-appstream/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-appsync/.gitignore b/packages/@aws-cdk/aws-appsync/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-appsync/.gitignore +++ b/packages/@aws-cdk/aws-appsync/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/.npmignore b/packages/@aws-cdk/aws-appsync/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-appsync/.npmignore +++ b/packages/@aws-cdk/aws-appsync/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 378a5a4c8bd7d..e815444352288 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -111,6 +111,7 @@ export class ApiStack extends Stack { type: AttributeType.STRING, }, }); + // If your table is already created you can also use use import table and use it as data source. const customerDS = api.addDynamoDbDataSource('Customer', 'The customer data source', customerTable); customerDS.createResolver({ typeName: 'Query', diff --git a/packages/@aws-cdk/aws-appsync/jest.config.js b/packages/@aws-cdk/aws-appsync/jest.config.js index d9634b8ea0c5d..ff8ca0285ed49 100644 --- a/packages/@aws-cdk/aws-appsync/jest.config.js +++ b/packages/@aws-cdk/aws-appsync/jest.config.js @@ -1,4 +1,4 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = { ...baseConfig, coverageThreshold: { diff --git a/packages/@aws-cdk/aws-appsync/lib/data-source.ts b/packages/@aws-cdk/aws-appsync/lib/data-source.ts new file mode 100644 index 0000000000000..0daf1f996a452 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/data-source.ts @@ -0,0 +1,251 @@ +import { ITable } from '@aws-cdk/aws-dynamodb'; +import { IGrantable, IPrincipal, IRole, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; +import { IFunction } from '@aws-cdk/aws-lambda'; +import { Construct, IResolvable } from '@aws-cdk/core'; +import { CfnDataSource } from './appsync.generated'; +import { GraphQLApi } from './graphqlapi'; +import { BaseResolverProps, Resolver } from './resolver'; + +/** + * Base properties for an AppSync datasource + */ +export interface BaseDataSourceProps { + /** + * The API to attach this data source to + */ + readonly api: GraphQLApi; + /** + * The name of the data source + */ + readonly name: string; + /** + * the description of the data source + * + * @default - None + */ + readonly description?: string; +} + +/** + * properties for an AppSync datasource backed by a resource + */ +export interface BackedDataSourceProps extends BaseDataSourceProps { + /** + * The IAM service role to be assumed by AppSync to interact with the data source + * + * @default - Create a new role + */ + readonly serviceRole?: IRole; +} + +/** + * props used by implementations of BaseDataSource to provide configuration. Should not be used directly. + */ +export interface ExtendedDataSourceProps { + /** + * the type of the AppSync datasource + */ + readonly type: string; + /** + * configuration for DynamoDB Datasource + * + * @default - No config + */ + readonly dynamoDbConfig?: CfnDataSource.DynamoDBConfigProperty | IResolvable; + /** + * configuration for Elasticsearch Datasource + * + * @default - No config + */ + readonly elasticsearchConfig?: CfnDataSource.ElasticsearchConfigProperty | IResolvable; + /** + * configuration for HTTP Datasource + * + * @default - No config + */ + readonly httpConfig?: CfnDataSource.HttpConfigProperty | IResolvable; + /** + * configuration for Lambda Datasource + * + * @default - No config + */ + readonly lambdaConfig?: CfnDataSource.LambdaConfigProperty | IResolvable; + /** + * configuration for RDS Datasource + * + * @default - No config + */ + readonly relationalDatabaseConfig?: CfnDataSource.RelationalDatabaseConfigProperty | IResolvable; +} + +/** + * Abstract AppSync datasource implementation. Do not use directly but use subclasses for concrete datasources + */ +export abstract class BaseDataSource extends Construct { + /** + * the name of the data source + */ + public readonly name: string; + /** + * the underlying CFN data source resource + */ + public readonly ds: CfnDataSource; + + protected api: GraphQLApi; + protected serviceRole?: IRole; + + constructor(scope: Construct, id: string, props: BackedDataSourceProps, extended: ExtendedDataSourceProps) { + super(scope, id); + + if (extended.type !== 'NONE') { + this.serviceRole = props.serviceRole || new Role(this, 'ServiceRole', { assumedBy: new ServicePrincipal('appsync') }); + } + + this.ds = new CfnDataSource(this, 'Resource', { + apiId: props.api.apiId, + name: props.name, + description: props.description, + serviceRoleArn: this.serviceRole?.roleArn, + ...extended, + }); + this.name = props.name; + this.api = props.api; + } + + /** + * creates a new resolver for this datasource and API using the given properties + */ + public createResolver(props: BaseResolverProps): Resolver { + return new Resolver(this, `${props.typeName}${props.fieldName}Resolver`, { + api: this.api, + dataSource: this, + ...props, + }); + } +} + +/** + * Abstract AppSync datasource implementation. Do not use directly but use subclasses for resource backed datasources + */ +export abstract class BackedDataSource extends BaseDataSource implements IGrantable { + /** + * the principal of the data source to be IGrantable + */ + public readonly grantPrincipal: IPrincipal; + + constructor(scope: Construct, id: string, props: BackedDataSourceProps, extended: ExtendedDataSourceProps) { + super(scope, id, props, extended); + + this.grantPrincipal = this.serviceRole!; + } +} + +/** + * Properties for an AppSync dummy datasource + */ +export interface NoneDataSourceProps extends BaseDataSourceProps { +} + +/** + * An AppSync dummy datasource + */ +export class NoneDataSource extends BaseDataSource { + constructor(scope: Construct, id: string, props: NoneDataSourceProps) { + super(scope, id, props, { + type: 'NONE', + }); + } +} + +/** + * Properties for an AppSync DynamoDB datasource + */ +export interface DynamoDbDataSourceProps extends BackedDataSourceProps { + /** + * The DynamoDB table backing this data source + * [disable-awslint:ref-via-interface] + */ + readonly table: ITable; + /** + * Specify whether this DS is read only or has read and write permissions to the DynamoDB table + * + * @default false + */ + readonly readOnlyAccess?: boolean; + /** + * use credentials of caller to access DynamoDB + * + * @default false + */ + readonly useCallerCredentials?: boolean; +} + +/** + * An AppSync datasource backed by a DynamoDB table + */ +export class DynamoDbDataSource extends BackedDataSource { + constructor(scope: Construct, id: string, props: DynamoDbDataSourceProps) { + super(scope, id, props, { + type: 'AMAZON_DYNAMODB', + dynamoDbConfig: { + tableName: props.table.tableName, + awsRegion: props.table.stack.region, + useCallerCredentials: props.useCallerCredentials, + }, + }); + if (props.readOnlyAccess) { + props.table.grantReadData(this); + } else { + props.table.grantReadWriteData(this); + } + } +} + +/** + * Properties for an AppSync http datasource + */ +export interface HttpDataSourceProps extends BaseDataSourceProps { + /** + * The http endpoint + */ + readonly endpoint: string; +} + +/** + * An AppSync datasource backed by a http endpoint + */ +export class HttpDataSource extends BaseDataSource { + constructor(scope: Construct, id: string, props: HttpDataSourceProps) { + super(scope, id, props, { + httpConfig: { + endpoint: props.endpoint, + }, + type: 'HTTP', + }); + } +} + +/** + * Properties for an AppSync Lambda datasource + */ +export interface LambdaDataSourceProps extends BackedDataSourceProps { + /** + * The Lambda function to call to interact with this data source + */ + readonly lambdaFunction: IFunction; +} + +/** + * An AppSync datasource backed by a Lambda function + */ +export class LambdaDataSource extends BackedDataSource { + constructor(scope: Construct, id: string, props: LambdaDataSourceProps) { + super(scope, id, props, { + type: 'AWS_LAMBDA', + lambdaConfig: { + lambdaFunctionArn: props.lambdaFunction.functionArn, + }, + }); + props.lambdaFunction.grantInvoke(this); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index bbc92625c5749..c01883c81b011 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -1,9 +1,6 @@ import { IUserPool } from '@aws-cdk/aws-cognito'; -import { Table } from '@aws-cdk/aws-dynamodb'; +import { ITable } from '@aws-cdk/aws-dynamodb'; import { - IGrantable, - IPrincipal, - IRole, ManagedPolicy, Role, ServicePrincipal, @@ -13,11 +10,10 @@ import { Construct, Duration, IResolvable } from '@aws-cdk/core'; import { readFileSync } from 'fs'; import { CfnApiKey, - CfnDataSource, CfnGraphQLApi, CfnGraphQLSchema, - CfnResolver, } from './appsync.generated'; +import { DynamoDbDataSource, HttpDataSource, LambdaDataSource, NoneDataSource } from './data-source'; /** * enum with all possible values for AppSync authorization type @@ -345,9 +341,9 @@ export class GraphQLApi extends Construct { if ( defaultAuthorizationType === AuthorizationType.API_KEY || - props.authorizationConfig?.additionalAuthorizationModes?.findIndex( + props.authorizationConfig?.additionalAuthorizationModes?.some( (authMode) => authMode.authorizationType === AuthorizationType.API_KEY - ) !== -1 + ) ) { const apiKeyConfig: ApiKeyConfig = props.authorizationConfig ?.defaultAuthorization?.apiKeyConfig || { @@ -393,7 +389,7 @@ export class GraphQLApi extends Construct { public addDynamoDbDataSource( name: string, description: string, - table: Table, + table: ITable, ): DynamoDbDataSource { return new DynamoDbDataSource(this, `${name}DS`, { api: this, @@ -570,817 +566,3 @@ export class GraphQLApi extends Construct { return authModes ? this.formatAdditionalAuthorizationModes(authModes) : undefined; } } - -/** - * Base properties for an AppSync datasource - */ -export interface BaseDataSourceProps { - /** - * The API to attach this data source to - */ - readonly api: GraphQLApi; - /** - * The name of the data source - */ - readonly name: string; - /** - * the description of the data source - * - * @default - None - */ - readonly description?: string; -} - -/** - * properties for an AppSync datasource backed by a resource - */ -export interface BackedDataSourceProps extends BaseDataSourceProps { - /** - * The IAM service role to be assumed by AppSync to interact with the data source - * - * @default - Create a new role - */ - readonly serviceRole?: IRole; -} - -/** - * props used by implementations of BaseDataSource to provide configuration. Should not be used directly. - */ -export interface ExtendedDataSourceProps { - /** - * the type of the AppSync datasource - */ - readonly type: string; - /** - * configuration for DynamoDB Datasource - * - * @default - No config - */ - readonly dynamoDbConfig?: CfnDataSource.DynamoDBConfigProperty | IResolvable; - /** - * configuration for Elasticsearch Datasource - * - * @default - No config - */ - readonly elasticsearchConfig?: CfnDataSource.ElasticsearchConfigProperty | IResolvable; - /** - * configuration for HTTP Datasource - * - * @default - No config - */ - readonly httpConfig?: CfnDataSource.HttpConfigProperty | IResolvable; - /** - * configuration for Lambda Datasource - * - * @default - No config - */ - readonly lambdaConfig?: CfnDataSource.LambdaConfigProperty | IResolvable; - /** - * configuration for RDS Datasource - * - * @default - No config - */ - readonly relationalDatabaseConfig?: CfnDataSource.RelationalDatabaseConfigProperty | IResolvable; -} - -/** - * Abstract AppSync datasource implementation. Do not use directly but use subclasses for concrete datasources - */ -export abstract class BaseDataSource extends Construct { - /** - * the name of the data source - */ - public readonly name: string; - /** - * the underlying CFN data source resource - */ - public readonly ds: CfnDataSource; - - protected api: GraphQLApi; - protected serviceRole?: IRole; - - constructor(scope: Construct, id: string, props: BackedDataSourceProps, extended: ExtendedDataSourceProps) { - super(scope, id); - - if (extended.type !== 'NONE') { - this.serviceRole = props.serviceRole || new Role(this, 'ServiceRole', { assumedBy: new ServicePrincipal('appsync') }); - } - - this.ds = new CfnDataSource(this, 'Resource', { - apiId: props.api.apiId, - name: props.name, - description: props.description, - serviceRoleArn: this.serviceRole?.roleArn, - ...extended, - }); - this.name = props.name; - this.api = props.api; - } - - /** - * creates a new resolver for this datasource and API using the given properties - */ - public createResolver(props: BaseResolverProps): Resolver { - return new Resolver(this, `${props.typeName}${props.fieldName}Resolver`, { - api: this.api, - dataSource: this, - ...props, - }); - } -} - -/** - * Abstract AppSync datasource implementation. Do not use directly but use subclasses for resource backed datasources - */ -export abstract class BackedDataSource extends BaseDataSource implements IGrantable { - /** - * the principal of the data source to be IGrantable - */ - public readonly grantPrincipal: IPrincipal; - - constructor(scope: Construct, id: string, props: BackedDataSourceProps, extended: ExtendedDataSourceProps) { - super(scope, id, props, extended); - - this.grantPrincipal = this.serviceRole!; - } -} - -/** - * Properties for an AppSync dummy datasource - */ -export interface NoneDataSourceProps extends BaseDataSourceProps { -} - -/** - * An AppSync dummy datasource - */ -export class NoneDataSource extends BaseDataSource { - constructor(scope: Construct, id: string, props: NoneDataSourceProps) { - super(scope, id, props, { - type: 'NONE', - }); - } -} - -/** - * Properties for an AppSync DynamoDB datasource - */ -export interface DynamoDbDataSourceProps extends BackedDataSourceProps { - /** - * The DynamoDB table backing this data source - * [disable-awslint:ref-via-interface] - */ - readonly table: Table; - /** - * Specify whether this DS is read only or has read and write permissions to the DynamoDB table - * - * @default false - */ - readonly readOnlyAccess?: boolean; - /** - * use credentials of caller to access DynamoDB - * - * @default false - */ - readonly useCallerCredentials?: boolean; -} - -/** - * An AppSync datasource backed by a DynamoDB table - */ -export class DynamoDbDataSource extends BackedDataSource { - constructor(scope: Construct, id: string, props: DynamoDbDataSourceProps) { - super(scope, id, props, { - type: 'AMAZON_DYNAMODB', - dynamoDbConfig: { - tableName: props.table.tableName, - awsRegion: props.table.stack.region, - useCallerCredentials: props.useCallerCredentials, - }, - }); - if (props.readOnlyAccess) { - props.table.grantReadData(this); - } else { - props.table.grantReadWriteData(this); - } - } -} - -/** - * Properties for an AppSync http datasource - */ -export interface HttpDataSourceProps extends BaseDataSourceProps { - /** - * The http endpoint - */ - readonly endpoint: string; -} - -/** - * An AppSync datasource backed by a http endpoint - */ -export class HttpDataSource extends BaseDataSource { - constructor(scope: Construct, id: string, props: HttpDataSourceProps) { - super(scope, id, props, { - httpConfig: { - endpoint: props.endpoint, - }, - type: 'HTTP', - }); - } -} - -/** - * Properties for an AppSync Lambda datasource - */ -export interface LambdaDataSourceProps extends BackedDataSourceProps { - /** - * The Lambda function to call to interact with this data source - */ - readonly lambdaFunction: IFunction; -} - -/** - * An AppSync datasource backed by a Lambda function - */ -export class LambdaDataSource extends BackedDataSource { - constructor(scope: Construct, id: string, props: LambdaDataSourceProps) { - super(scope, id, props, { - type: 'AWS_LAMBDA', - lambdaConfig: { - lambdaFunctionArn: props.lambdaFunction.functionArn, - }, - }); - props.lambdaFunction.grantInvoke(this); - } -} - -function concatAndDedup(left: T[], right: T[]): T[] { - return left.concat(right).filter((elem, index, self) => { - return index === self.indexOf(elem); - }); -} - -/** - * Utility class to represent DynamoDB key conditions. - */ -abstract class BaseKeyCondition { - public and(cond: BaseKeyCondition): BaseKeyCondition { - return new (class extends BaseKeyCondition { - constructor(private readonly left: BaseKeyCondition, private readonly right: BaseKeyCondition) { - super(); - } - - public renderCondition(): string { - return `${this.left.renderCondition()} AND ${this.right.renderCondition()}`; - } - - public keyNames(): string[] { - return concatAndDedup(this.left.keyNames(), this.right.keyNames()); - } - - public args(): string[] { - return concatAndDedup(this.left.args(), this.right.args()); - } - })(this, cond); - } - - public renderExpressionNames(): string { - return this.keyNames() - .map((keyName: string) => { - return `"#${keyName}" : "${keyName}"`; - }) - .join(', '); - } - - public renderExpressionValues(): string { - return this.args() - .map((arg: string) => { - return `":${arg}" : $util.dynamodb.toDynamoDBJson($ctx.args.${arg})`; - }) - .join(', '); - } - - public abstract renderCondition(): string; - public abstract keyNames(): string[]; - public abstract args(): string[]; -} - -/** - * Utility class to represent DynamoDB "begins_with" key conditions. - */ -class BeginsWith extends BaseKeyCondition { - constructor(private readonly keyName: string, private readonly arg: string) { - super(); - } - - public renderCondition(): string { - return `begins_with(#${this.keyName}, :${this.arg})`; - } - - public keyNames(): string[] { - return [this.keyName]; - } - - public args(): string[] { - return [this.arg]; - } -} - -/** - * Utility class to represent DynamoDB binary key conditions. - */ -class BinaryCondition extends BaseKeyCondition { - constructor(private readonly keyName: string, private readonly op: string, private readonly arg: string) { - super(); - } - - public renderCondition(): string { - return `#${this.keyName} ${this.op} :${this.arg}`; - } - - public keyNames(): string[] { - return [this.keyName]; - } - - public args(): string[] { - return [this.arg]; - } -} - -/** - * Utility class to represent DynamoDB "between" key conditions. - */ -class Between extends BaseKeyCondition { - constructor(private readonly keyName: string, private readonly arg1: string, private readonly arg2: string) { - super(); - } - - public renderCondition(): string { - return `#${this.keyName} BETWEEN :${this.arg1} AND :${this.arg2}`; - } - - public keyNames(): string[] { - return [this.keyName]; - } - - public args(): string[] { - return [this.arg1, this.arg2]; - } -} - -/** - * Factory class for DynamoDB key conditions. - */ -export class KeyCondition { - - /** - * Condition k = arg, true if the key attribute k is equal to the Query argument - */ - public static eq(keyName: string, arg: string): KeyCondition { - return new KeyCondition(new BinaryCondition(keyName, '=', arg)); - } - - /** - * Condition k < arg, true if the key attribute k is less than the Query argument - */ - public static lt(keyName: string, arg: string): KeyCondition { - return new KeyCondition(new BinaryCondition(keyName, '<', arg)); - } - - /** - * Condition k <= arg, true if the key attribute k is less than or equal to the Query argument - */ - public static le(keyName: string, arg: string): KeyCondition { - return new KeyCondition(new BinaryCondition(keyName, '<=', arg)); - } - - /** - * Condition k > arg, true if the key attribute k is greater than the the Query argument - */ - public static gt(keyName: string, arg: string): KeyCondition { - return new KeyCondition(new BinaryCondition(keyName, '>', arg)); - } - - /** - * Condition k >= arg, true if the key attribute k is greater or equal to the Query argument - */ - public static ge(keyName: string, arg: string): KeyCondition { - return new KeyCondition(new BinaryCondition(keyName, '>=', arg)); - } - - /** - * Condition (k, arg). True if the key attribute k begins with the Query argument. - */ - public static beginsWith(keyName: string, arg: string): KeyCondition { - return new KeyCondition(new BeginsWith(keyName, arg)); - } - - /** - * Condition k BETWEEN arg1 AND arg2, true if k >= arg1 and k <= arg2. - */ - public static between(keyName: string, arg1: string, arg2: string): KeyCondition { - return new KeyCondition(new Between(keyName, arg1, arg2)); - } - - private constructor(private readonly cond: BaseKeyCondition) { } - - /** - * Conjunction between two conditions. - */ - public and(keyCond: KeyCondition): KeyCondition { - return new KeyCondition(this.cond.and(keyCond.cond)); - } - - /** - * Renders the key condition to a VTL string. - */ - public renderTemplate(): string { - return `"query" : { - "expression" : "${this.cond.renderCondition()}", - "expressionNames" : { - ${this.cond.renderExpressionNames()} - }, - "expressionValues" : { - ${this.cond.renderExpressionValues()} - } - }`; - } -} - -/** - * Utility class representing the assigment of a value to an attribute. - */ -export class Assign { - constructor(private readonly attr: string, private readonly arg: string) { } - - /** - * Renders the assignment as a VTL string. - */ - public renderAsAssignment(): string { - return `"${this.attr}" : $util.dynamodb.toDynamoDBJson(${this.arg})`; - } - - /** - * Renders the assignment as a map element. - */ - public putInMap(map: string): string { - return `$util.qr($${map}.put("${this.attr}", ${this.arg}))`; - } -} - -/** - * Utility class to allow assigning a value or an auto-generated id - * to a partition key. - */ -export class PartitionKeyStep { - constructor(private readonly key: string) { } - - /** - * Assign an auto-generated value to the partition key. - */ - public is(val: string): PartitionKey { - return new PartitionKey(new Assign(this.key, `$ctx.args.${val}`)); - } - - /** - * Assign an auto-generated value to the partition key. - */ - public auto(): PartitionKey { - return new PartitionKey(new Assign(this.key, '$util.autoId()')); - } -} - -/** - * Utility class to allow assigning a value or an auto-generated id - * to a sort key. - */ -export class SortKeyStep { - constructor(private readonly pkey: Assign, private readonly skey: string) { } - - /** - * Assign an auto-generated value to the sort key. - */ - public is(val: string): PrimaryKey { - return new PrimaryKey(this.pkey, new Assign(this.skey, `$ctx.args.${val}`)); - } - - /** - * Assign an auto-generated value to the sort key. - */ - public auto(): PrimaryKey { - return new PrimaryKey(this.pkey, new Assign(this.skey, '$util.autoId()')); - } -} - -/** - * Specifies the assignment to the primary key. It either - * contains the full primary key or only the partition key. - */ -export class PrimaryKey { - /** - * Allows assigning a value to the partition key. - */ - public static partition(key: string): PartitionKeyStep { - return new PartitionKeyStep(key); - } - - constructor(protected readonly pkey: Assign, private readonly skey?: Assign) { } - - /** - * Renders the key assignment to a VTL string. - */ - public renderTemplate(): string { - const assignments = [this.pkey.renderAsAssignment()]; - if (this.skey) { - assignments.push(this.skey.renderAsAssignment()); - } - return `"key" : { - ${assignments.join(',')} - }`; - } -} - -/** - * Specifies the assignment to the partition key. It can be - * enhanced with the assignment of the sort key. - */ -export class PartitionKey extends PrimaryKey { - constructor(pkey: Assign) { - super(pkey); - } - - /** - * Allows assigning a value to the sort key. - */ - public sort(key: string): SortKeyStep { - return new SortKeyStep(this.pkey, key); - } -} - -/** - * Specifies the attribute value assignments. - */ -export class AttributeValues { - constructor(private readonly container: string, private readonly assignments: Assign[] = []) { } - - /** - * Allows assigning a value to the specified attribute. - */ - public attribute(attr: string): AttributeValuesStep { - return new AttributeValuesStep(attr, this.container, this.assignments); - } - - /** - * Renders the variables required for `renderTemplate`. - */ - public renderVariables(): string { - return `#set($input = ${this.container}) - ${this.assignments.map(a => a.putInMap('input')).join('\n')}`; - } - - /** - * Renders the attribute value assingments to a VTL string. - */ - public renderTemplate(): string { - return '"attributeValues": $util.dynamodb.toMapValuesJson($input)'; - } -} - -/** - * Utility class to allow assigning a value to an attribute. - */ -export class AttributeValuesStep { - constructor(private readonly attr: string, private readonly container: string, private readonly assignments: Assign[]) { } - - /** - * Assign the value to the current attribute. - */ - public is(val: string): AttributeValues { - this.assignments.push(new Assign(this.attr, val)); - return new AttributeValues(this.container, this.assignments); - } -} - -/** - * Factory class for attribute value assignments. - */ -export class Values { - /** - * Treats the specified object as a map of assignments, where the property - * names represent attribute names. It’s opinionated about how it represents - * some of the nested objects: e.g., it will use lists (“L”) rather than sets - * (“SS”, “NS”, “BS”). By default it projects the argument container ("$ctx.args"). - */ - public static projecting(arg?: string): AttributeValues { - return new AttributeValues('$ctx.args' + (arg ? `.${arg}` : '')); - } - - /** - * Allows assigning a value to the specified attribute. - */ - public static attribute(attr: string): AttributeValuesStep { - return new AttributeValues('{}').attribute(attr); - } -} - -/** - * MappingTemplates for AppSync resolvers - */ -export abstract class MappingTemplate { - - /** - * Create a mapping template from the given string - */ - public static fromString(template: string): MappingTemplate { - return new StringMappingTemplate(template); - } - - /** - * Create a mapping template from the given file - */ - public static fromFile(fileName: string): MappingTemplate { - return new StringMappingTemplate(readFileSync(fileName).toString('UTF-8')); - } - - /** - * Mapping template for a result list from DynamoDB - */ - public static dynamoDbResultList(): MappingTemplate { - return this.fromString('$util.toJson($ctx.result.items)'); - } - - /** - * Mapping template for a single result item from DynamoDB - */ - public static dynamoDbResultItem(): MappingTemplate { - return this.fromString('$util.toJson($ctx.result)'); - } - - /** - * Mapping template to scan a DynamoDB table to fetch all entries - */ - public static dynamoDbScanTable(): MappingTemplate { - return this.fromString('{"version" : "2017-02-28", "operation" : "Scan"}'); - } - - /** - * Mapping template to query a set of items from a DynamoDB table - * - * @param cond the key condition for the query - */ - public static dynamoDbQuery(cond: KeyCondition): MappingTemplate { - return this.fromString(`{"version" : "2017-02-28", "operation" : "Query", ${cond.renderTemplate()}}`); - } - - /** - * Mapping template to get a single item from a DynamoDB table - * - * @param keyName the name of the hash key field - * @param idArg the name of the Query argument - */ - public static dynamoDbGetItem(keyName: string, idArg: string): MappingTemplate { - return this.fromString(`{"version": "2017-02-28", "operation": "GetItem", "key": {"${keyName}": $util.dynamodb.toDynamoDBJson($ctx.args.${idArg})}}`); - } - - /** - * Mapping template to delete a single item from a DynamoDB table - * - * @param keyName the name of the hash key field - * @param idArg the name of the Mutation argument - */ - public static dynamoDbDeleteItem(keyName: string, idArg: string): MappingTemplate { - return this.fromString(`{"version": "2017-02-28", "operation": "DeleteItem", "key": {"${keyName}": $util.dynamodb.toDynamoDBJson($ctx.args.${idArg})}}`); - } - - /** - * Mapping template to save a single item to a DynamoDB table - * - * @param key the assigment of Mutation values to the primary key - * @param values the assignment of Mutation values to the table attributes - */ - public static dynamoDbPutItem(key: PrimaryKey, values: AttributeValues): MappingTemplate { - return this.fromString(` - ${values.renderVariables()} - { - "version": "2017-02-28", - "operation": "PutItem", - ${key.renderTemplate()}, - ${values.renderTemplate()} - }`); - } - - /** - * Mapping template to invoke a Lambda function - * - * @param payload the VTL template snippet of the payload to send to the lambda. - * If no payload is provided all available context fields are sent to the Lambda function - */ - public static lambdaRequest(payload: string = '$util.toJson($ctx)'): MappingTemplate { - return this.fromString(`{"version": "2017-02-28", "operation": "Invoke", "payload": ${payload}}`); - } - - /** - * Mapping template to return the Lambda result to the caller - */ - public static lambdaResult(): MappingTemplate { - return this.fromString('$util.toJson($ctx.result)'); - } - - /** - * this is called to render the mapping template to a VTL string - */ - public abstract renderTemplate(): string; - -} - -class StringMappingTemplate extends MappingTemplate { - - constructor(private readonly template: string) { - super(); - } - - public renderTemplate() { - return this.template; - } -} - -/** - * Basic properties for an AppSync resolver - */ -export interface BaseResolverProps { - /** - * name of the GraphQL type this resolver is attached to - */ - readonly typeName: string; - /** - * name of the GraphQL fiel din the given type this resolver is attached to - */ - readonly fieldName: string; - /** - * configuration of the pipeline resolver - * - * @default - create a UNIT resolver - */ - readonly pipelineConfig?: CfnResolver.PipelineConfigProperty | IResolvable; - /** - * The request mapping template for this resolver - * - * @default - No mapping template - */ - readonly requestMappingTemplate?: MappingTemplate; - /** - * The response mapping template for this resolver - * - * @default - No mapping template - */ - readonly responseMappingTemplate?: MappingTemplate; -} - -/** - * Additional properties for an AppSync resolver like GraphQL API reference and datasource - */ -export interface ResolverProps extends BaseResolverProps { - /** - * The API this resolver is attached to - */ - readonly api: GraphQLApi; - /** - * The data source this resolver is using - * - * @default - No datasource - */ - readonly dataSource?: BaseDataSource; -} - -/** - * An AppSync resolver - */ -export class Resolver extends Construct { - - /** - * the ARN of the resolver - */ - public readonly arn: string; - - private resolver: CfnResolver; - - constructor(scope: Construct, id: string, props: ResolverProps) { - super(scope, id); - - this.resolver = new CfnResolver(this, 'Resource', { - apiId: props.api.apiId, - typeName: props.typeName, - fieldName: props.fieldName, - dataSourceName: props.dataSource ? props.dataSource.name : undefined, - kind: props.pipelineConfig ? 'PIPELINE' : 'UNIT', - requestMappingTemplate: props.requestMappingTemplate ? props.requestMappingTemplate.renderTemplate() : undefined, - responseMappingTemplate: props.responseMappingTemplate ? props.responseMappingTemplate.renderTemplate() : undefined, - }); - this.resolver.addDependsOn(props.api.schema); - if (props.dataSource) { - this.resolver.addDependsOn(props.dataSource.ds); - } - this.arn = this.resolver.attrResolverArn; - } -} diff --git a/packages/@aws-cdk/aws-appsync/lib/index.ts b/packages/@aws-cdk/aws-appsync/lib/index.ts index 9df17ffda669d..852cbed1c54f6 100644 --- a/packages/@aws-cdk/aws-appsync/lib/index.ts +++ b/packages/@aws-cdk/aws-appsync/lib/index.ts @@ -1,3 +1,7 @@ // AWS::AppSync CloudFormation Resources: export * from './appsync.generated'; +export * from './key'; +export * from './data-source'; +export * from './mapping-template'; +export * from './resolver'; export * from './graphqlapi'; diff --git a/packages/@aws-cdk/aws-appsync/lib/key.ts b/packages/@aws-cdk/aws-appsync/lib/key.ts new file mode 100644 index 0000000000000..7295a8d6bd032 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/key.ts @@ -0,0 +1,256 @@ +import { BaseKeyCondition, BeginsWith, Between, BinaryCondition } from './private'; + +/** + * Factory class for DynamoDB key conditions. + */ +export class KeyCondition { + + /** + * Condition k = arg, true if the key attribute k is equal to the Query argument + */ + public static eq(keyName: string, arg: string): KeyCondition { + return new KeyCondition(new BinaryCondition(keyName, '=', arg)); + } + + /** + * Condition k < arg, true if the key attribute k is less than the Query argument + */ + public static lt(keyName: string, arg: string): KeyCondition { + return new KeyCondition(new BinaryCondition(keyName, '<', arg)); + } + + /** + * Condition k <= arg, true if the key attribute k is less than or equal to the Query argument + */ + public static le(keyName: string, arg: string): KeyCondition { + return new KeyCondition(new BinaryCondition(keyName, '<=', arg)); + } + + /** + * Condition k > arg, true if the key attribute k is greater than the the Query argument + */ + public static gt(keyName: string, arg: string): KeyCondition { + return new KeyCondition(new BinaryCondition(keyName, '>', arg)); + } + + /** + * Condition k >= arg, true if the key attribute k is greater or equal to the Query argument + */ + public static ge(keyName: string, arg: string): KeyCondition { + return new KeyCondition(new BinaryCondition(keyName, '>=', arg)); + } + + /** + * Condition (k, arg). True if the key attribute k begins with the Query argument. + */ + public static beginsWith(keyName: string, arg: string): KeyCondition { + return new KeyCondition(new BeginsWith(keyName, arg)); + } + + /** + * Condition k BETWEEN arg1 AND arg2, true if k >= arg1 and k <= arg2. + */ + public static between(keyName: string, arg1: string, arg2: string): KeyCondition { + return new KeyCondition(new Between(keyName, arg1, arg2)); + } + + private constructor(private readonly cond: BaseKeyCondition) { } + + /** + * Conjunction between two conditions. + */ + public and(keyCond: KeyCondition): KeyCondition { + return new KeyCondition(this.cond.and(keyCond.cond)); + } + + /** + * Renders the key condition to a VTL string. + */ + public renderTemplate(): string { + return `"query" : { + "expression" : "${this.cond.renderCondition()}", + "expressionNames" : { + ${this.cond.renderExpressionNames()} + }, + "expressionValues" : { + ${this.cond.renderExpressionValues()} + } + }`; + } +} + +/** + * Utility class representing the assigment of a value to an attribute. + */ +export class Assign { + constructor(private readonly attr: string, private readonly arg: string) { } + + /** + * Renders the assignment as a VTL string. + */ + public renderAsAssignment(): string { + return `"${this.attr}" : $util.dynamodb.toDynamoDBJson(${this.arg})`; + } + + /** + * Renders the assignment as a map element. + */ + public putInMap(map: string): string { + return `$util.qr($${map}.put("${this.attr}", ${this.arg}))`; + } +} + +/** + * Utility class to allow assigning a value or an auto-generated id + * to a partition key. + */ +export class PartitionKeyStep { + constructor(private readonly key: string) { } + + /** + * Assign an auto-generated value to the partition key. + */ + public is(val: string): PartitionKey { + return new PartitionKey(new Assign(this.key, `$ctx.args.${val}`)); + } + + /** + * Assign an auto-generated value to the partition key. + */ + public auto(): PartitionKey { + return new PartitionKey(new Assign(this.key, '$util.autoId()')); + } +} + +/** + * Utility class to allow assigning a value or an auto-generated id + * to a sort key. + */ +export class SortKeyStep { + constructor(private readonly pkey: Assign, private readonly skey: string) { } + + /** + * Assign an auto-generated value to the sort key. + */ + public is(val: string): PrimaryKey { + return new PrimaryKey(this.pkey, new Assign(this.skey, `$ctx.args.${val}`)); + } + + /** + * Assign an auto-generated value to the sort key. + */ + public auto(): PrimaryKey { + return new PrimaryKey(this.pkey, new Assign(this.skey, '$util.autoId()')); + } +} + +/** + * Specifies the assignment to the primary key. It either + * contains the full primary key or only the partition key. + */ +export class PrimaryKey { + /** + * Allows assigning a value to the partition key. + */ + public static partition(key: string): PartitionKeyStep { + return new PartitionKeyStep(key); + } + + constructor(protected readonly pkey: Assign, private readonly skey?: Assign) { } + + /** + * Renders the key assignment to a VTL string. + */ + public renderTemplate(): string { + const assignments = [this.pkey.renderAsAssignment()]; + if (this.skey) { + assignments.push(this.skey.renderAsAssignment()); + } + return `"key" : { + ${assignments.join(',')} + }`; + } +} + +/** + * Specifies the assignment to the partition key. It can be + * enhanced with the assignment of the sort key. + */ +export class PartitionKey extends PrimaryKey { + constructor(pkey: Assign) { + super(pkey); + } + + /** + * Allows assigning a value to the sort key. + */ + public sort(key: string): SortKeyStep { + return new SortKeyStep(this.pkey, key); + } +} + +/** + * Specifies the attribute value assignments. + */ +export class AttributeValues { + constructor(private readonly container: string, private readonly assignments: Assign[] = []) { } + + /** + * Allows assigning a value to the specified attribute. + */ + public attribute(attr: string): AttributeValuesStep { + return new AttributeValuesStep(attr, this.container, this.assignments); + } + + /** + * Renders the variables required for `renderTemplate`. + */ + public renderVariables(): string { + return `#set($input = ${this.container}) + ${this.assignments.map(a => a.putInMap('input')).join('\n')}`; + } + + /** + * Renders the attribute value assingments to a VTL string. + */ + public renderTemplate(): string { + return '"attributeValues": $util.dynamodb.toMapValuesJson($input)'; + } +} + +/** + * Utility class to allow assigning a value to an attribute. + */ +export class AttributeValuesStep { + constructor(private readonly attr: string, private readonly container: string, private readonly assignments: Assign[]) { } + + /** + * Assign the value to the current attribute. + */ + public is(val: string): AttributeValues { + this.assignments.push(new Assign(this.attr, val)); + return new AttributeValues(this.container, this.assignments); + } +} + +/** + * Factory class for attribute value assignments. + */ +export class Values { + /** + * Treats the specified object as a map of assignments, where the property + * names represent attribute names. It’s opinionated about how it represents + * some of the nested objects: e.g., it will use lists (“L”) rather than sets + * (“SS”, “NS”, “BS”). By default it projects the argument container ("$ctx.args"). + */ + public static projecting(arg?: string): AttributeValues { + return new AttributeValues('$ctx.args' + (arg ? `.${arg}` : '')); + } + + /** + * Allows assigning a value to the specified attribute. + */ + public static attribute(attr: string): AttributeValuesStep { + return new AttributeValues('{}').attribute(attr); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/mapping-template.ts b/packages/@aws-cdk/aws-appsync/lib/mapping-template.ts new file mode 100644 index 0000000000000..d4c0011c54342 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/mapping-template.ts @@ -0,0 +1,121 @@ +import { readFileSync } from 'fs'; +import { AttributeValues, KeyCondition, PrimaryKey } from './key'; + +/** + * MappingTemplates for AppSync resolvers + */ +export abstract class MappingTemplate { + /** + * Create a mapping template from the given string + */ + public static fromString(template: string): MappingTemplate { + return new StringMappingTemplate(template); + } + + /** + * Create a mapping template from the given file + */ + public static fromFile(fileName: string): MappingTemplate { + return new StringMappingTemplate(readFileSync(fileName).toString('UTF-8')); + } + + /** + * Mapping template for a result list from DynamoDB + */ + public static dynamoDbResultList(): MappingTemplate { + return this.fromString('$util.toJson($ctx.result.items)'); + } + + /** + * Mapping template for a single result item from DynamoDB + */ + public static dynamoDbResultItem(): MappingTemplate { + return this.fromString('$util.toJson($ctx.result)'); + } + + /** + * Mapping template to scan a DynamoDB table to fetch all entries + */ + public static dynamoDbScanTable(): MappingTemplate { + return this.fromString('{"version" : "2017-02-28", "operation" : "Scan"}'); + } + + /** + * Mapping template to query a set of items from a DynamoDB table + * + * @param cond the key condition for the query + */ + public static dynamoDbQuery(cond: KeyCondition): MappingTemplate { + return this.fromString(`{"version" : "2017-02-28", "operation" : "Query", ${cond.renderTemplate()}}`); + } + + /** + * Mapping template to get a single item from a DynamoDB table + * + * @param keyName the name of the hash key field + * @param idArg the name of the Query argument + */ + public static dynamoDbGetItem(keyName: string, idArg: string): MappingTemplate { + return this.fromString(`{"version": "2017-02-28", "operation": "GetItem", "key": {"${keyName}": $util.dynamodb.toDynamoDBJson($ctx.args.${idArg})}}`); + } + + /** + * Mapping template to delete a single item from a DynamoDB table + * + * @param keyName the name of the hash key field + * @param idArg the name of the Mutation argument + */ + public static dynamoDbDeleteItem(keyName: string, idArg: string): MappingTemplate { + return this.fromString(`{"version": "2017-02-28", "operation": "DeleteItem", "key": {"${keyName}": $util.dynamodb.toDynamoDBJson($ctx.args.${idArg})}}`); + } + + /** + * Mapping template to save a single item to a DynamoDB table + * + * @param key the assigment of Mutation values to the primary key + * @param values the assignment of Mutation values to the table attributes + */ + public static dynamoDbPutItem(key: PrimaryKey, values: AttributeValues): MappingTemplate { + return this.fromString(` + ${values.renderVariables()} + { + "version": "2017-02-28", + "operation": "PutItem", + ${key.renderTemplate()}, + ${values.renderTemplate()} + }`); + } + + /** + * Mapping template to invoke a Lambda function + * + * @param payload the VTL template snippet of the payload to send to the lambda. + * If no payload is provided all available context fields are sent to the Lambda function + */ + public static lambdaRequest(payload: string = '$util.toJson($ctx)'): MappingTemplate { + return this.fromString(`{"version": "2017-02-28", "operation": "Invoke", "payload": ${payload}}`); + } + + /** + * Mapping template to return the Lambda result to the caller + */ + public static lambdaResult(): MappingTemplate { + return this.fromString('$util.toJson($ctx.result)'); + } + + /** + * this is called to render the mapping template to a VTL string + */ + public abstract renderTemplate(): string; +} + +class StringMappingTemplate extends MappingTemplate { + + constructor(private readonly template: string) { + super(); + } + + public renderTemplate() { + return this.template; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/private.ts b/packages/@aws-cdk/aws-appsync/lib/private.ts new file mode 100644 index 0000000000000..9118b503349c3 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/private.ts @@ -0,0 +1,113 @@ +function concatAndDedup(left: T[], right: T[]): T[] { + return left.concat(right).filter((elem, index, self) => { + return index === self.indexOf(elem); + }); +} + +/** + * Utility class to represent DynamoDB key conditions. + */ +export abstract class BaseKeyCondition { + public and(cond: BaseKeyCondition): BaseKeyCondition { + return new (class extends BaseKeyCondition { + constructor(private readonly left: BaseKeyCondition, private readonly right: BaseKeyCondition) { + super(); + } + + public renderCondition(): string { + return `${this.left.renderCondition()} AND ${this.right.renderCondition()}`; + } + + public keyNames(): string[] { + return concatAndDedup(this.left.keyNames(), this.right.keyNames()); + } + + public args(): string[] { + return concatAndDedup(this.left.args(), this.right.args()); + } + })(this, cond); + } + + public renderExpressionNames(): string { + return this.keyNames() + .map((keyName: string) => { + return `"#${keyName}" : "${keyName}"`; + }) + .join(', '); + } + + public renderExpressionValues(): string { + return this.args() + .map((arg: string) => { + return `":${arg}" : $util.dynamodb.toDynamoDBJson($ctx.args.${arg})`; + }) + .join(', '); + } + + public abstract renderCondition(): string; + public abstract keyNames(): string[]; + public abstract args(): string[]; +} + +/** + * Utility class to represent DynamoDB "begins_with" key conditions. + */ +export class BeginsWith extends BaseKeyCondition { + constructor(private readonly keyName: string, private readonly arg: string) { + super(); + } + + public renderCondition(): string { + return `begins_with(#${this.keyName}, :${this.arg})`; + } + + public keyNames(): string[] { + return [this.keyName]; + } + + public args(): string[] { + return [this.arg]; + } +} + +/** + * Utility class to represent DynamoDB binary key conditions. + */ +export class BinaryCondition extends BaseKeyCondition { + constructor(private readonly keyName: string, private readonly op: string, private readonly arg: string) { + super(); + } + + public renderCondition(): string { + return `#${this.keyName} ${this.op} :${this.arg}`; + } + + public keyNames(): string[] { + return [this.keyName]; + } + + public args(): string[] { + return [this.arg]; + } +} + +/** + * Utility class to represent DynamoDB "between" key conditions. + */ +export class Between extends BaseKeyCondition { + constructor(private readonly keyName: string, private readonly arg1: string, private readonly arg2: string) { + super(); + } + + public renderCondition(): string { + return `#${this.keyName} BETWEEN :${this.arg1} AND :${this.arg2}`; + } + + public keyNames(): string[] { + return [this.keyName]; + } + + public args(): string[] { + return [this.arg1, this.arg2]; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/resolver.ts b/packages/@aws-cdk/aws-appsync/lib/resolver.ts new file mode 100644 index 0000000000000..ef5e524ac75ae --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/resolver.ts @@ -0,0 +1,83 @@ +import { Construct, IResolvable } from '@aws-cdk/core'; +import { CfnResolver } from './appsync.generated'; +import { BaseDataSource } from './data-source'; +import { GraphQLApi } from './graphqlapi'; +import { MappingTemplate } from './mapping-template'; +/** + * Basic properties for an AppSync resolver + */ +export interface BaseResolverProps { + /** + * name of the GraphQL type this resolver is attached to + */ + readonly typeName: string; + /** + * name of the GraphQL fiel din the given type this resolver is attached to + */ + readonly fieldName: string; + /** + * configuration of the pipeline resolver + * + * @default - create a UNIT resolver + */ + readonly pipelineConfig?: CfnResolver.PipelineConfigProperty | IResolvable; + /** + * The request mapping template for this resolver + * + * @default - No mapping template + */ + readonly requestMappingTemplate?: MappingTemplate; + /** + * The response mapping template for this resolver + * + * @default - No mapping template + */ + readonly responseMappingTemplate?: MappingTemplate; +} + +/** + * Additional properties for an AppSync resolver like GraphQL API reference and datasource + */ +export interface ResolverProps extends BaseResolverProps { + /** + * The API this resolver is attached to + */ + readonly api: GraphQLApi; + /** + * The data source this resolver is using + * + * @default - No datasource + */ + readonly dataSource?: BaseDataSource; +} + +/** + * An AppSync resolver + */ +export class Resolver extends Construct { + /** + * the ARN of the resolver + */ + public readonly arn: string; + + private resolver: CfnResolver; + + constructor(scope: Construct, id: string, props: ResolverProps) { + super(scope, id); + + this.resolver = new CfnResolver(this, 'Resource', { + apiId: props.api.apiId, + typeName: props.typeName, + fieldName: props.fieldName, + dataSourceName: props.dataSource ? props.dataSource.name : undefined, + kind: props.pipelineConfig ? 'PIPELINE' : 'UNIT', + requestMappingTemplate: props.requestMappingTemplate ? props.requestMappingTemplate.renderTemplate() : undefined, + responseMappingTemplate: props.responseMappingTemplate ? props.responseMappingTemplate.renderTemplate() : undefined, + }); + this.resolver.addDependsOn(props.api.schema); + if (props.dataSource) { + this.resolver.addDependsOn(props.dataSource.ds); + } + this.arn = this.resolver.attrResolverArn; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts new file mode 100644 index 0000000000000..14b87e5eee463 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts @@ -0,0 +1,81 @@ +import '@aws-cdk/assert/jest'; +import * as cdk from '@aws-cdk/core'; +import * as path from 'path'; +import * as appsync from '../lib'; + +describe('AppSync Authorization Config', () => { + test('AppSync creates default api key', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schemaDefinitionFile: path.join(__dirname, 'schema.graphql'), + }); + + // THEN + expect(stack).toHaveResource('AWS::AppSync::ApiKey'); + }); + + test('AppSync creates api key from additionalAuthorizationModes', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schemaDefinitionFile: path.join(__dirname, 'schema.graphql'), + authorizationConfig: { + defaultAuthorization: { + authorizationType: appsync.AuthorizationType.IAM, + }, + additionalAuthorizationModes: [ + { authorizationType: appsync.AuthorizationType.API_KEY }, + ], + }, + }); + + // THEN + expect(stack).toHaveResource('AWS::AppSync::ApiKey'); + }); + + test('AppSync does not create unspecified api key from additionalAuthorizationModes', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schemaDefinitionFile: path.join(__dirname, 'schema.graphql'), + authorizationConfig: { + defaultAuthorization: { + authorizationType: appsync.AuthorizationType.IAM, + }, + }, + }); + + // THEN + expect(stack).not.toHaveResource('AWS::AppSync::ApiKey'); + }); + + test('appsync does not create unspecified api key with empty additionalAuthorizationModes', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schemaDefinitionFile: path.join(__dirname, 'schema.graphql'), + authorizationConfig: { + defaultAuthorization: { + authorizationType: appsync.AuthorizationType.IAM, + }, + additionalAuthorizationModes: [], + }, + }); + + // THEN + expect(stack).not.toHaveResource('AWS::AppSync::ApiKey'); + }); +}); diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json index 9fe51c84d0b28..dc19df3e4c395 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json @@ -42,8 +42,14 @@ "Properties": { "AccountRecoverySetting": { "RecoveryMechanisms": [ - { "Name": "verified_phone_number", "Priority": 1 }, - { "Name": "verified_email", "Priority": 2 } + { + "Name": "verified_phone_number", + "Priority": 1 + }, + { + "Name": "verified_email", + "Priority": 2 + } ] }, "AdminCreateUserConfig": { @@ -95,7 +101,10 @@ "Type": "AWS::AppSync::ApiKey", "Properties": { "ApiId": { - "Fn::GetAtt": ["ApiF70053CD", "ApiId"] + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] }, "Description": "Default API Key created by CDK" } @@ -104,9 +113,12 @@ "Type": "AWS::AppSync::GraphQLSchema", "Properties": { "ApiId": { - "Fn::GetAtt": ["ApiF70053CD", "ApiId"] + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] }, - "Definition": "type ServiceVersion {\n version: String!\n}\n\ntype Customer {\n id: String!\n name: String!\n}\n\ninput SaveCustomerInput {\n name: String!\n}\n\ntype Order {\n customer: String!\n order: String!\n}\n\ntype Query {\n getServiceVersion: ServiceVersion\n getCustomers: [Customer]\n getCustomer(id: String): Customer\n getCustomerOrdersEq(customer: String): Order\n getCustomerOrdersLt(customer: String): Order\n getCustomerOrdersLe(customer: String): Order\n getCustomerOrdersGt(customer: String): Order\n getCustomerOrdersGe(customer: String): Order\n getCustomerOrdersFilter(customer: String, order: String): Order\n getCustomerOrdersBetween(customer: String, order1: String, order2: String): Order\n}\n\ninput FirstOrderInput {\n product: String!\n quantity: Int!\n}\n\ntype Mutation {\n addCustomer(customer: SaveCustomerInput!): Customer\n saveCustomer(id: String!, customer: SaveCustomerInput!): Customer\n removeCustomer(id: String!): Customer\n saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order\n doPostOnAws: String!\n}\n" + "Definition": "type ServiceVersion @aws_api_key {\n version: String!\n}\n\ntype Customer @aws_api_key {\n id: String!\n name: String!\n}\n\ninput SaveCustomerInput {\n name: String!\n}\n\ntype Order @aws_api_key {\n customer: String!\n order: String!\n}\n\ntype Payment @aws_api_key {\n id: String!\n amount: String!\n}\n\ninput PaymentInput {\n amount: String!\n}\n\ntype Query @aws_api_key {\n getServiceVersion: ServiceVersion\n getCustomers: [Customer]\n getCustomer(id: String): Customer\n getCustomerOrdersEq(customer: String): Order\n getCustomerOrdersLt(customer: String): Order\n getCustomerOrdersLe(customer: String): Order\n getCustomerOrdersGt(customer: String): Order\n getCustomerOrdersGe(customer: String): Order\n getCustomerOrdersFilter(customer: String, order: String): Order\n getCustomerOrdersBetween(customer: String, order1: String, order2: String): Order\n getPayment(id: String): Payment\n}\n\ninput FirstOrderInput {\n product: String!\n quantity: Int!\n}\n\ntype Mutation @aws_api_key {\n addCustomer(customer: SaveCustomerInput!): Customer\n saveCustomer(id: String!, customer: SaveCustomerInput!): Customer\n removeCustomer(id: String!): Customer\n saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order\n savePayment(payment: PaymentInput!): Payment\n doPostOnAws: String!\n}\n" } }, "ApiNoneDSB4E6495F": { @@ -591,6 +603,147 @@ "ApiSchema510EECD7" ] }, + "ApiPaymentDSServiceRole7A857DD9": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "appsync.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ApiPaymentDSServiceRoleDefaultPolicy1BE875C5": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":dynamodb:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":table/PaymentTable" + ] + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ApiPaymentDSServiceRoleDefaultPolicy1BE875C5", + "Roles": [ + { + "Ref": "ApiPaymentDSServiceRole7A857DD9" + } + ] + } + }, + "ApiPaymentDS69022256": { + "Type": "AWS::AppSync::DataSource", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "Name": "Payment", + "Type": "AMAZON_DYNAMODB", + "Description": "The payment data source", + "DynamoDBConfig": { + "AwsRegion": { + "Ref": "AWS::Region" + }, + "TableName": "PaymentTable" + }, + "ServiceRoleArn": { + "Fn::GetAtt": [ + "ApiPaymentDSServiceRole7A857DD9", + "Arn" + ] + } + } + }, + "ApiPaymentDSQuerygetPaymentResolver25686F48": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "FieldName": "getPayment", + "TypeName": "Query", + "DataSourceName": "Payment", + "Kind": "UNIT", + "RequestMappingTemplate": "{\"version\": \"2017-02-28\", \"operation\": \"GetItem\", \"key\": {\"id\": $util.dynamodb.toDynamoDBJson($ctx.args.id)}}", + "ResponseMappingTemplate": "$util.toJson($ctx.result)" + }, + "DependsOn": [ + "ApiPaymentDS69022256", + "ApiSchema510EECD7" + ] + }, + "ApiPaymentDSMutationsavePaymentResolver08FBC62D": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "FieldName": "savePayment", + "TypeName": "Mutation", + "DataSourceName": "Payment", + "Kind": "UNIT", + "RequestMappingTemplate": "\n #set($input = $ctx.args.payment)\n \n {\n \"version\": \"2017-02-28\",\n \"operation\": \"PutItem\",\n \"key\" : {\n \"id\" : $util.dynamodb.toDynamoDBJson($util.autoId())\n },\n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }", + "ResponseMappingTemplate": "$util.toJson($ctx.result)" + }, + "DependsOn": [ + "ApiPaymentDS69022256", + "ApiSchema510EECD7" + ] + }, "ApihttpDSServiceRole8B5C9457": { "Type": "AWS::IAM::Role", "Properties": { @@ -699,6 +852,26 @@ }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" + }, + "PaymentTableE140D25E": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts index fb2be9eeac531..5ced7472fcb05 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts @@ -12,6 +12,20 @@ import { Values, } from '../lib'; +/* + * Creates an Appsync GraphQL API and with multiple tables. + * Testing for importing, querying, and mutability. + * + * Stack verification steps: + * Add to a table through appsync GraphQL API. + * Read from a table through appsync API. + * + * -- aws appsync list-graphql-apis -- obtain apiId -- + * -- aws appsync get-graphql-api --api-id [apiId] -- obtain GraphQL endpoint -- + * -- aws appsync list-api-keys --api-id [apiId] -- obtain api key -- + * -- bash verify.integ.graphql.sh [apiKey] [url] -- shows query and mutation -- + */ + const app = new App(); const stack = new Stack(app, 'aws-appsync-integ'); @@ -72,8 +86,20 @@ const orderTable = new Table(stack, 'OrderTable', { removalPolicy: RemovalPolicy.DESTROY, }); +new Table(stack, 'PaymentTable', { + billingMode: BillingMode.PAY_PER_REQUEST, + partitionKey: { + name: 'id', + type: AttributeType.STRING, + }, + removalPolicy: RemovalPolicy.DESTROY, +}); + +const paymentTable = Table.fromTableName(stack, 'ImportedPaymentTable', 'PaymentTable'); + const customerDS = api.addDynamoDbDataSource('Customer', 'The customer data source', customerTable); const orderDS = api.addDynamoDbDataSource('Order', 'The order data source', orderTable); +const paymentDS = api.addDynamoDbDataSource('Payment', 'The payment data source', paymentTable); customerDS.createResolver({ typeName: 'Query', @@ -148,6 +174,19 @@ orderDS.createResolver({ responseMappingTemplate: MappingTemplate.dynamoDbResultList(), }); +paymentDS.createResolver({ + typeName: 'Query', + fieldName: 'getPayment', + requestMappingTemplate: MappingTemplate.dynamoDbGetItem('id', 'id'), + responseMappingTemplate: MappingTemplate.dynamoDbResultItem(), +}); +paymentDS.createResolver({ + typeName: 'Mutation', + fieldName: 'savePayment', + requestMappingTemplate: MappingTemplate.dynamoDbPutItem(PrimaryKey.partition('id').auto(), Values.projecting('payment')), + responseMappingTemplate: MappingTemplate.dynamoDbResultItem(), +}); + const httpDS = api.addHttpDataSource('http', 'The http data source', 'https://aws.amazon.com/'); httpDS.createResolver({ diff --git a/packages/@aws-cdk/aws-appsync/test/schema.graphql b/packages/@aws-cdk/aws-appsync/test/schema.graphql index 24af9a154ec59..67576c3ed816c 100644 --- a/packages/@aws-cdk/aws-appsync/test/schema.graphql +++ b/packages/@aws-cdk/aws-appsync/test/schema.graphql @@ -1,43 +1,54 @@ -type ServiceVersion { - version: String! +type ServiceVersion @aws_api_key { + version: String! } -type Customer { - id: String! - name: String! +type Customer @aws_api_key { + id: String! + name: String! } input SaveCustomerInput { - name: String! + name: String! } -type Order { - customer: String! - order: String! +type Order @aws_api_key { + customer: String! + order: String! } -type Query { - getServiceVersion: ServiceVersion - getCustomers: [Customer] - getCustomer(id: String): Customer - getCustomerOrdersEq(customer: String): Order - getCustomerOrdersLt(customer: String): Order - getCustomerOrdersLe(customer: String): Order - getCustomerOrdersGt(customer: String): Order - getCustomerOrdersGe(customer: String): Order - getCustomerOrdersFilter(customer: String, order: String): Order - getCustomerOrdersBetween(customer: String, order1: String, order2: String): Order +type Payment @aws_api_key { + id: String! + amount: String! +} + +input PaymentInput { + amount: String! +} + +type Query @aws_api_key { + getServiceVersion: ServiceVersion + getCustomers: [Customer] + getCustomer(id: String): Customer + getCustomerOrdersEq(customer: String): Order + getCustomerOrdersLt(customer: String): Order + getCustomerOrdersLe(customer: String): Order + getCustomerOrdersGt(customer: String): Order + getCustomerOrdersGe(customer: String): Order + getCustomerOrdersFilter(customer: String, order: String): Order + getCustomerOrdersBetween(customer: String, order1: String, order2: String): Order + getPayment(id: String): Payment } input FirstOrderInput { - product: String! - quantity: Int! + product: String! + quantity: Int! } -type Mutation { - addCustomer(customer: SaveCustomerInput!): Customer - saveCustomer(id: String!, customer: SaveCustomerInput!): Customer - removeCustomer(id: String!): Customer - saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order - doPostOnAws: String! +type Mutation @aws_api_key { + addCustomer(customer: SaveCustomerInput!): Customer + saveCustomer(id: String!, customer: SaveCustomerInput!): Customer + removeCustomer(id: String!): Customer + saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order + savePayment(payment: PaymentInput!): Payment + doPostOnAws: String! } diff --git a/packages/@aws-cdk/aws-appsync/test/verify.graphql.sh b/packages/@aws-cdk/aws-appsync/test/verify.graphql.sh new file mode 100644 index 0000000000000..c865fc9640113 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/verify.graphql.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +############ +# WiP # +############ + +aws appsync list-graphql-apis + +printf "\nInput the apiId for desired graphql-api: " +IFS= +userinput="" +while read -n1 key +do +# if input == enter key +if [[ "$key" == "" ]]; +then +break; +fi +# Add the key to the variable which is pressed by the user. +userinput+=$key +done + +aws appsync get-graphql-api --api-id "$userinput" +aws appsync list-api-keys --api-id "$userinput" \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/verify.integ.graphql.sh b/packages/@aws-cdk/aws-appsync/test/verify.integ.graphql.sh new file mode 100644 index 0000000000000..f92fab3c176e6 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/verify.integ.graphql.sh @@ -0,0 +1,13 @@ +#!/bin/bash +echo "---" +echo "Before mutation (query for customers)" +curl -XPOST -H "Content-Type:application/graphql" -H "x-api-key:da2-kegodibyrzfbhiubdnjfmvy52u" -d '{ "query": "query { getCustomers { id name } }" }' https://ws3az3nucjcjjbm4o6ykdqltge.appsync-api.us-east-1.amazonaws.com/graphql +echo "" +echo "---" +echo "Sending mutation (add customer)" +curl -XPOST -H "Content-Type:application/graphql" -H "x-api-key:$1" -d '{ "query": "mutation { addCustomer( customer:{ name: \"test\" }) { id name } }" }' $2 +echo "" +echo "---" +echo "After mutation (query for customer)" +curl -XPOST -H "Content-Type:application/graphql" -H "x-api-key:da2-kegodibyrzfbhiubdnjfmvy52u" -d '{ "query": "query { getCustomers { id name } }" }' https://ws3az3nucjcjjbm4o6ykdqltge.appsync-api.us-east-1.amazonaws.com/graphql +echo "" \ No newline at end of file diff --git a/packages/@aws-cdk/aws-athena/.gitignore b/packages/@aws-cdk/aws-athena/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-athena/.gitignore +++ b/packages/@aws-cdk/aws-athena/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-athena/.npmignore b/packages/@aws-cdk/aws-athena/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-athena/.npmignore +++ b/packages/@aws-cdk/aws-athena/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-athena/jest.config.js b/packages/@aws-cdk/aws-athena/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-athena/jest.config.js +++ b/packages/@aws-cdk/aws-athena/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-autoscaling-common/.gitignore b/packages/@aws-cdk/aws-autoscaling-common/.gitignore index a349bac1b340f..d8fe41921d760 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/.gitignore +++ b/packages/@aws-cdk/aws-autoscaling-common/.gitignore @@ -18,3 +18,5 @@ tsconfig.json *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-common/.npmignore b/packages/@aws-cdk/aws-autoscaling-common/.npmignore index 9e5af6ca8e34d..b0d6aa6c90bf1 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/.npmignore +++ b/packages/@aws-cdk/aws-autoscaling-common/.npmignore @@ -24,4 +24,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-common/package.json b/packages/@aws-cdk/aws-autoscaling-common/package.json index ccabb1ecb87e2..ec3b5affa8cc1 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/package.json +++ b/packages/@aws-cdk/aws-autoscaling-common/package.json @@ -62,7 +62,7 @@ "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "fast-check": "^1.25.1", + "fast-check": "^1.26.0", "nodeunit": "^0.11.3", "pkglint": "0.0.0" }, diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/.gitignore b/packages/@aws-cdk/aws-autoscaling-hooktargets/.gitignore index 23a79075f642c..147448f7df4fe 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/.gitignore +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/.npmignore b/packages/@aws-cdk/aws-autoscaling-hooktargets/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/.npmignore +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/jest.config.js b/packages/@aws-cdk/aws-autoscaling-hooktargets/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/jest.config.js +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-autoscaling/.gitignore b/packages/@aws-cdk/aws-autoscaling/.gitignore index bc3bea4d08402..d6ae98ad007fc 100644 --- a/packages/@aws-cdk/aws-autoscaling/.gitignore +++ b/packages/@aws-cdk/aws-autoscaling/.gitignore @@ -15,4 +15,5 @@ nyc.config.js *.snk !.eslintrc.js -!jest.config.js \ No newline at end of file +!jest.config.js +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling/.npmignore b/packages/@aws-cdk/aws-autoscaling/.npmignore index fba0982f5ee45..5d8f93d8a9c7e 100644 --- a/packages/@aws-cdk/aws-autoscaling/.npmignore +++ b/packages/@aws-cdk/aws-autoscaling/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -jest.config.js \ No newline at end of file +jest.config.js +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling/jest.config.js b/packages/@aws-cdk/aws-autoscaling/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-autoscaling/jest.config.js +++ b/packages/@aws-cdk/aws-autoscaling/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts index 2b0b906f4b892..92ef8f62ba2e0 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts @@ -8,7 +8,7 @@ import * as cdk from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; import * as autoscaling from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ nodeunitShim({ 'default fleet'(test: Test) { @@ -1134,17 +1134,17 @@ nodeunitShim({ // THEN expect(stack).to(haveResource('AWS::AutoScaling::AutoScalingGroup', { - NotificationConfigurations : [ + NotificationConfigurations: [ { - TopicARN : { Ref : 'MyTopic86869434' }, - NotificationTypes : [ + TopicARN: { Ref: 'MyTopic86869434' }, + NotificationTypes: [ 'autoscaling:EC2_INSTANCE_LAUNCH_ERROR', 'autoscaling:EC2_INSTANCE_TERMINATE_ERROR', ], }, { - TopicARN : { Ref : 'MyTopic86869434' }, - NotificationTypes : [ + TopicARN: { Ref: 'MyTopic86869434' }, + NotificationTypes: [ 'autoscaling:EC2_INSTANCE_TERMINATE', ], }, @@ -1174,10 +1174,10 @@ nodeunitShim({ // THEN expect(stack).to(haveResource('AWS::AutoScaling::AutoScalingGroup', { - NotificationConfigurations : [ + NotificationConfigurations: [ { - TopicARN : { Ref : 'MyTopic86869434' }, - NotificationTypes : [ + TopicARN: { Ref: 'MyTopic86869434' }, + NotificationTypes: [ 'autoscaling:EC2_INSTANCE_LAUNCH', 'autoscaling:EC2_INSTANCE_LAUNCH_ERROR', 'autoscaling:EC2_INSTANCE_TERMINATE', @@ -1206,10 +1206,10 @@ nodeunitShim({ // THEN expect(stack).to(haveResource('AWS::AutoScaling::AutoScalingGroup', { - NotificationConfigurations : [ + NotificationConfigurations: [ { - TopicARN : { Ref : 'MyTopic86869434' }, - NotificationTypes : [ + TopicARN: { Ref: 'MyTopic86869434' }, + NotificationTypes: [ 'autoscaling:EC2_INSTANCE_LAUNCH', 'autoscaling:EC2_INSTANCE_LAUNCH_ERROR', 'autoscaling:EC2_INSTANCE_TERMINATE', diff --git a/packages/@aws-cdk/aws-autoscalingplans/.gitignore b/packages/@aws-cdk/aws-autoscalingplans/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-autoscalingplans/.gitignore +++ b/packages/@aws-cdk/aws-autoscalingplans/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscalingplans/.npmignore b/packages/@aws-cdk/aws-autoscalingplans/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-autoscalingplans/.npmignore +++ b/packages/@aws-cdk/aws-autoscalingplans/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscalingplans/jest.config.js b/packages/@aws-cdk/aws-autoscalingplans/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-autoscalingplans/jest.config.js +++ b/packages/@aws-cdk/aws-autoscalingplans/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-backup/.gitignore b/packages/@aws-cdk/aws-backup/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-backup/.gitignore +++ b/packages/@aws-cdk/aws-backup/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-backup/.npmignore b/packages/@aws-cdk/aws-backup/.npmignore index 2e1e60862c02c..5c003cc4e3040 100644 --- a/packages/@aws-cdk/aws-backup/.npmignore +++ b/packages/@aws-cdk/aws-backup/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-backup/jest.config.js b/packages/@aws-cdk/aws-backup/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-backup/jest.config.js +++ b/packages/@aws-cdk/aws-backup/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-batch/.gitignore b/packages/@aws-cdk/aws-batch/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-batch/.gitignore +++ b/packages/@aws-cdk/aws-batch/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-batch/.npmignore b/packages/@aws-cdk/aws-batch/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-batch/.npmignore +++ b/packages/@aws-cdk/aws-batch/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-batch/jest.config.js b/packages/@aws-cdk/aws-batch/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-batch/jest.config.js +++ b/packages/@aws-cdk/aws-batch/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-batch/lib/job-definition-image-config.ts b/packages/@aws-cdk/aws-batch/lib/job-definition-image-config.ts index 1d075c7e2d756..55dccb51e485c 100644 --- a/packages/@aws-cdk/aws-batch/lib/job-definition-image-config.ts +++ b/packages/@aws-cdk/aws-batch/lib/job-definition-image-config.ts @@ -38,7 +38,6 @@ class TaskDefinition { * * @internal */ - // tslint:disable-next-line: no-empty public _linkContainer() {} /** diff --git a/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts b/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts index f09b30f0129fa..36258615fbf8e 100644 --- a/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts +++ b/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts @@ -168,7 +168,7 @@ describe('Batch Compute Evironment', () => { generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2, hardwareType: ecs.AmiHardwareType.STANDARD, }), - instanceRole: new iam.CfnInstanceProfile(stack, 'Instance-Profile', { + instanceRole: new iam.CfnInstanceProfile(stack, 'Instance-Profile', { roles: [ new iam.Role(stack, 'Ecs-Instance-Role', { assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), managedPolicies: [ diff --git a/packages/@aws-cdk/aws-budgets/.gitignore b/packages/@aws-cdk/aws-budgets/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-budgets/.gitignore +++ b/packages/@aws-cdk/aws-budgets/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-budgets/.npmignore b/packages/@aws-cdk/aws-budgets/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-budgets/.npmignore +++ b/packages/@aws-cdk/aws-budgets/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-budgets/jest.config.js b/packages/@aws-cdk/aws-budgets/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-budgets/jest.config.js +++ b/packages/@aws-cdk/aws-budgets/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cassandra/.gitignore b/packages/@aws-cdk/aws-cassandra/.gitignore index d57af28d42320..192200b9c7097 100644 --- a/packages/@aws-cdk/aws-cassandra/.gitignore +++ b/packages/@aws-cdk/aws-cassandra/.gitignore @@ -16,3 +16,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cassandra/.npmignore b/packages/@aws-cdk/aws-cassandra/.npmignore index 2f1ff45adc883..c8874799485a3 100644 --- a/packages/@aws-cdk/aws-cassandra/.npmignore +++ b/packages/@aws-cdk/aws-cassandra/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cassandra/jest.config.js b/packages/@aws-cdk/aws-cassandra/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-cassandra/jest.config.js +++ b/packages/@aws-cdk/aws-cassandra/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-ce/.gitignore b/packages/@aws-cdk/aws-ce/.gitignore index e9fee23607e76..5aa413b898780 100644 --- a/packages/@aws-cdk/aws-ce/.gitignore +++ b/packages/@aws-cdk/aws-ce/.gitignore @@ -2,7 +2,6 @@ *.js.map *.d.ts tsconfig.json -tslint.json node_modules *.generated.ts dist @@ -17,3 +16,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml diff --git a/packages/@aws-cdk/aws-ce/.npmignore b/packages/@aws-cdk/aws-ce/.npmignore index 2f1ff45adc883..c8874799485a3 100644 --- a/packages/@aws-cdk/aws-ce/.npmignore +++ b/packages/@aws-cdk/aws-ce/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ce/jest.config.js b/packages/@aws-cdk/aws-ce/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-ce/jest.config.js +++ b/packages/@aws-cdk/aws-ce/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-certificatemanager/.gitignore b/packages/@aws-cdk/aws-certificatemanager/.gitignore index 0bd6133da4d09..018c65919d67c 100644 --- a/packages/@aws-cdk/aws-certificatemanager/.gitignore +++ b/packages/@aws-cdk/aws-certificatemanager/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-certificatemanager/.npmignore b/packages/@aws-cdk/aws-certificatemanager/.npmignore index 13e93138e8461..5b76353bc079b 100644 --- a/packages/@aws-cdk/aws-certificatemanager/.npmignore +++ b/packages/@aws-cdk/aws-certificatemanager/.npmignore @@ -24,4 +24,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-certificatemanager/README.md b/packages/@aws-cdk/aws-certificatemanager/README.md index 7afb3c8f82251..15fbc1a8e09b4 100644 --- a/packages/@aws-cdk/aws-certificatemanager/README.md +++ b/packages/@aws-cdk/aws-certificatemanager/README.md @@ -20,13 +20,9 @@ After requesting a certificate, you will need to prove that you own the domain in question before the certificate will be granted. The CloudFormation deployment will wait until this verification process has been completed. -Because of this wait time, it's better to provision your certificates -either in a separate stack from your main service, or provision them -manually and import them into your CDK application. - -The CDK also provides a custom resource which can be used for automatic -validation if the DNS records for the domain are managed through Route53 (see -below). +Because of this wait time, when using manual validation methods, it's better +to provision your certificates either in a separate stack from your main +service, or provision them manually and import them into your CDK application. ### Email validation @@ -39,25 +35,49 @@ in the AWS Certificate Manager User Guide. ### DNS validation -DNS-validated certificates are validated by configuring appropriate DNS -records for your domain. +If Amazon Route 53 is your DNS provider for the requested domain, the DNS record can be +created automatically: + +```ts +new Certificate(this, 'Certificate', { + domainName: 'hello.example.com', + validation: CertificateValidation.fromDns(myHostedZone), // Route 53 hosted zone +}); +``` -See [Validate with DNS](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate-dns.html) +Otherwise DNS records must be added manually and the stack will not complete +creating until the records are added. + +```ts +new Certificate(this, 'Certificate', { + domainName: 'hello.example.com', + validation: CertificateValidation.fromDns(), // Records must be added manually +}); +``` + +See also [Validate with DNS](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate-dns.html) in the AWS Certificate Manager User Guide. -### Automatic DNS-validated certificates using Route53 +When working with multiple domains, use the `CertificateValidation.fromDnsMultiZone()`: + +[multiple domains DNS validation](test/example.dns.lit.ts) + +Use the `DnsValidatedCertificate` construct for cross-region certificate creation: + +```ts +new DnsValidatedCertificate(this, 'CrossRegionCertificate', { + domainName: 'hello.example.com', + hostedZone: myHostedZone, + region: 'us-east-1', +}); +``` -The `DnsValidatedCertificateRequest` class provides a Custom Resource by which -you can request a TLS certificate from AWS Certificate Manager that is -automatically validated using a cryptographically secure DNS record. For this to -work, there must be a Route 53 public zone that is responsible for serving -records under the Domain Name of the requested certificate. For example, if you -request a certificate for `www.example.com`, there must be a Route 53 public -zone `example.com` that provides authoritative records for the domain. +This is useful when deploying a stack in a region other than `us-east-1` with a +certificate for a CloudFront distribution. -Example: +If cross-region is not needed, the recommended solution is to use the +`Certificate` construct which uses a native CloudFormation implementation. -[request a validated certificate example](test/example.dns-validated-request.lit.ts) ### Importing @@ -71,4 +91,4 @@ const certificate = Certificate.fromCertificateArn(this, 'Certificate', arn); ### Sharing between Stacks To share the certificate between stacks in the same CDK application, simply -pass the `Certificate` object between the stacks. \ No newline at end of file +pass the `Certificate` object between the stacks. diff --git a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json index c3d25504c9324..377aff5edcb80 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json +++ b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json @@ -10,7 +10,7 @@ "scripts": { "build": "echo No build", "test": "jest", - "lint": "eslint lib", + "eslint": "eslint lib", "build+test+package": "npm run build+test", "build+test": "npm run build && npm test" }, diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts b/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts index fcc7ec3cf33c2..77e6247f38198 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts @@ -1,3 +1,4 @@ +import * as route53 from '@aws-cdk/aws-route53'; import { Construct, IResource, Resource, Token } from '@aws-cdk/core'; import { CfnCertificate } from './certificatemanager.generated'; import { apexDomain } from './util'; @@ -40,6 +41,7 @@ export interface CertificateProps { * Has to be a superdomain of the requested domain. * * @default - Apex domain is used for every domain that's not overridden. + * @deprecated use `validation` instead. */ readonly validationDomains?: {[domainName: string]: string}; @@ -47,26 +49,124 @@ export interface CertificateProps { * Validation method used to assert domain ownership * * @default ValidationMethod.EMAIL + * @deprecated use `validation` instead. */ readonly validationMethod?: ValidationMethod; + + /** + * How to validate this certifcate + * + * @default CertificateValidation.fromEmail() + */ + readonly validation?: CertificateValidation; +} + +/** + * Properties for certificate validation + */ +export interface CertificationValidationProps { + /** + * Validation method + * + * @default ValidationMethod.EMAIL + */ + readonly method?: ValidationMethod; + + /** + * Hosted zone to use for DNS validation + * + * @default - use email validation + */ + readonly hostedZone?: route53.IHostedZone; + + /** + * A map of hosted zones to use for DNS validation + * + * @default - use `hostedZone` + */ + readonly hostedZones?: { [domainName: string]: route53.IHostedZone }; + + /** + * Validation domains to use for email validation + * + * @default - Apex domain + */ + readonly validationDomains?: { [domainName: string]: string }; +} + +/** + * How to validate a certificate + */ +export class CertificateValidation { + /** + * Validate the certifcate with DNS + * + * IMPORTANT: If `hostedZone` is not specified, DNS records must be added + * manually and the stack will not complete creating until the records are + * added. + * + * @param hostedZone the hosted zone where DNS records must be created + */ + public static fromDns(hostedZone?: route53.IHostedZone) { + return new CertificateValidation({ + method: ValidationMethod.DNS, + hostedZone, + }); + } + + /** + * Validate the certifcate with automatically created DNS records in multiple + * Amazon Route 53 hosted zones. + * + * @param hostedZones a map of hosted zones where DNS records must be created + * for the domains in the certificate + */ + public static fromDnsMultiZone(hostedZones: { [domainName: string]: route53.IHostedZone }) { + return new CertificateValidation({ + method: ValidationMethod.DNS, + hostedZones, + }); + } + + /** + * Validate the certifcate with Email + * + * IMPORTANT: if you are creating a certificate as part of your stack, the stack + * will not complete creating until you read and follow the instructions in the + * email that you will receive. + * + * ACM will send validation emails to the following addresses: + * + * admin@domain.com + * administrator@domain.com + * hostmaster@domain.com + * postmaster@domain.com + * webmaster@domain.com + * + * For every domain that you register. + * + * @param validationDomains a map of validation domains to use for domains in the certificate + */ + public static fromEmail(validationDomains?: { [domainName: string]: string }) { + return new CertificateValidation({ + method: ValidationMethod.EMAIL, + validationDomains, + }); + } + + /** + * The validation method + */ + public readonly method: ValidationMethod; + + /** @param props Certification validation properties */ + private constructor(public readonly props: CertificationValidationProps) { + this.method = props.method ?? ValidationMethod.EMAIL; + } } /** * A certificate managed by AWS Certificate Manager - * - * IMPORTANT: if you are creating a certificate as part of your stack, the stack - * will not complete creating until you read and follow the instructions in the - * email that you will receive. - * - * ACM will send validation emails to the following addresses: - * - * admin@domain.com - * administrator@domain.com - * hostmaster@domain.com - * postmaster@domain.com - * webmaster@domain.com - * - * For every domain that you register. */ export class Certificate extends Resource implements ICertificate { @@ -89,33 +189,27 @@ export class Certificate extends Resource implements ICertificate { constructor(scope: Construct, id: string, props: CertificateProps) { super(scope, id); + let validation: CertificateValidation; + if (props.validation) { + validation = props.validation; + } else { // Deprecated props + if (props.validationMethod === ValidationMethod.DNS) { + validation = CertificateValidation.fromDns(); + } else { + validation = CertificateValidation.fromEmail(props.validationDomains); + } + } + const allDomainNames = [props.domainName].concat(props.subjectAlternativeNames || []); const cert = new CfnCertificate(this, 'Resource', { domainName: props.domainName, subjectAlternativeNames: props.subjectAlternativeNames, - domainValidationOptions: allDomainNames.map(domainValidationOption), - validationMethod: props.validationMethod, + domainValidationOptions: renderDomainValidation(validation, allDomainNames), + validationMethod: validation.method, }); this.certificateArn = cert.ref; - - /** - * Return the domain validation options for the given domain - * - * Closes over props. - */ - function domainValidationOption(domainName: string): CfnCertificate.DomainValidationOptionProperty { - let validationDomain = props.validationDomains && props.validationDomains[domainName]; - if (validationDomain === undefined) { - if (Token.isUnresolved(domainName)) { - throw new Error('When using Tokens for domain names, \'validationDomains\' needs to be supplied'); - } - validationDomain = apexDomain(domainName); - } - - return { domainName, validationDomain }; - } } } @@ -136,4 +230,33 @@ export enum ValidationMethod { * @see https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate-dns.html */ DNS = 'DNS', -} \ No newline at end of file +} + +// eslint-disable-next-line max-len +function renderDomainValidation(validation: CertificateValidation, domainNames: string[]): CfnCertificate.DomainValidationOptionProperty[] | undefined { + const domainValidation: CfnCertificate.DomainValidationOptionProperty[] = []; + + switch (validation.method) { + case ValidationMethod.DNS: + for (const domainName of domainNames) { + const hostedZone = validation.props.hostedZones?.[domainName] ?? validation.props.hostedZone; + if (hostedZone) { + domainValidation.push({ domainName, hostedZoneId: hostedZone.hostedZoneId }); + } + } + break; + case ValidationMethod.EMAIL: + for (const domainName of domainNames) { + const validationDomain = validation.props.validationDomains?.[domainName]; + if (!validationDomain && Token.isUnresolved(domainName)) { + throw new Error('When using Tokens for domain names, \'validationDomains\' needs to be supplied'); + } + domainValidation.push({ domainName, validationDomain: validationDomain ?? apexDomain(domainName) }); + } + break; + default: + throw new Error(`Unknown validation method ${validation.method}`); + } + + return domainValidation.length !== 0 ? domainValidation : undefined; +} diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/public-suffixes.ts b/packages/@aws-cdk/aws-certificatemanager/lib/public-suffixes.ts index 28a0acc326317..7f2222e22aabb 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/public-suffixes.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/public-suffixes.ts @@ -1,5 +1,5 @@ // This file has been generated using ../suffixes/build-map.py -// tslint:disable:no-trailing-whitespace object-literal-key-quotes +/* eslint-disable quote-props */ export const publicSuffixes = { 'ac': { 'com': {}, diff --git a/packages/@aws-cdk/aws-certificatemanager/suffixes/build-map.py b/packages/@aws-cdk/aws-certificatemanager/suffixes/build-map.py index 7c3cd3aa6411b..f63053337111e 100644 --- a/packages/@aws-cdk/aws-certificatemanager/suffixes/build-map.py +++ b/packages/@aws-cdk/aws-certificatemanager/suffixes/build-map.py @@ -31,5 +31,5 @@ with open('../lib/public-suffixes.ts', 'w') as o: o.write('// This file has been generated using ../suffixes/build-map.py\n') - o.write('// tslint:disable:no-trailing-whitespace object-literal-key-quotes\n') + o.write('/* eslint-disable no-trailing-spaces, quote-props */\n') o.write('export const publicSuffixes = %s;' % json.dumps(trie, indent=2)) diff --git a/packages/@aws-cdk/aws-certificatemanager/test/example.dns.lit.ts b/packages/@aws-cdk/aws-certificatemanager/test/example.dns.lit.ts new file mode 100644 index 0000000000000..c2cfd41ff0e15 --- /dev/null +++ b/packages/@aws-cdk/aws-certificatemanager/test/example.dns.lit.ts @@ -0,0 +1,36 @@ +import * as route53 from '@aws-cdk/aws-route53'; +import { App, CfnOutput, Construct, Stack } from '@aws-cdk/core'; +import * as acm from '../lib'; + +class AcmStack extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + /// !show + const exampleCom = new route53.HostedZone(this, 'ExampleCom', { + zoneName: 'example.com', + }); + + const exampleNet = new route53.HostedZone(this, 'ExampelNet', { + zoneName: 'example.net', + }); + + const cert = new acm.Certificate(this, 'Certificate', { + domainName: 'test.example.com', + subjectAlternativeNames: ['cool.example.com', 'test.example.net'], + validation: acm.CertificateValidation.fromDnsMultiZone({ + 'text.example.com': exampleCom, + 'cool.example.com': exampleCom, + 'test.example.net': exampleNet, + }), + }); + /// !hide + + new CfnOutput(this, 'Output', { + value: cert.certificateArn, + }); + } +} + +const app = new App(); +new AcmStack(app, 'AcmStack'); +app.synth(); diff --git a/packages/@aws-cdk/aws-certificatemanager/test/test.certificate.ts b/packages/@aws-cdk/aws-certificatemanager/test/test.certificate.ts index 01f3da457d700..3b0de2e1ba1f2 100644 --- a/packages/@aws-cdk/aws-certificatemanager/test/test.certificate.ts +++ b/packages/@aws-cdk/aws-certificatemanager/test/test.certificate.ts @@ -1,7 +1,8 @@ import { expect, haveResource } from '@aws-cdk/assert'; +import * as route53 from '@aws-cdk/aws-route53'; import { Lazy, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; -import { Certificate, ValidationMethod } from '../lib'; +import { Certificate, CertificateValidation, ValidationMethod } from '../lib'; export = { 'apex domain selection by default'(test: Test) { @@ -104,4 +105,129 @@ export = { test.done(); }, + + 'CertificateValidation.fromEmail'(test: Test) { + const stack = new Stack(); + + new Certificate(stack, 'Certificate', { + domainName: 'test.example.com', + subjectAlternativeNames: ['extra.example.com'], + validation: CertificateValidation.fromEmail({ + 'test.example.com': 'example.com', + }), + }); + + expect(stack).to(haveResource('AWS::CertificateManager::Certificate', { + DomainName: 'test.example.com', + SubjectAlternativeNames: ['extra.example.com'], + DomainValidationOptions: [ + { + DomainName: 'test.example.com', + ValidationDomain: 'example.com', + }, + { + DomainName: 'extra.example.com', + ValidationDomain: 'example.com', + }, + ], + ValidationMethod: 'EMAIL', + })); + + test.done(); + }, + + 'CertificateValidation.fromDns'(test: Test) { + const stack = new Stack(); + + new Certificate(stack, 'Certificate', { + domainName: 'test.example.com', + subjectAlternativeNames: ['extra.example.com'], + validation: CertificateValidation.fromDns(), + }); + + expect(stack).to(haveResource('AWS::CertificateManager::Certificate', { + DomainName: 'test.example.com', + SubjectAlternativeNames: ['extra.example.com'], + ValidationMethod: 'DNS', + })); + + test.done(); + }, + + 'CertificateValidation.fromDns with hosted zone'(test: Test) { + const stack = new Stack(); + + const exampleCom = new route53.HostedZone(stack, 'ExampleCom', { + zoneName: 'example.com', + }); + + new Certificate(stack, 'Certificate', { + domainName: 'test.example.com', + validation: CertificateValidation.fromDns(exampleCom), + }); + + expect(stack).to(haveResource('AWS::CertificateManager::Certificate', { + DomainName: 'test.example.com', + DomainValidationOptions: [ + { + DomainName: 'test.example.com', + HostedZoneId: { + Ref: 'ExampleCom20E1324B', + }, + }, + ], + ValidationMethod: 'DNS', + })); + + test.done(); + }, + + 'CertificateValidation.fromDnsMultiZone'(test: Test) { + const stack = new Stack(); + + const exampleCom = new route53.HostedZone(stack, 'ExampleCom', { + zoneName: 'example.com', + }); + + const exampleNet = new route53.HostedZone(stack, 'ExampleNet', { + zoneName: 'example.com', + }); + + new Certificate(stack, 'Certificate', { + domainName: 'test.example.com', + subjectAlternativeNames: ['cool.example.com', 'test.example.net'], + validation: CertificateValidation.fromDnsMultiZone({ + 'test.example.com': exampleCom, + 'cool.example.com': exampleCom, + 'test.example.net': exampleNet, + }), + }); + + expect(stack).to(haveResource('AWS::CertificateManager::Certificate', { + DomainName: 'test.example.com', + DomainValidationOptions: [ + { + DomainName: 'test.example.com', + HostedZoneId: { + Ref: 'ExampleCom20E1324B', + }, + }, + { + DomainName: 'cool.example.com', + HostedZoneId: { + Ref: 'ExampleCom20E1324B', + }, + }, + { + DomainName: 'test.example.net', + HostedZoneId: { + Ref: 'ExampleNetF7CA40C9', + }, + }, + ], + ValidationMethod: 'DNS', + })); + + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-chatbot/.gitignore b/packages/@aws-cdk/aws-chatbot/.gitignore index d57af28d42320..192200b9c7097 100644 --- a/packages/@aws-cdk/aws-chatbot/.gitignore +++ b/packages/@aws-cdk/aws-chatbot/.gitignore @@ -16,3 +16,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-chatbot/.npmignore b/packages/@aws-cdk/aws-chatbot/.npmignore index 2f1ff45adc883..c8874799485a3 100644 --- a/packages/@aws-cdk/aws-chatbot/.npmignore +++ b/packages/@aws-cdk/aws-chatbot/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-chatbot/jest.config.js b/packages/@aws-cdk/aws-chatbot/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-chatbot/jest.config.js +++ b/packages/@aws-cdk/aws-chatbot/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cloud9/.gitignore b/packages/@aws-cdk/aws-cloud9/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-cloud9/.gitignore +++ b/packages/@aws-cdk/aws-cloud9/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloud9/.npmignore b/packages/@aws-cdk/aws-cloud9/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-cloud9/.npmignore +++ b/packages/@aws-cdk/aws-cloud9/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloud9/jest.config.js b/packages/@aws-cdk/aws-cloud9/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-cloud9/jest.config.js +++ b/packages/@aws-cdk/aws-cloud9/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cloudformation/.gitignore b/packages/@aws-cdk/aws-cloudformation/.gitignore index 32a10d785e8fb..dcc1dc41e477f 100644 --- a/packages/@aws-cdk/aws-cloudformation/.gitignore +++ b/packages/@aws-cdk/aws-cloudformation/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/.npmignore b/packages/@aws-cdk/aws-cloudformation/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-cloudformation/.npmignore +++ b/packages/@aws-cdk/aws-cloudformation/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/asset-directory-fixture/index.ts b/packages/@aws-cdk/aws-cloudformation/test/asset-directory-fixture/index.ts index 3da16e9b84b6c..adfa3cdf1ef24 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/asset-directory-fixture/index.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/asset-directory-fixture/index.ts @@ -1,4 +1,4 @@ -// tslint:disable:no-console +/* eslint-disable no-console */ exports.handler = async (evt: any) => { console.error(JSON.stringify(evt, undefined, 2)); diff --git a/packages/@aws-cdk/aws-cloudformation/test/core-custom-resource-provider-fixture/index.ts b/packages/@aws-cdk/aws-cloudformation/test/core-custom-resource-provider-fixture/index.ts index 5a372f057593e..b83a9218a3e58 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/core-custom-resource-provider-fixture/index.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/core-custom-resource-provider-fixture/index.ts @@ -1,4 +1,4 @@ -// tslint:disable: no-console +/* eslint-disable no-console */ export function handler(event: any) { console.log('I am a custom resource'); diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.core-custom-resources.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.core-custom-resources.ts index 2c219f757825d..084e661b5c0b1 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.core-custom-resources.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.core-custom-resources.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets /* * Stack verification steps: * - Deploy with `--no-clean` diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts index 02f2a1e55b638..3431211740c88 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as lambda from '@aws-cdk/aws-lambda'; import { App, Construct, Stack } from '@aws-cdk/core'; import * as path from 'path'; diff --git a/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts b/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts index 4a71ab81e0ea3..4eedbf0e75c4c 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts @@ -1,13 +1,13 @@ +import * as fs from 'fs'; +import * as path from 'path'; import { expect, haveResource, matchTemplate, SynthUtils } from '@aws-cdk/assert'; import * as s3_assets from '@aws-cdk/aws-s3-assets'; import * as sns from '@aws-cdk/aws-sns'; import { App, CfnParameter, CfnResource, Construct, ContextProvider, Stack } from '@aws-cdk/core'; -import * as fs from 'fs'; import { Test } from 'nodeunit'; -import * as path from 'path'; import { NestedStack } from '../lib/nested-stack'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ export = { 'fails if defined as a root'(test: Test) { diff --git a/packages/@aws-cdk/aws-cloudformation/test/test.resource.ts b/packages/@aws-cdk/aws-cloudformation/test/test.resource.ts index b66d845634353..5dcfe360336c2 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/test.resource.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/test.resource.ts @@ -5,7 +5,7 @@ import * as cdk from '@aws-cdk/core'; import { Test, testCase } from 'nodeunit'; import { CustomResource, CustomResourceProvider } from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = testCase({ 'custom resources honor removalPolicy': { diff --git a/packages/@aws-cdk/aws-cloudfront/.gitignore b/packages/@aws-cdk/aws-cloudfront/.gitignore index 27e8c6a71e4ff..832d814be5d7a 100644 --- a/packages/@aws-cdk/aws-cloudfront/.gitignore +++ b/packages/@aws-cdk/aws-cloudfront/.gitignore @@ -13,3 +13,6 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js +!jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/.npmignore b/packages/@aws-cdk/aws-cloudfront/.npmignore index eb063bd7f38c8..cccec9064cc31 100644 --- a/packages/@aws-cdk/aws-cloudfront/.npmignore +++ b/packages/@aws-cdk/aws-cloudfront/.npmignore @@ -22,4 +22,7 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/README.md b/packages/@aws-cdk/aws-cloudfront/README.md index b0ae2f6f79ba3..0ceb2f310733a 100644 --- a/packages/@aws-cdk/aws-cloudfront/README.md +++ b/packages/@aws-cdk/aws-cloudfront/README.md @@ -1,18 +1,137 @@ ## Amazon CloudFront Construct Library + --- -![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) +| Features | Stability | +| --- | --- | +| CFN Resources | ![Stable](https://img.shields.io/badge/stable-success.svg?style=for-the-badge) | +| Higher level constructs for Distribution | ![Experimental](https://img.shields.io/badge/experimental-important.svg?style=for-the-badge) | +| Higher level constructs for CloudFrontWebDistribution | ![Stable](https://img.shields.io/badge/stable-success.svg?style=for-the-badge) | -> All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. +> **CFN Resources:** All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. -![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) +> **Experimental:** Higher level constructs in this module that are marked as experimental are under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. -> 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. +> **Stable:** Higher level constructs in this module that are marked stable will not undergo any breaking changes. They will strictly follow the [Semantic Versioning](https://semver.org/) model. --- +Amazon CloudFront is a web service that speeds up distribution of your static and dynamic web content, such as .html, .css, .js, and image files, to +your users. CloudFront delivers your content through a worldwide network of data centers called edge locations. When a user requests content that +you're serving with CloudFront, the user is routed to the edge location that provides the lowest latency, so that content is delivered with the best +possible performance. + +## Distribution API - Experimental + +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +The `Distribution` API is currently being built to replace the existing `CloudFrontWebDistribution` API. The `Distribution` API is optimized for the +most common use cases of CloudFront distributions (e.g., single origin and behavior, few customizations) while still providing the ability for more +advanced use cases. The API focuses on simplicity for the common use cases, and convenience methods for creating the behaviors and origins necessary +for more complex use cases. + +### Creating a distribution + +CloudFront distributions deliver your content from one or more origins; an origin is the location where you store the original version of your +content. Origins can be created from S3 buckets or a custom origin (HTTP server). Each distribution has a default behavior which applies to all +requests to that distribution, and routes requests to a primary origin. + +#### From an S3 Bucket + +An S3 bucket can be added as an origin. If the bucket is configured as a website endpoint, the distribution can use S3 redirects and S3 custom error +documents. + +```ts +import * as cloudfront from '@aws-cdk/aws-cloudfront'; + +// Creates a distribution for a S3 bucket. +const myBucket = new s3.Bucket(this, 'myBucket'); +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { origin: cloudfront.Origin.fromBucket(myBucket) }, +}); +``` + +The above will treat the bucket differently based on if `IBucket.isWebsite` is set or not. If the bucket is configured as a website, the bucket is +treated as an HTTP origin, and the built-in S3 redirects and error pages can be used. Otherwise, the bucket is handled as a bucket origin and +CloudFront's redirect and error handling will be used. In the latter case, the Origin wil create an origin access identity and grant it access to the +underlying bucket. This can be used in conjunction with a bucket that is not public to require that your users access your content using CloudFront +URLs and not S3 URLs directly. + +### Domain Names and Certificates + +When you create a distribution, CloudFront assigns a domain name for the distribution, for example: `d111111abcdef8.cloudfront.net`; this value can +be retrieved from `distribution.distributionDomainName`. CloudFront distributions use a default certificate (`*.cloudfront.net`) to support HTTPS by +default. If you want to use your own domain name, such as `www.example.com`, you must associate a certificate with your distribution that contains +your domain name. The certificate must be present in the AWS Certificate Manager (ACM) service in the US East (N. Virginia) region; the certificate +may either be created by ACM, or created elsewhere and imported into ACM. + +```ts +const myCertificate = new acm.DnsValidatedCertificate(this, 'mySiteCert', { + domainName: 'www.example.com', + hostedZone, +}); +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { origin: cloudfront.Origin.fromBucket(myBucket) }, + certificate: myCertificate, +}); +``` + +### Multiple Behaviors & Origins + +Each distribution has a default behavior which applies to all requests to that distribution; additional behaviors may be specified for a +given URL path pattern. Behaviors allow routing with multiple origins, controlling which HTTP methods to support, whether to require users to +use HTTPS, and what query strings or cookies to forward to your origin, among others. + +The properties of the default behavior can be adjusted as part of the distribution creation. The following example shows configuring the HTTP +methods and viewer protocol policy of the cache. + +```ts +const myWebDistribution = new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { + origin: cloudfront.Origin.fromBucket(myBucket), + allowedMethods: AllowedMethods.ALLOW_ALL, + viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + } +}); +``` + +Additional behaviors can be specified at creation, or added after the initial creation. Each additional behavior is associated with an origin, +and enable customization for a specific set of resources based on a URL path pattern. For example, we can add a behavior to `myWebDistribution` to +override the default time-to-live (TTL) for all of the images. + +```ts +myWebDistribution.addBehavior('/images/*.jpg', cloudfront.Origin.fromBucket(myOtherBucket), { + viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + defaultTtl: cdk.Duration.days(7), +}); +``` + +These behaviors can also be specified at distribution creation time. + +```ts +const bucketOrigin = cloudfront.Origin.fromBucket(myBucket); +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { + origin: bucketOrigin, + allowedMethods: AllowedMethods.ALLOW_ALL, + viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + }, + additionalBehaviors: { + '/images/*.jpg': { + origin: bucketOrigin, + viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + defaultTtl: cdk.Duration.days(7), + }, + }, +}); +``` + +## CloudFrontWebDistribution API - Stable + +![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge) + A CloudFront construct - for setting up the AWS CDN with ease! Example usage: @@ -75,9 +194,32 @@ CloudFront supports adding restrictions to your distribution. See [Restricting the Geographic Distribution of Your Content](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/georestrictions.html) in the CloudFront User Guide. Example: + ```ts new cloudfront.CloudFrontWebDistribution(stack, 'MyDistribution', { //... geoRestriction: GeoRestriction.whitelist('US', 'UK') }); -``` \ No newline at end of file +``` + +### Connection behaviors between CloudFront and your origin + +CloudFront provides you even more control over the connection behaviors between CloudFront and your origin. You can now configure the number of connection attempts CloudFront will make to your origin and the origin connection timeout for each attempt. + +See [Origin Connection Attempts](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html#origin-connection-attempts) + +See [Origin Connection Timeout](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html#origin-connection-timeout) + +Example usage: + +```ts +const distribution = new CloudFrontWebDistribution(this, 'MyDistribution', { + originConfigs: [ + { + ..., + connectionAttempts: 3, + connectionTimeout: cdk.Duration.seconds(10), + } + ] +}); +``` diff --git a/packages/@aws-cdk/aws-cloudfront/jest.config.js b/packages/@aws-cdk/aws-cloudfront/jest.config.js new file mode 100644 index 0000000000000..54e28beb9798b --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index 9b124dd37d722..2290a8c366a2d 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -1,14 +1,354 @@ +import * as acm from '@aws-cdk/aws-certificatemanager'; +import { Construct, IResource, Lazy, Resource, Stack, Token, Duration } from '@aws-cdk/core'; +import { CfnDistribution } from './cloudfront.generated'; +import { Origin } from './origin'; +import { CacheBehavior } from './private/cache-behavior'; + /** * Interface for CloudFront distributions */ -export interface IDistribution { +export interface IDistribution extends IResource { /** - * The domain name of the distribution + * The domain name of the Distribution, such as d111111abcdef8.cloudfront.net. + * + * @attribute + * @deprecated - Use `distributionDomainName` instead. */ readonly domainName: string; + /** + * The domain name of the Distribution, such as d111111abcdef8.cloudfront.net. + * + * @attribute + */ + readonly distributionDomainName: string; + /** * The distribution ID for this distribution. + * + * @attribute */ readonly distributionId: string; -} \ No newline at end of file +} + +/** + * Attributes used to import a Distribution. + * + * @experimental + */ +export interface DistributionAttributes { + /** + * The generated domain name of the Distribution, such as d111111abcdef8.cloudfront.net. + * + * @attribute + */ + readonly domainName: string; + + /** + * The distribution ID for this distribution. + * + * @attribute + */ + readonly distributionId: string; +} + +/** + * Properties for a Distribution + * + * @experimental + */ +export interface DistributionProps { + /** + * The default behavior for the distribution. + */ + readonly defaultBehavior: BehaviorOptions; + + /** + * Additional behaviors for the distribution, mapped by the pathPattern that specifies which requests to apply the behavior to. + * + * @default - no additional behaviors are added. + */ + readonly additionalBehaviors?: Record; + + /** + * A certificate to associate with the distribution. The certificate must be located in N. Virginia (us-east-1). + * + * @default - the CloudFront wildcard certificate (*.cloudfront.net) will be used. + */ + readonly certificate?: acm.ICertificate; + + /** + * The price class that corresponds with the maximum price that you want to pay for CloudFront service. + * If you specify PriceClass_All, CloudFront responds to requests for your objects from all CloudFront edge locations. + * If you specify a price class other than PriceClass_All, CloudFront serves your objects from the CloudFront edge location + * that has the lowest latency among the edge locations in your price class. + * + * @default PriceClass.PRICE_CLASS_ALL + */ + readonly priceClass?: PriceClass; + + /** + * How CloudFront should handle requests that are not successful (e.g., PageNotFound). + * + * @default - No custom error responses. + */ + readonly errorResponses?: ErrorResponse[]; +} + +/** + * A CloudFront distribution with associated origin(s) and caching behavior(s). + * + * @experimental + */ +export class Distribution extends Resource implements IDistribution { + + /** + * Creates a Distribution construct that represents an external (imported) distribution. + */ + public static fromDistributionAttributes(scope: Construct, id: string, attrs: DistributionAttributes): IDistribution { + return new class extends Resource implements IDistribution { + public readonly domainName: string; + public readonly distributionDomainName: string; + public readonly distributionId: string; + + constructor() { + super(scope, id); + this.domainName = attrs.domainName; + this.distributionDomainName = attrs.domainName; + this.distributionId = attrs.distributionId; + } + }(); + } + + public readonly domainName: string; + public readonly distributionDomainName: string; + public readonly distributionId: string; + + private readonly defaultBehavior: CacheBehavior; + private readonly additionalBehaviors: CacheBehavior[] = []; + private readonly origins: Set = new Set(); + + private readonly errorResponses: ErrorResponse[]; + private readonly certificate?: acm.ICertificate; + + constructor(scope: Construct, id: string, props: DistributionProps) { + super(scope, id); + + if (props.certificate) { + const certificateRegion = Stack.of(this).parseArn(props.certificate.certificateArn).region; + if (!Token.isUnresolved(certificateRegion) && certificateRegion !== 'us-east-1') { + throw new Error('Distribution certificates must be in the us-east-1 region and the certificate you provided is in $Region.'); + } + } + + this.defaultBehavior = new CacheBehavior({ pathPattern: '*', ...props.defaultBehavior }); + this.addOrigin(this.defaultBehavior.origin); + if (props.additionalBehaviors) { + Object.entries(props.additionalBehaviors).forEach(([pathPattern, behaviorOptions]) => { + this.addBehavior(pathPattern, behaviorOptions.origin, behaviorOptions); + }); + } + + this.certificate = props.certificate; + this.errorResponses = props.errorResponses ?? []; + + const distribution = new CfnDistribution(this, 'CFDistribution', { distributionConfig: { + enabled: true, + origins: Lazy.anyValue({ produce: () => this.renderOrigins() }), + defaultCacheBehavior: this.defaultBehavior._renderBehavior(), + cacheBehaviors: Lazy.anyValue({ produce: () => this.renderCacheBehaviors() }), + viewerCertificate: this.certificate ? { acmCertificateArn: this.certificate.certificateArn } : undefined, + customErrorResponses: this.renderErrorResponses(), + priceClass: props.priceClass ?? undefined, + } }); + + this.domainName = distribution.attrDomainName; + this.distributionDomainName = distribution.attrDomainName; + this.distributionId = distribution.ref; + } + + /** + * Adds a new behavior to this distribution for the given pathPattern. + * + * @param pathPattern the path pattern (e.g., 'images/*') that specifies which requests to apply the behavior to. + * @param behaviorOptions the options for the behavior at this path. + */ + public addBehavior(pathPattern: string, origin: Origin, behaviorOptions: AddBehaviorOptions = {}) { + if (pathPattern === '*') { + throw new Error('Only the default behavior can have a path pattern of \'*\''); + } + this.additionalBehaviors.push(new CacheBehavior({ pathPattern, origin, ...behaviorOptions })); + this.addOrigin(origin); + } + + private addOrigin(origin: Origin) { + if (!this.origins.has(origin)) { + this.origins.add(origin); + origin._bind(this, { originIndex: this.origins.size }); + } + } + + private renderOrigins(): CfnDistribution.OriginProperty[] { + const renderedOrigins: CfnDistribution.OriginProperty[] = []; + this.origins.forEach(origin => renderedOrigins.push(origin._renderOrigin())); + return renderedOrigins; + } + + private renderCacheBehaviors(): CfnDistribution.CacheBehaviorProperty[] | undefined { + if (this.additionalBehaviors.length === 0) { return undefined; } + return this.additionalBehaviors.map(behavior => behavior._renderBehavior()); + } + + private renderErrorResponses(): CfnDistribution.CustomErrorResponseProperty[] | undefined { + if (this.errorResponses.length === 0) { return undefined; } + function validateCustomErrorResponse(errorResponse: ErrorResponse) { + if (errorResponse.responsePagePath && !errorResponse.responseHttpStatus) { + throw new Error('\'responseCode\' must be provided if \'responsePagePath\' is defined'); + } + if (!errorResponse.responseHttpStatus && !errorResponse.ttl) { + throw new Error('A custom error response without either a \'responseCode\' or \'errorCachingMinTtl\' is not valid.'); + } + } + this.errorResponses.forEach(e => validateCustomErrorResponse(e)); + + return this.errorResponses.map(errorConfig => { + return { + errorCachingMinTtl: errorConfig.ttl?.toSeconds(), + errorCode: errorConfig.httpStatus, + responseCode: errorConfig.responseHttpStatus, + responsePagePath: errorConfig.responsePagePath, + }; + }); + } + +} + +/** + * The price class determines how many edge locations CloudFront will use for your distribution. + * See https://aws.amazon.com/cloudfront/pricing/ for full list of supported regions. + */ +export enum PriceClass { + /** USA, Canada, Europe, & Israel */ + PRICE_CLASS_100 = 'PriceClass_100', + /** PRICE_CLASS_100 + South Africa, Kenya, Middle East, Japan, Singapore, South Korea, Taiwan, Hong Kong, & Philippines */ + PRICE_CLASS_200 = 'PriceClass_200', + /** All locations */ + PRICE_CLASS_ALL = 'PriceClass_All' +} + +/** + * How HTTPs should be handled with your distribution. + */ +export enum ViewerProtocolPolicy { + /** HTTPS only */ + HTTPS_ONLY = 'https-only', + /** Will redirect HTTP requests to HTTPS */ + REDIRECT_TO_HTTPS = 'redirect-to-https', + /** Both HTTP and HTTPS supported */ + ALLOW_ALL = 'allow-all' +} + +/** + * Defines what protocols CloudFront will use to connect to an origin. + */ +export enum OriginProtocolPolicy { + /** Connect on HTTP only */ + HTTP_ONLY = 'http-only', + /** Connect with the same protocol as the viewer */ + MATCH_VIEWER = 'match-viewer', + /** Connect on HTTPS only */ + HTTPS_ONLY = 'https-only', +} + +/** + * The HTTP methods that the Behavior will accept requests on. + */ +export class AllowedMethods { + /** HEAD and GET */ + public static readonly ALLOW_GET_HEAD = new AllowedMethods(['GET', 'HEAD']); + /** HEAD, GET, and OPTIONS */ + public static readonly ALLOW_GET_HEAD_OPTIONS = new AllowedMethods(['GET', 'HEAD', 'OPTIONS']); + /** All supported HTTP methods */ + public static readonly ALLOW_ALL = new AllowedMethods(['GET', 'HEAD', 'OPTIONS', 'PUT', 'PATCH', 'POST', 'DELETE']); + + /** HTTP methods supported */ + public readonly methods: string[]; + + private constructor(methods: string[]) { this.methods = methods; } +} + +/** + * Options for configuring custom error responses. + * + * @experimental + */ +export interface ErrorResponse { + /** + * The minimum amount of time, in seconds, that you want CloudFront to cache the HTTP status code specified in ErrorCode. + * + * @default - the default caching TTL behavior applies + */ + readonly ttl?: Duration; + /** + * The HTTP status code for which you want to specify a custom error page and/or a caching duration. + */ + readonly httpStatus: number; + /** + * The HTTP status code that you want CloudFront to return to the viewer along with the custom error page. + * + * If you specify a value for `responseHttpStatus`, you must also specify a value for `responsePagePath`. + * + * @default - not set, the error code will be returned as the response code. + */ + readonly responseHttpStatus?: number; + /** + * The path to the custom error page that you want CloudFront to return to a viewer when your origin returns the + * `httpStatus`, for example, /4xx-errors/403-forbidden.html + * + * @default - the default CloudFront response is shown. + */ + readonly responsePagePath?: string; +} + +/** + * Options for adding a new behavior to a Distribution. + * + * @experimental + */ +export interface AddBehaviorOptions { + /** + * HTTP methods to allow for this behavior. + * + * @default - GET and HEAD + */ + readonly allowedMethods?: AllowedMethods; + + /** + * Whether CloudFront will forward query strings to the origin. + * If this is set to true, CloudFront will forward all query parameters to the origin, and cache + * based on all parameters. See `forwardQueryStringCacheKeys` for a way to limit the query parameters + * CloudFront caches on. + * + * @default false + */ + readonly forwardQueryString?: boolean; + + /** + * A set of query string parameter names to use for caching if `forwardQueryString` is set to true. + * + * @default [] + */ + readonly forwardQueryStringCacheKeys?: string[]; +} + +/** + * Options for creating a new behavior. + * + * @experimental + */ +export interface BehaviorOptions extends AddBehaviorOptions { + /** + * The origin that you want CloudFront to route requests to when they match this behavior. + */ + readonly origin: Origin; +} diff --git a/packages/@aws-cdk/aws-cloudfront/lib/index.ts b/packages/@aws-cdk/aws-cloudfront/lib/index.ts index 85b081a3f9e9a..bf106211657c9 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/index.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/index.ts @@ -1,5 +1,6 @@ export * from './distribution'; export * from './web_distribution'; +export * from './origin'; export * from './origin_access_identity'; // AWS::CloudFront CloudFormation Resources: diff --git a/packages/@aws-cdk/aws-cloudfront/lib/origin.ts b/packages/@aws-cdk/aws-cloudfront/lib/origin.ts new file mode 100644 index 0000000000000..f876c71bdf4b2 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/lib/origin.ts @@ -0,0 +1,195 @@ +import { IBucket } from '@aws-cdk/aws-s3'; +import { Construct } from '@aws-cdk/core'; +import { CfnDistribution } from './cloudfront.generated'; +import { OriginProtocolPolicy } from './distribution'; +import { OriginAccessIdentity } from './origin_access_identity'; + +/** + * Properties to be used to create an Origin. Prefer to use one of the Origin.from* factory methods rather than + * instantiating an Origin directly from these properties. + * + * @experimental + */ +export interface OriginProps { + /** + * The domain name of the Amazon S3 bucket or HTTP server origin. + */ + readonly domainName: string; +} + +/** + * Options passed to Origin.bind(). + */ +interface OriginBindOptions { + /** + * The positional index of this origin within the distribution. Used for ensuring unique IDs. + */ + readonly originIndex: number; +} + +/** + * Represents a distribution origin, that describes the Amazon S3 bucket, HTTP server (for example, a web server), + * Amazon MediaStore, or other server from which CloudFront gets your files. + * + * @experimental + */ +export abstract class Origin { + + /** + * Creates a pre-configured origin for a S3 bucket. + * If this bucket has been configured for static website hosting, then `fromWebsiteBucket` should be used instead. + * + * An Origin Access Identity will be created and granted read access to the bucket. + * + * @param bucket the bucket to act as an origin. + */ + public static fromBucket(bucket: IBucket): Origin { + if (bucket.isWebsite) { + return new HttpOrigin({ + domainName: bucket.bucketWebsiteDomainName, + protocolPolicy: OriginProtocolPolicy.HTTP_ONLY, // S3 only supports HTTP for website buckets + }); + } else { + return new S3Origin({ domainName: bucket.bucketRegionalDomainName, bucket }); + } + } + + /** + * The domain name of the origin. + */ + public readonly domainName: string; + + private originId!: string; + + constructor(props: OriginProps) { + this.domainName = props.domainName; + } + + /** + * The unique id for this origin. + * + * Cannot be accesed until bind() is called. + */ + public get id(): string { + if (!this.originId) { throw new Error('Cannot access originId until `bind` is called.'); } + return this.originId; + } + + /** + * Binds the origin to the associated Distribution. Can be used to grant permissions, create dependent resources, etc. + * + * @internal + */ + public _bind(scope: Construct, options: OriginBindOptions): void { + this.originId = new Construct(scope, `Origin${options.originIndex}`).node.uniqueId; + } + + /** + * Creates and returns the CloudFormation representation of this origin. + * + * @internal + */ + public _renderOrigin(): CfnDistribution.OriginProperty { + const s3OriginConfig = this.renderS3OriginConfig(); + const customOriginConfig = this.renderCustomOriginConfig(); + + if (!s3OriginConfig && !customOriginConfig) { + throw new Error('Subclass must override and provide either s3OriginConfig or customOriginConfig'); + } + + return { + domainName: this.domainName, + id: this.id, + s3OriginConfig, + customOriginConfig, + }; + } + + // Overridden by sub-classes to provide S3 origin config. + protected renderS3OriginConfig(): CfnDistribution.S3OriginConfigProperty | undefined { + return undefined; + } + + // Overridden by sub-classes to provide custom origin config. + protected renderCustomOriginConfig(): CfnDistribution.CustomOriginConfigProperty | undefined { + return undefined; + } + +} + +/** + * Properties for an Origin backed by an S3 bucket + * + * @experimental + */ +export interface S3OriginProps extends OriginProps { + /** + * The bucket to use as an origin. + */ + readonly bucket: IBucket; +} + +/** + * An Origin specific to a S3 bucket (not configured for website hosting). + * + * Contains additional logic around bucket permissions and origin access identities. + * + * @experimental + */ +export class S3Origin extends Origin { + private readonly bucket: IBucket; + private originAccessIdentity!: OriginAccessIdentity; + + constructor(props: S3OriginProps) { + super(props); + this.bucket = props.bucket; + } + + /** @internal */ + public _bind(scope: Construct, options: OriginBindOptions) { + super._bind(scope, options); + if (!this.originAccessIdentity) { + this.originAccessIdentity = new OriginAccessIdentity(scope, `S3Origin${options.originIndex}`); + this.bucket.grantRead(this.originAccessIdentity); + } + } + + protected renderS3OriginConfig(): CfnDistribution.S3OriginConfigProperty | undefined { + return { originAccessIdentity: `origin-access-identity/cloudfront/${this.originAccessIdentity.originAccessIdentityName}` }; + } +} + +/** + * Properties for an Origin backed by an S3 website-configured bucket, load balancer, or custom HTTP server. + * + * @experimental + */ +export interface HttpOriginProps extends OriginProps { + /** + * Specifies the protocol (HTTP or HTTPS) that CloudFront uses to connect to the origin. + * + * @default OriginProtocolPolicy.HTTPS_ONLY + */ + readonly protocolPolicy?: OriginProtocolPolicy; +} + +/** + * An Origin for an HTTP server or S3 bucket configured for website hosting. + * + * @experimental + */ +export class HttpOrigin extends Origin { + + private readonly protocolPolicy?: OriginProtocolPolicy; + + constructor(props: HttpOriginProps) { + super(props); + this.protocolPolicy = props.protocolPolicy; + } + + protected renderCustomOriginConfig(): CfnDistribution.CustomOriginConfigProperty | undefined { + return { + originProtocolPolicy: this.protocolPolicy ?? OriginProtocolPolicy.HTTPS_ONLY, + }; + } +} diff --git a/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts new file mode 100644 index 0000000000000..59f0226a92a31 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts @@ -0,0 +1,55 @@ +import { CfnDistribution } from '../cloudfront.generated'; +import { BehaviorOptions, ViewerProtocolPolicy } from '../distribution'; +import { Origin } from '../origin'; + +/** + * Properties for specifying custom behaviors for origins. + */ +export interface CacheBehaviorProps extends BehaviorOptions { + /** + * The pattern (e.g., `images/*.jpg`) that specifies which requests to apply the behavior to. + * There must be exactly one behavior associated with each `Distribution` that has a path pattern + * of '*', which acts as the catch-all default behavior. + */ + readonly pathPattern: string; +} + +/** + * Allows configuring a variety of CloudFront functionality for a given URL path pattern. + * + * Note: This really should simply by called 'Behavior', but this name is already taken by the legacy + * CloudFrontWebDistribution implementation. + */ +export class CacheBehavior { + + /** + * Origin that this behavior will route traffic to. + */ + public readonly origin: Origin; + + constructor(private readonly props: CacheBehaviorProps) { + this.origin = props.origin; + } + + /** + * Creates and returns the CloudFormation representation of this behavior. + * This renders as a "CacheBehaviorProperty" regardless of if this is a default + * cache behavior or not, as the two are identical except that the pathPattern + * is omitted for the default cache behavior. + * + * @internal + */ + public _renderBehavior(): CfnDistribution.CacheBehaviorProperty { + return { + pathPattern: this.props.pathPattern, + targetOriginId: this.origin.id, + allowedMethods: this.props.allowedMethods?.methods ?? undefined, + forwardedValues: { + queryString: this.props.forwardQueryString ?? false, + queryStringCacheKeys: this.props.forwardQueryStringCacheKeys, + }, + viewerProtocolPolicy: ViewerProtocolPolicy.ALLOW_ALL, + }; + } + +} diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts index ab5eda0eb0834..e25bfce74409a 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts @@ -4,7 +4,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import { CfnDistribution } from './cloudfront.generated'; -import { IDistribution } from './distribution'; +import { IDistribution, OriginProtocolPolicy, PriceClass, ViewerProtocolPolicy } from './distribution'; import { IOriginAccessIdentity } from './origin_access_identity'; export enum HttpVersion { @@ -12,24 +12,6 @@ export enum HttpVersion { HTTP2 = 'http2' } -/** - * The price class determines how many edge locations CloudFront will use for your distribution. - */ -export enum PriceClass { - PRICE_CLASS_100 = 'PriceClass_100', - PRICE_CLASS_200 = 'PriceClass_200', - PRICE_CLASS_ALL = 'PriceClass_All' -} - -/** - * How HTTPs should be handled with your distribution. - */ -export enum ViewerProtocolPolicy { - HTTPS_ONLY = 'https-only', - REDIRECT_TO_HTTPS = 'redirect-to-https', - ALLOW_ALL = 'allow-all' -} - /** * Configuration for custom domain names * @@ -140,15 +122,31 @@ export interface LoggingConfiguration { * One or the other must be passed, and it is invalid to pass both in the same SourceConfiguration. */ export interface SourceConfiguration { + /** + * The number of times that CloudFront attempts to connect to the origin. + * You can specify 1, 2, or 3 as the number of attempts. + * + * @default 3 + */ + readonly connectionAttempts?: number; + + /** + * The number of seconds that CloudFront waits when trying to establish a connection to the origin. + * You can specify a number of seconds between 1 and 10 (inclusive). + * + * @default cdk.Duration.seconds(10) + */ + readonly connectionTimeout?: cdk.Duration; + /** * An s3 origin source - if you're using s3 for your assets */ - readonly s3OriginSource?: S3OriginConfig + readonly s3OriginSource?: S3OriginConfig; /** * A custom origin source - for all non-s3 sources. */ - readonly customOriginSource?: CustomOriginConfig, + readonly customOriginSource?: CustomOriginConfig; /** * The behaviors associated with this source. @@ -161,7 +159,7 @@ export interface SourceConfiguration { * * @default / */ - readonly originPath?: string, + readonly originPath?: string; /** * Any additional headers to pass to the origin @@ -231,12 +229,6 @@ export enum OriginSslPolicy { TLS_V1_2 = 'TLSv1.2', } -export enum OriginProtocolPolicy { - HTTP_ONLY = 'http-only', - MATCH_VIEWER = 'match-viewer', - HTTPS_ONLY = 'https-only', -} - /** * S3 origin configuration for CloudFront */ @@ -517,6 +509,7 @@ export class GeoRestriction { } locations.forEach(location => { if (!GeoRestriction.LOCATION_REGEX.test(location)) { + // eslint-disable-next-line max-len throw new Error(`Invalid location format for location: ${location}, location should be two-letter and uppercase country ISO 3166-1-alpha-2 code`); } }); @@ -672,9 +665,9 @@ interface BehaviorWithOrigin extends Behavior { * * You can customize the distribution using additional properties from the CloudFrontWebDistributionProps interface. * - * + * @resource AWS::CloudFront::Distribution */ -export class CloudFrontWebDistribution extends cdk.Construct implements IDistribution { +export class CloudFrontWebDistribution extends cdk.Resource implements IDistribution { /** * The logging bucket for this CloudFront distribution. * If logging is not enabled for this distribution - this property will be undefined. @@ -684,10 +677,19 @@ export class CloudFrontWebDistribution extends cdk.Construct implements IDistrib /** * The domain name created by CloudFront for this distribution. * If you are using aliases for your distribution, this is the domainName your DNS records should point to. - * (In Route53, you could create an ALIAS record to this value, for example. ) + * (In Route53, you could create an ALIAS record to this value, for example.) + * + * @deprecated - Use `distributionDomainName` instead. */ public readonly domainName: string; + /** + * The domain name created by CloudFront for this distribution. + * If you are using aliases for your distribution, this is the domainName your DNS records should point to. + * (In Route53, you could create an ALIAS record to this value, for example.) + */ + public readonly distributionDomainName: string; + /** * The distribution ID for this distribution. */ @@ -723,7 +725,7 @@ export class CloudFrontWebDistribution extends cdk.Construct implements IDistrib httpVersion: props.httpVersion || HttpVersion.HTTP2, priceClass: props.priceClass || PriceClass.PRICE_CLASS_100, ipv6Enabled: (props.enableIpV6 !== undefined) ? props.enableIpV6 : true, - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len customErrorResponses: props.errorConfigurations, // TODO: validation : https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-customerrorresponse.html#cfn-cloudfront-distribution-customerrorresponse-errorcachingminttl webAclId: props.webACLId, }; @@ -771,6 +773,16 @@ export class CloudFrontWebDistribution extends cdk.Construct implements IDistrib } } + const connectionAttempts = originConfig.connectionAttempts ?? 3; + if (connectionAttempts < 1 || 3 < connectionAttempts || !Number.isInteger(connectionAttempts)) { + throw new Error('connectionAttempts: You can specify 1, 2, or 3 as the number of attempts.'); + } + + const connectionTimeout = (originConfig.connectionTimeout || cdk.Duration.seconds(10)).toSeconds(); + if (connectionTimeout < 1 || 10 < connectionTimeout || !Number.isInteger(connectionTimeout)) { + throw new Error('connectionTimeout: You can specify a number of seconds between 1 and 10 (inclusive).'); + } + const originProperty: CfnDistribution.OriginProperty = { id: originId, domainName: originConfig.s3OriginSource @@ -791,6 +803,8 @@ export class CloudFrontWebDistribution extends cdk.Construct implements IDistrib originSslProtocols: originConfig.customOriginSource.allowedOriginSSLVersions || [OriginSslPolicy.TLS_V1_2], } : undefined, + connectionAttempts, + connectionTimeout, }; for (const behavior of originConfig.behaviors) { @@ -855,7 +869,7 @@ export class CloudFrontWebDistribution extends cdk.Construct implements IDistrib const validProtocols = this.VALID_SSL_PROTOCOLS[sslSupportMethod as SSLMethod]; if (validProtocols.indexOf(minimumProtocolVersion.toString()) === -1) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`${minimumProtocolVersion} is not compabtible with sslMethod ${sslSupportMethod}.\n\tValid Protocols are: ${validProtocols.join(', ')}`); } } @@ -892,6 +906,7 @@ export class CloudFrontWebDistribution extends cdk.Construct implements IDistrib const distribution = new CfnDistribution(this, 'CFDistribution', { distributionConfig }); this.node.defaultChild = distribution; this.domainName = distribution.attrDomainName; + this.distributionDomainName = distribution.attrDomainName; this.distributionId = distribution.ref; } diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index 0ac31be17d436..1e12b8d85ce67 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -47,7 +47,8 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::CloudFront" + "cloudformation": "AWS::CloudFront", + "jest": true }, "keywords": [ "aws", @@ -63,12 +64,11 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.711.0", + "aws-sdk": "^2.715.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "nodeunit": "^0.11.3", + "nodeunit-shim": "0.0.0", "pkglint": "0.0.0" }, "dependencies": { @@ -94,11 +94,23 @@ "node": ">= 10.13.0 <13 || >=13.7.0" }, "stability": "experimental", - "maturity": "experimental", + "features": [ + { + "name": "Higher level constructs for Distribution", + "stability": "Experimental" + }, + { + "name": "Higher level constructs for CloudFrontWebDistribution", + "stability": "Stable" + } + ], "awslint": { "exclude": [ + "props-physical-name:@aws-cdk/aws-cloudfront.Distribution", + "props-physical-name:@aws-cdk/aws-cloudfront.DistributionProps", + "props-physical-name:@aws-cdk/aws-cloudfront.CloudFrontWebDistribution", + "props-physical-name:@aws-cdk/aws-cloudfront.CloudFrontWebDistributionProps", "props-physical-name:@aws-cdk/aws-cloudfront.OriginAccessIdentityProps", - "docs-public-apis:@aws-cdk/aws-cloudfront.OriginProtocolPolicy", "docs-public-apis:@aws-cdk/aws-cloudfront.ViewerProtocolPolicy.ALLOW_ALL", "docs-public-apis:@aws-cdk/aws-cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS", "props-default-doc:@aws-cdk/aws-cloudfront.Behavior.isDefaultBehavior", @@ -118,18 +130,11 @@ "docs-public-apis:@aws-cdk/aws-cloudfront.HttpVersion.HTTP1_1", "docs-public-apis:@aws-cdk/aws-cloudfront.HttpVersion.HTTP2", "docs-public-apis:@aws-cdk/aws-cloudfront.LambdaEdgeEventType", - "docs-public-apis:@aws-cdk/aws-cloudfront.ViewerProtocolPolicy.HTTPS_ONLY", - "docs-public-apis:@aws-cdk/aws-cloudfront.OriginProtocolPolicy.HTTP_ONLY", - "docs-public-apis:@aws-cdk/aws-cloudfront.OriginProtocolPolicy.MATCH_VIEWER", - "docs-public-apis:@aws-cdk/aws-cloudfront.OriginProtocolPolicy.HTTPS_ONLY", "docs-public-apis:@aws-cdk/aws-cloudfront.OriginSslPolicy", "docs-public-apis:@aws-cdk/aws-cloudfront.OriginSslPolicy.SSL_V3", "docs-public-apis:@aws-cdk/aws-cloudfront.OriginSslPolicy.TLS_V1", "docs-public-apis:@aws-cdk/aws-cloudfront.OriginSslPolicy.TLS_V1_1", "docs-public-apis:@aws-cdk/aws-cloudfront.OriginSslPolicy.TLS_V1_2", - "docs-public-apis:@aws-cdk/aws-cloudfront.PriceClass.PRICE_CLASS_100", - "docs-public-apis:@aws-cdk/aws-cloudfront.PriceClass.PRICE_CLASS_200", - "docs-public-apis:@aws-cdk/aws-cloudfront.PriceClass.PRICE_CLASS_ALL", "docs-public-apis:@aws-cdk/aws-cloudfront.SSLMethod.SNI", "docs-public-apis:@aws-cdk/aws-cloudfront.SSLMethod.VIP", "docs-public-apis:@aws-cdk/aws-cloudfront.SecurityPolicyProtocol.SSL_V3", diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts new file mode 100644 index 0000000000000..86c91c2254cf8 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts @@ -0,0 +1,300 @@ +import '@aws-cdk/assert/jest'; +import * as acm from '@aws-cdk/aws-certificatemanager'; +import * as s3 from '@aws-cdk/aws-s3'; +import { App, Duration, Stack } from '@aws-cdk/core'; +import { Distribution, Origin, PriceClass } from '../lib'; + +let app: App; +let stack: Stack; + +beforeEach(() => { + app = new App(); + stack = new Stack(app, 'Stack', { + env: { account: '1234', region: 'testregion' }, + }); +}); + +test('minimal example renders correctly', () => { + const origin = Origin.fromBucket(new s3.Bucket(stack, 'Bucket')); + new Distribution(stack, 'MyDist', { defaultBehavior: { origin } }); + + expect(stack).toHaveResource('AWS::CloudFront::Distribution', { + DistributionConfig: { + DefaultCacheBehavior: { + ForwardedValues: { QueryString: false }, + TargetOriginId: 'StackMyDistOrigin1D6D5E535', + ViewerProtocolPolicy: 'allow-all', + }, + Enabled: true, + Origins: [{ + DomainName: { 'Fn::GetAtt': [ 'Bucket83908E77', 'RegionalDomainName' ] }, + Id: 'StackMyDistOrigin1D6D5E535', + S3OriginConfig: { + OriginAccessIdentity: { 'Fn::Join': [ '', + [ 'origin-access-identity/cloudfront/', { Ref: 'MyDistS3Origin1ED86A27E' } ], + ]}, + }, + }], + }, + }); +}); + +describe('multiple behaviors', () => { + + test('a second behavior can\'t be specified with the catch-all path pattern', () => { + const origin = Origin.fromBucket(new s3.Bucket(stack, 'Bucket')); + + expect(() => { + new Distribution(stack, 'MyDist', { + defaultBehavior: { origin }, + additionalBehaviors: { + '*': { origin }, + }, + }); + }).toThrow(/Only the default behavior can have a path pattern of \'*\'/); + }); + + test('a second behavior can be added to the original origin', () => { + const origin = Origin.fromBucket(new s3.Bucket(stack, 'Bucket')); + new Distribution(stack, 'MyDist', { + defaultBehavior: { origin }, + additionalBehaviors: { + 'api/*': { origin }, + }, + }); + + expect(stack).toHaveResource('AWS::CloudFront::Distribution', { + DistributionConfig: { + DefaultCacheBehavior: { + ForwardedValues: { QueryString: false }, + TargetOriginId: 'StackMyDistOrigin1D6D5E535', + ViewerProtocolPolicy: 'allow-all', + }, + CacheBehaviors: [{ + PathPattern: 'api/*', + ForwardedValues: { QueryString: false }, + TargetOriginId: 'StackMyDistOrigin1D6D5E535', + ViewerProtocolPolicy: 'allow-all', + }], + Enabled: true, + Origins: [{ + DomainName: { 'Fn::GetAtt': [ 'Bucket83908E77', 'RegionalDomainName' ] }, + Id: 'StackMyDistOrigin1D6D5E535', + S3OriginConfig: { + OriginAccessIdentity: { 'Fn::Join': [ '', + [ 'origin-access-identity/cloudfront/', { Ref: 'MyDistS3Origin1ED86A27E' } ], + ]}, + }, + }], + }, + }); + }); + + test('a second behavior can be added to a secondary origin', () => { + const origin = Origin.fromBucket(new s3.Bucket(stack, 'Bucket')); + const origin2 = Origin.fromBucket(new s3.Bucket(stack, 'Bucket2')); + new Distribution(stack, 'MyDist', { + defaultBehavior: { origin }, + additionalBehaviors: { + 'api/*': { origin: origin2 }, + }, + }); + + expect(stack).toHaveResource('AWS::CloudFront::Distribution', { + DistributionConfig: { + DefaultCacheBehavior: { + ForwardedValues: { QueryString: false }, + TargetOriginId: 'StackMyDistOrigin1D6D5E535', + ViewerProtocolPolicy: 'allow-all', + }, + CacheBehaviors: [{ + PathPattern: 'api/*', + ForwardedValues: { QueryString: false }, + TargetOriginId: 'StackMyDistOrigin20B96F3AD', + ViewerProtocolPolicy: 'allow-all', + }], + Enabled: true, + Origins: [{ + DomainName: { 'Fn::GetAtt': [ 'Bucket83908E77', 'RegionalDomainName' ] }, + Id: 'StackMyDistOrigin1D6D5E535', + S3OriginConfig: { + OriginAccessIdentity: { 'Fn::Join': [ '', + [ 'origin-access-identity/cloudfront/', { Ref: 'MyDistS3Origin1ED86A27E' } ], + ]}, + }, + }, + { + DomainName: { 'Fn::GetAtt': [ 'Bucket25524B414', 'RegionalDomainName' ] }, + Id: 'StackMyDistOrigin20B96F3AD', + S3OriginConfig: { + OriginAccessIdentity: { 'Fn::Join': [ '', + [ 'origin-access-identity/cloudfront/', { Ref: 'MyDistS3Origin2E88F08BB' } ], + ]}, + }, + }], + }, + }); + }); + + test('behavior creation order is preserved', () => { + const origin = Origin.fromBucket(new s3.Bucket(stack, 'Bucket')); + const origin2 = Origin.fromBucket(new s3.Bucket(stack, 'Bucket2')); + const dist = new Distribution(stack, 'MyDist', { + defaultBehavior: { origin }, + additionalBehaviors: { + 'api/1*': { origin: origin2 }, + }, + }); + dist.addBehavior('api/2*', origin); + + expect(stack).toHaveResource('AWS::CloudFront::Distribution', { + DistributionConfig: { + DefaultCacheBehavior: { + ForwardedValues: { QueryString: false }, + TargetOriginId: 'StackMyDistOrigin1D6D5E535', + ViewerProtocolPolicy: 'allow-all', + }, + CacheBehaviors: [{ + PathPattern: 'api/1*', + ForwardedValues: { QueryString: false }, + TargetOriginId: 'StackMyDistOrigin20B96F3AD', + ViewerProtocolPolicy: 'allow-all', + }, + { + PathPattern: 'api/2*', + ForwardedValues: { QueryString: false }, + TargetOriginId: 'StackMyDistOrigin1D6D5E535', + ViewerProtocolPolicy: 'allow-all', + }], + Enabled: true, + Origins: [{ + DomainName: { 'Fn::GetAtt': [ 'Bucket83908E77', 'RegionalDomainName' ] }, + Id: 'StackMyDistOrigin1D6D5E535', + S3OriginConfig: { + OriginAccessIdentity: { 'Fn::Join': [ '', + [ 'origin-access-identity/cloudfront/', { Ref: 'MyDistS3Origin1ED86A27E' } ], + ]}, + }, + }, + { + DomainName: { 'Fn::GetAtt': [ 'Bucket25524B414', 'RegionalDomainName' ] }, + Id: 'StackMyDistOrigin20B96F3AD', + S3OriginConfig: { + OriginAccessIdentity: { 'Fn::Join': [ '', + [ 'origin-access-identity/cloudfront/', { Ref: 'MyDistS3Origin2E88F08BB' } ], + ]}, + }, + }], + }, + }); + }); + +}); + +describe('certificates', () => { + + test('should fail if using an imported certificate from outside of us-east-1', () => { + const origin = Origin.fromBucket(new s3.Bucket(stack, 'Bucket')); + const certificate = acm.Certificate.fromCertificateArn(stack, 'Cert', 'arn:aws:acm:eu-west-1:123456789012:certificate/12345678-1234-1234-1234-123456789012'); + + expect(() => { + new Distribution(stack, 'Dist', { + defaultBehavior: { origin }, + certificate, + }); + }).toThrow(/Distribution certificates must be in the us-east-1 region/); + }); + + test('adding a certificate renders the correct ViewerCertificate property', () => { + const certificate = acm.Certificate.fromCertificateArn(stack, 'Cert', 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012'); + + new Distribution(stack, 'Dist', { + defaultBehavior: { origin: Origin.fromBucket(new s3.Bucket(stack, 'Bucket')) }, + certificate, + }); + + expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', { + DistributionConfig: { + ViewerCertificate: { + AcmCertificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012', + }, + }, + }); + }); +}); + +describe('custom error responses', () => { + + test('should fail if responsePagePath is defined but responseCode is not', () => { + const origin = Origin.fromBucket(new s3.Bucket(stack, 'Bucket')); + + expect(() => { + new Distribution(stack, 'Dist', { + defaultBehavior: { origin }, + errorResponses: [{ + httpStatus: 404, + responsePagePath: '/errors/404.html', + }], + }); + }).toThrow(/\'responseCode\' must be provided if \'responsePagePath\' is defined/); + }); + + test('should fail if only the error code is provided', () => { + const origin = Origin.fromBucket(new s3.Bucket(stack, 'Bucket')); + + expect(() => { + new Distribution(stack, 'Dist', { + defaultBehavior: { origin }, + errorResponses: [{ httpStatus: 404 }], + }); + }).toThrow(/A custom error response without either a \'responseCode\' or \'errorCachingMinTtl\' is not valid./); + }); + + test('should render the array of error configs if provided', () => { + const origin = Origin.fromBucket(new s3.Bucket(stack, 'Bucket')); + new Distribution(stack, 'Dist', { + defaultBehavior: { origin }, + errorResponses: [{ + httpStatus: 404, + responseHttpStatus: 404, + responsePagePath: '/errors/404.html', + }, + { + httpStatus: 500, + ttl: Duration.seconds(2), + }], + }); + + expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', { + DistributionConfig: { + CustomErrorResponses: [ + { + ErrorCode: 404, + ResponseCode: 404, + ResponsePagePath: '/errors/404.html', + }, + { + ErrorCachingMinTTL: 2, + ErrorCode: 500, + }, + ], + }, + }); + }); + +}); + +test('price class is included if provided', () => { + const origin = Origin.fromBucket(new s3.Bucket(stack, 'Bucket')); + new Distribution(stack, 'Dist', { + defaultBehavior: { origin }, + priceClass: PriceClass.PRICE_CLASS_200, + }); + + expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', { + DistributionConfig: { + PriceClass: 'PriceClass_200', + }, + }); + +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-bucket-logging.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-bucket-logging.expected.json index 70780cca41033..36a334898a57f 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-bucket-logging.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-bucket-logging.expected.json @@ -44,6 +44,8 @@ }, "Origins": [ { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, "CustomOriginConfig": { "HTTPPort": 80, "HTTPSPort": 443, @@ -114,6 +116,8 @@ }, "Origins": [ { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, "CustomOriginConfig": { "HTTPPort": 80, "HTTPSPort": 443, diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-custom-s3.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-custom-s3.expected.json index 954b13e1edc67..7699faf6df792 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-custom-s3.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-custom-s3.expected.json @@ -72,6 +72,8 @@ "IPV6Enabled": true, "Origins": [ { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, "CustomOriginConfig": { "HTTPPort": 80, "HTTPSPort": 443, diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-custom.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-custom.expected.json index 92071bb9ef237..5943d0d822d3c 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-custom.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-custom.expected.json @@ -29,6 +29,8 @@ "IPV6Enabled": true, "Origins": [ { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, "CustomOriginConfig": { "HTTPPort": 80, "HTTPSPort": 443, diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-empty-root.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-empty-root.expected.json index a5298a4968004..9b20011d6a9a1 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-empty-root.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-empty-root.expected.json @@ -29,6 +29,8 @@ "IPV6Enabled": true, "Origins": [ { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, "CustomOriginConfig": { "HTTPPort": 80, "HTTPSPort": 443, diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.expected.json index 25ae7addfd317..9307879bca924 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.expected.json @@ -34,6 +34,8 @@ "IPV6Enabled": true, "Origins": [ { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, "DomainName": { "Fn::GetAtt": [ "Bucket83908E77", diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.ts index 453b51df57fa4..89936cad97485 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.ts @@ -16,7 +16,7 @@ new cloudfront.CloudFrontWebDistribution(stack, 'MyDistribution', { s3OriginSource: { s3BucketSource: sourceBucket, }, - behaviors : [ {isDefaultBehavior: true}], + behaviors: [ {isDefaultBehavior: true}], }, ], geoRestriction: cloudfront.GeoRestriction.whitelist('US', 'UK'), diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-ipv6-disabled.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-ipv6-disabled.expected.json index 5ffa5da872430..30bc881df48f3 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-ipv6-disabled.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-ipv6-disabled.expected.json @@ -34,6 +34,8 @@ "IPV6Enabled": false, "Origins": [ { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, "DomainName": { "Fn::GetAtt": [ "Bucket83908E77", diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-ipv6-disabled.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-ipv6-disabled.ts index 889c412bfb44f..10f17b6189ddb 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-ipv6-disabled.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-ipv6-disabled.ts @@ -17,7 +17,7 @@ new cloudfront.CloudFrontWebDistribution(stack, 'MyDistribution', { s3OriginSource: { s3BucketSource: sourceBucket, }, - behaviors : [ {isDefaultBehavior: true}], + behaviors: [ {isDefaultBehavior: true}], }, ], enableIpV6: false, diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-lambda-association.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-lambda-association.expected.json index cce226bd3a090..eb45a5dd3192d 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-lambda-association.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-lambda-association.expected.json @@ -107,6 +107,8 @@ "IPV6Enabled": true, "Origins": [ { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, "DomainName": { "Fn::GetAtt": [ "Bucket83908E77", diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-lambda-association.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-lambda-association.ts index 237732d8128b8..e1581ada8fc82 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-lambda-association.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-lambda-association.ts @@ -27,7 +27,7 @@ new cloudfront.CloudFrontWebDistribution(stack, 'MyDistribution', { s3OriginSource: { s3BucketSource: sourceBucket, }, - behaviors : [ {isDefaultBehavior: true, lambdaFunctionAssociations: [{ + behaviors: [ {isDefaultBehavior: true, lambdaFunctionAssociations: [{ eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST, lambdaFunction: lambdaVersion, }]}], diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3.expected.json index b1ddafe584cf3..8f6357e72272e 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3.expected.json @@ -102,6 +102,8 @@ "IPV6Enabled": true, "Origins": [ { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, "DomainName": { "Fn::GetAtt": [ "Bucket83908E77", diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.expected.json index 861887bd54f48..068f2e3492856 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.expected.json @@ -32,6 +32,8 @@ "IPV6Enabled": true, "Origins": [ { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, "CustomOriginConfig": { "HTTPPort": 80, "HTTPSPort": 443, diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront.expected.json index a5f3615733d82..acadf8baf6866 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront.expected.json @@ -34,6 +34,8 @@ "IPV6Enabled": true, "Origins": [ { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, "DomainName": { "Fn::GetAtt": [ "Bucket83908E77", diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront.ts index 28dc4b478b33b..534ad08f9042d 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront.ts @@ -17,7 +17,7 @@ new cloudfront.CloudFrontWebDistribution(stack, 'MyDistribution', { s3OriginSource: { s3BucketSource: sourceBucket, }, - behaviors : [ {isDefaultBehavior: true}], + behaviors: [ {isDefaultBehavior: true}], }, ], }); diff --git a/packages/@aws-cdk/aws-cloudfront/test/test.oai.ts b/packages/@aws-cdk/aws-cloudfront/test/oai.test.ts similarity index 93% rename from packages/@aws-cdk/aws-cloudfront/test/test.oai.ts rename to packages/@aws-cdk/aws-cloudfront/test/oai.test.ts index bd067aa902f03..3f3a95327a9f1 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/test.oai.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/oai.test.ts @@ -1,11 +1,11 @@ import { expect } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import { OriginAccessIdentity } from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ -export = { +nodeunitShim({ 'Origin Access Identity with automatic comment'(test: Test) { const stack = new cdk.Stack(); @@ -66,4 +66,4 @@ export = { test.done(); }, -}; +}); diff --git a/packages/@aws-cdk/aws-cloudfront/test/origin.test.ts b/packages/@aws-cdk/aws-cloudfront/test/origin.test.ts new file mode 100644 index 0000000000000..b02a10e6300db --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/origin.test.ts @@ -0,0 +1,73 @@ +import '@aws-cdk/assert/jest'; +import * as s3 from '@aws-cdk/aws-s3'; +import { App, Stack } from '@aws-cdk/core'; +import { Distribution, Origin } from '../lib'; + +let app: App; +let stack: Stack; + +beforeEach(() => { + app = new App(); + stack = new Stack(app, 'Stack', { + env: { account: '1234', region: 'testregion' }, + }); +}); + +describe('fromBucket', () => { + + test('as bucket, renders all properties, including S3Origin config', () => { + const bucket = new s3.Bucket(stack, 'Bucket'); + + const origin = Origin.fromBucket(bucket); + origin._bind(stack, { originIndex: 0 }); + + expect(origin._renderOrigin()).toEqual({ + id: 'StackOrigin029E19582', + domainName: bucket.bucketRegionalDomainName, + s3OriginConfig: { + originAccessIdentity: 'origin-access-identity/cloudfront/${Token[TOKEN.69]}', + }, + }); + }); + + test('as bucket, creates an OriginAccessIdentity and grants read permissions on the bucket', () => { + const bucket = new s3.Bucket(stack, 'Bucket'); + + const origin = Origin.fromBucket(bucket); + new Distribution(stack, 'Dist', { defaultBehavior: { origin } }); + + expect(stack).toHaveResourceLike('AWS::CloudFront::CloudFrontOriginAccessIdentity', { + CloudFrontOriginAccessIdentityConfig: { + Comment: 'Allows CloudFront to reach the bucket', + }, + }); + expect(stack).toHaveResourceLike('AWS::S3::BucketPolicy', { + PolicyDocument: { + Statement: [{ + Principal: { + CanonicalUser: { 'Fn::GetAtt': [ 'DistS3Origin1C4519663', 'S3CanonicalUserId' ] }, + }, + }], + }, + }); + }); + + test('as website buvcket, renders all properties, including custom origin config', () => { + const bucket = new s3.Bucket(stack, 'Bucket', { + websiteIndexDocument: 'index.html', + }); + + const origin = Origin.fromBucket(bucket); + origin._bind(stack, { originIndex: 0 }); + + expect(origin._renderOrigin()).toEqual({ + id: 'StackOrigin029E19582', + domainName: bucket.bucketWebsiteDomainName, + customOriginConfig: { + originProtocolPolicy: 'http-only', + }, + }); + }); + +}); + diff --git a/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts b/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts new file mode 100644 index 0000000000000..b70d3ef02c590 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts @@ -0,0 +1,54 @@ +import '@aws-cdk/assert/jest'; +import * as s3 from '@aws-cdk/aws-s3'; +import { App, Stack } from '@aws-cdk/core'; +import { AllowedMethods, Origin } from '../../lib'; +import { CacheBehavior } from '../../lib/private/cache-behavior'; + +let app: App; +let stack: Stack; + +beforeEach(() => { + app = new App(); + stack = new Stack(app, 'Stack', { + env: { account: '1234', region: 'testregion' }, + }); +}); + +test('renders the minimum template with an origin and path specified', () => { + const origin = Origin.fromBucket(new s3.Bucket(stack, 'MyBucket')); + const behavior = new CacheBehavior({ + origin, + pathPattern: '*', + }); + origin._bind(stack, { originIndex: 0 }); + + expect(behavior._renderBehavior()).toEqual({ + targetOriginId: behavior.origin.id, + pathPattern: '*', + forwardedValues: { queryString: false }, + viewerProtocolPolicy: 'allow-all', + }); +}); + +test('renders with all properties specified', () => { + const origin = Origin.fromBucket(new s3.Bucket(stack, 'MyBucket')); + const behavior = new CacheBehavior({ + origin, + pathPattern: '*', + allowedMethods: AllowedMethods.ALLOW_ALL, + forwardQueryString: true, + forwardQueryStringCacheKeys: ['user_id', 'auth'], + }); + origin._bind(stack, { originIndex: 0 }); + + expect(behavior._renderBehavior()).toEqual({ + targetOriginId: behavior.origin.id, + pathPattern: '*', + allowedMethods: ['GET', 'HEAD', 'OPTIONS', 'PUT', 'PATCH', 'POST', 'DELETE'], + forwardedValues: { + queryString: true, + queryStringCacheKeys: ['user_id', 'auth'], + }, + viewerProtocolPolicy: 'allow-all', + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/test.basic.ts b/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts similarity index 82% rename from packages/@aws-cdk/aws-cloudfront/test/test.basic.ts rename to packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts index 39366da64117d..c8b2fb322995f 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/test.basic.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts @@ -3,7 +3,7 @@ import * as certificatemanager from '@aws-cdk/aws-certificatemanager'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import { CfnDistribution, CloudFrontWebDistribution, @@ -15,9 +15,9 @@ import { ViewerProtocolPolicy, } from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ -export = { +nodeunitShim({ 'distribution with custom origin adds custom origin'(test: Test) { const stack = new cdk.Stack(); @@ -82,6 +82,8 @@ export = { 'TLSv1.2', ], }, + 'ConnectionAttempts': 3, + 'ConnectionTimeout': 10, 'DomainName': 'myorigin.com', 'Id': 'origin1', 'OriginCustomHeaders': [ @@ -139,6 +141,8 @@ export = { 'DefaultRootObject': 'index.html', 'Origins': [ { + 'ConnectionAttempts': 3, + 'ConnectionTimeout': 10, 'DomainName': { 'Fn::GetAtt': [ 'Bucket83908E77', @@ -215,6 +219,8 @@ export = { 'DefaultRootObject': 'index.html', 'Origins': [ { + 'ConnectionAttempts': 3, + 'ConnectionTimeout': 10, 'DomainName': { 'Fn::GetAtt': [ 'Bucket83908E77', @@ -294,6 +300,8 @@ export = { 'DefaultRootObject': 'index.html', 'Origins': [ { + 'ConnectionAttempts': 3, + 'ConnectionTimeout': 10, 'DomainName': { 'Fn::GetAtt': [ 'Bucket83908E77', @@ -370,6 +378,8 @@ export = { 'DefaultRootObject': 'index.html', 'Origins': [ { + 'ConnectionAttempts': 3, + 'ConnectionTimeout': 10, 'DomainName': { 'Fn::GetAtt': [ 'Bucket83908E77', @@ -809,7 +819,7 @@ export = { originConfigs: [ { s3OriginSource: { s3BucketSource: sourceBucket }, - behaviors : [ + behaviors: [ { isDefaultBehavior: true, lambdaFunctionAssociations: [ { @@ -859,7 +869,7 @@ export = { originConfigs: [ { s3OriginSource: { s3BucketSource: sourceBucket }, - behaviors : [ + behaviors: [ { isDefaultBehavior: true, lambdaFunctionAssociations: [ { @@ -878,7 +888,7 @@ export = { }, 'geo restriction': { - 'success' : { + 'success': { 'whitelist'(test: Test) { const stack = new cdk.Stack(); const sourceBucket = new s3.Bucket(stack, 'Bucket'); @@ -905,6 +915,8 @@ export = { 'DefaultRootObject': 'index.html', 'Origins': [ { + 'ConnectionAttempts': 3, + 'ConnectionTimeout': 10, 'DomainName': { 'Fn::GetAtt': [ 'Bucket83908E77', @@ -979,6 +991,8 @@ export = { 'DefaultRootObject': 'index.html', 'Origins': [ { + 'ConnectionAttempts': 3, + 'ConnectionTimeout': 10, 'DomainName': { 'Fn::GetAtt': [ 'Bucket83908E77', @@ -1032,25 +1046,175 @@ export = { 'throws if locations is empty array'(test: Test) { test.throws(() => { GeoRestriction.whitelist(); - }, 'Should provide at least 1 location'); + }, /Should provide at least 1 location/); test.throws(() => { GeoRestriction.blacklist(); - }, 'Should provide at least 1 location'); + }, /Should provide at least 1 location/); test.done(); }, 'throws if locations format is wrong'(test: Test) { test.throws(() => { GeoRestriction.whitelist('us'); - }, 'Invalid location format for location: us, location should be two-letter and uppercase country ISO 3166-1-alpha-2 code'); + }, /Invalid location format for location: us, location should be two-letter and uppercase country ISO 3166-1-alpha-2 code/); test.throws(() => { GeoRestriction.blacklist('us'); - }, 'Invalid location format for location: us, location should be two-letter and uppercase country ISO 3166-1-alpha-2 code'); + }, /Invalid location format for location: us, location should be two-letter and uppercase country ISO 3166-1-alpha-2 code/); + + test.done(); + }, + }, + }, + 'Connection behaviors between CloudFront and your origin': { + 'success': { + 'connectionAttempts = 1'(test: Test) { + const stack = new cdk.Stack(); + test.doesNotThrow(() => { + new CloudFrontWebDistribution(stack, 'Distribution', { + originConfigs: [{ + behaviors: [{ isDefaultBehavior: true }], + connectionAttempts: 1, + customOriginSource: { domainName: 'myorigin.com' }, + }], + }); + }, /connectionAttempts: You can specify 1, 2, or 3 as the number of attempts./); + test.done(); + }, + '3 = connectionAttempts'(test: Test) { + const stack = new cdk.Stack(); + test.doesNotThrow(() => { + new CloudFrontWebDistribution(stack, 'Distribution', { + originConfigs: [{ + behaviors: [{ isDefaultBehavior: true }], + connectionAttempts: 3, + customOriginSource: { domainName: 'myorigin.com' }, + }], + }); + }, /connectionAttempts: You can specify 1, 2, or 3 as the number of attempts./); + test.done(); + }, + 'connectionTimeout = 1'(test: Test) { + const stack = new cdk.Stack(); + test.doesNotThrow(() => { + new CloudFrontWebDistribution(stack, 'Distribution', { + originConfigs: [{ + behaviors: [{ isDefaultBehavior: true }], + connectionTimeout: cdk.Duration.seconds(1), + customOriginSource: { domainName: 'myorigin.com' }, + }], + }); + }, /connectionTimeout: You can specify a number of seconds between 1 and 10 (inclusive)./); + test.done(); + }, + '10 = connectionTimeout'(test: Test) { + const stack = new cdk.Stack(); + test.doesNotThrow(() => { + new CloudFrontWebDistribution(stack, 'Distribution', { + originConfigs: [{ + behaviors: [{ isDefaultBehavior: true }], + connectionTimeout: cdk.Duration.seconds(10), + customOriginSource: { domainName: 'myorigin.com' }, + }], + }); + }, /connectionTimeout: You can specify a number of seconds between 1 and 10 (inclusive)./); + test.done(); + }, + }, + 'errors': { + 'connectionAttempts = 1.1'(test: Test) { + const stack = new cdk.Stack(); + test.throws(() => { + new CloudFrontWebDistribution(stack, 'Distribution', { + originConfigs: [{ + behaviors: [{ isDefaultBehavior: true }], + connectionAttempts: 1.1, + customOriginSource: { domainName: 'myorigin.com' }, + }], + }); + }, /connectionAttempts: You can specify 1, 2, or 3 as the number of attempts./); + test.done(); + }, + 'connectionAttempts = -1'(test: Test) { + const stack = new cdk.Stack(); + test.throws(() => { + new CloudFrontWebDistribution(stack, 'Distribution', { + originConfigs: [{ + behaviors: [{ isDefaultBehavior: true }], + connectionAttempts: -1, + customOriginSource: { domainName: 'myorigin.com' }, + }], + }); + }, /connectionAttempts: You can specify 1, 2, or 3 as the number of attempts./); + test.done(); + }, + 'connectionAttempts < 1'(test: Test) { + const stack = new cdk.Stack(); + test.throws(() => { + new CloudFrontWebDistribution(stack, 'Distribution', { + originConfigs: [{ + behaviors: [{ isDefaultBehavior: true }], + connectionAttempts: 0, + customOriginSource: { domainName: 'myorigin.com' }, + }], + }); + }, /connectionAttempts: You can specify 1, 2, or 3 as the number of attempts./); + test.done(); + }, + '3 < connectionAttempts'(test: Test) { + const stack = new cdk.Stack(); + test.throws(() => { + new CloudFrontWebDistribution(stack, 'Distribution', { + originConfigs: [{ + behaviors: [{ isDefaultBehavior: true }], + connectionAttempts: 4, + customOriginSource: { domainName: 'myorigin.com' }, + }], + }); + }, /connectionAttempts: You can specify 1, 2, or 3 as the number of attempts./); + test.done(); + }, + 'connectionTimeout = 1.1'(test: Test) { + const stack = new cdk.Stack(); + test.throws(() => { + new CloudFrontWebDistribution(stack, 'Distribution', { + originConfigs: [{ + behaviors: [{ isDefaultBehavior: true }], + connectionTimeout: cdk.Duration.seconds(1.1), + customOriginSource: { domainName: 'myorigin.com' }, + }], + }); + }, /connectionTimeout: You can specify a number of seconds between 1 and 10 \(inclusive\)./); + test.done(); + }, + 'connectionTimeout < 1'(test: Test) { + const stack = new cdk.Stack(); + test.throws(() => { + new CloudFrontWebDistribution(stack, 'Distribution', { + originConfigs: [{ + behaviors: [{ isDefaultBehavior: true }], + connectionTimeout: cdk.Duration.seconds(0), + customOriginSource: { domainName: 'myorigin.com' }, + }], + }); + }, /connectionTimeout: You can specify a number of seconds between 1 and 10 \(inclusive\)./); + test.done(); + }, + '10 < connectionTimeout'(test: Test) { + const stack = new cdk.Stack(); + test.throws(() => { + new CloudFrontWebDistribution(stack, 'Distribution', { + originConfigs: [{ + behaviors: [{ isDefaultBehavior: true }], + connectionTimeout: cdk.Duration.seconds(11), + customOriginSource: { domainName: 'myorigin.com' }, + }], + }); + }, /connectionTimeout: You can specify a number of seconds between 1 and 10 \(inclusive\)./); test.done(); }, }, }, -}; +}); diff --git a/packages/@aws-cdk/aws-cloudtrail/.gitignore b/packages/@aws-cdk/aws-cloudtrail/.gitignore index 0cf71f1d5facd..aab273c24a413 100644 --- a/packages/@aws-cdk/aws-cloudtrail/.gitignore +++ b/packages/@aws-cdk/aws-cloudtrail/.gitignore @@ -14,4 +14,5 @@ nyc.config.js *.snk !.eslintrc.js -!jest.config.js \ No newline at end of file +!jest.config.js +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudtrail/.npmignore b/packages/@aws-cdk/aws-cloudtrail/.npmignore index 57328c980bcca..34633f72bca87 100644 --- a/packages/@aws-cdk/aws-cloudtrail/.npmignore +++ b/packages/@aws-cdk/aws-cloudtrail/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudtrail/jest.config.js b/packages/@aws-cdk/aws-cloudtrail/jest.config.js index 05f9c256c2792..b2dbd8ec76f4f 100644 --- a/packages/@aws-cdk/aws-cloudtrail/jest.config.js +++ b/packages/@aws-cdk/aws-cloudtrail/jest.config.js @@ -1,10 +1,10 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = { ...baseConfig, coverageThreshold: { global: { branches: 75, - statements: 80, + statements: 80, } } }; diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index 741a28cd1a1dc..ad09e05dae7b7 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.711.0", + "aws-sdk": "^2.715.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail-supplied-bucket.lit.ts b/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail-supplied-bucket.lit.ts index ad8614b3c1564..672d146192c09 100644 --- a/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail-supplied-bucket.lit.ts +++ b/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail-supplied-bucket.lit.ts @@ -30,7 +30,7 @@ Trailbucket.addToResourcePolicy(new iam.PolicyStatement({ resources: [Trailbucket.arnForObjects(`AWSLogs/${cdk.Stack.of(stack).account}/*`)], actions: ['s3:PutObject'], principals: [cloudTrailPrincipal], - conditions: { + conditions: { StringEquals: {'s3:x-amz-acl': 'bucket-owner-full-control'}, }, })); diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/.gitignore b/packages/@aws-cdk/aws-cloudwatch-actions/.gitignore index 23a79075f642c..147448f7df4fe 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/.gitignore +++ b/packages/@aws-cdk/aws-cloudwatch-actions/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/.npmignore b/packages/@aws-cdk/aws-cloudwatch-actions/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/.npmignore +++ b/packages/@aws-cdk/aws-cloudwatch-actions/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/jest.config.js b/packages/@aws-cdk/aws-cloudwatch-actions/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/jest.config.js +++ b/packages/@aws-cdk/aws-cloudwatch-actions/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cloudwatch/.gitignore b/packages/@aws-cdk/aws-cloudwatch/.gitignore index 9ad8ff2dfbcc9..0f4bf01dd552c 100644 --- a/packages/@aws-cdk/aws-cloudwatch/.gitignore +++ b/packages/@aws-cdk/aws-cloudwatch/.gitignore @@ -15,3 +15,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudwatch/.npmignore b/packages/@aws-cdk/aws-cloudwatch/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-cloudwatch/.npmignore +++ b/packages/@aws-cdk/aws-cloudwatch/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/alarm-rule.ts b/packages/@aws-cdk/aws-cloudwatch/lib/alarm-rule.ts index f860bcb095d44..7d7a25d2e9104 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/alarm-rule.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/alarm-rule.ts @@ -62,7 +62,6 @@ export class AlarmRule { * @param operand IAlarmRule to be wrapped in NOT operator. */ public static not(operand: IAlarmRule): IAlarmRule { - // tslint:disable-next-line:new-parens return new class implements IAlarmRule { public renderAlarmRule(): string { return `(NOT (${operand.renderAlarmRule()}))`; @@ -76,7 +75,6 @@ export class AlarmRule { * @param value boolean value to be used in rule expression. */ public static fromBoolean(value: boolean): IAlarmRule { - // tslint:disable-next-line:new-parens return new class implements IAlarmRule { public renderAlarmRule(): string { return `${String(value).toUpperCase()}`; @@ -91,7 +89,6 @@ export class AlarmRule { * @param alarmState AlarmState to be used in Rule Expression. */ public static fromAlarm(alarm: IAlarm, alarmState: AlarmState): IAlarmRule { - // tslint:disable-next-line:new-parens return new class implements IAlarmRule { public renderAlarmRule(): string { return `${alarmState}(${alarm.alarmArn})`; @@ -105,7 +102,6 @@ export class AlarmRule { * @param alarmRule string to be used in Rule Expression. */ public static fromString(alarmRule: string): IAlarmRule { - // tslint:disable-next-line:new-parens return new class implements IAlarmRule { public renderAlarmRule(): string { return alarmRule; @@ -114,7 +110,6 @@ export class AlarmRule { } private static concat(operator: Operator, ...operands: IAlarmRule[]): IAlarmRule { - // tslint:disable-next-line:new-parens return new class implements IAlarmRule { public renderAlarmRule(): string { const expression = operands diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts index 299220eb8d42f..d1ebea096629e 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts @@ -197,7 +197,7 @@ export class Alarm extends AlarmBase { this.metric = props.metric; const datapoints = props.datapointsToAlarm || props.evaluationPeriods; this.annotation = { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len label: `${this.metric} ${OPERATOR_SYMBOLS[comparisonOperator]} ${props.threshold} for ${datapoints} datapoints within ${describePeriod(props.evaluationPeriods * metricPeriod(props.metric).toSeconds())}`, value: props.threshold, }; diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/private/metric-util.ts b/packages/@aws-cdk/aws-cloudwatch/lib/private/metric-util.ts index bae996a137e71..589e3d99ad474 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/private/metric-util.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/private/metric-util.ts @@ -109,7 +109,7 @@ export function metricPeriod(metric: IMetric): Duration { * repeated in all places where code needs to make a distinction on the type * of metric object that is being passed. */ -// tslint:disable-next-line:max-line-length +// eslint-disable-next-line max-len export function dispatchMetric(metric: IMetric, fns: { withStat: (x: MetricStatConfig, c: MetricConfig) => A, withExpression: (x: MetricExpressionConfig, c: MetricConfig) => B }): A | B { const conf = metric.toMetricConfig(); if (conf.metricStat && conf.mathExpression) { diff --git a/packages/@aws-cdk/aws-cloudwatch/test/test.dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/test/test.dashboard.ts index 34b0241d338b5..80d24718af79a 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/test.dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/test.dashboard.ts @@ -180,7 +180,7 @@ function thatHasWidgets(widgets: any): (props: any) => boolean { const actualWidgets = JSON.parse(props.DashboardBody).widgets; return isSuperObject(actualWidgets, widgets); } catch (e) { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.error('Error parsing', props); throw e; } diff --git a/packages/@aws-cdk/aws-codebuild/.gitignore b/packages/@aws-cdk/aws-codebuild/.gitignore index 32a10d785e8fb..dcc1dc41e477f 100644 --- a/packages/@aws-cdk/aws-codebuild/.gitignore +++ b/packages/@aws-cdk/aws-codebuild/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild/.npmignore b/packages/@aws-cdk/aws-codebuild/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-codebuild/.npmignore +++ b/packages/@aws-cdk/aws-codebuild/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild/lib/pipeline-project.ts b/packages/@aws-cdk/aws-codebuild/lib/pipeline-project.ts index 8c06e079b2a97..4d233def20845 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/pipeline-project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/pipeline-project.ts @@ -3,7 +3,6 @@ import { CodePipelineArtifacts } from './codepipeline-artifacts'; import { CodePipelineSource } from './codepipeline-source'; import { CommonProjectProps, Project } from './project'; -// tslint:disable-next-line:no-empty-interface export interface PipelineProjectProps extends CommonProjectProps { } diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index ec2b8f7e41334..f9e29ceea015a 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.711.0", + "aws-sdk": "^2.715.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/test/test.codebuild.ts b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts index 7badf7c9b8f46..8fcf052c9358f 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts @@ -9,7 +9,7 @@ import * as codebuild from '../lib'; import { CodePipelineSource } from '../lib/codepipeline-source'; import { NoSource } from '../lib/no-source'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'default properties': { diff --git a/packages/@aws-cdk/aws-codebuild/test/test.project.ts b/packages/@aws-cdk/aws-codebuild/test/test.project.ts index 19dd60ed5e758..0714a400acc25 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.project.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.project.ts @@ -6,7 +6,7 @@ import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as codebuild from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'can use filename as buildspec'(test: Test) { diff --git a/packages/@aws-cdk/aws-codebuild/test/test.report-group.ts b/packages/@aws-cdk/aws-codebuild/test/test.report-group.ts index 1e942413cc08f..4bc69d40ee4c6 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.report-group.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.report-group.ts @@ -6,7 +6,7 @@ import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as codebuild from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ /* eslint-disable quotes */ export = { diff --git a/packages/@aws-cdk/aws-codecommit/.gitignore b/packages/@aws-cdk/aws-codecommit/.gitignore index 7fce433df3f45..86fc837df8fca 100644 --- a/packages/@aws-cdk/aws-codecommit/.gitignore +++ b/packages/@aws-cdk/aws-codecommit/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codecommit/.npmignore b/packages/@aws-cdk/aws-codecommit/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-codecommit/.npmignore +++ b/packages/@aws-cdk/aws-codecommit/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index 604dbb6317382..55f2a8022e0ab 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.711.0", + "aws-sdk": "^2.715.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codedeploy/.gitignore b/packages/@aws-cdk/aws-codedeploy/.gitignore index 0bd6133da4d09..018c65919d67c 100644 --- a/packages/@aws-cdk/aws-codedeploy/.gitignore +++ b/packages/@aws-cdk/aws-codedeploy/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codedeploy/.npmignore b/packages/@aws-cdk/aws-codedeploy/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-codedeploy/.npmignore +++ b/packages/@aws-cdk/aws-codedeploy/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-config.ts b/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-config.ts index 33520ea4d84d5..7b8c43a1ae0d5 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-config.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-config.ts @@ -3,7 +3,7 @@ import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as codedeploy from '../../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'CodeDeploy DeploymentConfig': { diff --git a/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts index d87b2e3a98b2a..fd486f20f1eb2 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts @@ -7,7 +7,7 @@ import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as codedeploy from '../../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'CodeDeploy Server Deployment Group': { diff --git a/packages/@aws-cdk/aws-codeguruprofiler/.gitignore b/packages/@aws-cdk/aws-codeguruprofiler/.gitignore index d57af28d42320..192200b9c7097 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/.gitignore +++ b/packages/@aws-cdk/aws-codeguruprofiler/.gitignore @@ -16,3 +16,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codeguruprofiler/.npmignore b/packages/@aws-cdk/aws-codeguruprofiler/.npmignore index 2f1ff45adc883..c8874799485a3 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/.npmignore +++ b/packages/@aws-cdk/aws-codeguruprofiler/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codeguruprofiler/jest.config.js b/packages/@aws-cdk/aws-codeguruprofiler/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/jest.config.js +++ b/packages/@aws-cdk/aws-codeguruprofiler/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-codeguruprofiler/test/profiling-group.test.ts b/packages/@aws-cdk/aws-codeguruprofiler/test/profiling-group.test.ts index 0fbf063cccfaa..8b26f916d26f1 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/test/profiling-group.test.ts +++ b/packages/@aws-cdk/aws-codeguruprofiler/test/profiling-group.test.ts @@ -3,7 +3,7 @@ import { AccountRootPrincipal, Role } from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; import { ProfilingGroup } from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ describe('profiling group', () => { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/.gitignore b/packages/@aws-cdk/aws-codepipeline-actions/.gitignore index 9ad8ff2dfbcc9..0f4bf01dd552c 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/.gitignore +++ b/packages/@aws-cdk/aws-codepipeline-actions/.gitignore @@ -15,3 +15,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/.npmignore b/packages/@aws-cdk/aws-codepipeline-actions/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/.npmignore +++ b/packages/@aws-cdk/aws-codepipeline-actions/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/README.md b/packages/@aws-cdk/aws-codepipeline-actions/README.md index 7bb4d5dec9c49..825f15b8c2662 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/README.md +++ b/packages/@aws-cdk/aws-codepipeline-actions/README.md @@ -14,9 +14,9 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions'; ``` -### Sources +## Sources -#### AWS CodeCommit +### AWS CodeCommit To use a CodeCommit Repository in a CodePipeline: @@ -62,7 +62,14 @@ new codepipeline_actions.CodeBuildAction({ }); ``` -#### GitHub +### GitHub + +If you want to use a GitHub repository as the source, you must create: + +* A [GitHub Access Token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) +* A [Secrets Manager PlainText Secret](https://docs.aws.amazon.com/secretsmanager/latest/userguide/manage_create-basic-secret.html) + with the value of the **GitHub Access Token**. Pick whatever name you want + (for example `my-github-token`) and pass it as the argument of `oauthToken`. To use GitHub as the source of a CodePipeline: @@ -104,7 +111,7 @@ new codepipeline_actions.CodeBuildAction({ }); ``` -#### BitBucket +### BitBucket CodePipeline can use a BitBucket Git repository as a source: @@ -135,7 +142,7 @@ const sourceAction = new codepipeline_actions.BitBucketSourceAction({ the above class `BitBucketSourceAction` is experimental - we reserve the right to make breaking changes to it. -#### AWS S3 +### AWS S3 To use an S3 Bucket as a source in CodePipeline: @@ -205,7 +212,7 @@ new codepipeline_actions.CodeBuildAction({ }); ``` -#### AWS ECR +### AWS ECR To use an ECR Repository as a source in a Pipeline: @@ -246,9 +253,9 @@ new codepipeline_actions.CodeBuildAction({ }); ``` -### Build & test +## Build & test -#### AWS CodeBuild +### AWS CodeBuild Example of a CodeBuild Project used in a Pipeline, alongside CodeCommit: @@ -301,7 +308,7 @@ const testAction = new codepipeline_actions.CodeBuildAction({ }); ``` -##### Multiple inputs and outputs +#### Multiple inputs and outputs When you want to have multiple inputs and/or outputs for a Project used in a Pipeline, instead of using the `secondarySources` and `secondaryArtifacts` @@ -375,7 +382,7 @@ const project = new codebuild.PipelineProject(this, 'MyProject', { }); ``` -##### Variables +#### Variables The CodeBuild action emits variables. Unlike many other actions, the variables are not static, @@ -417,7 +424,7 @@ new codepipeline_actions.CodeBuildAction({ }); ``` -#### Jenkins +### Jenkins In order to use Jenkins Actions in the Pipeline, you first need to create a `JenkinsProvider`: @@ -459,9 +466,9 @@ const buildAction = new codepipeline_actions.JenkinsAction({ }); ``` -### Deploy +## Deploy -#### AWS CloudFormation +### AWS CloudFormation This module contains Actions that allows you to deploy to CloudFormation from AWS CodePipeline. @@ -497,7 +504,7 @@ using a CloudFormation CodePipeline Action. Example: [Example of deploying a Lambda through CodePipeline](test/integ.lambda-deployed-through-codepipeline.lit.ts) -##### Cross-account actions +#### Cross-account actions If you want to update stacks in a different account, pass the `account` property when creating the action: @@ -534,9 +541,9 @@ new codepipeline_actions.CloudFormationCreateUpdateStackAction({ }); ``` -#### AWS CodeDeploy +### AWS CodeDeploy -##### Server deployments +#### Server deployments To use CodeDeploy for EC2/on-premise deployments in a Pipeline: @@ -589,7 +596,7 @@ where you will define your Pipeline, and deploy the `lambdaStack` using a CloudFormation CodePipeline Action (see above for a complete example). -#### ECS +### ECS CodePipeline can deploy an ECS service. The deploy Action receives one input Artifact which contains the [image definition file]: @@ -616,7 +623,7 @@ const deployStage = pipeline.addStage({ [image definition file]: https://docs.aws.amazon.com/codepipeline/latest/userguide/pipelines-create.html#pipelines-create-image-definitions -#### AWS S3 +### AWS S3 To use an S3 Bucket as a deployment target in CodePipeline: @@ -636,7 +643,7 @@ const deployStage = pipeline.addStage({ }); ``` -#### Alexa Skill +### Alexa Skill You can deploy to Alexa using CodePipeline with the following Action: @@ -687,9 +694,9 @@ new codepipeline_actions.AlexaSkillDeployAction({ }); ``` -### Approve & invoke +## Approve & invoke -#### Manual approval Action +### Manual approval Action This package contains an Action that stops the Pipeline until someone manually clicks the approve button: @@ -712,7 +719,7 @@ but `notifyEmails` were, a new SNS Topic will be created (and accessible through the `notificationTopic` property of the Action). -#### AWS Lambda +### AWS Lambda This module contains an Action that allows you to invoke a Lambda function in a Pipeline: diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/bitbucket/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/bitbucket/source-action.ts index 6fb8770796824..75f34777471e5 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/bitbucket/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/bitbucket/source-action.ts @@ -47,7 +47,6 @@ export interface BitBucketSourceActionProps extends codepipeline.CommonAwsAction readonly branch?: string; // long URL in @see - // tslint:disable:max-line-length /** * Whether the output should be the contents of the repository * (which is the default), @@ -60,7 +59,6 @@ export interface BitBucketSourceActionProps extends codepipeline.CommonAwsAction * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/action-reference-CodestarConnectionSource.html#action-reference-CodestarConnectionSource-config */ readonly codeBuildCloneOutput?: boolean; - // tslint:enable:max-line-length } /** diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts index 44ad86f4c3f34..1982b0b8336bf 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts @@ -139,7 +139,6 @@ export class CloudFormationExecuteChangeSetAction extends CloudFormationAction { } } -// tslint:disable:max-line-length Because of long URLs in documentation /** * Properties common to CloudFormation actions that stage deployments */ @@ -235,7 +234,6 @@ interface CloudFormationDeployActionProps extends CloudFormationActionProps { */ readonly extraInputs?: codepipeline.Artifact[]; } -// tslint:enable:max-line-length /** * Base class for all CloudFormation actions that execute or stage deployments. @@ -451,7 +449,6 @@ export class CloudFormationCreateUpdateStackAction extends CloudFormationDeployA /** * Properties for the CloudFormationDeleteStackAction. */ -// tslint:disable-next-line:no-empty-interface export interface CloudFormationDeleteStackActionProps extends CloudFormationDeployActionProps { } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/custom-action-registration.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/custom-action-registration.ts index 3b51ffd0630ab..7741531b4fd88 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/custom-action-registration.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/custom-action-registration.ts @@ -19,9 +19,6 @@ export interface CustomActionProperty { */ description?: string; - // because of @see URLs - // tslint:disable:max-line-length - /** * Whether this property is a key. * @@ -39,8 +36,6 @@ export interface CustomActionProperty { */ queryable?: boolean; - // tslint:enable:max-line-length - /** * Whether this property is required. */ diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts index 7cabc529fbf07..987968728cb4d 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts @@ -8,9 +8,6 @@ import { Action } from '../action'; * Construction properties of the {@link LambdaInvokeAction Lambda invoke CodePipeline Action}. */ export interface LambdaInvokeActionProps extends codepipeline.CommonAwsActionProps { - // because of @see links - // tslint:disable:max-line-length - /** * The optional input Artifacts of the Action. * A Lambda Action can have up to 5 inputs. @@ -41,8 +38,6 @@ export interface LambdaInvokeActionProps extends codepipeline.CommonAwsActionPro */ readonly userParameters?: { [key: string]: any }; - // tslint:enable:max-line-length - /** * The lambda function to invoke. */ diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package.json b/packages/@aws-cdk/aws-codepipeline-actions/package.json index 158acb5f2e44d..de7642c8649db 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package.json @@ -68,7 +68,7 @@ "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "lodash": "^4.17.15", + "lodash": "^4.17.19", "nodeunit": "^0.11.3", "pkglint": "0.0.0" }, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/test.bitbucket-source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/test.bitbucket-source-action.ts index f245a720a2fd9..a4f120f2abf68 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/test.bitbucket-source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/test.bitbucket-source-action.ts @@ -5,7 +5,7 @@ import { Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as cpactions from '../../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'BitBucket source Action': { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts index ea613c14db4f4..122add214cc3a 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts @@ -8,7 +8,7 @@ import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as cpactions from '../../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'CreateChangeSetAction can be used to make a change set from a CodePipeline'(test: Test) { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts index 610c55176c061..9a6b99351ee7b 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts @@ -8,7 +8,7 @@ import * as nodeunit from 'nodeunit'; import * as cpactions from '../../lib'; export = nodeunit.testCase({ - 'CreateReplaceChangeSet': { + CreateReplaceChangeSet: { 'works'(test: nodeunit.Test) { const app = new cdk.App(); const stack = new cdk.Stack(app, 'Stack'); @@ -95,9 +95,9 @@ export = nodeunit.testCase({ Condition: { StringEqualsIfExists: { 'cloudformation:ChangeSetName': 'MyChangeSet' } }, Effect: 'Allow', Resource: [ - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':cloudformation:', { Ref: 'AWS::Region' }, ':', { Ref: 'AWS::AccountId' }, ':stack/StackA/*' ] ] }, - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':cloudformation:', { Ref: 'AWS::Region' }, ':', { Ref: 'AWS::AccountId' }, ':stack/StackB/*' ] ] }, ], }, @@ -108,7 +108,7 @@ export = nodeunit.testCase({ }, }, - 'ExecuteChangeSet': { + ExecuteChangeSet: { 'works'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); @@ -167,9 +167,9 @@ export = nodeunit.testCase({ Condition: { StringEqualsIfExists: { 'cloudformation:ChangeSetName': 'MyChangeSet' } }, Effect: 'Allow', Resource: [ - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':cloudformation:', { Ref: 'AWS::Region' }, ':', { Ref: 'AWS::AccountId' }, ':stack/StackA/*' ] ] }, - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':cloudformation:', { Ref: 'AWS::Region' }, ':', { Ref: 'AWS::AccountId' }, ':stack/StackB/*' ] ] }, ], }, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/codebuild/test.codebuild-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/codebuild/test.codebuild-action.ts index 10c09811e851e..3dc54909d8f53 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/codebuild/test.codebuild-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/codebuild/test.codebuild-action.ts @@ -8,7 +8,7 @@ import { App, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as cpactions from '../../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'CodeBuild action': { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/test.codecommit-source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/test.codecommit-source-action.ts index 0650c50f2b596..e62e301168fa1 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/test.codecommit-source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/test.codecommit-source-action.ts @@ -6,7 +6,7 @@ import { Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as cpactions from '../../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'CodeCommit Source Action': { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/ecr/test.ecr-source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/ecr/test.ecr-source-action.ts index 6160e7f159af5..b27ad5f89f880 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/ecr/test.ecr-source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/ecr/test.ecr-source-action.ts @@ -6,7 +6,7 @@ import { Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as cpactions from '../../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'ECR source Action': { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/github/test.github-source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/github/test.github-source-action.ts index 02f8847c6bf92..56131d3d15b1e 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/github/test.github-source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/github/test.github-source-action.ts @@ -5,7 +5,7 @@ import { SecretValue, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as cpactions from '../../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'GitHub source Action': { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.ts index 33311f6a5a7d6..921227a3224ba 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.ts @@ -4,7 +4,7 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as cdk from '@aws-cdk/core'; import * as cpactions from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ const app = new cdk.App(); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.ts index 4f5eba55965c1..e969e350482dc 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.ts @@ -7,7 +7,7 @@ import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import * as cpactions from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ const app = new cdk.App(); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/lambda/test.lambda-invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda/test.lambda-invoke-action.ts index ab0cd120ac90e..84af1c50a7583 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/lambda/test.lambda-invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda/test.lambda-invoke-action.ts @@ -7,7 +7,7 @@ import { Aws, Lazy, SecretValue, Stack, Token } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as cpactions from '../../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'Lambda invoke Action': { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/s3/test.s3-deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/test.s3-deploy-action.ts index 37ad397848a51..01ebb43dca605 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/s3/test.s3-deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/test.s3-deploy-action.ts @@ -5,7 +5,7 @@ import { Duration, SecretValue, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as cpactions from '../../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'S3 Deploy Action': { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/s3/test.s3-source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/test.s3-source-action.ts index 909fcb9bd6d8b..6c927ed36d1cb 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/s3/test.s3-source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/test.s3-source-action.ts @@ -6,7 +6,7 @@ import { Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as cpactions from '../../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'S3 Source Action': { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/test.manual-approval.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/test.manual-approval.ts index 7adc663d9e6c2..8efbbd0040ca9 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/test.manual-approval.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/test.manual-approval.ts @@ -5,7 +5,7 @@ import { SecretValue, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as cpactions from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'manual approval Action': { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts index 64f9c44a41147..6a7001630b3c5 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts @@ -11,7 +11,7 @@ import { App, Aws, CfnParameter, ConstructNode, SecretValue, Stack } from '@aws- import { Test } from 'nodeunit'; import * as cpactions from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'basic pipeline'(test: Test) { diff --git a/packages/@aws-cdk/aws-codepipeline/.gitignore b/packages/@aws-cdk/aws-codepipeline/.gitignore index 9ad8ff2dfbcc9..0f4bf01dd552c 100644 --- a/packages/@aws-cdk/aws-codepipeline/.gitignore +++ b/packages/@aws-cdk/aws-codepipeline/.gitignore @@ -15,3 +15,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline/.npmignore b/packages/@aws-cdk/aws-codepipeline/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-codepipeline/.npmignore +++ b/packages/@aws-cdk/aws-codepipeline/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.action.ts b/packages/@aws-cdk/aws-codepipeline/test/test.action.ts index 1ff1783a9c3a9..2032be87ac430 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.action.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.action.ts @@ -7,7 +7,7 @@ import * as validations from '../lib/validation'; import { FakeBuildAction } from './fake-build-action'; import { FakeSourceAction } from './fake-source-action'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'artifact bounds validation': { diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.artifacts.ts b/packages/@aws-cdk/aws-codepipeline/test/test.artifacts.ts index b638a3c1c7b90..dc023846667e3 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.artifacts.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.artifacts.ts @@ -5,7 +5,7 @@ import * as codepipeline from '../lib'; import { FakeBuildAction } from './fake-build-action'; import { FakeSourceAction } from './fake-source-action'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'Artifacts in CodePipeline': { diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts index 5d1c91edd51af..32f3604191e6a 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts @@ -9,7 +9,7 @@ import * as codepipeline from '../lib'; import { FakeBuildAction } from './fake-build-action'; import { FakeSourceAction } from './fake-source-action'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'Pipeline': { @@ -305,7 +305,7 @@ export = { app.node.setContext(cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT, true); const pipelineStack = new cdk.Stack(app, 'PipelineStack', { - env: { region: 'us-west-2', account: '123456789012' }, + env: { region: 'us-west-2', account: '123456789012' }, }); const sourceOutput = new codepipeline.Artifact(); new codepipeline.Pipeline(pipelineStack, 'Pipeline', { diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.stages.ts b/packages/@aws-cdk/aws-codepipeline/test/test.stages.ts index 19ead05bb1410..62088e0f9d8b0 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.stages.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.stages.ts @@ -4,7 +4,7 @@ import { Test } from 'nodeunit'; import * as codepipeline from '../lib'; import { Stage } from '../lib/stage'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'Pipeline Stages': { @@ -105,7 +105,6 @@ export = { }); // incredibly, an arrow function below causes nodeunit to crap out with: // "TypeError: Function has non-object prototype 'undefined' in instanceof check" - // tslint:disable-next-line:only-arrow-functions }, function(e: any) { return /rightBefore/.test(e) && /justAfter/.test(e); }); diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.variables.ts b/packages/@aws-cdk/aws-codepipeline/test/test.variables.ts index 8c01171187c6d..92ae8c8b8ac6a 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.variables.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.variables.ts @@ -5,7 +5,7 @@ import * as codepipeline from '../lib'; import { FakeBuildAction } from './fake-build-action'; import { FakeSourceAction } from './fake-source-action'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'Pipeline Variables': { diff --git a/packages/@aws-cdk/aws-codestar/.gitignore b/packages/@aws-cdk/aws-codestar/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-codestar/.gitignore +++ b/packages/@aws-cdk/aws-codestar/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codestar/.npmignore b/packages/@aws-cdk/aws-codestar/.npmignore index 2e1e60862c02c..5c003cc4e3040 100644 --- a/packages/@aws-cdk/aws-codestar/.npmignore +++ b/packages/@aws-cdk/aws-codestar/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codestar/jest.config.js b/packages/@aws-cdk/aws-codestar/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-codestar/jest.config.js +++ b/packages/@aws-cdk/aws-codestar/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-codestarconnections/.gitignore b/packages/@aws-cdk/aws-codestarconnections/.gitignore index d57af28d42320..192200b9c7097 100644 --- a/packages/@aws-cdk/aws-codestarconnections/.gitignore +++ b/packages/@aws-cdk/aws-codestarconnections/.gitignore @@ -16,3 +16,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codestarconnections/.npmignore b/packages/@aws-cdk/aws-codestarconnections/.npmignore index 2f1ff45adc883..c8874799485a3 100644 --- a/packages/@aws-cdk/aws-codestarconnections/.npmignore +++ b/packages/@aws-cdk/aws-codestarconnections/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codestarconnections/jest.config.js b/packages/@aws-cdk/aws-codestarconnections/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-codestarconnections/jest.config.js +++ b/packages/@aws-cdk/aws-codestarconnections/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-codestarnotifications/.gitignore b/packages/@aws-cdk/aws-codestarnotifications/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-codestarnotifications/.gitignore +++ b/packages/@aws-cdk/aws-codestarnotifications/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codestarnotifications/.npmignore b/packages/@aws-cdk/aws-codestarnotifications/.npmignore index 683e3e0847e1f..a7c5b49852b3b 100644 --- a/packages/@aws-cdk/aws-codestarnotifications/.npmignore +++ b/packages/@aws-cdk/aws-codestarnotifications/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codestarnotifications/jest.config.js b/packages/@aws-cdk/aws-codestarnotifications/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-codestarnotifications/jest.config.js +++ b/packages/@aws-cdk/aws-codestarnotifications/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cognito/.gitignore b/packages/@aws-cdk/aws-cognito/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-cognito/.gitignore +++ b/packages/@aws-cdk/aws-cognito/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/.npmignore b/packages/@aws-cdk/aws-cognito/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-cognito/.npmignore +++ b/packages/@aws-cdk/aws-cognito/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/jest.config.js b/packages/@aws-cdk/aws-cognito/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-cognito/jest.config.js +++ b/packages/@aws-cdk/aws-cognito/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-attr.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-attr.ts index 60c011fd9a71b..e9881bd8b3a14 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-attr.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-attr.ts @@ -147,14 +147,12 @@ export interface ICustomAttribute { * Configuration that will be fed into CloudFormation for any custom attribute type. */ export interface CustomAttributeConfig { - // tslint:disable:max-line-length /** * The data type of the custom attribute. * * @see https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_SchemaAttributeType.html#CognitoUserPools-Type-SchemaAttributeType-AttributeDataType */ readonly dataType: string; - // tslint:enable:max-line-length /** * The constraints for a custom attribute of 'String' data type. diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts index c584ca8be7e46..0d89de96fbc72 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts @@ -132,12 +132,10 @@ export class OAuthScope { return new OAuthScope(name); } - // tslint:disable:max-line-length /** * The name of this scope as recognized by CloudFormation. * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolclient.html#cfn-cognito-userpoolclient-allowedoauthscopes */ - // tslint:enable:max-line-length public readonly scopeName: string; private constructor(scopeName: string) { diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-domain-cfdist.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-domain-cfdist.ts index abde60a7cd12c..7a45e144ffc3c 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-domain-cfdist.ts +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-domain-cfdist.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import { App, CfnOutput, Stack } from '@aws-cdk/core'; import { UserPool } from '../lib'; diff --git a/packages/@aws-cdk/aws-config/.gitignore b/packages/@aws-cdk/aws-config/.gitignore index 0bd6133da4d09..018c65919d67c 100644 --- a/packages/@aws-cdk/aws-config/.gitignore +++ b/packages/@aws-cdk/aws-config/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-config/.npmignore b/packages/@aws-cdk/aws-config/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-config/.npmignore +++ b/packages/@aws-cdk/aws-config/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-datapipeline/.gitignore b/packages/@aws-cdk/aws-datapipeline/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-datapipeline/.gitignore +++ b/packages/@aws-cdk/aws-datapipeline/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-datapipeline/.npmignore b/packages/@aws-cdk/aws-datapipeline/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-datapipeline/.npmignore +++ b/packages/@aws-cdk/aws-datapipeline/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-datapipeline/jest.config.js b/packages/@aws-cdk/aws-datapipeline/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-datapipeline/jest.config.js +++ b/packages/@aws-cdk/aws-datapipeline/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-dax/.gitignore b/packages/@aws-cdk/aws-dax/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-dax/.gitignore +++ b/packages/@aws-cdk/aws-dax/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dax/.npmignore b/packages/@aws-cdk/aws-dax/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-dax/.npmignore +++ b/packages/@aws-cdk/aws-dax/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dax/jest.config.js b/packages/@aws-cdk/aws-dax/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-dax/jest.config.js +++ b/packages/@aws-cdk/aws-dax/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-detective/.gitignore b/packages/@aws-cdk/aws-detective/.gitignore index e9fee23607e76..5aa413b898780 100644 --- a/packages/@aws-cdk/aws-detective/.gitignore +++ b/packages/@aws-cdk/aws-detective/.gitignore @@ -2,7 +2,6 @@ *.js.map *.d.ts tsconfig.json -tslint.json node_modules *.generated.ts dist @@ -17,3 +16,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml diff --git a/packages/@aws-cdk/aws-detective/.npmignore b/packages/@aws-cdk/aws-detective/.npmignore index 2f1ff45adc883..c8874799485a3 100644 --- a/packages/@aws-cdk/aws-detective/.npmignore +++ b/packages/@aws-cdk/aws-detective/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-detective/jest.config.js b/packages/@aws-cdk/aws-detective/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-detective/jest.config.js +++ b/packages/@aws-cdk/aws-detective/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-directoryservice/.gitignore b/packages/@aws-cdk/aws-directoryservice/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-directoryservice/.gitignore +++ b/packages/@aws-cdk/aws-directoryservice/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-directoryservice/.npmignore b/packages/@aws-cdk/aws-directoryservice/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-directoryservice/.npmignore +++ b/packages/@aws-cdk/aws-directoryservice/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-directoryservice/jest.config.js b/packages/@aws-cdk/aws-directoryservice/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-directoryservice/jest.config.js +++ b/packages/@aws-cdk/aws-directoryservice/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-dlm/.gitignore b/packages/@aws-cdk/aws-dlm/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-dlm/.gitignore +++ b/packages/@aws-cdk/aws-dlm/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dlm/.npmignore b/packages/@aws-cdk/aws-dlm/.npmignore index b0edf81a01371..ab90672b1d91e 100644 --- a/packages/@aws-cdk/aws-dlm/.npmignore +++ b/packages/@aws-cdk/aws-dlm/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dlm/jest.config.js b/packages/@aws-cdk/aws-dlm/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-dlm/jest.config.js +++ b/packages/@aws-cdk/aws-dlm/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-dms/.gitignore b/packages/@aws-cdk/aws-dms/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-dms/.gitignore +++ b/packages/@aws-cdk/aws-dms/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dms/.npmignore b/packages/@aws-cdk/aws-dms/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-dms/.npmignore +++ b/packages/@aws-cdk/aws-dms/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dms/jest.config.js b/packages/@aws-cdk/aws-dms/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-dms/jest.config.js +++ b/packages/@aws-cdk/aws-dms/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-docdb/.gitignore b/packages/@aws-cdk/aws-docdb/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-docdb/.gitignore +++ b/packages/@aws-cdk/aws-docdb/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-docdb/.npmignore b/packages/@aws-cdk/aws-docdb/.npmignore index b0edf81a01371..ab90672b1d91e 100644 --- a/packages/@aws-cdk/aws-docdb/.npmignore +++ b/packages/@aws-cdk/aws-docdb/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-docdb/jest.config.js b/packages/@aws-cdk/aws-docdb/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-docdb/jest.config.js +++ b/packages/@aws-cdk/aws-docdb/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-docdb/lib/instance.ts b/packages/@aws-cdk/aws-docdb/lib/instance.ts index ab6400f5480e4..cdefa1d155f9f 100644 --- a/packages/@aws-cdk/aws-docdb/lib/instance.ts +++ b/packages/@aws-cdk/aws-docdb/lib/instance.ts @@ -145,7 +145,6 @@ export interface DatabaseInstanceProps { */ readonly autoMinorVersionUpgrade?: boolean; - // tslint:disable:max-line-length /** * The weekly time range (in UTC) during which system maintenance can occur. * @@ -156,7 +155,6 @@ export interface DatabaseInstanceProps { * time for each AWS Region, occurring on a random day of the week. To see * the time blocks available, see https://docs.aws.amazon.com/documentdb/latest/developerguide/db-instance-maintain.html#maintenance-window */ - // tslint:enable:max-line-length readonly preferredMaintenanceWindow?: string; /** diff --git a/packages/@aws-cdk/aws-dynamodb-global/.gitignore b/packages/@aws-cdk/aws-dynamodb-global/.gitignore index 802166eff0d99..ad9e57500b0bc 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/.gitignore +++ b/packages/@aws-cdk/aws-dynamodb-global/.gitignore @@ -17,3 +17,5 @@ nyc.config.js *.snk !test/test.lambda.handler.js !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb-global/.npmignore b/packages/@aws-cdk/aws-dynamodb-global/.npmignore index 92e94b941656e..e418511182841 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/.npmignore +++ b/packages/@aws-cdk/aws-dynamodb-global/.npmignore @@ -22,4 +22,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/jest.config.js b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/jest.config.js index a9acbf03c2b7a..7e38dba6f3ba0 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/jest.config.js +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/jest.config.js @@ -3,10 +3,7 @@ module.exports = { "/lib", "/test" ], - "transform": { - "^.+\\.tsx?$": "ts-jest" - }, - "testRegex": "(/test/.*|(\\.|/)(test|spec))\\.(ts|js)x?$", + "testRegex": "(/test/.*|(\\.|/)(test|spec))\\.(ts|js)$", "moduleFileExtensions": [ "ts", "tsx", diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json index 741bb93500809..f3a9fcb2d92fa 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json @@ -10,7 +10,7 @@ "scripts": { "build": "echo No build", "test": "jest", - "lint": "eslint lib", + "eslint": "eslint lib", "build+test+package": "npm run build+test", "build+test": "npm run build && npm test" }, diff --git a/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts b/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts index 28829b80f20a4..e68f9230b69cd 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts +++ b/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts @@ -4,7 +4,7 @@ import { App, CfnOutput, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import { GlobalTable, GlobalTableProps } from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ // CDK parameters const CONSTRUCT_NAME = 'aws-cdk-dynamodb-global'; diff --git a/packages/@aws-cdk/aws-dynamodb/.gitignore b/packages/@aws-cdk/aws-dynamodb/.gitignore index b6a49df09e933..4af109b196444 100644 --- a/packages/@aws-cdk/aws-dynamodb/.gitignore +++ b/packages/@aws-cdk/aws-dynamodb/.gitignore @@ -16,3 +16,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/.npmignore b/packages/@aws-cdk/aws-dynamodb/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-dynamodb/.npmignore +++ b/packages/@aws-cdk/aws-dynamodb/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/jest.config.js b/packages/@aws-cdk/aws-dynamodb/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-dynamodb/jest.config.js +++ b/packages/@aws-cdk/aws-dynamodb/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-dynamodb/lib/replica-handler/index.ts b/packages/@aws-cdk/aws-dynamodb/lib/replica-handler/index.ts index 044d1c0c359ac..814bad346ece2 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/replica-handler/index.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/replica-handler/index.ts @@ -1,4 +1,4 @@ -/* tslint:disable no-console */ +/* eslint-disable no-console */ import type { IsCompleteRequest, IsCompleteResponse, OnEventRequest, OnEventResponse } from '@aws-cdk/custom-resources/lib/provider-framework/types'; import { DynamoDB } from 'aws-sdk'; // eslint-disable-line import/no-extraneous-dependencies diff --git a/packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts b/packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts index d10f023631297..dbfd8761aff05 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts @@ -1,8 +1,8 @@ +import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import { Construct, Duration, NestedStack, Stack } from '@aws-cdk/core'; import * as cr from '@aws-cdk/custom-resources'; -import * as path from 'path'; export class ReplicaProvider extends NestedStack { /** diff --git a/packages/@aws-cdk/aws-dynamodb/lib/scalable-table-attribute.ts b/packages/@aws-cdk/aws-dynamodb/lib/scalable-table-attribute.ts index 83ce773e31d9a..4ec8e1365773f 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/scalable-table-attribute.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/scalable-table-attribute.ts @@ -17,7 +17,7 @@ export class ScalableTableAttribute extends appscaling.BaseScalableAttribute { */ public scaleOnUtilization(props: UtilizationScalingProps) { if (props.targetUtilizationPercent < 10 || props.targetUtilizationPercent > 90) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new RangeError(`targetUtilizationPercent for DynamoDB scaling must be between 10 and 90 percent, got: ${props.targetUtilizationPercent}`); } const predefinedMetric = this.props.dimension.indexOf('ReadCapacity') === -1 diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index d2594c95fa9b2..f5cf9df385869 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -890,7 +890,7 @@ export class Table extends TableBase { } this.billingMode = BillingMode.PAY_PER_REQUEST; } else if (props.stream) { - streamSpecification = { streamViewType : props.stream }; + streamSpecification = { streamViewType: props.stream }; } this.table = new CfnTable(this, 'Resource', { diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index 07f3492a7ef4f..852e7d393c9c0 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.3", - "aws-sdk": "^2.711.0", + "@types/jest": "^26.0.4", + "aws-sdk": "^2.715.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", @@ -73,7 +73,7 @@ "jest": "^25.5.4", "pkglint": "0.0.0", "sinon": "^9.0.2", - "ts-jest": "^26.1.1" + "ts-jest": "^26.1.3" }, "dependencies": { "@aws-cdk/aws-applicationautoscaling": "0.0.0", diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts index c0c0fe9633ac0..05287f09adaa5 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts @@ -16,7 +16,7 @@ import { TableEncryption, } from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ // CDK parameters const CONSTRUCT_NAME = 'MyTable'; @@ -59,7 +59,7 @@ function* LSI_GENERATOR(): Generator { while (true) { const localSecondaryIndexProps: LocalSecondaryIndexProps = { indexName: `${LSI_NAME}${n}`, - sortKey: { name : `${LSI_SORT_KEY.name}${n}`, type: LSI_SORT_KEY.type }, + sortKey: { name: `${LSI_SORT_KEY.name}${n}`, type: LSI_SORT_KEY.type }, }; yield localSecondaryIndexProps; n++; @@ -2775,7 +2775,7 @@ describe('global', () => { function testGrant(expectedActions: string[], invocation: (user: iam.IPrincipal, table: Table) => void) { // GIVEN const stack = new Stack(); - const table = new Table(stack, 'my-table', { partitionKey: { name: 'ID', type: AttributeType.STRING } }); + const table = new Table(stack, 'my-table', { partitionKey: { name: 'ID', type: AttributeType.STRING } }); const user = new iam.User(stack, 'user'); // WHEN diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.global.ts b/packages/@aws-cdk/aws-dynamodb/test/integ.global.ts index c911a7b698f7d..b4ae5d01abccb 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.global.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.global.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import { App, Construct, RemovalPolicy, Stack, StackProps } from '@aws-cdk/core'; import * as dynamodb from '../lib'; diff --git a/packages/@aws-cdk/aws-ec2/.gitignore b/packages/@aws-cdk/aws-ec2/.gitignore index 1d4a8175e1579..286cb975b2420 100644 --- a/packages/@aws-cdk/aws-ec2/.gitignore +++ b/packages/@aws-cdk/aws-ec2/.gitignore @@ -15,4 +15,5 @@ nyc.config.js *.snk !.eslintrc.js -!jest.config.js \ No newline at end of file +!jest.config.js +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/.npmignore b/packages/@aws-cdk/aws-ec2/.npmignore index fba0982f5ee45..5d8f93d8a9c7e 100644 --- a/packages/@aws-cdk/aws-ec2/.npmignore +++ b/packages/@aws-cdk/aws-ec2/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -jest.config.js \ No newline at end of file +jest.config.js +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/jest.config.js b/packages/@aws-cdk/aws-ec2/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-ec2/jest.config.js +++ b/packages/@aws-cdk/aws-ec2/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index dfc44b8f65a8c..e5dc208be10f0 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -404,6 +404,7 @@ export class SecurityGroup extends SecurityGroupBase { // In the case of "allowAllOutbound", we don't add any more rules. There // is only one rule which allows all traffic and that subsumes any other // rule. + this.node.addWarning('Ignoring Egress rule since \'allowAllOutbound\' is set to true; To add customize rules, set allowAllOutbound=false on the SecurityGroup'); return; } else { // Otherwise, if the bogus rule exists we can now remove it because the diff --git a/packages/@aws-cdk/aws-ec2/lib/util.ts b/packages/@aws-cdk/aws-ec2/lib/util.ts index e33bc9338eccb..4f5a765b96bc5 100644 --- a/packages/@aws-cdk/aws-ec2/lib/util.ts +++ b/packages/@aws-cdk/aws-ec2/lib/util.ts @@ -58,12 +58,12 @@ export class ImportSubnetGroup { this.groups = this.subnetIds.length / this.availabilityZones.length; if (Math.floor(this.groups) !== this.groups) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`Number of ${idField} (${this.subnetIds.length}) must be a multiple of availability zones (${this.availabilityZones.length}).`); } if (this.routeTableIds.length !== this.subnetIds.length && routeTableIds != null) { // We don't err if no routeTableIds were provided to maintain backwards-compatibility. See https://github.com/aws/aws-cdk/pull/3171 - // tslint:disable-next-line: max-line-length + /* eslint-disable max-len */ throw new Error(`Number of ${routeTableIdField} (${this.routeTableIds.length}) must be equal to the amount of ${idField} (${this.subnetIds.length}).`); } diff --git a/packages/@aws-cdk/aws-ec2/lib/volume.ts b/packages/@aws-cdk/aws-ec2/lib/volume.ts index 6a6445dc87379..d379ae6622b1f 100644 --- a/packages/@aws-cdk/aws-ec2/lib/volume.ts +++ b/packages/@aws-cdk/aws-ec2/lib/volume.ts @@ -473,7 +473,7 @@ abstract class VolumeBase extends Resource implements IVolume { const result = Grant.addToPrincipal({ grantee, actions: [ 'ec2:AttachVolume' ], - resourceArns : this.collectGrantResourceArns(instances), + resourceArns: this.collectGrantResourceArns(instances), }); if (this.encryptionKey) { @@ -519,7 +519,7 @@ abstract class VolumeBase extends Resource implements IVolume { const result = Grant.addToPrincipal({ grantee, actions: [ 'ec2:DetachVolume' ], - resourceArns : this.collectGrantResourceArns(instances), + resourceArns: this.collectGrantResourceArns(instances), }); // Note: No encryption key permissions are required to detach an encrypted volume. return result; @@ -642,10 +642,6 @@ export class Volume extends VolumeBase { } 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`'); } diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index a49ff09fe6634..3767f348ce81a 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import { Aws, Construct, ContextProvider, IResource, Lazy, Resource, Token } from '@aws-cdk/core'; +import { Aws, Construct, ContextProvider, IResource, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; import { Connections, IConnectable } from './connections'; import { CfnVPCEndpoint } from './ec2.generated'; import { Peer } from './peer'; @@ -315,7 +315,10 @@ export class InterfaceVpcEndpointAwsService implements IInterfaceVpcEndpointServ public readonly privateDnsDefault?: boolean = true; constructor(name: string, prefix?: string, port?: number) { - this.name = `${prefix || 'com.amazonaws'}.${Aws.REGION}.${name}`; + const region = Lazy.stringValue({ + produce: (context) => Stack.of(context.scope).region, + }); + this.name = `${prefix || 'com.amazonaws'}.${region}.${name}`; this.port = port || 443; } } @@ -471,24 +474,8 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn this.connections.allowDefaultPortFrom(Peer.ipv4(props.vpc.vpcCidrBlock)); } - const lookupSupportedAzs = props.lookupSupportedAzs ?? false; - const subnetSelection = props.vpc.selectSubnets({ ...props.subnets, onePerAz: true }); - let subnets; - - // If we don't have an account/region, we will not be able to do filtering on AZs since - // they will be undefined - // Otherwise, we filter by AZ - const agnostic = (Token.isUnresolved(this.stack.account) || Token.isUnresolved(this.stack.region)); - - if (agnostic && lookupSupportedAzs) { - throw new Error('Cannot look up VPC endpoint availability zones if account/region are not specified'); - } else if (!agnostic && lookupSupportedAzs) { - const availableAZs = this.availableAvailabilityZones(props.service.name); - subnets = subnetSelection.subnets.filter(s => availableAZs.includes(s.availabilityZone)); - } else { - subnets = subnetSelection.subnets; - } - const subnetIds = subnets.map(s => s.subnetId); + // Determine which subnets to place the endpoint in + const subnetIds = this.endpointSubnets(props); const endpoint = new CfnVPCEndpoint(this, 'Resource', { privateDnsEnabled: props.privateDnsEnabled ?? props.service.privateDnsDefault ?? true, @@ -506,6 +493,61 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn this.vpcEndpointNetworkInterfaceIds = endpoint.attrNetworkInterfaceIds; } + /** + * Determine which subnets to place the endpoint in. This is in its own function + * because there's a lot of code. + */ + private endpointSubnets(props: InterfaceVpcEndpointProps) { + const lookupSupportedAzs = props.lookupSupportedAzs ?? false; + const subnetSelection = props.vpc.selectSubnets({ ...props.subnets, onePerAz: true }); + + // If we don't have an account/region, we will not be able to do filtering on AZs since + // they will be undefined + const agnosticAcct = Token.isUnresolved(this.stack.account); + const agnosticRegion = Token.isUnresolved(this.stack.region); + + // Some service names, such as AWS service name references, use Tokens to automatically + // fill in the region + // If it is an InterfaceVpcEndpointAwsService, then the reference will be resolvable since + // only references the region + const isAwsService = Token.isUnresolved(props.service.name) && props.service instanceof InterfaceVpcEndpointAwsService; + + // Determine what name we pass to the context provider, either the verbatim name + // or a resolved version if it is an AWS service reference + let lookupServiceName = props.service.name; + if (isAwsService && !agnosticRegion) { + lookupServiceName = Stack.of(this).resolve(props.service.name); + } else { + // It's an agnostic service and we don't know how to resolve it. + // This is ok if the stack is region agnostic and we're not looking up + // AZs + lookupServiceName = props.service.name; + } + + // Check if lookup is impossible and throw an appropriate error + // Context provider cannot make an AWS call without an account/region + if ((agnosticAcct || agnosticRegion) && lookupSupportedAzs) { + throw new Error('Cannot look up VPC endpoint availability zones if account/region are not specified'); + } + // Context provider doesn't know the name of the service if there is a Token + // in the name + const agnosticService = Token.isUnresolved(lookupServiceName); + if (agnosticService && lookupSupportedAzs) { + throw new Error(`Cannot lookup AZs for a service name with a Token: ${props.service.name}`); + } + + // Here we do the actual lookup for AZs, if told to do so + let subnets; + if (lookupSupportedAzs) { + const availableAZs = this.availableAvailabilityZones(lookupServiceName); + subnets = subnetSelection.subnets.filter(s => availableAZs.includes(s.availabilityZone)); + } else { + subnets = subnetSelection.subnets; + } + const subnetIds = subnets.map(s => s.subnetId); + return subnetIds; + } + private availableAvailabilityZones(serviceName: string): string[] { // Here we check what AZs the endpoint service is available in // If for whatever reason we can't retrieve the AZs, and no context is set, diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 509c42daebaf9..0b6ef2473b4db 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -1406,11 +1406,10 @@ export class Subnet extends Resource implements ISubnet { /** * Import existing subnet from id. */ - // tslint:disable:no-shadowed-variable + // eslint-disable-next-line no-shadow public static fromSubnetId(scope: Construct, id: string, subnetId: string): ISubnet { return this.fromSubnetAttributes(scope, id, { subnetId }); } - // tslint:enable:no-shadowed-variable /** * The Availability Zone the subnet is located in @@ -1662,7 +1661,6 @@ function routerTypeToPropName(routerType: RouterType) { })[routerType]; } -// tslint:disable-next-line:no-empty-interface export interface PublicSubnetProps extends SubnetProps { } @@ -1701,7 +1699,6 @@ export class PublicSubnet extends Subnet implements IPublicSubnet { } } -// tslint:disable-next-line:no-empty-interface export interface PrivateSubnetProps extends SubnetProps { } @@ -1746,11 +1743,11 @@ class ImportedVpc extends VpcBase { this._vpnGatewayId = props.vpnGatewayId; this.incompleteSubnetDefinition = isIncomplete; - // tslint:disable:max-line-length + /* eslint-disable max-len */ const pub = new ImportSubnetGroup(props.publicSubnetIds, props.publicSubnetNames, props.publicSubnetRouteTableIds, SubnetType.PUBLIC, this.availabilityZones, 'publicSubnetIds', 'publicSubnetNames', 'publicSubnetRouteTableIds'); const priv = new ImportSubnetGroup(props.privateSubnetIds, props.privateSubnetNames, props.privateSubnetRouteTableIds, SubnetType.PRIVATE, this.availabilityZones, 'privateSubnetIds', 'privateSubnetNames', 'privateSubnetRouteTableIds'); const iso = new ImportSubnetGroup(props.isolatedSubnetIds, props.isolatedSubnetNames, props.isolatedSubnetRouteTableIds, SubnetType.ISOLATED, this.availabilityZones, 'isolatedSubnetIds', 'isolatedSubnetNames', 'isolatedSubnetRouteTableIds'); - // tslint:enable:max-line-length + /* eslint-enable max-len */ this.publicSubnets = pub.import(this); this.privateSubnets = priv.import(this); @@ -1876,7 +1873,7 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat const ref = Token.isUnresolved(attrs.subnetId) ? `at '${scope.node.path}/${id}'` : `'${attrs.subnetId}'`; - // tslint:disable-next-line: max-line-length + // eslint-disable-next-line max-len scope.node.addWarning(`No routeTableId was provided to the subnet ${ref}. Attempting to read its .routeTable.routeTableId will return null/undefined. (More info: https://github.com/aws/aws-cdk/pull/3171)`); } @@ -1890,7 +1887,7 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat public get availabilityZone(): string { if (!this._availabilityZone) { - // tslint:disable-next-line: max-line-length + // eslint-disable-next-line max-len throw new Error("You cannot reference a Subnet's availability zone if it was not supplied. Add the availabilityZone when importing using Subnet.fromSubnetAttributes()"); } return this._availabilityZone; @@ -1924,12 +1921,12 @@ function determineNatGatewayCount(requestedCount: number | undefined, subnetConf const count = requestedCount !== undefined ? Math.min(requestedCount, azCount) : (hasPrivateSubnets ? azCount : 0); if (count === 0 && hasPrivateSubnets) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error('If you do not want NAT gateways (natGateways=0), make sure you don\'t configure any PRIVATE subnets in \'subnetConfiguration\' (make them PUBLIC or ISOLATED instead)'); } if (count > 0 && !hasPublicSubnets) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`If you configure PRIVATE subnets in 'subnetConfiguration', you must also configure PUBLIC subnets to put the NAT gateways into (got ${JSON.stringify(subnetConfig)}.`); } diff --git a/packages/@aws-cdk/aws-ec2/lib/vpn.ts b/packages/@aws-cdk/aws-ec2/lib/vpn.ts index 073271572ad92..d826c4cbab0cd 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpn.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpn.ts @@ -1,6 +1,6 @@ +import * as net from 'net'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as cdk from '@aws-cdk/core'; -import * as net from 'net'; import { CfnCustomerGateway, CfnVPNConnection, @@ -252,9 +252,9 @@ export class VpnConnection extends cdk.Resource implements IVpnConnection { props.tunnelOptions.forEach((options, index) => { if (options.preSharedKey && !/^[a-zA-Z1-9._][a-zA-Z\d._]{7,63}$/.test(options.preSharedKey)) { - // tslint:disable:max-line-length + /* eslint-disable max-len */ throw new Error(`The \`preSharedKey\` ${options.preSharedKey} for tunnel ${index + 1} is invalid. Allowed characters are alphanumeric characters and ._. Must be between 8 and 64 characters in length and cannot start with zero (0).`); - // tslint:enable:max-line-length + /* eslint-enable max-len */ } if (options.tunnelInsideCidr) { @@ -263,9 +263,8 @@ export class VpnConnection extends cdk.Resource implements IVpnConnection { } if (!/^169\.254\.\d{1,3}\.\d{1,3}\/30$/.test(options.tunnelInsideCidr)) { - // tslint:disable:max-line-length + /* eslint-disable-next-line max-len */ throw new Error(`The \`tunnelInsideCidr\` ${options.tunnelInsideCidr} for tunnel ${index + 1} is not a size /30 CIDR block from the 169.254.0.0/16 range.`); - // tslint:enable:max-line-length } } }); diff --git a/packages/@aws-cdk/aws-ec2/lib/windows-versions.ts b/packages/@aws-cdk/aws-ec2/lib/windows-versions.ts index 9d151296b7e1a..b14134eec2363 100644 --- a/packages/@aws-cdk/aws-ec2/lib/windows-versions.ts +++ b/packages/@aws-cdk/aws-ec2/lib/windows-versions.ts @@ -115,7 +115,7 @@ export enum WindowsVersion { WINDOWS_SERVER_2016_SWEDISH_FULL_BASE = 'Windows_Server-2016-Swedish-Full-Base', WINDOWS_SERVER_2016_TURKISH_FULL_BASE = 'Windows_Server-2016-Turkish-Full-Base', WINDOWS_SERVER_2008_R2_SP1_ENGLISH_64BIT_CORE_SQL_2012_SP4_STANDARD = 'Windows_Server-2008-R2_SP1-English-64Bit-Core_SQL_2012_SP4_Standard', - // tslint:disable-next-line: max-line-length + // eslint-disable-next-line max-len WINDOWS_SERVER_2008_R2_SP1_LANGUAGE_PACKS_64BIT_SQL_2008_R2_SP3_STANDARD = 'Windows_Server-2008-R2_SP1-Language_Packs-64Bit-SQL_2008_R2_SP3_Standard', WINDOWS_SERVER_2012_RTM_CZECH_64BIT_BASE = 'Windows_Server-2012-RTM-Czech-64Bit-Base', WINDOWS_SERVER_2012_RTM_TURKISH_64BIT_BASE = 'Windows_Server-2012-RTM-Turkish-64Bit-Base', diff --git a/packages/@aws-cdk/aws-ec2/test/instance.test.ts b/packages/@aws-cdk/aws-ec2/test/instance.test.ts index 06afe520cb1aa..2a78f3cdf516e 100644 --- a/packages/@aws-cdk/aws-ec2/test/instance.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/instance.test.ts @@ -104,7 +104,7 @@ nodeunitShim({ test.done(); }, - 'blockDeviceMappings': { + blockDeviceMappings: { 'can set blockDeviceMappings'(test: Test) { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-ec2/test/volume.test.ts b/packages/@aws-cdk/aws-ec2/test/volume.test.ts index dbe368285fb40..0600d201755a7 100644 --- a/packages/@aws-cdk/aws-ec2/test/volume.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/volume.test.ts @@ -1251,47 +1251,6 @@ nodeunitShim({ 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', - }); - }, '`availabilityZone` is a region followed by a letter (ex: `us-east-1a`), or a token'); - test.throws(() => { - new Volume(stack, `Volume${idx++}`, { - availabilityZone: 'Virginia', - }); - }, '`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) - }); - }, '`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) - }); - }, '`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(); diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-endpoint-service.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc-endpoint-service.test.ts index f7840f14829a2..0f641a3403839 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc-endpoint-service.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc-endpoint-service.test.ts @@ -3,7 +3,7 @@ import { ArnPrincipal } from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; -// tslint:disable-next-line:max-line-length +// eslint-disable-next-line max-len import { IVpcEndpointServiceLoadBalancer, Vpc, VpcEndpointService } from '../lib'; /** diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-endpoint.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc-endpoint.test.ts index 30c6018c961ac..b0e63a42a4874 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc-endpoint.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc-endpoint.test.ts @@ -3,7 +3,7 @@ import { AnyPrincipal, PolicyStatement } from '@aws-cdk/aws-iam'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { ContextProvider, Stack } from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; -// tslint:disable-next-line:max-line-length +// eslint-disable-next-line max-len import { GatewayVpcEndpoint, GatewayVpcEndpointAwsService, InterfaceVpcEndpoint, InterfaceVpcEndpointAwsService, InterfaceVpcEndpointService, SecurityGroup, SubnetType, Vpc } from '../lib'; nodeunitShim({ @@ -470,6 +470,49 @@ nodeunitShim({ ], })); + test.done(); + }, + 'test endpoint service context with aws service'(test: Test) { + // GIVEN + const stack = new Stack(undefined, 'TestStack', { env: { account: '123456789012', region: 'us-east-1' } }); + + // Setup context for stack AZs + stack.node.setContext( + ContextProvider.getKey(stack, { + provider: cxschema.ContextProvider.AVAILABILITY_ZONE_PROVIDER, + }).key, + ['us-east-1a', 'us-east-1b', 'us-east-1c']); + // Setup context for endpoint service AZs + stack.node.setContext( + ContextProvider.getKey(stack, { + provider: cxschema.ContextProvider.ENDPOINT_SERVICE_AVAILABILITY_ZONE_PROVIDER, + props: { + serviceName: 'com.amazonaws.us-east-1.execute-api', + }, + }).key, + ['us-east-1a', 'us-east-1c']); + + const vpc = new Vpc(stack, 'VPC'); + + // WHEN + vpc.addInterfaceEndpoint('API Gateway', { + service: InterfaceVpcEndpointAwsService.APIGATEWAY, + lookupSupportedAzs: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::EC2::VPCEndpoint', { + ServiceName: 'com.amazonaws.us-east-1.execute-api', + SubnetIds: [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', + }, + { + Ref: 'VPCPrivateSubnet3Subnet3EDCD457', + }, + ], + })); + test.done(); }, }, diff --git a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts index 280c1e2ac4e11..b92ef155a5ccb 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts @@ -1179,7 +1179,7 @@ nodeunitShim({ const subnet = Subnet.fromSubnetId(stack, 'subnet1', 'pub-1'); // THEN - // tslint:disable-next-line: max-line-length + // eslint-disable-next-line max-len test.throws(() => subnet.availabilityZone, "You cannot reference a Subnet's availability zone if it was not supplied. Add the availabilityZone when importing using Subnet.fromSubnetAttributes()"); test.done(); }, @@ -1189,11 +1189,11 @@ nodeunitShim({ const stack = getTestStack(); // WHEN - const subnet = Subnet.fromSubnetAttributes(stack, 'subnet1', { subnetId : 'pub-1', availabilityZone: '' }); + const subnet = Subnet.fromSubnetAttributes(stack, 'subnet1', { subnetId: 'pub-1', availabilityZone: '' }); // THEN test.deepEqual(subnet.subnetId, 'pub-1'); - // tslint:disable-next-line: max-line-length + // eslint-disable-next-line max-len test.throws(() => subnet.availabilityZone, "You cannot reference a Subnet's availability zone if it was not supplied. Add the availabilityZone when importing using Subnet.fromSubnetAttributes()"); test.done(); }, @@ -1203,7 +1203,7 @@ nodeunitShim({ const stack = getTestStack(); // WHEN - const subnet = Subnet.fromSubnetAttributes(stack, 'subnet1', { subnetId : 'pub-1', availabilityZone: 'az-1234' }); + const subnet = Subnet.fromSubnetAttributes(stack, 'subnet1', { subnetId: 'pub-1', availabilityZone: 'az-1234' }); // THEN test.deepEqual(subnet.subnetId, 'pub-1'); @@ -1305,11 +1305,11 @@ function hasTags(expectedTags: Array<{Key: string, Value: string}>): (props: any }); return actualTags.length === expectedTags.length; } catch (e) { - // tslint:disable:no-console + /* eslint-disable no-console */ console.error('Tags are incorrect'); console.error('found tags ', props.Tags); console.error('expected tags ', expectedTags); - // tslint:enable:no-console + /* eslint-enable no-console */ throw e; } }; diff --git a/packages/@aws-cdk/aws-ecr-assets/.gitignore b/packages/@aws-cdk/aws-ecr-assets/.gitignore index 84107ada8a317..7e3964a75701e 100644 --- a/packages/@aws-cdk/aws-ecr-assets/.gitignore +++ b/packages/@aws-cdk/aws-ecr-assets/.gitignore @@ -15,3 +15,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr-assets/.npmignore b/packages/@aws-cdk/aws-ecr-assets/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-ecr-assets/.npmignore +++ b/packages/@aws-cdk/aws-ecr-assets/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr-assets/test/test.image-asset.ts b/packages/@aws-cdk/aws-ecr-assets/test/test.image-asset.ts index 3a3d0d94e5661..54930ec176e14 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/test.image-asset.ts +++ b/packages/@aws-cdk/aws-ecr-assets/test/test.image-asset.ts @@ -7,7 +7,7 @@ import { Test } from 'nodeunit'; import * as path from 'path'; import { DockerImageAsset } from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'test instantiating Asset Image'(test: Test) { diff --git a/packages/@aws-cdk/aws-ecr/.gitignore b/packages/@aws-cdk/aws-ecr/.gitignore index 0bd6133da4d09..018c65919d67c 100644 --- a/packages/@aws-cdk/aws-ecr/.gitignore +++ b/packages/@aws-cdk/aws-ecr/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/.npmignore b/packages/@aws-cdk/aws-ecr/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-ecr/.npmignore +++ b/packages/@aws-cdk/aws-ecr/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/README.md b/packages/@aws-cdk/aws-ecr/README.md index bcb5c01688dc9..97115adc5f647 100644 --- a/packages/@aws-cdk/aws-ecr/README.md +++ b/packages/@aws-cdk/aws-ecr/README.md @@ -22,7 +22,7 @@ const repository = new ecr.Repository(this, 'Repository'); ### Image scanning -Amazon ECR image scanning helps in identifying software vulnerabilities in your container images. You can manually scan container images stored in Amazon ECR, or you can configure your repositories to scan images when you push them to a repository. To create a new reposity to scan on push, simply enable `imageScanOnPush` in the properties +Amazon ECR image scanning helps in identifying software vulnerabilities in your container images. You can manually scan container images stored in Amazon ECR, or you can configure your repositories to scan images when you push them to a repository. To create a new repository to scan on push, simply enable `imageScanOnPush` in the properties ```ts const repository = new ecr.Repository(stack, 'Repo', { diff --git a/packages/@aws-cdk/aws-ecr/test/integ.imagescan.ts b/packages/@aws-cdk/aws-ecr/test/integ.imagescan.ts index 9feece9392f8e..a7b1766081354 100644 --- a/packages/@aws-cdk/aws-ecr/test/integ.imagescan.ts +++ b/packages/@aws-cdk/aws-ecr/test/integ.imagescan.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as cdk from '@aws-cdk/core'; import * as ecr from '../lib'; diff --git a/packages/@aws-cdk/aws-ecr/test/test.repository.ts b/packages/@aws-cdk/aws-ecr/test/test.repository.ts index f93cfe16fe006..f53e67310d7e5 100644 --- a/packages/@aws-cdk/aws-ecr/test/test.repository.ts +++ b/packages/@aws-cdk/aws-ecr/test/test.repository.ts @@ -4,7 +4,7 @@ import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as ecr from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'construct repository'(test: Test) { @@ -51,7 +51,7 @@ export = { // THEN expect(stack).to(haveResource('AWS::ECR::Repository', { LifecyclePolicy: { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len LifecyclePolicyText: '{"rules":[{"rulePriority":1,"selection":{"tagStatus":"tagged","tagPrefixList":["abc"],"countType":"imageCountMoreThan","countNumber":1},"action":{"type":"expire"}}]}', }, })); @@ -72,7 +72,7 @@ export = { // THEN expect(stack).to(haveResource('AWS::ECR::Repository', { LifecyclePolicy: { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len LifecyclePolicyText: '{"rules":[{"rulePriority":1,"selection":{"tagStatus":"any","countType":"sinceImagePushed","countNumber":5,"countUnit":"days"},"action":{"type":"expire"}}]}', }, })); @@ -93,7 +93,7 @@ export = { // THEN expect(stack).to(haveResource('AWS::ECR::Repository', { LifecyclePolicy: { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len LifecyclePolicyText: '{"rules":[{"rulePriority":1,"selection":{"tagStatus":"any","countType":"imageCountMoreThan","countNumber":5},"action":{"type":"expire"}}]}', }, })); @@ -113,7 +113,7 @@ export = { // THEN expect(stack).to(haveResource('AWS::ECR::Repository', { LifecyclePolicy: { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len LifecyclePolicyText: '{"rules":[{"rulePriority":10,"selection":{"tagStatus":"tagged","tagPrefixList":["b"],"countType":"imageCountMoreThan","countNumber":5},"action":{"type":"expire"}},{"rulePriority":11,"selection":{"tagStatus":"tagged","tagPrefixList":["a"],"countType":"imageCountMoreThan","countNumber":5},"action":{"type":"expire"}}]}', }, })); @@ -133,7 +133,7 @@ export = { // THEN expect(stack).to(haveResource('AWS::ECR::Repository', { LifecyclePolicy: { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len LifecyclePolicyText: '{"rules":[{"rulePriority":1,"selection":{"tagStatus":"tagged","tagPrefixList":["important"],"countType":"imageCountMoreThan","countNumber":999},"action":{"type":"expire"}},{"rulePriority":2,"selection":{"tagStatus":"any","countType":"imageCountMoreThan","countNumber":5},"action":{"type":"expire"}}]}', }, })); @@ -155,7 +155,7 @@ export = { // THEN expect(stack).to(haveResource('AWS::ECR::Repository', { 'LifecyclePolicy': { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len 'LifecyclePolicyText': '{"rules":[{"rulePriority":1,"selection":{"tagStatus":"any","countType":"imageCountMoreThan","countNumber":3},"action":{"type":"expire"}}]}', }, })); diff --git a/packages/@aws-cdk/aws-ecs-patterns/.gitignore b/packages/@aws-cdk/aws-ecs-patterns/.gitignore index 32a10d785e8fb..dcc1dc41e477f 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/.gitignore +++ b/packages/@aws-cdk/aws-ecs-patterns/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/.npmignore b/packages/@aws-cdk/aws-ecs-patterns/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/.npmignore +++ b/packages/@aws-cdk/aws-ecs-patterns/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts index f5908a12e704f..27da4d31e3be0 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts @@ -1,4 +1,4 @@ -import { DnsValidatedCertificate, ICertificate } from '@aws-cdk/aws-certificatemanager'; +import { Certificate, CertificateValidation, ICertificate } from '@aws-cdk/aws-certificatemanager'; import { IVpc } from '@aws-cdk/aws-ec2'; import { AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerImage, ICluster, LogDriver, PropagatedTagSource, Secret } from '@aws-cdk/aws-ecs'; import { ApplicationListener, ApplicationLoadBalancer, ApplicationProtocol, ApplicationTargetGroup, @@ -335,9 +335,9 @@ export abstract class ApplicationLoadBalancedServiceBase extends cdk.Construct { if (props.certificate !== undefined) { this.certificate = props.certificate; } else { - this.certificate = new DnsValidatedCertificate(this, 'Certificate', { + this.certificate = new Certificate(this, 'Certificate', { domainName: props.domainName, - hostedZone: props.domainZone, + validation: CertificateValidation.fromDns(props.domainZone), }); } } diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts index e5bb9ba08ce7f..5764b3fcdaeb5 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts @@ -1,4 +1,4 @@ -import { DnsValidatedCertificate, ICertificate } from '@aws-cdk/aws-certificatemanager'; +import { Certificate, CertificateValidation, ICertificate } from '@aws-cdk/aws-certificatemanager'; import { IVpc } from '@aws-cdk/aws-ec2'; import { AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerDefinition, ContainerImage, ICluster, LogDriver, PropagatedTagSource, Protocol, Secret } from '@aws-cdk/aws-ecs'; @@ -536,9 +536,9 @@ export abstract class ApplicationMultipleTargetGroupsServiceBase extends Constru if (certificate !== undefined) { return certificate; } else { - return new DnsValidatedCertificate(this, `Certificate${listenerName}`, { + return new Certificate(this, `Certificate${listenerName}`, { domainName, - hostedZone: domainZone, + validation: CertificateValidation.fromDns(domainZone), }); } } @@ -611,4 +611,4 @@ interface ListenerConfig { * @default - No Route53 hosted domain zone. */ readonly domainZone?: IHostedZone; -} \ 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 09f786c2ea760..71736c651573b 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 @@ -462,17 +462,17 @@ export = { }); // THEN - stack contains a load balancer, a service, and a certificate - expect(stack).to(haveResource('AWS::CloudFormation::CustomResource', { - ServiceToken: { - 'Fn::GetAtt': [ - 'ServiceCertificateCertificateRequestorFunctionB69CD117', - 'Arn', - ], - }, + expect(stack).to(haveResource('AWS::CertificateManager::Certificate', { DomainName: 'api.example.com', - HostedZoneId: { - Ref: 'HostedZoneDB99F866', - }, + DomainValidationOptions: [ + { + DomainName: 'api.example.com', + HostedZoneId: { + Ref: 'HostedZoneDB99F866', + }, + }, + ], + ValidationMethod: 'DNS', })); expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer')); @@ -481,10 +481,9 @@ export = { Port: 443, Protocol: 'HTTPS', Certificates: [{ - CertificateArn: { 'Fn::GetAtt': [ - 'ServiceCertificateCertificateRequestorResource0FC297E9', - 'Arn', - ]}, + CertificateArn: { + Ref: 'ServiceCertificateA7C65FE6', + }, }], })); 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 e7434c8e2eafc..57ef1e2c0e4a9 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 @@ -443,10 +443,7 @@ "Certificates": [ { "CertificateArn": { - "Fn::GetAtt": [ - "myServiceCertificateCertificateRequestorResource76DB9304", - "Arn" - ] + "Ref": "myServiceCertificate152F9DDA" } } ] @@ -463,154 +460,19 @@ } } }, - "myServiceCertificateCertificateRequestorFunctionServiceRoleB55B306C": { - "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" - ] - ] - } - ] - } - }, - "myServiceCertificateCertificateRequestorFunctionServiceRoleDefaultPolicyFB403A3B": { - "Type": "AWS::IAM::Policy", + "myServiceCertificate152F9DDA": { + "Type": "AWS::CertificateManager::Certificate", "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "acm:RequestCertificate", - "acm:DescribeCertificate", - "acm:DeleteCertificate" - ], - "Effect": "Allow", - "Resource": "*" - }, - { - "Action": "route53:GetChange", - "Effect": "Allow", - "Resource": "*" - }, - { - "Action": "route53:changeResourceRecordSets", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":route53:::hostedzone/fakeId" - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "myServiceCertificateCertificateRequestorFunctionServiceRoleDefaultPolicyFB403A3B", - "Roles": [ + "DomainName": "test.example.com", + "DomainValidationOptions": [ { - "Ref": "myServiceCertificateCertificateRequestorFunctionServiceRoleB55B306C" + "DomainName": "test.example.com", + "HostedZoneId": "fakeId" } - ] + ], + "ValidationMethod": "DNS" } }, - "myServiceCertificateCertificateRequestorFunctionC16CEAAF": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParameters19e461d2ff1a5b90438fed6ceee4c197d7efee8712a6f76d85b501ab20bfb1a2S3BucketFCCD3A76" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters19e461d2ff1a5b90438fed6ceee4c197d7efee8712a6f76d85b501ab20bfb1a2S3VersionKey07AF06B6" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters19e461d2ff1a5b90438fed6ceee4c197d7efee8712a6f76d85b501ab20bfb1a2S3VersionKey07AF06B6" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "index.certificateRequestHandler", - "Role": { - "Fn::GetAtt": [ - "myServiceCertificateCertificateRequestorFunctionServiceRoleB55B306C", - "Arn" - ] - }, - "Runtime": "nodejs10.x", - "Timeout": 900 - }, - "DependsOn": [ - "myServiceCertificateCertificateRequestorFunctionServiceRoleDefaultPolicyFB403A3B", - "myServiceCertificateCertificateRequestorFunctionServiceRoleB55B306C" - ] - }, - "myServiceCertificateCertificateRequestorResource76DB9304": { - "Type": "AWS::CloudFormation::CustomResource", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "myServiceCertificateCertificateRequestorFunctionC16CEAAF", - "Arn" - ] - }, - "DomainName": "test.example.com", - "HostedZoneId": "fakeId" - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "myServiceDNSD76FB53A": { "Type": "AWS::Route53::RecordSet", "Properties": { @@ -872,19 +734,5 @@ ] } } - }, - "Parameters": { - "AssetParameters19e461d2ff1a5b90438fed6ceee4c197d7efee8712a6f76d85b501ab20bfb1a2S3BucketFCCD3A76": { - "Type": "String", - "Description": "S3 bucket for asset \"19e461d2ff1a5b90438fed6ceee4c197d7efee8712a6f76d85b501ab20bfb1a2\"" - }, - "AssetParameters19e461d2ff1a5b90438fed6ceee4c197d7efee8712a6f76d85b501ab20bfb1a2S3VersionKey07AF06B6": { - "Type": "String", - "Description": "S3 key for asset version \"19e461d2ff1a5b90438fed6ceee4c197d7efee8712a6f76d85b501ab20bfb1a2\"" - }, - "AssetParameters19e461d2ff1a5b90438fed6ceee4c197d7efee8712a6f76d85b501ab20bfb1a2ArtifactHash652C125C": { - "Type": "String", - "Description": "Artifact hash for asset \"19e461d2ff1a5b90438fed6ceee4c197d7efee8712a6f76d85b501ab20bfb1a2\"" - } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/.gitignore b/packages/@aws-cdk/aws-ecs/.gitignore index e18824365eed3..255c76b856cb1 100644 --- a/packages/@aws-cdk/aws-ecs/.gitignore +++ b/packages/@aws-cdk/aws-ecs/.gitignore @@ -15,3 +15,5 @@ nyc.config.js !lib/images/adopt-repository/* *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/.npmignore b/packages/@aws-cdk/aws-ecs/.npmignore index 047d5ca251254..d683303ca5c16 100644 --- a/packages/@aws-cdk/aws-ecs/.npmignore +++ b/packages/@aws-cdk/aws-ecs/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 702c4617bace2..75c4ac3698e14 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -474,7 +474,7 @@ const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('example-image'), memoryLimitMiB: 256, - logging: ecs.LogDrivers.awslogs({ streamPrefix: 'EventDemo' }) + logging: ecs.LogDrivers.awsLogs({ streamPrefix: 'EventDemo' }) }); ``` diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 6e37c99b2250a..acf23a40e9305 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -591,7 +591,7 @@ export abstract class BaseService extends Resource * This method is called to create a networkConfiguration. * @deprecated use configureAwsVpcNetworkingWithSecurityGroups instead. */ - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len protected configureAwsVpcNetworking(vpc: ec2.IVpc, assignPublicIp?: boolean, vpcSubnets?: ec2.SubnetSelection, securityGroup?: ec2.ISecurityGroup) { if (vpcSubnets === undefined) { vpcSubnets = assignPublicIp ? { subnetType: ec2.SubnetType.PUBLIC } : {}; @@ -613,7 +613,7 @@ export abstract class BaseService extends Resource /** * This method is called to create a networkConfiguration. */ - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len protected configureAwsVpcNetworkingWithSecurityGroups(vpc: ec2.IVpc, assignPublicIp?: boolean, vpcSubnets?: ec2.SubnetSelection, securityGroups?: ec2.ISecurityGroup[]) { if (vpcSubnets === undefined) { vpcSubnets = assignPublicIp ? { subnetType: ec2.SubnetType.PUBLIC } : {}; diff --git a/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts b/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts index 8560a3c0e2ba7..447cd0224b114 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts @@ -6,7 +6,6 @@ import { Construct } from '@aws-cdk/core'; /** * The properties of a scalable attribute representing task count. */ -// tslint:disable-next-line:no-empty-interface export interface ScalableTaskCountProps extends appscaling.BaseScalableAttributeProps { } diff --git a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts index a04e51781b018..94bc5bbef39b4 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -341,7 +341,7 @@ export class TaskDefinition extends TaskDefinitionBase { const targetContainerPort = options.containerPort || targetContainer.containerPort; const portMapping = targetContainer.findPortMapping(targetContainerPort, targetProtocol); if (portMapping === undefined) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`Container '${targetContainer}' has no mapping for port ${options.containerPort} and protocol ${targetProtocol}. Did you call "container.addPortMappings()"?`); } return { diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts index d6bee7ccea946..a22c4801fcebb 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts @@ -420,7 +420,7 @@ export = { // THEN expect(stack).to(haveResource('AWS::ECR::Repository', { LifecyclePolicy: { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len LifecyclePolicyText: '{"rules":[{"rulePriority":10,"selection":{"tagStatus":"tagged","tagPrefixList":["abc"],"countType":"imageCountMoreThan","countNumber":1},"action":{"type":"expire"}}]}', RegistryId: '123456789101', }, diff --git a/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts b/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts index b7d4b59a47e79..d0be3430ceb50 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts @@ -56,7 +56,7 @@ export = { { Ref: 'EcsCluster97242B84', }, - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len ' >> /etc/ecs/ecs.config\nsudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP\nsudo service iptables save\necho ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config', ], ], @@ -201,7 +201,7 @@ export = { { Ref: 'EcsCluster97242B84', }, - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len ' >> /etc/ecs/ecs.config\nsudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP\nsudo service iptables save\necho ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config', ], ], @@ -501,7 +501,7 @@ export = { { Ref: 'EcsCluster97242B84', }, - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len ' >> /etc/ecs/ecs.config\nsudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP\nsudo service iptables save\necho ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config', ], ], @@ -1259,7 +1259,7 @@ export = { { Ref: 'EcsCluster97242B84', }, - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len ' >> /etc/ecs/ecs.config\nsudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP\nsudo service iptables save\necho ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config', ], ], diff --git a/packages/@aws-cdk/aws-efs/.gitignore b/packages/@aws-cdk/aws-efs/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-efs/.gitignore +++ b/packages/@aws-cdk/aws-efs/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-efs/.npmignore b/packages/@aws-cdk/aws-efs/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-efs/.npmignore +++ b/packages/@aws-cdk/aws-efs/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-efs/jest.config.js b/packages/@aws-cdk/aws-efs/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-efs/jest.config.js +++ b/packages/@aws-cdk/aws-efs/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; 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 942be37ca9a3f..8103ee40bae79 100644 --- a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts +++ b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts @@ -4,13 +4,11 @@ import { ConcreteDependable, Construct, IDependable, IResource, RemovalPolicy, R import { AccessPoint, AccessPointOptions } from './access-point'; import { CfnFileSystem, CfnMountTarget } from './efs.generated'; -// tslint:disable:max-line-length /** * EFS Lifecycle Policy, if a file is not accessed for given days, it will move to EFS Infrequent Access. * * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-elasticfilesystem-filesystem-lifecyclepolicies */ -// tslint:enable export enum LifecyclePolicy { /** * After 7 days of not being accessed. diff --git a/packages/@aws-cdk/aws-eks-legacy/.gitignore b/packages/@aws-cdk/aws-eks-legacy/.gitignore index 6031555a5720f..752d096cc6b12 100644 --- a/packages/@aws-cdk/aws-eks-legacy/.gitignore +++ b/packages/@aws-cdk/aws-eks-legacy/.gitignore @@ -15,3 +15,5 @@ coverage *.snk nyc.config.js !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks-legacy/.npmignore b/packages/@aws-cdk/aws-eks-legacy/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-eks-legacy/.npmignore +++ b/packages/@aws-cdk/aws-eks-legacy/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks-legacy/test/test.awsauth.ts b/packages/@aws-cdk/aws-eks-legacy/test/test.awsauth.ts index 6bdf28747b11e..c9bba19a0e35e 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/test.awsauth.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/test.awsauth.ts @@ -5,7 +5,7 @@ import { Cluster, KubernetesResource } from '../lib'; import { AwsAuth } from '../lib/aws-auth'; import { testFixtureNoVpc } from './util'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ export = { 'empty aws-auth'(test: Test) { diff --git a/packages/@aws-cdk/aws-eks-legacy/test/test.cluster.ts b/packages/@aws-cdk/aws-eks-legacy/test/test.cluster.ts index 3e536e0b92f6e..92b1dab642b6b 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/test.cluster.ts @@ -7,7 +7,7 @@ import * as eks from '../lib'; import { spotInterruptHandler } from '../lib/spot-interrupt-handler'; import { testFixture, testFixtureNoVpc } from './util'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ export = { 'a default cluster spans all subnets'(test: Test) { diff --git a/packages/@aws-cdk/aws-eks-legacy/test/test.helm-chart.ts b/packages/@aws-cdk/aws-eks-legacy/test/test.helm-chart.ts index e2a621871d023..5bdec9c9e62a3 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/test.helm-chart.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/test.helm-chart.ts @@ -3,7 +3,7 @@ import { Test } from 'nodeunit'; import * as eks from '../lib'; import { testFixtureCluster } from './util'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ export = { 'add Helm chart': { diff --git a/packages/@aws-cdk/aws-eks-legacy/test/test.manifest.ts b/packages/@aws-cdk/aws-eks-legacy/test/test.manifest.ts index 21459b633f6b0..30afa40e3069e 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/test.manifest.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/test.manifest.ts @@ -3,7 +3,7 @@ import { Test } from 'nodeunit'; import { Cluster, KubernetesResource } from '../lib'; import { testFixtureNoVpc } from './util'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ export = { 'basic usage'(test: Test) { diff --git a/packages/@aws-cdk/aws-eks-legacy/test/test.user-data.ts b/packages/@aws-cdk/aws-eks-legacy/test/test.user-data.ts index c3d62ede114b4..e841ebf280d62 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/test.user-data.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/test.user-data.ts @@ -4,7 +4,7 @@ import { App, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import { renderUserData } from '../lib/user-data'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ export = { 'default user data'(test: Test) { diff --git a/packages/@aws-cdk/aws-eks/.gitignore b/packages/@aws-cdk/aws-eks/.gitignore index 1ad6da1333303..369fd476d35b7 100644 --- a/packages/@aws-cdk/aws-eks/.gitignore +++ b/packages/@aws-cdk/aws-eks/.gitignore @@ -16,3 +16,5 @@ nyc.config.js .nycrc !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/.npmignore b/packages/@aws-cdk/aws-eks/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-eks/.npmignore +++ b/packages/@aws-cdk/aws-eks/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts index f20ddd85c5704..d88c8900e3020 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts @@ -1,4 +1,4 @@ -// tslint:disable:no-console +/* eslint-disable no-console */ import { IsCompleteResponse, OnEventResponse } from '@aws-cdk/custom-resources/lib/provider-framework/types'; // eslint-disable-next-line import/no-extraneous-dependencies diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts index 57d3ae20f8cef..7383689e4a95a 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts @@ -60,7 +60,7 @@ export abstract class ResourceHandler { } protected log(x: any) { - // tslint:disable-next-line: no-console + // eslint-disable-next-line no-console console.log(JSON.stringify(x, undefined, 2)); } diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts index f2b796297246a..d1be0c12e1e38 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts @@ -1,4 +1,4 @@ -// tslint:disable:no-console +/* eslint-disable no-console */ import { IsCompleteResponse } from '@aws-cdk/custom-resources/lib/provider-framework/types'; // eslint-disable-next-line import/no-extraneous-dependencies diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 6a245c3e3e2ef..64338e877075b 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -1,10 +1,10 @@ +import * as fs from 'fs'; +import * as path from 'path'; import * as autoscaling from '@aws-cdk/aws-autoscaling'; 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, CfnResource, 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'; @@ -292,6 +292,11 @@ export class KubernetesVersion { */ public static readonly V1_16 = KubernetesVersion.of('1.16'); + /** + * Kubernetes version 1.17 + */ + public static readonly V1_17 = KubernetesVersion.of('1.17'); + /** * Custom cluster version * @param version custom version number @@ -492,6 +497,16 @@ export class Cluster extends Resource implements ICluster { resource = new ClusterResource(this, 'Resource', clusterProps); this._clusterResource = resource; + // see https://github.com/aws/aws-cdk/issues/9027 + this._clusterResource.creationRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ec2:DescribeVpcs'], + resources: [ stack.formatArn({ + service: 'ec2', + resource: 'vpc', + resourceName: this.vpc.vpcId, + })], + })); + // we use an SSM parameter as a barrier because it's free and fast. this._kubectlReadyBarrier = new CfnResource(this, 'KubectlReadyBarrier', { type: 'AWS::SSM::Parameter', diff --git a/packages/@aws-cdk/aws-eks/lib/user-data.ts b/packages/@aws-cdk/aws-eks/lib/user-data.ts index b613663b42a67..cf38cf7ee9761 100644 --- a/packages/@aws-cdk/aws-eks/lib/user-data.ts +++ b/packages/@aws-cdk/aws-eks/lib/user-data.ts @@ -2,7 +2,7 @@ import * as autoscaling from '@aws-cdk/aws-autoscaling'; import { Stack } from '@aws-cdk/core'; import { BootstrapOptions, ICluster } from './cluster'; -// tslint:disable-next-line:max-line-length +// eslint-disable-next-line max-len export function renderAmazonLinuxUserData(clusterName: string, autoScalingGroup: autoscaling.AutoScalingGroup, options: BootstrapOptions = {}): string[] { const stack = Stack.of(autoScalingGroup); diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index ded5a08310a3c..523f7e750e7bb 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -65,7 +65,7 @@ "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.31", "@types/yaml": "1.2.0", - "aws-sdk": "^2.711.0", + "aws-sdk": "^2.715.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json index afec1aeda1a31..1aa1b2d54725d 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 @@ -768,6 +768,25 @@ "Effect": "Allow", "Resource": "*" }, + { + "Action": "ec2:DescribeVpcs", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ec2:test-region:12345678:vpc/", + { + "Ref": "Vpc8378EB38" + } + ] + ] + } + }, { "Action": "iam:PassRole", "Effect": "Allow", 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 f346b24dee180..6340be221a298 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,7 @@ +/// !cdk-integ pragma:ignore-assets import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { App, CfnOutput, Duration } from '@aws-cdk/core'; +import { App, CfnOutput, Duration, Token } from '@aws-cdk/core'; import * as eks from '../lib'; import * as hello from './hello-k8s'; import { TestStack } from './util'; @@ -112,10 +113,36 @@ class EksClusterStack extends TestStack { } } +// this test uses the bottlerocket image, which is only supported in these +// regions. see https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/aws-eks#bottlerocket +const supportedRegions = [ + 'ap-northeast-1', + 'ap-south-1', + 'eu-central-1', + 'us-east-1', + 'us-west-2', +]; + const app = new App(); // since the EKS optimized AMI is hard-coded here based on the region, // we need to actually pass in a specific region. -new EksClusterStack(app, 'aws-cdk-eks-cluster-test'); +const stack = new EksClusterStack(app, 'aws-cdk-eks-cluster-test'); + +if (process.env.CDK_INTEG_ACCOUNT !== '12345678') { + + // only validate if we are about to actually deploy. + // TODO: better way to determine this, right now the 'CDK_INTEG_ACCOUNT' seems like the only way. + + if (Token.isUnresolved(stack.region)) { + throw new Error(`region (${stack.region}) cannot be a token and must be configured to one of: ${supportedRegions}`); + } + + if (!supportedRegions.includes(stack.region)) { + throw new Error(`region (${stack.region}) must be configured to one of: ${supportedRegions}`); + } + +} + app.synth(); diff --git a/packages/@aws-cdk/aws-eks/test/test.awsauth.ts b/packages/@aws-cdk/aws-eks/test/test.awsauth.ts index ca13492487027..998dd00c184fb 100644 --- a/packages/@aws-cdk/aws-eks/test/test.awsauth.ts +++ b/packages/@aws-cdk/aws-eks/test/test.awsauth.ts @@ -5,7 +5,7 @@ import { Cluster, KubernetesResource, KubernetesVersion } from '../lib'; import { AwsAuth } from '../lib/aws-auth'; import { testFixtureNoVpc } from './util'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ const CLUSTER_VERSION = KubernetesVersion.V1_16; diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index 4101348a06774..1b87a8d94a152 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -1,16 +1,16 @@ +import * as fs from 'fs'; +import * as path from 'path'; import { countResources, expect, haveResource, haveResourceLike, not } from '@aws-cdk/assert'; 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'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ const CLUSTER_VERSION = eks.KubernetesVersion.V1_16; @@ -1040,6 +1040,29 @@ export = { Effect: 'Allow', Resource: '*', }, + { + Action: 'ec2:DescribeVpcs', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ec2:us-east-1:', + { + Ref: 'AWS::AccountId', + }, + ':vpc/', + { + Ref: 'MyClusterDefaultVpc76C24A38', + }, + ], + ], + }, + }, ], Version: '2012-10-17', }, @@ -1109,6 +1132,29 @@ export = { Effect: 'Allow', Resource: '*', }, + { + Action: 'ec2:DescribeVpcs', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ec2:us-east-1:', + { + Ref: 'AWS::AccountId', + }, + ':vpc/', + { + Ref: 'MyClusterDefaultVpc76C24A38', + }, + ], + ], + }, + }, ], Version: '2012-10-17', }, diff --git a/packages/@aws-cdk/aws-eks/test/test.fargate.ts b/packages/@aws-cdk/aws-eks/test/test.fargate.ts index 870a871f266fa..531b49882b3c3 100644 --- a/packages/@aws-cdk/aws-eks/test/test.fargate.ts +++ b/packages/@aws-cdk/aws-eks/test/test.fargate.ts @@ -405,6 +405,33 @@ export = { Effect: 'Allow', Resource: '*', }, + { + Action: 'ec2:DescribeVpcs', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ec2:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':vpc/', + { + Ref: 'FargateClusterDefaultVpcE69D3A13', + }, + ], + ], + }, + }, { Action: 'iam:PassRole', Effect: 'Allow', 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 5041cc6a17701..a0340ff817ed6 100644 --- a/packages/@aws-cdk/aws-eks/test/test.helm-chart.ts +++ b/packages/@aws-cdk/aws-eks/test/test.helm-chart.ts @@ -4,7 +4,7 @@ import { Test } from 'nodeunit'; import * as eks from '../lib'; import { testFixtureCluster } from './util'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ export = { 'add Helm chart': { diff --git a/packages/@aws-cdk/aws-eks/test/test.manifest.ts b/packages/@aws-cdk/aws-eks/test/test.manifest.ts index 565cf28aaffbf..567a3d50abcde 100644 --- a/packages/@aws-cdk/aws-eks/test/test.manifest.ts +++ b/packages/@aws-cdk/aws-eks/test/test.manifest.ts @@ -3,7 +3,7 @@ import { Test } from 'nodeunit'; import { Cluster, KubernetesResource, KubernetesVersion } from '../lib'; import { testFixtureNoVpc } from './util'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ const CLUSTER_VERSION = KubernetesVersion.V1_16; diff --git a/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts b/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts index 226fd09bc30e9..16c81f6567cbc 100644 --- a/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts +++ b/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts @@ -5,7 +5,7 @@ import { Test } from 'nodeunit'; import * as eks from '../lib'; import { testFixture } from './util'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ const CLUSTER_VERSION = eks.KubernetesVersion.V1_16; diff --git a/packages/@aws-cdk/aws-eks/test/test.service-account.ts b/packages/@aws-cdk/aws-eks/test/test.service-account.ts index 8c83c62da2810..06c17a6bdd1a0 100644 --- a/packages/@aws-cdk/aws-eks/test/test.service-account.ts +++ b/packages/@aws-cdk/aws-eks/test/test.service-account.ts @@ -4,7 +4,7 @@ import { Test } from 'nodeunit'; import * as eks from '../lib'; import { testFixtureCluster } from './util'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ export = { 'add Service Account': { diff --git a/packages/@aws-cdk/aws-eks/test/test.user-data.ts b/packages/@aws-cdk/aws-eks/test/test.user-data.ts index 021cad8f16d3b..69a3bde80a1cb 100644 --- a/packages/@aws-cdk/aws-eks/test/test.user-data.ts +++ b/packages/@aws-cdk/aws-eks/test/test.user-data.ts @@ -4,7 +4,7 @@ import { App, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import { renderAmazonLinuxUserData } from '../lib/user-data'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ export = { 'default user data'(test: Test) { diff --git a/packages/@aws-cdk/aws-elasticache/.gitignore b/packages/@aws-cdk/aws-elasticache/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-elasticache/.gitignore +++ b/packages/@aws-cdk/aws-elasticache/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticache/.npmignore b/packages/@aws-cdk/aws-elasticache/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-elasticache/.npmignore +++ b/packages/@aws-cdk/aws-elasticache/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticache/jest.config.js b/packages/@aws-cdk/aws-elasticache/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-elasticache/jest.config.js +++ b/packages/@aws-cdk/aws-elasticache/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-elasticbeanstalk/.gitignore b/packages/@aws-cdk/aws-elasticbeanstalk/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-elasticbeanstalk/.gitignore +++ b/packages/@aws-cdk/aws-elasticbeanstalk/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticbeanstalk/.npmignore b/packages/@aws-cdk/aws-elasticbeanstalk/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-elasticbeanstalk/.npmignore +++ b/packages/@aws-cdk/aws-elasticbeanstalk/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticbeanstalk/jest.config.js b/packages/@aws-cdk/aws-elasticbeanstalk/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-elasticbeanstalk/jest.config.js +++ b/packages/@aws-cdk/aws-elasticbeanstalk/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/.gitignore b/packages/@aws-cdk/aws-elasticloadbalancing/.gitignore index 32a10d785e8fb..dcc1dc41e477f 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/.gitignore +++ b/packages/@aws-cdk/aws-elasticloadbalancing/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/.npmignore b/packages/@aws-cdk/aws-elasticloadbalancing/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/.npmignore +++ b/packages/@aws-cdk/aws-elasticloadbalancing/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.gitignore b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.gitignore index 0c82e8606a62b..f2e99f2cab348 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.gitignore +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.gitignore @@ -16,4 +16,5 @@ nyc.config.js .cdk.staging !.eslintrc.js -!jest.config.js \ No newline at end of file +!jest.config.js +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.npmignore b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.npmignore index 837d590e98218..d7886d9208116 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.npmignore +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.npmignore @@ -21,4 +21,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/jest.config.js b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/jest.config.js index c68c147dd5514..5c1ef76634a9f 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/jest.config.js +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/jest.config.js @@ -1,4 +1,4 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = { ...baseConfig, coverageThreshold: { diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.gitignore b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.gitignore index 1109bfe833d86..ceda962aa6202 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.gitignore +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.gitignore @@ -16,3 +16,5 @@ nyc.config.js .cdk.staging !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.npmignore b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.npmignore index 36fe3528aa4c6..a0bf754e3cc79 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.npmignore +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.npmignore @@ -21,4 +21,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/jest.config.js b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/jest.config.js +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/.gitignore b/packages/@aws-cdk/aws-elasticloadbalancingv2/.gitignore index 0bd6133da4d09..018c65919d67c 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/.gitignore +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/.npmignore b/packages/@aws-cdk/aws-elasticloadbalancingv2/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/.npmignore +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts index 6809b777a9343..7897f5582eb0a 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -308,7 +308,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis */ public addTargets(id: string, props: AddApplicationTargetsProps): ApplicationTargetGroup { if (!this.loadBalancer.vpc) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error('Can only call addTargets() when using a constructed Load Balancer or an imported Load Balancer with specified vpc; construct a new TargetGroup and use addTargetGroup'); } @@ -594,7 +594,7 @@ class ImportedApplicationListener extends Resource implements IApplicationListen * @returns The newly created target group */ public addTargets(_id: string, _props: AddApplicationTargetsProps): ApplicationTargetGroup { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error('Can only call addTargets() when using a constructed ApplicationListener; construct a new TargetGroup and use addTargetGroup.'); } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts index bbf91ed8834e4..a9e469394b8c3 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts @@ -561,13 +561,13 @@ class ImportedApplicationLoadBalancer extends Resource implements IApplicationLo public get loadBalancerCanonicalHostedZoneId(): string { if (this.props.loadBalancerCanonicalHostedZoneId) { return this.props.loadBalancerCanonicalHostedZoneId; } - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`'loadBalancerCanonicalHostedZoneId' was not provided when constructing Application Load Balancer ${this.node.path} from attributes`); } public get loadBalancerDnsName(): string { if (this.props.loadBalancerDnsName) { return this.props.loadBalancerDnsName; } - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`'loadBalancerDnsName' was not provided when constructing Application Load Balancer ${this.node.path} from attributes`); } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts index 691d2b100a64e..4a4d66a952abb 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts @@ -181,7 +181,7 @@ export class NetworkListener extends BaseListener implements INetworkListener { */ public addTargets(id: string, props: AddNetworkTargetsProps): NetworkTargetGroup { if (!this.loadBalancer.vpc) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error('Can only call addTargets() when using a constructed Load Balancer or imported Load Balancer with specified VPC; construct a new TargetGroup and use addTargetGroup'); } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts index cf7ccbf04b1ed..28e23cc56a257 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts @@ -69,13 +69,13 @@ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoa public get loadBalancerCanonicalHostedZoneId(): string { if (attrs.loadBalancerCanonicalHostedZoneId) { return attrs.loadBalancerCanonicalHostedZoneId; } - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`'loadBalancerCanonicalHostedZoneId' was not provided when constructing Network Load Balancer ${this.node.path} from attributes`); } public get loadBalancerDnsName(): string { if (attrs.loadBalancerDnsName) { return attrs.loadBalancerDnsName; } - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`'loadBalancerDnsName' was not provided when constructing Network Load Balancer ${this.node.path} from attributes`); } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts index 12e5ea3dd715f..36a30433361de 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts @@ -151,7 +151,6 @@ export class NetworkTargetGroup extends TargetGroupBase implements INetworkTarge /** * A network target group */ -// tslint:disable-next-line:no-empty-interface export interface INetworkTargetGroup extends ITargetGroup { /** * Register a listener that is load balancing to this target group. 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 a51e40c0df0db..7c7447d19eba0 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 @@ -2,6 +2,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import { Construct, IResource, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; +import { RegionInfo } from '@aws-cdk/region-info'; import { CfnLoadBalancer } from '../elasticloadbalancingv2.generated'; import { Attributes, ifUndefined, renderAttributes } from './util'; @@ -170,7 +171,7 @@ export abstract class BaseLoadBalancer extends Resource { throw new Error('Region is required to enable ELBv2 access logging'); } - const account = ELBV2_ACCOUNTS[region]; + const account = RegionInfo.get(region).elbv2Account; if (!account) { throw new Error(`Cannot enable access logging; don't know ELBv2 account for region ${region}`); } @@ -198,32 +199,3 @@ export abstract class BaseLoadBalancer extends Resource { this.setAttribute(key, undefined); } } - -// https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-logging-bucket-permissions -const ELBV2_ACCOUNTS: { [region: string]: string } = { - 'us-east-1': '127311923021', - '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', - 'ap-northeast-2': '600734575887', - 'ap-northeast-3': '383597477331', - 'ap-southeast-1': '114774131450', - 'ap-southeast-2': '783225319266', - 'ap-south-1': '718504428378', - 'me-south-1': '076674570225', - 'sa-east-1': '507241528517', - 'us-gov-west-1': '048591011584', - 'us-gov-east-1': '190560391635', - 'cn-north-1': '638102146993', - 'cn-northwest-1': '037604701340', -}; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts index 3281aa8deb02f..2776d533faefa 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts @@ -50,7 +50,7 @@ export function defaultProtocolForPort(port: number): ApplicationProtocol { /** * Given a protocol and a port, try to guess the other one if it's undefined */ -// tslint:disable-next-line:max-line-length +// eslint-disable-next-line max-len export function determineProtocolAndPort(protocol: ApplicationProtocol | undefined, port: number | undefined): [ApplicationProtocol | undefined, number | undefined] { if (protocol === undefined && port === undefined) { return [undefined, undefined]; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json index 0ad90f1fd4c85..0f9a33289b9d8 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json @@ -78,6 +78,7 @@ "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/region-info": "0.0.0", "constructs": "^3.0.2" }, "homepage": "https://github.com/aws/aws-cdk", @@ -89,7 +90,8 @@ "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", - "constructs": "^3.0.2" + "constructs": "^3.0.2", + "@aws-cdk/region-info": "0.0.0" }, "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.target-group.ts index 72c4132d6e218..d4ae698b3a258 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.target-group.ts @@ -84,16 +84,16 @@ export = { // THEN expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::TargetGroup', { - HealthCheckEnabled : true, - HealthCheckIntervalSeconds : 255, - HealthCheckPath : '/arbitrary', - HealthCheckTimeoutSeconds : 192, - HealthyThresholdCount : 29, - Matcher : { - HttpCode : '255', + HealthCheckEnabled: true, + HealthCheckIntervalSeconds: 255, + HealthCheckPath: '/arbitrary', + HealthCheckTimeoutSeconds: 192, + HealthyThresholdCount: 29, + Matcher: { + HttpCode: '255', }, Port: 80, - UnhealthyThresholdCount : 27, + UnhealthyThresholdCount: 27, })); test.done(); diff --git a/packages/@aws-cdk/aws-elasticsearch/.gitignore b/packages/@aws-cdk/aws-elasticsearch/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-elasticsearch/.gitignore +++ b/packages/@aws-cdk/aws-elasticsearch/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticsearch/.npmignore b/packages/@aws-cdk/aws-elasticsearch/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-elasticsearch/.npmignore +++ b/packages/@aws-cdk/aws-elasticsearch/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticsearch/jest.config.js b/packages/@aws-cdk/aws-elasticsearch/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-elasticsearch/jest.config.js +++ b/packages/@aws-cdk/aws-elasticsearch/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-emr/.gitignore b/packages/@aws-cdk/aws-emr/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-emr/.gitignore +++ b/packages/@aws-cdk/aws-emr/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-emr/.npmignore b/packages/@aws-cdk/aws-emr/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-emr/.npmignore +++ b/packages/@aws-cdk/aws-emr/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-emr/jest.config.js b/packages/@aws-cdk/aws-emr/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-emr/jest.config.js +++ b/packages/@aws-cdk/aws-emr/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-events-targets/.gitignore b/packages/@aws-cdk/aws-events-targets/.gitignore index 317d3b822f33e..2ed02868c78fb 100644 --- a/packages/@aws-cdk/aws-events-targets/.gitignore +++ b/packages/@aws-cdk/aws-events-targets/.gitignore @@ -18,3 +18,5 @@ nyc.config.js lib/sdk-api-metadata.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events-targets/.npmignore b/packages/@aws-cdk/aws-events-targets/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-events-targets/.npmignore +++ b/packages/@aws-cdk/aws-events-targets/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events-targets/jest.config.js b/packages/@aws-cdk/aws-events-targets/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-events-targets/jest.config.js +++ b/packages/@aws-cdk/aws-events-targets/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-events-targets/lib/aws-api-handler/index.ts b/packages/@aws-cdk/aws-events-targets/lib/aws-api-handler/index.ts index 3d8a4aceffebf..88158298e2e7c 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/aws-api-handler/index.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/aws-api-handler/index.ts @@ -1,4 +1,4 @@ -// tslint:disable:no-console +/* eslint-disable no-console */ // eslint-disable-next-line import/no-extraneous-dependencies import * as AWS from 'aws-sdk'; import { AwsApiInput } from '../aws-api'; diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index b6409db580dd6..07c1d0d7770a1 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.711.0", + "aws-sdk": "^2.715.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-events-targets/test/aws-api/aws-api-handler.test.ts b/packages/@aws-cdk/aws-events-targets/test/aws-api/aws-api-handler.test.ts index 6697ce91dd77a..bfe355090fe3e 100644 --- a/packages/@aws-cdk/aws-events-targets/test/aws-api/aws-api-handler.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/aws-api/aws-api-handler.test.ts @@ -1,4 +1,4 @@ -// tslint:disable:no-console +/* eslint-disable no-console */ import * as SDK from 'aws-sdk'; import * as AWS from 'aws-sdk-mock'; import { AwsApiProps } from '../../lib'; diff --git a/packages/@aws-cdk/aws-events-targets/test/aws-api/integ.aws-api.ts b/packages/@aws-cdk/aws-events-targets/test/aws-api/integ.aws-api.ts index 9dc135dfef993..cc1b49612b04e 100644 --- a/packages/@aws-cdk/aws-events-targets/test/aws-api/integ.aws-api.ts +++ b/packages/@aws-cdk/aws-events-targets/test/aws-api/integ.aws-api.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as events from '@aws-cdk/aws-events'; import * as cdk from '@aws-cdk/core'; import * as targets from '../../lib'; diff --git a/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.ts b/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.ts index d42173c6fc188..17d17a291e244 100644 --- a/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.ts +++ b/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.ts @@ -25,7 +25,7 @@ timer2.addTarget(new targets.LambdaFunction(fn)); app.synth(); -// tslint:disable:no-console +/* eslint-disable no-console */ function handler(event: any, _context: any, callback: any) { console.log(JSON.stringify(event, undefined, 2)); return callback(); diff --git a/packages/@aws-cdk/aws-events/.gitignore b/packages/@aws-cdk/aws-events/.gitignore index 32a10d785e8fb..dcc1dc41e477f 100644 --- a/packages/@aws-cdk/aws-events/.gitignore +++ b/packages/@aws-cdk/aws-events/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/.npmignore b/packages/@aws-cdk/aws-events/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-events/.npmignore +++ b/packages/@aws-cdk/aws-events/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/test.rule.ts b/packages/@aws-cdk/aws-events/test/test.rule.ts index 304bf91ed4dcb..2b669e92491d9 100644 --- a/packages/@aws-cdk/aws-events/test/test.rule.ts +++ b/packages/@aws-cdk/aws-events/test/test.rule.ts @@ -5,7 +5,7 @@ import { Test } from 'nodeunit'; import { EventBus, EventField, IRule, IRuleTarget, RuleTargetConfig, RuleTargetInput, Schedule } from '../lib'; import { Rule } from '../lib/rule'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'default rule'(test: Test) { diff --git a/packages/@aws-cdk/aws-eventschemas/.gitignore b/packages/@aws-cdk/aws-eventschemas/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-eventschemas/.gitignore +++ b/packages/@aws-cdk/aws-eventschemas/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eventschemas/.npmignore b/packages/@aws-cdk/aws-eventschemas/.npmignore index 683e3e0847e1f..a7c5b49852b3b 100644 --- a/packages/@aws-cdk/aws-eventschemas/.npmignore +++ b/packages/@aws-cdk/aws-eventschemas/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eventschemas/jest.config.js b/packages/@aws-cdk/aws-eventschemas/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-eventschemas/jest.config.js +++ b/packages/@aws-cdk/aws-eventschemas/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-fms/.gitignore b/packages/@aws-cdk/aws-fms/.gitignore index d57af28d42320..192200b9c7097 100644 --- a/packages/@aws-cdk/aws-fms/.gitignore +++ b/packages/@aws-cdk/aws-fms/.gitignore @@ -16,3 +16,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-fms/.npmignore b/packages/@aws-cdk/aws-fms/.npmignore index 2f1ff45adc883..c8874799485a3 100644 --- a/packages/@aws-cdk/aws-fms/.npmignore +++ b/packages/@aws-cdk/aws-fms/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-fms/jest.config.js b/packages/@aws-cdk/aws-fms/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-fms/jest.config.js +++ b/packages/@aws-cdk/aws-fms/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-fsx/.gitignore b/packages/@aws-cdk/aws-fsx/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-fsx/.gitignore +++ b/packages/@aws-cdk/aws-fsx/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-fsx/.npmignore b/packages/@aws-cdk/aws-fsx/.npmignore index b0edf81a01371..ab90672b1d91e 100644 --- a/packages/@aws-cdk/aws-fsx/.npmignore +++ b/packages/@aws-cdk/aws-fsx/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-fsx/jest.config.js b/packages/@aws-cdk/aws-fsx/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-fsx/jest.config.js +++ b/packages/@aws-cdk/aws-fsx/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-gamelift/.gitignore b/packages/@aws-cdk/aws-gamelift/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-gamelift/.gitignore +++ b/packages/@aws-cdk/aws-gamelift/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/.npmignore b/packages/@aws-cdk/aws-gamelift/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-gamelift/.npmignore +++ b/packages/@aws-cdk/aws-gamelift/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/jest.config.js b/packages/@aws-cdk/aws-gamelift/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-gamelift/jest.config.js +++ b/packages/@aws-cdk/aws-gamelift/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-globalaccelerator/.gitignore b/packages/@aws-cdk/aws-globalaccelerator/.gitignore index e9fee23607e76..5aa413b898780 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/.gitignore +++ b/packages/@aws-cdk/aws-globalaccelerator/.gitignore @@ -2,7 +2,6 @@ *.js.map *.d.ts tsconfig.json -tslint.json node_modules *.generated.ts dist @@ -17,3 +16,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml diff --git a/packages/@aws-cdk/aws-globalaccelerator/.npmignore b/packages/@aws-cdk/aws-globalaccelerator/.npmignore index 2f1ff45adc883..c8874799485a3 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/.npmignore +++ b/packages/@aws-cdk/aws-globalaccelerator/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator/jest.config.js b/packages/@aws-cdk/aws-globalaccelerator/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/jest.config.js +++ b/packages/@aws-cdk/aws-globalaccelerator/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint-group.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint-group.ts index cc81779c7131c..b26b2bf5a8625 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint-group.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint-group.ts @@ -130,7 +130,7 @@ export class EndpointConfiguration extends cdk.Construct { return { clientIpPreservationEnabled: this.props.clientIpReservation, endpointId: this.props.endpointId, - weight: this.props.weight, + weight: this.props.weight, }; } } diff --git a/packages/@aws-cdk/aws-glue/.gitignore b/packages/@aws-cdk/aws-glue/.gitignore index 0bd6133da4d09..266c0684c6844 100644 --- a/packages/@aws-cdk/aws-glue/.gitignore +++ b/packages/@aws-cdk/aws-glue/.gitignore @@ -14,3 +14,6 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js +!jest.config.js + +junit.xml diff --git a/packages/@aws-cdk/aws-glue/.npmignore b/packages/@aws-cdk/aws-glue/.npmignore index fe4df9a06d9a9..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-glue/.npmignore +++ b/packages/@aws-cdk/aws-glue/.npmignore @@ -19,6 +19,8 @@ dist tsconfig.json .eslintrc.js +jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-glue/README.md b/packages/@aws-cdk/aws-glue/README.md index 3f61ce18700b7..d7bb3cad73639 100644 --- a/packages/@aws-cdk/aws-glue/README.md +++ b/packages/@aws-cdk/aws-glue/README.md @@ -55,6 +55,8 @@ new glue.Table(stack, 'MyTable', { }); ``` +By default, an S3 bucket will be created to store the table's data and stored in the bucket root. You can also manually pass the `bucket` and `s3Prefix`: + #### Partitions To improve query performance, a table can specify `partitionKeys` on which data is stored and queried separately. For example, you might partition a table by `year` and `month` to optimize queries based on a time window: diff --git a/packages/@aws-cdk/aws-glue/jest.config.js b/packages/@aws-cdk/aws-glue/jest.config.js new file mode 100644 index 0000000000000..cd664e1d069e5 --- /dev/null +++ b/packages/@aws-cdk/aws-glue/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-glue/lib/table.ts b/packages/@aws-cdk/aws-glue/lib/table.ts index f451e853dcb3a..3ed844d2d600a 100644 --- a/packages/@aws-cdk/aws-glue/lib/table.ts +++ b/packages/@aws-cdk/aws-glue/lib/table.ts @@ -87,7 +87,7 @@ export interface TableProps { /** * S3 prefix under which table objects are stored. * - * @default data/ + * @default - No prefix. The data will be stored under the root of the bucket. */ readonly s3Prefix?: string; @@ -236,7 +236,7 @@ export class Table extends Resource implements ITable { this.database = props.database; this.dataFormat = props.dataFormat; - this.s3Prefix = (props.s3Prefix !== undefined && props.s3Prefix !== null) ? props.s3Prefix : 'data/'; + this.s3Prefix = props.s3Prefix ?? ''; validateSchema(props.columns, props.partitionKeys); this.columns = props.columns; @@ -340,7 +340,7 @@ function validateSchema(columns: Column[], partitionKeys?: Column[]): void { const names = new Set(); (columns.concat(partitionKeys || [])).forEach(column => { if (names.has(column.name)) { - throw new Error('column names and partition keys must be unique, but \'p1\' is duplicated'); + throw new Error(`column names and partition keys must be unique, but \'${column.name}\' is duplicated`); } names.add(column.name); }); diff --git a/packages/@aws-cdk/aws-glue/package.json b/packages/@aws-cdk/aws-glue/package.json index d60997aff5011..0227fcaab0492 100644 --- a/packages/@aws-cdk/aws-glue/package.json +++ b/packages/@aws-cdk/aws-glue/package.json @@ -47,7 +47,8 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::Glue" + "cloudformation": "AWS::Glue", + "jest": true }, "keywords": [ "aws", @@ -67,7 +68,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "nodeunit": "^0.11.3", + "jest": "^25.5.4", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-glue/test/database.test.ts b/packages/@aws-cdk/aws-glue/test/database.test.ts new file mode 100644 index 0000000000000..7ca9409525345 --- /dev/null +++ b/packages/@aws-cdk/aws-glue/test/database.test.ts @@ -0,0 +1,92 @@ +import { expect } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import { Stack } from '@aws-cdk/core'; +import { deepEqual, throws } from 'assert'; +import * as glue from '../lib'; + +test('default database does not create a bucket', () => { + const stack = new Stack(); + + new glue.Database(stack, 'Database', { + databaseName: 'test_database', + }); + + expect(stack).toMatch({ + Resources: { + DatabaseB269D8BB: { + Type: 'AWS::Glue::Database', + Properties: { + CatalogId: { + Ref: 'AWS::AccountId', + }, + DatabaseInput: { + Name: 'test_database', + }, + }, + }, + }, + }); + +}); + +test('explicit locationURI', () => { + const stack = new Stack(); + + new glue.Database(stack, 'Database', { + databaseName: 'test_database', + locationUri: 's3://my-uri/', + }); + + expect(stack).toMatch({ + Resources: { + DatabaseB269D8BB: { + Type: 'AWS::Glue::Database', + Properties: { + CatalogId: { + Ref: 'AWS::AccountId', + }, + DatabaseInput: { + LocationUri: 's3://my-uri/', + Name: 'test_database', + }, + }, + }, + }, + }); + +}); + +test('fromDatabase', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const database = glue.Database.fromDatabaseArn(stack, 'import', 'arn:aws:glue:us-east-1:123456789012:database/db1'); + + // THEN + deepEqual(database.databaseArn, 'arn:aws:glue:us-east-1:123456789012:database/db1'); + deepEqual(database.databaseName, 'db1'); + deepEqual(stack.resolve(database.catalogArn), { 'Fn::Join': [ '', + [ 'arn:', { Ref: 'AWS::Partition' }, ':glue:', { Ref: 'AWS::Region' }, ':', { Ref: 'AWS::AccountId' }, ':catalog' ] ] }); + deepEqual(stack.resolve(database.catalogId), { Ref: 'AWS::AccountId' }); +}); + +test('locationUri length must be >= 1', () => { + const stack = new Stack(); + throws(() => + new glue.Database(stack, 'Database', { + databaseName: 'test_database', + locationUri: '', + }), + ); +}); + +test('locationUri length must be <= 1024', () => { + const stack = new Stack(); + throws(() => + new glue.Database(stack, 'Database', { + databaseName: 'test_database', + locationUri: 'a'.repeat(1025), + }), + ); +}); diff --git a/packages/@aws-cdk/aws-glue/test/integ.table.expected.json b/packages/@aws-cdk/aws-glue/test/integ.table.expected.json index c56c5990fe7cf..8be4979ba9ba2 100644 --- a/packages/@aws-cdk/aws-glue/test/integ.table.expected.json +++ b/packages/@aws-cdk/aws-glue/test/integ.table.expected.json @@ -71,7 +71,7 @@ { "Ref": "DataBucketE3889A50" }, - "/data/" + "/" ] ] }, @@ -140,7 +140,7 @@ { "Ref": "DataBucketE3889A50" }, - "/data/" + "/" ] ] }, @@ -209,7 +209,7 @@ { "Ref": "DataBucketE3889A50" }, - "/data/" + "/" ] ] }, @@ -278,7 +278,7 @@ { "Ref": "DataBucketE3889A50" }, - "/data/" + "/" ] ] }, @@ -439,7 +439,7 @@ { "Ref": "MyEncryptedTableBucket7B28486D" }, - "/data/" + "/" ] ] }, @@ -532,7 +532,7 @@ "Arn" ] }, - "/data/" + "/" ] ] } @@ -609,7 +609,7 @@ "Arn" ] }, - "/data/" + "/" ] ] } @@ -721,7 +721,7 @@ "Arn" ] }, - "/data/" + "/" ] ] } @@ -827,4 +827,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-glue/test/schema.test.ts b/packages/@aws-cdk/aws-glue/test/schema.test.ts new file mode 100644 index 0000000000000..f7910dc0bd852 --- /dev/null +++ b/packages/@aws-cdk/aws-glue/test/schema.test.ts @@ -0,0 +1,241 @@ +import '@aws-cdk/assert/jest'; +import { doesNotThrow, equal, throws } from 'assert'; +import { Schema } from '../lib'; + +test('boolean type', () => { + equal(Schema.BOOLEAN.inputString, 'boolean'); + equal(Schema.BOOLEAN.isPrimitive, true); +}); + +test('binary type', () => { + equal(Schema.BINARY.inputString, 'binary'); + equal(Schema.BINARY.isPrimitive, true); +}); + +test('bigint type', () => { + equal(Schema.BIG_INT.inputString, 'bigint'); + equal(Schema.BIG_INT.isPrimitive, true); +}); + +test('double type', () => { + equal(Schema.DOUBLE.inputString, 'double'); + equal(Schema.DOUBLE.isPrimitive, true); +}); + +test('float type', () => { + equal(Schema.FLOAT.inputString, 'float'); + equal(Schema.FLOAT.isPrimitive, true); +}); + +test('integer type', () => { + equal(Schema.INTEGER.inputString, 'int'); + equal(Schema.INTEGER.isPrimitive, true); +}); + +test('smallint type', () => { + equal(Schema.SMALL_INT.inputString, 'smallint'); + equal(Schema.SMALL_INT.isPrimitive, true); +}); + +test('tinyint type', () => { + equal(Schema.TINY_INT.inputString, 'tinyint'); + equal(Schema.TINY_INT.isPrimitive, true); +}); + +test('decimal type', () => { + equal(Schema.decimal(16).inputString, 'decimal(16)'); + equal(Schema.decimal(16, 1).inputString, 'decimal(16,1)'); + equal(Schema.decimal(16).isPrimitive, true); + equal(Schema.decimal(16, 1).isPrimitive, true); +}); +// TODO: decimal bounds + +test('date type', () => { + equal(Schema.DATE.inputString, 'date'); + equal(Schema.DATE.isPrimitive, true); +}); + +test('timestamp type', () => { + equal(Schema.TIMESTAMP.inputString, 'timestamp'); + equal(Schema.TIMESTAMP.isPrimitive, true); +}); + +test('string type', () => { + equal(Schema.STRING.inputString, 'string'); + equal(Schema.STRING.isPrimitive, true); +}); + +test('char type', () => { + equal(Schema.char(1).inputString, 'char(1)'); + equal(Schema.char(1).isPrimitive, true); +}); + +test('char length must be test(at least 1', () => { + doesNotThrow(() => Schema.char(1)); + throws(() => Schema.char(0)); + throws(() => Schema.char(-1)); +}); + +test('char length must test(be <= 255', () => { + doesNotThrow(() => Schema.char(255)); + throws(() => Schema.char(256)); +}); + +test('varchar type', () => { + equal(Schema.varchar(1).inputString, 'varchar(1)'); + equal(Schema.varchar(1).isPrimitive, true); +}); + +test('varchar length must be test(at least 1', () => { + doesNotThrow(() => Schema.varchar(1)); + throws(() => Schema.varchar(0)); + throws(() => Schema.varchar(-1)); +}); + +test('varchar length must test(be <= 65535', () => { + doesNotThrow(() => Schema.varchar(65535)); + throws(() => Schema.varchar(65536)); +}); + +test('test(array', () => { + const type = Schema.array(Schema.STRING); + equal(type.inputString, 'array'); + equal(type.isPrimitive, false); +}); + +test('array', () => { + const type = Schema.array(Schema.char(1)); + equal(type.inputString, 'array'); + equal(type.isPrimitive, false); +}); + +test('test(array', () => { + const type = Schema.array( + Schema.array(Schema.STRING)); + equal(type.inputString, 'array>'); + equal(type.isPrimitive, false); +}); + +test('test(array', () => { + const type = Schema.array( + Schema.map(Schema.STRING, Schema.STRING)); + equal(type.inputString, 'array>'); + equal(type.isPrimitive, false); +}); + +test('test(array', () => { + const type = Schema.array( + Schema.struct([{ + name: 'key', + type: Schema.STRING, + }])); + equal(type.inputString, 'array>'); + equal(type.isPrimitive, false); +}); + +test('map', () => { + const type = Schema.map( + Schema.STRING, + Schema.STRING, + ); + equal(type.inputString, 'map'); + equal(type.isPrimitive, false); +}); + +test('map', () => { + const type = Schema.map( + Schema.INTEGER, + Schema.STRING, + ); + equal(type.inputString, 'map'); + equal(type.isPrimitive, false); +}); + +test('map', () => { + const type = Schema.map( + Schema.char(1), + Schema.char(1), + ); + equal(type.inputString, 'map'); + equal(type.isPrimitive, false); +}); + +test('map', () => { + const type = Schema.map( + Schema.char(1), + Schema.array(Schema.STRING), + ); + equal(type.inputString, 'map>'); + equal(type.isPrimitive, false); +}); + +test('map', () => { + const type = Schema.map( + Schema.char(1), + Schema.map( + Schema.STRING, + Schema.STRING), + ); + equal(type.inputString, 'map>'); + equal(type.isPrimitive, false); +}); + +test('map', () => { + const type = Schema.map( + Schema.char(1), + Schema.struct([{ + name: 'key', + type: Schema.STRING, + }]), + ); + equal(type.inputString, 'map>'); + equal(type.isPrimitive, false); +}); + +test('map throws if keyType is test(non-primitive', () => { + throws(() => Schema.map( + Schema.array(Schema.STRING), + Schema.STRING, + )); + throws(() => Schema.map( + Schema.map(Schema.STRING, Schema.STRING), + Schema.STRING, + )); + throws(() => Schema.map( + Schema.struct([{ + name: 'key', + type: Schema.STRING, + }]), + Schema.STRING, + )); +}); + +test('struct type', () => { + const type = Schema.struct([{ + name: 'primitive', + type: Schema.STRING, + }, { + name: 'with_comment', + type: Schema.STRING, + comment: 'this has a comment', + }, { + name: 'array', + type: Schema.array(Schema.STRING), + }, { + name: 'map', + type: Schema.map(Schema.STRING, Schema.STRING), + }, { + name: 'nested_struct', + type: Schema.struct([{ + name: 'nested', + type: Schema.STRING, + comment: 'nested comment', + }]), + }]); + + equal(type.isPrimitive, false); + equal( + type.inputString, + // eslint-disable-next-line max-len + 'struct,map:map,nested_struct:struct>'); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-glue/test/table.test.ts b/packages/@aws-cdk/aws-glue/test/table.test.ts new file mode 100644 index 0000000000000..592fadf650165 --- /dev/null +++ b/packages/@aws-cdk/aws-glue/test/table.test.ts @@ -0,0 +1,1599 @@ +import { expect as cdkExpect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import { deepEqual, doesNotThrow, equal, notEqual, ok } from 'assert'; +import * as glue from '../lib'; + +test('unpartitioned JSON table', () => { + const app = new cdk.App(); + const dbStack = new cdk.Stack(app, 'db'); + const database = new glue.Database(dbStack, 'Database', { + databaseName: 'database', + }); + + const tableStack = new cdk.Stack(app, 'table'); + const table = new glue.Table(tableStack, 'Table', { + database, + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, + }); + equal(table.encryption, glue.TableEncryption.UNENCRYPTED); + + cdkExpect(tableStack).to(haveResource('AWS::S3::Bucket', { + Type: 'AWS::S3::Bucket', + DeletionPolicy: 'Retain', + UpdateReplacePolicy: 'Retain', + }, ResourcePart.CompleteDefinition)); + + cdkExpect(tableStack).to(haveResource('AWS::Glue::Table', { + CatalogId: { + Ref: 'AWS::AccountId', + }, + DatabaseName: { + 'Fn::ImportValue': 'db:ExportsOutputRefDatabaseB269D8BB88F4B1C4', + }, + TableInput: { + Name: 'table', + Description: 'table generated by CDK', + Parameters: { + has_encrypted_data: false, + }, + StorageDescriptor: { + Columns: [ + { + Name: 'col', + Type: 'string', + }, + ], + Compressed: false, + InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', + Location: { + 'Fn::Join': [ + '', + [ + 's3://', + { + Ref: 'TableBucketDA42407C', + }, + '/', + ], + ], + }, + OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', + SerdeInfo: { + SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', + }, + StoredAsSubDirectories: false, + }, + TableType: 'EXTERNAL_TABLE', + }, + })); + +}); + +test('partitioned JSON table', () => { + const app = new cdk.App(); + const dbStack = new cdk.Stack(app, 'db'); + const database = new glue.Database(dbStack, 'Database', { + databaseName: 'database', + }); + + const tableStack = new cdk.Stack(app, 'table'); + const table = new glue.Table(tableStack, 'Table', { + database, + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + partitionKeys: [{ + name: 'year', + type: glue.Schema.SMALL_INT, + }], + dataFormat: glue.DataFormat.JSON, + }); + equal(table.encryption, glue.TableEncryption.UNENCRYPTED); + equal(table.encryptionKey, undefined); + equal(table.bucket.encryptionKey, undefined); + + cdkExpect(tableStack).to(haveResource('AWS::Glue::Table', { + CatalogId: { + Ref: 'AWS::AccountId', + }, + DatabaseName: { + 'Fn::ImportValue': 'db:ExportsOutputRefDatabaseB269D8BB88F4B1C4', + }, + TableInput: { + Name: 'table', + Description: 'table generated by CDK', + Parameters: { + has_encrypted_data: false, + }, + PartitionKeys: [ + { + Name: 'year', + Type: 'smallint', + }, + ], + StorageDescriptor: { + Columns: [ + { + Name: 'col', + Type: 'string', + }, + ], + Compressed: false, + InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', + Location: { + 'Fn::Join': [ + '', + [ + 's3://', + { + Ref: 'TableBucketDA42407C', + }, + '/', + ], + ], + }, + OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', + SerdeInfo: { + SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', + }, + StoredAsSubDirectories: false, + }, + TableType: 'EXTERNAL_TABLE', + }, + })); + +}); + +test('compressed table', () => { + const stack = new cdk.Stack(); + const database = new glue.Database(stack, 'Database', { + databaseName: 'database', + }); + + const table = new glue.Table(stack, 'Table', { + database, + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + compressed: true, + dataFormat: glue.DataFormat.JSON, + }); + equal(table.encryptionKey, undefined); + equal(table.bucket.encryptionKey, undefined); + + cdkExpect(stack).to(haveResource('AWS::Glue::Table', { + CatalogId: { + Ref: 'AWS::AccountId', + }, + DatabaseName: { + Ref: 'DatabaseB269D8BB', + }, + TableInput: { + Name: 'table', + Description: 'table generated by CDK', + Parameters: { + has_encrypted_data: false, + }, + StorageDescriptor: { + Columns: [ + { + Name: 'col', + Type: 'string', + }, + ], + Compressed: true, + InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', + Location: { + 'Fn::Join': [ + '', + [ + 's3://', + { + Ref: 'TableBucketDA42407C', + }, + '/', + ], + ], + }, + OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', + SerdeInfo: { + SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', + }, + StoredAsSubDirectories: false, + }, + TableType: 'EXTERNAL_TABLE', + }, + })); + +}); + +test('table.node.defaultChild', () => { + // GIVEN + const stack = new cdk.Stack(); + const database = new glue.Database(stack, 'Database', { + databaseName: 'database', + }); + + // WHEN + const table = new glue.Table(stack, 'Table', { + database, + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + compressed: true, + dataFormat: glue.DataFormat.JSON, + }); + + // THEN + ok(table.node.defaultChild instanceof glue.CfnTable); +}); + +test('encrypted table: SSE-S3', () => { + const stack = new cdk.Stack(); + const database = new glue.Database(stack, 'Database', { + databaseName: 'database', + }); + + const table = new glue.Table(stack, 'Table', { + database, + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + encryption: glue.TableEncryption.S3_MANAGED, + dataFormat: glue.DataFormat.JSON, + }); + equal(table.encryption, glue.TableEncryption.S3_MANAGED); + equal(table.encryptionKey, undefined); + equal(table.bucket.encryptionKey, undefined); + + cdkExpect(stack).to(haveResource('AWS::Glue::Table', { + CatalogId: { + Ref: 'AWS::AccountId', + }, + DatabaseName: { + Ref: 'DatabaseB269D8BB', + }, + TableInput: { + Name: 'table', + Description: 'table generated by CDK', + Parameters: { + has_encrypted_data: true, + }, + StorageDescriptor: { + Columns: [ + { + Name: 'col', + Type: 'string', + }, + ], + Compressed: false, + InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', + Location: { + 'Fn::Join': [ + '', + [ + 's3://', + { + Ref: 'TableBucketDA42407C', + }, + '/', + ], + ], + }, + OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', + SerdeInfo: { + SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', + }, + StoredAsSubDirectories: false, + }, + TableType: 'EXTERNAL_TABLE', + }, + })); + + cdkExpect(stack).to(haveResource('AWS::S3::Bucket', { + BucketEncryption: { + ServerSideEncryptionConfiguration: [ + { + ServerSideEncryptionByDefault: { + SSEAlgorithm: 'AES256', + }, + }, + ], + }, + })); + +}); + +test('encrypted table: SSE-KMS (implicitly created key)', () => { + const stack = new cdk.Stack(); + const database = new glue.Database(stack, 'Database', { + databaseName: 'database', + }); + + const table = new glue.Table(stack, 'Table', { + database, + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + encryption: glue.TableEncryption.KMS, + dataFormat: glue.DataFormat.JSON, + }); + equal(table.encryption, glue.TableEncryption.KMS); + equal(table.encryptionKey, table.bucket.encryptionKey); + + cdkExpect(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: [ + 'kms:Create*', + 'kms:Describe*', + 'kms:Enable*', + 'kms:List*', + 'kms:Put*', + 'kms:Update*', + 'kms:Revoke*', + 'kms:Disable*', + 'kms:Get*', + 'kms:Delete*', + 'kms:ScheduleKeyDeletion', + 'kms:CancelKeyDeletion', + 'kms:GenerateDataKey', + 'kms:TagResource', + 'kms:UntagResource', + ], + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + Description: 'Created by Table/Bucket', + })); + + cdkExpect(stack).to(haveResource('AWS::S3::Bucket', { + BucketEncryption: { + ServerSideEncryptionConfiguration: [ + { + ServerSideEncryptionByDefault: { + KMSMasterKeyID: { + 'Fn::GetAtt': [ + 'TableBucketKey3E9F984A', + 'Arn', + ], + }, + SSEAlgorithm: 'aws:kms', + }, + }, + ], + }, + })); + + cdkExpect(stack).to(haveResource('AWS::Glue::Table', { + CatalogId: { + Ref: 'AWS::AccountId', + }, + DatabaseName: { + Ref: 'DatabaseB269D8BB', + }, + TableInput: { + Name: 'table', + Description: 'table generated by CDK', + Parameters: { + has_encrypted_data: true, + }, + StorageDescriptor: { + Columns: [ + { + Name: 'col', + Type: 'string', + }, + ], + Compressed: false, + InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', + Location: { + 'Fn::Join': [ + '', + [ + 's3://', + { + Ref: 'TableBucketDA42407C', + }, + '/', + ], + ], + }, + OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', + SerdeInfo: { + SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', + }, + StoredAsSubDirectories: false, + }, + TableType: 'EXTERNAL_TABLE', + }, + })); + +}); + +test('encrypted table: SSE-KMS (explicitly created key)', () => { + const stack = new cdk.Stack(); + const database = new glue.Database(stack, 'Database', { + databaseName: 'database', + }); + const encryptionKey = new kms.Key(stack, 'MyKey'); + + const table = new glue.Table(stack, 'Table', { + database, + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + encryption: glue.TableEncryption.KMS, + encryptionKey, + dataFormat: glue.DataFormat.JSON, + }); + equal(table.encryption, glue.TableEncryption.KMS); + equal(table.encryptionKey, table.bucket.encryptionKey); + notEqual(table.encryptionKey, undefined); + + cdkExpect(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: [ + 'kms:Create*', + 'kms:Describe*', + 'kms:Enable*', + 'kms:List*', + 'kms:Put*', + 'kms:Update*', + 'kms:Revoke*', + 'kms:Disable*', + 'kms:Get*', + 'kms:Delete*', + 'kms:ScheduleKeyDeletion', + 'kms:CancelKeyDeletion', + 'kms:GenerateDataKey', + 'kms:TagResource', + 'kms:UntagResource', + ], + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + + cdkExpect(stack).to(haveResource('AWS::S3::Bucket', { + BucketEncryption: { + ServerSideEncryptionConfiguration: [ + { + ServerSideEncryptionByDefault: { + KMSMasterKeyID: { + 'Fn::GetAtt': [ + 'MyKey6AB29FA6', + 'Arn', + ], + }, + SSEAlgorithm: 'aws:kms', + }, + }, + ], + }, + })); + + cdkExpect(stack).to(haveResource('AWS::Glue::Table', { + CatalogId: { + Ref: 'AWS::AccountId', + }, + DatabaseName: { + Ref: 'DatabaseB269D8BB', + }, + TableInput: { + Description: 'table generated by CDK', + Name: 'table', + Parameters: { + has_encrypted_data: true, + }, + StorageDescriptor: { + Columns: [ + { + Name: 'col', + Type: 'string', + }, + ], + Compressed: false, + InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', + Location: { + 'Fn::Join': [ + '', + [ + 's3://', + { + Ref: 'TableBucketDA42407C', + }, + '/', + ], + ], + }, + OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', + SerdeInfo: { + SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', + }, + StoredAsSubDirectories: false, + }, + TableType: 'EXTERNAL_TABLE', + }, + })); + +}); + +test('encrypted table: SSE-KMS_MANAGED', () => { + const stack = new cdk.Stack(); + const database = new glue.Database(stack, 'Database', { + databaseName: 'database', + }); + + const table = new glue.Table(stack, 'Table', { + database, + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + encryption: glue.TableEncryption.KMS_MANAGED, + dataFormat: glue.DataFormat.JSON, + }); + equal(table.encryption, glue.TableEncryption.KMS_MANAGED); + equal(table.encryptionKey, undefined); + equal(table.bucket.encryptionKey, undefined); + + cdkExpect(stack).to(haveResource('AWS::S3::Bucket', { + BucketEncryption: { + ServerSideEncryptionConfiguration: [ + { + ServerSideEncryptionByDefault: { + SSEAlgorithm: 'aws:kms', + }, + }, + ], + }, + })); + + cdkExpect(stack).to(haveResource('AWS::Glue::Table', { + CatalogId: { + Ref: 'AWS::AccountId', + }, + DatabaseName: { + Ref: 'DatabaseB269D8BB', + }, + TableInput: { + Name: 'table', + Description: 'table generated by CDK', + Parameters: { + has_encrypted_data: true, + }, + StorageDescriptor: { + Columns: [ + { + Name: 'col', + Type: 'string', + }, + ], + Compressed: false, + InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', + Location: { + 'Fn::Join': [ + '', + [ + 's3://', + { + Ref: 'TableBucketDA42407C', + }, + '/', + ], + ], + }, + OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', + SerdeInfo: { + SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', + }, + StoredAsSubDirectories: false, + }, + TableType: 'EXTERNAL_TABLE', + }, + })); + +}); + +test('encrypted table: CSE-KMS (implicitly created key)', () => { + const stack = new cdk.Stack(); + const database = new glue.Database(stack, 'Database', { + databaseName: 'database', + }); + + const table = new glue.Table(stack, 'Table', { + database, + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + encryption: glue.TableEncryption.CLIENT_SIDE_KMS, + dataFormat: glue.DataFormat.JSON, + }); + equal(table.encryption, glue.TableEncryption.CLIENT_SIDE_KMS); + notEqual(table.encryptionKey, undefined); + equal(table.bucket.encryptionKey, undefined); + + cdkExpect(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: [ + 'kms:Create*', + 'kms:Describe*', + 'kms:Enable*', + 'kms:List*', + 'kms:Put*', + 'kms:Update*', + 'kms:Revoke*', + 'kms:Disable*', + 'kms:Get*', + 'kms:Delete*', + 'kms:ScheduleKeyDeletion', + 'kms:CancelKeyDeletion', + 'kms:GenerateDataKey', + 'kms:TagResource', + 'kms:UntagResource', + ], + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + + cdkExpect(stack).to(haveResource('AWS::Glue::Table', { + CatalogId: { + Ref: 'AWS::AccountId', + }, + DatabaseName: { + Ref: 'DatabaseB269D8BB', + }, + TableInput: { + Description: 'table generated by CDK', + Name: 'table', + Parameters: { + has_encrypted_data: true, + }, + StorageDescriptor: { + Columns: [ + { + Name: 'col', + Type: 'string', + }, + ], + Compressed: false, + InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', + Location: { + 'Fn::Join': [ + '', + [ + 's3://', + { + Ref: 'TableBucketDA42407C', + }, + '/', + ], + ], + }, + OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', + SerdeInfo: { + SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', + }, + StoredAsSubDirectories: false, + }, + TableType: 'EXTERNAL_TABLE', + }, + })); + +}); + +test('encrypted table: CSE-KMS (explicitly created key)', () => { + const stack = new cdk.Stack(); + const database = new glue.Database(stack, 'Database', { + databaseName: 'database', + }); + const encryptionKey = new kms.Key(stack, 'MyKey'); + + const table = new glue.Table(stack, 'Table', { + database, + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + encryption: glue.TableEncryption.CLIENT_SIDE_KMS, + encryptionKey, + dataFormat: glue.DataFormat.JSON, + }); + equal(table.encryption, glue.TableEncryption.CLIENT_SIDE_KMS); + notEqual(table.encryptionKey, undefined); + equal(table.bucket.encryptionKey, undefined); + + cdkExpect(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: [ + 'kms:Create*', + 'kms:Describe*', + 'kms:Enable*', + 'kms:List*', + 'kms:Put*', + 'kms:Update*', + 'kms:Revoke*', + 'kms:Disable*', + 'kms:Get*', + 'kms:Delete*', + 'kms:ScheduleKeyDeletion', + 'kms:CancelKeyDeletion', + 'kms:GenerateDataKey', + 'kms:TagResource', + 'kms:UntagResource', + ], + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + + cdkExpect(stack).to(haveResource('AWS::Glue::Table', { + CatalogId: { + Ref: 'AWS::AccountId', + }, + DatabaseName: { + Ref: 'DatabaseB269D8BB', + }, + TableInput: { + Description: 'table generated by CDK', + Name: 'table', + Parameters: { + has_encrypted_data: true, + }, + StorageDescriptor: { + Columns: [ + { + Name: 'col', + Type: 'string', + }, + ], + Compressed: false, + InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', + Location: { + 'Fn::Join': [ + '', + [ + 's3://', + { + Ref: 'TableBucketDA42407C', + }, + '/', + ], + ], + }, + OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', + SerdeInfo: { + SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', + }, + StoredAsSubDirectories: false, + }, + TableType: 'EXTERNAL_TABLE', + }, + })); + +}); + +test('encrypted table: CSE-KMS (explicitly passed bucket and key)', () => { + const stack = new cdk.Stack(); + const database = new glue.Database(stack, 'Database', { + databaseName: 'database', + }); + const bucket = new s3.Bucket(stack, 'Bucket'); + const encryptionKey = new kms.Key(stack, 'MyKey'); + + const table = new glue.Table(stack, 'Table', { + database, + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + bucket, + encryption: glue.TableEncryption.CLIENT_SIDE_KMS, + encryptionKey, + dataFormat: glue.DataFormat.JSON, + }); + equal(table.encryption, glue.TableEncryption.CLIENT_SIDE_KMS); + notEqual(table.encryptionKey, undefined); + equal(table.bucket.encryptionKey, undefined); + + cdkExpect(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: [ + 'kms:Create*', + 'kms:Describe*', + 'kms:Enable*', + 'kms:List*', + 'kms:Put*', + 'kms:Update*', + 'kms:Revoke*', + 'kms:Disable*', + 'kms:Get*', + 'kms:Delete*', + 'kms:ScheduleKeyDeletion', + 'kms:CancelKeyDeletion', + 'kms:GenerateDataKey', + 'kms:TagResource', + 'kms:UntagResource', + ], + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + + cdkExpect(stack).to(haveResource('AWS::Glue::Table', { + CatalogId: { + Ref: 'AWS::AccountId', + }, + DatabaseName: { + Ref: 'DatabaseB269D8BB', + }, + TableInput: { + Description: 'table generated by CDK', + Name: 'table', + Parameters: { + has_encrypted_data: true, + }, + StorageDescriptor: { + Columns: [ + { + Name: 'col', + Type: 'string', + }, + ], + Compressed: false, + InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', + Location: { + 'Fn::Join': [ + '', + [ + 's3://', + { + Ref: 'Bucket83908E77', + }, + '/', + ], + ], + }, + OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', + SerdeInfo: { + SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', + }, + StoredAsSubDirectories: false, + }, + TableType: 'EXTERNAL_TABLE', + }, + })); + +}); + +test('explicit s3 bucket and prefix', () => { + const app = new cdk.App(); + const dbStack = new cdk.Stack(app, 'db'); + const stack = new cdk.Stack(app, 'app'); + const bucket = new s3.Bucket(stack, 'ExplicitBucket'); + const database = new glue.Database(dbStack, 'Database', { + databaseName: 'database', + }); + + new glue.Table(stack, 'Table', { + database, + bucket, + s3Prefix: 'prefix/', + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, + }); + + cdkExpect(stack).to(haveResource('AWS::Glue::Table', { + CatalogId: { + Ref: 'AWS::AccountId', + }, + DatabaseName: { + 'Fn::ImportValue': 'db:ExportsOutputRefDatabaseB269D8BB88F4B1C4', + }, + TableInput: { + Description: 'table generated by CDK', + Name: 'table', + Parameters: { + has_encrypted_data: false, + }, + StorageDescriptor: { + Columns: [ + { + Name: 'col', + Type: 'string', + }, + ], + Compressed: false, + InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', + Location: { + 'Fn::Join': [ + '', + [ + 's3://', + { + Ref: 'ExplicitBucket0AA51A3F', + }, + '/prefix/', + ], + ], + }, + OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', + SerdeInfo: { + SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', + }, + StoredAsSubDirectories: false, + }, + TableType: 'EXTERNAL_TABLE', + }, + })); + +}); + +test('explicit s3 bucket and with empty prefix', () => { + const app = new cdk.App(); + const dbStack = new cdk.Stack(app, 'db'); + const stack = new cdk.Stack(app, 'app'); + const bucket = new s3.Bucket(stack, 'ExplicitBucket'); + const database = new glue.Database(dbStack, 'Database', { + databaseName: 'database', + }); + + new glue.Table(stack, 'Table', { + database, + bucket, + s3Prefix: '', + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, + }); + + cdkExpect(stack).to(haveResource('AWS::Glue::Table', { + CatalogId: { + Ref: 'AWS::AccountId', + }, + DatabaseName: { + 'Fn::ImportValue': 'db:ExportsOutputRefDatabaseB269D8BB88F4B1C4', + }, + TableInput: { + Description: 'table generated by CDK', + Name: 'table', + Parameters: { + has_encrypted_data: false, + }, + StorageDescriptor: { + Columns: [ + { + Name: 'col', + Type: 'string', + }, + ], + Compressed: false, + InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', + Location: { + 'Fn::Join': [ + '', + [ + 's3://', + { + Ref: 'ExplicitBucket0AA51A3F', + }, + '/', + ], + ], + }, + OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', + SerdeInfo: { + SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', + }, + StoredAsSubDirectories: false, + }, + TableType: 'EXTERNAL_TABLE', + }, + })); + +}); + +test('grants: read only', () => { + const stack = new cdk.Stack(); + const user = new iam.User(stack, 'User'); + const database = new glue.Database(stack, 'Database', { + databaseName: 'database', + }); + + const table = new glue.Table(stack, 'Table', { + database, + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + compressed: true, + dataFormat: glue.DataFormat.JSON, + }); + + table.grantRead(user); + + cdkExpect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'glue:BatchDeletePartition', + 'glue:BatchGetPartition', + 'glue:GetPartition', + 'glue:GetPartitions', + 'glue:GetTable', + 'glue:GetTables', + 'glue:GetTableVersions', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':glue:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':table/', + { + Ref: 'DatabaseB269D8BB', + }, + '/', + { + Ref: 'Table4C2D914F', + }, + ], + ], + }, + }, + { + Action: [ + 's3:GetObject*', + 's3:GetBucket*', + 's3:List*', + ], + Effect: 'Allow', + Resource: [ + { + 'Fn::GetAtt': [ + 'TableBucketDA42407C', + 'Arn', + ], + }, + { + 'Fn::Join': [ + '', + [ + { + 'Fn::GetAtt': [ + 'TableBucketDA42407C', + 'Arn', + ], + }, + '/', + ], + ], + }, + ], + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'UserDefaultPolicy1F97781E', + Users: [ + { + Ref: 'User00B015A1', + }, + ], + })); + +}); + +test('grants: write only', () => { + const stack = new cdk.Stack(); + const user = new iam.User(stack, 'User'); + const database = new glue.Database(stack, 'Database', { + databaseName: 'database', + }); + + const table = new glue.Table(stack, 'Table', { + database, + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + compressed: true, + dataFormat: glue.DataFormat.JSON, + }); + + table.grantWrite(user); + + cdkExpect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'glue:BatchCreatePartition', + 'glue:BatchDeletePartition', + 'glue:CreatePartition', + 'glue:DeletePartition', + 'glue:UpdatePartition', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':glue:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':table/', + { + Ref: 'DatabaseB269D8BB', + }, + '/', + { + Ref: 'Table4C2D914F', + }, + ], + ], + }, + }, + { + Action: [ + 's3:DeleteObject*', + 's3:PutObject*', + 's3:Abort*', + ], + Effect: 'Allow', + Resource: [ + { + 'Fn::GetAtt': [ + 'TableBucketDA42407C', + 'Arn', + ], + }, + { + 'Fn::Join': [ + '', + [ + { + 'Fn::GetAtt': [ + 'TableBucketDA42407C', + 'Arn', + ], + }, + '/', + ], + ], + }, + ], + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'UserDefaultPolicy1F97781E', + Users: [ + { + Ref: 'User00B015A1', + }, + ], + })); + +}); + +test('grants: read and write', () => { + const stack = new cdk.Stack(); + const user = new iam.User(stack, 'User'); + const database = new glue.Database(stack, 'Database', { + databaseName: 'database', + }); + + const table = new glue.Table(stack, 'Table', { + database, + tableName: 'table', + columns: [{ + name: 'col', + type: glue.Schema.STRING, + }], + compressed: true, + dataFormat: glue.DataFormat.JSON, + }); + + table.grantReadWrite(user); + + cdkExpect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'glue:BatchDeletePartition', + 'glue:BatchGetPartition', + 'glue:GetPartition', + 'glue:GetPartitions', + 'glue:GetTable', + 'glue:GetTables', + 'glue:GetTableVersions', + 'glue:BatchCreatePartition', + 'glue:BatchDeletePartition', + 'glue:CreatePartition', + 'glue:DeletePartition', + 'glue:UpdatePartition', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':glue:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':table/', + { + Ref: 'DatabaseB269D8BB', + }, + '/', + { + Ref: 'Table4C2D914F', + }, + ], + ], + }, + }, + { + Action: [ + 's3:GetObject*', + 's3:GetBucket*', + 's3:List*', + 's3:DeleteObject*', + 's3:PutObject*', + 's3:Abort*', + ], + Effect: 'Allow', + Resource: [ + { + 'Fn::GetAtt': [ + 'TableBucketDA42407C', + 'Arn', + ], + }, + { + 'Fn::Join': [ + '', + [ + { + 'Fn::GetAtt': [ + 'TableBucketDA42407C', + 'Arn', + ], + }, + '/', + ], + ], + }, + ], + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'UserDefaultPolicy1F97781E', + Users: [ + { + Ref: 'User00B015A1', + }, + ], + })); + +}); + +test('validate: at least one column', () => { + expect(() => { + createTable({ + columns: [], + tableName: 'name', + }); + }).toThrowError('you must specify at least one column for the table'); + +}); + +test('validate: unique column names', () => { + expect(() => { + createTable({ + tableName: 'name', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }, { + name: 'col1', + type: glue.Schema.STRING, + }], + }); + }).toThrowError("column names and partition keys must be unique, but 'col1' is duplicated"); + +}); + +test('validate: unique partition keys', () => { + expect(() => { + createTable({ + tableName: 'name', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + partitionKeys: [{ + name: 'p1', + type: glue.Schema.STRING, + }, { + name: 'p1', + type: glue.Schema.STRING, + }], + }); + }).toThrowError("column names and partition keys must be unique, but 'p1' is duplicated"); + +}); + +test('validate: column names and partition keys are all unique', () => { + expect(() => { createTable({ + tableName: 'name', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + partitionKeys: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + }); + }).toThrowError("column names and partition keys must be unique, but 'col1' is duplicated"); + +}); + +test('validate: can not specify an explicit bucket and encryption', () => { + expect(() => { + createTable({ + tableName: 'name', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + bucket: new s3.Bucket(new cdk.Stack(), 'Bucket'), + encryption: glue.TableEncryption.KMS, + }); + }).toThrowError('you can not specify encryption settings if you also provide a bucket'); +}); + +test('validate: can explicitly pass bucket if Encryption undefined', () => { + doesNotThrow(() => createTable({ + tableName: 'name', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + bucket: new s3.Bucket(new cdk.Stack(), 'Bucket'), + encryption: undefined, + })); +}); + +test('validate: can explicitly pass bucket if Unencrypted', () => { + doesNotThrow(() => createTable({ + tableName: 'name', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + bucket: new s3.Bucket(new cdk.Stack(), 'Bucket'), + encryption: undefined, + })); +}); + +test('validate: can explicitly pass bucket if ClientSideKms', () => { + doesNotThrow(() => createTable({ + tableName: 'name', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + bucket: new s3.Bucket(new cdk.Stack(), 'Bucket'), + encryption: glue.TableEncryption.CLIENT_SIDE_KMS, + })); +}); + +test('Table.fromTableArn', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const table = glue.Table.fromTableArn(stack, 'boom', 'arn:aws:glue:us-east-1:123456789012:table/db1/tbl1'); + + // THEN + deepEqual(table.tableArn, 'arn:aws:glue:us-east-1:123456789012:table/db1/tbl1'); + deepEqual(table.tableName, 'tbl1'); +}); + +function createTable(props: Pick>): void { + const stack = new cdk.Stack(); + new glue.Table(stack, 'table', { + ...props, + database: new glue.Database(stack, 'db', { + databaseName: 'database_name', + }), + dataFormat: glue.DataFormat.JSON, + }); +} diff --git a/packages/@aws-cdk/aws-glue/test/test.database.ts b/packages/@aws-cdk/aws-glue/test/test.database.ts deleted file mode 100644 index 121dd7a8ef594..0000000000000 --- a/packages/@aws-cdk/aws-glue/test/test.database.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { expect } from '@aws-cdk/assert'; -import { Stack } from '@aws-cdk/core'; -import { Test } from 'nodeunit'; -import * as glue from '../lib'; - -export = { - 'default database does not create a bucket'(test: Test) { - const stack = new Stack(); - - new glue.Database(stack, 'Database', { - databaseName: 'test_database', - }); - - expect(stack).toMatch({ - Resources: { - DatabaseB269D8BB: { - Type: 'AWS::Glue::Database', - Properties: { - CatalogId: { - Ref: 'AWS::AccountId', - }, - DatabaseInput: { - Name: 'test_database', - }, - }, - }, - }, - }); - - test.done(); - }, - - 'explicit locationURI'(test: Test) { - const stack = new Stack(); - - new glue.Database(stack, 'Database', { - databaseName: 'test_database', - locationUri: 's3://my-uri/', - }); - - expect(stack).toMatch({ - Resources: { - DatabaseB269D8BB: { - Type: 'AWS::Glue::Database', - Properties: { - CatalogId: { - Ref: 'AWS::AccountId', - }, - DatabaseInput: { - LocationUri: 's3://my-uri/', - Name: 'test_database', - }, - }, - }, - }, - }); - - test.done(); - }, - - 'fromDatabase'(test: Test) { - // GIVEN - const stack = new Stack(); - - // WHEN - const database = glue.Database.fromDatabaseArn(stack, 'import', 'arn:aws:glue:us-east-1:123456789012:database/db1'); - - // THEN - test.deepEqual(database.databaseArn, 'arn:aws:glue:us-east-1:123456789012:database/db1'); - test.deepEqual(database.databaseName, 'db1'); - test.deepEqual(stack.resolve(database.catalogArn), { 'Fn::Join': [ '', - [ 'arn:', { Ref: 'AWS::Partition' }, ':glue:', { Ref: 'AWS::Region' }, ':', { Ref: 'AWS::AccountId' }, ':catalog' ] ] }); - test.deepEqual(stack.resolve(database.catalogId), { Ref: 'AWS::AccountId' }); - test.done(); - }, - - 'locationUri length must be >= 1'(test: Test) { - const stack = new Stack(); - test.throws(() => - new glue.Database(stack, 'Database', { - databaseName: 'test_database', - locationUri: '', - }), - ); - test.done(); - }, - - 'locationUri length must be <= 1024'(test: Test) { - const stack = new Stack(); - test.throws(() => - new glue.Database(stack, 'Database', { - databaseName: 'test_database', - locationUri: 'a'.repeat(1025), - }), - ); - test.done(); - }, -}; diff --git a/packages/@aws-cdk/aws-glue/test/test.schema.ts b/packages/@aws-cdk/aws-glue/test/test.schema.ts deleted file mode 100644 index a78f056c0214c..0000000000000 --- a/packages/@aws-cdk/aws-glue/test/test.schema.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { Test } from 'nodeunit'; - -import { Schema } from '../lib'; - -export = { - 'boolean type'(test: Test) { - test.equals(Schema.BOOLEAN.inputString, 'boolean'); - test.equals(Schema.BOOLEAN.isPrimitive, true); - test.done(); - }, - - 'binary type'(test: Test) { - test.equals(Schema.BINARY.inputString, 'binary'); - test.equals(Schema.BINARY.isPrimitive, true); - test.done(); - }, - - 'bigint type'(test: Test) { - test.equals(Schema.BIG_INT.inputString, 'bigint'); - test.equals(Schema.BIG_INT.isPrimitive, true); - test.done(); - }, - - 'double type'(test: Test) { - test.equals(Schema.DOUBLE.inputString, 'double'); - test.equals(Schema.DOUBLE.isPrimitive, true); - test.done(); - }, - - 'float type'(test: Test) { - test.equals(Schema.FLOAT.inputString, 'float'); - test.equals(Schema.FLOAT.isPrimitive, true); - test.done(); - }, - - 'integer type'(test: Test) { - test.equals(Schema.INTEGER.inputString, 'int'); - test.equals(Schema.INTEGER.isPrimitive, true); - test.done(); - }, - - 'smallint type'(test: Test) { - test.equals(Schema.SMALL_INT.inputString, 'smallint'); - test.equals(Schema.SMALL_INT.isPrimitive, true); - test.done(); - }, - - 'tinyint type'(test: Test) { - test.equals(Schema.TINY_INT.inputString, 'tinyint'); - test.equals(Schema.TINY_INT.isPrimitive, true); - test.done(); - }, - - 'decimal type'(test: Test) { - test.equals(Schema.decimal(16).inputString, 'decimal(16)'); - test.equals(Schema.decimal(16, 1).inputString, 'decimal(16,1)'); - test.equals(Schema.decimal(16).isPrimitive, true); - test.equals(Schema.decimal(16, 1).isPrimitive, true); - test.done(); - }, - // TODO: decimal bounds - - 'date type'(test: Test) { - test.equals(Schema.DATE.inputString, 'date'); - test.equals(Schema.DATE.isPrimitive, true); - test.done(); - }, - - 'timestamp type'(test: Test) { - test.equals(Schema.TIMESTAMP.inputString, 'timestamp'); - test.equals(Schema.TIMESTAMP.isPrimitive, true); - test.done(); - }, - - 'string type'(test: Test) { - test.equals(Schema.STRING.inputString, 'string'); - test.equals(Schema.STRING.isPrimitive, true); - test.done(); - }, - - 'char type'(test: Test) { - test.equals(Schema.char(1).inputString, 'char(1)'); - test.equals(Schema.char(1).isPrimitive, true); - test.done(); - }, - - 'char length must be at least 1'(test: Test) { - test.doesNotThrow(() => Schema.char(1)); - test.throws(() => Schema.char(0)); - test.throws(() => Schema.char(-1)); - test.done(); - }, - - 'char length must be <= 255'(test: Test) { - test.doesNotThrow(() => Schema.char(255)); - test.throws(() => Schema.char(256)); - test.done(); - }, - - 'varchar type'(test: Test) { - test.equals(Schema.varchar(1).inputString, 'varchar(1)'); - test.equals(Schema.varchar(1).isPrimitive, true); - test.done(); - }, - - 'varchar length must be at least 1'(test: Test) { - test.doesNotThrow(() => Schema.varchar(1)); - test.throws(() => Schema.varchar(0)); - test.throws(() => Schema.varchar(-1)); - test.done(); - }, - - 'varchar length must be <= 65535'(test: Test) { - test.doesNotThrow(() => Schema.varchar(65535)); - test.throws(() => Schema.varchar(65536)); - test.done(); - }, - - 'array'(test: Test) { - const type = Schema.array(Schema.STRING); - test.equals(type.inputString, 'array'); - test.equals(type.isPrimitive, false); - test.done(); - }, - - 'array'(test: Test) { - const type = Schema.array(Schema.char(1)); - test.equals(type.inputString, 'array'); - test.equals(type.isPrimitive, false); - test.done(); - }, - - 'array'(test: Test) { - const type = Schema.array( - Schema.array(Schema.STRING)); - test.equals(type.inputString, 'array>'); - test.equals(type.isPrimitive, false); - test.done(); - }, - - 'array'(test: Test) { - const type = Schema.array( - Schema.map(Schema.STRING, Schema.STRING)); - test.equals(type.inputString, 'array>'); - test.equals(type.isPrimitive, false); - test.done(); - }, - - 'array'(test: Test) { - const type = Schema.array( - Schema.struct([{ - name: 'key', - type: Schema.STRING, - }])); - test.equals(type.inputString, 'array>'); - test.equals(type.isPrimitive, false); - test.done(); - }, - - 'map'(test: Test) { - const type = Schema.map( - Schema.STRING, - Schema.STRING, - ); - test.equals(type.inputString, 'map'); - test.equals(type.isPrimitive, false); - test.done(); - }, - - 'map'(test: Test) { - const type = Schema.map( - Schema.INTEGER, - Schema.STRING, - ); - test.equals(type.inputString, 'map'); - test.equals(type.isPrimitive, false); - test.done(); - }, - - 'map'(test: Test) { - const type = Schema.map( - Schema.char(1), - Schema.char(1), - ); - test.equals(type.inputString, 'map'); - test.equals(type.isPrimitive, false); - test.done(); - }, - - 'map'(test: Test) { - const type = Schema.map( - Schema.char(1), - Schema.array(Schema.STRING), - ); - test.equals(type.inputString, 'map>'); - test.equals(type.isPrimitive, false); - test.done(); - }, - - 'map'(test: Test) { - const type = Schema.map( - Schema.char(1), - Schema.map( - Schema.STRING, - Schema.STRING), - ); - test.equals(type.inputString, 'map>'); - test.equals(type.isPrimitive, false); - test.done(); - }, - - 'map'(test: Test) { - const type = Schema.map( - Schema.char(1), - Schema.struct([{ - name: 'key', - type: Schema.STRING, - }]), - ); - test.equals(type.inputString, 'map>'); - test.equals(type.isPrimitive, false); - test.done(); - }, - - 'map throws if keyType is non-primitive'(test: Test) { - test.throws(() => Schema.map( - Schema.array(Schema.STRING), - Schema.STRING, - )); - test.throws(() => Schema.map( - Schema.map(Schema.STRING, Schema.STRING), - Schema.STRING, - )); - test.throws(() => Schema.map( - Schema.struct([{ - name: 'key', - type: Schema.STRING, - }]), - Schema.STRING, - )); - test.done(); - }, - - 'struct type'(test: Test) { - const type = Schema.struct([{ - name: 'primitive', - type: Schema.STRING, - }, { - name: 'with_comment', - type: Schema.STRING, - comment: 'this has a comment', - }, { - name: 'array', - type: Schema.array(Schema.STRING), - }, { - name: 'map', - type: Schema.map(Schema.STRING, Schema.STRING), - }, { - name: 'nested_struct', - type: Schema.struct([{ - name: 'nested', - type: Schema.STRING, - comment: 'nested comment', - }]), - }]); - - test.equals(type.isPrimitive, false); - test.equals( - type.inputString, - // tslint:disable-next-line:max-line-length - 'struct,map:map,nested_struct:struct>'); - test.done(); - }, -}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-glue/test/test.table.ts b/packages/@aws-cdk/aws-glue/test/test.table.ts deleted file mode 100644 index b00466ebdb21d..0000000000000 --- a/packages/@aws-cdk/aws-glue/test/test.table.ts +++ /dev/null @@ -1,1626 +0,0 @@ -import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; -import * as iam from '@aws-cdk/aws-iam'; -import * as kms from '@aws-cdk/aws-kms'; -import * as s3 from '@aws-cdk/aws-s3'; -import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; -import * as glue from '../lib'; - -export = { - 'unpartitioned JSON table'(test: Test) { - const app = new cdk.App(); - const dbStack = new cdk.Stack(app, 'db'); - const database = new glue.Database(dbStack, 'Database', { - databaseName: 'database', - }); - - const tableStack = new cdk.Stack(app, 'table'); - const table = new glue.Table(tableStack, 'Table', { - database, - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - dataFormat: glue.DataFormat.JSON, - }); - test.equals(table.encryption, glue.TableEncryption.UNENCRYPTED); - - expect(tableStack).to(haveResource('AWS::S3::Bucket', { - Type: 'AWS::S3::Bucket', - DeletionPolicy: 'Retain', - UpdateReplacePolicy: 'Retain', - }, ResourcePart.CompleteDefinition)); - - expect(tableStack).to(haveResource('AWS::Glue::Table', { - CatalogId: { - Ref: 'AWS::AccountId', - }, - DatabaseName: { - 'Fn::ImportValue': 'db:ExportsOutputRefDatabaseB269D8BB88F4B1C4', - }, - TableInput: { - Name: 'table', - Description: 'table generated by CDK', - Parameters: { - has_encrypted_data: false, - }, - StorageDescriptor: { - Columns: [ - { - Name: 'col', - Type: 'string', - }, - ], - Compressed: false, - InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', - Location: { - 'Fn::Join': [ - '', - [ - 's3://', - { - Ref: 'TableBucketDA42407C', - }, - '/data/', - ], - ], - }, - OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', - SerdeInfo: { - SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', - }, - StoredAsSubDirectories: false, - }, - TableType: 'EXTERNAL_TABLE', - }, - })); - - test.done(); - }, - - 'partitioned JSON table'(test: Test) { - const app = new cdk.App(); - const dbStack = new cdk.Stack(app, 'db'); - const database = new glue.Database(dbStack, 'Database', { - databaseName: 'database', - }); - - const tableStack = new cdk.Stack(app, 'table'); - const table = new glue.Table(tableStack, 'Table', { - database, - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - partitionKeys: [{ - name: 'year', - type: glue.Schema.SMALL_INT, - }], - dataFormat: glue.DataFormat.JSON, - }); - test.equals(table.encryption, glue.TableEncryption.UNENCRYPTED); - test.equals(table.encryptionKey, undefined); - test.equals(table.bucket.encryptionKey, undefined); - - expect(tableStack).to(haveResource('AWS::Glue::Table', { - CatalogId: { - Ref: 'AWS::AccountId', - }, - DatabaseName: { - 'Fn::ImportValue': 'db:ExportsOutputRefDatabaseB269D8BB88F4B1C4', - }, - TableInput: { - Name: 'table', - Description: 'table generated by CDK', - Parameters: { - has_encrypted_data: false, - }, - PartitionKeys: [ - { - Name: 'year', - Type: 'smallint', - }, - ], - StorageDescriptor: { - Columns: [ - { - Name: 'col', - Type: 'string', - }, - ], - Compressed: false, - InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', - Location: { - 'Fn::Join': [ - '', - [ - 's3://', - { - Ref: 'TableBucketDA42407C', - }, - '/data/', - ], - ], - }, - OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', - SerdeInfo: { - SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', - }, - StoredAsSubDirectories: false, - }, - TableType: 'EXTERNAL_TABLE', - }, - })); - - test.done(); - }, - - 'compressed table'(test: Test) { - const stack = new cdk.Stack(); - const database = new glue.Database(stack, 'Database', { - databaseName: 'database', - }); - - const table = new glue.Table(stack, 'Table', { - database, - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - compressed: true, - dataFormat: glue.DataFormat.JSON, - }); - test.equals(table.encryptionKey, undefined); - test.equals(table.bucket.encryptionKey, undefined); - - expect(stack).to(haveResource('AWS::Glue::Table', { - CatalogId: { - Ref: 'AWS::AccountId', - }, - DatabaseName: { - Ref: 'DatabaseB269D8BB', - }, - TableInput: { - Name: 'table', - Description: 'table generated by CDK', - Parameters: { - has_encrypted_data: false, - }, - StorageDescriptor: { - Columns: [ - { - Name: 'col', - Type: 'string', - }, - ], - Compressed: true, - InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', - Location: { - 'Fn::Join': [ - '', - [ - 's3://', - { - Ref: 'TableBucketDA42407C', - }, - '/data/', - ], - ], - }, - OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', - SerdeInfo: { - SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', - }, - StoredAsSubDirectories: false, - }, - TableType: 'EXTERNAL_TABLE', - }, - })); - - test.done(); - }, - - 'table.node.defaultChild'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const database = new glue.Database(stack, 'Database', { - databaseName: 'database', - }); - - // WHEN - const table = new glue.Table(stack, 'Table', { - database, - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - compressed: true, - dataFormat: glue.DataFormat.JSON, - }); - - // THEN - test.ok(table.node.defaultChild instanceof glue.CfnTable); - test.done(); - }, - - 'encrypted table': { - 'SSE-S3'(test: Test) { - const stack = new cdk.Stack(); - const database = new glue.Database(stack, 'Database', { - databaseName: 'database', - }); - - const table = new glue.Table(stack, 'Table', { - database, - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - encryption: glue.TableEncryption.S3_MANAGED, - dataFormat: glue.DataFormat.JSON, - }); - test.equals(table.encryption, glue.TableEncryption.S3_MANAGED); - test.equals(table.encryptionKey, undefined); - test.equals(table.bucket.encryptionKey, undefined); - - expect(stack).to(haveResource('AWS::Glue::Table', { - CatalogId: { - Ref: 'AWS::AccountId', - }, - DatabaseName: { - Ref: 'DatabaseB269D8BB', - }, - TableInput: { - Name: 'table', - Description: 'table generated by CDK', - Parameters: { - has_encrypted_data: true, - }, - StorageDescriptor: { - Columns: [ - { - Name: 'col', - Type: 'string', - }, - ], - Compressed: false, - InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', - Location: { - 'Fn::Join': [ - '', - [ - 's3://', - { - Ref: 'TableBucketDA42407C', - }, - '/data/', - ], - ], - }, - OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', - SerdeInfo: { - SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', - }, - StoredAsSubDirectories: false, - }, - TableType: 'EXTERNAL_TABLE', - }, - })); - - expect(stack).to(haveResource('AWS::S3::Bucket', { - BucketEncryption: { - ServerSideEncryptionConfiguration: [ - { - ServerSideEncryptionByDefault: { - SSEAlgorithm: 'AES256', - }, - }, - ], - }, - })); - - test.done(); - }, - - 'SSE-KMS (implicitly created key)'(test: Test) { - const stack = new cdk.Stack(); - const database = new glue.Database(stack, 'Database', { - databaseName: 'database', - }); - - const table = new glue.Table(stack, 'Table', { - database, - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - encryption: glue.TableEncryption.KMS, - dataFormat: glue.DataFormat.JSON, - }); - test.equals(table.encryption, glue.TableEncryption.KMS); - test.equals(table.encryptionKey, table.bucket.encryptionKey); - - expect(stack).to(haveResource('AWS::KMS::Key', { - KeyPolicy: { - Statement: [ - { - Action: [ - 'kms:Create*', - 'kms:Describe*', - 'kms:Enable*', - 'kms:List*', - 'kms:Put*', - 'kms:Update*', - 'kms:Revoke*', - 'kms:Disable*', - 'kms:Get*', - 'kms:Delete*', - 'kms:ScheduleKeyDeletion', - 'kms:CancelKeyDeletion', - 'kms:GenerateDataKey', - 'kms:TagResource', - 'kms:UntagResource', - ], - Effect: 'Allow', - Principal: { - AWS: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::', - { - Ref: 'AWS::AccountId', - }, - ':root', - ], - ], - }, - }, - Resource: '*', - }, - ], - Version: '2012-10-17', - }, - Description: 'Created by Table/Bucket', - })); - - expect(stack).to(haveResource('AWS::S3::Bucket', { - BucketEncryption: { - ServerSideEncryptionConfiguration: [ - { - ServerSideEncryptionByDefault: { - KMSMasterKeyID: { - 'Fn::GetAtt': [ - 'TableBucketKey3E9F984A', - 'Arn', - ], - }, - SSEAlgorithm: 'aws:kms', - }, - }, - ], - }, - })); - - expect(stack).to(haveResource('AWS::Glue::Table', { - CatalogId: { - Ref: 'AWS::AccountId', - }, - DatabaseName: { - Ref: 'DatabaseB269D8BB', - }, - TableInput: { - Name: 'table', - Description: 'table generated by CDK', - Parameters: { - has_encrypted_data: true, - }, - StorageDescriptor: { - Columns: [ - { - Name: 'col', - Type: 'string', - }, - ], - Compressed: false, - InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', - Location: { - 'Fn::Join': [ - '', - [ - 's3://', - { - Ref: 'TableBucketDA42407C', - }, - '/data/', - ], - ], - }, - OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', - SerdeInfo: { - SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', - }, - StoredAsSubDirectories: false, - }, - TableType: 'EXTERNAL_TABLE', - }, - })); - - test.done(); - }, - - 'SSE-KMS (explicitly created key)'(test: Test) { - const stack = new cdk.Stack(); - const database = new glue.Database(stack, 'Database', { - databaseName: 'database', - }); - const encryptionKey = new kms.Key(stack, 'MyKey'); - - const table = new glue.Table(stack, 'Table', { - database, - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - encryption: glue.TableEncryption.KMS, - encryptionKey, - dataFormat: glue.DataFormat.JSON, - }); - test.equals(table.encryption, glue.TableEncryption.KMS); - test.equals(table.encryptionKey, table.bucket.encryptionKey); - test.notEqual(table.encryptionKey, undefined); - - expect(stack).to(haveResource('AWS::KMS::Key', { - KeyPolicy: { - Statement: [ - { - Action: [ - 'kms:Create*', - 'kms:Describe*', - 'kms:Enable*', - 'kms:List*', - 'kms:Put*', - 'kms:Update*', - 'kms:Revoke*', - 'kms:Disable*', - 'kms:Get*', - 'kms:Delete*', - 'kms:ScheduleKeyDeletion', - 'kms:CancelKeyDeletion', - 'kms:GenerateDataKey', - 'kms:TagResource', - 'kms:UntagResource', - ], - Effect: 'Allow', - Principal: { - AWS: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::', - { - Ref: 'AWS::AccountId', - }, - ':root', - ], - ], - }, - }, - Resource: '*', - }, - ], - Version: '2012-10-17', - }, - })); - - expect(stack).to(haveResource('AWS::S3::Bucket', { - BucketEncryption: { - ServerSideEncryptionConfiguration: [ - { - ServerSideEncryptionByDefault: { - KMSMasterKeyID: { - 'Fn::GetAtt': [ - 'MyKey6AB29FA6', - 'Arn', - ], - }, - SSEAlgorithm: 'aws:kms', - }, - }, - ], - }, - })); - - expect(stack).to(haveResource('AWS::Glue::Table', { - CatalogId: { - Ref: 'AWS::AccountId', - }, - DatabaseName: { - Ref: 'DatabaseB269D8BB', - }, - TableInput: { - Description: 'table generated by CDK', - Name: 'table', - Parameters: { - has_encrypted_data: true, - }, - StorageDescriptor: { - Columns: [ - { - Name: 'col', - Type: 'string', - }, - ], - Compressed: false, - InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', - Location: { - 'Fn::Join': [ - '', - [ - 's3://', - { - Ref: 'TableBucketDA42407C', - }, - '/data/', - ], - ], - }, - OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', - SerdeInfo: { - SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', - }, - StoredAsSubDirectories: false, - }, - TableType: 'EXTERNAL_TABLE', - }, - })); - - test.done(); - }, - - 'SSE-KMS_MANAGED'(test: Test) { - const stack = new cdk.Stack(); - const database = new glue.Database(stack, 'Database', { - databaseName: 'database', - }); - - const table = new glue.Table(stack, 'Table', { - database, - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - encryption: glue.TableEncryption.KMS_MANAGED, - dataFormat: glue.DataFormat.JSON, - }); - test.equals(table.encryption, glue.TableEncryption.KMS_MANAGED); - test.equals(table.encryptionKey, undefined); - test.equals(table.bucket.encryptionKey, undefined); - - expect(stack).to(haveResource('AWS::S3::Bucket', { - BucketEncryption: { - ServerSideEncryptionConfiguration: [ - { - ServerSideEncryptionByDefault: { - SSEAlgorithm: 'aws:kms', - }, - }, - ], - }, - })); - - expect(stack).to(haveResource('AWS::Glue::Table', { - CatalogId: { - Ref: 'AWS::AccountId', - }, - DatabaseName: { - Ref: 'DatabaseB269D8BB', - }, - TableInput: { - Name: 'table', - Description: 'table generated by CDK', - Parameters: { - has_encrypted_data: true, - }, - StorageDescriptor: { - Columns: [ - { - Name: 'col', - Type: 'string', - }, - ], - Compressed: false, - InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', - Location: { - 'Fn::Join': [ - '', - [ - 's3://', - { - Ref: 'TableBucketDA42407C', - }, - '/data/', - ], - ], - }, - OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', - SerdeInfo: { - SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', - }, - StoredAsSubDirectories: false, - }, - TableType: 'EXTERNAL_TABLE', - }, - })); - - test.done(); - }, - - 'CSE-KMS (implicitly created key)'(test: Test) { - const stack = new cdk.Stack(); - const database = new glue.Database(stack, 'Database', { - databaseName: 'database', - }); - - const table = new glue.Table(stack, 'Table', { - database, - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - encryption: glue.TableEncryption.CLIENT_SIDE_KMS, - dataFormat: glue.DataFormat.JSON, - }); - test.equals(table.encryption, glue.TableEncryption.CLIENT_SIDE_KMS); - test.notEqual(table.encryptionKey, undefined); - test.equals(table.bucket.encryptionKey, undefined); - - expect(stack).to(haveResource('AWS::KMS::Key', { - KeyPolicy: { - Statement: [ - { - Action: [ - 'kms:Create*', - 'kms:Describe*', - 'kms:Enable*', - 'kms:List*', - 'kms:Put*', - 'kms:Update*', - 'kms:Revoke*', - 'kms:Disable*', - 'kms:Get*', - 'kms:Delete*', - 'kms:ScheduleKeyDeletion', - 'kms:CancelKeyDeletion', - 'kms:GenerateDataKey', - 'kms:TagResource', - 'kms:UntagResource', - ], - Effect: 'Allow', - Principal: { - AWS: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::', - { - Ref: 'AWS::AccountId', - }, - ':root', - ], - ], - }, - }, - Resource: '*', - }, - ], - Version: '2012-10-17', - }, - })); - - expect(stack).to(haveResource('AWS::Glue::Table', { - CatalogId: { - Ref: 'AWS::AccountId', - }, - DatabaseName: { - Ref: 'DatabaseB269D8BB', - }, - TableInput: { - Description: 'table generated by CDK', - Name: 'table', - Parameters: { - has_encrypted_data: true, - }, - StorageDescriptor: { - Columns: [ - { - Name: 'col', - Type: 'string', - }, - ], - Compressed: false, - InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', - Location: { - 'Fn::Join': [ - '', - [ - 's3://', - { - Ref: 'TableBucketDA42407C', - }, - '/data/', - ], - ], - }, - OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', - SerdeInfo: { - SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', - }, - StoredAsSubDirectories: false, - }, - TableType: 'EXTERNAL_TABLE', - }, - })); - - test.done(); - }, - - 'CSE-KMS (explicitly created key)'(test: Test) { - const stack = new cdk.Stack(); - const database = new glue.Database(stack, 'Database', { - databaseName: 'database', - }); - const encryptionKey = new kms.Key(stack, 'MyKey'); - - const table = new glue.Table(stack, 'Table', { - database, - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - encryption: glue.TableEncryption.CLIENT_SIDE_KMS, - encryptionKey, - dataFormat: glue.DataFormat.JSON, - }); - test.equals(table.encryption, glue.TableEncryption.CLIENT_SIDE_KMS); - test.notEqual(table.encryptionKey, undefined); - test.equals(table.bucket.encryptionKey, undefined); - - expect(stack).to(haveResource('AWS::KMS::Key', { - KeyPolicy: { - Statement: [ - { - Action: [ - 'kms:Create*', - 'kms:Describe*', - 'kms:Enable*', - 'kms:List*', - 'kms:Put*', - 'kms:Update*', - 'kms:Revoke*', - 'kms:Disable*', - 'kms:Get*', - 'kms:Delete*', - 'kms:ScheduleKeyDeletion', - 'kms:CancelKeyDeletion', - 'kms:GenerateDataKey', - 'kms:TagResource', - 'kms:UntagResource', - ], - Effect: 'Allow', - Principal: { - AWS: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::', - { - Ref: 'AWS::AccountId', - }, - ':root', - ], - ], - }, - }, - Resource: '*', - }, - ], - Version: '2012-10-17', - }, - })); - - expect(stack).to(haveResource('AWS::Glue::Table', { - CatalogId: { - Ref: 'AWS::AccountId', - }, - DatabaseName: { - Ref: 'DatabaseB269D8BB', - }, - TableInput: { - Description: 'table generated by CDK', - Name: 'table', - Parameters: { - has_encrypted_data: true, - }, - StorageDescriptor: { - Columns: [ - { - Name: 'col', - Type: 'string', - }, - ], - Compressed: false, - InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', - Location: { - 'Fn::Join': [ - '', - [ - 's3://', - { - Ref: 'TableBucketDA42407C', - }, - '/data/', - ], - ], - }, - OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', - SerdeInfo: { - SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', - }, - StoredAsSubDirectories: false, - }, - TableType: 'EXTERNAL_TABLE', - }, - })); - - test.done(); - }, - - 'CSE-KMS (explicitly passed bucket and key)'(test: Test) { - const stack = new cdk.Stack(); - const database = new glue.Database(stack, 'Database', { - databaseName: 'database', - }); - const bucket = new s3.Bucket(stack, 'Bucket'); - const encryptionKey = new kms.Key(stack, 'MyKey'); - - const table = new glue.Table(stack, 'Table', { - database, - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - bucket, - encryption: glue.TableEncryption.CLIENT_SIDE_KMS, - encryptionKey, - dataFormat: glue.DataFormat.JSON, - }); - test.equals(table.encryption, glue.TableEncryption.CLIENT_SIDE_KMS); - test.notEqual(table.encryptionKey, undefined); - test.equals(table.bucket.encryptionKey, undefined); - - expect(stack).to(haveResource('AWS::KMS::Key', { - KeyPolicy: { - Statement: [ - { - Action: [ - 'kms:Create*', - 'kms:Describe*', - 'kms:Enable*', - 'kms:List*', - 'kms:Put*', - 'kms:Update*', - 'kms:Revoke*', - 'kms:Disable*', - 'kms:Get*', - 'kms:Delete*', - 'kms:ScheduleKeyDeletion', - 'kms:CancelKeyDeletion', - 'kms:GenerateDataKey', - 'kms:TagResource', - 'kms:UntagResource', - ], - Effect: 'Allow', - Principal: { - AWS: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::', - { - Ref: 'AWS::AccountId', - }, - ':root', - ], - ], - }, - }, - Resource: '*', - }, - ], - Version: '2012-10-17', - }, - })); - - expect(stack).to(haveResource('AWS::Glue::Table', { - CatalogId: { - Ref: 'AWS::AccountId', - }, - DatabaseName: { - Ref: 'DatabaseB269D8BB', - }, - TableInput: { - Description: 'table generated by CDK', - Name: 'table', - Parameters: { - has_encrypted_data: true, - }, - StorageDescriptor: { - Columns: [ - { - Name: 'col', - Type: 'string', - }, - ], - Compressed: false, - InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', - Location: { - 'Fn::Join': [ - '', - [ - 's3://', - { - Ref: 'Bucket83908E77', - }, - '/data/', - ], - ], - }, - OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', - SerdeInfo: { - SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', - }, - StoredAsSubDirectories: false, - }, - TableType: 'EXTERNAL_TABLE', - }, - })); - - test.done(); - }, - }, - - 'explicit s3 bucket and prefix'(test: Test) { - const app = new cdk.App(); - const dbStack = new cdk.Stack(app, 'db'); - const stack = new cdk.Stack(app, 'app'); - const bucket = new s3.Bucket(stack, 'ExplicitBucket'); - const database = new glue.Database(dbStack, 'Database', { - databaseName: 'database', - }); - - new glue.Table(stack, 'Table', { - database, - bucket, - s3Prefix: 'prefix/', - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - dataFormat: glue.DataFormat.JSON, - }); - - expect(stack).to(haveResource('AWS::Glue::Table', { - CatalogId: { - Ref: 'AWS::AccountId', - }, - DatabaseName: { - 'Fn::ImportValue': 'db:ExportsOutputRefDatabaseB269D8BB88F4B1C4', - }, - TableInput: { - Description: 'table generated by CDK', - Name: 'table', - Parameters: { - has_encrypted_data: false, - }, - StorageDescriptor: { - Columns: [ - { - Name: 'col', - Type: 'string', - }, - ], - Compressed: false, - InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', - Location: { - 'Fn::Join': [ - '', - [ - 's3://', - { - Ref: 'ExplicitBucket0AA51A3F', - }, - '/prefix/', - ], - ], - }, - OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', - SerdeInfo: { - SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', - }, - StoredAsSubDirectories: false, - }, - TableType: 'EXTERNAL_TABLE', - }, - })); - - test.done(); - }, - - 'explicit s3 bucket and with empty prefix'(test: Test) { - const app = new cdk.App(); - const dbStack = new cdk.Stack(app, 'db'); - const stack = new cdk.Stack(app, 'app'); - const bucket = new s3.Bucket(stack, 'ExplicitBucket'); - const database = new glue.Database(dbStack, 'Database', { - databaseName: 'database', - }); - - new glue.Table(stack, 'Table', { - database, - bucket, - s3Prefix: '', - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - dataFormat: glue.DataFormat.JSON, - }); - - expect(stack).to(haveResource('AWS::Glue::Table', { - CatalogId: { - Ref: 'AWS::AccountId', - }, - DatabaseName: { - 'Fn::ImportValue': 'db:ExportsOutputRefDatabaseB269D8BB88F4B1C4', - }, - TableInput: { - Description: 'table generated by CDK', - Name: 'table', - Parameters: { - has_encrypted_data: false, - }, - StorageDescriptor: { - Columns: [ - { - Name: 'col', - Type: 'string', - }, - ], - Compressed: false, - InputFormat: 'org.apache.hadoop.mapred.TextInputFormat', - Location: { - 'Fn::Join': [ - '', - [ - 's3://', - { - Ref: 'ExplicitBucket0AA51A3F', - }, - '/', - ], - ], - }, - OutputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', - SerdeInfo: { - SerializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', - }, - StoredAsSubDirectories: false, - }, - TableType: 'EXTERNAL_TABLE', - }, - })); - - test.done(); - }, - - 'grants': { - 'read only'(test: Test) { - const stack = new cdk.Stack(); - const user = new iam.User(stack, 'User'); - const database = new glue.Database(stack, 'Database', { - databaseName: 'database', - }); - - const table = new glue.Table(stack, 'Table', { - database, - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - compressed: true, - dataFormat: glue.DataFormat.JSON, - }); - - table.grantRead(user); - - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ - { - Action: [ - 'glue:BatchDeletePartition', - 'glue:BatchGetPartition', - 'glue:GetPartition', - 'glue:GetPartitions', - 'glue:GetTable', - 'glue:GetTables', - 'glue:GetTableVersions', - ], - Effect: 'Allow', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':glue:', - { - Ref: 'AWS::Region', - }, - ':', - { - Ref: 'AWS::AccountId', - }, - ':table/', - { - Ref: 'DatabaseB269D8BB', - }, - '/', - { - Ref: 'Table4C2D914F', - }, - ], - ], - }, - }, - { - Action: [ - 's3:GetObject*', - 's3:GetBucket*', - 's3:List*', - ], - Effect: 'Allow', - Resource: [ - { - 'Fn::GetAtt': [ - 'TableBucketDA42407C', - 'Arn', - ], - }, - { - 'Fn::Join': [ - '', - [ - { - 'Fn::GetAtt': [ - 'TableBucketDA42407C', - 'Arn', - ], - }, - '/data/', - ], - ], - }, - ], - }, - ], - Version: '2012-10-17', - }, - PolicyName: 'UserDefaultPolicy1F97781E', - Users: [ - { - Ref: 'User00B015A1', - }, - ], - })); - - test.done(); - }, - - 'write only'(test: Test) { - const stack = new cdk.Stack(); - const user = new iam.User(stack, 'User'); - const database = new glue.Database(stack, 'Database', { - databaseName: 'database', - }); - - const table = new glue.Table(stack, 'Table', { - database, - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - compressed: true, - dataFormat: glue.DataFormat.JSON, - }); - - table.grantWrite(user); - - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ - { - Action: [ - 'glue:BatchCreatePartition', - 'glue:BatchDeletePartition', - 'glue:CreatePartition', - 'glue:DeletePartition', - 'glue:UpdatePartition', - ], - Effect: 'Allow', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':glue:', - { - Ref: 'AWS::Region', - }, - ':', - { - Ref: 'AWS::AccountId', - }, - ':table/', - { - Ref: 'DatabaseB269D8BB', - }, - '/', - { - Ref: 'Table4C2D914F', - }, - ], - ], - }, - }, - { - Action: [ - 's3:DeleteObject*', - 's3:PutObject*', - 's3:Abort*', - ], - Effect: 'Allow', - Resource: [ - { - 'Fn::GetAtt': [ - 'TableBucketDA42407C', - 'Arn', - ], - }, - { - 'Fn::Join': [ - '', - [ - { - 'Fn::GetAtt': [ - 'TableBucketDA42407C', - 'Arn', - ], - }, - '/data/', - ], - ], - }, - ], - }, - ], - Version: '2012-10-17', - }, - PolicyName: 'UserDefaultPolicy1F97781E', - Users: [ - { - Ref: 'User00B015A1', - }, - ], - })); - - test.done(); - }, - - 'read and write'(test: Test) { - const stack = new cdk.Stack(); - const user = new iam.User(stack, 'User'); - const database = new glue.Database(stack, 'Database', { - databaseName: 'database', - }); - - const table = new glue.Table(stack, 'Table', { - database, - tableName: 'table', - columns: [{ - name: 'col', - type: glue.Schema.STRING, - }], - compressed: true, - dataFormat: glue.DataFormat.JSON, - }); - - table.grantReadWrite(user); - - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ - { - Action: [ - 'glue:BatchDeletePartition', - 'glue:BatchGetPartition', - 'glue:GetPartition', - 'glue:GetPartitions', - 'glue:GetTable', - 'glue:GetTables', - 'glue:GetTableVersions', - 'glue:BatchCreatePartition', - 'glue:BatchDeletePartition', - 'glue:CreatePartition', - 'glue:DeletePartition', - 'glue:UpdatePartition', - ], - Effect: 'Allow', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':glue:', - { - Ref: 'AWS::Region', - }, - ':', - { - Ref: 'AWS::AccountId', - }, - ':table/', - { - Ref: 'DatabaseB269D8BB', - }, - '/', - { - Ref: 'Table4C2D914F', - }, - ], - ], - }, - }, - { - Action: [ - 's3:GetObject*', - 's3:GetBucket*', - 's3:List*', - 's3:DeleteObject*', - 's3:PutObject*', - 's3:Abort*', - ], - Effect: 'Allow', - Resource: [ - { - 'Fn::GetAtt': [ - 'TableBucketDA42407C', - 'Arn', - ], - }, - { - 'Fn::Join': [ - '', - [ - { - 'Fn::GetAtt': [ - 'TableBucketDA42407C', - 'Arn', - ], - }, - '/data/', - ], - ], - }, - ], - }, - ], - Version: '2012-10-17', - }, - PolicyName: 'UserDefaultPolicy1F97781E', - Users: [ - { - Ref: 'User00B015A1', - }, - ], - })); - - test.done(); - }, - }, - - 'validate': { - 'at least one column'(test: Test) { - test.throws(() => { - createTable({ - columns: [], - tableName: 'name', - }); - }, undefined, 'you must specify at least one column for the table'); - - test.done(); - }, - - 'unique column names'(test: Test) { - test.throws(() => { - createTable({ - tableName: 'name', - columns: [{ - name: 'col1', - type: glue.Schema.STRING, - }, { - name: 'col1', - type: glue.Schema.STRING, - }], - }); - }, undefined, "column names and partition keys must be unique, but 'col1' is duplicated."); - - test.done(); - }, - - 'unique partition keys'(test: Test) { - test.throws(() => createTable({ - tableName: 'name', - columns: [{ - name: 'col1', - type: glue.Schema.STRING, - }], - partitionKeys: [{ - name: 'p1', - type: glue.Schema.STRING, - }, { - name: 'p1', - type: glue.Schema.STRING, - }], - }), undefined, "column names and partition keys must be unique, but 'p1' is duplicated"); - - test.done(); - }, - - 'column names and partition keys are all unique'(test: Test) { - test.throws(() => createTable({ - tableName: 'name', - columns: [{ - name: 'col1', - type: glue.Schema.STRING, - }], - partitionKeys: [{ - name: 'col1', - type: glue.Schema.STRING, - }], - }), "column names and partition keys must be unique, but 'col1' is duplicated"); - - test.done(); - }, - - 'can not specify an explicit bucket and encryption'(test: Test) { - test.throws(() => createTable({ - tableName: 'name', - columns: [{ - name: 'col1', - type: glue.Schema.STRING, - }], - bucket: new s3.Bucket(new cdk.Stack(), 'Bucket'), - encryption: glue.TableEncryption.KMS, - }), undefined, 'you can not specify encryption settings if you also provide a bucket'); - test.done(); - }, - - 'can explicitly pass bucket if Encryption undefined'(test: Test) { - test.doesNotThrow(() => createTable({ - tableName: 'name', - columns: [{ - name: 'col1', - type: glue.Schema.STRING, - }], - bucket: new s3.Bucket(new cdk.Stack(), 'Bucket'), - encryption: undefined, - })); - test.done(); - }, - - 'can explicitly pass bucket if Unencrypted'(test: Test) { - test.doesNotThrow(() => createTable({ - tableName: 'name', - columns: [{ - name: 'col1', - type: glue.Schema.STRING, - }], - bucket: new s3.Bucket(new cdk.Stack(), 'Bucket'), - encryption: undefined, - })); - test.done(); - }, - - 'can explicitly pass bucket if ClientSideKms'(test: Test) { - test.doesNotThrow(() => createTable({ - tableName: 'name', - columns: [{ - name: 'col1', - type: glue.Schema.STRING, - }], - bucket: new s3.Bucket(new cdk.Stack(), 'Bucket'), - encryption: glue.TableEncryption.CLIENT_SIDE_KMS, - })); - test.done(); - }, - }, - - 'Table.fromTableArn'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - const table = glue.Table.fromTableArn(stack, 'boom', 'arn:aws:glue:us-east-1:123456789012:table/db1/tbl1'); - - // THEN - test.deepEqual(table.tableArn, 'arn:aws:glue:us-east-1:123456789012:table/db1/tbl1'); - test.deepEqual(table.tableName, 'tbl1'); - test.done(); - }, -}; - -function createTable(props: Pick>): void { - const stack = new cdk.Stack(); - new glue.Table(stack, 'table', { - ...props, - database: new glue.Database(stack, 'db', { - databaseName: 'database_name', - }), - dataFormat: glue.DataFormat.JSON, - }); -} diff --git a/packages/@aws-cdk/aws-greengrass/.gitignore b/packages/@aws-cdk/aws-greengrass/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-greengrass/.gitignore +++ b/packages/@aws-cdk/aws-greengrass/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-greengrass/.npmignore b/packages/@aws-cdk/aws-greengrass/.npmignore index b0edf81a01371..ab90672b1d91e 100644 --- a/packages/@aws-cdk/aws-greengrass/.npmignore +++ b/packages/@aws-cdk/aws-greengrass/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-greengrass/jest.config.js b/packages/@aws-cdk/aws-greengrass/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-greengrass/jest.config.js +++ b/packages/@aws-cdk/aws-greengrass/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-guardduty/.gitignore b/packages/@aws-cdk/aws-guardduty/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-guardduty/.gitignore +++ b/packages/@aws-cdk/aws-guardduty/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-guardduty/.npmignore b/packages/@aws-cdk/aws-guardduty/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-guardduty/.npmignore +++ b/packages/@aws-cdk/aws-guardduty/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-guardduty/jest.config.js b/packages/@aws-cdk/aws-guardduty/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-guardduty/jest.config.js +++ b/packages/@aws-cdk/aws-guardduty/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iam/.gitignore b/packages/@aws-cdk/aws-iam/.gitignore index 988ed792f9310..ce6c033dbda83 100644 --- a/packages/@aws-cdk/aws-iam/.gitignore +++ b/packages/@aws-cdk/aws-iam/.gitignore @@ -14,3 +14,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/.npmignore b/packages/@aws-cdk/aws-iam/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-iam/.npmignore +++ b/packages/@aws-cdk/aws-iam/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/jest.config.js b/packages/@aws-cdk/aws-iam/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-iam/jest.config.js +++ b/packages/@aws-cdk/aws-iam/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iam/lib/grant.ts b/packages/@aws-cdk/aws-iam/lib/grant.ts index 0f882c4a73d00..c0cb065f7ee2c 100644 --- a/packages/@aws-cdk/aws-iam/lib/grant.ts +++ b/packages/@aws-cdk/aws-iam/lib/grant.ts @@ -255,7 +255,7 @@ export class Grant implements cdk.IDependable { */ public assertSuccess(): void { if (!this.success) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`${describeGrant(this.options)} could not be added on either identity or resource policy.`); } } diff --git a/packages/@aws-cdk/aws-iam/lib/lazy-role.ts b/packages/@aws-cdk/aws-iam/lib/lazy-role.ts index bfc83817ad0f8..febb6372d25e6 100644 --- a/packages/@aws-cdk/aws-iam/lib/lazy-role.ts +++ b/packages/@aws-cdk/aws-iam/lib/lazy-role.ts @@ -9,7 +9,6 @@ import { IRole, Role, RoleProps } from './role'; /** * Properties for defining a LazyRole */ -// tslint:disable-next-line:no-empty-interface export interface LazyRoleProps extends RoleProps { } diff --git a/packages/@aws-cdk/aws-iam/lib/oidc-provider.ts b/packages/@aws-cdk/aws-iam/lib/oidc-provider.ts index 06fe2395f687f..b8345827699d1 100644 --- a/packages/@aws-cdk/aws-iam/lib/oidc-provider.ts +++ b/packages/@aws-cdk/aws-iam/lib/oidc-provider.ts @@ -1,5 +1,5 @@ -import { Construct, CustomResource, CustomResourceProvider, CustomResourceProviderRuntime, IResource, Resource, Stack, Token } from '@aws-cdk/core'; import * as path from 'path'; +import { Construct, CustomResource, CustomResourceProvider, CustomResourceProviderRuntime, IResource, Resource, Stack, Token } from '@aws-cdk/core'; const RESOURCE_TYPE = 'Custom::AWSCDKOpenIdConnectProvider'; diff --git a/packages/@aws-cdk/aws-iam/lib/oidc-provider/external.ts b/packages/@aws-cdk/aws-iam/lib/oidc-provider/external.ts index 43512b4bc7a4e..d926caf405e22 100644 --- a/packages/@aws-cdk/aws-iam/lib/oidc-provider/external.ts +++ b/packages/@aws-cdk/aws-iam/lib/oidc-provider/external.ts @@ -1,9 +1,9 @@ /* istanbul ignore file */ -// eslint-disable-next-line import/no-extraneous-dependencies -import * as aws from 'aws-sdk'; import * as tls from 'tls'; import * as url from 'url'; +// eslint-disable-next-line import/no-extraneous-dependencies +import * as aws from 'aws-sdk'; let client: aws.IAM; @@ -13,7 +13,7 @@ function iam() { } function defaultLogger(fmt: string, ...args: any[]) { - // tslint:disable-next-line: no-console + // eslint-disable-next-line no-console console.log(fmt, ...args); } @@ -41,7 +41,7 @@ async function downloadThumbprint(issuerUrl: string) { } // allows unit test to replace with mocks -// tslint:disable:max-line-length +/* eslint-disable max-len */ export const external = { downloadThumbprint, log: defaultLogger, diff --git a/packages/@aws-cdk/aws-iam/lib/role.ts b/packages/@aws-cdk/aws-iam/lib/role.ts index e62dcd45af0be..8b62425be50c6 100644 --- a/packages/@aws-cdk/aws-iam/lib/role.ts +++ b/packages/@aws-cdk/aws-iam/lib/role.ts @@ -177,10 +177,9 @@ export class Role extends Resource implements IRole { const parsedArn = scopeStack.parseArn(roleArn); const resourceName = parsedArn.resourceName!; // service roles have an ARN like 'arn:aws:iam:::role/service-role/' - // we want to support these as well, so strip out the 'service-role/' prefix if we see it - const roleName = resourceName.startsWith('service-role/') - ? resourceName.slice('service-role/'.length) - : resourceName; + // or 'arn:aws:iam:::role/service-role/servicename.amazonaws.com/service-role/' + // we want to support these as well, so we just use the element after the last slash as role name + const roleName = resourceName.split('/').pop()!; class Import extends Resource implements IRole { public readonly grantPrincipal: IPrincipal = this; diff --git a/packages/@aws-cdk/aws-iam/test/escape-hatch.test.ts b/packages/@aws-cdk/aws-iam/test/escape-hatch.test.ts index 95498d8fcb926..4aa2ce2e9ddb8 100644 --- a/packages/@aws-cdk/aws-iam/test/escape-hatch.test.ts +++ b/packages/@aws-cdk/aws-iam/test/escape-hatch.test.ts @@ -5,7 +5,7 @@ import '@aws-cdk/assert/jest'; import { Stack } from '@aws-cdk/core'; import * as iam from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ describe('IAM escape hatches', () => { test('addPropertyOverride should allow overriding supported properties', () => { diff --git a/packages/@aws-cdk/aws-iam/test/immutable-role.test.ts b/packages/@aws-cdk/aws-iam/test/immutable-role.test.ts index 51bb0d2496c0f..0a14afee947a5 100644 --- a/packages/@aws-cdk/aws-iam/test/immutable-role.test.ts +++ b/packages/@aws-cdk/aws-iam/test/immutable-role.test.ts @@ -2,7 +2,7 @@ import '@aws-cdk/assert/jest'; import { Construct, Stack } from '@aws-cdk/core'; import * as iam from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ describe('ImmutableRole', () => { let stack: Stack; diff --git a/packages/@aws-cdk/aws-iam/test/integ.condition-with-ref.ts b/packages/@aws-cdk/aws-iam/test/integ.condition-with-ref.ts index 576f6bd835083..3a8efb6f1ecb5 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.condition-with-ref.ts +++ b/packages/@aws-cdk/aws-iam/test/integ.condition-with-ref.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import { App, CfnJson, CfnParameter, Construct, Stack } from '@aws-cdk/core'; import { AccountRootPrincipal, Role } from '../lib'; diff --git a/packages/@aws-cdk/aws-iam/test/integ.oidc-provider.ts b/packages/@aws-cdk/aws-iam/test/integ.oidc-provider.ts index 33a241251f4bf..d428ff1204e54 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.oidc-provider.ts +++ b/packages/@aws-cdk/aws-iam/test/integ.oidc-provider.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import { App, Stack } from '@aws-cdk/core'; import * as iam from '../lib'; @@ -21,4 +22,4 @@ new iam.OpenIdConnectProvider(stack, 'Thumbprints', { ], }); -app.synth(); \ No newline at end of file +app.synth(); diff --git a/packages/@aws-cdk/aws-iam/test/policy-statement.test.ts b/packages/@aws-cdk/aws-iam/test/policy-statement.test.ts index e5dd73e49c684..a21954a811c6b 100644 --- a/packages/@aws-cdk/aws-iam/test/policy-statement.test.ts +++ b/packages/@aws-cdk/aws-iam/test/policy-statement.test.ts @@ -127,38 +127,36 @@ describe('IAM policy statement', () => { test('the kitchen sink', () => { const stack = new Stack(); - /* tslint:disable */ const policyDocument = { - 'Version': '2012-10-17', - 'Statement': [ + Version: '2012-10-17', + Statement: [ { - 'Sid': 'FirstStatement', - 'Effect': 'Allow', - 'Action': 'iam:ChangePassword', - 'Resource': '*', + Sid: 'FirstStatement', + Effect: 'Allow', + Action: 'iam:ChangePassword', + Resource: '*', }, { - 'Sid': 'SecondStatement', - 'Effect': 'Allow', - 'Action': 's3:ListAllMyBuckets', - 'Resource': '*', + Sid: 'SecondStatement', + Effect: 'Allow', + Action: 's3:ListAllMyBuckets', + Resource: '*', }, { - 'Sid': 'ThirdStatement', - 'Effect': 'Allow', - 'Action': [ + Sid: 'ThirdStatement', + Effect: 'Allow', + Action: [ 's3:List*', 's3:Get*', ], - 'Resource': [ + Resource: [ 'arn:aws:s3:::confidential-data', 'arn:aws:s3:::confidential-data/*', ], - 'Condition': {'Bool': {'aws:MultiFactorAuthPresent': 'true'}}, + Condition: {Bool: {'aws:MultiFactorAuthPresent': 'true'}}, }, ], }; - /* tslint:enable */ const doc = PolicyDocument.fromJson(policyDocument); diff --git a/packages/@aws-cdk/aws-iam/test/policy.test.ts b/packages/@aws-cdk/aws-iam/test/policy.test.ts index 2ca1e54b7154c..479476854b6c3 100644 --- a/packages/@aws-cdk/aws-iam/test/policy.test.ts +++ b/packages/@aws-cdk/aws-iam/test/policy.test.ts @@ -3,7 +3,7 @@ import '@aws-cdk/assert/jest'; import { App, CfnResource, Stack } from '@aws-cdk/core'; import { AnyPrincipal, CfnPolicy, Group, Policy, PolicyStatement, Role, ServicePrincipal, User } from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ describe('IAM policy', () => { let app: App; diff --git a/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts b/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts index c168194c08766..295cae174fe6a 100644 --- a/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts +++ b/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts @@ -2,7 +2,7 @@ import '@aws-cdk/assert/jest'; import { App, CfnElement, Lazy, Stack } from '@aws-cdk/core'; import { AnyPrincipal, ArnPrincipal, IRole, Policy, PolicyStatement, Role } from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ const roleAccount = '123456789012'; const notRoleAccount = '012345678901'; @@ -292,7 +292,7 @@ describe('IAM Role.fromRoleArn', () => { describe('that belongs to a stack with account equal to the account in the imported role ARN', () => { beforeEach(() => { - policyStack = new Stack(app, 'PolicyStack', { env: { account : roleAccount } }); + policyStack = new Stack(app, 'PolicyStack', { env: { account: roleAccount } }); importedRole.attachInlinePolicy(somePolicy(policyStack, 'MyPolicy')); }); @@ -480,20 +480,45 @@ describe('IAM Role.fromRoleArn', () => { describe('imported with the ARN of a service role', () => { beforeEach(() => { roleStack = new Stack(); - importedRole = Role.fromRoleArn(roleStack, 'Role', - `arn:aws:iam::${roleAccount}:role/service-role/codebuild-role`); }); - it("correctly strips the 'service-role' prefix from the role name", () => { - new Policy(roleStack, 'Policy', { - statements: [somePolicyStatement()], - roles: [importedRole], + describe('without a service principal in the role name', () => { + beforeEach(() => { + importedRole = Role.fromRoleArn(roleStack, 'Role', + `arn:aws:iam::${roleAccount}:role/service-role/codebuild-role`); + }); + + it("correctly strips the 'service-role' prefix from the role name", () => { + new Policy(roleStack, 'Policy', { + statements: [somePolicyStatement()], + roles: [importedRole], + }); + + expect(roleStack).toHaveResourceLike('AWS::IAM::Policy', { + 'Roles': [ + 'codebuild-role', + ], + }); + }); + }); + + describe('with a service principal in the role name', () => { + beforeEach(() => { + importedRole = Role.fromRoleArn(roleStack, 'Role', + `arn:aws:iam::${roleAccount}:role/aws-service-role/anyservice.amazonaws.com/codebuild-role`); }); - expect(roleStack).toHaveResourceLike('AWS::IAM::Policy', { - 'Roles': [ - 'codebuild-role', - ], + it("correctly strips both the 'aws-service-role' prefix and the service principal from the role name", () => { + new Policy(roleStack, 'Policy', { + statements: [somePolicyStatement()], + roles: [importedRole], + }); + + expect(roleStack).toHaveResourceLike('AWS::IAM::Policy', { + 'Roles': [ + 'codebuild-role', + ], + }); }); }); }); diff --git a/packages/@aws-cdk/aws-imagebuilder/.gitignore b/packages/@aws-cdk/aws-imagebuilder/.gitignore index e9fee23607e76..5aa413b898780 100644 --- a/packages/@aws-cdk/aws-imagebuilder/.gitignore +++ b/packages/@aws-cdk/aws-imagebuilder/.gitignore @@ -2,7 +2,6 @@ *.js.map *.d.ts tsconfig.json -tslint.json node_modules *.generated.ts dist @@ -17,3 +16,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml diff --git a/packages/@aws-cdk/aws-imagebuilder/.npmignore b/packages/@aws-cdk/aws-imagebuilder/.npmignore index 2f1ff45adc883..c8874799485a3 100644 --- a/packages/@aws-cdk/aws-imagebuilder/.npmignore +++ b/packages/@aws-cdk/aws-imagebuilder/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-imagebuilder/jest.config.js b/packages/@aws-cdk/aws-imagebuilder/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-imagebuilder/jest.config.js +++ b/packages/@aws-cdk/aws-imagebuilder/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-inspector/.gitignore b/packages/@aws-cdk/aws-inspector/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-inspector/.gitignore +++ b/packages/@aws-cdk/aws-inspector/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-inspector/.npmignore b/packages/@aws-cdk/aws-inspector/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-inspector/.npmignore +++ b/packages/@aws-cdk/aws-inspector/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-inspector/jest.config.js b/packages/@aws-cdk/aws-inspector/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-inspector/jest.config.js +++ b/packages/@aws-cdk/aws-inspector/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iot/.gitignore b/packages/@aws-cdk/aws-iot/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-iot/.gitignore +++ b/packages/@aws-cdk/aws-iot/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot/.npmignore b/packages/@aws-cdk/aws-iot/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-iot/.npmignore +++ b/packages/@aws-cdk/aws-iot/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot/jest.config.js b/packages/@aws-cdk/aws-iot/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-iot/jest.config.js +++ b/packages/@aws-cdk/aws-iot/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iot1click/.gitignore b/packages/@aws-cdk/aws-iot1click/.gitignore index adcba106db8d1..7bdb507ae2cc7 100644 --- a/packages/@aws-cdk/aws-iot1click/.gitignore +++ b/packages/@aws-cdk/aws-iot1click/.gitignore @@ -14,3 +14,5 @@ tsconfig.json *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot1click/.npmignore b/packages/@aws-cdk/aws-iot1click/.npmignore index ac1c98567f9a5..3dffd1ce79a72 100644 --- a/packages/@aws-cdk/aws-iot1click/.npmignore +++ b/packages/@aws-cdk/aws-iot1click/.npmignore @@ -26,4 +26,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot1click/jest.config.js b/packages/@aws-cdk/aws-iot1click/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-iot1click/jest.config.js +++ b/packages/@aws-cdk/aws-iot1click/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iotanalytics/.gitignore b/packages/@aws-cdk/aws-iotanalytics/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-iotanalytics/.gitignore +++ b/packages/@aws-cdk/aws-iotanalytics/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotanalytics/.npmignore b/packages/@aws-cdk/aws-iotanalytics/.npmignore index b0edf81a01371..ab90672b1d91e 100644 --- a/packages/@aws-cdk/aws-iotanalytics/.npmignore +++ b/packages/@aws-cdk/aws-iotanalytics/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotanalytics/jest.config.js b/packages/@aws-cdk/aws-iotanalytics/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-iotanalytics/jest.config.js +++ b/packages/@aws-cdk/aws-iotanalytics/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iotevents/.gitignore b/packages/@aws-cdk/aws-iotevents/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-iotevents/.gitignore +++ b/packages/@aws-cdk/aws-iotevents/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotevents/.npmignore b/packages/@aws-cdk/aws-iotevents/.npmignore index 2e1e60862c02c..5c003cc4e3040 100644 --- a/packages/@aws-cdk/aws-iotevents/.npmignore +++ b/packages/@aws-cdk/aws-iotevents/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotevents/jest.config.js b/packages/@aws-cdk/aws-iotevents/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-iotevents/jest.config.js +++ b/packages/@aws-cdk/aws-iotevents/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iotthingsgraph/.gitignore b/packages/@aws-cdk/aws-iotthingsgraph/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-iotthingsgraph/.gitignore +++ b/packages/@aws-cdk/aws-iotthingsgraph/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotthingsgraph/.npmignore b/packages/@aws-cdk/aws-iotthingsgraph/.npmignore index 5177fc0435bf1..917201c845418 100644 --- a/packages/@aws-cdk/aws-iotthingsgraph/.npmignore +++ b/packages/@aws-cdk/aws-iotthingsgraph/.npmignore @@ -26,4 +26,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotthingsgraph/jest.config.js b/packages/@aws-cdk/aws-iotthingsgraph/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-iotthingsgraph/jest.config.js +++ b/packages/@aws-cdk/aws-iotthingsgraph/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-kinesis/.gitignore b/packages/@aws-cdk/aws-kinesis/.gitignore index 20017a2b4c57d..117fb096875af 100644 --- a/packages/@aws-cdk/aws-kinesis/.gitignore +++ b/packages/@aws-cdk/aws-kinesis/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesis/.npmignore b/packages/@aws-cdk/aws-kinesis/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-kinesis/.npmignore +++ b/packages/@aws-cdk/aws-kinesis/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesis/jest.config.js b/packages/@aws-cdk/aws-kinesis/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-kinesis/jest.config.js +++ b/packages/@aws-cdk/aws-kinesis/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-kinesis/test/stream.test.ts b/packages/@aws-cdk/aws-kinesis/test/stream.test.ts index 40bbe5ea8097b..b9a5216e1b6a6 100644 --- a/packages/@aws-cdk/aws-kinesis/test/stream.test.ts +++ b/packages/@aws-cdk/aws-kinesis/test/stream.test.ts @@ -4,7 +4,7 @@ import * as kms from '@aws-cdk/aws-kms'; import { App, Duration, Stack } from '@aws-cdk/core'; import { Stream, StreamEncryption } from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ describe('Kinesis data streams', () => { diff --git a/packages/@aws-cdk/aws-kinesisanalytics/.gitignore b/packages/@aws-cdk/aws-kinesisanalytics/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics/.gitignore +++ b/packages/@aws-cdk/aws-kinesisanalytics/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesisanalytics/.npmignore b/packages/@aws-cdk/aws-kinesisanalytics/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics/.npmignore +++ b/packages/@aws-cdk/aws-kinesisanalytics/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesisanalytics/jest.config.js b/packages/@aws-cdk/aws-kinesisanalytics/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics/jest.config.js +++ b/packages/@aws-cdk/aws-kinesisanalytics/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-kinesisfirehose/.gitignore b/packages/@aws-cdk/aws-kinesisfirehose/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/.gitignore +++ b/packages/@aws-cdk/aws-kinesisfirehose/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesisfirehose/.npmignore b/packages/@aws-cdk/aws-kinesisfirehose/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/.npmignore +++ b/packages/@aws-cdk/aws-kinesisfirehose/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesisfirehose/jest.config.js b/packages/@aws-cdk/aws-kinesisfirehose/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/jest.config.js +++ b/packages/@aws-cdk/aws-kinesisfirehose/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-kms/.gitignore b/packages/@aws-cdk/aws-kms/.gitignore index 7fce433df3f45..86fc837df8fca 100644 --- a/packages/@aws-cdk/aws-kms/.gitignore +++ b/packages/@aws-cdk/aws-kms/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kms/.npmignore b/packages/@aws-cdk/aws-kms/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-kms/.npmignore +++ b/packages/@aws-cdk/aws-kms/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kms/lib/alias.ts b/packages/@aws-cdk/aws-kms/lib/alias.ts index 2ba88fdb650ce..b91a7b1187a89 100644 --- a/packages/@aws-cdk/aws-kms/lib/alias.ts +++ b/packages/@aws-cdk/aws-kms/lib/alias.ts @@ -129,7 +129,6 @@ export class Alias extends AliasBase { * @param attrs the properties of the referenced KMS Alias */ public static fromAliasAttributes(scope: Construct, id: string, attrs: AliasAttributes): IAlias { - // tslint:disable-next-line: class-name class _Alias extends AliasBase { public get aliasName() { return attrs.aliasName; } public get aliasTargetKey() { return attrs.aliasTargetKey; } diff --git a/packages/@aws-cdk/aws-kms/test/test.alias.ts b/packages/@aws-cdk/aws-kms/test/test.alias.ts index 33df260bbf8e2..dc931220febaa 100644 --- a/packages/@aws-cdk/aws-kms/test/test.alias.ts +++ b/packages/@aws-cdk/aws-kms/test/test.alias.ts @@ -4,7 +4,7 @@ import { Test } from 'nodeunit'; import { Alias } from '../lib/alias'; import { IKey, Key } from '../lib/key'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'default alias'(test: Test) { diff --git a/packages/@aws-cdk/aws-kms/test/test.key.ts b/packages/@aws-cdk/aws-kms/test/test.key.ts index 7adfd1d052d5c..05ff62d596a67 100644 --- a/packages/@aws-cdk/aws-kms/test/test.key.ts +++ b/packages/@aws-cdk/aws-kms/test/test.key.ts @@ -12,7 +12,7 @@ import { App, CfnOutput, RemovalPolicy, Stack, Tag } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import { Key } from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ const ACTIONS: string[] = [ 'kms:Create*', 'kms:Describe*', diff --git a/packages/@aws-cdk/aws-lakeformation/.gitignore b/packages/@aws-cdk/aws-lakeformation/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-lakeformation/.gitignore +++ b/packages/@aws-cdk/aws-lakeformation/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lakeformation/.npmignore b/packages/@aws-cdk/aws-lakeformation/.npmignore index 2e1e60862c02c..5c003cc4e3040 100644 --- a/packages/@aws-cdk/aws-lakeformation/.npmignore +++ b/packages/@aws-cdk/aws-lakeformation/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lakeformation/jest.config.js b/packages/@aws-cdk/aws-lakeformation/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-lakeformation/jest.config.js +++ b/packages/@aws-cdk/aws-lakeformation/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-lambda-destinations/.gitignore b/packages/@aws-cdk/aws-lambda-destinations/.gitignore index 23a79075f642c..147448f7df4fe 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/.gitignore +++ b/packages/@aws-cdk/aws-lambda-destinations/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-destinations/.npmignore b/packages/@aws-cdk/aws-lambda-destinations/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/.npmignore +++ b/packages/@aws-cdk/aws-lambda-destinations/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-destinations/jest.config.js b/packages/@aws-cdk/aws-lambda-destinations/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/jest.config.js +++ b/packages/@aws-cdk/aws-lambda-destinations/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-lambda-event-sources/.gitignore b/packages/@aws-cdk/aws-lambda-event-sources/.gitignore index 2d2f100c9395d..d0a956699806b 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/.gitignore +++ b/packages/@aws-cdk/aws-lambda-event-sources/.gitignore @@ -15,3 +15,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-event-sources/.npmignore b/packages/@aws-cdk/aws-lambda-event-sources/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/.npmignore +++ b/packages/@aws-cdk/aws-lambda-event-sources/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-event-sources/README.md b/packages/@aws-cdk/aws-lambda-event-sources/README.md index 5680ee27dcf8b..5d1287d0ace7f 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/README.md +++ b/packages/@aws-cdk/aws-lambda-event-sources/README.md @@ -25,7 +25,7 @@ The following code sets up a lambda function with an SQS queue event source - const fn = new lambda.Function(this, 'MyFunction', { /* ... */ }); const queue = new sqs.Queue(this, 'MyQueue'); -const eventSource = lambda.addEventSource(new SqsEventSource(queue); +const eventSource = fn.addEventSource(new SqsEventSource(queue)); const eventSourceId = eventSource.eventSourceId; ``` diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts index 2e78487075fbd..1a2de4c390155 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts @@ -1,5 +1,6 @@ import * as kinesis from '@aws-cdk/aws-kinesis'; import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; import {StreamEventSource, StreamEventSourceProps} from './stream'; export interface KinesisEventSourceProps extends StreamEventSourceProps { @@ -14,9 +15,11 @@ export class KinesisEventSource extends StreamEventSource { constructor(readonly stream: kinesis.IStream, props: KinesisEventSourceProps) { super(props); - if (this.props.batchSize !== undefined && (this.props.batchSize < 1 || this.props.batchSize > 10000)) { - throw new Error(`Maximum batch size must be between 1 and 10000 inclusive (given ${this.props.batchSize})`); - } + this.props.batchSize !== undefined && cdk.withResolved(this.props.batchSize, batchSize => { + if (batchSize < 1 || batchSize > 10000) { + throw new Error(`Maximum batch size must be between 1 and 10000 inclusive (given ${this.props.batchSize})`); + } + }); } public bind(target: lambda.IFunction) { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.expected.json index 135d1ca0514a2..0e57bd7dc85d1 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.expected.json @@ -91,7 +91,7 @@ "Type": "AWS::Lambda::Function", "Properties": { "Code": { - "ZipFile": "exports.handler = async function handler(event) {\n // tslint:disable-next-line:no-console\n console.log('event:', JSON.stringify(event, undefined, 2));\n throw new Error();\n}" + "ZipFile": "exports.handler = async function handler(event) {\n // eslint-disable-next-line no-console\n console.log('event:', JSON.stringify(event, undefined, 2));\n throw new Error();\n}" }, "Handler": "index.handler", "Role": { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.ts index 6a9f0f621d1b5..73ed35b217b81 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.ts @@ -12,7 +12,7 @@ import { KinesisEventSource, SqsDlq } from '../lib'; */ async function handler(event: any) { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.log('event:', JSON.stringify(event, undefined, 2)); throw new Error(); } diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json index bfcb316461be5..a59e90cbe486d 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json @@ -176,7 +176,7 @@ "Properties": { "Description": "AWS CloudFormation handler for \"Custom::S3BucketNotifications\" resources (@aws-cdk/aws-s3)", "Code": { - "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require('https');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require('url');\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration,\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse('FAILED', err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse('SUCCESS');\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // tslint:disable-next-line:max-line-length\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error messge as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: 'PUT',\n headers: {\n 'content-type': '',\n 'content-length': responseBody.length,\n },\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on('error', (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" + "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require('https');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require('url');\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration,\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse('FAILED', err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse('SUCCESS');\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // eslint-disable-next-line max-len\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error messge as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: 'PUT',\n headers: {\n 'content-type': '',\n 'content-length': responseBody.length,\n },\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on('error', (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" }, "Handler": "index.handler", "Role": { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test-function.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test-function.ts index 1328875986981..ab686f17a1185 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test-function.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test-function.ts @@ -11,7 +11,7 @@ export class TestFunction extends lambda.Function { } } -// tslint:disable:no-console +/* eslint-disable no-console */ async function handler(event: any) { console.log('event:', JSON.stringify(event, undefined, 2)); return { event }; diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts index 008a820f295cf..745b016109d30 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts @@ -7,7 +7,7 @@ import { Test } from 'nodeunit'; import * as sources from '../lib'; import { TestFunction } from './test-function'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'sufficiently complex example'(test: Test) { @@ -66,7 +66,7 @@ export = { 'StreamArn', ], }, - 'FunctionName': { + 'FunctionName': { 'Ref': 'Fn9270CBC0', }, 'BatchSize': 100, @@ -102,7 +102,7 @@ export = { 'StreamArn', ], }, - 'FunctionName': { + 'FunctionName': { 'Ref': 'Fn9270CBC0', }, 'BatchSize': 50, @@ -200,7 +200,7 @@ export = { 'StreamArn', ], }, - 'FunctionName': { + 'FunctionName': { 'Ref': 'Fn9270CBC0', }, 'MaximumBatchingWindowInSeconds': 120, @@ -300,7 +300,7 @@ export = { 'StreamArn', ], }, - 'FunctionName': { + 'FunctionName': { 'Ref': 'Fn9270CBC0', }, 'MaximumRetryAttempts': 10, @@ -380,7 +380,7 @@ export = { 'StreamArn', ], }, - 'FunctionName': { + 'FunctionName': { 'Ref': 'Fn9270CBC0', }, 'BisectBatchOnFunctionError': true, @@ -416,7 +416,7 @@ export = { 'StreamArn', ], }, - 'FunctionName': { + 'FunctionName': { 'Ref': 'Fn9270CBC0', }, 'ParallelizationFactor': 5, @@ -496,7 +496,7 @@ export = { 'StreamArn', ], }, - 'FunctionName': { + 'FunctionName': { 'Ref': 'Fn9270CBC0', }, 'MaximumRecordAgeInSeconds': 100, @@ -577,7 +577,7 @@ export = { 'StreamArn', ], }, - 'FunctionName': { + 'FunctionName': { 'Ref': 'Fn9270CBC0', }, 'DestinationConfig': { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.kinesis.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.kinesis.ts index 8a7a69c255567..6fea04f3d9089 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.kinesis.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.kinesis.ts @@ -6,7 +6,7 @@ import { Test } from 'nodeunit'; import * as sources from '../lib'; import { TestFunction } from './test-function'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'sufficiently complex example'(test: Test) { @@ -66,7 +66,7 @@ export = { 'Arn', ], }, - 'FunctionName': { + 'FunctionName': { 'Ref': 'Fn9270CBC0', }, 'BatchSize': 100, @@ -96,7 +96,7 @@ export = { 'Arn', ], }, - 'FunctionName': { + 'FunctionName': { 'Ref': 'Fn9270CBC0', }, 'BatchSize': 50, @@ -136,6 +136,21 @@ export = { test.done(); }, + 'accepts if batch size is a token'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const stream = new kinesis.Stream(stack, 'S'); + + // WHEN + fn.addEventSource(new sources.KinesisEventSource(stream, { + batchSize: cdk.Lazy.numberValue({ produce: () => 10 }), + startingPosition: lambda.StartingPosition.LATEST, + })); + + test.done(); + }, + 'specific maxBatchingWindow'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -156,7 +171,7 @@ export = { 'Arn', ], }, - 'FunctionName': { + 'FunctionName': { 'Ref': 'Fn9270CBC0', }, 'MaximumBatchingWindowInSeconds': 120, diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.s3.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.s3.ts index 321502a62df92..bb7111b0d3a7b 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.s3.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.s3.ts @@ -5,7 +5,7 @@ import { Test } from 'nodeunit'; import * as sources from '../lib'; import { TestFunction } from './test-function'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'sufficiently complex example'(test: Test) { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.sns.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.sns.ts index 4a83b510d2bf1..12224e95b6040 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.sns.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.sns.ts @@ -5,7 +5,7 @@ import { Test } from 'nodeunit'; import * as sources from '../lib'; import { TestFunction } from './test-function'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'sufficiently complex example'(test: Test) { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.sqs.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.sqs.ts index 22abb1243dc33..c2fcf18239b64 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.sqs.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.sqs.ts @@ -5,7 +5,7 @@ import { Test } from 'nodeunit'; import * as sources from '../lib'; import { TestFunction } from './test-function'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'defaults'(test: Test) { diff --git a/packages/@aws-cdk/aws-lambda-nodejs/.gitignore b/packages/@aws-cdk/aws-lambda-nodejs/.gitignore index ce811676a5cb2..5973361931b84 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/.gitignore +++ b/packages/@aws-cdk/aws-lambda-nodejs/.gitignore @@ -18,3 +18,5 @@ nyc.config.js !test/function.test.handler2.js !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-nodejs/.npmignore b/packages/@aws-cdk/aws-lambda-nodejs/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/.npmignore +++ b/packages/@aws-cdk/aws-lambda-nodejs/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index 20b79f7878cdc..d65f6ae08996b 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -52,6 +52,15 @@ new lambda.NodejsFunction(this, 'my-handler', { }); ``` +Use the `buildArgs` prop to pass build arguments when building the bundling image: +```ts +new lambda.NodejsFunction(this, 'my-handler', { + buildArgs: { + HTTPS_PROXY: 'https://127.0.0.1:3001', + }, +}); +``` + ### Configuring Parcel The `NodejsFunction` construct exposes some [Parcel](https://parceljs.org/) options via properties: `minify`, `sourceMaps` and `cacheDir`. diff --git a/packages/@aws-cdk/aws-lambda-nodejs/jest.config.js b/packages/@aws-cdk/aws-lambda-nodejs/jest.config.js index 6371e05b69738..fc310b5014407 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/jest.config.js +++ b/packages/@aws-cdk/aws-lambda-nodejs/jest.config.js @@ -1,4 +1,4 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = { ...baseConfig, coverageThreshold: { diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 063d1b2cbff62..392726c75298d 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -1,7 +1,7 @@ -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 * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; import { PackageJsonManager } from './package-json-manager'; import { findUp } from './util'; @@ -71,6 +71,13 @@ export interface ParcelBaseOptions { * @default - 2.0.0-beta.1 */ readonly parcelVersion?: string; + + /** + * Build arguments to pass when building the bundling image. + * + * @default - no build arguments are passed + */ + readonly buildArgs?: { [key:string] : string }; } /** @@ -105,6 +112,7 @@ export class Bundling { // Bundling image derived from runtime bundling image (lambci) const image = cdk.BundlingDockerImage.fromAsset(path.join(__dirname, '../parcel'), { buildArgs: { + ...options.buildArgs ?? {}, IMAGE: options.runtime.bundlingDockerImage.image, PARCEL_VERSION: options.parcelVersion ?? '2.0.0-beta.1', }, @@ -145,7 +153,16 @@ export class Bundling { // 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' : ''}`; + const parcelCommand = [ + '$(node -p "require.resolve(\'parcel\')")', // Parcel is not globally installed, find its "bin" + 'build', containerEntryPath.replace(/\\/g, '/'), // Always use POSIX paths in the container + '--target', 'cdk-lambda', + '--no-autoinstall', + '--no-scope-hoist', + ...options.cacheDir + ? ['--cache-dir', '/parcel-cache'] + : [], + ].join(' '); let installer = Installer.NPM; let lockfile: string | undefined; diff --git a/packages/@aws-cdk/aws-lambda-nodejs/parcel/Dockerfile b/packages/@aws-cdk/aws-lambda-nodejs/parcel/Dockerfile index 0b91c0df6f600..51f1c720c247e 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/parcel/Dockerfile +++ b/packages/@aws-cdk/aws-lambda-nodejs/parcel/Dockerfile @@ -13,7 +13,8 @@ RUN mkdir /npm-cache && \ RUN npm install --global yarn # Install parcel 2 (fix the version since it's still in beta) +# install at "/" so that node_modules will be in the path for /asset-input ARG PARCEL_VERSION=2.0.0-beta.1 -RUN yarn global add parcel@$PARCEL_VERSION +RUN cd / && npm install 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 aaa1553943f0a..cc7dac2261023 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -1,9 +1,9 @@ -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 * as path from 'path'; +import { Code, Runtime } from '@aws-cdk/aws-lambda'; +import { AssetHashType, BundlingDockerImage } from '@aws-cdk/core'; +import { version as delayVersion } from 'delay/package.json'; import { Bundling } from '../lib/bundling'; import * as util from '../lib/util'; @@ -18,6 +18,7 @@ const findUpMock = jest.spyOn(util, 'findUp').mockImplementation((name: string, } return originalFindUp(name, directory); }); +const fromAssetMock = jest.spyOn(BundlingDockerImage, 'fromAsset'); beforeEach(() => { jest.clearAllMocks(); @@ -44,7 +45,8 @@ test('Parcel bundling', () => { volumes: [{ containerPath: '/parcel-cache', hostPath: '/cache-dir' }], workingDirectory: '/asset-input/folder', command: [ - 'bash', '-c', 'parcel build /asset-input/folder/entry.ts --target cdk-lambda --cache-dir /parcel-cache', + 'bash', '-c', + '$(node -p "require.resolve(\'parcel\')") build /asset-input/folder/entry.ts --target cdk-lambda --no-autoinstall --no-scope-hoist --cache-dir /parcel-cache', ], }), }); @@ -105,7 +107,7 @@ test('Parcel bundling with externals and dependencies', () => { 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', + '$(node -p "require.resolve(\'parcel\')") build /asset-input/folder/entry.ts --target cdk-lambda --no-autoinstall --no-scope-hoist && mv /asset-input/.package.json /asset-output/package.json && cd /asset-output && npm install', ], }), }); @@ -157,3 +159,20 @@ test('Detects yarn.lock', () => { }), }); }); + +test('with build args', () => { + Bundling.parcel({ + entry: '/project/folder/entry.ts', + runtime: Runtime.NODEJS_12_X, + projectRoot: '/project', + buildArgs: { + HELLO: 'WORLD', + }, + }); + + expect(fromAssetMock).toHaveBeenCalledWith(expect.stringMatching(/parcel$/), expect.objectContaining({ + buildArgs: expect.objectContaining({ + HELLO: 'WORLD', + }), + })); +}); 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 index 31abbb04b4cfe..3cadcf1746f65 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/dependencies.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/dependencies.ts @@ -1,4 +1,4 @@ -// tslint:disable:no-console +/* eslint-disable no-console */ import { S3 } from 'aws-sdk'; // eslint-disable-line import/no-extraneous-dependencies import * as delay from 'delay'; diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/ts-handler.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/ts-handler.ts index 5bfa54ae0f09f..94da6641cb2bd 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/ts-handler.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/ts-handler.ts @@ -1,5 +1,5 @@ import { mult } from './util'; export async function handler(): Promise { - console.log(mult(3, 4)); // tslint:disable-line no-console + console.log(mult(3, 4)); // eslint-disable-line no-console } 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 index d67505788c16c..41a491bb88ede 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/integ.dependencies.expected.json +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.dependencies.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersf3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4S3BucketC81DD688" + "Ref": "AssetParametersebe4053f51756bfe12e0de8e07d4b67c2c2a4346090e1ad12622987dabe996b2S3BucketB430E8D1" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4S3VersionKeyDA9CBF67" + "Ref": "AssetParametersebe4053f51756bfe12e0de8e07d4b67c2c2a4346090e1ad12622987dabe996b2S3VersionKeyF30AC4DF" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4S3VersionKeyDA9CBF67" + "Ref": "AssetParametersebe4053f51756bfe12e0de8e07d4b67c2c2a4346090e1ad12622987dabe996b2S3VersionKeyF30AC4DF" } ] } @@ -87,17 +87,17 @@ } }, "Parameters": { - "AssetParametersf3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4S3BucketC81DD688": { + "AssetParametersebe4053f51756bfe12e0de8e07d4b67c2c2a4346090e1ad12622987dabe996b2S3BucketB430E8D1": { "Type": "String", - "Description": "S3 bucket for asset \"f3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4\"" + "Description": "S3 bucket for asset \"ebe4053f51756bfe12e0de8e07d4b67c2c2a4346090e1ad12622987dabe996b2\"" }, - "AssetParametersf3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4S3VersionKeyDA9CBF67": { + "AssetParametersebe4053f51756bfe12e0de8e07d4b67c2c2a4346090e1ad12622987dabe996b2S3VersionKeyF30AC4DF": { "Type": "String", - "Description": "S3 key for asset version \"f3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4\"" + "Description": "S3 key for asset version \"ebe4053f51756bfe12e0de8e07d4b67c2c2a4346090e1ad12622987dabe996b2\"" }, - "AssetParametersf3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4ArtifactHash0E6684C0": { + "AssetParametersebe4053f51756bfe12e0de8e07d4b67c2c2a4346090e1ad12622987dabe996b2ArtifactHash6E38BF0B": { "Type": "String", - "Description": "Artifact hash for asset \"f3a8dacfae15c18a4397faeaae668d7170beb89acf2fd97e47f260f73587bde4\"" + "Description": "Artifact hash for asset \"ebe4053f51756bfe12e0de8e07d4b67c2c2a4346090e1ad12622987dabe996b2\"" } } } \ No newline at end of file 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 b88a7e8503886..ae90b68067c4b 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": "AssetParameters9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88S3BucketD344F833" + "Ref": "AssetParameters12f1d42878e237685b5a4cb717404fa08bf4aa659ccb573c7916f7e818ffc091S3Bucket3A595CE7" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88S3VersionKeyEB3332E0" + "Ref": "AssetParameters12f1d42878e237685b5a4cb717404fa08bf4aa659ccb573c7916f7e818ffc091S3VersionKey708CAAF7" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88S3VersionKeyEB3332E0" + "Ref": "AssetParameters12f1d42878e237685b5a4cb717404fa08bf4aa659ccb573c7916f7e818ffc091S3VersionKey708CAAF7" } ] } @@ -121,7 +121,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0S3Bucket3B0DF548" + "Ref": "AssetParameters5383ed2a06cec74db0261318b2a3d648f26aa1a48e5e34ff40fb218e9eaf9941S3BucketB102419B" }, "S3Key": { "Fn::Join": [ @@ -134,7 +134,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0S3VersionKey1D84CC0E" + "Ref": "AssetParameters5383ed2a06cec74db0261318b2a3d648f26aa1a48e5e34ff40fb218e9eaf9941S3VersionKey468D1E85" } ] } @@ -147,7 +147,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0S3VersionKey1D84CC0E" + "Ref": "AssetParameters5383ed2a06cec74db0261318b2a3d648f26aa1a48e5e34ff40fb218e9eaf9941S3VersionKey468D1E85" } ] } @@ -172,29 +172,29 @@ } }, "Parameters": { - "AssetParameters9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88S3BucketD344F833": { + "AssetParameters12f1d42878e237685b5a4cb717404fa08bf4aa659ccb573c7916f7e818ffc091S3Bucket3A595CE7": { "Type": "String", - "Description": "S3 bucket for asset \"9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88\"" + "Description": "S3 bucket for asset \"12f1d42878e237685b5a4cb717404fa08bf4aa659ccb573c7916f7e818ffc091\"" }, - "AssetParameters9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88S3VersionKeyEB3332E0": { + "AssetParameters12f1d42878e237685b5a4cb717404fa08bf4aa659ccb573c7916f7e818ffc091S3VersionKey708CAAF7": { "Type": "String", - "Description": "S3 key for asset version \"9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88\"" + "Description": "S3 key for asset version \"12f1d42878e237685b5a4cb717404fa08bf4aa659ccb573c7916f7e818ffc091\"" }, - "AssetParameters9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88ArtifactHash079EA103": { + "AssetParameters12f1d42878e237685b5a4cb717404fa08bf4aa659ccb573c7916f7e818ffc091ArtifactHashECEF4AD0": { "Type": "String", - "Description": "Artifact hash for asset \"9003cb217f859844be0ac9b0b22c7eb387ac397607197d29b624cbf8dc872a88\"" + "Description": "Artifact hash for asset \"12f1d42878e237685b5a4cb717404fa08bf4aa659ccb573c7916f7e818ffc091\"" }, - "AssetParameters0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0S3Bucket3B0DF548": { + "AssetParameters5383ed2a06cec74db0261318b2a3d648f26aa1a48e5e34ff40fb218e9eaf9941S3BucketB102419B": { "Type": "String", - "Description": "S3 bucket for asset \"0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0\"" + "Description": "S3 bucket for asset \"5383ed2a06cec74db0261318b2a3d648f26aa1a48e5e34ff40fb218e9eaf9941\"" }, - "AssetParameters0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0S3VersionKey1D84CC0E": { + "AssetParameters5383ed2a06cec74db0261318b2a3d648f26aa1a48e5e34ff40fb218e9eaf9941S3VersionKey468D1E85": { "Type": "String", - "Description": "S3 key for asset version \"0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0\"" + "Description": "S3 key for asset version \"5383ed2a06cec74db0261318b2a3d648f26aa1a48e5e34ff40fb218e9eaf9941\"" }, - "AssetParameters0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0ArtifactHash7545DAEB": { + "AssetParameters5383ed2a06cec74db0261318b2a3d648f26aa1a48e5e34ff40fb218e9eaf9941ArtifactHashF56A9434": { "Type": "String", - "Description": "Artifact hash for asset \"0a35a944532d281b38e1ee670488bc40e0c813140eb0a41371db4c5a32202be0\"" + "Description": "Artifact hash for asset \"5383ed2a06cec74db0261318b2a3d648f26aa1a48e5e34ff40fb218e9eaf9941\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/.gitignore b/packages/@aws-cdk/aws-lambda/.gitignore index 2d2f100c9395d..d0a956699806b 100644 --- a/packages/@aws-cdk/aws-lambda/.gitignore +++ b/packages/@aws-cdk/aws-lambda/.gitignore @@ -15,3 +15,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/.npmignore b/packages/@aws-cdk/aws-lambda/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-lambda/.npmignore +++ b/packages/@aws-cdk/aws-lambda/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index 4ca6c84b320a2..7797c913ec40f 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -240,6 +240,22 @@ const fn = new lambda.Function(this, 'MyFunction', { See [the AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html) to learn more about AWS Lambda's X-Ray support. +### Lambda with Profiling + +```ts +import * as lambda from '@aws-cdk/aws-lambda'; + +const fn = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = function(event, ctx, cb) { return cb(null, "hi"); }'), + profiling: true +}); +``` + +See [the AWS documentation](https://docs.aws.amazon.com/codeguru/latest/profiler-ug/setting-up-lambda.html) +to learn more about AWS Lambda's Profiling support. + ### Lambda with Reserved Concurrent Executions ```ts diff --git a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts index 556b0cc732af1..6fc56a2e1d325 100644 --- a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts +++ b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts @@ -152,13 +152,18 @@ export class EventSourceMapping extends cdk.Resource implements IEventSourceMapp throw new Error('maxRecordAge must be between 60 seconds and 7 days inclusive'); } - if (props.retryAttempts && (props.retryAttempts < 0 || props.retryAttempts > 10000)) { - throw new Error(`retryAttempts must be between 0 and 10000 inclusive, got ${props.retryAttempts}`); - } + props.retryAttempts !== undefined && cdk.withResolved(props.retryAttempts, (attempts) => { + if (attempts < 0 || attempts > 10000) { + throw new Error(`retryAttempts must be between 0 and 10000 inclusive, got ${attempts}`); + } + }); + + props.parallelizationFactor !== undefined && cdk.withResolved(props.parallelizationFactor, (factor) => { + if (factor < 1 || factor > 10) { + throw new Error(`parallelizationFactor must be between 1 and 10 inclusive, got ${factor}`); + } + }); - if ((props.parallelizationFactor || props.parallelizationFactor === 0) && (props.parallelizationFactor < 1 || props.parallelizationFactor > 10)) { - throw new Error(`parallelizationFactor must be between 1 and 10 inclusive, got ${props.parallelizationFactor}`); - } let destinationConfig; diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index b9a2e6b4ef166..39ad7d665fcb7 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -237,7 +237,7 @@ export abstract class FunctionBase extends Resource implements IFunction { */ public get connections(): ec2.Connections { if (!this._connections) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error('Only VPC-associated Lambda Functions have security groups to manage. Supply the "vpc" parameter when creating the Lambda, or "securityGroupId" when importing it.'); } return this._connections; diff --git a/packages/@aws-cdk/aws-lambda/lib/function-hash.ts b/packages/@aws-cdk/aws-lambda/lib/function-hash.ts index f37364c054e8b..fcc7cb4b75e2f 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-hash.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-hash.ts @@ -1,5 +1,5 @@ -import { CfnResource, Stack } from '@aws-cdk/core'; import * as crypto from 'crypto'; +import { CfnResource, Stack } from '@aws-cdk/core'; import { Function as LambdaFunction } from './function'; export function calculateFunctionHash(fn: LambdaFunction) { diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index dbd77a2a9d793..d450e85ca913a 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -1,4 +1,5 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import { IProfilingGroup, ProfilingGroup } from '@aws-cdk/aws-codeguruprofiler'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; @@ -190,6 +191,22 @@ export interface FunctionOptions extends EventInvokeConfigOptions { */ readonly tracing?: Tracing; + /** + * Enable profiling. + * @see https://docs.aws.amazon.com/codeguru/latest/profiler-ug/setting-up-lambda.html + * + * @default - No profiling. + */ + readonly profiling?: boolean; + + /** + * Profiling Group. + * @see https://docs.aws.amazon.com/codeguru/latest/profiler-ug/setting-up-lambda.html + * + * @default - A new profiling group will be created if `profiling` is set. + */ + readonly profilingGroup?: IProfilingGroup; + /** * A list of layers to add to the function's execution environment. You can configure your Lambda function to pull in * additional code during initialization in the form of layers. Layers are packages of libraries or other dependencies @@ -485,8 +502,6 @@ export class Function extends FunctionBase { physicalName: props.functionName, }); - this.environment = props.environment || {}; - const managedPolicies = new Array(); // the arn is in the form of - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole @@ -520,6 +535,30 @@ export class Function extends FunctionBase { const code = props.code.bind(this); verifyCodeConfig(code, props.runtime); + let profilingGroupEnvironmentVariables = {}; + if (props.profilingGroup && props.profiling !== false) { + this.validateProfilingEnvironmentVariables(props); + props.profilingGroup.grantPublish(this.role); + profilingGroupEnvironmentVariables = { + AWS_CODEGURU_PROFILER_GROUP_ARN: Stack.of(scope).formatArn({ + service: 'codeguru-profiler', + resource: 'profilingGroup', + resourceName: props.profilingGroup.profilingGroupName, + }), + AWS_CODEGURU_PROFILER_ENABLED: 'TRUE', + }; + } else if (props.profiling) { + this.validateProfilingEnvironmentVariables(props); + const profilingGroup = new ProfilingGroup(this, 'ProfilingGroup'); + profilingGroup.grantPublish(this.role); + profilingGroupEnvironmentVariables = { + AWS_CODEGURU_PROFILER_GROUP_ARN: profilingGroup.profilingGroupArn, + AWS_CODEGURU_PROFILER_ENABLED: 'TRUE', + }; + } + + this.environment = { ...profilingGroupEnvironmentVariables, ...(props.environment || {}) }; + this.deadLetterQueue = this.buildDeadLetterQueue(props); const resource: CfnFunction = new CfnFunction(this, 'Resource', { @@ -852,6 +891,12 @@ export class Function extends FunctionBase { mode: props.tracing, }; } + + private validateProfilingEnvironmentVariables(props: FunctionProps) { + if (props.environment && (props.environment.AWS_CODEGURU_PROFILER_GROUP_ARN || props.environment.AWS_CODEGURU_PROFILER_ENABLED)) { + throw new Error('AWS_CODEGURU_PROFILER_GROUP_ARN and AWS_CODEGURU_PROFILER_ENABLED must not be set when profiling options enabled'); + } + } } /** diff --git a/packages/@aws-cdk/aws-lambda/lib/log-retention-provider/index.ts b/packages/@aws-cdk/aws-lambda/lib/log-retention-provider/index.ts index 16d44a5fd83de..f32a6e426aea6 100644 --- a/packages/@aws-cdk/aws-lambda/lib/log-retention-provider/index.ts +++ b/packages/@aws-cdk/aws-lambda/lib/log-retention-provider/index.ts @@ -1,4 +1,4 @@ -// tslint:disable:no-console +/* eslint-disable no-console */ // eslint-disable-next-line import/no-extraneous-dependencies import * as AWS from 'aws-sdk'; diff --git a/packages/@aws-cdk/aws-lambda/lib/log-retention.ts b/packages/@aws-cdk/aws-lambda/lib/log-retention.ts index 6c5fec2da7cd9..8e64262143a10 100644 --- a/packages/@aws-cdk/aws-lambda/lib/log-retention.ts +++ b/packages/@aws-cdk/aws-lambda/lib/log-retention.ts @@ -1,7 +1,7 @@ +import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; import * as cdk from '@aws-cdk/core'; -import * as path from 'path'; import { Code } from './code'; import { Runtime } from './runtime'; import { SingletonFunction } from './singleton-lambda'; diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index a2da45a45e1a7..d62071663eb02 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -71,12 +71,12 @@ "@types/lodash": "^4.14.157", "@types/nodeunit": "^0.0.31", "@types/sinon": "^9.0.4", - "aws-sdk": "^2.711.0", + "aws-sdk": "^2.715.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", + "lodash": "^4.17.19", "nock": "^13.0.2", "nodeunit": "^0.11.3", "pkglint": "0.0.0", @@ -84,6 +84,7 @@ }, "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", + "@aws-cdk/aws-codeguruprofiler": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-efs": "0.0.0", "@aws-cdk/aws-events": "0.0.0", @@ -99,6 +100,7 @@ "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", + "@aws-cdk/aws-codeguruprofiler": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-efs": "0.0.0", "@aws-cdk/aws-events": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda/test/inline.expected.json b/packages/@aws-cdk/aws-lambda/test/inline.expected.json deleted file mode 100644 index fd1c0abac77c2..0000000000000 --- a/packages/@aws-cdk/aws-lambda/test/inline.expected.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "Resources": { - "MyBucketF68F3FF0": { - "Type": "AWS::S3::Bucket" - }, - "MyLambdaServiceRole4539ECB6": { - "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"]]} - - ] - } - }, - "MyLambdaServiceRoleDefaultPolicy5BBC6F68": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject*", - "s3:Abort*" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "MyBucketF68F3FF0", - "Arn" - ] - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "MyBucketF68F3FF0", - "Arn" - ] - }, - "/", - "*" - ] - ] - } - ] - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "MyLambdaServiceRoleDefaultPolicy5BBC6F68", - "Roles": [ - { - "Ref": "MyLambdaServiceRole4539ECB6" - } - ] - } - }, - "MyLambdaCCE802FB": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "exports.handler = (_event, _context, callback) => {\n // tslint:disable:no-console\n const S3 = require('aws-sdk').S3;\n const client = new S3();\n const bucketName = process.env.BUCKET_NAME;\n client.upload({ Bucket: bucketName, Key: 'myfile.txt', Body: 'Hello, world' }, (err, data) => {\n if (err) {\n return callback(err);\n }\n console.log(data);\n return callback();\n });\n }" - }, - "Environment": { - "Variables": { - "BUCKET_NAME": { - "Ref": "MyBucketF68F3FF0" - } - } - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "MyLambdaServiceRole4539ECB6", - "Arn" - ] - }, - "Runtime": "nodejs6.10", - "Timeout": 30 - }, - "DependsOn": [ - "MyLambdaServiceRole4539ECB6", - "MyLambdaServiceRoleDefaultPolicy5BBC6F68" - ] - } - } -} diff --git a/packages/@aws-cdk/aws-lambda/test/integ.assets.file.ts b/packages/@aws-cdk/aws-lambda/test/integ.assets.file.ts index d8dba4b219acd..284be833fc02d 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.assets.file.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.assets.file.ts @@ -1,5 +1,5 @@ -import * as cdk from '@aws-cdk/core'; import * as path from 'path'; +import * as cdk from '@aws-cdk/core'; import * as lambda from '../lib'; class TestStack extends cdk.Stack { diff --git a/packages/@aws-cdk/aws-lambda/test/integ.assets.lit.ts b/packages/@aws-cdk/aws-lambda/test/integ.assets.lit.ts index 5a4f23fd980fb..5efcc22f635fa 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.assets.lit.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.assets.lit.ts @@ -1,5 +1,5 @@ -import * as cdk from '@aws-cdk/core'; import * as path from 'path'; +import * as cdk from '@aws-cdk/core'; import * as lambda from '../lib'; class TestStack extends cdk.Stack { diff --git a/packages/@aws-cdk/aws-lambda/test/integ.bundling.ts b/packages/@aws-cdk/aws-lambda/test/integ.bundling.ts index 6c1715bd05747..200ca72f204b6 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.bundling.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.bundling.ts @@ -1,5 +1,5 @@ -import { App, CfnOutput, Construct, Stack, StackProps } from '@aws-cdk/core'; import * as path from 'path'; +import { App, CfnOutput, Construct, Stack, StackProps } from '@aws-cdk/core'; import * as lambda from '../lib'; /** diff --git a/packages/@aws-cdk/aws-lambda/test/integ.current-version.ts b/packages/@aws-cdk/aws-lambda/test/integ.current-version.ts index 672664da22390..a6f4e4dae32c0 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.current-version.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.current-version.ts @@ -1,5 +1,5 @@ -import { App, RemovalPolicy, Stack } from '@aws-cdk/core'; import * as path from 'path'; +import { App, RemovalPolicy, Stack } from '@aws-cdk/core'; import * as lambda from '../lib'; class TestStack extends Stack { diff --git a/packages/@aws-cdk/aws-lambda/test/integ.layer-version.lit.ts b/packages/@aws-cdk/aws-lambda/test/integ.layer-version.lit.ts index 81de040df8f53..d26722365c72e 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.layer-version.lit.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.layer-version.lit.ts @@ -1,5 +1,5 @@ -import * as cdk from '@aws-cdk/core'; import * as path from 'path'; +import * as cdk from '@aws-cdk/core'; import * as lambda from '../lib'; const app = new cdk.App(); diff --git a/packages/@aws-cdk/aws-lambda/test/integ.log-retention.ts b/packages/@aws-cdk/aws-lambda/test/integ.log-retention.ts index fdfe73de0dd9f..7b4901f651252 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.log-retention.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.log-retention.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as logs from '@aws-cdk/aws-logs'; import * as cdk from '@aws-cdk/core'; import * as lambda from '../lib'; diff --git a/packages/@aws-cdk/aws-lambda/test/test.code.ts b/packages/@aws-cdk/aws-lambda/test/test.code.ts index 98ac8e0364cac..354771c697a0c 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.code.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.code.ts @@ -1,11 +1,11 @@ +import * as path from 'path'; import { expect, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Test } from 'nodeunit'; -import * as path from 'path'; import * as lambda from '../lib'; -// tslint:disable:no-string-literal +/* eslint-disable dot-notation */ export = { 'lambda.Code.fromInline': { diff --git a/packages/@aws-cdk/aws-lambda/test/test.event-source-mapping.ts b/packages/@aws-cdk/aws-lambda/test/test.event-source-mapping.ts index 942c7fae8b337..4de8108b609d2 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.event-source-mapping.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.event-source-mapping.ts @@ -103,6 +103,22 @@ export = { test.done(); }, + 'accepts if retryAttempts is a token'(test: Test) { + const stack = new cdk.Stack(); + const fn = new Function(stack, 'fn', { + handler: 'index.handler', + code: Code.fromInline('exports.handler = ${handler.toString()}'), + runtime: Runtime.NODEJS_10_X, + }); + + new EventSourceMapping(stack, 'test', { + target: fn, + eventSourceArn: '', + retryAttempts: cdk.Lazy.numberValue({ produce: () => 100 }), + }); + + test.done(); + }, 'throws if parallelizationFactor is below 1'(test: Test) { const stack = new cdk.Stack(); const fn = new Function(stack, 'fn', { @@ -144,6 +160,23 @@ export = { test.done(); }, + 'accepts if parallelizationFactor is a token'(test: Test) { + const stack = new cdk.Stack(); + const fn = new Function(stack, 'fn', { + handler: 'index.handler', + code: Code.fromInline('exports.handler = ${handler.toString()}'), + runtime: Runtime.NODEJS_10_X, + }); + + new EventSourceMapping(stack, 'test', { + target: fn, + eventSourceArn: '', + parallelizationFactor: cdk.Lazy.numberValue({ produce: () => 20 }), + }); + + test.done(); + }, + 'import event source mapping'(test: Test) { const stack = new cdk.Stack(undefined, undefined, { stackName: 'test-stack' }); const imported = EventSourceMapping.fromEventSourceMappingId(stack, 'imported', '14e0db71-5d35-4eb5-b481-8945cf9d10c2'); diff --git a/packages/@aws-cdk/aws-lambda/test/test.function-hash.ts b/packages/@aws-cdk/aws-lambda/test/test.function-hash.ts index 4e51df039be96..b17050d43c8d0 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.function-hash.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.function-hash.ts @@ -1,6 +1,6 @@ +import * as path from 'path'; import { CfnOutput, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; -import * as path from 'path'; import * as lambda from '../lib'; import { calculateFunctionHash, trimFromStart } from '../lib/function-hash'; diff --git a/packages/@aws-cdk/aws-lambda/test/test.function.ts b/packages/@aws-cdk/aws-lambda/test/test.function.ts index ee4a003b7ad5b..52cbb1b77a06b 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.function.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.function.ts @@ -1,4 +1,6 @@ +import * as path from 'path'; import { expect, haveOutput, haveResource } from '@aws-cdk/assert'; +import { ProfilingGroup } from '@aws-cdk/aws-codeguruprofiler'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as efs from '@aws-cdk/aws-efs'; import * as logs from '@aws-cdk/aws-logs'; @@ -7,9 +9,10 @@ import * as sqs from '@aws-cdk/aws-sqs'; import * as cdk from '@aws-cdk/core'; import * as _ from 'lodash'; import {Test, testCase} from 'nodeunit'; -import * as path from 'path'; import * as lambda from '../lib'; +/* eslint-disable quote-props */ + export = testCase({ 'add incompatible layer'(test: Test) { // GIVEN @@ -163,13 +166,11 @@ export = testCase({ logRetention: logs.RetentionDays.FIVE_DAYS, }); - // tslint:disable:no-unused-expression // Call logGroup a few times. If more than one instance of LogRetention was created, // the second call will fail on duplicate constructs. fn.logGroup; fn.logGroup; fn.logGroup; - // tslint:enable:no-unused-expression test.done(); }, @@ -184,8 +185,180 @@ export = testCase({ test.done(); }, - 'currentVersion': { + 'default function with CDK created Profiling Group'(test: Test) { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + profiling: true, + }); + expect(stack).to(haveResource('AWS::CodeGuruProfiler::ProfilingGroup', { + ProfilingGroupName: 'MyLambdaProfilingGroupC5B6CCD8', + })); + + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'codeguru-profiler:ConfigureAgent', + 'codeguru-profiler:PostAgentProfile', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': ['MyLambdaProfilingGroupEC6DE32F', 'Arn'], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', + Roles: [ + { + Ref: 'MyLambdaServiceRole4539ECB6', + }, + ], + })); + + expect(stack).to(haveResource('AWS::Lambda::Function', { + Environment: { + Variables: { + AWS_CODEGURU_PROFILER_GROUP_ARN: { 'Fn::GetAtt': ['MyLambdaProfilingGroupEC6DE32F', 'Arn'] }, + AWS_CODEGURU_PROFILER_ENABLED: 'TRUE', + }, + }, + })); + + test.done(); + }, + + 'default function with client provided Profiling Group'(test: Test) { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + profilingGroup: new ProfilingGroup(stack, 'ProfilingGroup'), + }); + + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'codeguru-profiler:ConfigureAgent', + 'codeguru-profiler:PostAgentProfile', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': ['ProfilingGroup26979FD7', 'Arn'], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', + Roles: [ + { + Ref: 'MyLambdaServiceRole4539ECB6', + }, + ], + })); + + expect(stack).to(haveResource('AWS::Lambda::Function', { + Environment: { + Variables: { + AWS_CODEGURU_PROFILER_GROUP_ARN: { + 'Fn::Join': [ + '', + [ + 'arn:', { Ref: 'AWS::Partition' }, ':codeguru-profiler:', { Ref: 'AWS::Region' }, + ':', { Ref: 'AWS::AccountId' }, ':profilingGroup/', { Ref: 'ProfilingGroup26979FD7' }, + ], + ], + }, + AWS_CODEGURU_PROFILER_ENABLED: 'TRUE', + }, + }, + })); + + test.done(); + }, + + 'default function with client provided Profiling Group but profiling set to false'(test: Test) { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + profiling: false, + profilingGroup: new ProfilingGroup(stack, 'ProfilingGroup'), + }); + + expect(stack).notTo(haveResource('AWS::IAM::Policy')); + + expect(stack).notTo(haveResource('AWS::Lambda::Function', { + Environment: { + Variables: { + AWS_CODEGURU_PROFILER_GROUP_ARN: { + 'Fn::Join': [ + '', + [ + 'arn:', { Ref: 'AWS::Partition' }, ':codeguru-profiler:', { Ref: 'AWS::Region' }, + ':', { Ref: 'AWS::AccountId' }, ':profilingGroup/', { Ref: 'ProfilingGroup26979FD7' }, + ], + ], + }, + AWS_CODEGURU_PROFILER_ENABLED: 'TRUE', + }, + }, + })); + + test.done(); + }, + + 'default function with profiling enabled and client provided env vars'(test: Test) { + const stack = new cdk.Stack(); + + test.throws(() => new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + profiling: true, + environment: { + AWS_CODEGURU_PROFILER_GROUP_ARN: 'profiler_group_arn', + AWS_CODEGURU_PROFILER_ENABLED: 'yes', + }, + }), + /AWS_CODEGURU_PROFILER_GROUP_ARN and AWS_CODEGURU_PROFILER_ENABLED must not be set when profiling options enabled/); + + test.done(); + }, + + 'default function with client provided Profiling Group and client provided env vars'(test: Test) { + const stack = new cdk.Stack(); + + test.throws(() => new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + profilingGroup: new ProfilingGroup(stack, 'ProfilingGroup'), + environment: { + AWS_CODEGURU_PROFILER_GROUP_ARN: 'profiler_group_arn', + AWS_CODEGURU_PROFILER_ENABLED: 'yes', + }, + }), + /AWS_CODEGURU_PROFILER_GROUP_ARN and AWS_CODEGURU_PROFILER_ENABLED must not be set when profiling options enabled/); + + test.done(); + }, + + 'currentVersion': { // see test.function-hash.ts for more coverage for this 'logical id of version is based on the function hash'(test: Test) { // GIVEN diff --git a/packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts b/packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts index b442ad67b4cdf..69e0f62e9c668 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts @@ -3,7 +3,7 @@ import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as lambda from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'can import a Lambda version by ARN'(test: Test) { diff --git a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts index 6a697833e4700..2cecb63d05c08 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts @@ -1,3 +1,4 @@ +import * as path from 'path'; import { expect, haveResource, MatchStyle, ResourcePart } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; @@ -5,10 +6,9 @@ import * as logs from '@aws-cdk/aws-logs'; import * as sqs from '@aws-cdk/aws-sqs'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; -import * as path from 'path'; import * as lambda from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'default function'(test: Test) { @@ -40,7 +40,7 @@ export = { }, ManagedPolicyArns: // arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len [{ 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole']] }], }, }, @@ -88,7 +88,7 @@ export = { Version: '2012-10-17', }, ManagedPolicyArns: - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len [{ 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole']] }], }, }, @@ -171,7 +171,7 @@ export = { 'Version': '2012-10-17', }, 'ManagedPolicyArns': - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len [{ 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole']] }], }, }, @@ -1305,7 +1305,7 @@ export = { }, ManagedPolicyArns: // arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len [{ 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole']] }], }, }, diff --git a/packages/@aws-cdk/aws-lambda/test/test.layers.ts b/packages/@aws-cdk/aws-lambda/test/test.layers.ts index 3ec35f172f382..9a1b2664aa268 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.layers.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.layers.ts @@ -1,9 +1,9 @@ -import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import * as path from 'path'; +import { canonicalizeTemplate, expect, haveResource, ResourcePart, SynthUtils } from '@aws-cdk/assert'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Test, testCase } from 'nodeunit'; -import * as path from 'path'; import * as lambda from '../lib'; export = testCase({ @@ -85,9 +85,9 @@ export = testCase({ }); // THEN - expect(stack).to(haveResource('AWS::Lambda::LayerVersion', { + expect(canonicalizeTemplate(SynthUtils.toCloudFormation(stack))).to(haveResource('AWS::Lambda::LayerVersion', { Metadata: { - 'aws:asset:path': 'asset.8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34', + 'aws:asset:path': 'asset.Asset1Hash', 'aws:asset:property': 'Content', }, }, ResourcePart.CompleteDefinition)); diff --git a/packages/@aws-cdk/aws-lambda/test/test.log-retention.ts b/packages/@aws-cdk/aws-lambda/test/test.log-retention.ts index 4374c95518968..b06c6089982ba 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.log-retention.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.log-retention.ts @@ -5,7 +5,7 @@ import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import { LogRetention } from '../lib/log-retention'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'log retention construct'(test: Test) { diff --git a/packages/@aws-cdk/aws-logs-destinations/.gitignore b/packages/@aws-cdk/aws-logs-destinations/.gitignore index 23a79075f642c..147448f7df4fe 100644 --- a/packages/@aws-cdk/aws-logs-destinations/.gitignore +++ b/packages/@aws-cdk/aws-logs-destinations/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-logs-destinations/.npmignore b/packages/@aws-cdk/aws-logs-destinations/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-logs-destinations/.npmignore +++ b/packages/@aws-cdk/aws-logs-destinations/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-logs-destinations/jest.config.js b/packages/@aws-cdk/aws-logs-destinations/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-logs-destinations/jest.config.js +++ b/packages/@aws-cdk/aws-logs-destinations/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-logs/.gitignore b/packages/@aws-cdk/aws-logs/.gitignore index eb70374119bbf..5a7cc52c7eec6 100644 --- a/packages/@aws-cdk/aws-logs/.gitignore +++ b/packages/@aws-cdk/aws-logs/.gitignore @@ -15,3 +15,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-logs/.npmignore b/packages/@aws-cdk/aws-logs/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-logs/.npmignore +++ b/packages/@aws-cdk/aws-logs/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-macie/.gitignore b/packages/@aws-cdk/aws-macie/.gitignore index e9fee23607e76..5aa413b898780 100644 --- a/packages/@aws-cdk/aws-macie/.gitignore +++ b/packages/@aws-cdk/aws-macie/.gitignore @@ -2,7 +2,6 @@ *.js.map *.d.ts tsconfig.json -tslint.json node_modules *.generated.ts dist @@ -17,3 +16,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml diff --git a/packages/@aws-cdk/aws-macie/.npmignore b/packages/@aws-cdk/aws-macie/.npmignore index 2f1ff45adc883..c8874799485a3 100644 --- a/packages/@aws-cdk/aws-macie/.npmignore +++ b/packages/@aws-cdk/aws-macie/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-macie/jest.config.js b/packages/@aws-cdk/aws-macie/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-macie/jest.config.js +++ b/packages/@aws-cdk/aws-macie/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-managedblockchain/.gitignore b/packages/@aws-cdk/aws-managedblockchain/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-managedblockchain/.gitignore +++ b/packages/@aws-cdk/aws-managedblockchain/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-managedblockchain/.npmignore b/packages/@aws-cdk/aws-managedblockchain/.npmignore index 2e1e60862c02c..5c003cc4e3040 100644 --- a/packages/@aws-cdk/aws-managedblockchain/.npmignore +++ b/packages/@aws-cdk/aws-managedblockchain/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-managedblockchain/jest.config.js b/packages/@aws-cdk/aws-managedblockchain/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-managedblockchain/jest.config.js +++ b/packages/@aws-cdk/aws-managedblockchain/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-mediaconvert/.gitignore b/packages/@aws-cdk/aws-mediaconvert/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-mediaconvert/.gitignore +++ b/packages/@aws-cdk/aws-mediaconvert/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-mediaconvert/.npmignore b/packages/@aws-cdk/aws-mediaconvert/.npmignore index 683e3e0847e1f..a7c5b49852b3b 100644 --- a/packages/@aws-cdk/aws-mediaconvert/.npmignore +++ b/packages/@aws-cdk/aws-mediaconvert/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-mediaconvert/jest.config.js b/packages/@aws-cdk/aws-mediaconvert/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-mediaconvert/jest.config.js +++ b/packages/@aws-cdk/aws-mediaconvert/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-medialive/.gitignore b/packages/@aws-cdk/aws-medialive/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-medialive/.gitignore +++ b/packages/@aws-cdk/aws-medialive/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-medialive/.npmignore b/packages/@aws-cdk/aws-medialive/.npmignore index 2e1e60862c02c..5c003cc4e3040 100644 --- a/packages/@aws-cdk/aws-medialive/.npmignore +++ b/packages/@aws-cdk/aws-medialive/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-medialive/jest.config.js b/packages/@aws-cdk/aws-medialive/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-medialive/jest.config.js +++ b/packages/@aws-cdk/aws-medialive/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-mediastore/.gitignore b/packages/@aws-cdk/aws-mediastore/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-mediastore/.gitignore +++ b/packages/@aws-cdk/aws-mediastore/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-mediastore/.npmignore b/packages/@aws-cdk/aws-mediastore/.npmignore index 2e1e60862c02c..5c003cc4e3040 100644 --- a/packages/@aws-cdk/aws-mediastore/.npmignore +++ b/packages/@aws-cdk/aws-mediastore/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-mediastore/jest.config.js b/packages/@aws-cdk/aws-mediastore/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-mediastore/jest.config.js +++ b/packages/@aws-cdk/aws-mediastore/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-msk/.gitignore b/packages/@aws-cdk/aws-msk/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-msk/.gitignore +++ b/packages/@aws-cdk/aws-msk/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-msk/.npmignore b/packages/@aws-cdk/aws-msk/.npmignore index 5177fc0435bf1..917201c845418 100644 --- a/packages/@aws-cdk/aws-msk/.npmignore +++ b/packages/@aws-cdk/aws-msk/.npmignore @@ -26,4 +26,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-msk/jest.config.js b/packages/@aws-cdk/aws-msk/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-msk/jest.config.js +++ b/packages/@aws-cdk/aws-msk/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-neptune/.gitignore b/packages/@aws-cdk/aws-neptune/.gitignore index adcba106db8d1..7bdb507ae2cc7 100644 --- a/packages/@aws-cdk/aws-neptune/.gitignore +++ b/packages/@aws-cdk/aws-neptune/.gitignore @@ -14,3 +14,5 @@ tsconfig.json *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-neptune/.npmignore b/packages/@aws-cdk/aws-neptune/.npmignore index ac1c98567f9a5..3dffd1ce79a72 100644 --- a/packages/@aws-cdk/aws-neptune/.npmignore +++ b/packages/@aws-cdk/aws-neptune/.npmignore @@ -26,4 +26,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-neptune/jest.config.js b/packages/@aws-cdk/aws-neptune/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-neptune/jest.config.js +++ b/packages/@aws-cdk/aws-neptune/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-networkmanager/.gitignore b/packages/@aws-cdk/aws-networkmanager/.gitignore index d57af28d42320..192200b9c7097 100644 --- a/packages/@aws-cdk/aws-networkmanager/.gitignore +++ b/packages/@aws-cdk/aws-networkmanager/.gitignore @@ -16,3 +16,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-networkmanager/.npmignore b/packages/@aws-cdk/aws-networkmanager/.npmignore index 2f1ff45adc883..c8874799485a3 100644 --- a/packages/@aws-cdk/aws-networkmanager/.npmignore +++ b/packages/@aws-cdk/aws-networkmanager/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-networkmanager/jest.config.js b/packages/@aws-cdk/aws-networkmanager/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-networkmanager/jest.config.js +++ b/packages/@aws-cdk/aws-networkmanager/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-opsworks/.gitignore b/packages/@aws-cdk/aws-opsworks/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-opsworks/.gitignore +++ b/packages/@aws-cdk/aws-opsworks/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-opsworks/.npmignore b/packages/@aws-cdk/aws-opsworks/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-opsworks/.npmignore +++ b/packages/@aws-cdk/aws-opsworks/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-opsworks/jest.config.js b/packages/@aws-cdk/aws-opsworks/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-opsworks/jest.config.js +++ b/packages/@aws-cdk/aws-opsworks/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-opsworkscm/.gitignore b/packages/@aws-cdk/aws-opsworkscm/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-opsworkscm/.gitignore +++ b/packages/@aws-cdk/aws-opsworkscm/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-opsworkscm/.npmignore b/packages/@aws-cdk/aws-opsworkscm/.npmignore index b0edf81a01371..ab90672b1d91e 100644 --- a/packages/@aws-cdk/aws-opsworkscm/.npmignore +++ b/packages/@aws-cdk/aws-opsworkscm/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-opsworkscm/jest.config.js b/packages/@aws-cdk/aws-opsworkscm/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-opsworkscm/jest.config.js +++ b/packages/@aws-cdk/aws-opsworkscm/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-pinpoint/.gitignore b/packages/@aws-cdk/aws-pinpoint/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-pinpoint/.gitignore +++ b/packages/@aws-cdk/aws-pinpoint/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-pinpoint/.npmignore b/packages/@aws-cdk/aws-pinpoint/.npmignore index 5177fc0435bf1..917201c845418 100644 --- a/packages/@aws-cdk/aws-pinpoint/.npmignore +++ b/packages/@aws-cdk/aws-pinpoint/.npmignore @@ -26,4 +26,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-pinpoint/jest.config.js b/packages/@aws-cdk/aws-pinpoint/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-pinpoint/jest.config.js +++ b/packages/@aws-cdk/aws-pinpoint/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-pinpointemail/.gitignore b/packages/@aws-cdk/aws-pinpointemail/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-pinpointemail/.gitignore +++ b/packages/@aws-cdk/aws-pinpointemail/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-pinpointemail/.npmignore b/packages/@aws-cdk/aws-pinpointemail/.npmignore index 2e1e60862c02c..5c003cc4e3040 100644 --- a/packages/@aws-cdk/aws-pinpointemail/.npmignore +++ b/packages/@aws-cdk/aws-pinpointemail/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-pinpointemail/jest.config.js b/packages/@aws-cdk/aws-pinpointemail/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-pinpointemail/jest.config.js +++ b/packages/@aws-cdk/aws-pinpointemail/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-qldb/.gitignore b/packages/@aws-cdk/aws-qldb/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-qldb/.gitignore +++ b/packages/@aws-cdk/aws-qldb/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-qldb/.npmignore b/packages/@aws-cdk/aws-qldb/.npmignore index 2e1e60862c02c..5c003cc4e3040 100644 --- a/packages/@aws-cdk/aws-qldb/.npmignore +++ b/packages/@aws-cdk/aws-qldb/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-qldb/jest.config.js b/packages/@aws-cdk/aws-qldb/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-qldb/jest.config.js +++ b/packages/@aws-cdk/aws-qldb/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-ram/.gitignore b/packages/@aws-cdk/aws-ram/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-ram/.gitignore +++ b/packages/@aws-cdk/aws-ram/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ram/.npmignore b/packages/@aws-cdk/aws-ram/.npmignore index b0edf81a01371..ab90672b1d91e 100644 --- a/packages/@aws-cdk/aws-ram/.npmignore +++ b/packages/@aws-cdk/aws-ram/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ram/jest.config.js b/packages/@aws-cdk/aws-ram/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-ram/jest.config.js +++ b/packages/@aws-cdk/aws-ram/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-rds/.gitignore b/packages/@aws-cdk/aws-rds/.gitignore index 32a10d785e8fb..dcc1dc41e477f 100644 --- a/packages/@aws-cdk/aws-rds/.gitignore +++ b/packages/@aws-cdk/aws-rds/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/.npmignore b/packages/@aws-cdk/aws-rds/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-rds/.npmignore +++ b/packages/@aws-cdk/aws-rds/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index 7b73fdfe92373..b6ba9cb161b71 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -1,4 +1,5 @@ ## Amazon Relational Database Service Construct Library + --- @@ -6,66 +7,108 @@ > 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) +![cdk-constructs: Developer Preview](https://img.shields.io/badge/cdk--constructs-developer--preview-informational.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. +> The APIs of higher level constructs in this module are in **developer preview** before they become stable. We will only make breaking changes to address unforeseen API issues. Therefore, these APIs are not subject to [Semantic Versioning](https://semver.org/), and breaking changes will be announced in 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. --- -### Starting a Clustered Database +```typescript +import * as rds from '@aws-cdk/aws-rds'; +``` + +### Starting a clustered database To set up a clustered database (like Aurora), define a `DatabaseCluster`. You must always launch a database in a VPC. Use the `vpcSubnets` attribute to control whether your instances will be launched privately or publicly: ```ts -const cluster = new DatabaseCluster(this, 'Database', { - engine: DatabaseClusterEngine.AURORA, - masterUser: { - username: 'clusteradmin' +const cluster = new rds.DatabaseCluster(this, 'Database', { + engine: rds.DatabaseClusterEngine.AURORA, + masterUser: { + username: 'clusteradmin' + }, + instanceProps: { + // optional, defaults to t3.medium + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE, }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE, - }, - vpc - } + vpc, + }, }); ``` + +To use a specific version of the engine +(which is recommended, in order to avoid surprise updates when RDS add support for a newer version of the engine), +use the static factory methods on `DatabaseClusterEngine`: + +```typescript +new rds.DatabaseCluster(this, 'Database', { + engine: rds.DatabaseClusterEngine.aurora({ + version: rds.AuroraEngineVersion.VER_1_17_9, // different version class for each engine type + }, + ... +}) +``` + +If there isn't a constant for the exact version you want to use, +all of the `Version` classes have a static `of` method that can be used to create an arbitrary version. + By default, the master password will be generated and stored in AWS Secrets Manager with auto-generated description. Your cluster will be empty by default. To add a default database upon construction, specify the `defaultDatabaseName` attribute. -### Starting an Instance Database +### Starting an instance database + To set up a instance database, define a `DatabaseInstance`. You must always launch a database in a VPC. Use the `vpcSubnets` attribute to control whether your instances will be launched privately or publicly: ```ts -const instance = new DatabaseInstance(stack, 'Instance', { - engine: rds.DatabaseInstanceEngine.ORACLE_SE1, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'syscdk', - vpc +const instance = new rds.DatabaseInstance(this, 'Instance', { + engine: rds.DatabaseInstanceEngine.ORACLE_SE1, + // optional, defaults to m5.large + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + masterUsername: 'syscdk', + vpc, }); ``` + By default, the master password will be generated and stored in AWS Secrets Manager. +To use a specific version of the engine +(which is recommended, in order to avoid surprise updates when RDS add support for a newer version of the engine), +use the static factory methods on `DatabaseInstanceEngine`: + +```typescript +const instance = new rds.DatabaseInstance(this, 'Instance', { + engine: rds.DatabaseInstanceEngine.oracleSe2({ + version: rds.OracleEngineVersion.VER_19, // different version class for each engine type + }), + ... +}); +``` + +If there isn't a constant for the exact version you want to use, +all of the `Version` classes have a static `of` method that can be used to create an arbitrary version. + To use the storage auto scaling option of RDS you can specify the maximum allocated storage. This is the upper limit to which RDS can automatically scale the storage. More info can be found [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_PIOPS.StorageTypes.html#USER_PIOPS.Autoscaling) Example for max storage configuration: ```ts -const instance = new DatabaseInstance(stack, 'Instance', { - engine: rds.DatabaseInstanceEngine.ORACLE_SE1, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'syscdk', - vpc, - maxAllocatedStorage: 200 +const instance = new rds.DatabaseInstance(this, 'Instance', { + engine: rds.DatabaseInstanceEngine.ORACLE_SE1, + // optional, defaults to m5.large + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + masterUsername: 'syscdk', + vpc, + maxAllocatedStorage: 200, }); ``` @@ -73,17 +116,18 @@ Use `DatabaseInstanceFromSnapshot` and `DatabaseInstanceReadReplica` to create a a source database respectively: ```ts -new DatabaseInstanceFromSnapshot(stack, 'Instance', { - snapshotIdentifier: 'my-snapshot', - engine: rds.DatabaseInstanceEngine.POSTGRES, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), - vpc +new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { + snapshotIdentifier: 'my-snapshot', + engine: rds.DatabaseInstanceEngine.POSTGRES, + // optional, defaults to m5.large + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + vpc, }); -new DatabaseInstanceReadReplica(stack, 'ReadReplica', { - sourceDatabaseInstance: sourceInstance, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), - vpc +new rds.DatabaseInstanceReadReplica(stack, 'ReadReplica', { + sourceDatabaseInstance: sourceInstance, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + vpc, }); ``` @@ -91,8 +135,8 @@ Creating a "production" Oracle database instance with option and parameter group [example of setting up a production oracle instance](test/integ.instance.lit.ts) - ### Instance events + To define Amazon CloudWatch event rules for database instances, use the `onEvent` method: @@ -122,7 +166,9 @@ const address = instance.instanceEndpoint.socketAddress; // "HOSTNAME:PORT" ``` ### Rotating credentials + When the master password is generated and stored in AWS Secrets Manager, it can be rotated automatically: + ```ts instance.addRotationSingleUser(); // Will rotate automatically after 30 days ``` @@ -130,31 +176,36 @@ instance.addRotationSingleUser(); // Will rotate automatically after 30 days [example of setting up master password rotation for a cluster](test/integ.cluster-rotation.lit.ts) The multi user rotation scheme is also available: + ```ts instance.addRotationMultiUser('MyUser', { - secret: myImportedSecret // This secret must have the `masterarn` key + secret: myImportedSecret, // This secret must have the `masterarn` key }); ``` It's also possible to create user credentials together with the instance/cluster and add rotation: + ```ts const myUserSecret = new rds.DatabaseSecret(this, 'MyUserSecret', { - username: 'myuser' - masterSecret: instance.secret + username: 'myuser', + masterSecret: instance.secret, }); const myUserSecretAttached = myUserSecret.attach(instance); // Adds DB connections information in the secret instance.addRotationMultiUser('MyUser', { // Add rotation using the multi user scheme - secret: myUserSecretAttached + secret: myUserSecretAttached, }); ``` + **Note**: This user must be created manually in the database using the master credentials. The rotation will start as soon as this user exists. See also [@aws-cdk/aws-secretsmanager](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-secretsmanager/README.md) for credentials rotation of existing clusters/instances. ### Metrics + Database instances expose metrics (`cloudwatch.Metric`): + ```ts // The number of database connections in use (average over 5 minutes) const dbConnections = instance.metricDatabaseConnections(); @@ -181,12 +232,14 @@ data into S3](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/postg The following snippet sets up a database cluster with different S3 buckets where the data is imported and exported - ```ts +import * as s3 from '@aws-cdk/aws-s3'; + const importBucket = new s3.Bucket(this, 'importbucket'); const exportBucket = new s3.Bucket(this, 'exportbucket'); -new DatabaseCluster(this, 'dbcluster', { - // ... - s3ImportBuckets: [ importBucket ], - s3ExportBuckets: [ exportBucket ] +new rds.DatabaseCluster(this, 'dbcluster', { + // ... + s3ImportBuckets: [importBucket], + s3ExportBuckets: [exportBucket], }); ``` @@ -205,13 +258,13 @@ import * as secrets from '@aws-cdk/aws-secretsmanager'; const vpc: ec2.IVpc = ...; const securityGroup: ec2.ISecurityGroup = ...; -const secret: secrets.ISecret = ...; +const secrets: secrets.ISecret[] = [...]; const dbInstance: rds.IDatabaseInstance = ...; const proxy = dbInstance.addProxy('proxy', { connectionBorrowTimeout: cdk.Duration.seconds(30), maxConnectionsPercent: 50, - secret, + secrets, vpc, }); ``` diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts new file mode 100644 index 0000000000000..b16a9799d59c0 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts @@ -0,0 +1,479 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; +import * as core from '@aws-cdk/core'; +import { IEngine } from './engine'; +import { EngineVersion } from './engine-version'; +import { IParameterGroup, ParameterGroup } from './parameter-group'; + +/** + * The extra options passed to the {@link IClusterEngine.bindToCluster} method. + */ +export interface ClusterEngineBindOptions { + /** + * The role used for S3 importing. + * + * @default - none + */ + readonly s3ImportRole?: iam.IRole; + + /** + * The role used for S3 exporting. + * + * @default - none + */ + readonly s3ExportRole?: iam.IRole; + + /** + * The customer-provided ParameterGroup. + * + * @default - none + */ + readonly parameterGroup?: IParameterGroup; +} + +/** + * The type returned from the {@link IClusterEngine.bindToCluster} method. + */ +export interface ClusterEngineConfig { + /** + * The ParameterGroup to use for the cluster. + * + * @default - no ParameterGroup will be used + */ + readonly parameterGroup?: IParameterGroup; + + /** + * The port to use for this cluster, + * unless the customer specified the port directly. + * + * @default - use the default port for clusters (3306) + */ + readonly port?: number; +} + +/** + * The interface representing a database cluster (as opposed to instance) engine. + */ +export interface IClusterEngine extends IEngine { + /** The application used by this engine to perform rotation for a single-user scenario. */ + readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; + + /** The application used by this engine to perform rotation for a multi-user scenario. */ + readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; + + /** + * Method called when the engine is used to create a new cluster. + */ + bindToCluster(scope: core.Construct, options: ClusterEngineBindOptions): ClusterEngineConfig; +} + +interface ClusterEngineBaseProps { + readonly engineType: string; + readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; + readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; + readonly defaultPort?: number; + readonly engineVersion?: EngineVersion; +} + +abstract class ClusterEngineBase implements IClusterEngine { + public readonly engineType: string; + public readonly engineVersion?: EngineVersion; + public readonly parameterGroupFamily?: string; + public readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; + public readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; + + private readonly defaultPort?: number; + + constructor(props: ClusterEngineBaseProps) { + this.engineType = props.engineType; + this.singleUserRotationApplication = props.singleUserRotationApplication; + this.multiUserRotationApplication = props.multiUserRotationApplication; + this.defaultPort = props.defaultPort; + this.engineVersion = props.engineVersion; + this.parameterGroupFamily = this.engineVersion ? `${this.engineType}${this.engineVersion.majorVersion}` : undefined; + } + + public bindToCluster(scope: core.Construct, options: ClusterEngineBindOptions): ClusterEngineConfig { + const parameterGroup = options.parameterGroup ?? this.defaultParameterGroup(scope); + return { + parameterGroup, + port: this.defaultPort, + }; + } + + /** + * Return an optional default ParameterGroup, + * possibly an imported one, + * if one wasn't provided by the customer explicitly. + */ + protected abstract defaultParameterGroup(scope: core.Construct): IParameterGroup | undefined; +} + +interface MysqlClusterEngineBaseProps { + readonly engineType: string; + readonly engineVersion?: EngineVersion; + readonly defaultMajorVersion: string; +} + +abstract class MySqlClusterEngineBase extends ClusterEngineBase { + constructor(props: MysqlClusterEngineBaseProps) { + super({ + ...props, + singleUserRotationApplication: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, + multiUserRotationApplication: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, + engineVersion: props.engineVersion ? props.engineVersion : { majorVersion: props.defaultMajorVersion }, + }); + } + + public bindToCluster(scope: core.Construct, options: ClusterEngineBindOptions): ClusterEngineConfig { + const config = super.bindToCluster(scope, options); + const parameterGroup = options.parameterGroup ?? (options.s3ImportRole || options.s3ExportRole + ? new ParameterGroup(scope, 'ClusterParameterGroup', { + engine: this, + }) + : config.parameterGroup); + if (options.s3ImportRole) { + parameterGroup?.addParameter('aurora_load_from_s3_role', options.s3ImportRole.roleArn); + } + if (options.s3ExportRole) { + parameterGroup?.addParameter('aurora_select_into_s3_role', options.s3ExportRole.roleArn); + } + + return { + ...config, + parameterGroup, + }; + } +} + +/** + * The versions for the Aurora cluster engine + * (those returned by {@link DatabaseClusterEngine.aurora}). + */ +export class AuroraEngineVersion { + /** Version "5.6.10a". */ + public static readonly VER_10A = AuroraEngineVersion.builtIn_5_6('10a', false); + /** Version "5.6.mysql_aurora.1.17.9". */ + public static readonly VER_1_17_9 = AuroraEngineVersion.builtIn_5_6('1.17.9'); + /** Version "5.6.mysql_aurora.1.19.0". */ + public static readonly VER_1_19_0 = AuroraEngineVersion.builtIn_5_6('1.19.0'); + /** Version "5.6.mysql_aurora.1.19.1". */ + public static readonly VER_1_19_1 = AuroraEngineVersion.builtIn_5_6('1.19.1'); + /** Version "5.6.mysql_aurora.1.19.2". */ + public static readonly VER_1_19_2 = AuroraEngineVersion.builtIn_5_6('1.19.2'); + /** Version "5.6.mysql_aurora.1.19.5". */ + public static readonly VER_1_19_5 = AuroraEngineVersion.builtIn_5_6('1.19.5'); + /** Version "5.6.mysql_aurora.1.19.6". */ + public static readonly VER_1_19_6 = AuroraEngineVersion.builtIn_5_6('1.19.6'); + /** Version "5.6.mysql_aurora.1.20.0". */ + public static readonly VER_1_20_0 = AuroraEngineVersion.builtIn_5_6('1.20.0'); + /** Version "5.6.mysql_aurora.1.20.1". */ + public static readonly VER_1_20_1 = AuroraEngineVersion.builtIn_5_6('1.20.1'); + /** Version "5.6.mysql_aurora.1.21.0". */ + public static readonly VER_1_21_0 = AuroraEngineVersion.builtIn_5_6('1.21.0'); + /** Version "5.6.mysql_aurora.1.22.0". */ + public static readonly VER_1_22_0 = AuroraEngineVersion.builtIn_5_6('1.22.0'); + /** Version "5.6.mysql_aurora.1.22.1". */ + public static readonly VER_1_22_1 = AuroraEngineVersion.builtIn_5_6('1.22.1'); + /** Version "5.6.mysql_aurora.1.22.1.3". */ + public static readonly VER_1_22_1_3 = AuroraEngineVersion.builtIn_5_6('1.22.1.3'); + /** Version "5.6.mysql_aurora.1.22.2". */ + public static readonly VER_1_22_2 = AuroraEngineVersion.builtIn_5_6('1.22.2'); + + /** + * Create a new AuroraEngineVersion with an arbitrary version. + * + * @param auroraFullVersion the full version string, + * for example "5.6.mysql_aurora.1.78.3.6" + * @param auroraMajorVersion the major version of the engine, + * defaults to "5.6" + */ + public static of(auroraFullVersion: string, auroraMajorVersion?: string): AuroraEngineVersion { + return new AuroraEngineVersion(auroraFullVersion, auroraMajorVersion); + } + + private static builtIn_5_6(minorVersion: string, addStandardPrefix: boolean = true): AuroraEngineVersion { + return new AuroraEngineVersion(`5.6.${addStandardPrefix ? 'mysql_aurora.' : ''}${minorVersion}`); + } + + /** The full version string, for example, "5.6.mysql_aurora.1.78.3.6". */ + public readonly auroraFullVersion: string; + /** The major version of the engine. Currently, it's always "5.6". */ + public readonly auroraMajorVersion: string; + + private constructor(auroraFullVersion: string, auroraMajorVersion: string = '5.6') { + this.auroraFullVersion = auroraFullVersion; + this.auroraMajorVersion = auroraMajorVersion; + } +} + +/** + * Creation properties of the plain Aurora database cluster engine. + * Used in {@link DatabaseClusterEngine.aurora}. + */ +export interface AuroraClusterEngineProps { + /** The version of the Aurora cluster engine. */ + readonly version: AuroraEngineVersion; +} + +class AuroraClusterEngine extends MySqlClusterEngineBase { + constructor(version?: AuroraEngineVersion) { + super({ + engineType: 'aurora', + engineVersion: version + ? { + fullVersion: version.auroraFullVersion, + majorVersion: version.auroraMajorVersion, + } + : undefined, + defaultMajorVersion: '5.6', + }); + } + + protected defaultParameterGroup(_scope: core.Construct): IParameterGroup | undefined { + // the default.aurora5.6 ParameterGroup is actually the default, + // so just return undefined in this case + return undefined; + } +} + +/** + * The versions for the Aurora MySQL cluster engine + * (those returned by {@link DatabaseClusterEngine.auroraMysql}). + */ +export class AuroraMysqlEngineVersion { + /** Version "5.7.12". */ + public static readonly VER_5_7_12 = AuroraMysqlEngineVersion.builtIn_5_7('12', false); + /** Version "5.7.mysql_aurora.2.03.2". */ + public static readonly VER_2_03_2 = AuroraMysqlEngineVersion.builtIn_5_7('2.03.2'); + /** Version "5.7.mysql_aurora.2.03.3". */ + public static readonly VER_2_03_3 = AuroraMysqlEngineVersion.builtIn_5_7('2.03.3'); + /** Version "5.7.mysql_aurora.2.03.4". */ + public static readonly VER_2_03_4 = AuroraMysqlEngineVersion.builtIn_5_7('2.03.4'); + /** Version "5.7.mysql_aurora.2.04.0". */ + public static readonly VER_2_04_0 = AuroraMysqlEngineVersion.builtIn_5_7('2.04.0'); + /** Version "5.7.mysql_aurora.2.04.1". */ + public static readonly VER_2_04_1 = AuroraMysqlEngineVersion.builtIn_5_7('2.04.1'); + /** Version "5.7.mysql_aurora.2.04.2". */ + public static readonly VER_2_04_2 = AuroraMysqlEngineVersion.builtIn_5_7('2.04.2'); + /** Version "5.7.mysql_aurora.2.04.3". */ + public static readonly VER_2_04_3 = AuroraMysqlEngineVersion.builtIn_5_7('2.04.3'); + /** Version "5.7.mysql_aurora.2.04.4". */ + public static readonly VER_2_04_4 = AuroraMysqlEngineVersion.builtIn_5_7('2.04.4'); + /** Version "5.7.mysql_aurora.2.04.5". */ + public static readonly VER_2_04_5 = AuroraMysqlEngineVersion.builtIn_5_7('2.04.5'); + /** Version "5.7.mysql_aurora.2.04.6". */ + public static readonly VER_2_04_6 = AuroraMysqlEngineVersion.builtIn_5_7('2.04.6'); + /** Version "5.7.mysql_aurora.2.04.7". */ + public static readonly VER_2_04_7 = AuroraMysqlEngineVersion.builtIn_5_7('2.04.7'); + /** Version "5.7.mysql_aurora.2.04.8". */ + public static readonly VER_2_04_8 = AuroraMysqlEngineVersion.builtIn_5_7('2.04.8'); + /** Version "5.7.mysql_aurora.2.05.0". */ + public static readonly VER_2_05_0 = AuroraMysqlEngineVersion.builtIn_5_7('2.05.0'); + /** Version "5.7.mysql_aurora.2.06.0". */ + public static readonly VER_2_06_0 = AuroraMysqlEngineVersion.builtIn_5_7('2.06.0'); + /** Version "5.7.mysql_aurora.2.07.0". */ + public static readonly VER_2_07_0 = AuroraMysqlEngineVersion.builtIn_5_7('2.07.0'); + /** Version "5.7.mysql_aurora.2.07.1". */ + public static readonly VER_2_07_1 = AuroraMysqlEngineVersion.builtIn_5_7('2.07.1'); + /** Version "5.7.mysql_aurora.2.07.2". */ + public static readonly VER_2_07_2 = AuroraMysqlEngineVersion.builtIn_5_7('2.07.2'); + /** Version "5.7.mysql_aurora.2.08.0". */ + public static readonly VER_2_08_0 = AuroraMysqlEngineVersion.builtIn_5_7('2.08.0'); + /** Version "5.7.mysql_aurora.2.08.1". */ + public static readonly VER_2_08_1 = AuroraMysqlEngineVersion.builtIn_5_7('2.08.1'); + + /** + * Create a new AuroraMysqlEngineVersion with an arbitrary version. + * + * @param auroraMysqlFullVersion the full version string, + * for example "5.7.mysql_aurora.2.78.3.6" + * @param auroraMysqlMajorVersion the major version of the engine, + * defaults to "5.7" + */ + public static of(auroraMysqlFullVersion: string, auroraMysqlMajorVersion?: string): AuroraMysqlEngineVersion { + return new AuroraMysqlEngineVersion(auroraMysqlFullVersion, auroraMysqlMajorVersion); + } + + private static builtIn_5_7(minorVersion: string, addStandardPrefix: boolean = true): AuroraMysqlEngineVersion { + return new AuroraMysqlEngineVersion(`5.7.${addStandardPrefix ? 'mysql_aurora.' : ''}${minorVersion}`); + } + + /** The full version string, for example, "5.7.mysql_aurora.1.78.3.6". */ + public readonly auroraMysqlFullVersion: string; + /** The major version of the engine. Currently, it's always "5.7". */ + public readonly auroraMysqlMajorVersion: string; + + private constructor(auroraMysqlFullVersion: string, auroraMysqlMajorVersion: string = '5.7') { + this.auroraMysqlFullVersion = auroraMysqlFullVersion; + this.auroraMysqlMajorVersion = auroraMysqlMajorVersion; + } +} + +/** + * Creation properties of the Aurora MySQL database cluster engine. + * Used in {@link DatabaseClusterEngine.auroraMysql}. + */ +export interface AuroraMysqlClusterEngineProps { + /** The version of the Aurora MySQL cluster engine. */ + readonly version: AuroraMysqlEngineVersion; +} + +class AuroraMysqlClusterEngine extends MySqlClusterEngineBase { + constructor(version?: AuroraMysqlEngineVersion) { + super({ + engineType: 'aurora-mysql', + engineVersion: version + ? { + fullVersion: version.auroraMysqlFullVersion, + majorVersion: version.auroraMysqlMajorVersion, + } + : undefined, + defaultMajorVersion: '5.7', + }); + } + + protected defaultParameterGroup(scope: core.Construct): IParameterGroup | undefined { + return ParameterGroup.fromParameterGroupName(scope, 'AuroraMySqlDatabaseClusterEngineDefaultParameterGroup', + `default.${this.parameterGroupFamily}`); + } +} + +/** + * The versions for the Aurora PostgreSQL cluster engine + * (those returned by {@link DatabaseClusterEngine.auroraPostgres}). + */ +export class AuroraPostgresEngineVersion { + /** Version "9.6.8". */ + public static readonly VER_9_6_8 = AuroraPostgresEngineVersion.of('9.6.8', '9.6'); + /** Version "9.6.9". */ + public static readonly VER_9_6_9 = AuroraPostgresEngineVersion.of('9.6.9', '9.6'); + /** Version "9.6.11". */ + public static readonly VER_9_6_11 = AuroraPostgresEngineVersion.of('9.6.11', '9.6'); + /** Version "9.6.12". */ + public static readonly VER_9_6_12 = AuroraPostgresEngineVersion.of('9.6.12', '9.6'); + /** Version "9.6.16". */ + public static readonly VER_9_6_16 = AuroraPostgresEngineVersion.of('9.6.16', '9.6'); + /** Version "9.6.17". */ + public static readonly VER_9_6_17 = AuroraPostgresEngineVersion.of('9.6.17', '9.6'); + /** Version "10.4". */ + public static readonly VER_10_4 = AuroraPostgresEngineVersion.of('10.4', '10'); + /** Version "10.5". */ + public static readonly VER_10_5 = AuroraPostgresEngineVersion.of('10.5', '10'); + /** Version "10.6". */ + public static readonly VER_10_6 = AuroraPostgresEngineVersion.of('10.6', '10'); + /** Version "10.7". */ + public static readonly VER_10_7 = AuroraPostgresEngineVersion.of('10.7', '10'); + /** Version "10.11". */ + public static readonly VER_10_11 = AuroraPostgresEngineVersion.of('10.11', '10'); + /** Version "10.12". */ + public static readonly VER_10_12 = AuroraPostgresEngineVersion.of('10.12', '10'); + /** Version "11.4". */ + public static readonly VER_11_4 = AuroraPostgresEngineVersion.of('11.4', '11'); + /** Version "11.6". */ + public static readonly VER_11_6 = AuroraPostgresEngineVersion.of('11.6', '11'); + /** Version "11.7". */ + public static readonly VER_11_7 = AuroraPostgresEngineVersion.of('11.7', '11'); + + /** + * Create a new AuroraPostgresEngineVersion with an arbitrary version. + * + * @param auroraPostgresFullVersion the full version string, + * for example "9.6.25.1" + * @param auroraPostgresMajorVersion the major version of the engine, + * for example "9.6" + */ + public static of(auroraPostgresFullVersion: string, auroraPostgresMajorVersion: string): AuroraPostgresEngineVersion { + return new AuroraPostgresEngineVersion(auroraPostgresFullVersion, auroraPostgresMajorVersion); + } + + /** The full version string, for example, "9.6.25.1". */ + public readonly auroraPostgresFullVersion: string; + /** The major version of the engine, for example, "9.6". */ + public readonly auroraPostgresMajorVersion: string; + + private constructor(auroraPostgresFullVersion: string, auroraPostgresMajorVersion: string) { + this.auroraPostgresFullVersion = auroraPostgresFullVersion; + this.auroraPostgresMajorVersion = auroraPostgresMajorVersion; + } +} + +/** + * Creation properties of the Aurora PostgreSQL database cluster engine. + * Used in {@link DatabaseClusterEngine.auroraPostgres}. + */ +export interface AuroraPostgresClusterEngineProps { + /** The version of the Aurora PostgreSQL cluster engine. */ + readonly version: AuroraPostgresEngineVersion; +} + +class AuroraPostgresClusterEngine extends ClusterEngineBase { + constructor(version?: AuroraPostgresEngineVersion) { + super({ + engineType: 'aurora-postgresql', + singleUserRotationApplication: secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER, + multiUserRotationApplication: secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_MULTI_USER, + defaultPort: 5432, + engineVersion: version + ? { + fullVersion: version.auroraPostgresFullVersion, + majorVersion: version.auroraPostgresMajorVersion, + } + : undefined, + }); + } + + protected defaultParameterGroup(scope: core.Construct): IParameterGroup | undefined { + if (!this.parameterGroupFamily) { + throw new Error('Could not create a new ParameterGroup for an unversioned aurora-postgresql cluster engine. ' + + 'Please either use a versioned engine, or pass an explicit ParameterGroup when creating the cluster'); + } + return ParameterGroup.fromParameterGroupName(scope, 'AuroraPostgreSqlDatabaseClusterEngineDefaultParameterGroup', + `default.${this.parameterGroupFamily}`); + } +} + +/** + * A database cluster engine. Provides mapping to the serverless application + * used for secret rotation. + */ +export class DatabaseClusterEngine { + /** + * The unversioned 'aurora' cluster engine. + * + * @deprecated using unversioned engines is an availability risk. + * We recommend using versioned engines created using the {@link aurora()} method + */ + public static readonly AURORA: IClusterEngine = new AuroraClusterEngine(); + + /** + * The unversioned 'aurora-msql' cluster engine. + * + * @deprecated using unversioned engines is an availability risk. + * We recommend using versioned engines created using the {@link auroraMysql()} method + */ + public static readonly AURORA_MYSQL: IClusterEngine = new AuroraMysqlClusterEngine(); + + /** + * The unversioned 'aurora-postgresql' cluster engine. + * + * @deprecated using unversioned engines is an availability risk. + * We recommend using versioned engines created using the {@link auroraPostgres()} method + */ + public static readonly AURORA_POSTGRESQL: IClusterEngine = new AuroraPostgresClusterEngine(); + + /** Creates a new plain Aurora database cluster engine. */ + public static aurora(props: AuroraClusterEngineProps): IClusterEngine { + return new AuroraClusterEngine(props.version); + } + + /** Creates a new Aurora MySQL database cluster engine. */ + public static auroraMysql(props: AuroraMysqlClusterEngineProps): IClusterEngine { + return new AuroraMysqlClusterEngine(props.version); + } + + /** Creates a new Aurora PostgreSQL database cluster engine. */ + public static auroraPostgres(props: AuroraPostgresClusterEngineProps): IClusterEngine { + return new AuroraPostgresClusterEngine(props.version); + } +} diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 51392769e64dd..2318fb1d9762e 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -4,11 +4,12 @@ import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import { CfnDeletionPolicy, Construct, Duration, RemovalPolicy, Resource, Token } from '@aws-cdk/core'; +import { IClusterEngine } from './cluster-engine'; import { DatabaseClusterAttributes, IDatabaseCluster } from './cluster-ref'; import { DatabaseSecret } from './database-secret'; import { Endpoint } from './endpoint'; -import { ClusterParameterGroup, IParameterGroup } from './parameter-group'; -import { BackupProps, DatabaseClusterEngine, InstanceProps, Login, RotationMultiUserOptions } from './props'; +import { IParameterGroup } from './parameter-group'; +import { BackupProps, InstanceProps, Login, RotationMultiUserOptions } from './props'; import { DatabaseProxy, DatabaseProxyOptions, ProxyTarget } from './proxy'; import { CfnDBCluster, CfnDBInstance, CfnDBSubnetGroup } from './rds.generated'; @@ -19,14 +20,7 @@ export interface DatabaseClusterProps { /** * What kind of database to start */ - readonly engine: DatabaseClusterEngine; - - /** - * What version of the database to start - * - * @default - The default for the engine is used. - */ - readonly engineVersion?: string; + readonly engine: IClusterEngine; /** * How many replicas/instances to create @@ -404,7 +398,6 @@ export class DatabaseCluster extends DatabaseClusterBase { } } - let clusterParameterGroup = props.parameterGroup; const clusterAssociatedRoles: CfnDBCluster.DBClusterRoleProperty[] = []; if (s3ImportRole || s3ExportRole) { if (s3ImportRole) { @@ -413,40 +406,26 @@ export class DatabaseCluster extends DatabaseClusterBase { if (s3ExportRole) { clusterAssociatedRoles.push({ roleArn: s3ExportRole.roleArn }); } - - // MySQL requires the associated roles to be specified as cluster parameters as well, PostgreSQL does not - if (props.engine === DatabaseClusterEngine.AURORA || props.engine === DatabaseClusterEngine.AURORA_MYSQL) { - if (!clusterParameterGroup) { - const parameterGroupFamily = props.engine.parameterGroupFamily(props.engineVersion); - if (!parameterGroupFamily) { - throw new Error(`No parameter group family found for database engine ${props.engine.name} with version ${props.engineVersion}.` + - 'Failed to set the correct cluster parameters for s3 import and export roles.'); - } - clusterParameterGroup = new ClusterParameterGroup(this, 'ClusterParameterGroup', { - family: parameterGroupFamily, - }); - } - - if (clusterParameterGroup instanceof ClusterParameterGroup) { // ignore imported ClusterParameterGroup - if (s3ImportRole) { - clusterParameterGroup.addParameter('aurora_load_from_s3_role', s3ImportRole.roleArn); - } - if (s3ExportRole) { - clusterParameterGroup.addParameter('aurora_select_into_s3_role', s3ExportRole.roleArn); - } - } - } } + // bind the engine to the Cluster + const clusterEngineBindConfig = props.engine.bindToCluster(this, { + s3ImportRole, + s3ExportRole, + parameterGroup: props.parameterGroup, + }); + const clusterParameterGroup = props.parameterGroup ?? clusterEngineBindConfig.parameterGroup; + const clusterParameterGroupConfig = clusterParameterGroup?.bindToCluster({}); + const cluster = new CfnDBCluster(this, 'Resource', { // Basic - engine: props.engine.name, - engineVersion: props.engineVersion, + engine: props.engine.engineType, + engineVersion: props.engine.engineVersion?.fullVersion, dbClusterIdentifier: props.clusterIdentifier, dbSubnetGroupName: subnetGroup.ref, vpcSecurityGroupIds: securityGroups.map(sg => sg.securityGroupId), - port: props.port, - dbClusterParameterGroupName: clusterParameterGroup && clusterParameterGroup.parameterGroupName, + port: props.port ?? clusterEngineBindConfig.port, + dbClusterParameterGroupName: clusterParameterGroupConfig?.parameterGroupName, associatedRoles: clusterAssociatedRoles.length > 0 ? clusterAssociatedRoles : undefined, // Admin masterUsername: secret ? secret.secretValueFromJson('username').toString() : props.masterUser.username, @@ -504,6 +483,8 @@ export class DatabaseCluster extends DatabaseClusterBase { }); } + const instanceType = props.instanceProps.instanceType ?? ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MEDIUM); + const instanceParameterGroupConfig = props.instanceProps.parameterGroup?.bindToInstance({}); for (let i = 0; i < instanceCount; i++) { const instanceIndex = i + 1; @@ -515,16 +496,16 @@ export class DatabaseCluster extends DatabaseClusterBase { const instance = new CfnDBInstance(this, `Instance${instanceIndex}`, { // Link to cluster - engine: props.engine.name, - engineVersion: props.engineVersion, + engine: props.engine.engineType, + engineVersion: props.engine.engineVersion?.fullVersion, dbClusterIdentifier: cluster.ref, dbInstanceIdentifier: instanceIdentifier, // Instance properties - dbInstanceClass: databaseInstanceType(props.instanceProps.instanceType), + dbInstanceClass: databaseInstanceType(instanceType), publiclyAccessible, // This is already set on the Cluster. Unclear to me whether it should be repeated or not. Better yes. dbSubnetGroupName: subnetGroup.ref, - dbParameterGroupName: props.instanceProps.parameterGroup && props.instanceProps.parameterGroup.parameterGroupName, + dbParameterGroupName: instanceParameterGroupConfig?.parameterGroupName, monitoringInterval: props.monitoringInterval && props.monitoringInterval.toSeconds(), monitoringRoleArn: monitoringRole && monitoringRole.roleArn, }); diff --git a/packages/@aws-cdk/aws-rds/lib/engine-version.ts b/packages/@aws-cdk/aws-rds/lib/engine-version.ts new file mode 100644 index 0000000000000..167f9094b591f --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/engine-version.ts @@ -0,0 +1,23 @@ +/** + * A version of an engine - + * for either a cluster, or instance. + */ +export interface EngineVersion { + /** + * The full version string of the engine, + * for example, "5.6.mysql_aurora.1.22.1". + * It can be undefined, + * which means RDS should use whatever version it deems appropriate for the given engine type. + * + * @default - no version specified + */ + readonly fullVersion?: string; + + /** + * The major version of the engine, + * for example, "5.6". + * Used in specifying the ParameterGroup family + * and OptionGroup version for this engine. + */ + readonly majorVersion: string; +} diff --git a/packages/@aws-cdk/aws-rds/lib/engine.ts b/packages/@aws-cdk/aws-rds/lib/engine.ts new file mode 100644 index 0000000000000..b1ccef4084c87 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/engine.ts @@ -0,0 +1,31 @@ +import { EngineVersion } from './engine-version'; + +/** + * A common interface for database engines. + * Don't implement this interface directly, + * instead implement one of the known sub-interfaces, + * like IClusterEngine and IInstanceEngine. + */ +export interface IEngine { + /** The type of the engine, for example "mysql". */ + readonly engineType: string; + + /** + * The exact version of the engine that is used, + * for example "5.1.42". + * + * @default - use the default version for this engine type + */ + readonly engineVersion?: EngineVersion; + + /** + * The family to use for ParameterGroups using this engine. + * This is usually equal to "", + * but can sometimes be a variation of that. + * You can pass this property when creating new ParameterGroup. + * + * @default - the ParameterGroup family is not known + * (which means the major version of the engine is also not known) + */ + readonly parameterGroupFamily?: string; +} diff --git a/packages/@aws-cdk/aws-rds/lib/index.ts b/packages/@aws-cdk/aws-rds/lib/index.ts index 41489d58fc11d..d03d0ce49bb39 100644 --- a/packages/@aws-cdk/aws-rds/lib/index.ts +++ b/packages/@aws-cdk/aws-rds/lib/index.ts @@ -1,5 +1,9 @@ +export * from './engine'; +export * from './engine-version'; export * from './cluster'; export * from './cluster-ref'; +export * from './cluster-engine'; +export * from './instance-engine'; export * from './props'; export * from './parameter-group'; export * from './database-secret'; diff --git a/packages/@aws-cdk/aws-rds/lib/instance-engine.ts b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts new file mode 100644 index 0000000000000..b2099933e24be --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts @@ -0,0 +1,1123 @@ +import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; +import * as core from '@aws-cdk/core'; +import { IEngine } from './engine'; +import { EngineVersion } from './engine-version'; + +/** + * The options passed to {@link IInstanceEngine.bind}. + */ +export interface InstanceEngineBindOptions { + /** + * The timezone of the database, set by the customer. + * + * @default - none (it's an optional field) + */ + readonly timezone?: string; +} + +/** + * The type returned from the {@link IInstanceEngine.bind} method. + * Empty for now, + * but there might be fields added to it in the future. + */ +export interface InstanceEngineConfig { +} + +/** + * Interface representing a database instance (as opposed to cluster) engine. + */ +export interface IInstanceEngine extends IEngine { + /** The application used by this engine to perform rotation for a single-user scenario. */ + readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; + + /** The application used by this engine to perform rotation for a multi-user scenario. */ + readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; + + /** + * Method called when the engine is used to create a new instance. + */ + bindToInstance(scope: core.Construct, options: InstanceEngineBindOptions): InstanceEngineConfig; +} + +interface InstanceEngineBaseProps { + readonly engineType: string; + readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; + readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; + readonly version?: EngineVersion; + readonly parameterGroupFamily?: string; +} + +abstract class InstanceEngineBase implements IInstanceEngine { + public readonly engineType: string; + public readonly engineVersion?: EngineVersion; + public readonly parameterGroupFamily?: string; + public readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; + public readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; + + constructor(props: InstanceEngineBaseProps) { + this.engineType = props.engineType; + this.singleUserRotationApplication = props.singleUserRotationApplication; + this.multiUserRotationApplication = props.multiUserRotationApplication; + this.engineVersion = props.version; + this.parameterGroupFamily = props.parameterGroupFamily ?? + (this.engineVersion ? `${this.engineType}${this.engineVersion.majorVersion}` : undefined); + } + + public bindToInstance(_scope: core.Construct, options: InstanceEngineBindOptions): InstanceEngineConfig { + if (options.timezone) { + throw new Error(`timezone property can be configured only for Microsoft SQL Server, not ${this.engineType}`); + } + return { + }; + } +} + +/** + * The versions for the MariaDB instance engines + * (those returned by {@link DatabaseInstanceEngine.mariaDb}). + */ +export class MariaDbEngineVersion { + /** Version "10.0" (only a major version, without a specific minor version). */ + public static readonly VER_10_0 = MariaDbEngineVersion.of('10.0', '10.0'); + /** Version "10.0.17". */ + public static readonly VER_10_0_17 = MariaDbEngineVersion.of('10.0.17', '10.0'); + /** Version "10.0.24". */ + public static readonly VER_10_0_24 = MariaDbEngineVersion.of('10.0.24', '10.0'); + /** Version "10.0.28". */ + public static readonly VER_10_0_28 = MariaDbEngineVersion.of('10.0.28', '10.0'); + /** Version "10.0.31". */ + public static readonly VER_10_0_31 = MariaDbEngineVersion.of('10.0.31', '10.0'); + /** Version "10.0.32". */ + public static readonly VER_10_0_32 = MariaDbEngineVersion.of('10.0.32', '10.0'); + /** Version "10.0.34". */ + public static readonly VER_10_0_34 = MariaDbEngineVersion.of('10.0.34', '10.0'); + /** Version "10.0.35". */ + public static readonly VER_10_0_35 = MariaDbEngineVersion.of('10.0.35', '10.0'); + + /** Version "10.1" (only a major version, without a specific minor version). */ + public static readonly VER_10_1 = MariaDbEngineVersion.of('10.1', '10.1'); + /** Version "10.1.14". */ + public static readonly VER_10_1_14 = MariaDbEngineVersion.of('10.1.14', '10.1'); + /** Version "10.1.19". */ + public static readonly VER_10_1_19 = MariaDbEngineVersion.of('10.1.19', '10.1'); + /** Version "10.1.23". */ + public static readonly VER_10_1_23 = MariaDbEngineVersion.of('10.1.23', '10.1'); + /** Version "10.1.26". */ + public static readonly VER_10_1_26 = MariaDbEngineVersion.of('10.1.26', '10.1'); + /** Version "10.1.31". */ + public static readonly VER_10_1_31 = MariaDbEngineVersion.of('10.1.31', '10.1'); + /** Version "10.1.34". */ + public static readonly VER_10_1_34 = MariaDbEngineVersion.of('10.1.34', '10.1'); + + /** Version "10.2" (only a major version, without a specific minor version). */ + public static readonly VER_10_2 = MariaDbEngineVersion.of('10.2', '10.2'); + /** Version "10.2.11". */ + public static readonly VER_10_2_11 = MariaDbEngineVersion.of('10.2.11', '10.2'); + /** Version "10.2.12". */ + public static readonly VER_10_2_12 = MariaDbEngineVersion.of('10.2.12', '10.2'); + /** Version "10.2.15". */ + public static readonly VER_10_2_15 = MariaDbEngineVersion.of('10.2.15', '10.2'); + /** Version "10.2.21". */ + public static readonly VER_10_2_21 = MariaDbEngineVersion.of('10.2.21', '10.2'); + + /** Version "10.3" (only a major version, without a specific minor version). */ + public static readonly VER_10_3 = MariaDbEngineVersion.of('10.3', '10.3'); + /** Version "10.3.8". */ + public static readonly VER_10_3_8 = MariaDbEngineVersion.of('10.3.8', '10.3'); + /** Version "10.3.13". */ + public static readonly VER_10_3_13 = MariaDbEngineVersion.of('10.3.13', '10.3'); + /** Version "10.3.20". */ + public static readonly VER_10_3_20 = MariaDbEngineVersion.of('10.3.20', '10.3'); + /** Version "10.3.23". */ + public static readonly VER_10_3_23 = MariaDbEngineVersion.of('10.3.23', '10.3'); + + /** Version "10.4" (only a major version, without a specific minor version). */ + public static readonly VER_10_4 = MariaDbEngineVersion.of('10.4', '10.4'); + /** Version "10.4.8". */ + public static readonly VER_10_4_8 = MariaDbEngineVersion.of('10.4.8', '10.4'); + /** Version "10.4.13". */ + public static readonly VER_10_4_13 = MariaDbEngineVersion.of('10.4.13', '10.4'); + + /** + * Create a new MariaDbEngineVersion with an arbitrary version. + * + * @param mariaDbFullVersion the full version string, + * for example "10.5.28" + * @param mariaDbMajorVersion the major version of the engine, + * for example "10.5" + */ + public static of(mariaDbFullVersion: string, mariaDbMajorVersion: string): MariaDbEngineVersion { + return new MariaDbEngineVersion(mariaDbFullVersion, mariaDbMajorVersion); + } + + /** The full version string, for example, "10.5.28". */ + public readonly mariaDbFullVersion: string; + /** The major version of the engine, for example, "10.5". */ + public readonly mariaDbMajorVersion: string; + + private constructor(mariaDbFullVersion: string, mariaDbMajorVersion: string) { + this.mariaDbFullVersion = mariaDbFullVersion; + this.mariaDbMajorVersion = mariaDbMajorVersion; + } +} + +/** + * Properties for MariaDB instance engines. + * Used in {@link DatabaseInstanceEngine.mariaDb}. + */ +export interface MariaDbInstanceEngineProps { + /** The exact version of the engine to use. */ + readonly version: MariaDbEngineVersion; +} + +class MariaDbInstanceEngine extends InstanceEngineBase { + constructor(version?: MariaDbEngineVersion) { + super({ + engineType: 'mariadb', + singleUserRotationApplication: secretsmanager.SecretRotationApplication.MARIADB_ROTATION_SINGLE_USER, + multiUserRotationApplication: secretsmanager.SecretRotationApplication.MARIADB_ROTATION_MULTI_USER, + version: version + ? { + fullVersion: version.mariaDbFullVersion, + majorVersion: version.mariaDbMajorVersion, + } + : undefined, + }); + } +} + +/** + * The versions for the MySQL instance engines + * (those returned by {@link DatabaseInstanceEngine.mysql}). + */ +export class MysqlEngineVersion { + /** Version "5.5" (only a major version, without a specific minor version). */ + public static readonly VER_5_5 = MysqlEngineVersion.of('5.5', '5.5'); + /** Version "5.5.46". */ + public static readonly VER_5_5_46 = MysqlEngineVersion.of('5.5.46', '5.5'); + /** Version "5.5.53". */ + public static readonly VER_5_5_53 = MysqlEngineVersion.of('5.5.53', '5.5'); + /** Version "5.5.57". */ + public static readonly VER_5_5_57 = MysqlEngineVersion.of('5.5.57', '5.5'); + /** Version "5.5.59". */ + public static readonly VER_5_5_59 = MysqlEngineVersion.of('5.5.59', '5.5'); + /** Version "5.5.61". */ + public static readonly VER_5_5_61 = MysqlEngineVersion.of('5.5.61', '5.5'); + + /** Version "5.6" (only a major version, without a specific minor version). */ + public static readonly VER_5_6 = MysqlEngineVersion.of('5.6', '5.6'); + /** Version "5.6.34". */ + public static readonly VER_5_6_34 = MysqlEngineVersion.of('5.6.34', '5.6'); + /** Version "5.6.35". */ + public static readonly VER_5_6_35 = MysqlEngineVersion.of('5.6.35', '5.6'); + /** Version "5.6.37". */ + public static readonly VER_5_6_37 = MysqlEngineVersion.of('5.6.37', '5.6'); + /** Version "5.6.39". */ + public static readonly VER_5_6_39 = MysqlEngineVersion.of('5.6.39', '5.6'); + /** Version "5.6.40". */ + public static readonly VER_5_6_40 = MysqlEngineVersion.of('5.6.40', '5.6'); + /** Version "5.6.41". */ + public static readonly VER_5_6_41 = MysqlEngineVersion.of('5.6.41', '5.6'); + /** Version "5.6.43". */ + public static readonly VER_5_6_43 = MysqlEngineVersion.of('5.6.43', '5.6'); + /** Version "5.6.44". */ + public static readonly VER_5_6_44 = MysqlEngineVersion.of('5.6.44', '5.6'); + /** Version "5.6.46". */ + public static readonly VER_5_6_46 = MysqlEngineVersion.of('5.6.46', '5.6'); + /** Version "5.6.48". */ + public static readonly VER_5_6_48 = MysqlEngineVersion.of('5.6.48', '5.6'); + + /** Version "5.7" (only a major version, without a specific minor version). */ + public static readonly VER_5_7 = MysqlEngineVersion.of('5.7', '5.7'); + /** Version "5.7.16". */ + public static readonly VER_5_7_16 = MysqlEngineVersion.of('5.7.16', '5.7'); + /** Version "5.7.17". */ + public static readonly VER_5_7_17 = MysqlEngineVersion.of('5.7.17', '5.7'); + /** Version "5.7.19". */ + public static readonly VER_5_7_19 = MysqlEngineVersion.of('5.7.19', '5.7'); + /** Version "5.7.21". */ + public static readonly VER_5_7_21 = MysqlEngineVersion.of('5.7.21', '5.7'); + /** Version "5.7.22". */ + public static readonly VER_5_7_22 = MysqlEngineVersion.of('5.7.22', '5.7'); + /** Version "5.7.23". */ + public static readonly VER_5_7_23 = MysqlEngineVersion.of('5.7.23', '5.7'); + /** Version "5.7.24". */ + public static readonly VER_5_7_24 = MysqlEngineVersion.of('5.7.24', '5.7'); + /** Version "5.7.25". */ + public static readonly VER_5_7_25 = MysqlEngineVersion.of('5.7.25', '5.7'); + /** Version "5.7.26". */ + public static readonly VER_5_7_26 = MysqlEngineVersion.of('5.7.26', '5.7'); + /** Version "5.7.28". */ + public static readonly VER_5_7_28 = MysqlEngineVersion.of('5.7.28', '5.7'); + /** Version "5.7.30". */ + public static readonly VER_5_7_30 = MysqlEngineVersion.of('5.7.30', '5.7'); + + /** Version "8.0" (only a major version, without a specific minor version). */ + public static readonly VER_8_0 = MysqlEngineVersion.of('8.0', '8.0'); + /** Version "8.0.11". */ + public static readonly VER_8_0_11 = MysqlEngineVersion.of('8.0.11', '8.0'); + /** Version "8.0.13". */ + public static readonly VER_8_0_13 = MysqlEngineVersion.of('8.0.13', '8.0'); + /** Version "8.0.15". */ + public static readonly VER_8_0_15 = MysqlEngineVersion.of('8.0.15', '8.0'); + /** Version "8.0.16". */ + public static readonly VER_8_0_16 = MysqlEngineVersion.of('8.0.16', '8.0'); + /** Version "8.0.17". */ + public static readonly VER_8_0_17 = MysqlEngineVersion.of('8.0.17', '8.0'); + /** Version "8.0.19". */ + public static readonly VER_8_0_19 = MysqlEngineVersion.of('8.0.19', '8.0'); + + /** + * Create a new MysqlEngineVersion with an arbitrary version. + * + * @param mysqlFullVersion the full version string, + * for example "8.1.43" + * @param mysqlMajorVersion the major version of the engine, + * for example "8.1" + */ + public static of(mysqlFullVersion: string, mysqlMajorVersion: string): MysqlEngineVersion { + return new MysqlEngineVersion(mysqlFullVersion, mysqlMajorVersion); + } + + /** The full version string, for example, "10.5.28". */ + public readonly mysqlFullVersion: string; + /** The major version of the engine, for example, "10.5". */ + public readonly mysqlMajorVersion: string; + + private constructor(mysqlFullVersion: string, mysqlMajorVersion: string) { + this.mysqlFullVersion = mysqlFullVersion; + this.mysqlMajorVersion = mysqlMajorVersion; + } +} + +/** + * Properties for MySQL instance engines. + * Used in {@link DatabaseInstanceEngine.mysql}. + */ +export interface MySqlInstanceEngineProps { + /** The exact version of the engine to use. */ + readonly version: MysqlEngineVersion; +} + +class MySqlInstanceEngine extends InstanceEngineBase { + constructor(version?: MysqlEngineVersion) { + super({ + engineType: 'mysql', + singleUserRotationApplication: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, + multiUserRotationApplication: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, + version: version + ? { + fullVersion: version.mysqlFullVersion, + majorVersion: version.mysqlMajorVersion, + } + : undefined, + }); + } +} + +/** + * The versions for the PostgreSQL instance engines + * (those returned by {@link DatabaseInstanceEngine.postgres}). + */ +export class PostgresEngineVersion { + /** Version "9.5" (only a major version, without a specific minor version). */ + public static readonly VER_9_5 = PostgresEngineVersion.of('9.5', '9.5'); + /** Version "9.5.2". */ + public static readonly VER_9_5_2 = PostgresEngineVersion.of('9.5.2', '9.5'); + /** Version "9.5.4". */ + public static readonly VER_9_5_4 = PostgresEngineVersion.of('9.5.4', '9.5'); + /** Version "9.5.6". */ + public static readonly VER_9_5_6 = PostgresEngineVersion.of('9.5.6', '9.5'); + /** Version "9.5.7". */ + public static readonly VER_9_5_7 = PostgresEngineVersion.of('9.5.7', '9.5'); + /** Version "9.5.9". */ + public static readonly VER_9_5_9 = PostgresEngineVersion.of('9.5.9', '9.5'); + /** Version "9.5.10". */ + public static readonly VER_9_5_10 = PostgresEngineVersion.of('9.5.10', '9.5'); + /** Version "9.5.12". */ + public static readonly VER_9_5_12 = PostgresEngineVersion.of('9.5.12', '9.5'); + /** Version "9.5.13". */ + public static readonly VER_9_5_13 = PostgresEngineVersion.of('9.5.13', '9.5'); + /** Version "9.5.14". */ + public static readonly VER_9_5_14 = PostgresEngineVersion.of('9.5.14', '9.5'); + /** Version "9.5.15". */ + public static readonly VER_9_5_15 = PostgresEngineVersion.of('9.5.15', '9.5'); + /** Version "9.5.16". */ + public static readonly VER_9_5_16 = PostgresEngineVersion.of('9.5.16', '9.5'); + /** Version "9.5.18". */ + public static readonly VER_9_5_18 = PostgresEngineVersion.of('9.5.18', '9.5'); + /** Version "9.5.19". */ + public static readonly VER_9_5_19 = PostgresEngineVersion.of('9.5.19', '9.5'); + /** Version "9.5.20". */ + public static readonly VER_9_5_20 = PostgresEngineVersion.of('9.5.20', '9.5'); + /** Version "9.5.21". */ + public static readonly VER_9_5_21 = PostgresEngineVersion.of('9.5.21', '9.5'); + /** Version "9.5.22". */ + public static readonly VER_9_5_22 = PostgresEngineVersion.of('9.5.22', '9.5'); + + /** Version "9.6" (only a major version, without a specific minor version). */ + public static readonly VER_9_6 = PostgresEngineVersion.of('9.6', '9.6'); + /** Version "9.6.1". */ + public static readonly VER_9_6_1 = PostgresEngineVersion.of('9.6.1', '9.6'); + /** Version "9.6.2". */ + public static readonly VER_9_6_2 = PostgresEngineVersion.of('9.6.2', '9.6'); + /** Version "9.6.3". */ + public static readonly VER_9_6_3 = PostgresEngineVersion.of('9.6.3', '9.6'); + /** Version "9.6.5". */ + public static readonly VER_9_6_5 = PostgresEngineVersion.of('9.6.5', '9.6'); + /** Version "9.6.6". */ + public static readonly VER_9_6_6 = PostgresEngineVersion.of('9.6.6', '9.6'); + /** Version "9.6.8". */ + public static readonly VER_9_6_8 = PostgresEngineVersion.of('9.6.8', '9.6'); + /** Version "9.6.9". */ + public static readonly VER_9_6_9 = PostgresEngineVersion.of('9.6.9', '9.6'); + /** Version "9.6.10". */ + public static readonly VER_9_6_10 = PostgresEngineVersion.of('9.6.10', '9.6'); + /** Version "9.6.11". */ + public static readonly VER_9_6_11 = PostgresEngineVersion.of('9.6.11', '9.6'); + /** Version "9.6.12". */ + public static readonly VER_9_6_12 = PostgresEngineVersion.of('9.6.12', '9.6'); + /** Version "9.6.14". */ + public static readonly VER_9_6_14 = PostgresEngineVersion.of('9.6.14', '9.6'); + /** Version "9.6.15". */ + public static readonly VER_9_6_15 = PostgresEngineVersion.of('9.6.15', '9.6'); + /** Version "9.6.16". */ + public static readonly VER_9_6_16 = PostgresEngineVersion.of('9.6.16', '9.6'); + /** Version "9.6.17". */ + public static readonly VER_9_6_17 = PostgresEngineVersion.of('9.6.17', '9.6'); + /** Version "9.6.18". */ + public static readonly VER_9_6_18 = PostgresEngineVersion.of('9.6.18', '9.6'); + + /** Version "10" (only a major version, without a specific minor version). */ + public static readonly VER_10 = PostgresEngineVersion.of('10', '10'); + /** Version "10.1". */ + public static readonly VER_10_1 = PostgresEngineVersion.of('10.1', '10'); + /** Version "10.3". */ + public static readonly VER_10_3 = PostgresEngineVersion.of('10.3', '10'); + /** Version "10.4". */ + public static readonly VER_10_4 = PostgresEngineVersion.of('10.4', '10'); + /** Version "10.5". */ + public static readonly VER_10_5 = PostgresEngineVersion.of('10.5', '10'); + /** Version "10.6". */ + public static readonly VER_10_6 = PostgresEngineVersion.of('10.6', '10'); + /** Version "10.7". */ + public static readonly VER_10_7 = PostgresEngineVersion.of('10.7', '10'); + /** Version "10.9". */ + public static readonly VER_10_9 = PostgresEngineVersion.of('10.9', '10'); + /** Version "10.10". */ + public static readonly VER_10_10 = PostgresEngineVersion.of('10.10', '10'); + /** Version "10.11". */ + public static readonly VER_10_11 = PostgresEngineVersion.of('10.11', '10'); + /** Version "10.12". */ + public static readonly VER_10_12 = PostgresEngineVersion.of('10.12', '10'); + /** Version "10.13". */ + public static readonly VER_10_13 = PostgresEngineVersion.of('10.13', '10'); + + /** Version "11" (only a major version, without a specific minor version). */ + public static readonly VER_11 = PostgresEngineVersion.of('11', '11'); + /** Version "11.1". */ + public static readonly VER_11_1 = PostgresEngineVersion.of('11.1', '11'); + /** Version "11.2". */ + public static readonly VER_11_2 = PostgresEngineVersion.of('11.2', '11'); + /** Version "11.4". */ + public static readonly VER_11_4 = PostgresEngineVersion.of('11.4', '11'); + /** Version "11.5". */ + public static readonly VER_11_5 = PostgresEngineVersion.of('11.5', '11'); + /** Version "11.6". */ + public static readonly VER_11_6 = PostgresEngineVersion.of('11.6', '11'); + /** Version "11.7". */ + public static readonly VER_11_7 = PostgresEngineVersion.of('11.7', '11'); + /** Version "11.8". */ + public static readonly VER_11_8 = PostgresEngineVersion.of('11.8', '11'); + + /** Version "12" (only a major version, without a specific minor version). */ + public static readonly VER_12 = PostgresEngineVersion.of('12', '12'); + /** Version "12.2". */ + public static readonly VER_12_2 = PostgresEngineVersion.of('12.2', '12'); + /** Version "12.3". */ + public static readonly VER_12_3 = PostgresEngineVersion.of('12.3', '12'); + + /** + * Create a new PostgresEngineVersion with an arbitrary version. + * + * @param postgresFullVersion the full version string, + * for example "13.11" + * @param postgresMajorVersion the major version of the engine, + * for example "13" + */ + public static of(postgresFullVersion: string, postgresMajorVersion: string): PostgresEngineVersion { + return new PostgresEngineVersion(postgresFullVersion, postgresMajorVersion); + } + + /** The full version string, for example, "13.11". */ + public readonly postgresFullVersion: string; + /** The major version of the engine, for example, "13". */ + public readonly postgresMajorVersion: string; + + private constructor(postgresFullVersion: string, postgresMajorVersion: string) { + this.postgresFullVersion = postgresFullVersion; + this.postgresMajorVersion = postgresMajorVersion; + } +} + +/** + * Properties for PostgreSQL instance engines. + * Used in {@link DatabaseInstanceEngine.postgres}. + */ +export interface PostgresInstanceEngineProps { + /** The exact version of the engine to use. */ + readonly version: PostgresEngineVersion; +} + +/** + * The instance engine for PostgreSQL. + */ +class PostgresInstanceEngine extends InstanceEngineBase { + constructor(version?: PostgresEngineVersion) { + super({ + engineType: 'postgres', + singleUserRotationApplication: secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER, + multiUserRotationApplication: secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_MULTI_USER, + version: version + ? { + fullVersion: version.postgresFullVersion, + majorVersion: version.postgresMajorVersion, + } + : undefined, + }); + } +} + +/** + * The versions for the legacy Oracle instance engines + * (those returned by {@link DatabaseInstanceEngine.oracleSe} + * and {@link DatabaseInstanceEngine.oracleSe1}). + * Note: RDS will stop allowing creating new databases with this version in August 2020. + */ +export class OracleLegacyEngineVersion { + /** Version "11.2" (only a major version, without a specific minor version). */ + public static readonly VER_11_2 = OracleLegacyEngineVersion.of('11.2', '11.2'); + /** Version "11.2.0.2.v2". */ + public static readonly VER_11_2_0_2_V2 = OracleLegacyEngineVersion.of('11.2.0.2.v2', '11.2'); + /** Version "11.2.0.4.v1". */ + public static readonly VER_11_2_0_4_V1 = OracleLegacyEngineVersion.of('11.2.0.4.v1', '11.2'); + /** Version "11.2.0.4.v3". */ + public static readonly VER_11_2_0_4_V3 = OracleLegacyEngineVersion.of('11.2.0.4.v3', '11.2'); + /** Version "11.2.0.4.v4". */ + public static readonly VER_11_2_0_4_V4 = OracleLegacyEngineVersion.of('11.2.0.4.v4', '11.2'); + /** Version "11.2.0.4.v5". */ + public static readonly VER_11_2_0_4_V5 = OracleLegacyEngineVersion.of('11.2.0.4.v5', '11.2'); + /** Version "11.2.0.4.v6". */ + public static readonly VER_11_2_0_4_V6 = OracleLegacyEngineVersion.of('11.2.0.4.v6', '11.2'); + /** Version "11.2.0.4.v7". */ + public static readonly VER_11_2_0_4_V7 = OracleLegacyEngineVersion.of('11.2.0.4.v7', '11.2'); + /** Version "11.2.0.4.v8". */ + public static readonly VER_11_2_0_4_V8 = OracleLegacyEngineVersion.of('11.2.0.4.v8', '11.2'); + /** Version "11.2.0.4.v9". */ + public static readonly VER_11_2_0_4_V9 = OracleLegacyEngineVersion.of('11.2.0.4.v9', '11.2'); + /** Version "11.2.0.4.v10". */ + public static readonly VER_11_2_0_4_V10 = OracleLegacyEngineVersion.of('11.2.0.4.v10', '11.2'); + /** Version "11.2.0.4.v11". */ + public static readonly VER_11_2_0_4_V11 = OracleLegacyEngineVersion.of('11.2.0.4.v11', '11.2'); + /** Version "11.2.0.4.v12". */ + public static readonly VER_11_2_0_4_V12 = OracleLegacyEngineVersion.of('11.2.0.4.v12', '11.2'); + /** Version "11.2.0.4.v13". */ + public static readonly VER_11_2_0_4_V13 = OracleLegacyEngineVersion.of('11.2.0.4.v13', '11.2'); + /** Version "11.2.0.4.v14". */ + public static readonly VER_11_2_0_4_V14 = OracleLegacyEngineVersion.of('11.2.0.4.v14', '11.2'); + /** Version "11.2.0.4.v15". */ + public static readonly VER_11_2_0_4_V15 = OracleLegacyEngineVersion.of('11.2.0.4.v15', '11.2'); + /** Version "11.2.0.4.v16". */ + public static readonly VER_11_2_0_4_V16 = OracleLegacyEngineVersion.of('11.2.0.4.v16', '11.2'); + /** Version "11.2.0.4.v17". */ + public static readonly VER_11_2_0_4_V17 = OracleLegacyEngineVersion.of('11.2.0.4.v17', '11.2'); + /** Version "11.2.0.4.v18". */ + public static readonly VER_11_2_0_4_V18 = OracleLegacyEngineVersion.of('11.2.0.4.v18', '11.2'); + /** Version "11.2.0.4.v19". */ + public static readonly VER_11_2_0_4_V19 = OracleLegacyEngineVersion.of('11.2.0.4.v19', '11.2'); + /** Version "11.2.0.4.v20". */ + public static readonly VER_11_2_0_4_V20 = OracleLegacyEngineVersion.of('11.2.0.4.v20', '11.2'); + /** Version "11.2.0.4.v21". */ + public static readonly VER_11_2_0_4_V21 = OracleLegacyEngineVersion.of('11.2.0.4.v21', '11.2'); + /** Version "11.2.0.4.v22". */ + public static readonly VER_11_2_0_4_V22 = OracleLegacyEngineVersion.of('11.2.0.4.v22', '11.2'); + /** Version "11.2.0.4.v23". */ + public static readonly VER_11_2_0_4_V23 = OracleLegacyEngineVersion.of('11.2.0.4.v23', '11.2'); + /** Version "11.2.0.4.v24". */ + public static readonly VER_11_2_0_4_V24 = OracleLegacyEngineVersion.of('11.2.0.4.v24', '11.2'); + + private static of(oracleLegacyFullVersion: string, oracleLegacyMajorVersion: string): OracleLegacyEngineVersion { + return new OracleLegacyEngineVersion(oracleLegacyFullVersion, oracleLegacyMajorVersion); + } + + /** The full version string, for example, "11.2.0.4.v24". */ + public readonly oracleLegacyFullVersion: string; + /** The major version of the engine, for example, "11.2". */ + public readonly oracleLegacyMajorVersion: string; + + private constructor(oracleLegacyFullVersion: string, oracleLegacyMajorVersion: string) { + this.oracleLegacyFullVersion = oracleLegacyFullVersion; + this.oracleLegacyMajorVersion = oracleLegacyMajorVersion; + } +} + +/** + * The versions for the Oracle instance engines + * (those returned by {@link DatabaseInstanceEngine.oracleSe2} and + * {@link DatabaseInstanceEngine.oracleEe}). + */ +export class OracleEngineVersion { + /** Version "12.1" (only a major version, without a specific minor version). */ + public static readonly VER_12_1 = OracleEngineVersion.of('12.1', '12.1'); + /** Version "12.1.0.2.v1". */ + public static readonly VER_12_1_0_2_V1 = OracleEngineVersion.of('12.1.0.2.v1', '12.1'); + /** Version "12.1.0.2.v2". */ + public static readonly VER_12_1_0_2_V2 = OracleEngineVersion.of('12.1.0.2.v2', '12.1'); + /** Version "12.1.0.2.v3". */ + public static readonly VER_12_1_0_2_V3 = OracleEngineVersion.of('12.1.0.2.v3', '12.1'); + /** Version "12.1.0.2.v4". */ + public static readonly VER_12_1_0_2_V4 = OracleEngineVersion.of('12.1.0.2.v4', '12.1'); + /** Version "12.1.0.2.v5". */ + public static readonly VER_12_1_0_2_V5 = OracleEngineVersion.of('12.1.0.2.v5', '12.1'); + /** Version "12.1.0.2.v6". */ + public static readonly VER_12_1_0_2_V6 = OracleEngineVersion.of('12.1.0.2.v6', '12.1'); + /** Version "12.1.0.2.v7". */ + public static readonly VER_12_1_0_2_V7 = OracleEngineVersion.of('12.1.0.2.v7', '12.1'); + /** Version "12.1.0.2.v8". */ + public static readonly VER_12_1_0_2_V8 = OracleEngineVersion.of('12.1.0.2.v8', '12.1'); + /** Version "12.1.0.2.v9". */ + public static readonly VER_12_1_0_2_V9 = OracleEngineVersion.of('12.1.0.2.v9', '12.1'); + /** Version "12.1.0.2.v10". */ + public static readonly VER_12_1_0_2_V10 = OracleEngineVersion.of('12.1.0.2.v10', '12.1'); + /** Version "12.1.0.2.v11". */ + public static readonly VER_12_1_0_2_V11 = OracleEngineVersion.of('12.1.0.2.v11', '12.1'); + /** Version "12.1.0.2.v12". */ + public static readonly VER_12_1_0_2_V12 = OracleEngineVersion.of('12.1.0.2.v12', '12.1'); + /** Version "12.1.0.2.v13". */ + public static readonly VER_12_1_0_2_V13 = OracleEngineVersion.of('12.1.0.2.v13', '12.1'); + /** Version "12.1.0.2.v14". */ + public static readonly VER_12_1_0_2_V14 = OracleEngineVersion.of('12.1.0.2.v14', '12.1'); + /** Version "12.1.0.2.v15". */ + public static readonly VER_12_1_0_2_V15 = OracleEngineVersion.of('12.1.0.2.v15', '12.1'); + /** Version "12.1.0.2.v16". */ + public static readonly VER_12_1_0_2_V16 = OracleEngineVersion.of('12.1.0.2.v16', '12.1'); + /** Version "12.1.0.2.v17". */ + public static readonly VER_12_1_0_2_V17 = OracleEngineVersion.of('12.1.0.2.v17', '12.1'); + /** Version "12.1.0.2.v18". */ + public static readonly VER_12_1_0_2_V18 = OracleEngineVersion.of('12.1.0.2.v18', '12.1'); + /** Version "12.1.0.2.v19". */ + public static readonly VER_12_1_0_2_V19 = OracleEngineVersion.of('12.1.0.2.v19', '12.1'); + /** Version "12.1.0.2.v20". */ + public static readonly VER_12_1_0_2_V20 = OracleEngineVersion.of('12.1.0.2.v20', '12.1'); + + /** Version "12.2" (only a major version, without a specific minor version). */ + public static readonly VER_12_2 = OracleEngineVersion.of('12.2', '12.2'); + /** Version "12.2.0.1.ru-2018-10.rur-2018-10.r1". */ + public static readonly VER_12_2_0_1_2018_10_R1 = OracleEngineVersion.of('12.2.0.1.ru-2018-10.rur-2018-10.r1', '12.2'); + /** Version "12.2.0.1.ru-2019-01.rur-2019-01.r1". */ + public static readonly VER_12_2_0_1_2019_01_R1 = OracleEngineVersion.of('12.2.0.1.ru-2019-01.rur-2019-01.r1', '12.2'); + /** Version "12.2.0.1.ru-2019-04.rur-2019-04.r1". */ + public static readonly VER_12_2_0_1_2019_04_R1 = OracleEngineVersion.of('12.2.0.1.ru-2019-04.rur-2019-04.r1', '12.2'); + /** Version "12.2.0.1.ru-2019-07.rur-2019-07.r1". */ + public static readonly VER_12_2_0_1_2019_07_R1 = OracleEngineVersion.of('12.2.0.1.ru-2019-07.rur-2019-07.r1', '12.2'); + /** Version "12.2.0.1.ru-2019-10.rur-2019-10.r1". */ + public static readonly VER_12_2_0_1_2019_10_R1 = OracleEngineVersion.of('12.2.0.1.ru-2019-10.rur-2019-10.r1', '12.2'); + /** Version "12.2.0.1.ru-2020-01.rur-2020-01.r1". */ + public static readonly VER_12_2_0_1_2020_01_R1 = OracleEngineVersion.of('12.2.0.1.ru-2020-01.rur-2020-01.r1', '12.2'); + /** Version "12.2.0.1.ru-2020-04.rur-2020-04.r1". */ + public static readonly VER_12_2_0_1_2020_04_R1 = OracleEngineVersion.of('12.2.0.1.ru-2020-04.rur-2020-04.r1', '12.2'); + + /** Version "18" (only a major version, without a specific minor version). */ + public static readonly VER_18 = OracleEngineVersion.of('18', '18'); + /** Version "18.0.0.0.ru-2019-07.rur-2019-07.r1". */ + public static readonly VER_18_0_0_0_2019_07_R1 = OracleEngineVersion.of('18.0.0.0.ru-2019-07.rur-2019-07.r1', '18'); + /** Version "18.0.0.0.ru-2019-10.rur-2019-10.r1". */ + public static readonly VER_18_0_0_0_2019_10_R1 = OracleEngineVersion.of('18.0.0.0.ru-2019-10.rur-2019-10.r1', '18'); + /** Version "18.0.0.0.ru-2020-01.rur-2020-01.r1". */ + public static readonly VER_18_0_0_0_2020_01_R1 = OracleEngineVersion.of('18.0.0.0.ru-2020-01.rur-2020-01.r1', '18'); + /** Version "18.0.0.0.ru-2020-04.rur-2020-04.r1". */ + public static readonly VER_18_0_0_0_2020_04_R1 = OracleEngineVersion.of('18.0.0.0.ru-2020-04.rur-2020-04.r1', '18'); + + /** Version "19" (only a major version, without a specific minor version). */ + public static readonly VER_19 = OracleEngineVersion.of('19', '19'); + /** Version "19.0.0.0.ru-2019-07.rur-2019-07.r1". */ + public static readonly VER_19_0_0_0_2019_07_R1 = OracleEngineVersion.of('19.0.0.0.ru-2019-07.rur-2019-07.r1', '19'); + /** Version "19.0.0.0.ru-2019-10.rur-2019-10.r1". */ + public static readonly VER_19_0_0_0_2019_10_R1 = OracleEngineVersion.of('19.0.0.0.ru-2019-10.rur-2019-10.r1', '19'); + /** Version "19.0.0.0.ru-2020-01.rur-2020-01.r1". */ + public static readonly VER_19_0_0_0_2020_01_R1 = OracleEngineVersion.of('19.0.0.0.ru-2020-01.rur-2020-01.r1', '19'); + /** Version "19.0.0.0.ru-2020-04.rur-2020-04.r1". */ + public static readonly VER_19_0_0_0_2020_04_R1 = OracleEngineVersion.of('19.0.0.0.ru-2020-04.rur-2020-04.r1', '19'); + + /** + * Creates a new OracleEngineVersion with an arbitrary version. + * + * @param oracleFullVersion the full version string, + * for example "19.0.0.0.ru-2019-10.rur-2019-10.r1" + * @param oracleMajorVersion the major version of the engine, + * for example "19" + */ + public static of(oracleFullVersion: string, oracleMajorVersion: string): OracleEngineVersion { + return new OracleEngineVersion(oracleFullVersion, oracleMajorVersion); + } + + /** The full version string, for example, "19.0.0.0.ru-2019-10.rur-2019-10.r1". */ + public readonly oracleFullVersion: string; + /** The major version of the engine, for example, "19". */ + public readonly oracleMajorVersion: string; + + private constructor(oracleFullVersion: string, oracleMajorVersion: string) { + this.oracleFullVersion = oracleFullVersion; + this.oracleMajorVersion = oracleMajorVersion; + } +} + +interface OracleInstanceEngineBaseProps { + readonly engineType: string; + readonly version?: EngineVersion; +} + +abstract class OracleInstanceEngineBase extends InstanceEngineBase { + constructor(props: OracleInstanceEngineBaseProps) { + super({ + ...props, + singleUserRotationApplication: secretsmanager.SecretRotationApplication.ORACLE_ROTATION_SINGLE_USER, + multiUserRotationApplication: secretsmanager.SecretRotationApplication.ORACLE_ROTATION_MULTI_USER, + parameterGroupFamily: props.version ? `${props.engineType}-${props.version.majorVersion}` : undefined, + }); + } +} + +interface OracleInstanceEngineProps { + /** The exact version of the engine to use. */ + readonly version: OracleEngineVersion; +} + +/** + * Properties for Oracle Standard Edition instance engines. + * Used in {@link DatabaseInstanceEngine.oracleSe}. + */ +export interface OracleSeInstanceEngineProps { + /** The exact version of the engine to use. */ + readonly version: OracleLegacyEngineVersion; +} + +class OracleSeInstanceEngine extends OracleInstanceEngineBase { + constructor(version?: OracleLegacyEngineVersion) { + super({ + engineType: 'oracle-se', + version: version + ? { + fullVersion: version.oracleLegacyFullVersion, + majorVersion: version.oracleLegacyMajorVersion, + } + : { + majorVersion: '11.2', + }, + }); + } +} + +/** + * Properties for Oracle Standard Edition 1 instance engines. + * Used in {@link DatabaseInstanceEngine.oracleSe1}. + */ +export interface OracleSe1InstanceEngineProps { + /** The exact version of the engine to use. */ + readonly version: OracleLegacyEngineVersion; +} + +class OracleSe1InstanceEngine extends OracleInstanceEngineBase { + constructor(version?: OracleLegacyEngineVersion) { + super({ + engineType: 'oracle-se1', + version: version + ? { + fullVersion: version.oracleLegacyFullVersion, + majorVersion: version.oracleLegacyMajorVersion, + } + : { + majorVersion: '11.2', + }, + }); + } +} + +/** + * Properties for Oracle Standard Edition 2 instance engines. + * Used in {@link DatabaseInstanceEngine.oracleSe2}. + */ +export interface OracleSe2InstanceEngineProps extends OracleInstanceEngineProps { +} + +class OracleSe2InstanceEngine extends OracleInstanceEngineBase { + constructor(version?: OracleEngineVersion) { + super({ + engineType: 'oracle-se2', + version: version + ? { + fullVersion: version.oracleFullVersion, + majorVersion: version.oracleMajorVersion, + } + : undefined, + }); + } +} + +/** + * Properties for Oracle Enterprise Edition instance engines. + * Used in {@link DatabaseInstanceEngine.oracleEe}. + */ +export interface OracleEeInstanceEngineProps extends OracleInstanceEngineProps { +} + +class OracleEeInstanceEngine extends OracleInstanceEngineBase { + constructor(version?: OracleEngineVersion) { + super({ + engineType: 'oracle-ee', + version: version + ? { + fullVersion: version.oracleFullVersion, + majorVersion: version.oracleMajorVersion, + } + : undefined, + }); + } +} + +/** + * The versions for the SQL Server instance engines + * (those returned by {@link DatabaseInstanceEngine.sqlServerSe}, + * {@link DatabaseInstanceEngine.sqlServerEx}, {@link DatabaseInstanceEngine.sqlServerWeb} + * and {@link DatabaseInstanceEngine.sqlServerEe}). + */ +export class SqlServerEngineVersion { + /** Version "11.0" (only a major version, without a specific minor version). */ + public static readonly VER_11 = SqlServerEngineVersion.of('11.0', '11.0'); + /** Version "11.00.5058.0.v1". */ + public static readonly VER_11_00_5058_0_V1 = SqlServerEngineVersion.of('11.00.5058.0.v1', '11.0'); + /** Version "11.00.6020.0.v1". */ + public static readonly VER_11_00_6020_0_V1 = SqlServerEngineVersion.of('11.00.6020.0.v1', '11.0'); + /** Version "11.00.6594.0.v1". */ + public static readonly VER_11_00_6594_0_V1 = SqlServerEngineVersion.of('11.00.6594.0.v1', '11.0'); + /** Version "11.00.7462.6.v1". */ + public static readonly VER_11_00_7462_6_V1 = SqlServerEngineVersion.of('11.00.7462.6.v1', '11.0'); + /** Version "11.00.7493.4.v1". */ + public static readonly VER_11_00_7493_4_V1 = SqlServerEngineVersion.of('11.00.7493.4.v1', '11.0'); + + /** Version "12.0" (only a major version, without a specific minor version). */ + public static readonly VER_12 = SqlServerEngineVersion.of('12.0', '12.0'); + /** Version "12.00.5000.0.v1". */ + public static readonly VER_12_00_5000_0_V1 = SqlServerEngineVersion.of('12.00.5000.0.v1', '12.0'); + /** Version "12.00.5546.0.v1". */ + public static readonly VER_12_00_5546_0_V1 = SqlServerEngineVersion.of('12.00.5546.0.v1', '12.0'); + /** Version "12.00.5571.0.v1". */ + public static readonly VER_12_00_5571_0_V1 = SqlServerEngineVersion.of('12.00.5571.0.v1', '12.0'); + /** Version "12.00.6293.0.v1". */ + public static readonly VER_12_00_6293_0_V1 = SqlServerEngineVersion.of('12.00.6293.0.v1', '12.0'); + /** Version "12.00.6329.1.v1". */ + public static readonly VER_12_00_6329_1_V1 = SqlServerEngineVersion.of('12.00.6329.1.v1', '12.0'); + + /** Version "13.0" (only a major version, without a specific minor version). */ + public static readonly VER_13 = SqlServerEngineVersion.of('13.0', '13.0'); + /** Version "13.00.2164.0.v1". */ + public static readonly VER_13_00_2164_0_V1 = SqlServerEngineVersion.of('13.00.2164.0.v1', '13.0'); + /** Version "13.00.4422.0.v1". */ + public static readonly VER_13_00_4422_0_V1 = SqlServerEngineVersion.of('13.00.4422.0.v1', '13.0'); + /** Version "13.00.4451.0.v1". */ + public static readonly VER_13_00_4451_0_V1 = SqlServerEngineVersion.of('13.00.4451.0.v1', '13.0'); + /** Version "13.00.4466.4.v1". */ + public static readonly VER_13_00_4466_4_V1 = SqlServerEngineVersion.of('13.00.4466.4.v1', '13.0'); + /** Version "13.00.4522.0.v1". */ + public static readonly VER_13_00_4522_0_V1 = SqlServerEngineVersion.of('13.00.4522.0.v1', '13.0'); + /** Version "13.00.5216.0.v1". */ + public static readonly VER_13_00_5216_0_V1 = SqlServerEngineVersion.of('13.00.5216.0.v1', '13.0'); + /** Version "13.00.5292.0.v1". */ + public static readonly VER_13_00_5292_0_V1 = SqlServerEngineVersion.of('13.00.5292.0.v1', '13.0'); + /** Version "13.00.5366.0.v1". */ + public static readonly VER_13_00_5366_0_V1 = SqlServerEngineVersion.of('13.00.5366.0.v1', '13.0'); + /** Version "13.00.5426.0.v1". */ + public static readonly VER_13_00_5426_0_V1 = SqlServerEngineVersion.of('13.00.5426.0.v1', '13.0'); + /** Version "13.00.5598.27.v1". */ + public static readonly VER_13_00_5598_27_V1 = SqlServerEngineVersion.of('13.00.5598.27.v1', '13.0'); + + /** Version "14.0" (only a major version, without a specific minor version). */ + public static readonly VER_14 = SqlServerEngineVersion.of('14.0', '14.0'); + /** Version "14.00.1000.169.v1". */ + public static readonly VER_14_00_1000_169_V1 = SqlServerEngineVersion.of('14.00.1000.169.v1', '14.0'); + /** Version "14.00.3015.40.v1". */ + public static readonly VER_14_00_3015_40_V1 = SqlServerEngineVersion.of('14.00.3015.40.v1', '14.0'); + /** Version "14.00.3035.2.v1". */ + public static readonly VER_14_00_3035_2_V1 = SqlServerEngineVersion.of('14.00.3035.2.v1', '14.0'); + /** Version "14.00.3049.1.v1". */ + public static readonly VER_14_00_3049_1_V1 = SqlServerEngineVersion.of('14.00.3049.1.v1', '14.0'); + /** Version "14.00.3192.2.v1". */ + public static readonly VER_14_00_3192_2_V1 = SqlServerEngineVersion.of('14.00.3192.2.v1', '14.0'); + + /** + * Create a new SqlServerEngineVersion with an arbitrary version. + * + * @param sqlServerFullVersion the full version string, + * for example "15.00.3049.1.v1" + * @param sqlServerMajorVersion the major version of the engine, + * for example "15.0" + */ + public static of(sqlServerFullVersion: string, sqlServerMajorVersion: string): SqlServerEngineVersion { + return new SqlServerEngineVersion(sqlServerFullVersion, sqlServerMajorVersion); + } + + /** The full version string, for example, "15.00.3049.1.v1". */ + public readonly sqlServerFullVersion: string; + /** The major version of the engine, for example, "15.0". */ + public readonly sqlServerMajorVersion: string; + + private constructor(sqlServerFullVersion: string, sqlServerMajorVersion: string) { + this.sqlServerFullVersion = sqlServerFullVersion; + this.sqlServerMajorVersion = sqlServerMajorVersion; + } +} + +interface SqlServerInstanceEngineProps { + /** The exact version of the engine to use. */ + readonly version: SqlServerEngineVersion; +} + +interface SqlServerInstanceEngineBaseProps { + readonly engineType: string; + readonly version?: SqlServerEngineVersion; +} + +abstract class SqlServerInstanceEngineBase extends InstanceEngineBase { + constructor(props: SqlServerInstanceEngineBaseProps) { + super({ + ...props, + singleUserRotationApplication: secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_SINGLE_USER, + multiUserRotationApplication: secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_MULTI_USER, + version: props.version + ? { + fullVersion: props.version.sqlServerFullVersion, + majorVersion: props.version.sqlServerMajorVersion, + } + : undefined, + parameterGroupFamily: props.version ? `${props.engineType}-${props.version.sqlServerMajorVersion}` : undefined, + }); + } + + public bindToInstance(_scope: core.Construct, _options: InstanceEngineBindOptions): InstanceEngineConfig { + return { + }; + } +} + +/** + * Properties for SQL Server Standard Edition instance engines. + * Used in {@link DatabaseInstanceEngine.sqlServerSe}. + */ +export interface SqlServerSeInstanceEngineProps extends SqlServerInstanceEngineProps { +} + +class SqlServerSeInstanceEngine extends SqlServerInstanceEngineBase { + constructor(version?: SqlServerEngineVersion) { + super({ + engineType: 'sqlserver-se', + version, + }); + } +} + +/** + * Properties for SQL Server Express Edition instance engines. + * Used in {@link DatabaseInstanceEngine.sqlServerEx}. + */ +export interface SqlServerExInstanceEngineProps extends SqlServerInstanceEngineProps { +} + +class SqlServerExInstanceEngine extends SqlServerInstanceEngineBase { + constructor(version?: SqlServerEngineVersion) { + super({ + engineType: 'sqlserver-ex', + version, + }); + } +} + +/** + * Properties for SQL Server Web Edition instance engines. + * Used in {@link DatabaseInstanceEngine.sqlServerWeb}. + */ +export interface SqlServerWebInstanceEngineProps extends SqlServerInstanceEngineProps { +} + +class SqlServerWebInstanceEngine extends SqlServerInstanceEngineBase { + constructor(version?: SqlServerEngineVersion) { + super({ + engineType: 'sqlserver-web', + version, + }); + } +} + +/** + * Properties for SQL Server Enterprise Edition instance engines. + * Used in {@link DatabaseInstanceEngine.sqlServerEe}. + */ +export interface SqlServerEeInstanceEngineProps extends SqlServerInstanceEngineProps { +} + +class SqlServerEeInstanceEngine extends SqlServerInstanceEngineBase { + constructor(version?: SqlServerEngineVersion) { + super({ + engineType: 'sqlserver-ee', + version, + }); + } +} + +/** + * A database instance engine. Provides mapping to DatabaseEngine used for + * secret rotation. + */ +export class DatabaseInstanceEngine { + /** + * The unversioned 'mariadb' instance engine. + * + * @deprecated using unversioned engines is an availability risk. + * We recommend using versioned engines created using the {@link mariaDb()} method + */ + public static readonly MARIADB: IInstanceEngine = new MariaDbInstanceEngine(); + + /** + * The unversioned 'mysql' instance engine. + * + * @deprecated using unversioned engines is an availability risk. + * We recommend using versioned engines created using the {@link mysql()} method + */ + public static readonly MYSQL: IInstanceEngine = new MySqlInstanceEngine(); + + /** + * The unversioned 'oracle-ee' instance engine. + * + * @deprecated using unversioned engines is an availability risk. + * We recommend using versioned engines created using the {@link oracleEe()} method + */ + public static readonly ORACLE_EE: IInstanceEngine = new OracleEeInstanceEngine(); + + /** + * The unversioned 'oracle-se2' instance engine. + * + * @deprecated using unversioned engines is an availability risk. + * We recommend using versioned engines created using the {@link oracleSe2()} method + */ + public static readonly ORACLE_SE2: IInstanceEngine = new OracleSe2InstanceEngine(); + + /** + * The unversioned 'oracle-se1' instance engine. + * + * @deprecated using unversioned engines is an availability risk. + * We recommend using versioned engines created using the {@link oracleSe1()} method + */ + public static readonly ORACLE_SE1: IInstanceEngine = new OracleSe1InstanceEngine(); + + /** + * The unversioned 'oracle-se' instance engine. + * + * @deprecated using unversioned engines is an availability risk. + * We recommend using versioned engines created using the {@link oracleSe()} method + */ + public static readonly ORACLE_SE: IInstanceEngine = new OracleSeInstanceEngine(); + + /** + * The unversioned 'postgres' instance engine. + * + * @deprecated using unversioned engines is an availability risk. + * We recommend using versioned engines created using the {@link postgres()} method + */ + public static readonly POSTGRES: IInstanceEngine = new PostgresInstanceEngine(); + + /** + * The unversioned 'sqlserver-ee' instance engine. + * + * @deprecated using unversioned engines is an availability risk. + * We recommend using versioned engines created using the {@link sqlServerEe()} method + */ + public static readonly SQL_SERVER_EE: IInstanceEngine = new SqlServerEeInstanceEngine(); + + /** + * The unversioned 'sqlserver-se' instance engine. + * + * @deprecated using unversioned engines is an availability risk. + * We recommend using versioned engines created using the {@link sqlServerSe()} method + */ + public static readonly SQL_SERVER_SE: IInstanceEngine = new SqlServerSeInstanceEngine(); + + /** + * The unversioned 'sqlserver-ex' instance engine. + * + * @deprecated using unversioned engines is an availability risk. + * We recommend using versioned engines created using the {@link sqlServerEx()} method + */ + public static readonly SQL_SERVER_EX: IInstanceEngine = new SqlServerExInstanceEngine(); + + /** + * The unversioned 'sqlserver-web' instance engine. + * + * @deprecated using unversioned engines is an availability risk. + * We recommend using versioned engines created using the {@link sqlServerWeb()} method + */ + public static readonly SQL_SERVER_WEB: IInstanceEngine = new SqlServerWebInstanceEngine(); + + /** Creates a new MariaDB instance engine. */ + public static mariaDb(props: MariaDbInstanceEngineProps): IInstanceEngine { + return new MariaDbInstanceEngine(props.version); + } + + /** Creates a new MySQL instance engine. */ + public static mysql(props: MySqlInstanceEngineProps): IInstanceEngine { + return new MySqlInstanceEngine(props.version); + } + + /** Creates a new PostgreSQL instance engine. */ + public static postgres(props: PostgresInstanceEngineProps): IInstanceEngine { + return new PostgresInstanceEngine(props.version); + } + + /** Creates a new Oracle Standard Edition instance engine. */ + public static oracleSe(props: OracleSeInstanceEngineProps): IInstanceEngine { + return new OracleSeInstanceEngine(props.version); + } + + /** Creates a new Oracle Standard Edition 1 instance engine. */ + public static oracleSe1(props: OracleSe1InstanceEngineProps): IInstanceEngine { + return new OracleSe1InstanceEngine(props.version); + } + + /** Creates a new Oracle Standard Edition 1 instance engine. */ + public static oracleSe2(props: OracleSe2InstanceEngineProps): IInstanceEngine { + return new OracleSe2InstanceEngine(props.version); + } + + /** Creates a new Oracle Enterprise Edition instance engine. */ + public static oracleEe(props: OracleEeInstanceEngineProps): IInstanceEngine { + return new OracleEeInstanceEngine(props.version); + } + + /** Creates a new SQL Server Standard Edition instance engine. */ + public static sqlServerSe(props: SqlServerSeInstanceEngineProps): IInstanceEngine { + return new SqlServerSeInstanceEngine(props.version); + } + + /** Creates a new SQL Server Express Edition instance engine. */ + public static sqlServerEx(props: SqlServerExInstanceEngineProps): IInstanceEngine { + return new SqlServerExInstanceEngine(props.version); + } + + /** Creates a new SQL Server Web Edition instance engine. */ + public static sqlServerWeb(props: SqlServerWebInstanceEngineProps): IInstanceEngine { + return new SqlServerWebInstanceEngine(props.version); + } + + /** Creates a new SQL Server Enterprise Edition instance engine. */ + public static sqlServerEe(props: SqlServerEeInstanceEngineProps): IInstanceEngine { + return new SqlServerEeInstanceEngine(props.version); + } +} diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index c2fc8d4b0e8b7..0890b26b705d2 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -8,9 +8,10 @@ import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import { CfnDeletionPolicy, Construct, Duration, IResource, Lazy, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/core'; import { DatabaseSecret } from './database-secret'; import { Endpoint } from './endpoint'; +import { IInstanceEngine } from './instance-engine'; import { IOptionGroup } from './option-group'; import { IParameterGroup } from './parameter-group'; -import { DatabaseClusterEngine, RotationMultiUserOptions } from './props'; +import { RotationMultiUserOptions } from './props'; import { DatabaseProxy, DatabaseProxyOptions, ProxyTarget } from './proxy'; import { CfnDBInstance, CfnDBInstanceProps, CfnDBSubnetGroup } from './rds.generated'; @@ -164,90 +165,6 @@ export abstract class DatabaseInstanceBase extends Resource implements IDatabase } } -/** - * A database instance engine. Provides mapping to DatabaseEngine used for - * secret rotation. - */ -export class DatabaseInstanceEngine extends DatabaseClusterEngine { - /* tslint:disable max-line-length */ - public static readonly MARIADB = new DatabaseInstanceEngine('mariadb', secretsmanager.SecretRotationApplication.MARIADB_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.MARIADB_ROTATION_MULTI_USER, [ - { engineMajorVersion: '10.0', parameterGroupFamily: 'mariadb10.0' }, - { engineMajorVersion: '10.1', parameterGroupFamily: 'mariadb10.1' }, - { engineMajorVersion: '10.2', parameterGroupFamily: 'mariadb10.2' }, - { engineMajorVersion: '10.3', parameterGroupFamily: 'mariadb10.3' }, - ]); - - public static readonly MYSQL = new DatabaseInstanceEngine('mysql', secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, [ - { engineMajorVersion: '5.6', parameterGroupFamily: 'mysql5.6' }, - { engineMajorVersion: '5.7', parameterGroupFamily: 'mysql5.7' }, - { engineMajorVersion: '8.0', parameterGroupFamily: 'mysql8.0' }, - ]); - - public static readonly ORACLE_EE = new DatabaseInstanceEngine('oracle-ee', secretsmanager.SecretRotationApplication.ORACLE_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.ORACLE_ROTATION_MULTI_USER, [ - { engineMajorVersion: '11.2', parameterGroupFamily: 'oracle-ee-11.2' }, - { engineMajorVersion: '12.1', parameterGroupFamily: 'oracle-ee-12.1' }, - { engineMajorVersion: '12.2', parameterGroupFamily: 'oracle-ee-12.2' }, - { engineMajorVersion: '18', parameterGroupFamily: 'oracle-ee-18' }, - { engineMajorVersion: '19', parameterGroupFamily: 'oracle-ee-19' }, - ]); - - public static readonly ORACLE_SE2 = new DatabaseInstanceEngine('oracle-se2', secretsmanager.SecretRotationApplication.ORACLE_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.ORACLE_ROTATION_MULTI_USER, [ - { engineMajorVersion: '12.1', parameterGroupFamily: 'oracle-se2-12.1' }, - { engineMajorVersion: '12.2', parameterGroupFamily: 'oracle-se2-12.2' }, - { engineMajorVersion: '18', parameterGroupFamily: 'oracle-se2-18' }, - { engineMajorVersion: '19', parameterGroupFamily: 'oracle-se2-19' }, - ]); - - public static readonly ORACLE_SE1 = new DatabaseInstanceEngine('oracle-se1', secretsmanager.SecretRotationApplication.ORACLE_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.ORACLE_ROTATION_MULTI_USER, [ - { engineMajorVersion: '11.2', parameterGroupFamily: 'oracle-se1-11.2' }, - ]); - - public static readonly ORACLE_SE = new DatabaseInstanceEngine('oracle-se', secretsmanager.SecretRotationApplication.ORACLE_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.ORACLE_ROTATION_MULTI_USER, [ - { engineMajorVersion: '11.2', parameterGroupFamily: 'oracle-se-11.2' }, - ]); - - public static readonly POSTGRES = new DatabaseInstanceEngine('postgres', secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_MULTI_USER, [ - { engineMajorVersion: '9.3', parameterGroupFamily: 'postgres9.3' }, - { engineMajorVersion: '9.4', parameterGroupFamily: 'postgres9.4' }, - { engineMajorVersion: '9.5', parameterGroupFamily: 'postgres9.5' }, - { engineMajorVersion: '9.6', parameterGroupFamily: 'postgres9.6' }, - { engineMajorVersion: '10', parameterGroupFamily: 'postgres10' }, - { engineMajorVersion: '11', parameterGroupFamily: 'postgres11' }, - ]); - - public static readonly SQL_SERVER_EE = new DatabaseInstanceEngine('sqlserver-ee', secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_MULTI_USER, [ - { engineMajorVersion: '11', parameterGroupFamily: 'sqlserver-ee-11.0' }, - { engineMajorVersion: '12', parameterGroupFamily: 'sqlserver-ee-12.0' }, - { engineMajorVersion: '13', parameterGroupFamily: 'sqlserver-ee-13.0' }, - { engineMajorVersion: '14', parameterGroupFamily: 'sqlserver-ee-14.0' }, - ]); - - public static readonly SQL_SERVER_SE = new DatabaseInstanceEngine('sqlserver-se', secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_MULTI_USER, [ - { engineMajorVersion: '11', parameterGroupFamily: 'sqlserver-se-11.0' }, - { engineMajorVersion: '12', parameterGroupFamily: 'sqlserver-se-12.0' }, - { engineMajorVersion: '13', parameterGroupFamily: 'sqlserver-se-13.0' }, - { engineMajorVersion: '14', parameterGroupFamily: 'sqlserver-se-14.0' }, - ]); - - public static readonly SQL_SERVER_EX = new DatabaseInstanceEngine('sqlserver-ex', secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_MULTI_USER, [ - { engineMajorVersion: '11', parameterGroupFamily: 'sqlserver-ex-11.0' }, - { engineMajorVersion: '12', parameterGroupFamily: 'sqlserver-ex-12.0' }, - { engineMajorVersion: '13', parameterGroupFamily: 'sqlserver-ex-13.0' }, - { engineMajorVersion: '14', parameterGroupFamily: 'sqlserver-ex-14.0' }, - ]); - - public static readonly SQL_SERVER_WEB = new DatabaseInstanceEngine('sqlserver-web', secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_MULTI_USER, [ - { engineMajorVersion: '11', parameterGroupFamily: 'sqlserver-web-11.0' }, - { engineMajorVersion: '12', parameterGroupFamily: 'sqlserver-web-12.0' }, - { engineMajorVersion: '13', parameterGroupFamily: 'sqlserver-web-13.0' }, - { engineMajorVersion: '14', parameterGroupFamily: 'sqlserver-web-14.0' }, - ]); - /* tslint:enable max-line-length */ - - /** To make it a compile-time error to pass a DatabaseClusterEngine where a DatabaseInstanceEngine is expected. */ - public readonly isDatabaseInstanceEngine = true; -} - /** * The license model. */ @@ -326,11 +243,6 @@ export enum PerformanceInsightRetention { * Construction properties for a DatabaseInstanceNew */ export interface DatabaseInstanceNewProps { - /** - * The name of the compute and memory capacity classes. - */ - readonly instanceType: ec2.InstanceType; - /** * Specifies if the database instance is a multiple Availability Zone deployment. * @@ -527,7 +439,6 @@ export interface DatabaseInstanceNewProps { */ readonly autoMinorVersionUpgrade?: boolean; - // tslint:disable:max-line-length /** * The weekly time range (in UTC) during which system maintenance can occur. * @@ -538,7 +449,6 @@ export interface DatabaseInstanceNewProps { * time for each AWS Region, occurring on a random day of the week. To see * the time blocks available, see https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.Maintenance.html#Concepts.DBMaintenance */ - // tslint:enable:max-line-length readonly preferredMaintenanceWindow?: string; /** @@ -575,6 +485,8 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData public readonly connections: ec2.Connections; + protected abstract readonly instanceType: ec2.InstanceType; + protected readonly vpcPlacement?: ec2.SubnetSelection; protected readonly newCfnProps: CfnDBInstanceProps; @@ -626,7 +538,7 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData availabilityZone: props.multiAz ? undefined : props.availabilityZone, backupRetentionPeriod: props.backupRetention ? props.backupRetention.toDays() : undefined, copyTagsToSnapshot: props.copyTagsToSnapshot !== undefined ? props.copyTagsToSnapshot : true, - dbInstanceClass: `db.${props.instanceType}`, + dbInstanceClass: Lazy.stringValue({ produce: () => `db.${this.instanceType}` }), dbInstanceIdentifier: props.instanceIdentifier, dbSubnetGroupName: subnetGroup.ref, deleteAutomatedBackups: props.deleteAutomatedBackups, @@ -676,22 +588,21 @@ export interface DatabaseInstanceSourceProps extends DatabaseInstanceNewProps { /** * The database engine. */ - readonly engine: DatabaseInstanceEngine; + readonly engine: IInstanceEngine; /** - * The license model. + * The name of the compute and memory capacity for the instance. * - * @default - RDS default license model + * @default - m5.large (or, more specifically, db.m5.large) */ - readonly licenseModel?: LicenseModel; + readonly instanceType?: ec2.InstanceType; /** - * The engine version. To prevent automatic upgrades, be sure to specify the - * full version number. + * The license model. * - * @default - RDS default engine version + * @default - RDS default license model */ - readonly engineVersion?: string; + readonly licenseModel?: LicenseModel; /** * Whether to allow major version upgrades. @@ -753,6 +664,7 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa public abstract readonly secret?: secretsmanager.ISecret; protected readonly sourceCfnProps: CfnDBInstanceProps; + protected readonly instanceType: ec2.InstanceType; private readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; private readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; @@ -763,20 +675,18 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa this.singleUserRotationApplication = props.engine.singleUserRotationApplication; this.multiUserRotationApplication = props.engine.multiUserRotationApplication; - const timezoneSupport = [ DatabaseInstanceEngine.SQL_SERVER_EE, DatabaseInstanceEngine.SQL_SERVER_EX, - DatabaseInstanceEngine.SQL_SERVER_SE, DatabaseInstanceEngine.SQL_SERVER_WEB ]; - if (props.timezone && !timezoneSupport.includes(props.engine)) { - throw new Error(`timezone property can be configured only for Microsoft SQL Server, not ${props.engine.name}`); - } + props.engine.bindToInstance(this, props); + this.instanceType = props.instanceType ?? ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE); + const instanceParameterGroupConfig = props.parameterGroup?.bindToInstance({}); this.sourceCfnProps = { ...this.newCfnProps, allocatedStorage: props.allocatedStorage ? props.allocatedStorage.toString() : '100', allowMajorVersionUpgrade: props.allowMajorVersionUpgrade, dbName: props.databaseName, - dbParameterGroupName: props.parameterGroup && props.parameterGroup.parameterGroupName, - engine: props.engine.name, - engineVersion: props.engineVersion, + dbParameterGroupName: instanceParameterGroupConfig?.parameterGroupName, + engine: props.engine.engineType, + engineVersion: props.engine.engineVersion?.fullVersion, licenseModel: props.licenseModel, timezone: props.timezone, }; @@ -1012,6 +922,11 @@ export class DatabaseInstanceFromSnapshot extends DatabaseInstanceSource impleme * Construction properties for a DatabaseInstanceReadReplica. */ export interface DatabaseInstanceReadReplicaProps extends DatabaseInstanceNewProps { + /** + * The name of the compute and memory capacity classes. + */ + readonly instanceType: ec2.InstanceType; + /** * The source database instance. * @@ -1046,6 +961,7 @@ export class DatabaseInstanceReadReplica extends DatabaseInstanceNew implements public readonly dbInstanceEndpointAddress: string; public readonly dbInstanceEndpointPort: string; public readonly instanceEndpoint: Endpoint; + protected readonly instanceType: ec2.InstanceType; constructor(scope: Construct, id: string, props: DatabaseInstanceReadReplicaProps) { super(scope, id, props); @@ -1058,6 +974,7 @@ export class DatabaseInstanceReadReplica extends DatabaseInstanceNew implements storageEncrypted: props.storageEncryptionKey ? true : props.storageEncrypted, }); + this.instanceType = props.instanceType; this.instanceIdentifier = instance.ref; this.dbInstanceEndpointAddress = instance.attrEndpointAddress; this.dbInstanceEndpointPort = instance.attrEndpointPort; diff --git a/packages/@aws-cdk/aws-rds/lib/option-group.ts b/packages/@aws-cdk/aws-rds/lib/option-group.ts index 19ca1781bccda..418c71d1c7c6e 100644 --- a/packages/@aws-cdk/aws-rds/lib/option-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/option-group.ts @@ -1,6 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import { Construct, IResource, Resource } from '@aws-cdk/core'; -import { DatabaseInstanceEngine } from './instance'; +import { IInstanceEngine } from './instance-engine'; import { CfnOptionGroup } from './rds.generated'; /** @@ -62,13 +62,7 @@ export interface OptionGroupProps { /** * The database engine that this option group is associated with. */ - readonly engine: DatabaseInstanceEngine; - - /** - * The major version number of the database engine that this option group - * is associated with. - */ - readonly majorEngineVersion: string; + readonly engine: IInstanceEngine; /** * A description of the option group. @@ -110,10 +104,14 @@ export class OptionGroup extends Resource implements IOptionGroup { constructor(scope: Construct, id: string, props: OptionGroupProps) { super(scope, id); + const majorEngineVersion = props.engine.engineVersion?.majorVersion; + if (!majorEngineVersion) { + throw new Error("OptionGroup cannot be used with an engine that doesn't specify a version"); + } const optionGroup = new CfnOptionGroup(this, 'Resource', { - engineName: props.engine.name, - majorEngineVersion: props.majorEngineVersion, - optionGroupDescription: props.description || `Option group for ${props.engine.name} ${props.majorEngineVersion}`, + engineName: props.engine.engineType, + majorEngineVersion, + optionGroupDescription: props.description || `Option group for ${props.engine.engineType} ${majorEngineVersion}`, optionConfigurations: this.renderConfigurations(props.configurations), }); diff --git a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts index 40cddd310fa0c..bdd4c05525889 100644 --- a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts @@ -1,54 +1,63 @@ import { Construct, IResource, Lazy, Resource } from '@aws-cdk/core'; +import { IEngine } from './engine'; import { CfnDBClusterParameterGroup, CfnDBParameterGroup } from './rds.generated'; /** - * A parameter group + * Options for {@link IParameterGroup.bindToCluster}. + * Empty for now, but can be extended later. */ -export interface IParameterGroup extends IResource { - /** - * The name of this parameter group - * - * @attribute - */ +export interface ParameterGroupClusterBindOptions { +} + +/** + * The type returned from {@link IParameterGroup.bindToCluster}. + */ +export interface ParameterGroupClusterConfig { + /** The name of this parameter group. */ readonly parameterGroupName: string; } /** - * A new cluster or instance parameter group + * Options for {@link IParameterGroup.bindToInstance}. + * Empty for now, but can be extended later. */ -abstract class ParameterGroupBase extends Resource implements IParameterGroup { - /** - * Imports a parameter group - */ - public static fromParameterGroupName(scope: Construct, id: string, parameterGroupName: string): IParameterGroup { - class Import extends Resource implements IParameterGroup { - public readonly parameterGroupName = parameterGroupName; - } - return new Import(scope, id); - } +export interface ParameterGroupInstanceBindOptions { +} + +/** + * The type returned from {@link IParameterGroup.bindToInstance}. + */ +export interface ParameterGroupInstanceConfig { + /** The name of this parameter group. */ + readonly parameterGroupName: string; +} +/** + * A parameter group. + * Represents both a cluster parameter group, + * and an instance parameter group. + */ +export interface IParameterGroup extends IResource { /** - * The name of the parameter group + * Method called when this Parameter Group is used when defining a database cluster. */ - public abstract readonly parameterGroupName: string; + bindToCluster(options: ParameterGroupClusterBindOptions): ParameterGroupClusterConfig; /** - * Parameters of the parameter group + * Method called when this Parameter Group is used when defining a database instance. */ - protected parameters?: { [key: string]: string } = {}; + bindToInstance(options: ParameterGroupInstanceBindOptions): ParameterGroupInstanceConfig; /** - * Add a parameter to this parameter group + * Adds a parameter to this group. + * If this is an imported parameter group, + * this method does nothing. * - * @param key The key of the parameter to be added - * @param value The value of the parameter to be added + * @returns true if the parameter was actually added + * (i.e., this ParameterGroup is not imported), + * false otherwise */ - public addParameter(key: string, value: string) { - if (!this.parameters) { - this.parameters = {}; - } - this.parameters[key] = value; - } + addParameter(key: string, value: string): boolean; } /** @@ -56,9 +65,9 @@ abstract class ParameterGroupBase extends Resource implements IParameterGroup { */ export interface ParameterGroupProps { /** - * Database family of this parameter group + * The database engine for this parameter group. */ - readonly family: string; + readonly engine: IEngine; /** * Description for this parameter group @@ -76,60 +85,89 @@ export interface ParameterGroupProps { } /** - * A parameter group + * A parameter group. + * Represents both a cluster parameter group, + * and an instance parameter group. * * @resource AWS::RDS::DBParameterGroup */ -export class ParameterGroup extends ParameterGroupBase { +export class ParameterGroup extends Resource implements IParameterGroup { /** - * The name of the parameter group + * Imports a parameter group */ - public readonly parameterGroupName: string; - - constructor(scope: Construct, id: string, props: ParameterGroupProps) { - super(scope, id); + public static fromParameterGroupName(scope: Construct, id: string, parameterGroupName: string): IParameterGroup { + class Import extends Resource implements IParameterGroup { + public bindToCluster(_options: ParameterGroupClusterBindOptions): ParameterGroupClusterConfig { + return { parameterGroupName }; + } - this.parameters = props.parameters ? props.parameters : {}; + public bindToInstance(_options: ParameterGroupInstanceBindOptions): ParameterGroupInstanceConfig { + return { parameterGroupName }; + } - const resource = new CfnDBParameterGroup(this, 'Resource', { - description: props.description || `Parameter group for ${props.family}`, - family: props.family, - parameters: Lazy.anyValue({ produce: () => this.parameters }), - }); + public addParameter(_key: string, _value: string): boolean { + return false; + } + } - this.parameterGroupName = resource.ref; + return new Import(scope, id); } -} -/** - * Construction properties for a ClusterParameterGroup - */ -// tslint:disable-next-line:no-empty-interface -export interface ClusterParameterGroupProps extends ParameterGroupProps { + private readonly parameters: { [key: string]: string }; + private readonly family: string; + private readonly description?: string; -} -/** - * A cluster parameter group - * - * @resource AWS::RDS::DBClusterParameterGroup - */ -export class ClusterParameterGroup extends ParameterGroupBase { - /** - * The name of the parameter group - */ - public readonly parameterGroupName: string; + private clusterCfnGroup?: CfnDBClusterParameterGroup; + private instanceCfnGroup?: CfnDBParameterGroup; - constructor(scope: Construct, id: string, props: ClusterParameterGroupProps) { + constructor(scope: Construct, id: string, props: ParameterGroupProps) { super(scope, id); - this.parameters = props.parameters ? props.parameters : {}; + const family = props.engine.parameterGroupFamily; + if (!family) { + throw new Error("ParameterGroup cannot be used with an engine that doesn't specify a version"); + } + this.family = family; + this.description = props.description; + this.parameters = props.parameters ?? {}; + } + + public bindToCluster(_options: ParameterGroupClusterBindOptions): ParameterGroupClusterConfig { + if (!this.clusterCfnGroup) { + const id = this.instanceCfnGroup ? 'ClusterParameterGroup' : 'Resource'; + this.clusterCfnGroup = new CfnDBClusterParameterGroup(this, id, { + description: this.description || `Cluster parameter group for ${this.family}`, + family: this.family, + parameters: Lazy.anyValue({ produce: () => this.parameters }), + }); + } + return { + parameterGroupName: this.clusterCfnGroup.ref, + }; + } - const resource = new CfnDBClusterParameterGroup(this, 'Resource', { - description: props.description || `Cluster parameter group for ${props.family}`, - family: props.family, - parameters: Lazy.anyValue({ produce: () => this.parameters }), - }); + public bindToInstance(_options: ParameterGroupInstanceBindOptions): ParameterGroupInstanceConfig { + if (!this.instanceCfnGroup) { + const id = this.clusterCfnGroup ? 'InstanceParameterGroup' : 'Resource'; + this.instanceCfnGroup = new CfnDBParameterGroup(this, id, { + description: this.description || `Parameter group for ${this.family}`, + family: this.family, + parameters: Lazy.anyValue({ produce: () => this.parameters }), + }); + } + return { + parameterGroupName: this.instanceCfnGroup.ref, + }; + } - this.parameterGroupName = resource.ref; + /** + * Add a parameter to this parameter group + * + * @param key The key of the parameter to be added + * @param value The value of the parameter to be added + */ + public addParameter(key: string, value: string): boolean { + this.parameters[key] = value; + return true; } } diff --git a/packages/@aws-cdk/aws-rds/lib/private/version.ts b/packages/@aws-cdk/aws-rds/lib/private/version.ts deleted file mode 100644 index 9e7ce227c2b5c..0000000000000 --- a/packages/@aws-cdk/aws-rds/lib/private/version.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Compare two version strings. Fails if the comparison has to tiebreak with non-numbers. - * @returns 0 if both are same, 1 if 'a' is later version than 'b' and -1 if 'b' is later version than 'a' - */ -export function compare(a: string, b: string) { - const aParts = a.split('.'); - const bParts = b.split('.'); - for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) { - if (i === aParts.length) { return -1; } - if (i === bParts.length) { return 1; } - - if (!aParts[i] || !bParts[i] || isNaN(aParts[i] as any) || isNaN(bParts[i] as any)) { - throw new Error(`Can only compare version strings with numbers. Received [${a}] and [${b}].`); - } - const partCompare = parseInt(aParts[i], 10) - parseInt(bParts[i], 10); - - if (partCompare < 0) { return -1; } - if (partCompare > 0) { return 1; } - } - return 0; -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index e52841da926c0..c600c79de7037 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -3,99 +3,17 @@ import * as kms from '@aws-cdk/aws-kms'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import { Duration, SecretValue } from '@aws-cdk/core'; import { IParameterGroup } from './parameter-group'; -import { compare } from './private/version'; - -/** - * Engine major version and parameter group family pairs. - */ -export interface ParameterGroupFamily { - /** - * The engine major version name - */ - readonly engineMajorVersion: string; - - /** - * The parameter group family name - */ - readonly parameterGroupFamily: string -} - -/** - * A database cluster engine. Provides mapping to the serverless application - * used for secret rotation. - */ -export class DatabaseClusterEngine { - /* tslint:disable max-line-length */ - public static readonly AURORA = new DatabaseClusterEngine('aurora', secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, [ - { engineMajorVersion: '5.6', parameterGroupFamily: 'aurora5.6' }, - ]); - - public static readonly AURORA_MYSQL = new DatabaseClusterEngine('aurora-mysql', secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, [ - { engineMajorVersion: '5.7', parameterGroupFamily: 'aurora-mysql5.7' }, - ]); - - public static readonly AURORA_POSTGRESQL = new DatabaseClusterEngine('aurora-postgresql', secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_MULTI_USER, [ - { engineMajorVersion: '9.6', parameterGroupFamily: 'aurora-postgresql9.6'}, - { engineMajorVersion: '10', parameterGroupFamily: 'aurora-postgresql10' }, - { engineMajorVersion: '11', parameterGroupFamily: 'aurora-postgresql11'}, - ]); - /* tslint:enable max-line-length */ - - /** - * The engine. - */ - public readonly name: string; - - /** - * The single user secret rotation application. - */ - public readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; - - /** - * The multi user secret rotation application. - */ - public readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; - - private readonly parameterGroupFamilies?: ParameterGroupFamily[]; - - // tslint:disable-next-line max-line-length - constructor(name: string, singleUserRotationApplication: secretsmanager.SecretRotationApplication, multiUserRotationApplication: secretsmanager.SecretRotationApplication, parameterGroupFamilies?: ParameterGroupFamily[]) { - this.name = name; - this.singleUserRotationApplication = singleUserRotationApplication; - this.multiUserRotationApplication = multiUserRotationApplication; - this.parameterGroupFamilies = parameterGroupFamilies; - } - - /** - * Get the latest parameter group family for this engine. Latest is determined using semver on the engine major version. - * When `engineVersion` is specified, return the parameter group family corresponding to that engine version. - * Return undefined if no parameter group family is defined for this engine or for the requested `engineVersion`. - */ - public parameterGroupFamily(engineVersion?: string): string | undefined { - if (this.parameterGroupFamilies === undefined) { return undefined; } - if (engineVersion) { - const family = this.parameterGroupFamilies.find(x => engineVersion.startsWith(x.engineMajorVersion)); - if (family) { - return family.parameterGroupFamily; - } - } else if (this.parameterGroupFamilies.length > 0) { - const sorted = this.parameterGroupFamilies.slice().sort((a, b) => { - return compare(a.engineMajorVersion, b.engineMajorVersion); - }).reverse(); - return sorted[0].parameterGroupFamily; - } - return undefined; - } -} /** * Instance properties for database instances */ export interface InstanceProps { /** - * What type of instance to start for the replicas + * What type of instance to start for the replicas. + * + * @default - t3.medium (or, more precisely, db.t3.medium) */ - readonly instanceType: ec2.InstanceType; + readonly instanceType?: ec2.InstanceType; /** * What subnets to run the RDS instances in. diff --git a/packages/@aws-cdk/aws-rds/lib/proxy.ts b/packages/@aws-cdk/aws-rds/lib/proxy.ts index 9733056d8ed3b..e6e0ba5d66679 100644 --- a/packages/@aws-cdk/aws-rds/lib/proxy.ts +++ b/packages/@aws-cdk/aws-rds/lib/proxy.ts @@ -235,10 +235,9 @@ export interface DatabaseProxyOptions { /** * The secret that the proxy uses to authenticate to the RDS DB instance or Aurora DB cluster. * These secrets are stored within Amazon Secrets Manager. - * - * @default - no secret + * One or more secrets are required. */ - readonly secret: secretsmanager.ISecret; + readonly secrets: secretsmanager.ISecret[]; /** * One or more VPC security groups to associate with the new proxy. @@ -379,20 +378,26 @@ export class DatabaseProxy extends cdk.Resource assumedBy: new iam.ServicePrincipal('rds.amazonaws.com'), }); - props.secret.grantRead(role); + for (const secret of props.secrets) { + secret.grantRead(role); + } this.connections = new ec2.Connections({ securityGroups: props.securityGroups }); const bindResult = props.proxyTarget.bind(this); + if (props.secrets.length < 1) { + throw new Error('One or more secrets are required.'); + } + this.resource = new CfnDBProxy(this, 'Resource', { - auth: [ - { + auth: props.secrets.map(_ => { + return { authScheme: 'SECRETS', iamAuth: props.iamAuth ? 'REQUIRED' : 'DISABLED', - secretArn: props.secret.secretArn, - }, - ], + secretArn: _.secretArn, + }; + }), dbProxyName: this.physicalName, debugLogging: props.debugLogging, engineFamily: bindResult.engineFamily, diff --git a/packages/@aws-cdk/aws-rds/package.json b/packages/@aws-cdk/aws-rds/package.json index 1568b3346b09b..df41aac87053b 100644 --- a/packages/@aws-cdk/aws-rds/package.json +++ b/packages/@aws-cdk/aws-rds/package.json @@ -104,14 +104,12 @@ "awslint": { "exclude": [ "props-physical-name:@aws-cdk/aws-rds.ParameterGroupProps", - "props-physical-name:@aws-cdk/aws-rds.ClusterParameterGroupProps", "props-physical-name:@aws-cdk/aws-rds.DatabaseClusterProps", "props-physical-name:@aws-cdk/aws-rds.DatabaseInstanceProps", "props-physical-name:@aws-cdk/aws-rds.DatabaseInstanceFromSnapshotProps", "props-physical-name:@aws-cdk/aws-rds.DatabaseInstanceReadReplicaProps", "props-physical-name:@aws-cdk/aws-rds.DatabaseSecretProps", "props-physical-name:@aws-cdk/aws-rds.OptionGroupProps", - "docs-public-apis:@aws-cdk/aws-rds.DatabaseInstanceEngine.ORACLE_SE2", "docs-public-apis:@aws-cdk/aws-rds.SecretRotationApplication.semanticVersion", "docs-public-apis:@aws-cdk/aws-rds.SecretRotationApplication.applicationId", "docs-public-apis:@aws-cdk/aws-rds.SecretRotationApplication.SQLSERVER_ROTATION_SINGLE_USER", @@ -123,24 +121,11 @@ "docs-public-apis:@aws-cdk/aws-rds.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER", "docs-public-apis:@aws-cdk/aws-rds.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER", "docs-public-apis:@aws-cdk/aws-rds.SecretRotationApplication.MARIADB_ROTATION_SINGLE_USER", - "docs-public-apis:@aws-cdk/aws-rds.DatabaseClusterEngine.AURORA", - "docs-public-apis:@aws-cdk/aws-rds.DatabaseClusterEngine.AURORA_MYSQL", - "docs-public-apis:@aws-cdk/aws-rds.DatabaseClusterEngine.AURORA_POSTGRESQL", - "docs-public-apis:@aws-cdk/aws-rds.DatabaseInstanceEngine.MARIADB", - "docs-public-apis:@aws-cdk/aws-rds.DatabaseInstanceEngine.MYSQL", - "docs-public-apis:@aws-cdk/aws-rds.DatabaseInstanceEngine.ORACLE_EE", - "docs-public-apis:@aws-cdk/aws-rds.DatabaseInstanceEngine.ORACLE_SE", - "docs-public-apis:@aws-cdk/aws-rds.DatabaseInstanceEngine.ORACLE_SE1", - "docs-public-apis:@aws-cdk/aws-rds.SecretRotationApplication.MARIADB_ROTATION_MULTI_USER", - "docs-public-apis:@aws-cdk/aws-rds.DatabaseInstanceEngine.POSTGRES", - "docs-public-apis:@aws-cdk/aws-rds.DatabaseInstanceEngine.SQL_SERVER_EE", - "docs-public-apis:@aws-cdk/aws-rds.DatabaseInstanceEngine.SQL_SERVER_EX", - "docs-public-apis:@aws-cdk/aws-rds.DatabaseInstanceEngine.SQL_SERVER_SE", - "docs-public-apis:@aws-cdk/aws-rds.DatabaseInstanceEngine.SQL_SERVER_WEB" + "docs-public-apis:@aws-cdk/aws-rds.SecretRotationApplication.MARIADB_ROTATION_MULTI_USER" ] }, "stability": "experimental", - "maturity": "experimental", + "maturity": "developer-preview", "awscdkio": { "announce": false } diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts index 590a32fd7afb1..a5bdaba4883c9 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts @@ -1,16 +1,15 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import * as cdk from '@aws-cdk/core'; -import { DatabaseCluster, DatabaseClusterEngine } from '../lib'; -import { ClusterParameterGroup } from '../lib/parameter-group'; +import { DatabaseCluster, DatabaseClusterEngine, ParameterGroup } from '../lib'; const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-rds-integ'); const vpc = new ec2.Vpc(stack, 'VPC', { maxAzs: 2 }); -const params = new ClusterParameterGroup(stack, 'Params', { - family: 'aurora5.6', +const params = new ParameterGroup(stack, 'Params', { + engine: DatabaseClusterEngine.AURORA, description: 'A nice parameter group', parameters: { character_set_database: 'utf8mb4', diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts index d37fd89f9b935..f98c65a0950f3 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as targets from '@aws-cdk/aws-events-targets'; @@ -17,7 +18,7 @@ class DatabaseInstanceStack extends cdk.Stack { /// !show // Set open cursors with parameter group const parameterGroup = new rds.ParameterGroup(this, 'ParameterGroup', { - family: 'oracle-se1-11.2', + engine: rds.DatabaseInstanceEngine.ORACLE_SE1, parameters: { open_cursors: '2500', }, @@ -26,7 +27,6 @@ class DatabaseInstanceStack extends cdk.Stack { /// Add XMLDB and OEM with option group const optionGroup = new rds.OptionGroup(this, 'OptionGroup', { engine: rds.DatabaseInstanceEngine.ORACLE_SE1, - majorEngineVersion: '11.2', configurations: [ { name: 'XMLDB', diff --git a/packages/@aws-cdk/aws-rds/test/integ.proxy.ts b/packages/@aws-cdk/aws-rds/test/integ.proxy.ts index 22f5535237d77..8c2ef1879787b 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.proxy.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.proxy.ts @@ -17,7 +17,7 @@ const dbInstance = new rds.DatabaseInstance(stack, 'dbInstance', { new rds.DatabaseProxy(stack, 'dbProxy', { borrowTimeout: cdk.Duration.seconds(30), maxConnectionsPercent: 50, - secret: dbInstance.secret!, + secrets: [dbInstance.secret!], proxyTarget: rds.ProxyTarget.fromInstance(dbInstance), vpc, }); diff --git a/packages/@aws-cdk/aws-rds/test/private/test.version.ts b/packages/@aws-cdk/aws-rds/test/private/test.version.ts deleted file mode 100644 index 2977d702bd5cb..0000000000000 --- a/packages/@aws-cdk/aws-rds/test/private/test.version.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Test } from 'nodeunit'; -import { compare } from '../../lib/private/version'; - -export = { - 'compare - same versions'(test: Test) { - test.equals(compare('1', '1'), 0); - test.equals(compare('1.0', '1.0'), 0); - test.done(); - }, - - 'compare - a < b'(test: Test) { - test.equals(compare('1', '2'), -1); - test.equals(compare('1.0', '1.2'), -1); - test.equals(compare('1.3', '4'), -1); - test.equals(compare('1', '1.2'), -1); - test.equals(compare('1.0', '2.0'), -1); - test.equals(compare('4', '10'), -1); - test.done(); - }, - - 'compare - a > b'(test: Test) { - test.equals(compare('2', '1'), 1); - test.equals(compare('1.2', '1.0'), 1); - test.equals(compare('1.2', '1'), 1); - test.equals(compare('4', '1.2'), 1); - test.equals(compare('2.0', '1.0'), 1); - test.equals(compare('10', '4'), 1); - test.done(); - }, - - 'compare - NaN'(test: Test) { - test.throws(() => compare('1', ''), /only compare version strings with numbers/); - test.throws(() => compare('', '1'), /only compare version strings with numbers/); - test.throws(() => compare('', ''), /only compare version strings with numbers/); - test.throws(() => compare('4x', '1.0'), /only compare version strings with numbers/); - test.done(); - }, -}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster-engine.ts b/packages/@aws-cdk/aws-rds/test/test.cluster-engine.ts new file mode 100644 index 0000000000000..e32db06a40f9b --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/test.cluster-engine.ts @@ -0,0 +1,110 @@ +import { Test } from 'nodeunit'; +import { AuroraEngineVersion, AuroraMysqlEngineVersion, AuroraPostgresEngineVersion, DatabaseClusterEngine } from '../lib'; + +export = { + "default parameterGroupFamily for versionless Aurora cluster engine is 'aurora5.6'"(test: Test) { + // GIVEN + const engine = DatabaseClusterEngine.AURORA; + + // WHEN + const family = engine.parameterGroupFamily; + + // THEN + test.equals(family, 'aurora5.6'); + + test.done(); + }, + + "default parameterGroupFamily for versionless Aurora MySQL cluster engine is 'aurora-mysql5.7'"(test: Test) { + // GIVEN + const engine = DatabaseClusterEngine.AURORA_MYSQL; + + // WHEN + const family = engine.parameterGroupFamily; + + // THEN + test.equals(family, 'aurora-mysql5.7'); + + test.done(); + }, + + 'default parameterGroupFamily for versionless Aurora PostgreSQL is not defined'(test: Test) { + // GIVEN + const engine = DatabaseClusterEngine.AURORA_POSTGRESQL; + + // WHEN + const family = engine.parameterGroupFamily; + + // THEN + test.equals(family, undefined); + + test.done(); + }, + + 'cluster parameter group correctly determined for AURORA and given version'(test: Test) { + // GIVEN + const engine = DatabaseClusterEngine.aurora({ + version: AuroraEngineVersion.VER_1_22_2, + }); + + // WHEN + const family = engine.parameterGroupFamily; + + // THEN + test.equals(family, 'aurora5.6'); + + test.done(); + }, + + 'cluster parameter group correctly determined for AURORA_MYSQL and given version'(test: Test) { + // GIVEN + const engine = DatabaseClusterEngine.auroraMysql({ + version: AuroraMysqlEngineVersion.VER_2_07_1, + }); + + // WHEN + const family = engine.parameterGroupFamily; + + // THEN + test.equals(family, 'aurora-mysql5.7'); + + test.done(); + }, + + 'cluster parameter group correctly determined for AURORA_POSTGRESQL and given version'(test: Test) { + // GIVEN + const engine = DatabaseClusterEngine.auroraPostgres({ + version: AuroraPostgresEngineVersion.VER_11_6, + }); + + // WHEN + const family = engine.parameterGroupFamily; + + // THEN + test.equals(family, 'aurora-postgresql11'); + + test.done(); + }, + + 'parameter group family'(test: Test) { + // the PostgreSQL engine knows about the following major versions: 9.6, 10 and 11 + + test.equals(DatabaseClusterEngine.auroraPostgres({ version: AuroraPostgresEngineVersion.of('8', '8') }).parameterGroupFamily, + 'aurora-postgresql8'); + + test.equals(DatabaseClusterEngine.auroraPostgres({ version: AuroraPostgresEngineVersion.of('9', '9') }).parameterGroupFamily, + 'aurora-postgresql9'); + + test.equals(DatabaseClusterEngine.auroraPostgres({ version: AuroraPostgresEngineVersion.of('9.7', '9.7') }).parameterGroupFamily, + 'aurora-postgresql9.7'); + + test.equals(DatabaseClusterEngine.auroraPostgres({ version: AuroraPostgresEngineVersion.of('9.6', '9.6') }).parameterGroupFamily, + 'aurora-postgresql9.6'); + test.equals(DatabaseClusterEngine.auroraPostgres({ version: AuroraPostgresEngineVersion.of('9.6.1', '9.6') }).parameterGroupFamily, + 'aurora-postgresql9.6'); + test.equals(DatabaseClusterEngine.auroraPostgres({ version: AuroraPostgresEngineVersion.of('10.0', '10') }).parameterGroupFamily, + 'aurora-postgresql10'); + + test.done(); + }, +}; diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster.ts b/packages/@aws-cdk/aws-rds/test/test.cluster.ts index 8351399263c6a..85d2a6d2b75d4 100644 --- a/packages/@aws-cdk/aws-rds/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.cluster.ts @@ -5,7 +5,7 @@ import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; -import { ClusterParameterGroup, DatabaseCluster, DatabaseClusterEngine, ParameterGroup } from '../lib'; +import { AuroraMysqlEngineVersion, AuroraPostgresEngineVersion, DatabaseCluster, DatabaseClusterEngine, ParameterGroup } from '../lib'; export = { 'creating a Cluster also creates 2 DB Instances'(test: Test) { @@ -120,8 +120,8 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); // WHEN - const group = new ClusterParameterGroup(stack, 'Params', { - family: 'hello', + const group = new ParameterGroup(stack, 'Params', { + engine: DatabaseClusterEngine.AURORA, description: 'bye', parameters: { param: 'value', @@ -263,7 +263,7 @@ export = { const stack = testStack(); const vpc = new ec2.Vpc(stack, 'VPC'); const parameterGroup = new ParameterGroup(stack, 'ParameterGroup', { - family: 'hello', + engine: DatabaseClusterEngine.AURORA, parameters: { key: 'value', }, @@ -299,8 +299,9 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { - engine: DatabaseClusterEngine.AURORA_MYSQL, - engineVersion: '5.7.mysql_aurora.2.04.4', + engine: DatabaseClusterEngine.auroraMysql({ + version: AuroraMysqlEngineVersion.VER_2_04_4, + }), masterUser: { username: 'admin', }, @@ -326,8 +327,9 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { - engine: DatabaseClusterEngine.AURORA_POSTGRESQL, - engineVersion: '10.7', + engine: DatabaseClusterEngine.auroraPostgres({ + version: AuroraPostgresEngineVersion.VER_10_7, + }), masterUser: { username: 'admin', }, @@ -886,8 +888,8 @@ export = { const stack = testStack(); const vpc = new ec2.Vpc(stack, 'VPC'); - const parameterGroup = new ClusterParameterGroup(stack, 'ParameterGroup', { - family: 'family', + const parameterGroup = new ParameterGroup(stack, 'ParameterGroup', { + engine: DatabaseClusterEngine.AURORA, parameters: { key: 'value', }, @@ -933,7 +935,7 @@ export = { })); expect(stack).to(haveResource('AWS::RDS::DBClusterParameterGroup', { - Family: 'family', + Family: 'aurora5.6', Parameters: { key: 'value', aurora_load_from_s3_role: { @@ -954,7 +956,7 @@ export = { test.done(); }, - 'PostgreSQL cluster with s3 export buckets does not generate custom parameter group'(test: Test) { + 'PostgreSQL cluster with s3 export buckets does not generate custom parameter group and specifies the correct port'(test: Test) { // GIVEN const stack = testStack(); const vpc = new ec2.Vpc(stack, 'VPC'); @@ -963,7 +965,9 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { - engine: DatabaseClusterEngine.AURORA_POSTGRESQL, + engine: DatabaseClusterEngine.auroraPostgres({ + version: AuroraPostgresEngineVersion.VER_11_4, + }), instances: 1, masterUser: { username: 'admin', @@ -976,7 +980,7 @@ export = { }); // THEN - expect(stack).to(haveResource('AWS::RDS::DBCluster', { + expect(stack).to(haveResourceLike('AWS::RDS::DBCluster', { AssociatedRoles: [{ RoleArn: { 'Fn::GetAtt': [ @@ -985,6 +989,36 @@ export = { ], }, }], + DBClusterParameterGroupName: 'default.aurora-postgresql11', + Port: 5432, + })); + + expect(stack).notTo(haveResource('AWS::RDS::DBClusterParameterGroup')); + + test.done(); + }, + + 'MySQL cluster without S3 exports or imports references the correct default ParameterGroup'(test: Test) { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA_MYSQL, + instances: 1, + masterUser: { + username: 'admin', + }, + instanceProps: { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::RDS::DBCluster', { + DBClusterParameterGroupName: 'default.aurora-mysql5.7', })); expect(stack).notTo(haveResource('AWS::RDS::DBClusterParameterGroup')); diff --git a/packages/@aws-cdk/aws-rds/test/test.instance-engine.ts b/packages/@aws-cdk/aws-rds/test/test.instance-engine.ts new file mode 100644 index 0000000000000..4668cfd683163 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/test.instance-engine.ts @@ -0,0 +1,114 @@ +import { Test } from 'nodeunit'; +import * as rds from '../lib'; + +export = { + 'default parameterGroupFamily for versionless MariaDB instance engine is not defined'(test: Test) { + const engine = rds.DatabaseInstanceEngine.MARIADB; + + const family = engine.parameterGroupFamily; + + test.equals(family, undefined); + + test.done(); + }, + + 'default parameterGroupFamily for versionless MySQL instance engine is not defined'(test: Test) { + const engine = rds.DatabaseInstanceEngine.MYSQL; + + const family = engine.parameterGroupFamily; + + test.equals(family, undefined); + + test.done(); + }, + + 'default parameterGroupFamily for versionless PostgreSQL instance engine is not defined'(test: Test) { + const engine = rds.DatabaseInstanceEngine.POSTGRES; + + const family = engine.parameterGroupFamily; + + test.equals(family, undefined); + + test.done(); + }, + + "default parameterGroupFamily for versionless Oracle SE instance engine is 'oracle-se-11.2'"(test: Test) { + const engine = rds.DatabaseInstanceEngine.ORACLE_SE; + + const family = engine.parameterGroupFamily; + + test.equals(family, 'oracle-se-11.2'); + + test.done(); + }, + + "default parameterGroupFamily for versionless Oracle SE 1 instance engine is 'oracle-se1-11.2'"(test: Test) { + const engine = rds.DatabaseInstanceEngine.ORACLE_SE1; + + const family = engine.parameterGroupFamily; + + test.equals(family, 'oracle-se1-11.2'); + + test.done(); + }, + + 'default parameterGroupFamily for versionless Oracle SE 2 instance engine is not defined'(test: Test) { + const engine = rds.DatabaseInstanceEngine.ORACLE_SE2; + + const family = engine.parameterGroupFamily; + + test.equals(family, undefined); + + test.done(); + }, + + 'default parameterGroupFamily for versionless Oracle EE instance engine is not defined'(test: Test) { + const engine = rds.DatabaseInstanceEngine.ORACLE_EE; + + const family = engine.parameterGroupFamily; + + test.equals(family, undefined); + + test.done(); + }, + + 'default parameterGroupFamily for versionless SQL Server SE instance engine is not defined'(test: Test) { + const engine = rds.DatabaseInstanceEngine.SQL_SERVER_SE; + + const family = engine.parameterGroupFamily; + + test.equals(family, undefined); + + test.done(); + }, + + 'default parameterGroupFamily for versionless SQL Server EX instance engine is not defined'(test: Test) { + const engine = rds.DatabaseInstanceEngine.SQL_SERVER_EX; + + const family = engine.parameterGroupFamily; + + test.equals(family, undefined); + + test.done(); + }, + + 'default parameterGroupFamily for versionless SQL Server Web instance engine is not defined'(test: Test) { + const engine = rds.DatabaseInstanceEngine.SQL_SERVER_WEB; + + const family = engine.parameterGroupFamily; + + test.equals(family, undefined); + + test.done(); + }, + + 'default parameterGroupFamily for versionless SQL Server EE instance engine is not defined'(test: Test) { + const engine = rds.DatabaseInstanceEngine.SQL_SERVER_EE; + + const family = engine.parameterGroupFamily; + + test.equals(family, undefined); + + test.done(); + }, +}; diff --git a/packages/@aws-cdk/aws-rds/test/test.instance.ts b/packages/@aws-cdk/aws-rds/test/test.instance.ts index c22f926c95fbe..39136b7bc45a6 100644 --- a/packages/@aws-cdk/aws-rds/test/test.instance.ts +++ b/packages/@aws-cdk/aws-rds/test/test.instance.ts @@ -196,7 +196,6 @@ export = { const optionGroup = new rds.OptionGroup(stack, 'OptionGroup', { engine: rds.DatabaseInstanceEngine.ORACLE_SE1, - majorEngineVersion: '11.2', configurations: [ { name: 'XMLDB', @@ -205,7 +204,9 @@ export = { }); const parameterGroup = new rds.ParameterGroup(stack, 'ParameterGroup', { - family: 'hello', + engine: rds.DatabaseInstanceEngine.sqlServerEe({ + version: rds.SqlServerEngineVersion.VER_11, + }), description: 'desc', parameters: { key: 'value', @@ -691,7 +692,7 @@ export = { // THEN tzSupportedEngines.forEach((engine) => { - test.ok(new rds.DatabaseInstance(stack, `${engine.name}-db`, { + test.ok(new rds.DatabaseInstance(stack, `${engine.engineType}-db`, { engine, instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), masterUsername: 'master', @@ -701,7 +702,7 @@ export = { }); tzUnsupportedEngines.forEach((engine) => { - test.throws(() => new rds.DatabaseInstance(stack, `${engine.name}-db`, { + test.throws(() => new rds.DatabaseInstance(stack, `${engine.engineType}-db`, { engine, instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), masterUsername: 'master', diff --git a/packages/@aws-cdk/aws-rds/test/test.option-group.ts b/packages/@aws-cdk/aws-rds/test/test.option-group.ts index d61b1aae06e52..6152f45e881cd 100644 --- a/packages/@aws-cdk/aws-rds/test/test.option-group.ts +++ b/packages/@aws-cdk/aws-rds/test/test.option-group.ts @@ -2,7 +2,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; -import { DatabaseInstanceEngine, OptionGroup } from '../lib'; +import { DatabaseInstanceEngine, OptionGroup, OracleEngineVersion, OracleLegacyEngineVersion } from '../lib'; export = { 'create an option group'(test: Test) { @@ -11,8 +11,9 @@ export = { // WHEN new OptionGroup(stack, 'Options', { - engine: DatabaseInstanceEngine.ORACLE_SE1, - majorEngineVersion: '11.2', + engine: DatabaseInstanceEngine.oracleSe1({ + version: OracleLegacyEngineVersion.VER_11_2, + }), configurations: [ { name: 'XMLDB', @@ -42,8 +43,9 @@ export = { // WHEN const optionGroup = new OptionGroup(stack, 'Options', { - engine: DatabaseInstanceEngine.ORACLE_SE1, - majorEngineVersion: '11.2', + engine: DatabaseInstanceEngine.oracleSe({ + version: OracleLegacyEngineVersion.VER_11_2, + }), configurations: [ { name: 'OEM', @@ -56,9 +58,9 @@ export = { // THEN expect(stack).to(haveResource('AWS::RDS::OptionGroup', { - EngineName: 'oracle-se1', + EngineName: 'oracle-se', MajorEngineVersion: '11.2', - OptionGroupDescription: 'Option group for oracle-se1 11.2', + OptionGroupDescription: 'Option group for oracle-se 11.2', OptionConfigurations: [ { OptionName: 'OEM', @@ -100,8 +102,9 @@ export = { // THEN test.throws(() => new OptionGroup(stack, 'Options', { - engine: DatabaseInstanceEngine.ORACLE_SE1, - majorEngineVersion: '11.2', + engine: DatabaseInstanceEngine.oracleSe2({ + version: OracleEngineVersion.VER_12_1, + }), configurations: [ { name: 'OEM', diff --git a/packages/@aws-cdk/aws-rds/test/test.parameter-group.ts b/packages/@aws-cdk/aws-rds/test/test.parameter-group.ts index 4de1a34310768..f4a21db1fdec0 100644 --- a/packages/@aws-cdk/aws-rds/test/test.parameter-group.ts +++ b/packages/@aws-cdk/aws-rds/test/test.parameter-group.ts @@ -1,26 +1,47 @@ -import { expect, haveResource } from '@aws-cdk/assert'; +import { countResources, expect, haveResource } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; -import { ClusterParameterGroup, ParameterGroup } from '../lib'; +import { DatabaseClusterEngine, ParameterGroup } from '../lib'; export = { - 'create a parameter group'(test: Test) { + "does not create a parameter group if it wasn't bound to a cluster or instance"(test: Test) { // GIVEN const stack = new cdk.Stack(); // WHEN new ParameterGroup(stack, 'Params', { - family: 'hello', + engine: DatabaseClusterEngine.AURORA, description: 'desc', parameters: { key: 'value', }, }); + // THEN + expect(stack).to(countResources('AWS::RDS::DBParameterGroup', 0)); + expect(stack).to(countResources('AWS::RDS::DBClusterParameterGroup', 0)); + + test.done(); + }, + + 'create a parameter group when bound to an instance'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const parameterGroup = new ParameterGroup(stack, 'Params', { + engine: DatabaseClusterEngine.AURORA, + description: 'desc', + parameters: { + key: 'value', + }, + }); + parameterGroup.bindToInstance({}); + // THEN expect(stack).to(haveResource('AWS::RDS::DBParameterGroup', { Description: 'desc', - Family: 'hello', + Family: 'aurora5.6', Parameters: { key: 'value', }, @@ -29,23 +50,24 @@ export = { test.done(); }, - 'create a cluster parameter group'(test: Test) { + 'create a parameter group when bound to a cluster'(test: Test) { // GIVEN const stack = new cdk.Stack(); // WHEN - new ClusterParameterGroup(stack, 'Params', { - family: 'hello', + const parameterGroup = new ParameterGroup(stack, 'Params', { + engine: DatabaseClusterEngine.AURORA, description: 'desc', parameters: { key: 'value', }, }); + parameterGroup.bindToCluster({}); // THEN expect(stack).to(haveResource('AWS::RDS::DBClusterParameterGroup', { Description: 'desc', - Family: 'hello', + Family: 'aurora5.6', Parameters: { key: 'value', }, @@ -54,25 +76,48 @@ export = { test.done(); }, + 'creates 2 parameter groups when bound to a cluster and an instance'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const parameterGroup = new ParameterGroup(stack, 'Params', { + engine: DatabaseClusterEngine.AURORA, + description: 'desc', + parameters: { + key: 'value', + }, + }); + parameterGroup.bindToCluster({}); + parameterGroup.bindToInstance({}); + + // THEN + expect(stack).to(countResources('AWS::RDS::DBParameterGroup', 1)); + expect(stack).to(countResources('AWS::RDS::DBClusterParameterGroup', 1)); + + test.done(); + }, + 'Add an additional parameter to an existing parameter group'(test: Test) { // GIVEN const stack = new cdk.Stack(); // WHEN - const clusterParameterGroup = new ClusterParameterGroup(stack, 'Params', { - family: 'hello', + const clusterParameterGroup = new ParameterGroup(stack, 'Params', { + engine: DatabaseClusterEngine.AURORA, description: 'desc', parameters: { key1: 'value1', }, }); + clusterParameterGroup.bindToCluster({}); clusterParameterGroup.addParameter('key2', 'value2'); // THEN expect(stack).to(haveResource('AWS::RDS::DBClusterParameterGroup', { Description: 'desc', - Family: 'hello', + Family: 'aurora5.6', Parameters: { key1: 'value1', key2: 'value2', diff --git a/packages/@aws-cdk/aws-rds/test/test.props.ts b/packages/@aws-cdk/aws-rds/test/test.props.ts deleted file mode 100644 index 4e2df3c3dda52..0000000000000 --- a/packages/@aws-cdk/aws-rds/test/test.props.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { SecretRotationApplication } from '@aws-cdk/aws-secretsmanager'; -import { Test } from 'nodeunit'; -import { DatabaseClusterEngine } from '../lib'; - -export = { - 'cluster parameter group correctly determined for AURORA'(test: Test) { - // GIVEN - const engine = DatabaseClusterEngine.AURORA; - - // WHEN - const family = engine.parameterGroupFamily(); - - // THEN - test.equals(family, 'aurora5.6'); - - test.done(); - }, - - 'cluster parameter group correctly determined for AURORA_MYSQL'(test: Test) { - // GIVEN - const engine = DatabaseClusterEngine.AURORA_MYSQL; - - // WHEN - const family = engine.parameterGroupFamily(); - - // THEN - test.equals(family, 'aurora-mysql5.7'); - - test.done(); - }, - - 'cluster parameter group correctly determined for AURORA_POSTGRESQL'(test: Test) { - // GIVEN - const engine = DatabaseClusterEngine.AURORA_POSTGRESQL; - - // WHEN - const family = engine.parameterGroupFamily(); - - // THEN - test.equals(family, 'aurora-postgresql11'); - - test.done(); - }, - - 'cluster parameter group correctly determined for AURORA and given version'(test: Test) { - // GIVEN - const engine = DatabaseClusterEngine.AURORA; - - // WHEN - const family = engine.parameterGroupFamily('5.6.mysql_aurora.1.22.2'); - - // THEN - test.equals(family, 'aurora5.6'); - - test.done(); - }, - - 'cluster parameter group correctly determined for AURORA_MYSQL and given version'(test: Test) { - // GIVEN - const engine = DatabaseClusterEngine.AURORA_MYSQL; - - // WHEN - const family = engine.parameterGroupFamily('5.7.mysql_aurora.2.07.1'); - - // THEN - test.equals(family, 'aurora-mysql5.7'); - - test.done(); - }, - - 'cluster parameter group correctly determined for AURORA_POSTGRESQL and given version'(test: Test) { - // GIVEN - const engine = DatabaseClusterEngine.AURORA_POSTGRESQL; - - // WHEN - const family = engine.parameterGroupFamily('11.6'); - - // THEN - test.equals(family, 'aurora-postgresql11'); - - test.done(); - }, - - 'parameter group family'(test: Test) { - // WHEN - const engine1 = new DatabaseClusterEngine( - 'no-parameter-group-family', - SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER, - SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER); - const engine2 = new DatabaseClusterEngine( - 'aurora-postgresql', - SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER, - SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER, - [ - { engineMajorVersion: '1.0', parameterGroupFamily: 'family-1'}, - { engineMajorVersion: '2.0', parameterGroupFamily: 'family-2' }, - ]); - - // THEN - test.equals(engine1.parameterGroupFamily(), undefined); - test.equals(engine1.parameterGroupFamily('1'), undefined); - - test.equals(engine2.parameterGroupFamily('3'), undefined); - test.equals(engine2.parameterGroupFamily('1'), undefined); - test.equals(engine2.parameterGroupFamily('1.1'), undefined); - test.equals(engine2.parameterGroupFamily('1.0'), 'family-1'); - test.equals(engine2.parameterGroupFamily('2.0.2'), 'family-2'); - - test.done(); - }, -}; diff --git a/packages/@aws-cdk/aws-rds/test/test.proxy.ts b/packages/@aws-cdk/aws-rds/test/test.proxy.ts index 2ec6d93f6eb0a..6e4de63c7aeff 100644 --- a/packages/@aws-cdk/aws-rds/test/test.proxy.ts +++ b/packages/@aws-cdk/aws-rds/test/test.proxy.ts @@ -19,7 +19,7 @@ export = { // WHEN new rds.DatabaseProxy(stack, 'Proxy', { proxyTarget: rds.ProxyTarget.fromInstance(instance), - secret: instance.secret!, + secrets: [instance.secret!], vpc, }); @@ -79,8 +79,9 @@ export = { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new rds.DatabaseCluster(stack, 'Database', { - engine: rds.DatabaseClusterEngine.AURORA_POSTGRESQL, - engineVersion: '10.7', + engine: rds.DatabaseClusterEngine.auroraPostgres({ + version: rds.AuroraPostgresEngineVersion.VER_10_7, + }), masterUser: { username: 'admin', }, @@ -93,7 +94,7 @@ export = { // WHEN new rds.DatabaseProxy(stack, 'Proxy', { proxyTarget: rds.ProxyTarget.fromCluster(cluster), - secret: cluster.secret!, + secrets: [cluster.secret!], vpc, }); @@ -153,8 +154,9 @@ export = { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new rds.DatabaseCluster(stack, 'Database', { - engine: rds.DatabaseClusterEngine.AURORA_POSTGRESQL, - engineVersion: '10.7', + engine: rds.DatabaseClusterEngine.auroraPostgres({ + version: rds.AuroraPostgresEngineVersion.VER_10_7, + }), masterUser: { username: 'admin', }, @@ -168,7 +170,7 @@ export = { test.doesNotThrow(() => { new rds.DatabaseProxy(stack, 'Proxy', { proxyTarget: rds.ProxyTarget.fromCluster(cluster), - secret: cluster.secret!, + secrets: [cluster.secret!], vpc, }); }, /Cannot specify both dbInstanceIdentifiers and dbClusterIdentifiers/); @@ -179,4 +181,29 @@ export = { test.done(); }, + + 'One or more secrets are required.'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new rds.DatabaseCluster(stack, 'Database', { + engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_10_7 }), + masterUser: { username: 'admin' }, + instanceProps: { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, + }, + }); + + // WHEN + test.throws(() => { + new rds.DatabaseProxy(stack, 'Proxy', { + proxyTarget: rds.ProxyTarget.fromCluster(cluster), + secrets: [], // No secret + vpc, + }); + }, 'One or more secrets are required.'); + + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-redshift/.gitignore b/packages/@aws-cdk/aws-redshift/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-redshift/.gitignore +++ b/packages/@aws-cdk/aws-redshift/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-redshift/.npmignore b/packages/@aws-cdk/aws-redshift/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-redshift/.npmignore +++ b/packages/@aws-cdk/aws-redshift/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-redshift/jest.config.js b/packages/@aws-cdk/aws-redshift/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-redshift/jest.config.js +++ b/packages/@aws-cdk/aws-redshift/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-resourcegroups/.gitignore b/packages/@aws-cdk/aws-resourcegroups/.gitignore index d57af28d42320..192200b9c7097 100644 --- a/packages/@aws-cdk/aws-resourcegroups/.gitignore +++ b/packages/@aws-cdk/aws-resourcegroups/.gitignore @@ -16,3 +16,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-resourcegroups/.npmignore b/packages/@aws-cdk/aws-resourcegroups/.npmignore index 2f1ff45adc883..c8874799485a3 100644 --- a/packages/@aws-cdk/aws-resourcegroups/.npmignore +++ b/packages/@aws-cdk/aws-resourcegroups/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-resourcegroups/jest.config.js b/packages/@aws-cdk/aws-resourcegroups/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-resourcegroups/jest.config.js +++ b/packages/@aws-cdk/aws-resourcegroups/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-robomaker/.gitignore b/packages/@aws-cdk/aws-robomaker/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-robomaker/.gitignore +++ b/packages/@aws-cdk/aws-robomaker/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-robomaker/.npmignore b/packages/@aws-cdk/aws-robomaker/.npmignore index b0edf81a01371..ab90672b1d91e 100644 --- a/packages/@aws-cdk/aws-robomaker/.npmignore +++ b/packages/@aws-cdk/aws-robomaker/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-robomaker/jest.config.js b/packages/@aws-cdk/aws-robomaker/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-robomaker/jest.config.js +++ b/packages/@aws-cdk/aws-robomaker/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-route53-patterns/.gitignore b/packages/@aws-cdk/aws-route53-patterns/.gitignore index 23a79075f642c..147448f7df4fe 100644 --- a/packages/@aws-cdk/aws-route53-patterns/.gitignore +++ b/packages/@aws-cdk/aws-route53-patterns/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53-patterns/.npmignore b/packages/@aws-cdk/aws-route53-patterns/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-route53-patterns/.npmignore +++ b/packages/@aws-cdk/aws-route53-patterns/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53-patterns/jest.config.js b/packages/@aws-cdk/aws-route53-patterns/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-route53-patterns/jest.config.js +++ b/packages/@aws-cdk/aws-route53-patterns/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-route53-targets/.gitignore b/packages/@aws-cdk/aws-route53-targets/.gitignore index 23a79075f642c..147448f7df4fe 100644 --- a/packages/@aws-cdk/aws-route53-targets/.gitignore +++ b/packages/@aws-cdk/aws-route53-targets/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53-targets/.npmignore b/packages/@aws-cdk/aws-route53-targets/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-route53-targets/.npmignore +++ b/packages/@aws-cdk/aws-route53-targets/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53-targets/jest.config.js b/packages/@aws-cdk/aws-route53-targets/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-route53-targets/jest.config.js +++ b/packages/@aws-cdk/aws-route53-targets/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-route53-targets/test/cloudfront-target.test.ts b/packages/@aws-cdk/aws-route53-targets/test/cloudfront-target.test.ts index dfab6894a56e4..42d5084c8079e 100644 --- a/packages/@aws-cdk/aws-route53-targets/test/cloudfront-target.test.ts +++ b/packages/@aws-cdk/aws-route53-targets/test/cloudfront-target.test.ts @@ -17,7 +17,7 @@ test('use CloudFront as record target', () => { s3OriginSource: { s3BucketSource: sourceBucket, }, - behaviors : [ {isDefaultBehavior: true}], + behaviors: [ {isDefaultBehavior: true}], }, ], }); diff --git a/packages/@aws-cdk/aws-route53-targets/test/integ.cloudfront-alias-target.expected.json b/packages/@aws-cdk/aws-route53-targets/test/integ.cloudfront-alias-target.expected.json index 76bd0647bca15..7a91ba087aaab 100644 --- a/packages/@aws-cdk/aws-route53-targets/test/integ.cloudfront-alias-target.expected.json +++ b/packages/@aws-cdk/aws-route53-targets/test/integ.cloudfront-alias-target.expected.json @@ -59,6 +59,8 @@ "IPV6Enabled": true, "Origins": [ { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, "DomainName": { "Fn::GetAtt": [ "Bucket83908E77", diff --git a/packages/@aws-cdk/aws-route53-targets/test/integ.cloudfront-alias-target.ts b/packages/@aws-cdk/aws-route53-targets/test/integ.cloudfront-alias-target.ts index a2735b0b1b6c2..6e5d8902f207d 100644 --- a/packages/@aws-cdk/aws-route53-targets/test/integ.cloudfront-alias-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/test/integ.cloudfront-alias-target.ts @@ -20,7 +20,7 @@ const distribution = new cloudfront.CloudFrontWebDistribution(stack, 'MyDistribu s3OriginSource: { s3BucketSource: sourceBucket, }, - behaviors : [ {isDefaultBehavior: true}], + behaviors: [ {isDefaultBehavior: true}], }, ], }); diff --git a/packages/@aws-cdk/aws-route53/.gitignore b/packages/@aws-cdk/aws-route53/.gitignore index 7fce433df3f45..86fc837df8fca 100644 --- a/packages/@aws-cdk/aws-route53/.gitignore +++ b/packages/@aws-cdk/aws-route53/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53/.npmignore b/packages/@aws-cdk/aws-route53/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-route53/.npmignore +++ b/packages/@aws-cdk/aws-route53/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 70abfe6dcd65b..8015171860f6f 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.711.0", + "aws-sdk": "^2.715.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-route53resolver/.gitignore b/packages/@aws-cdk/aws-route53resolver/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-route53resolver/.gitignore +++ b/packages/@aws-cdk/aws-route53resolver/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53resolver/.npmignore b/packages/@aws-cdk/aws-route53resolver/.npmignore index b0edf81a01371..ab90672b1d91e 100644 --- a/packages/@aws-cdk/aws-route53resolver/.npmignore +++ b/packages/@aws-cdk/aws-route53resolver/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53resolver/jest.config.js b/packages/@aws-cdk/aws-route53resolver/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-route53resolver/jest.config.js +++ b/packages/@aws-cdk/aws-route53resolver/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-s3-assets/.gitignore b/packages/@aws-cdk/aws-s3-assets/.gitignore index 743b39099999a..cc6ed97299f89 100644 --- a/packages/@aws-cdk/aws-s3-assets/.gitignore +++ b/packages/@aws-cdk/aws-s3-assets/.gitignore @@ -16,3 +16,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-assets/.npmignore b/packages/@aws-cdk/aws-s3-assets/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-s3-assets/.npmignore +++ b/packages/@aws-cdk/aws-s3-assets/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-assets/jest.config.js b/packages/@aws-cdk/aws-s3-assets/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-s3-assets/jest.config.js +++ b/packages/@aws-cdk/aws-s3-assets/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-s3-assets/lib/asset.ts b/packages/@aws-cdk/aws-s3-assets/lib/asset.ts index 307575cf561ae..e457de127d442 100644 --- a/packages/@aws-cdk/aws-s3-assets/lib/asset.ts +++ b/packages/@aws-cdk/aws-s3-assets/lib/asset.ts @@ -1,11 +1,11 @@ +import * as fs from 'fs'; +import * as path from 'path'; import * as assets from '@aws-cdk/assets'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import * as fs from 'fs'; -import * as path from 'path'; import { toSymlinkFollow } from './compat'; const ARCHIVE_EXTENSIONS = [ '.zip', '.jar' ]; diff --git a/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts b/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts index 4f0f8b9ab4519..d08005a39f692 100644 --- a/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts +++ b/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts @@ -143,7 +143,7 @@ test('"grantRead" also gives KMS permissions when using the new bootstrap stack' Statement: arrayWith({ Action: ['kms:Decrypt', 'kms:DescribeKey'], Effect: 'Allow', - Resource: { 'Fn::ImportValue': 'CdkBootstrap-hnb659fds-FileAssetKeyArn' }, + Resource: { 'Fn::ImportValue': 'CdkBootstrap-hnb659fds-FileAssetKeyArn' }, }), }, }); diff --git a/packages/@aws-cdk/aws-s3-assets/test/integ.assets.bundling.lit.ts b/packages/@aws-cdk/aws-s3-assets/test/integ.assets.bundling.lit.ts index b1b144f2de275..1ba1ad26deee2 100644 --- a/packages/@aws-cdk/aws-s3-assets/test/integ.assets.bundling.lit.ts +++ b/packages/@aws-cdk/aws-s3-assets/test/integ.assets.bundling.lit.ts @@ -1,6 +1,6 @@ +import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import { App, BundlingDockerImage, Construct, Stack, StackProps } from '@aws-cdk/core'; -import * as path from 'path'; import * as assets from '../lib'; class TestStack extends Stack { diff --git a/packages/@aws-cdk/aws-s3-assets/test/integ.assets.directory.lit.ts b/packages/@aws-cdk/aws-s3-assets/test/integ.assets.directory.lit.ts index 07dfe9a1c0d53..e0470ea814e92 100644 --- a/packages/@aws-cdk/aws-s3-assets/test/integ.assets.directory.lit.ts +++ b/packages/@aws-cdk/aws-s3-assets/test/integ.assets.directory.lit.ts @@ -1,6 +1,6 @@ +import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; -import * as path from 'path'; import * as assets from '../lib'; class TestStack extends cdk.Stack { diff --git a/packages/@aws-cdk/aws-s3-assets/test/integ.assets.file.lit.ts b/packages/@aws-cdk/aws-s3-assets/test/integ.assets.file.lit.ts index ef85353769745..7e9c8544bac21 100644 --- a/packages/@aws-cdk/aws-s3-assets/test/integ.assets.file.lit.ts +++ b/packages/@aws-cdk/aws-s3-assets/test/integ.assets.file.lit.ts @@ -1,6 +1,6 @@ +import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; -import * as path from 'path'; import * as assets from '../lib'; class TestStack extends cdk.Stack { diff --git a/packages/@aws-cdk/aws-s3-assets/test/integ.assets.permissions.lit.ts b/packages/@aws-cdk/aws-s3-assets/test/integ.assets.permissions.lit.ts index c764f97980d15..3432cf744fbb0 100644 --- a/packages/@aws-cdk/aws-s3-assets/test/integ.assets.permissions.lit.ts +++ b/packages/@aws-cdk/aws-s3-assets/test/integ.assets.permissions.lit.ts @@ -1,6 +1,6 @@ +import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; -import * as path from 'path'; import * as assets from '../lib'; class TestStack extends cdk.Stack { diff --git a/packages/@aws-cdk/aws-s3-assets/test/integ.assets.refs.lit.ts b/packages/@aws-cdk/aws-s3-assets/test/integ.assets.refs.lit.ts index 2f8fded27f124..df5e43865e307 100644 --- a/packages/@aws-cdk/aws-s3-assets/test/integ.assets.refs.lit.ts +++ b/packages/@aws-cdk/aws-s3-assets/test/integ.assets.refs.lit.ts @@ -1,6 +1,6 @@ +import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; -import * as path from 'path'; import * as assets from '../lib'; class TestStack extends cdk.Stack { diff --git a/packages/@aws-cdk/aws-s3-assets/test/integ.multi-assets.ts b/packages/@aws-cdk/aws-s3-assets/test/integ.multi-assets.ts index 2024db70d9463..c16f8da96fb00 100644 --- a/packages/@aws-cdk/aws-s3-assets/test/integ.multi-assets.ts +++ b/packages/@aws-cdk/aws-s3-assets/test/integ.multi-assets.ts @@ -1,6 +1,6 @@ +import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; -import * as path from 'path'; import * as assets from '../lib'; class TestStack extends cdk.Stack { diff --git a/packages/@aws-cdk/aws-s3-deployment/.gitignore b/packages/@aws-cdk/aws-s3-deployment/.gitignore index 48b0b20af63bf..586b6b51a9c33 100644 --- a/packages/@aws-cdk/aws-s3-deployment/.gitignore +++ b/packages/@aws-cdk/aws-s3-deployment/.gitignore @@ -19,4 +19,5 @@ nyc.config.js *.snk !.eslintrc.js -!jest.config.js \ No newline at end of file +!jest.config.js +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-deployment/.npmignore b/packages/@aws-cdk/aws-s3-deployment/.npmignore index 5ad383d334409..f1b35d10f16cf 100644 --- a/packages/@aws-cdk/aws-s3-deployment/.npmignore +++ b/packages/@aws-cdk/aws-s3-deployment/.npmignore @@ -25,3 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts **/cdk.out + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-deployment/jest.config.js b/packages/@aws-cdk/aws-s3-deployment/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-s3-deployment/jest.config.js +++ b/packages/@aws-cdk/aws-s3-deployment/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts index c564b2da9d6c4..0fdd4fb18da23 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts +++ b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts @@ -1,11 +1,11 @@ +import * as crypto from 'crypto'; +import * as fs from 'fs'; +import * as path from 'path'; import * as cloudfront from '@aws-cdk/aws-cloudfront'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; -import * as crypto from 'crypto'; -import * as fs from 'fs'; -import * as path from 'path'; import { ISource, SourceConfig } from './source'; const now = Date.now(); diff --git a/packages/@aws-cdk/aws-s3-deployment/package.json b/packages/@aws-cdk/aws-s3-deployment/package.json index cde79ab1cfc0f..ed03166cd38c5 100644 --- a/packages/@aws-cdk/aws-s3-deployment/package.json +++ b/packages/@aws-cdk/aws-s3-deployment/package.json @@ -78,7 +78,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/jest": "^25.2.3", + "@types/jest": "^26.0.4", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "jest": "^25.5.4", diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts index ce64692d9a9ef..0859347c66c5f 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts @@ -7,8 +7,7 @@ import * as cdk from '@aws-cdk/core'; import * as path from 'path'; import * as s3deploy from '../lib'; -// tslint:disable:max-line-length -// tslint:disable:object-literal-key-quotes +/* eslint-disable max-len */ test('deploy from local directory asset', () => { // GIVEN @@ -23,16 +22,16 @@ test('deploy from local directory asset', () => { // THEN expect(stack).toHaveResource('Custom::CDKBucketDeployment', { - 'ServiceToken': { + ServiceToken: { 'Fn::GetAtt': [ 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536', 'Arn', ], }, - 'SourceBucketNames': [{ - 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3Bucket9CD8B20A', + SourceBucketNames: [{ + Ref: 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3Bucket9CD8B20A', }], - 'SourceObjectKeys': [{ + SourceObjectKeys: [{ 'Fn::Join': [ '', [ @@ -43,7 +42,7 @@ test('deploy from local directory asset', () => { 'Fn::Split': [ '||', { - 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', + Ref: 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', }, ], }, @@ -56,7 +55,7 @@ test('deploy from local directory asset', () => { 'Fn::Split': [ '||', { - 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', + Ref: 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', }, ], }, @@ -65,8 +64,8 @@ test('deploy from local directory asset', () => { ], ], }], - 'DestinationBucketName': { - 'Ref': 'DestC383B82A', + DestinationBucketName: { + Ref: 'DestC383B82A', }, }); }); @@ -87,21 +86,21 @@ test('deploy from local directory assets', () => { // THEN expect(stack).toHaveResource('Custom::CDKBucketDeployment', { - 'ServiceToken': { + ServiceToken: { 'Fn::GetAtt': [ 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536', 'Arn', ], }, - 'SourceBucketNames': [ + SourceBucketNames: [ { - 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3Bucket9CD8B20A', + Ref: 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3Bucket9CD8B20A', }, { - 'Ref': 'AssetParametersa94977ede0211fd3b45efa33d6d8d1d7bbe0c5a96d977139d8b16abfa96fe9cbS3Bucket99793559', + Ref: 'AssetParametersa94977ede0211fd3b45efa33d6d8d1d7bbe0c5a96d977139d8b16abfa96fe9cbS3Bucket99793559', }, ], - 'SourceObjectKeys': [ + SourceObjectKeys: [ { 'Fn::Join': [ '', @@ -113,7 +112,7 @@ test('deploy from local directory assets', () => { 'Fn::Split': [ '||', { - 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', + Ref: 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', }, ], }, @@ -126,7 +125,7 @@ test('deploy from local directory assets', () => { 'Fn::Split': [ '||', { - 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', + Ref: 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', }, ], }, @@ -146,7 +145,7 @@ test('deploy from local directory assets', () => { 'Fn::Split': [ '||', { - 'Ref': 'AssetParametersa94977ede0211fd3b45efa33d6d8d1d7bbe0c5a96d977139d8b16abfa96fe9cbS3VersionKeyD9ACE665', + Ref: 'AssetParametersa94977ede0211fd3b45efa33d6d8d1d7bbe0c5a96d977139d8b16abfa96fe9cbS3VersionKeyD9ACE665', }, ], }, @@ -159,7 +158,7 @@ test('deploy from local directory assets', () => { 'Fn::Split': [ '||', { - 'Ref': 'AssetParametersa94977ede0211fd3b45efa33d6d8d1d7bbe0c5a96d977139d8b16abfa96fe9cbS3VersionKeyD9ACE665', + Ref: 'AssetParametersa94977ede0211fd3b45efa33d6d8d1d7bbe0c5a96d977139d8b16abfa96fe9cbS3VersionKeyD9ACE665', }, ], }, @@ -169,8 +168,8 @@ test('deploy from local directory assets', () => { ], }, ], - 'DestinationBucketName': { - 'Ref': 'DestC383B82A', + DestinationBucketName: { + Ref: 'DestC383B82A', }, }); }); @@ -215,16 +214,16 @@ test('honors passed asset options', () => { // THEN expect(stack).toHaveResource('Custom::CDKBucketDeployment', { - 'ServiceToken': { + ServiceToken: { 'Fn::GetAtt': [ 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536', 'Arn', ], }, - 'SourceBucketNames': [{ - 'Ref': 'AssetParameters86f8bca4f28a0bcafef0a98fe4cea25c0071aca27401e35cfaecd06313373bcaS3BucketB41AE64D', + SourceBucketNames: [{ + Ref: 'AssetParameters86f8bca4f28a0bcafef0a98fe4cea25c0071aca27401e35cfaecd06313373bcaS3BucketB41AE64D', }], - 'SourceObjectKeys': [{ + SourceObjectKeys: [{ 'Fn::Join': [ '', [ @@ -235,7 +234,7 @@ test('honors passed asset options', () => { 'Fn::Split': [ '||', { - 'Ref': 'AssetParameters86f8bca4f28a0bcafef0a98fe4cea25c0071aca27401e35cfaecd06313373bcaS3VersionKeyF3CBA38F', + Ref: 'AssetParameters86f8bca4f28a0bcafef0a98fe4cea25c0071aca27401e35cfaecd06313373bcaS3VersionKeyF3CBA38F', }, ], }, @@ -248,7 +247,7 @@ test('honors passed asset options', () => { 'Fn::Split': [ '||', { - 'Ref': 'AssetParameters86f8bca4f28a0bcafef0a98fe4cea25c0071aca27401e35cfaecd06313373bcaS3VersionKeyF3CBA38F', + Ref: 'AssetParameters86f8bca4f28a0bcafef0a98fe4cea25c0071aca27401e35cfaecd06313373bcaS3VersionKeyF3CBA38F', }, ], }, @@ -257,8 +256,8 @@ test('honors passed asset options', () => { ], ], }], - 'DestinationBucketName': { - 'Ref': 'DestC383B82A', + DestinationBucketName: { + Ref: 'DestC383B82A', }, }); }); @@ -400,7 +399,7 @@ test('distribution can be used to provide a CloudFront distribution for invalida expect(stack).toHaveResource('Custom::CDKBucketDeployment', { DistributionId: { - 'Ref': 'DistributionCFDistribution882A7313', + Ref: 'DistributionCFDistribution882A7313', }, DistributionPaths: ['/images/*'], }); @@ -430,7 +429,7 @@ test('invalidation can happen without distributionPaths provided', () => { expect(stack).toHaveResource('Custom::CDKBucketDeployment', { DistributionId: { - 'Ref': 'DistributionCFDistribution882A7313', + Ref: 'DistributionCFDistribution882A7313', }, }); @@ -464,16 +463,16 @@ test('lambda execution role gets permissions to read from the source bucket and // THEN expect(stack).toHaveResource('AWS::IAM::Policy', { - 'PolicyDocument': { - 'Statement': [ + PolicyDocument: { + Statement: [ { - 'Action': [ + Action: [ 's3:GetObject*', 's3:GetBucket*', 's3:List*', ], - 'Effect': 'Allow', - 'Resource': [ + Effect: 'Allow', + Resource: [ { 'Fn::GetAtt': [ 'Source71E471F1', @@ -497,7 +496,7 @@ test('lambda execution role gets permissions to read from the source bucket and ], }, { - 'Action': [ + Action: [ 's3:GetObject*', 's3:GetBucket*', 's3:List*', @@ -505,8 +504,8 @@ test('lambda execution role gets permissions to read from the source bucket and 's3:PutObject*', 's3:Abort*', ], - 'Effect': 'Allow', - 'Resource': [ + Effect: 'Allow', + Resource: [ { 'Fn::GetAtt': [ 'DestC383B82A', @@ -530,12 +529,12 @@ test('lambda execution role gets permissions to read from the source bucket and ], }, ], - 'Version': '2012-10-17', + Version: '2012-10-17', }, - 'PolicyName': 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF', - 'Roles': [ + PolicyName: 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF', + Roles: [ { - 'Ref': 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265', + Ref: 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265', }, ], }); @@ -597,7 +596,7 @@ test('deployment allows custom role to be supplied', () => { expect(stack).toCountResources('AWS::IAM::Role', 1); expect(stack).toCountResources('AWS::Lambda::Function', 1); expect(stack).toHaveResource('AWS::Lambda::Function', { - 'Role': { + Role: { 'Fn::GetAtt': [ 'Role1ABCC5F0', 'Arn', @@ -620,7 +619,7 @@ test('deploy without deleting missing files from destination', () => { }); expect(stack).toHaveResourceLike('Custom::CDKBucketDeployment', { - 'Prune': false, + Prune: false, }); }); @@ -643,7 +642,7 @@ test('Deployment role gets KMS permissions when using assets from new style synt Statement: arrayWith({ Action: ['kms:Decrypt', 'kms:DescribeKey'], Effect: 'Allow', - Resource: { 'Fn::ImportValue': 'CdkBootstrap-hnb659fds-FileAssetKeyArn' }, + Resource: { 'Fn::ImportValue': 'CdkBootstrap-hnb659fds-FileAssetKeyArn' }, }), }, }); diff --git a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json index e5e0dfe714f00..7563384f9b9f0 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json @@ -34,6 +34,8 @@ "IPV6Enabled": true, "Origins": [ { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, "DomainName": { "Fn::GetAtt": [ "Destination3E3DC043D", diff --git a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.ts b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.ts index fb6e77dc9ed8e..e97b3f9d7f878 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.ts +++ b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.ts @@ -1,7 +1,7 @@ +import * as path from 'path'; import * as cloudfront from '@aws-cdk/aws-cloudfront'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; -import * as path from 'path'; import * as s3deploy from '../lib'; class TestBucketDeployment extends cdk.Stack { @@ -17,7 +17,7 @@ class TestBucketDeployment extends cdk.Stack { s3OriginSource: { s3BucketSource: bucket, }, - behaviors : [ {isDefaultBehavior: true}], + behaviors: [ {isDefaultBehavior: true}], }, ], }); diff --git a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.ts index ee9918709b0b5..b2b1754083bd7 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.ts +++ b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.ts @@ -1,6 +1,6 @@ +import * as path from 'path'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; -import * as path from 'path'; import * as s3deploy from '../lib'; class TestBucketDeployment extends cdk.Stack { diff --git a/packages/@aws-cdk/aws-s3-notifications/.gitignore b/packages/@aws-cdk/aws-s3-notifications/.gitignore index 23a79075f642c..147448f7df4fe 100644 --- a/packages/@aws-cdk/aws-s3-notifications/.gitignore +++ b/packages/@aws-cdk/aws-s3-notifications/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-notifications/.npmignore b/packages/@aws-cdk/aws-s3-notifications/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-s3-notifications/.npmignore +++ b/packages/@aws-cdk/aws-s3-notifications/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-notifications/jest.config.js b/packages/@aws-cdk/aws-s3-notifications/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-s3-notifications/jest.config.js +++ b/packages/@aws-cdk/aws-s3-notifications/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json index 96f176b1abbaa..e28b703234d0d 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.expected.json @@ -211,7 +211,7 @@ "Properties": { "Description": "AWS CloudFormation handler for \"Custom::S3BucketNotifications\" resources (@aws-cdk/aws-s3)", "Code": { - "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require('https');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require('url');\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration,\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse('FAILED', err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse('SUCCESS');\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // tslint:disable-next-line:max-line-length\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error messge as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: 'PUT',\n headers: {\n 'content-type': '',\n 'content-length': responseBody.length,\n },\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on('error', (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" + "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require('https');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require('url');\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration,\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse('FAILED', err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse('SUCCESS');\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // eslint-disable-next-line max-len\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error messge as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: 'PUT',\n headers: {\n 'content-type': '',\n 'content-length': responseBody.length,\n },\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on('error', (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" }, "Handler": "index.handler", "Role": { diff --git a/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json index 907ce064e3618..bb0b743ab2fdb 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.expected.json @@ -235,7 +235,7 @@ "Properties": { "Description": "AWS CloudFormation handler for \"Custom::S3BucketNotifications\" resources (@aws-cdk/aws-s3)", "Code": { - "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require('https');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require('url');\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration,\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse('FAILED', err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse('SUCCESS');\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // tslint:disable-next-line:max-line-length\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error messge as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: 'PUT',\n headers: {\n 'content-type': '',\n 'content-length': responseBody.length,\n },\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on('error', (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" + "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require('https');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require('url');\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration,\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse('FAILED', err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse('SUCCESS');\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // eslint-disable-next-line max-len\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error messge as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: 'PUT',\n headers: {\n 'content-type': '',\n 'content-length': responseBody.length,\n },\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on('error', (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" }, "Handler": "index.handler", "Role": { diff --git a/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.ts b/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.ts index 3c47bf21d5a30..c237b24e896e3 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.ts @@ -26,7 +26,7 @@ bucketB.addEventNotification(s3.EventType.OBJECT_REMOVED, new s3n.LambdaDestinat app.synth(); -// tslint:disable:no-console +/* eslint-disable no-console */ function handler(event: any, _context: any, callback: any) { console.log(JSON.stringify(event, undefined, 2)); return callback(null, event); diff --git a/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts b/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts index 2dff3ba044311..7a5a3cf25b96d 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts @@ -5,8 +5,8 @@ import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; import * as s3n from '../lib'; -// tslint:disable:object-literal-key-quotes -// tslint:disable:max-line-length +/* eslint-disable max-len */ +/* eslint-disable quote-props */ test('bucket without notifications', () => { const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json index 5d9f5d8eb4787..3ee1f5979fbdf 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/sns/integ.sns-bucket-notifications.expected.json @@ -194,7 +194,7 @@ "Properties": { "Description": "AWS CloudFormation handler for \"Custom::S3BucketNotifications\" resources (@aws-cdk/aws-s3)", "Code": { - "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require('https');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require('url');\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration,\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse('FAILED', err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse('SUCCESS');\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // tslint:disable-next-line:max-line-length\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error messge as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: 'PUT',\n headers: {\n 'content-type': '',\n 'content-length': responseBody.length,\n },\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on('error', (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" + "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require('https');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require('url');\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration,\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse('FAILED', err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse('SUCCESS');\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // eslint-disable-next-line max-len\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error messge as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: 'PUT',\n headers: {\n 'content-type': '',\n 'content-length': responseBody.length,\n },\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on('error', (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" }, "Handler": "index.handler", "Role": { diff --git a/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json b/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json index 88e05950c5c56..b4d969a3c6d42 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json +++ b/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.expected.json @@ -181,7 +181,7 @@ "Properties": { "Description": "AWS CloudFormation handler for \"Custom::S3BucketNotifications\" resources (@aws-cdk/aws-s3)", "Code": { - "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require('https');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require('url');\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration,\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse('FAILED', err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse('SUCCESS');\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // tslint:disable-next-line:max-line-length\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error messge as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: 'PUT',\n headers: {\n 'content-type': '',\n 'content-length': responseBody.length,\n },\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on('error', (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" + "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require('https');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require('url');\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration,\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse('FAILED', err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse('SUCCESS');\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // eslint-disable-next-line max-len\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error messge as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || 'See the details in CloudWatch Log Stream: ' + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: 'PUT',\n headers: {\n 'content-type': '',\n 'content-length': responseBody.length,\n },\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on('error', (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" }, "Handler": "index.handler", "Role": { diff --git a/packages/@aws-cdk/aws-s3/.gitignore b/packages/@aws-cdk/aws-s3/.gitignore index 32a10d785e8fb..dcc1dc41e477f 100644 --- a/packages/@aws-cdk/aws-s3/.gitignore +++ b/packages/@aws-cdk/aws-s3/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3/.npmignore b/packages/@aws-cdk/aws-s3/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-s3/.npmignore +++ b/packages/@aws-cdk/aws-s3/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index ddc2b8939913c..9c49dde1be853 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -54,6 +54,11 @@ export interface IBucket extends IResource { */ readonly bucketRegionalDomainName: string; + /** + * If this bucket has been configured for static website hosting. + */ + readonly isWebsite?: boolean; + /** * Optional KMS encryption key associated with this bucket. */ @@ -279,6 +284,13 @@ export interface BucketAttributes { readonly bucketWebsiteNewUrlFormat?: boolean; readonly encryptionKey?: kms.IKey; + + /** + * If this bucket has been configured for static website hosting. + * + * @default false + */ + readonly isWebsite?: boolean; } /** @@ -312,6 +324,11 @@ abstract class BucketBase extends Resource implements IBucket { */ public abstract readonly encryptionKey?: kms.IKey; + /** + * If this bucket has been configured for static website hosting. + */ + public abstract readonly isWebsite?: boolean; + /** * The resource policy associated with this bucket. * @@ -1003,6 +1020,7 @@ export class Bucket extends BucketBase { public readonly bucketDualStackDomainName = attrs.bucketDualStackDomainName || `${bucketName}.s3.dualstack.${region}.${urlSuffix}`; public readonly bucketWebsiteNewUrlFormat = newUrlFormat; public readonly encryptionKey = attrs.encryptionKey; + public readonly isWebsite = attrs.isWebsite ?? false; public policy?: BucketPolicy = undefined; protected autoCreatePolicy = false; protected disallowPublicAccess = false; @@ -1027,6 +1045,7 @@ export class Bucket extends BucketBase { public readonly bucketRegionalDomainName: string; public readonly encryptionKey?: kms.IKey; + public readonly isWebsite?: boolean; public policy?: BucketPolicy; protected autoCreatePolicy = true; protected disallowPublicAccess?: boolean; @@ -1046,12 +1065,15 @@ export class Bucket extends BucketBase { this.validateBucketName(this.physicalName); + const websiteConfiguration = this.renderWebsiteConfiguration(props); + this.isWebsite = (websiteConfiguration !== undefined); + const resource = new CfnBucket(this, 'Resource', { bucketName: this.physicalName, bucketEncryption, versioningConfiguration: props.versioned ? { status: 'Enabled' } : undefined, lifecycleConfiguration: Lazy.anyValue({ produce: () => this.parseLifecycleConfiguration() }), - websiteConfiguration: this.renderWebsiteConfiguration(props), + websiteConfiguration, publicAccessBlockConfiguration: props.blockPublicAccess, metricsConfigurations: Lazy.anyValue({ produce: () => this.parseMetricConfiguration() }), corsConfiguration: Lazy.anyValue({ produce: () => this.parseCorsConfiguration() }), @@ -1304,7 +1326,7 @@ export class Bucket extends BucketBase { const enabled = rule.enabled !== undefined ? rule.enabled : true; const x: CfnBucket.RuleProperty = { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len abortIncompleteMultipartUpload: rule.abortIncompleteMultipartUploadAfter !== undefined ? { daysAfterInitiation: rule.abortIncompleteMultipartUploadAfter.toDays() } : undefined, expirationDate: rule.expirationDate, expirationInDays: rule.expiration && rule.expiration.toDays(), diff --git a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts index 6d4c78682e699..4ceced2e93e2d 100644 --- a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts +++ b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts @@ -89,7 +89,7 @@ export class NotificationsResourceHandler extends cdk.Construct { } } -// tslint:disable:no-console +/* eslint-disable no-console */ /** * Lambda event handler for the custom resource. Bear in mind that we are going @@ -132,7 +132,7 @@ const handler = (event: any, context: any) => { console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj); } - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule // to allow sending an error messge as a reason. function submitResponse(responseStatus: string, reason?: string) { diff --git a/packages/@aws-cdk/aws-s3/test/test.bucket-policy.ts b/packages/@aws-cdk/aws-s3/test/test.bucket-policy.ts index 17121d3d6c31a..d09b566e56905 100644 --- a/packages/@aws-cdk/aws-s3/test/test.bucket-policy.ts +++ b/packages/@aws-cdk/aws-s3/test/test.bucket-policy.ts @@ -5,7 +5,7 @@ import { Test } from 'nodeunit'; import * as s3 from '../lib'; // to make it easy to copy & paste from output: -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'default properties'(test: Test) { diff --git a/packages/@aws-cdk/aws-s3/test/test.bucket.ts b/packages/@aws-cdk/aws-s3/test/test.bucket.ts index 92440f7d05586..3f177974fdbc1 100644 --- a/packages/@aws-cdk/aws-s3/test/test.bucket.ts +++ b/packages/@aws-cdk/aws-s3/test/test.bucket.ts @@ -7,7 +7,7 @@ import { EOL } from 'os'; import * as s3 from '../lib'; // to make it easy to copy & paste from output: -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'default bucket'(test: Test) { @@ -127,7 +127,6 @@ export = { test.throws(() => new s3.Bucket(stack, 'MyBucket', { bucketName: bucket, - // tslint:disable-next-line:only-arrow-functions }), function(err: Error) { return expectedErrors === err.message; }); @@ -1798,6 +1797,59 @@ export = { }, /The condition property cannot be an empty object/); test.done(); }, + 'isWebsite set properly with': { + 'only index doc'(test: Test) { + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Website', { + websiteIndexDocument: 'index2.html', + }); + test.equal(bucket.isWebsite, true); + test.done(); + }, + 'error and index docs'(test: Test) { + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Website', { + websiteIndexDocument: 'index2.html', + websiteErrorDocument: 'error.html', + }); + test.equal(bucket.isWebsite, true); + test.done(); + }, + 'redirects'(test: Test) { + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Website', { + websiteRedirect: { + hostName: 'www.example.com', + protocol: s3.RedirectProtocol.HTTPS, + }, + }); + test.equal(bucket.isWebsite, true); + test.done(); + }, + 'no website properties set'(test: Test) { + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Website'); + test.equal(bucket.isWebsite, false); + test.done(); + }, + 'imported website buckets'(test: Test) { + const stack = new cdk.Stack(); + const bucket = s3.Bucket.fromBucketAttributes(stack, 'Website', { + bucketArn: 'arn:aws:s3:::my-bucket', + isWebsite: true, + }); + test.equal(bucket.isWebsite, true); + test.done(); + }, + 'imported buckets'(test: Test) { + const stack = new cdk.Stack(); + const bucket = s3.Bucket.fromBucketAttributes(stack, 'NotWebsite', { + bucketArn: 'arn:aws:s3:::my-bucket', + }); + test.equal(bucket.isWebsite, false); + test.done(); + }, + }, }, 'Bucket.fromBucketArn'(test: Test) { diff --git a/packages/@aws-cdk/aws-sagemaker/.gitignore b/packages/@aws-cdk/aws-sagemaker/.gitignore index adcba106db8d1..7bdb507ae2cc7 100644 --- a/packages/@aws-cdk/aws-sagemaker/.gitignore +++ b/packages/@aws-cdk/aws-sagemaker/.gitignore @@ -14,3 +14,5 @@ tsconfig.json *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sagemaker/.npmignore b/packages/@aws-cdk/aws-sagemaker/.npmignore index 2f6c8553a5830..3ac7fc56cea02 100644 --- a/packages/@aws-cdk/aws-sagemaker/.npmignore +++ b/packages/@aws-cdk/aws-sagemaker/.npmignore @@ -26,4 +26,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sagemaker/jest.config.js b/packages/@aws-cdk/aws-sagemaker/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-sagemaker/jest.config.js +++ b/packages/@aws-cdk/aws-sagemaker/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-sam/.gitignore b/packages/@aws-cdk/aws-sam/.gitignore index adcba106db8d1..7bdb507ae2cc7 100644 --- a/packages/@aws-cdk/aws-sam/.gitignore +++ b/packages/@aws-cdk/aws-sam/.gitignore @@ -14,3 +14,5 @@ tsconfig.json *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sam/.npmignore b/packages/@aws-cdk/aws-sam/.npmignore index ac1c98567f9a5..3dffd1ce79a72 100644 --- a/packages/@aws-cdk/aws-sam/.npmignore +++ b/packages/@aws-cdk/aws-sam/.npmignore @@ -26,4 +26,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sam/jest.config.js b/packages/@aws-cdk/aws-sam/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-sam/jest.config.js +++ b/packages/@aws-cdk/aws-sam/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-sam/package.json b/packages/@aws-cdk/aws-sam/package.json index f49d0c41dc349..936f21d34f920 100644 --- a/packages/@aws-cdk/aws-sam/package.json +++ b/packages/@aws-cdk/aws-sam/package.json @@ -65,12 +65,12 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/jest": "^26.0.3", + "@types/jest": "^26.0.4", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", "jest": "^25.5.4", "pkglint": "0.0.0", - "ts-jest": "^26.1.1" + "ts-jest": "^26.1.3" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-sdb/.gitignore b/packages/@aws-cdk/aws-sdb/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-sdb/.gitignore +++ b/packages/@aws-cdk/aws-sdb/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sdb/.npmignore b/packages/@aws-cdk/aws-sdb/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-sdb/.npmignore +++ b/packages/@aws-cdk/aws-sdb/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sdb/jest.config.js b/packages/@aws-cdk/aws-sdb/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-sdb/jest.config.js +++ b/packages/@aws-cdk/aws-sdb/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-secretsmanager/.gitignore b/packages/@aws-cdk/aws-secretsmanager/.gitignore index d5c0c2743f469..65d2efc186f05 100644 --- a/packages/@aws-cdk/aws-secretsmanager/.gitignore +++ b/packages/@aws-cdk/aws-secretsmanager/.gitignore @@ -12,3 +12,5 @@ coverage dist tsconfig.json !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-secretsmanager/.npmignore b/packages/@aws-cdk/aws-secretsmanager/.npmignore index af5cffcab06c1..f937500da09a6 100644 --- a/packages/@aws-cdk/aws-secretsmanager/.npmignore +++ b/packages/@aws-cdk/aws-secretsmanager/.npmignore @@ -24,4 +24,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-securityhub/.gitignore b/packages/@aws-cdk/aws-securityhub/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-securityhub/.gitignore +++ b/packages/@aws-cdk/aws-securityhub/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-securityhub/.npmignore b/packages/@aws-cdk/aws-securityhub/.npmignore index 2e1e60862c02c..5c003cc4e3040 100644 --- a/packages/@aws-cdk/aws-securityhub/.npmignore +++ b/packages/@aws-cdk/aws-securityhub/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-securityhub/jest.config.js b/packages/@aws-cdk/aws-securityhub/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-securityhub/jest.config.js +++ b/packages/@aws-cdk/aws-securityhub/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-servicecatalog/.gitignore b/packages/@aws-cdk/aws-servicecatalog/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-servicecatalog/.gitignore +++ b/packages/@aws-cdk/aws-servicecatalog/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/.npmignore b/packages/@aws-cdk/aws-servicecatalog/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-servicecatalog/.npmignore +++ b/packages/@aws-cdk/aws-servicecatalog/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/jest.config.js b/packages/@aws-cdk/aws-servicecatalog/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-servicecatalog/jest.config.js +++ b/packages/@aws-cdk/aws-servicecatalog/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-servicediscovery/.gitignore b/packages/@aws-cdk/aws-servicediscovery/.gitignore index 0bd6133da4d09..018c65919d67c 100644 --- a/packages/@aws-cdk/aws-servicediscovery/.gitignore +++ b/packages/@aws-cdk/aws-servicediscovery/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicediscovery/.npmignore b/packages/@aws-cdk/aws-servicediscovery/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-servicediscovery/.npmignore +++ b/packages/@aws-cdk/aws-servicediscovery/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts index 99d6714dba64b..a59913200224a 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts @@ -4,7 +4,6 @@ import { NamespaceType } from './namespace'; import { IService } from './service'; import { CfnInstance } from './servicediscovery.generated'; -// tslint:disable-next-line:no-empty-interface export interface NonIpInstanceBaseProps extends BaseInstanceProps { } diff --git a/packages/@aws-cdk/aws-ses-actions/.gitignore b/packages/@aws-cdk/aws-ses-actions/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-ses-actions/.gitignore +++ b/packages/@aws-cdk/aws-ses-actions/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ses-actions/.npmignore b/packages/@aws-cdk/aws-ses-actions/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-ses-actions/.npmignore +++ b/packages/@aws-cdk/aws-ses-actions/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ses-actions/jest.config.js b/packages/@aws-cdk/aws-ses-actions/jest.config.js index 6371e05b69738..fc310b5014407 100644 --- a/packages/@aws-cdk/aws-ses-actions/jest.config.js +++ b/packages/@aws-cdk/aws-ses-actions/jest.config.js @@ -1,4 +1,4 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = { ...baseConfig, coverageThreshold: { diff --git a/packages/@aws-cdk/aws-ses-actions/lib/add-header.ts b/packages/@aws-cdk/aws-ses-actions/lib/add-header.ts index 46c2cebf0e8d8..543a60c15e698 100644 --- a/packages/@aws-cdk/aws-ses-actions/lib/add-header.ts +++ b/packages/@aws-cdk/aws-ses-actions/lib/add-header.ts @@ -27,9 +27,8 @@ export class AddHeader implements ses.IReceiptRuleAction { constructor(props: AddHeaderProps) { if (!/^[a-zA-Z0-9-]{1,50}$/.test(props.name)) { - // tslint:disable:max-line-length + // eslint-disable-next-line max-len throw new Error('Header `name` must be between 1 and 50 characters, inclusive, and consist of alphanumeric (a-z, A-Z, 0-9) characters and dashes only.'); - // tslint:enable:max-line-length } if (!/^[^\n\r]{0,2047}$/.test(props.value)) { diff --git a/packages/@aws-cdk/aws-ses-actions/lib/lambda.ts b/packages/@aws-cdk/aws-ses-actions/lib/lambda.ts index c345a3615d03a..3fa7d418e2ac4 100644 --- a/packages/@aws-cdk/aws-ses-actions/lib/lambda.ts +++ b/packages/@aws-cdk/aws-ses-actions/lib/lambda.ts @@ -70,7 +70,7 @@ export class Lambda implements ses.IReceiptRuleAction { if (permission) { // The Lambda could be imported rule.node.addDependency(permission); } else { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len rule.node.addWarning('This rule is using a Lambda action with an imported function. Ensure permission is given to SES to invoke that function.'); } diff --git a/packages/@aws-cdk/aws-ses/.gitignore b/packages/@aws-cdk/aws-ses/.gitignore index 0bd6133da4d09..018c65919d67c 100644 --- a/packages/@aws-cdk/aws-ses/.gitignore +++ b/packages/@aws-cdk/aws-ses/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ses/.npmignore b/packages/@aws-cdk/aws-ses/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-ses/.npmignore +++ b/packages/@aws-cdk/aws-ses/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts b/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts index 55bf25bb3983a..34ec0735865ca 100644 --- a/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts +++ b/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts @@ -154,7 +154,6 @@ export class ReceiptRule extends Resource implements IReceiptRule { } } -// tslint:disable-next-line:no-empty-interface export interface DropSpamReceiptRuleProps extends ReceiptRuleProps { } @@ -201,7 +200,7 @@ export class DropSpamReceiptRule extends Construct { } // Adapted from https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-action-lambda-example-functions.html -// tslint:disable:no-console +/* eslint-disable no-console */ function dropSpamCode(event: any, _: any, callback: any) { console.log('Spam filter'); @@ -216,7 +215,7 @@ function dropSpamCode(event: any, _: any, callback: any) { console.log('Dropping spam'); // Stop processing rule set, dropping message - callback(null, { disposition : 'STOP_RULE_SET' }); + callback(null, { disposition: 'STOP_RULE_SET' }); } else { callback(null, null); } diff --git a/packages/@aws-cdk/aws-ses/test/test.receipt-filter.ts b/packages/@aws-cdk/aws-ses/test/test.receipt-filter.ts index 37b73be9b4ad4..d380d535ad506 100644 --- a/packages/@aws-cdk/aws-ses/test/test.receipt-filter.ts +++ b/packages/@aws-cdk/aws-ses/test/test.receipt-filter.ts @@ -3,7 +3,7 @@ import { Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import { ReceiptFilter, ReceiptFilterPolicy, WhiteListReceiptFilter } from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'can create a receipt filter'(test: Test) { diff --git a/packages/@aws-cdk/aws-ses/test/test.receipt-rule-set.ts b/packages/@aws-cdk/aws-ses/test/test.receipt-rule-set.ts index 6e514fe9687e8..4d308d2f53561 100644 --- a/packages/@aws-cdk/aws-ses/test/test.receipt-rule-set.ts +++ b/packages/@aws-cdk/aws-ses/test/test.receipt-rule-set.ts @@ -3,7 +3,7 @@ import { Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import { ReceiptRuleSet } from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'can create a receipt rule set'(test: Test) { diff --git a/packages/@aws-cdk/aws-ses/test/test.receipt-rule.ts b/packages/@aws-cdk/aws-ses/test/test.receipt-rule.ts index 42502ba8398f1..f719e61d3ce87 100644 --- a/packages/@aws-cdk/aws-ses/test/test.receipt-rule.ts +++ b/packages/@aws-cdk/aws-ses/test/test.receipt-rule.ts @@ -3,7 +3,7 @@ import { Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import { ReceiptRule, ReceiptRuleSet, TlsPolicy } from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'can create receipt rules with second after first'(test: Test) { diff --git a/packages/@aws-cdk/aws-sns-subscriptions/.gitignore b/packages/@aws-cdk/aws-sns-subscriptions/.gitignore index 23a79075f642c..147448f7df4fe 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/.gitignore +++ b/packages/@aws-cdk/aws-sns-subscriptions/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns-subscriptions/.npmignore b/packages/@aws-cdk/aws-sns-subscriptions/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/.npmignore +++ b/packages/@aws-cdk/aws-sns-subscriptions/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns-subscriptions/jest.config.js b/packages/@aws-cdk/aws-sns-subscriptions/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/jest.config.js +++ b/packages/@aws-cdk/aws-sns-subscriptions/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json index 79897aec43ee0..38367e9204de2 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json @@ -38,7 +38,7 @@ "Type": "AWS::Lambda::Function", "Properties": { "Code": { - "ZipFile": "exports.handler = function handler(event, _context, callback) {\n // tslint:disable:no-console\n console.log('====================================================');\n console.log(JSON.stringify(event, undefined, 2));\n console.log('====================================================');\n return callback(undefined, event);\n}" + "ZipFile": "exports.handler = function handler(event, _context, callback) {\n /* eslint-disable no-console */\n console.log('====================================================');\n console.log(JSON.stringify(event, undefined, 2));\n console.log('====================================================');\n return callback(undefined, event);\n}" }, "Handler": "index.handler", "Role": { @@ -93,9 +93,7 @@ } }, "DeadLetterQueue9F481546": { - "Type": "AWS::SQS::Queue", - "Properties": { - } + "Type": "AWS::SQS::Queue" }, "DeadLetterQueuePolicyB1FB890C": { "Type": "AWS::SQS::QueuePolicy", @@ -167,7 +165,7 @@ "Type": "AWS::Lambda::Function", "Properties": { "Code": { - "ZipFile": "exports.handler = function handler(event, _context, callback) {\n // tslint:disable:no-console\n console.log('====================================================');\n console.log(JSON.stringify(event, undefined, 2));\n console.log('====================================================');\n return callback(undefined, event);\n}" + "ZipFile": "exports.handler = function handler(event, _context, callback) {\n /* eslint-disable no-console */\n console.log('====================================================');\n console.log(JSON.stringify(event, undefined, 2));\n console.log('====================================================');\n return callback(undefined, event);\n}" }, "Handler": "index.handler", "Role": { diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts index eb2e9fbc9a057..166fa8b982ba2 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts @@ -50,7 +50,7 @@ new SnsToLambda(app, 'aws-cdk-sns-lambda'); app.synth(); function handler(event: any, _context: any, callback: any) { - // tslint:disable:no-console + /* eslint-disable no-console */ console.log('===================================================='); console.log(JSON.stringify(event, undefined, 2)); console.log('===================================================='); diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts index 241c1dfdb1491..77c38a5259061 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts @@ -5,7 +5,7 @@ import * as sqs from '@aws-cdk/aws-sqs'; import { CfnParameter, Duration, Stack, Token } from '@aws-cdk/core'; import * as subs from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ let stack: Stack; let topic: sns.Topic; @@ -154,7 +154,7 @@ test('url subscription (with raw delivery)', () => { }); test('url subscription (unresolved url with protocol)', () => { - const urlToken = Token.asString({ Ref : 'my-url-1' }); + const urlToken = Token.asString({ Ref: 'my-url-1' }); topic.addSubscription(new subs.UrlSubscription(urlToken, {protocol: sns.SubscriptionProtocol.HTTPS})); expect(stack).toMatchTemplate({ @@ -181,8 +181,8 @@ test('url subscription (unresolved url with protocol)', () => { }); test('url subscription (double unresolved url with protocol)', () => { - const urlToken1 = Token.asString({ Ref : 'my-url-1' }); - const urlToken2 = Token.asString({ Ref : 'my-url-2' }); + const urlToken1 = Token.asString({ Ref: 'my-url-1' }); + const urlToken2 = Token.asString({ Ref: 'my-url-2' }); topic.addSubscription(new subs.UrlSubscription(urlToken1, {protocol: sns.SubscriptionProtocol.HTTPS})); topic.addSubscription(new subs.UrlSubscription(urlToken2, {protocol: sns.SubscriptionProtocol.HTTPS})); @@ -200,7 +200,7 @@ test('url subscription (double unresolved url with protocol)', () => { 'Type': 'AWS::SNS::Subscription', 'Properties': { 'Endpoint': { - 'Ref' : 'my-url-1', + 'Ref': 'my-url-1', }, 'Protocol': 'https', 'TopicArn': { 'Ref': 'MyTopic86869434' }, @@ -210,7 +210,7 @@ test('url subscription (double unresolved url with protocol)', () => { 'Type': 'AWS::SNS::Subscription', 'Properties': { 'Endpoint': { - 'Ref' : 'my-url-2', + 'Ref': 'my-url-2', }, 'Protocol': 'https', 'TopicArn': { 'Ref': 'MyTopic86869434' }, @@ -226,7 +226,7 @@ test('url subscription (unknown protocol)', () => { }); test('url subscription (unresolved url without protocol)', () => { - const urlToken = Token.asString({ Ref : 'my-url-1' }); + const urlToken = Token.asString({ Ref: 'my-url-1' }); expect(() => topic.addSubscription(new subs.UrlSubscription(urlToken))) .toThrowError(/Must provide protocol if url is unresolved/); @@ -580,7 +580,7 @@ test('email subscription', () => { }); test('email subscription with unresolved', () => { - const emailToken = Token.asString({ Ref : 'my-email-1' }); + const emailToken = Token.asString({ Ref: 'my-email-1' }); topic.addSubscription(new subs.EmailSubscription(emailToken)); expect(stack).toMatchTemplate({ @@ -596,7 +596,7 @@ test('email subscription with unresolved', () => { 'Type': 'AWS::SNS::Subscription', 'Properties': { 'Endpoint': { - 'Ref' : 'my-email-1', + 'Ref': 'my-email-1', }, 'Protocol': 'email', 'TopicArn': { @@ -609,8 +609,8 @@ test('email subscription with unresolved', () => { }); test('email and url subscriptions with unresolved', () => { - const emailToken = Token.asString({ Ref : 'my-email-1' }); - const urlToken = Token.asString({ Ref : 'my-url-1' }); + const emailToken = Token.asString({ Ref: 'my-email-1' }); + const urlToken = Token.asString({ Ref: 'my-url-1' }); topic.addSubscription(new subs.EmailSubscription(emailToken)); topic.addSubscription(new subs.UrlSubscription(urlToken, {protocol: sns.SubscriptionProtocol.HTTPS})); @@ -627,7 +627,7 @@ test('email and url subscriptions with unresolved', () => { 'Type': 'AWS::SNS::Subscription', 'Properties': { 'Endpoint': { - 'Ref' : 'my-email-1', + 'Ref': 'my-email-1', }, 'Protocol': 'email', 'TopicArn': { @@ -639,7 +639,7 @@ test('email and url subscriptions with unresolved', () => { 'Type': 'AWS::SNS::Subscription', 'Properties': { 'Endpoint': { - 'Ref' : 'my-url-1', + 'Ref': 'my-url-1', }, 'Protocol': 'https', 'TopicArn': { @@ -652,10 +652,10 @@ test('email and url subscriptions with unresolved', () => { }); test('email and url subscriptions with unresolved - four subscriptions', () => { - const emailToken1 = Token.asString({ Ref : 'my-email-1' }); - const emailToken2 = Token.asString({ Ref : 'my-email-2' }); - const emailToken3 = Token.asString({ Ref : 'my-email-3' }); - const emailToken4 = Token.asString({ Ref : 'my-email-4' }); + const emailToken1 = Token.asString({ Ref: 'my-email-1' }); + const emailToken2 = Token.asString({ Ref: 'my-email-2' }); + const emailToken3 = Token.asString({ Ref: 'my-email-3' }); + const emailToken4 = Token.asString({ Ref: 'my-email-4' }); topic.addSubscription(new subs.EmailSubscription(emailToken1)); topic.addSubscription(new subs.EmailSubscription(emailToken2)); @@ -675,7 +675,7 @@ test('email and url subscriptions with unresolved - four subscriptions', () => { 'Type': 'AWS::SNS::Subscription', 'Properties': { 'Endpoint': { - 'Ref' : 'my-email-1', + 'Ref': 'my-email-1', }, 'Protocol': 'email', 'TopicArn': { @@ -687,7 +687,7 @@ test('email and url subscriptions with unresolved - four subscriptions', () => { 'Type': 'AWS::SNS::Subscription', 'Properties': { 'Endpoint': { - 'Ref' : 'my-email-2', + 'Ref': 'my-email-2', }, 'Protocol': 'email', 'TopicArn': { @@ -699,7 +699,7 @@ test('email and url subscriptions with unresolved - four subscriptions', () => { 'Type': 'AWS::SNS::Subscription', 'Properties': { 'Endpoint': { - 'Ref' : 'my-email-3', + 'Ref': 'my-email-3', }, 'Protocol': 'email', 'TopicArn': { @@ -711,7 +711,7 @@ test('email and url subscriptions with unresolved - four subscriptions', () => { 'Type': 'AWS::SNS::Subscription', 'Properties': { 'Endpoint': { - 'Ref' : 'my-email-4', + 'Ref': 'my-email-4', }, 'Protocol': 'email', 'TopicArn': { @@ -1026,7 +1026,7 @@ test('sms subscription', () => { }); test('sms subscription with unresolved', () => { - const smsToken = Token.asString({ Ref : 'my-sms-1' }); + const smsToken = Token.asString({ Ref: 'my-sms-1' }); topic.addSubscription(new subs.SmsSubscription(smsToken)); expect(stack).toMatchTemplate({ @@ -1042,7 +1042,7 @@ test('sms subscription with unresolved', () => { 'Type': 'AWS::SNS::Subscription', 'Properties': { 'Endpoint': { - 'Ref' : 'my-sms-1', + 'Ref': 'my-sms-1', }, 'Protocol': 'sms', 'TopicArn': { diff --git a/packages/@aws-cdk/aws-sns/.gitignore b/packages/@aws-cdk/aws-sns/.gitignore index 7fce433df3f45..86fc837df8fca 100644 --- a/packages/@aws-cdk/aws-sns/.gitignore +++ b/packages/@aws-cdk/aws-sns/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns/.npmignore b/packages/@aws-cdk/aws-sns/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-sns/.npmignore +++ b/packages/@aws-cdk/aws-sns/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns/test/test.sns.ts b/packages/@aws-cdk/aws-sns/test/test.sns.ts index ad86fa6aaf2c7..87d1b899559e0 100644 --- a/packages/@aws-cdk/aws-sns/test/test.sns.ts +++ b/packages/@aws-cdk/aws-sns/test/test.sns.ts @@ -5,7 +5,7 @@ import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as sns from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'topic tests': { diff --git a/packages/@aws-cdk/aws-sqs/.gitignore b/packages/@aws-cdk/aws-sqs/.gitignore index 2d2f100c9395d..d0a956699806b 100644 --- a/packages/@aws-cdk/aws-sqs/.gitignore +++ b/packages/@aws-cdk/aws-sqs/.gitignore @@ -15,3 +15,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sqs/.npmignore b/packages/@aws-cdk/aws-sqs/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-sqs/.npmignore +++ b/packages/@aws-cdk/aws-sqs/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index 58921594b5c6c..e8f8ad2ac66e1 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.711.0", + "aws-sdk": "^2.715.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-sqs/test/test.sqs.ts b/packages/@aws-cdk/aws-sqs/test/test.sqs.ts index 15a67e269bf3a..4e2d09fea3594 100644 --- a/packages/@aws-cdk/aws-sqs/test/test.sqs.ts +++ b/packages/@aws-cdk/aws-sqs/test/test.sqs.ts @@ -5,7 +5,7 @@ import { CfnParameter, Duration, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as sqs from '../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'default properties'(test: Test) { diff --git a/packages/@aws-cdk/aws-ssm/.gitignore b/packages/@aws-cdk/aws-ssm/.gitignore index 0bd6133da4d09..018c65919d67c 100644 --- a/packages/@aws-cdk/aws-ssm/.gitignore +++ b/packages/@aws-cdk/aws-ssm/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ssm/.npmignore b/packages/@aws-cdk/aws-ssm/.npmignore index fe4df9a06d9a9..95a6e5fe5bb87 100644 --- a/packages/@aws-cdk/aws-ssm/.npmignore +++ b/packages/@aws-cdk/aws-ssm/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ssm/test/integ.parameter-arns.ts b/packages/@aws-cdk/aws-ssm/test/integ.parameter-arns.ts index 7d4076ac1006f..358d92abbce25 100644 --- a/packages/@aws-cdk/aws-ssm/test/integ.parameter-arns.ts +++ b/packages/@aws-cdk/aws-ssm/test/integ.parameter-arns.ts @@ -1,4 +1,4 @@ -// tslint:disable: max-line-length +/* eslint-disable max-len */ import { App, CfnOutput, CfnParameter, Stack } from '@aws-cdk/core'; import * as ssm from '../lib'; diff --git a/packages/@aws-cdk/aws-ssm/test/test.parameter.ts b/packages/@aws-cdk/aws-ssm/test/test.parameter.ts index 4f899048ccd62..ed7cde4d9a8ce 100644 --- a/packages/@aws-cdk/aws-ssm/test/test.parameter.ts +++ b/packages/@aws-cdk/aws-ssm/test/test.parameter.ts @@ -1,4 +1,4 @@ -// tslint:disable: max-line-length +/* eslint-disable max-len */ import { expect, haveResource } from '@aws-cdk/assert'; import * as iam from '@aws-cdk/aws-iam'; @@ -540,7 +540,7 @@ export = { test.done(); }, - 'valueForStringParameter': { + valueForStringParameter: { 'returns a token that represents the SSM parameter value'(test: Test) { // GIVEN diff --git a/packages/@aws-cdk/aws-ssm/test/test.util.ts b/packages/@aws-cdk/aws-ssm/test/test.util.ts index 0b65991441398..884f05b26d7e0 100644 --- a/packages/@aws-cdk/aws-ssm/test/test.util.ts +++ b/packages/@aws-cdk/aws-ssm/test/test.util.ts @@ -1,4 +1,4 @@ -// tslint:disable: max-line-length +/* eslint-disable max-len */ import { Stack, Token } from '@aws-cdk/core'; import { Test } from 'nodeunit'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/.gitignore b/packages/@aws-cdk/aws-stepfunctions-tasks/.gitignore index 1109bfe833d86..ceda962aa6202 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/.gitignore +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/.gitignore @@ -16,3 +16,5 @@ nyc.config.js .cdk.staging !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore b/packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore index 36fe3528aa4c6..a0bf754e3cc79 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore @@ -21,4 +21,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md index 305530f60d0fe..23b9f6660f65e 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md @@ -623,6 +623,19 @@ new tasks.LambdaInvoke(this, 'Invoke and set function response as task output', }); ``` +If you want to combine the input and the Lambda function response you can use +the `payloadResponseOnly` property and specify the `resultPath`. This will put the +Lambda function ARN directly in the "Resource" string, but it conflicts with the +integrationPattern, invocationType, clientContext, and qualifier properties. + +```ts +new tasks.LambdaInvoke(this, 'Invoke and combine function response with task input', { + lambdaFunction: myLambda, + payloadResponseOnly: true, + resultPath: '$.myLambda', +}); +``` + You can have Step Functions pause a task, and wait for an external process to return a task token. Read more about the [callback pattern](https://docs.aws.amazon.com/step-functions/latest/dg/callback-task-sample-sqs.html#call-back-lambda-example) diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/jest.config.js b/packages/@aws-cdk/aws-stepfunctions-tasks/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/jest.config.js +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/run-batch-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/run-batch-job.ts index faeb7009b18eb..80c4eb28140be 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/run-batch-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/run-batch-job.ts @@ -214,7 +214,6 @@ export class RunBatchJob implements sfn.IStepFunctionsTask { }); // validate timeout - // tslint:disable-next-line:no-unused-expression props.timeout !== undefined && withResolved(props.timeout.toSeconds(), (timeout) => { if (timeout < 60) { throw new Error(`timeout must be greater than 60 seconds. Received ${timeout} seconds.`); 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 c376a4ce91d26..8c5ef74bb5d9a 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 @@ -191,7 +191,6 @@ export class BatchSubmitJob extends sfn.TaskStateBase { }); // validate timeout - // tslint:disable-next-line:no-unused-expression props.timeout !== undefined && withResolved(props.timeout.toSeconds(), (timeout) => { if (timeout < 60) { throw new Error(`attempt duration must be greater than 60 seconds. Received ${timeout} seconds.`); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/private/utils.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/private/utils.ts index f951b7ed978e1..e860f6bd9d0d0 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/private/utils.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/private/utils.ts @@ -22,3 +22,9 @@ export function transformAttributeValueMap(attrMap?: { [key: string]: DynamoAttr } return attrMap ? transformedValue : undefined; } + +export function validateJsonPath(value: string) { + if (!value.startsWith('$')) { + throw new Error("Data JSON path values must either be exactly equal to '$' or start with '$.'"); + } +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/shared-types.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/shared-types.ts index 95a1151eb1803..1646ee42b792a 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/shared-types.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/shared-types.ts @@ -1,4 +1,4 @@ -import { transformAttributeValueMap } from './private/utils'; +import { transformAttributeValueMap, validateJsonPath } from './private/utils'; /** * Determines the level of detail about provisioned throughput consumption that is returned. @@ -205,9 +205,7 @@ export class DynamoAttributeValue { * @param value Json path that specifies state input to be used */ public static mapFromJsonPath(value: string) { - if (!value.startsWith('$')) { - throw new Error("Data JSON path values must either be exactly equal to '$' or start with '$.'"); - } + validateJsonPath(value); return new DynamoAttributeValue({ 'M.$': value }); } @@ -232,6 +230,17 @@ export class DynamoAttributeValue { return new DynamoAttributeValue({ BOOL: value }); } + /** + * Sets an attribute of type Boolean from state input through Json path. + * For example: "BOOL": true + * + * @param value Json path that specifies state input to be used + */ + public static booleanFromJsonPath(value: string) { + validateJsonPath(value); + return new DynamoAttributeValue({ BOOL: value.toString() }); + } + /** * Represents the data for the attribute. Data can be * i.e. "S": "Hello" diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eval-nodejs10.x-handler/index.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eval-nodejs10.x-handler/index.ts index 166ac3a6df520..4bd79ff06fa27 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eval-nodejs10.x-handler/index.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eval-nodejs10.x-handler/index.ts @@ -1,4 +1,4 @@ -// tslint:disable:no-console no-eval +/* eslint-disable no-console */ import { Event } from '../evaluate-expression'; export async function handler(event: Event): Promise { 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 90ac0c2f0ab33..6c44c555f80a4 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/lambda/invoke.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/lambda/invoke.ts @@ -46,6 +46,17 @@ export interface LambdaInvokeProps extends sfn.TaskStateBaseProps { * @default - Version or alias inherent to the `lambdaFunction` object. */ readonly qualifier?: string; + + /** + * Invoke the Lambda in a way that only returns the payload response without additional metadata. + * + * The `payloadResponseOnly` property cannot be used if `integrationPattern`, `invocationType`, + * `clientContext`, or `qualifier` are specified. + * It always uses the REQUEST_RESPONSE behavior. + * + * @default false + */ + readonly payloadResponseOnly?: boolean; } /** @@ -76,6 +87,13 @@ export class LambdaInvoke extends sfn.TaskStateBase { throw new Error('Task Token is required in `payload` for callback. Use JsonPath.taskToken to set the token.'); } + if (props.payloadResponseOnly && + (props.integrationPattern || props.invocationType || props.clientContext || props.qualifier)) { + throw new Error( + "The 'payloadResponseOnly' property cannot be used if 'integrationPattern', 'invocationType', 'clientContext', or 'qualifier' are specified.", + ); + } + this.taskMetrics = { metricPrefixSingular: 'LambdaFunction', metricPrefixPlural: 'LambdaFunctions', @@ -100,16 +118,23 @@ export class LambdaInvoke extends sfn.TaskStateBase { * @internal */ protected _renderTask(): any { - return { - Resource: integrationResourceArn('lambda', 'invoke', this.integrationPattern), - Parameters: sfn.FieldUtils.renderObject({ - FunctionName: this.props.lambdaFunction.functionArn, - Payload: this.props.payload ? this.props.payload.value : sfn.TaskInput.fromDataAt('$').value, - InvocationType: this.props.invocationType, - ClientContext: this.props.clientContext, - Qualifier: this.props.qualifier, - }), - }; + if (this.props.payloadResponseOnly) { + return { + Resource: this.props.lambdaFunction.functionArn, + ...this.props.payload && { Parameters: sfn.FieldUtils.renderObject(this.props.payload.value) }, + }; + } else { + return { + Resource: integrationResourceArn('lambda', 'invoke', this.integrationPattern), + Parameters: sfn.FieldUtils.renderObject({ + FunctionName: this.props.lambdaFunction.functionArn, + Payload: this.props.payload ? this.props.payload.value : sfn.TaskInput.fromDataAt('$').value, + InvocationType: this.props.invocationType, + ClientContext: this.props.clientContext, + Qualifier: this.props.qualifier, + }), + }; + } } } 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 2991c762c025d..f5e5d7c76bd2a 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 @@ -98,7 +98,7 @@ test('supports tokens', () => { }, End: true, Parameters: { - // tslint:disable:object-literal-key-quotes + /* eslint-disable quote-props */ Key: { SOME_KEY: { 'S.$': '$.partitionKey' }, OTHER_KEY: { 'N.$': '$.sortKey' }, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.expected.json index e4876ddef2c6a..094221d1ba6bb 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.expected.json @@ -193,7 +193,7 @@ { "Ref": "AWS::Partition" }, - ":states:::dynamodb:putItem\",\"Parameters\":{\"Item\":{\"MessageId\":{\"S\":\"1234\"},\"Text\":{\"S.$\":\"$.bar\"},\"TotalCount\":{\"N\":\"18\"}},\"TableName\":\"", + ":states:::dynamodb:putItem\",\"Parameters\":{\"Item\":{\"MessageId\":{\"S\":\"1234\"},\"Text\":{\"S.$\":\"$.bar\"},\"TotalCount\":{\"N\":\"18\"},\"Activated\":{\"BOOL.$\":\"$.foo\"}},\"TableName\":\"", { "Ref": "Messages804FA4EB" }, 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 07e05b7414a9f..e78bba4f6721e 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 @@ -36,6 +36,7 @@ class CallDynamoDBStack extends cdk.Stack { MessageId: tasks.DynamoAttributeValue.fromString(MESSAGE_ID), Text: tasks.DynamoAttributeValue.fromString(sfn.JsonPath.stringAt('$.bar')), TotalCount: tasks.DynamoAttributeValue.fromNumber(firstNumber), + Activated: tasks.DynamoAttributeValue.booleanFromJsonPath(sfn.JsonPath.stringAt('$.foo')), }, table, }); 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 a21a4decc0b30..1164a269522d1 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 @@ -27,6 +27,7 @@ test('PutItem task', () => { expressionAttributeNames: { OTHER_KEY: '#OK' }, expressionAttributeValues: { ':val': tasks.DynamoAttributeValue.numberFromString(sfn.JsonPath.stringAt('$.Item.TotalCount.N')), + ':bool': tasks.DynamoAttributeValue.booleanFromJsonPath(sfn.JsonPath.stringAt('$.Item.flag')), }, returnConsumedCapacity: tasks.DynamoConsumedCapacity.TOTAL, returnItemCollectionMetrics: tasks.DynamoItemCollectionMetrics.SIZE, @@ -56,7 +57,10 @@ test('PutItem task', () => { }, ConditionExpression: 'ForumName <> :f and Subject <> :s', ExpressionAttributeNames: { OTHER_KEY: '#OK' }, - ExpressionAttributeValues: { ':val': { 'N.$': '$.Item.TotalCount.N' } }, + ExpressionAttributeValues: { + ':val': { 'N.$': '$.Item.TotalCount.N' }, + ':bool': { 'BOOL.$': '$.Item.flag' }, + }, ReturnConsumedCapacity: 'TOTAL', ReturnItemCollectionMetrics: 'SIZE', ReturnValues: 'ALL_NEW', 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 d848ef9035132..f0810de3e3949 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 @@ -234,6 +234,33 @@ describe('DynamoAttributeValue', () => { }); }); + + test('from invalid boolean with json path', () => { + // GIVEN + const m = 'invalid'; + + // WHEN / THEN + expect(() => { + tasks.DynamoAttributeValue.booleanFromJsonPath(m); + }).toThrow("Data JSON path values must either be exactly equal to '$' or start with '$.'"); + + }); + + + test('from boolean with json path', () => { + // GIVEN + const m = '$.path'; + // WHEN + const attribute = tasks.DynamoAttributeValue.booleanFromJsonPath(sfn.JsonPath.stringAt(m)); + + // THEN + expect(sfn.FieldUtils.renderObject(attribute)).toEqual({ + attributeValue: { + 'BOOL.$': m, + }, + }); + }); + test('from boolean', () => { // WHEN const attribute = tasks.DynamoAttributeValue.fromBoolean(true); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/run-tasks.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/run-tasks.test.ts index d124b1ac9c6ee..a60a993967bee 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/run-tasks.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/run-tasks.test.ts @@ -9,7 +9,7 @@ let stack: Stack; let vpc: ec2.Vpc; let cluster: ecs.Cluster; -// tslint:disable: object-literal-key-quotes +/* eslint-disable quote-props */ beforeEach(() => { // GIVEN 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 8034635352b32..905ce9d65bfe3 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 @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../lib'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.payload.only.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.payload.only.expected.json new file mode 100644 index 0000000000000..b994f8b20cc5e --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.payload.only.expected.json @@ -0,0 +1,212 @@ +{ + "Resources": { + "submitJobLambdaServiceRole4D897ABD": { + "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" + ] + ] + } + ] + } + }, + "submitJobLambdaEFB00F3C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async () => {\n return {\n statusCode: '200',\n body: 'hello, world!'\n };\n };" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "submitJobLambdaServiceRole4D897ABD", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + }, + "DependsOn": [ + "submitJobLambdaServiceRole4D897ABD" + ] + }, + "checkJobStateLambdaServiceRoleB8B57B65": { + "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" + ] + ] + } + ] + } + }, + "checkJobStateLambda4618B7B7": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async function(event, context) {\n return {\n status: event.statusCode === '200' ? 'SUCCEEDED' : 'FAILED'\n };\n };" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "checkJobStateLambdaServiceRoleB8B57B65", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + }, + "DependsOn": [ + "checkJobStateLambdaServiceRoleB8B57B65" + ] + }, + "StateMachineRoleB840431D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachineRoleDefaultPolicyDF1E6607": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "submitJobLambdaEFB00F3C", + "Arn" + ] + } + }, + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "checkJobStateLambda4618B7B7", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "StateMachineRoleDefaultPolicyDF1E6607", + "Roles": [ + { + "Ref": "StateMachineRoleB840431D" + } + ] + } + }, + "StateMachine2E01A3A5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"Invoke Handler\",\"States\":{\"Invoke Handler\":{\"Next\":\"Check the job state\",\"Type\":\"Task\",\"Resource\":\"", + { + "Fn::GetAtt": [ + "submitJobLambdaEFB00F3C", + "Arn" + ] + }, + "\"},\"Check the job state\":{\"Next\":\"Job Complete?\",\"Type\":\"Task\",\"Resource\":\"", + { + "Fn::GetAtt": [ + "checkJobStateLambda4618B7B7", + "Arn" + ] + }, + "\"},\"Job Complete?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.status\",\"StringEquals\":\"FAILED\",\"Next\":\"Job Failed\"},{\"Variable\":\"$.status\",\"StringEquals\":\"SUCCEEDED\",\"Next\":\"Final step\"}]},\"Job Failed\":{\"Type\":\"Fail\",\"Error\":\"Received a status that was not 200\",\"Cause\":\"Job Failed\"},\"Final step\":{\"Type\":\"Pass\",\"End\":true}},\"TimeoutSeconds\":30}" + ] + ] + } + }, + "DependsOn": [ + "StateMachineRoleDefaultPolicyDF1E6607", + "StateMachineRoleB840431D" + ] + } + }, + "Outputs": { + "stateMachineArn": { + "Value": { + "Ref": "StateMachine2E01A3A5" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.payload.only.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.payload.only.ts new file mode 100644 index 0000000000000..711a5ceb458f6 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke.payload.only.ts @@ -0,0 +1,76 @@ +import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import { LambdaInvoke } from '../../lib'; + +/* + * Creates a state machine with a task state to invoke a Lambda function + * The state machine creates a couple of Lambdas that pass results forward + * and into a Choice state that validates the output. + * + * Stack verification steps: + * The generated State Machine can be executed from the CLI (or Step Functions console) + * and runs with an execution status of `Succeeded`. + * + * -- aws stepfunctions start-execution --state-machine-arn provides execution arn + * -- aws stepfunctions describe-execution --execution-arn returns a status of `Succeeded` + */ +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-stepfunctions-tasks-lambda-invoke-integ'); + +const submitJobLambda = new Function(stack, 'submitJobLambda', { + code: Code.fromInline(`exports.handler = async () => { + return { + statusCode: '200', + body: 'hello, world!' + }; + };`), + runtime: Runtime.NODEJS_10_X, + handler: 'index.handler', +}); + +const submitJob = new LambdaInvoke(stack, 'Invoke Handler', { + lambdaFunction: submitJobLambda, + payloadResponseOnly: true, +}); + +const checkJobStateLambda = new Function(stack, 'checkJobStateLambda', { + code: Code.fromInline(`exports.handler = async function(event, context) { + return { + status: event.statusCode === '200' ? 'SUCCEEDED' : 'FAILED' + }; + };`), + runtime: Runtime.NODEJS_10_X, + handler: 'index.handler', +}); + +const checkJobState = new LambdaInvoke(stack, 'Check the job state', { + lambdaFunction: checkJobStateLambda, + payloadResponseOnly: true, +}); + +const isComplete = new sfn.Choice(stack, 'Job Complete?'); +const jobFailed = new sfn.Fail(stack, 'Job Failed', { + cause: 'Job Failed', + error: 'Received a status that was not 200', +}); +const finalStatus = new sfn.Pass(stack, 'Final step'); + +const chain = sfn.Chain.start(submitJob) + .next(checkJobState) + .next( + isComplete + .when(sfn.Condition.stringEquals('$.status', 'FAILED'), jobFailed) + .when(sfn.Condition.stringEquals('$.status', 'SUCCEEDED'), finalStatus), + ); + +const sm = new sfn.StateMachine(stack, 'StateMachine', { + definition: chain, + timeout: cdk.Duration.seconds(30), +}); + +new cdk.CfnOutput(stack, 'stateMachineArn', { + value: sm.stateMachineArn, +}); + +app.synth(); 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 5bb39f17385ff..dc2cf11ac367b 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 @@ -4,7 +4,7 @@ import * as sfn from '@aws-cdk/aws-stepfunctions'; import { Stack } from '@aws-cdk/core'; import { LambdaInvocationType, LambdaInvoke } from '../../lib'; -// tslint:disable: object-literal-key-quotes +/* eslint-disable quote-props */ describe('LambdaInvoke', () => { @@ -177,6 +177,104 @@ describe('LambdaInvoke', () => { }); }); + test('Invoke lambda with payloadResponseOnly', () => { + // WHEN + const task = new LambdaInvoke(stack, 'Task', { + lambdaFunction, + payloadResponseOnly: true, + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + End: true, + Type: 'Task', + Resource: { + 'Fn::GetAtt': [ + 'Fn9270CBC0', + 'Arn', + ], + }, + }); + }); + + test('Invoke lambda with payloadResponseOnly with payload', () => { + // WHEN + const task = new LambdaInvoke(stack, 'Task', { + lambdaFunction, + payloadResponseOnly: true, + payload: sfn.TaskInput.fromObject({ + foo: 'bar', + }), + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + End: true, + Type: 'Task', + Resource: { + 'Fn::GetAtt': [ + 'Fn9270CBC0', + 'Arn', + ], + }, + Parameters: { + foo: 'bar', + }, + }); + }); + + test('fails when integrationPattern used with payloadResponseOnly', () => { + expect(() => { + new LambdaInvoke(stack, 'Task', { + lambdaFunction, + payloadResponseOnly: true, + integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, + payload: sfn.TaskInput.fromObject({ + token: sfn.JsonPath.taskToken, + }), + }); + }).toThrow(/The 'payloadResponseOnly' property cannot be used if 'integrationPattern', 'invocationType', 'clientContext', or 'qualifier' are specified./); + }); + + test('fails when invocationType used with payloadResponseOnly', () => { + expect(() => { + new LambdaInvoke(stack, 'Task', { + lambdaFunction, + payloadResponseOnly: true, + payload: sfn.TaskInput.fromObject({ + foo: 'bar', + }), + invocationType: LambdaInvocationType.REQUEST_RESPONSE, + }); + }).toThrow(/The 'payloadResponseOnly' property cannot be used if 'integrationPattern', 'invocationType', 'clientContext', or 'qualifier' are specified./); + }); + + test('fails when clientContext used with payloadResponseOnly', () => { + expect(() => { + new LambdaInvoke(stack, 'Task', { + lambdaFunction, + payloadResponseOnly: true, + payload: sfn.TaskInput.fromObject({ + foo: 'bar', + }), + clientContext: 'eyJoZWxsbyI6IndvcmxkIn0=', + }); + }).toThrow(/The 'payloadResponseOnly' property cannot be used if 'integrationPattern', 'invocationType', 'clientContext', or 'qualifier' are specified./); + }); + + test('fails when qualifier used with payloadResponseOnly', () => { + expect(() => { + new LambdaInvoke(stack, 'Task', { + lambdaFunction, + payloadResponseOnly: true, + payload: sfn.TaskInput.fromObject({ + foo: 'bar', + }), + qualifier: '1', + }); + }).toThrow(/The 'payloadResponseOnly' property cannot be used if 'integrationPattern', 'invocationType', 'clientContext', or 'qualifier' are specified./); + }); + test('fails when WAIT_FOR_TASK_TOKEN integration pattern is used without supplying a task token in payload', () => { expect(() => { new LambdaInvoke(stack, 'Task', { diff --git a/packages/@aws-cdk/aws-stepfunctions/.gitignore b/packages/@aws-cdk/aws-stepfunctions/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-stepfunctions/.gitignore +++ b/packages/@aws-cdk/aws-stepfunctions/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/.npmignore b/packages/@aws-cdk/aws-stepfunctions/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-stepfunctions/.npmignore +++ b/packages/@aws-cdk/aws-stepfunctions/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index 7a74e563b4cff..fc1ee6b294dcd 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -137,7 +137,7 @@ will be passed as the state's output. ```ts // Makes the current JSON state { ..., "subObject": { "hello": "world" } } const pass = new stepfunctions.Pass(this, 'Add Hello World', { - result: { hello: 'world' }, + result: stepfunctions.Result.fromObject({ hello: 'world' }), resultPath: '$.subObject', }); diff --git a/packages/@aws-cdk/aws-stepfunctions/jest.config.js b/packages/@aws-cdk/aws-stepfunctions/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-stepfunctions/jest.config.js +++ b/packages/@aws-cdk/aws-stepfunctions/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/condition.ts b/packages/@aws-cdk/aws-stepfunctions/lib/condition.ts index b447509546014..2f334af06190c 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/condition.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/condition.ts @@ -177,8 +177,8 @@ enum CompoundOperator { class VariableComparison extends Condition { constructor(private readonly variable: string, private readonly comparisonOperator: ComparisonOperator, private readonly value: any) { super(); - if (!/^\$[.[]/.test(variable)) { - throw new Error(`Variable reference must start with '$.' or '$[', got '${variable}'`); + if (!/^\$|(\$[.[])/.test(variable)) { + throw new Error(`Variable reference must be '$', start with '$.', or start with '$[', got '${variable}'`); } } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts index 14d65ccdc541a..08c375a62bf0b 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts @@ -1,5 +1,6 @@ import * as cdk from '@aws-cdk/core'; import { Chain } from '../chain'; +import { FieldUtils } from '../fields'; import { StateGraph } from '../state-graph'; import { CatchProps, IChainable, INextable, RetryProps } from '../types'; import { StateType } from './private/state-type'; @@ -156,6 +157,7 @@ export class Map extends State implements INextable { ResultPath: renderJsonPath(this.resultPath), ...this.renderNextEnd(), ...this.renderInputOutput(), + ...this.renderParameters(), ...this.renderRetryCatch(), ...this.renderIterator(), ...this.renderItemsPath(), @@ -185,4 +187,13 @@ export class Map extends State implements INextable { ItemsPath: renderJsonPath(this.itemsPath), }; } + + /** + * Render Parameters in ASL JSON format + */ + private renderParameters(): any { + return FieldUtils.renderObject({ + Parameters: this.parameters, + }); + } } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts index 8d82eecd398d7..d586a3e472d98 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts @@ -215,7 +215,7 @@ export abstract class State extends cdk.Construct implements IChainable { if (this.containingGraph === graph) { return; } if (this.containingGraph) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`Trying to use state '${this.stateId}' in ${graph}, but is already in ${this.containingGraph}. Every state can only be used in one graph.`); } diff --git a/packages/@aws-cdk/aws-stepfunctions/test/condition.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/condition.test.ts index afde2f815fcde..aef483b4583a5 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/condition.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/condition.test.ts @@ -11,6 +11,9 @@ describe('Condition Variables', () => { test('Condition variables can start with $[', () => { expect(() => stepfunctions.Condition.stringEquals('$[0]', 'a')).not.toThrow(); }), + test('Condition variables can reference the state input $', () => { + expect(() => stepfunctions.Condition.stringEquals('$', 'a')).not.toThrow(); + }), test('NotConditon must render properly', () => { assertRendersTo(stepfunctions.Condition.not(stepfunctions.Condition.stringEquals('$.a', 'b')), { Not: { Variable: '$.a', StringEquals: 'b' } }); }), diff --git a/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts index c5ad0bb6d5697..972764ea10d68 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts @@ -25,7 +25,10 @@ describe('Map State', () => { 'Map State': { Type: 'Map', End: true, - Parameters: { foo: 'foo', bar: '$.bar' }, + Parameters: { + 'foo': 'foo', + 'bar.$': '$.bar', + }, Iterator: { StartAt: 'Pass State', States: { diff --git a/packages/@aws-cdk/aws-synthetics/.gitignore b/packages/@aws-cdk/aws-synthetics/.gitignore index e9fee23607e76..5aa413b898780 100644 --- a/packages/@aws-cdk/aws-synthetics/.gitignore +++ b/packages/@aws-cdk/aws-synthetics/.gitignore @@ -2,7 +2,6 @@ *.js.map *.d.ts tsconfig.json -tslint.json node_modules *.generated.ts dist @@ -17,3 +16,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml diff --git a/packages/@aws-cdk/aws-synthetics/.npmignore b/packages/@aws-cdk/aws-synthetics/.npmignore index 1bd81793f7aa0..548b39048e917 100644 --- a/packages/@aws-cdk/aws-synthetics/.npmignore +++ b/packages/@aws-cdk/aws-synthetics/.npmignore @@ -22,4 +22,5 @@ tsconfig.json .eslintrc.js jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-synthetics/jest.config.js b/packages/@aws-cdk/aws-synthetics/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-synthetics/jest.config.js +++ b/packages/@aws-cdk/aws-synthetics/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-transfer/.gitignore b/packages/@aws-cdk/aws-transfer/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-transfer/.gitignore +++ b/packages/@aws-cdk/aws-transfer/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-transfer/.npmignore b/packages/@aws-cdk/aws-transfer/.npmignore index 2e1e60862c02c..5c003cc4e3040 100644 --- a/packages/@aws-cdk/aws-transfer/.npmignore +++ b/packages/@aws-cdk/aws-transfer/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-transfer/jest.config.js b/packages/@aws-cdk/aws-transfer/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-transfer/jest.config.js +++ b/packages/@aws-cdk/aws-transfer/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-waf/.gitignore b/packages/@aws-cdk/aws-waf/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-waf/.gitignore +++ b/packages/@aws-cdk/aws-waf/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-waf/.npmignore b/packages/@aws-cdk/aws-waf/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-waf/.npmignore +++ b/packages/@aws-cdk/aws-waf/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-waf/jest.config.js b/packages/@aws-cdk/aws-waf/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-waf/jest.config.js +++ b/packages/@aws-cdk/aws-waf/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-wafregional/.gitignore b/packages/@aws-cdk/aws-wafregional/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-wafregional/.gitignore +++ b/packages/@aws-cdk/aws-wafregional/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-wafregional/.npmignore b/packages/@aws-cdk/aws-wafregional/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-wafregional/.npmignore +++ b/packages/@aws-cdk/aws-wafregional/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-wafregional/jest.config.js b/packages/@aws-cdk/aws-wafregional/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-wafregional/jest.config.js +++ b/packages/@aws-cdk/aws-wafregional/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-wafv2/.gitignore b/packages/@aws-cdk/aws-wafv2/.gitignore index 94c80a5f08e4a..becda34c45624 100644 --- a/packages/@aws-cdk/aws-wafv2/.gitignore +++ b/packages/@aws-cdk/aws-wafv2/.gitignore @@ -13,3 +13,5 @@ dist tsconfig.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-wafv2/.npmignore b/packages/@aws-cdk/aws-wafv2/.npmignore index 683e3e0847e1f..a7c5b49852b3b 100644 --- a/packages/@aws-cdk/aws-wafv2/.npmignore +++ b/packages/@aws-cdk/aws-wafv2/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-wafv2/jest.config.js b/packages/@aws-cdk/aws-wafv2/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-wafv2/jest.config.js +++ b/packages/@aws-cdk/aws-wafv2/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-workspaces/.gitignore b/packages/@aws-cdk/aws-workspaces/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/aws-workspaces/.gitignore +++ b/packages/@aws-cdk/aws-workspaces/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-workspaces/.npmignore b/packages/@aws-cdk/aws-workspaces/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/aws-workspaces/.npmignore +++ b/packages/@aws-cdk/aws-workspaces/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-workspaces/jest.config.js b/packages/@aws-cdk/aws-workspaces/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-workspaces/jest.config.js +++ b/packages/@aws-cdk/aws-workspaces/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/cdk-assets-schema/.gitignore b/packages/@aws-cdk/cdk-assets-schema/.gitignore index 9c86e7fa0fe0b..bb785cfb74f08 100644 --- a/packages/@aws-cdk/cdk-assets-schema/.gitignore +++ b/packages/@aws-cdk/cdk-assets-schema/.gitignore @@ -14,3 +14,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/cdk-assets-schema/.npmignore b/packages/@aws-cdk/cdk-assets-schema/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/cdk-assets-schema/.npmignore +++ b/packages/@aws-cdk/cdk-assets-schema/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/cdk-assets-schema/jest.config.js b/packages/@aws-cdk/cdk-assets-schema/jest.config.js index c68c147dd5514..5c1ef76634a9f 100644 --- a/packages/@aws-cdk/cdk-assets-schema/jest.config.js +++ b/packages/@aws-cdk/cdk-assets-schema/jest.config.js @@ -1,4 +1,4 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = { ...baseConfig, coverageThreshold: { diff --git a/packages/@aws-cdk/cdk-assets-schema/lib/index.ts b/packages/@aws-cdk/cdk-assets-schema/lib/index.ts index c90b194d4e271..d016a0a03cf7d 100644 --- a/packages/@aws-cdk/cdk-assets-schema/lib/index.ts +++ b/packages/@aws-cdk/cdk-assets-schema/lib/index.ts @@ -1,4 +1,4 @@ -// tslint:disable-next-line: no-console +/* eslint-disable no-console */ console.error('error: @aws-cdk/cdk-assets-schema has been merged into @aws-cdk/cloud-assembly-schema'); export { }; diff --git a/packages/@aws-cdk/cdk-assets-schema/package.json b/packages/@aws-cdk/cdk-assets-schema/package.json index 8d65ed30aecd4..4659569db0eec 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.3", + "@types/jest": "^26.0.4", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", "pkglint": "0.0.0" diff --git a/packages/@aws-cdk/cfnspec/build-tools/build.ts b/packages/@aws-cdk/cfnspec/build-tools/build.ts index 5b2910f12d7c4..941d8e95d8915 100644 --- a/packages/@aws-cdk/cfnspec/build-tools/build.ts +++ b/packages/@aws-cdk/cfnspec/build-tools/build.ts @@ -5,10 +5,10 @@ * document at `spec/specification.json`. */ +import * as path from 'path'; import * as fastJsonPatch from 'fast-json-patch'; import * as fs from 'fs-extra'; import * as md5 from 'md5'; -import * as path from 'path'; import { schema } from '../lib'; import { detectScrutinyTypes } from './scrutiny'; @@ -71,7 +71,7 @@ function replaceIncompleteTypes(spec: schema.Specification) { && !schema.isCollectionProperty(definition) && !schema.isScalarProperty(definition) && !schema.isPrimitiveProperty(definition)) { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.log(`[${name}] Incomplete type, adding empty "Properties" field`); (definition as unknown as schema.RecordProperty).Properties = {}; @@ -103,11 +103,11 @@ function merge(spec: any, fragment: any, jsonPath: string[]) { const specVal = spec[key]; const fragVal = fragment[key]; if (typeof specVal !== typeof fragVal) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`Attempted to merge ${JSON.stringify(fragVal)} into incompatible ${JSON.stringify(specVal)} at path ${jsonPath.join('/')}/${key}`); } if (typeof specVal !== 'object') { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`Conflict when attempting to merge ${JSON.stringify(fragVal)} into ${JSON.stringify(specVal)} at path ${jsonPath.join('/')}/${key}`); } merge(specVal, fragVal, [...jsonPath, key]); @@ -120,7 +120,7 @@ function merge(spec: any, fragment: any, jsonPath: string[]) { function patch(spec: any, fragment: any) { if (!fragment) { return; } if ('patch' in fragment) { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.log(`Applying patch: ${fragment.patch.description}`); fastJsonPatch.applyPatch(spec, fragment.patch.operations); } else { @@ -155,7 +155,7 @@ function normalize(spec: schema.Specification): schema.Specification { main() .catch(e => { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.error(e.stack); process.exit(-1); }); diff --git a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts index d2cb97e565a29..2454f92b35e91 100644 --- a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts +++ b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts @@ -5,13 +5,13 @@ * have an AWS construct library. */ -import * as fs from 'fs-extra'; import * as path from 'path'; +import * as fs from 'fs-extra'; import * as cfnspec from '../lib'; // don't be a prude: -// tslint:disable:no-console -// tslint:disable:object-literal-key-quotes +/* eslint-disable no-console */ +/* eslint-disable quote-props */ async function main() { const root = path.join(__dirname, '..', '..'); @@ -192,7 +192,6 @@ async function main() { '*.js.map', '*.d.ts', 'tsconfig.json', - 'tslint.json', 'node_modules', '*.generated.ts', 'dist', @@ -275,7 +274,7 @@ async function main() { ]); await write('jest.config.js', [ - "const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config');", + "const baseConfig = require('cdk-build-tools/config/jest.config');", 'module.exports = baseConfig;', ]); diff --git a/packages/@aws-cdk/cfnspec/build-tools/spec-diff.ts b/packages/@aws-cdk/cfnspec/build-tools/spec-diff.ts index 539daba733844..889e631eacf27 100644 --- a/packages/@aws-cdk/cfnspec/build-tools/spec-diff.ts +++ b/packages/@aws-cdk/cfnspec/build-tools/spec-diff.ts @@ -1,8 +1,7 @@ -import * as fs from 'fs-extra'; import * as util from 'util'; +import * as fs from 'fs-extra'; /* eslint-disable @typescript-eslint/no-require-imports */ -// tslint:disable-next-line:no-var-requires const jsonDiff = require('json-diff').diff; /* eslint-enable */ diff --git a/packages/@aws-cdk/cfnspec/package.json b/packages/@aws-cdk/cfnspec/package.json index dc442ba4f5d9e..42a75db2906aa 100644 --- a/packages/@aws-cdk/cfnspec/package.json +++ b/packages/@aws-cdk/cfnspec/package.json @@ -6,6 +6,7 @@ "update": "cdk-build && /bin/bash build-tools/update.sh", "build": "cdk-build && node build-tools/build", "watch": "cdk-watch", + "lint": "cdk-lint", "pkglint": "pkglint -f", "test": "cdk-test", "package": "cdk-package", diff --git a/packages/@aws-cdk/cfnspec/test/test.augmentation.ts b/packages/@aws-cdk/cfnspec/test/test.augmentation.ts index 2c65016a4e037..34222c197d867 100644 --- a/packages/@aws-cdk/cfnspec/test/test.augmentation.ts +++ b/packages/@aws-cdk/cfnspec/test/test.augmentation.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; -import { Test } from 'nodeunit'; import * as path from 'path'; +import { Test } from 'nodeunit'; import * as cfnspec from '../lib'; import { MetricType } from '../lib/schema'; diff --git a/packages/@aws-cdk/cloud-assembly-schema/.gitignore b/packages/@aws-cdk/cloud-assembly-schema/.gitignore index 06b07c0980981..eec83f25377a7 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/.gitignore +++ b/packages/@aws-cdk/cloud-assembly-schema/.gitignore @@ -6,7 +6,6 @@ scripts/*.js node_modules dist tsconfig.json -tslint.json .jsii .LAST_BUILD @@ -17,3 +16,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml diff --git a/packages/@aws-cdk/cloud-assembly-schema/.npmignore b/packages/@aws-cdk/cloud-assembly-schema/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/.npmignore +++ b/packages/@aws-cdk/cloud-assembly-schema/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/jest.config.js b/packages/@aws-cdk/cloud-assembly-schema/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/jest.config.js +++ b/packages/@aws-cdk/cloud-assembly-schema/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts index 66275b88e312b..4c7c5229f0fc0 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts @@ -4,27 +4,23 @@ import * as semver from 'semver'; import * as assets from './assets'; import * as assembly from './cloud-assembly'; +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-require-imports */ + // this prefix is used by the CLI to identify this specific error. // in which case we want to instruct the user to upgrade his CLI. // see exec.ts#createAssembly export const VERSION_MISMATCH: string = 'Cloud assembly schema version mismatch'; -// tslint:disable: no-var-requires - -// eslint-disable-next-line @typescript-eslint/no-require-imports const ASSETS_SCHEMA = require('../schema/assets.schema.json'); -// eslint-disable-next-line @typescript-eslint/no-require-imports const ASSEMBLY_SCHEMA = require('../schema/cloud-assembly.schema.json'); /** * Version is shared for both manifests */ -// eslint-disable-next-line @typescript-eslint/no-require-imports const SCHEMA_VERSION = require('../schema/cloud-assembly.version.json').version; -// tslint:enable: no-var-requires - /** * Protocol utility class. */ diff --git a/packages/@aws-cdk/cloud-assembly-schema/package.json b/packages/@aws-cdk/cloud-assembly-schema/package.json index eac2654ff030b..4fd383e55aba2 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.3", + "@types/jest": "^26.0.4", "@types/mock-fs": "^4.10.0", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", diff --git a/packages/@aws-cdk/cloud-assembly-schema/scripts/update-schema.ts b/packages/@aws-cdk/cloud-assembly-schema/scripts/update-schema.ts index 2c8ac1c50f236..350a26e51f9fe 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/scripts/update-schema.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/scripts/update-schema.ts @@ -5,7 +5,7 @@ import * as semver from 'semver'; import * as tjs from 'typescript-json-schema'; function log(message: string) { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.log(message); } diff --git a/packages/@aws-cdk/cloudformation-diff/.gitignore b/packages/@aws-cdk/cloudformation-diff/.gitignore index 9d5b9f1ce1539..c9b9bcc8658a1 100644 --- a/packages/@aws-cdk/cloudformation-diff/.gitignore +++ b/packages/@aws-cdk/cloudformation-diff/.gitignore @@ -12,3 +12,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-diff/.npmignore b/packages/@aws-cdk/cloudformation-diff/.npmignore index f5e2864c265a7..582a6ea324723 100644 --- a/packages/@aws-cdk/cloudformation-diff/.npmignore +++ b/packages/@aws-cdk/cloudformation-diff/.npmignore @@ -17,4 +17,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-diff/jest.config.js b/packages/@aws-cdk/cloudformation-diff/jest.config.js index e4f227679d683..2bf3bec36d750 100644 --- a/packages/@aws-cdk/cloudformation-diff/jest.config.js +++ b/packages/@aws-cdk/cloudformation-diff/jest.config.js @@ -1,4 +1,4 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = { ...baseConfig, coverageThreshold: { diff --git a/packages/@aws-cdk/cloudformation-diff/lib/diff/types.ts b/packages/@aws-cdk/cloudformation-diff/lib/diff/types.ts index 1fb9d025cff20..4f75a233e6a74 100644 --- a/packages/@aws-cdk/cloudformation-diff/lib/diff/types.ts +++ b/packages/@aws-cdk/cloudformation-diff/lib/diff/types.ts @@ -1,5 +1,5 @@ -import * as cfnspec from '@aws-cdk/cfnspec'; import { AssertionError } from 'assert'; +import * as cfnspec from '@aws-cdk/cfnspec'; import { IamChanges } from '../iam/iam-changes'; import { SecurityGroupChanges } from '../network/security-group-changes'; import { deepEqual } from './util'; diff --git a/packages/@aws-cdk/cloudformation-diff/lib/format.ts b/packages/@aws-cdk/cloudformation-diff/lib/format.ts index 8a5cd4bd260eb..53e7177ad914f 100644 --- a/packages/@aws-cdk/cloudformation-diff/lib/format.ts +++ b/packages/@aws-cdk/cloudformation-diff/lib/format.ts @@ -1,5 +1,5 @@ -import * as colors from 'colors/safe'; import { format } from 'util'; +import * as colors from 'colors/safe'; import { Difference, isPropertyDifference, ResourceDifference, ResourceImpact } from './diff-template'; import { DifferenceCollection, TemplateDiff } from './diff/types'; import { deepEqual } from './diff/util'; @@ -11,7 +11,6 @@ import { SecurityGroupChanges } from './network/security-group-changes'; const PATH_METADATA_KEY = 'aws:cdk:path'; /* eslint-disable @typescript-eslint/no-require-imports */ -// tslint:disable-next-line:no-var-requires const { structuredPatch } = require('diff'); /* eslint-enable */ @@ -159,7 +158,7 @@ class Formatter { const resourceType = diff.isRemoval ? diff.oldResourceType : diff.newResourceType; - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len this.print(`${this.formatPrefix(diff)} ${this.formatValue(resourceType, colors.cyan)} ${this.formatLogicalId(logicalId)} ${this.formatImpact(diff.changeImpact)}`); if (diff.isUpdate) { diff --git a/packages/@aws-cdk/cloudformation-diff/package.json b/packages/@aws-cdk/cloudformation-diff/package.json index 3196b202c7f27..9ae38d5877c09 100644 --- a/packages/@aws-cdk/cloudformation-diff/package.json +++ b/packages/@aws-cdk/cloudformation-diff/package.json @@ -29,14 +29,14 @@ "table": "^5.4.6" }, "devDependencies": { - "@types/jest": "^26.0.3", + "@types/jest": "^26.0.4", "@types/string-width": "^4.0.1", "@types/table": "^4.0.7", "cdk-build-tools": "0.0.0", - "fast-check": "^1.25.1", + "fast-check": "^1.26.0", "jest": "^25.5.4", "pkglint": "0.0.0", - "ts-jest": "^26.1.1" + "ts-jest": "^26.1.3" }, "repository": { "url": "https://github.com/aws/aws-cdk.git", diff --git a/packages/@aws-cdk/cloudformation-diff/test/network/rule.test.ts b/packages/@aws-cdk/cloudformation-diff/test/network/rule.test.ts index 49220f961a0fa..e790deb4ed7ad 100644 --- a/packages/@aws-cdk/cloudformation-diff/test/network/rule.test.ts +++ b/packages/@aws-cdk/cloudformation-diff/test/network/rule.test.ts @@ -88,7 +88,7 @@ const twoArbitraryRules = fc.record({ rule2: arbitraryRule, copyIp: fc.boolean(), copyFromPort: fc.boolean(), - copyToPort : fc.boolean(), + copyToPort: fc.boolean(), copyCidrIp: fc.boolean(), copySecurityGroupId: fc.boolean(), copyPrefixListId: fc.boolean(), diff --git a/packages/@aws-cdk/cloudformation-include/.gitignore b/packages/@aws-cdk/cloudformation-include/.gitignore index 4bd1c7e74895a..afe770ed84a67 100644 --- a/packages/@aws-cdk/cloudformation-include/.gitignore +++ b/packages/@aws-cdk/cloudformation-include/.gitignore @@ -1,5 +1,4 @@ *.js -tslint.json *.js.map *.d.ts *.generated.ts @@ -20,3 +19,5 @@ nyc.config.js !build.js cfn-types-2-classes.json !jest.config.js + +junit.xml diff --git a/packages/@aws-cdk/cloudformation-include/.npmignore b/packages/@aws-cdk/cloudformation-include/.npmignore index b4fa4a2dc1486..5f0a7e632cc2b 100644 --- a/packages/@aws-cdk/cloudformation-include/.npmignore +++ b/packages/@aws-cdk/cloudformation-include/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/README.md b/packages/@aws-cdk/cloudformation-include/README.md index 01be06067dde4..e8ea292934958 100644 --- a/packages/@aws-cdk/cloudformation-include/README.md +++ b/packages/@aws-cdk/cloudformation-include/README.md @@ -142,6 +142,92 @@ and any changes you make to it will be reflected in the resulting template: output.value = cfnBucket.attrArn; ``` +## Nested Stacks + +This module also support templates that use [nested stacks](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-nested-stacks.html). + +For example, if you have the following parent template: + +```json +{ + "Resources": { + "ChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://my-s3-template-source.s3.amazonaws.com/child-import-stack.json", + "Parameters": { + "MyBucketParameter": "my-bucket-name" + } + } + } + } +} +``` + +where the child template pointed to by `https://my-s3-template-source.s3.amazonaws.com/child-import-stack.json` is: + +```json +{ + "Parameters": { + "MyBucketParameter": { + "Type": "String", + "Default": "default-bucket-param-name" + } + }, + "Resources": { + "BucketImport": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Ref": "MyBucketParameter" + } + } + } + } +} +``` + +You can include both the parent stack and the nested stack in your CDK Application as follows: + +```typescript +const parentTemplate = new inc.CfnInclude(stack, 'ParentStack', { + templateFile: 'path/to/my-parent-template.json', + nestedStacks: { + 'ChildStack': { + templateFile: 'path/to/my-nested-template.json', + }, + }, +}); +``` + +Now you can access the ChildStack nested stack and included template with: + +```typescript +const includedChildStack = parentTemplate.getNestedStack('ChildStack'); +const childStack: core.NestedStack = includedChildStack.stack; +const childStackTemplate: cfn_inc.CfnInclude = includedChildStack.includedTemplate; +``` + +Now you can reference resources from `ChildStack` and modify them like any other included template: + +```typescript +const bucket = childStackTemplate.getResource('MyBucket') as s3.CfnBucket; +bucket.bucketName = 'my-new-bucket-name'; + +const bucketReadRole = new iam.Role(childStack, 'MyRole', { + assumedBy: new iam.AccountRootPrincipal(), +}); + +bucketReadRole.addToPolicy(new iam.PolicyStatement({ + actions: [ + 's3:GetObject*', + 's3:GetBucket*', + 's3:List*', + ], + resources: [bucket.attrArn], +})); +``` + ## Known limitations This module is still in its early, experimental stage, diff --git a/packages/@aws-cdk/cloudformation-include/jest.config.js b/packages/@aws-cdk/cloudformation-include/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/cloudformation-include/jest.config.js +++ b/packages/@aws-cdk/cloudformation-include/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index 035631fd282d5..7be10e373ae45 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -13,6 +13,36 @@ export interface CfnIncludeProps { * Both JSON and YAML template formats are supported. */ readonly templateFile: string; + + /** + * Specifies the template files that define nested stacks that should be included. + * + * If your template specifies a stack that isn't included here, it won't be created as a NestedStack + * resource, and it won't be accessible from {@link CfnInclude.getNestedStack}. + * + * If you include a stack here with an ID that isn't in the template, + * or is in the template but is not a nested stack, + * template creation will fail and an error will be thrown. + */ + readonly nestedStacks?: { [stackName: string]: CfnIncludeProps }; +} + +/** + * The type returned from {@link CfnInclude.getNestedStack}. + * Contains both the NestedStack object and + * CfnInclude representations of the child stack. + */ +export interface IncludedNestedStack { + /** + * The NestedStack object which respresents the scope of the template. + */ + readonly stack: core.NestedStack; + + /** + * The CfnInclude that respresents the template, which can + * be used to access Resources and other template elements. + */ + readonly includedTemplate: CfnInclude; } /** @@ -25,6 +55,8 @@ export class CfnInclude extends core.CfnElement { private readonly resources: { [logicalId: string]: core.CfnResource } = {}; private readonly parameters: { [logicalId: string]: core.CfnParameter } = {}; private readonly outputs: { [logicalId: string]: core.CfnOutput } = {}; + private readonly nestedStacks: { [logicalId: string]: IncludedNestedStack } = {}; + private readonly nestedStacksToInclude: { [name: string]: CfnIncludeProps }; private readonly template: any; private readonly preserveLogicalIds: boolean; @@ -47,11 +79,20 @@ export class CfnInclude extends core.CfnElement { this.createCondition(conditionName); } + this.nestedStacksToInclude = props.nestedStacks || {}; + // instantiate all resources as CDK L1 objects for (const logicalId of Object.keys(this.template.Resources || {})) { this.getOrCreateResource(logicalId); } + // verify that all nestedStacks have been instantiated + for (const nestedStackId of Object.keys(props.nestedStacks || {})) { + if (!(nestedStackId in this.resources)) { + throw new Error(`Nested Stack with logical ID '${nestedStackId}' was not found in the template`); + } + } + const outputScope = new core.Construct(this, '$Ouputs'); for (const logicalId of Object.keys(this.template.Outputs || {})) { @@ -137,6 +178,24 @@ export class CfnInclude extends core.CfnElement { return ret; } + /** + * Returns the NestedStack with name logicalId. + * For a nested stack to be returned by this method, it must be specified in the {@link CfnIncludeProps.nestedStacks} + * @param logicalId the ID of the stack to retrieve, as it appears in the template. + */ + public getNestedStack(logicalId: string): IncludedNestedStack { + if (!this.nestedStacks[logicalId]) { + if (!this.template.Resources[logicalId]) { + throw new Error(`Nested Stack with logical ID '${logicalId}' was not found in the template`); + } else if (this.template.Resources[logicalId].Type !== 'AWS::CloudFormation::Stack') { + throw new Error(`Resource with logical ID '${logicalId}' is not a CloudFormation Stack`); + } else { + throw new Error(`Nested Stack '${logicalId}' was not included in the nestedStacks property when including the parent template`); + } + } + return this.nestedStacks[logicalId]; + } + /** @internal */ public _toCloudFormation(): object { const ret: { [section: string]: any } = {}; @@ -283,7 +342,10 @@ export class CfnInclude extends core.CfnElement { const options: core.FromCloudFormationOptions = { finder, }; - const l1Instance = jsClassFromModule.fromCloudFormation(this, logicalId, resourceAttributes, options); + + const l1Instance = this.nestedStacksToInclude[logicalId] + ? this.createNestedStack(logicalId, finder) + : jsClassFromModule.fromCloudFormation(this, logicalId, resourceAttributes, options); if (this.preserveLogicalIds) { // override the logical ID to match the original template @@ -293,4 +355,66 @@ export class CfnInclude extends core.CfnElement { this.resources[logicalId] = l1Instance; return l1Instance; } + + private createNestedStack(nestedStackId: string, finder: core.ICfnFinder): core.CfnResource { + const templateResources = this.template.Resources || {}; + const nestedStackAttributes = templateResources[nestedStackId] || {}; + + if (nestedStackAttributes.Type !== 'AWS::CloudFormation::Stack') { + throw new Error(`Nested Stack with logical ID '${nestedStackId}' is not an AWS::CloudFormation::Stack resource`); + } + if (nestedStackAttributes.CreationPolicy) { + throw new Error('CreationPolicy is not supported by the AWS::CloudFormation::Stack resource'); + } + if (nestedStackAttributes.UpdatePolicy) { + throw new Error('UpdatePolicy is not supported by the AWS::CloudFormation::Stack resource'); + } + + const cfnParser = new cfn_parse.CfnParser({ + finder, + }); + const nestedStackProps = cfnParser.parseValue(nestedStackAttributes.Properties); + const nestedStack = new core.NestedStack(this, nestedStackId, { + parameters: nestedStackProps.Parameters, + notificationArns: nestedStackProps.NotificationArns, + timeout: nestedStackProps.Timeout, + }); + + // we know this is never undefined for nested stacks + const nestedStackResource: core.CfnResource = nestedStack.nestedStackResource!; + // handle resource attributes + const cfnOptions = nestedStackResource.cfnOptions; + cfnOptions.metadata = cfnParser.parseValue(nestedStackAttributes.Metadata); + cfnOptions.deletionPolicy = cfnParser.parseDeletionPolicy(nestedStackAttributes.DeletionPolicy); + cfnOptions.updateReplacePolicy = cfnParser.parseDeletionPolicy(nestedStackAttributes.UpdateReplacePolicy); + // handle DependsOn + nestedStackAttributes.DependsOn = nestedStackAttributes.DependsOn ?? []; + const dependencies: string[] = Array.isArray(nestedStackAttributes.DependsOn) ? + nestedStackAttributes.DependsOn : [nestedStackAttributes.DependsOn]; + for (const dep of dependencies) { + const depResource = finder.findResource(dep); + if (!depResource) { + throw new Error(`nested stack '${nestedStackId}' depends on '${dep}' that doesn't exist`); + } + nestedStackResource.node.addDependency(depResource); + } + // handle Condition + if (nestedStackAttributes.Condition) { + const condition = finder.findCondition(nestedStackAttributes.Condition); + if (!condition) { + throw new Error(`nested stack '${nestedStackId}' uses Condition '${nestedStackAttributes.Condition}' that doesn't exist`); + } + cfnOptions.condition = condition; + } + + const propStack = this.nestedStacksToInclude[nestedStackId]; + const template = new CfnInclude(nestedStack, nestedStackId, { + templateFile: propStack.templateFile, + nestedStacks: propStack.nestedStacks, + }); + const includedStack: IncludedNestedStack = { stack: nestedStack, includedTemplate: template }; + this.nestedStacks[nestedStackId] = includedStack; + + return nestedStackResource; + } } diff --git a/packages/@aws-cdk/cloudformation-include/package.json b/packages/@aws-cdk/cloudformation-include/package.json index de407f258d640..6bc822e8c5c97 100644 --- a/packages/@aws-cdk/cloudformation-include/package.json +++ b/packages/@aws-cdk/cloudformation-include/package.json @@ -37,6 +37,7 @@ "watch": "cdk-watch", "lint": "cdk-lint", "test": "cdk-test", + "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", "awslint": "cdk-awslint", @@ -302,12 +303,13 @@ }, "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/jest": "^26.0.3", + "@types/jest": "^26.0.4", "@types/yaml": "1.2.0", "cdk-build-tools": "0.0.0", + "cdk-integ-tools": "0.0.0", "jest": "^25.4.0", "pkglint": "0.0.0", - "ts-jest": "^26.1.1" + "ts-jest": "^26.1.3" }, "bundledDependencies": [ "yaml" @@ -327,6 +329,11 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, + "awslint": { + "exclude": [ + "props-no-cfn-types:@aws-cdk/cloudformation-include.CfnIncludeProps.nestedStacks" + ] + }, "stability": "experimental", "maturity": "experimental", "awscdkio": { diff --git a/packages/@aws-cdk/cloudformation-include/test/integ.nested-stacks.expected.json b/packages/@aws-cdk/cloudformation-include/test/integ.nested-stacks.expected.json new file mode 100644 index 0000000000000..1a8759a8256de --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/integ.nested-stacks.expected.json @@ -0,0 +1,72 @@ +{ + "Resources": { + "ChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3BucketEAA24F0C" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3VersionKey1194CAB2" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3VersionKey1194CAB2" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "MyBucketParameter": "some-magic-bucket-name" + } + } + } + }, + "Parameters": { + "AssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3BucketEAA24F0C": { + "Type": "String", + "Description": "S3 bucket for asset \"5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50\"" + }, + "AssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3VersionKey1194CAB2": { + "Type": "String", + "Description": "S3 key for asset version \"5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50\"" + }, + "AssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50ArtifactHash9C417847": { + "Type": "String", + "Description": "Artifact hash for asset \"5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/integ.nested-stacks.ts b/packages/@aws-cdk/cloudformation-include/test/integ.nested-stacks.ts new file mode 100644 index 0000000000000..5ccd876c328db --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/integ.nested-stacks.ts @@ -0,0 +1,17 @@ +import * as core from '@aws-cdk/core'; +import * as inc from '../lib'; + +const app = new core.App(); + +const stack = new core.Stack(app, 'ParentStack'); + +new inc.CfnInclude(stack, 'ParentStack', { + templateFile: 'test-templates/nested/parent-one-child.json', + nestedStacks: { + ChildStack: { + templateFile: 'test-templates/nested/grandchild-import-stack.json', + }, + }, +}); + +app.synth(); diff --git a/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts index 0d1ba70795e5d..765b0a7de265e 100644 --- a/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts @@ -1,7 +1,7 @@ +import * as path from 'path'; import { SynthUtils } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import * as core from '@aws-cdk/core'; -import * as path from 'path'; import * as inc from '../lib'; describe('CDK Include', () => { diff --git a/packages/@aws-cdk/cloudformation-include/test/nested-stacks.test.ts b/packages/@aws-cdk/cloudformation-include/test/nested-stacks.test.ts new file mode 100644 index 0000000000000..0d16cdfa057cc --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/nested-stacks.test.ts @@ -0,0 +1,588 @@ +import * as path from 'path'; +import { ResourcePart } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as core from '@aws-cdk/core'; +import * as inc from '../lib'; +import * as futils from '../lib/file-utils'; + +/* eslint-disable quote-props */ +/* eslint-disable quotes */ + +describe('CDK Include', () => { + let stack: core.Stack; + + beforeEach(() => { + stack = new core.Stack(); + }); + + test('can ingest a template with one child', () => { + const parentTemplate = new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-one-child.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('grandchild-import-stack.json'), + }, + }, + }); + + const childStack = parentTemplate.getNestedStack('ChildStack'); + expect(childStack.stack).toMatchTemplate( + loadTestFileToJsObject('grandchild-import-stack.json'), + ); + }); + + test('can ingest a template with two children', () => { + const parentTemplate = new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-two-children.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('grandchild-import-stack.json'), + }, + 'AnotherChildStack': { + templateFile: testTemplateFilePath('grandchild-import-stack.json'), + }, + }, + }); + + const childStack = parentTemplate.getNestedStack('ChildStack'); + const anotherChildStack = parentTemplate.getNestedStack('AnotherChildStack'); + expect(childStack.stack).toMatchTemplate( + loadTestFileToJsObject('grandchild-import-stack.json'), + ); + + expect(anotherChildStack.stack).toMatchTemplate( + loadTestFileToJsObject('grandchild-import-stack.json'), + ); + }); + + test('can ingest a template with one child and one grandchild', () => { + const parentTemplate = new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-two-children.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('child-import-stack.json'), + nestedStacks: { + 'GrandChildStack': { + templateFile: testTemplateFilePath('grandchild-import-stack.json'), + }, + }, + }, + }, + }); + + const childStack = parentTemplate.getNestedStack('ChildStack'); + const grandChildStack = childStack.includedTemplate.getNestedStack('GrandChildStack'); + expect(childStack.stack).toMatchTemplate( + loadTestFileToJsObject('child-import-stack.expected.json'), + ); + + expect(grandChildStack.stack).toMatchTemplate( + loadTestFileToJsObject('grandchild-import-stack.json'), + ); + }); + + test('throws an error when provided a nested stack that is not present in the template', () => { + expect(() => { + new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-two-children.json'), + nestedStacks: { + 'FakeStack': { + templateFile: testTemplateFilePath('child-import-stack.json'), + }, + }, + }); + }).toThrow(/Nested Stack with logical ID 'FakeStack' was not found in the template/); + }); + + test('throws an exception when NestedStacks contains an ID that is not a CloudFormation::Stack in the template', () => { + expect(() => { + new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('child-import-stack.json'), + nestedStacks: { + 'BucketImport': { + templateFile: testTemplateFilePath('grandchild-import-stack.json'), + }, + }, + }); + }).toThrow(/Nested Stack with logical ID 'BucketImport' is not an AWS::CloudFormation::Stack resource/); + }); + + test('throws an exception when the nestedStack resource uses the CreationPolicy attribute', () => { + expect(() => { + new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-creation-policy.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('grandchild-import-stack.json'), + }, + }, + }); + }).toThrow(/CreationPolicy is not supported by the AWS::CloudFormation::Stack resource/); + }); + + test('throws an exception when the nested stack resource uses the UpdatePolicy attribute', () => { + expect(() => { + new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-update-policy.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('grandchild-import-stack.json'), + }, + }, + }); + }).toThrow(/UpdatePolicy is not supported by the AWS::CloudFormation::Stack resource/); + }); + + test('throws an exception when a nested stack refers to a Condition that does not exist in the template', () => { + expect(() => { + new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-invalid-condition.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('grandchild-import-stack.json'), + }, + }, + }); + }).toThrow(/nested stack 'ChildStack' uses Condition 'FakeCondition' that doesn't exist/); + }); + + test('throws an exception when a nested stacks depends on a resource that does not exist in the template', () => { + expect(() => { + new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-bad-depends-on.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('child-import-stack.json'), + }, + }, + }); + }).toThrow(/nested stack 'ChildStack' depends on 'AFakeResource' that doesn't exist/); + }); + + test('can modify resources in nested stacks', () => { + const parent = new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('child-import-stack.json'), + nestedStacks: { + 'GrandChildStack': { + templateFile: testTemplateFilePath('grandchild-import-stack.json'), + }, + }, + }); + + const childTemplate = parent.getNestedStack('GrandChildStack').includedTemplate; + const bucket = childTemplate.getResource('BucketImport') as s3.CfnBucket; + + bucket.bucketName = 'modified-bucket-name'; + + expect(childTemplate.stack).toHaveResource('AWS::S3::Bucket', { BucketName: 'modified-bucket-name' }); + }); + + test('can use a condition', () => { + const parent = new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-valid-condition.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('grandchild-import-stack.json'), + }, + }, + }); + + const alwaysFalseCondition = parent.getCondition('AlwaysFalseCond'); + + expect(parent.getResource('ChildStack').cfnOptions.condition).toBe(alwaysFalseCondition); + }); + + test('asset parameters generated in parent and child are identical', () => { + new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-one-child.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('grandchild-import-stack.json'), + }, + }, + }); + + const assetParam = 'AssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3BucketEAA24F0C'; + const assetParamKey = 'AssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3VersionKey1194CAB2'; + expect(stack).toMatchTemplate({ + "Parameters": { + [assetParam]: { + "Type": "String", + "Description": "S3 bucket for asset \"5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50\"", + }, + [assetParamKey]: { + "Type": "String", + "Description": "S3 key for asset version \"5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50\"", + }, + "AssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50ArtifactHash9C417847": { + "Type": "String", + "Description": "Artifact hash for asset \"5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50\"", + }, + }, + "Resources": { + "ChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ "", [ + "https://s3.", + { "Ref": "AWS::Region" }, + ".", + { "Ref": "AWS::URLSuffix" }, + "/", + { "Ref": assetParam }, + "/", + { "Fn::Select": [ + 0, + { "Fn::Split": [ + "||", + { "Ref": assetParamKey }, + ]}, + ]}, + { "Fn::Select": [ + 1, + { "Fn::Split": [ + "||", + { "Ref": assetParamKey }, + ]}, + ]}, + ]], + }, + "Parameters": { + "MyBucketParameter": "some-magic-bucket-name", + }, + }, + }, + }, + }); + }); + + test('templates with nested stacks that were not provided in the nestedStacks property are left unmodified', () => { + new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-two-children.json'), + }); + + expect(stack).toMatchTemplate(loadTestFileToJsObject('parent-two-children.json')); + }); + + test('getNestedStack() throws an exception when getting a resource that does not exist in the template', () => { + const parentTemplate = new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-two-children.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('child-import-stack.json'), + }, + }, + }); + + expect(() => { + parentTemplate.getNestedStack('FakeStack'); + }).toThrow(/Nested Stack with logical ID 'FakeStack' was not found/); + }); + + test('getNestedStack() throws an exception when getting a resource that exists in the template, but is not a Stack', () => { + const parentTemplate = new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-two-children.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('child-import-stack.json'), + }, + }, + }); + + const childTemplate = parentTemplate.getNestedStack('ChildStack').includedTemplate; + + expect(() => { + childTemplate.getNestedStack('BucketImport'); + }).toThrow(/Resource with logical ID 'BucketImport' is not a CloudFormation Stack/); + }); + + test('getNestedStack() throws an exception when getting a resource that exists in the template, but was not specified in the props', () => { + const parentTemplate = new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-two-children.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('child-import-stack.json'), + }, + }, + }); + + expect(() => { + parentTemplate.getNestedStack('AnotherChildStack'); + }).toThrow(/Nested Stack 'AnotherChildStack' was not included in the nestedStacks property when including the parent template/); + }); + + test('correctly handles renaming of references across nested stacks', () => { + const parentTemplate = new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('cross-stack-refs.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('child-import-stack.json'), + }, + }, + }); + const cfnBucket = parentTemplate.getResource('Bucket'); + cfnBucket.overrideLogicalId('DifferentBucket'); + const parameter = parentTemplate.getParameter('Param'); + parameter.overrideLogicalId('DifferentParameter'); + + expect(stack).toHaveResourceLike('AWS::CloudFormation::Stack', { + "Parameters": { + "Param1": { + "Ref": "DifferentParameter", + }, + "Param2": { + "Fn::GetAtt": ["DifferentBucket", "Arn"], + }, + }, + }); + }); + + test('returns the CfnStack object from getResource() for a nested stack that was not in the nestedStacks property', () => { + const cfnTemplate = new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-two-children.json'), + }); + + const childStack1 = cfnTemplate.getResource('ChildStack'); + + expect(childStack1).toBeInstanceOf(core.CfnStack); + }); + + test('returns the CfnStack object from getResource() for a nested stack that was in the nestedStacks property', () => { + const cfnTemplate = new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-one-child.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('child-import-stack.json'), + }, + }, + }); + + const childStack1 = cfnTemplate.getResource('ChildStack'); + + expect(childStack1).toBeInstanceOf(core.CfnStack); + }); + + test("handles Metadata, DeletionPolicy, and UpdateReplacePolicy attributes of the nested stack's resource", () => { + const cfnTemplate = new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-with-attributes.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('child-import-stack.json'), + }, + 'AnotherChildStack': { + templateFile: testTemplateFilePath('child-import-stack.json'), + }, + }, + }); + + expect(stack).toHaveResourceLike('AWS::CloudFormation::Stack', { + "Metadata": { + "Property1": "Value1", + }, + "DeletionPolicy": "Retain", + "DependsOn": [ + "AnotherChildStack", + ], + "UpdateReplacePolicy": "Retain", + }, ResourcePart.CompleteDefinition); + + cfnTemplate.getNestedStack('AnotherChildStack'); + }); + + test('correctly parses NotificationsARNs, Timeout', () => { + new inc.CfnInclude(stack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-with-attributes.json'), + }); + + expect(stack).toHaveResourceLike('AWS::CloudFormation::Stack', { + "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/child-import-stack.json", + "NotificationARNs": ["arn1"], + "TimeoutInMinutes": 5, + }); + }); + + describe('for a parent stack with children and grandchildren', () => { + let assetStack: core.Stack; + let parentTemplate: inc.CfnInclude; + let child: inc.IncludedNestedStack; + let grandChild: inc.IncludedNestedStack; + + let parentBucketParam: string; + let parentKeyParam: string; + let grandChildBucketParam: string; + let grandChildKeyParam: string; + + let childBucketParam: string; + let childKeyParam: string; + + beforeAll(() => { + assetStack = new core.Stack(); + parentTemplate = new inc.CfnInclude(assetStack, 'ParentStack', { + templateFile: testTemplateFilePath('parent-one-child.json'), + nestedStacks: { + 'ChildStack': { + templateFile: testTemplateFilePath('child-no-bucket.json'), + nestedStacks: { + 'GrandChildStack': { + templateFile: testTemplateFilePath('grandchild-import-stack.json'), + }, + }, + }, + }, + }); + + child = parentTemplate.getNestedStack('ChildStack'); + grandChild = child.includedTemplate.getNestedStack('GrandChildStack'); + + parentBucketParam = 'AssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3BucketEAA24F0C'; + parentKeyParam = 'AssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3VersionKey1194CAB2'; + grandChildBucketParam = 'referencetoAssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3BucketEAA24F0CRef'; + grandChildKeyParam = 'referencetoAssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3VersionKey1194CAB2Ref'; + + childBucketParam = 'AssetParameters891fd3ec75dc881b0fe40dc9fd1b433672637585c015265a5f0dab6bf79818d5S3Bucket23278F13'; + childKeyParam = 'AssetParameters891fd3ec75dc881b0fe40dc9fd1b433672637585c015265a5f0dab6bf79818d5S3VersionKey7316205A'; + }); + + test('correctly creates parameters in the parent stack, and passes them to the child stack', () => { + expect(assetStack).toMatchTemplate({ + "Parameters": { + [parentBucketParam]: { + "Type": "String", + "Description": "S3 bucket for asset \"5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50\"", + }, + [parentKeyParam]: { + "Type": "String", + "Description": "S3 key for asset version \"5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50\"", + }, + "AssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50ArtifactHash9C417847": { + "Type": "String", + "Description": "Artifact hash for asset \"5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50\"", + }, + [childBucketParam]: { + "Type": "String", + "Description": "S3 bucket for asset \"891fd3ec75dc881b0fe40dc9fd1b433672637585c015265a5f0dab6bf79818d5\"", + }, + [childKeyParam]: { + "Type": "String", + "Description": "S3 key for asset version \"891fd3ec75dc881b0fe40dc9fd1b433672637585c015265a5f0dab6bf79818d5\"", + }, + "AssetParameters891fd3ec75dc881b0fe40dc9fd1b433672637585c015265a5f0dab6bf79818d5ArtifactHashA1DE5198": { + "Type": "String", + "Description": "Artifact hash for asset \"891fd3ec75dc881b0fe40dc9fd1b433672637585c015265a5f0dab6bf79818d5\"", + }, + }, + "Resources": { + "ChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ "", [ + "https://s3.", + { "Ref": "AWS::Region" }, + ".", + { "Ref": "AWS::URLSuffix" }, + "/", + { "Ref": childBucketParam }, + "/", + { "Fn::Select": [ + 0, + { "Fn::Split": [ + "||", + { "Ref": childKeyParam }, + ]}, + ]}, + { "Fn::Select": [ + 1, + { "Fn::Split": [ + "||", + { "Ref": childKeyParam }, + ]}, + ]}, + ]], + }, + "Parameters": { + "MyBucketParameter": "some-magic-bucket-name", + [grandChildBucketParam]: { + "Ref": parentBucketParam, + }, + [grandChildKeyParam]: { + "Ref": parentKeyParam, + }, + }, + }, + }, + }, + }); + }); + + test('correctly creates parameters in the child stack, and passes them to the grandchild stack', () => { + expect(child.stack).toMatchTemplate({ + "Parameters": { + "MyBucketParameter": { + "Type": "String", + "Default": "default-bucket-param-name", + }, + [grandChildBucketParam]: { + "Type": "String", + }, + [grandChildKeyParam]: { + "Type": "String", + }, + }, + "Resources": { + "GrandChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ "", [ + "https://s3.", + { "Ref": "AWS::Region" }, + ".", + { "Ref": "AWS::URLSuffix" }, + "/", + { "Ref": grandChildBucketParam }, + "/", + { "Fn::Select": [ + 0, + { "Fn::Split": [ + "||", + { "Ref": grandChildKeyParam }, + ]}, + ]}, + { + "Fn::Select": [ + 1, + { "Fn::Split": [ + "||", + { "Ref": grandChildKeyParam }, + ]}, + ], + }, + ]], + }, + "Parameters": { + "MyBucketParameter": "some-other-bucket-name", + }, + }, + }, + }, + }); + }); + + test('leaves grandchild stack unmodified', () => { + expect(grandChild.stack).toMatchTemplate( + loadTestFileToJsObject('grandchild-import-stack.json'), + ); + }); + }); +}); + +function loadTestFileToJsObject(testTemplate: string): any { + return futils.readJsonSync(testTemplateFilePath(testTemplate)); +} + +function testTemplateFilePath(testTemplate: string) { + return path.join(__dirname, 'test-templates/nested', testTemplate); +} diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/child-import-stack.expected.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/child-import-stack.expected.json new file mode 100644 index 0000000000000..d1edb5eabf11c --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/child-import-stack.expected.json @@ -0,0 +1,78 @@ +{ + "Parameters": { + "MyBucketParameter": { + "Type": "String", + "Default": "default-bucket-param-name" + }, + "referencetoAssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3BucketEAA24F0CRef": { + "Type": "String" + }, + "referencetoAssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3VersionKey1194CAB2Ref": { + "Type": "String" + } + }, + "Resources": { + "GrandChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "referencetoAssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3BucketEAA24F0CRef" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "referencetoAssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3VersionKey1194CAB2Ref" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "referencetoAssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3VersionKey1194CAB2Ref" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "MyBucketParameter": "some-other-bucket-name" + } + } + }, + "BucketImport": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Ref": "MyBucketParameter" + } + } + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/child-import-stack.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/child-import-stack.json new file mode 100644 index 0000000000000..25e2e5f9bf183 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/child-import-stack.json @@ -0,0 +1,27 @@ +{ + "Parameters": { + "MyBucketParameter": { + "Type": "String", + "Default": "default-bucket-param-name" + } + }, + "Resources": { + "BucketImport": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Ref": "MyBucketParameter" + } + } + }, + "GrandChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/grandchild-import-stack.json", + "Parameters": { + "MyBucketParameter": "some-other-bucket-name" + } + } + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/child-no-bucket.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/child-no-bucket.json new file mode 100644 index 0000000000000..63ede414bbdaa --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/child-no-bucket.json @@ -0,0 +1,19 @@ +{ + "Parameters": { + "MyBucketParameter": { + "Type": "String", + "Default": "default-bucket-param-name" + } + }, + "Resources": { + "GrandChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/grandchild-import-stack.json", + "Parameters": { + "MyBucketParameter": "some-other-bucket-name" + } + } + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/cross-stack-refs.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/cross-stack-refs.json new file mode 100644 index 0000000000000..d51bd69c1a26c --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/cross-stack-refs.json @@ -0,0 +1,22 @@ +{ + "Parameters": { + "Param": { + "Type": "String" + } + }, + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket" + }, + "ChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/child-import-stack.json", + "Parameters": { + "Param1": { "Ref": "Param" }, + "Param2": { "Fn::GetAtt": ["Bucket", "Arn"] } + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/grandchild-import-stack.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/grandchild-import-stack.json new file mode 100644 index 0000000000000..cf8b9a7953356 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/grandchild-import-stack.json @@ -0,0 +1,26 @@ +{ + "Parameters": { + "MyBucketParameter": { + "Type": "String", + "Default": "default-bucket-param-name" + } + }, + "Resources": { + "BucketImport": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::Join": [ + "-", + [ + "bucket-name-prefix", + { + "Ref": "MyBucketParameter" + } + ] + ] + } + } + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/only-nested-stack.expected.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/only-nested-stack.expected.json new file mode 100644 index 0000000000000..e29a1437b87ff --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/only-nested-stack.expected.json @@ -0,0 +1,69 @@ +{ + "Resources": { + "NestedStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters6b884775090ed88cd1a143f64442a92a6c34eaeff3857976d15ef2e3beee05d7S3BucketC8A1BF52" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters6b884775090ed88cd1a143f64442a92a6c34eaeff3857976d15ef2e3beee05d7S3VersionKeyA9E03E19" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters6b884775090ed88cd1a143f64442a92a6c34eaeff3857976d15ef2e3beee05d7S3VersionKeyA9E03E19" + } + ] + } + ] + } + ] + ] + } + } + } + }, + "Parameters": { + "AssetParameters6b884775090ed88cd1a143f64442a92a6c34eaeff3857976d15ef2e3beee05d7S3BucketC8A1BF52": { + "Type": "String", + "Description": "S3 bucket for asset \"6b884775090ed88cd1a143f64442a92a6c34eaeff3857976d15ef2e3beee05d7\"" + }, + "AssetParameters6b884775090ed88cd1a143f64442a92a6c34eaeff3857976d15ef2e3beee05d7S3VersionKeyA9E03E19": { + "Type": "String", + "Description": "S3 key for asset version \"6b884775090ed88cd1a143f64442a92a6c34eaeff3857976d15ef2e3beee05d7\"" + }, + "AssetParameters6b884775090ed88cd1a143f64442a92a6c34eaeff3857976d15ef2e3beee05d7ArtifactHash605B2835": { + "Type": "String", + "Description": "Artifact hash for asset \"6b884775090ed88cd1a143f64442a92a6c34eaeff3857976d15ef2e3beee05d7\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/only-nested-stack.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/only-nested-stack.json new file mode 100644 index 0000000000000..af762ed14cb89 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/only-nested-stack.json @@ -0,0 +1,10 @@ +{ + "Resources": { + "NestedStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "doesnt-matter" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-bad-depends-on.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-bad-depends-on.json new file mode 100644 index 0000000000000..badd2f7d78f9e --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-bad-depends-on.json @@ -0,0 +1,13 @@ +{ + "Resources": { + "ChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/child-import-stack.json" + }, + "DependsOn": [ + "AFakeResource" + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-creation-policy.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-creation-policy.json new file mode 100644 index 0000000000000..1143271208967 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-creation-policy.json @@ -0,0 +1,15 @@ +{ + "Resources": { + "ChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/grandchild-import-stack.json", + "Parameters": { + "MyBucketParameter": "some-magic-bucket-name" + } + }, + "CreationPolicy": { + } + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-invalid-condition.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-invalid-condition.json new file mode 100644 index 0000000000000..5d042b11b02b3 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-invalid-condition.json @@ -0,0 +1,14 @@ +{ + "Resources": { + "ChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/grandchild-import-stack.json", + "Parameters": { + "MyBucketParameter": "some-magic-bucket-name" + } + }, + "Condition": "FakeCondition" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-one-child.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-one-child.json new file mode 100644 index 0000000000000..1fd331f53213f --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-one-child.json @@ -0,0 +1,13 @@ +{ + "Resources": { + "ChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/grandchild-import-stack.json", + "Parameters": { + "MyBucketParameter": "some-magic-bucket-name" + } + } + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-two-children.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-two-children.json new file mode 100644 index 0000000000000..1ed2a9403afb6 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-two-children.json @@ -0,0 +1,22 @@ +{ + "Resources": { + "ChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/child-import-stack.json", + "Parameters": { + "MyBucketParameter": "some-magic-bucket-name" + } + } + }, + "AnotherChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/child-import-stack.json", + "Parameters": { + "MyBucketParameter": "another-magic-bucket-name" + } + } + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-update-policy.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-update-policy.json new file mode 100644 index 0000000000000..8e4daa8f65014 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-update-policy.json @@ -0,0 +1,15 @@ +{ + "Resources": { + "ChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/grandchild-import-stack.json", + "Parameters": { + "MyBucketParameter": "some-magic-bucket-name" + } + }, + "UpdatePolicy": { + } + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-valid-condition.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-valid-condition.json new file mode 100644 index 0000000000000..6f7ebee7b51b0 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-valid-condition.json @@ -0,0 +1,24 @@ +{ + "Conditions": { + "AlwaysFalseCond": { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "completely-made-up-region" + ] + } + }, + "Resources": { + "ChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/grandchild-import-stack.json", + "Parameters": { + "MyBucketParameter": "some-magic-bucket-name" + } + }, + "Condition": "AlwaysFalseCond" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-with-attributes.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-with-attributes.json new file mode 100644 index 0000000000000..c12bd223063c1 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/nested/parent-with-attributes.json @@ -0,0 +1,26 @@ +{ + "Resources": { + "ChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/child-import-stack.json" + }, + "DependsOn": [ + "AnotherChildStack" + ], + "Metadata": { + "Property1": "Value1" + }, + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain" + }, + "AnotherChildStack": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://cfn-templates-set.s3.amazonaws.com/child-import-stack.json", + "NotificationARNs": [ "arn1" ], + "TimeoutInMinutes": 5 + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts index 57e6d0470dcb9..83b2370db5fac 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -1,13 +1,13 @@ +import * as path from 'path'; import { ResourcePart } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import * as core from '@aws-cdk/core'; -import * as path from 'path'; import * as inc from '../lib'; import * as futils from '../lib/file-utils'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ /* eslint-disable quotes */ describe('CDK Include', () => { diff --git a/packages/@aws-cdk/cloudformation-include/test/yaml-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/yaml-templates.test.ts index 19b7551c50876..01bba1610a9b5 100644 --- a/packages/@aws-cdk/cloudformation-include/test/yaml-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/yaml-templates.test.ts @@ -1,10 +1,10 @@ +import * as path from 'path'; import '@aws-cdk/assert/jest'; import * as core from '@aws-cdk/core'; -import * as path from 'path'; import * as inc from '../lib'; import * as futils from '../lib/file-utils'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ /* eslint-disable quotes */ describe('CDK Include', () => { diff --git a/packages/@aws-cdk/core/.gitignore b/packages/@aws-cdk/core/.gitignore index 9ab74aa9b2181..5960e89add3c9 100644 --- a/packages/@aws-cdk/core/.gitignore +++ b/packages/@aws-cdk/core/.gitignore @@ -13,3 +13,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/core/.npmignore b/packages/@aws-cdk/core/.npmignore index d021dc7ac0182..dfcc4c31f1c8d 100644 --- a/packages/@aws-cdk/core/.npmignore +++ b/packages/@aws-cdk/core/.npmignore @@ -24,4 +24,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/app.ts b/packages/@aws-cdk/core/lib/app.ts index 1546ab19ee53c..50996a72ff85a 100644 --- a/packages/@aws-cdk/core/lib/app.ts +++ b/packages/@aws-cdk/core/lib/app.ts @@ -49,7 +49,7 @@ export interface AppProps { * * @default - no additional context */ - readonly context?: { [key: string]: string }; + readonly context?: { [key: string]: any }; /** * Include construct tree metadata as part of the Cloud Assembly. diff --git a/packages/@aws-cdk/core/lib/asset-staging.ts b/packages/@aws-cdk/core/lib/asset-staging.ts index 2a0702c20d76f..92a2b6b9bdf97 100644 --- a/packages/@aws-cdk/core/lib/asset-staging.ts +++ b/packages/@aws-cdk/core/lib/asset-staging.ts @@ -1,8 +1,8 @@ -import * as cxapi from '@aws-cdk/cx-api'; import * as crypto from 'crypto'; -import * as fs from 'fs-extra'; import * as os from 'os'; import * as path from 'path'; +import * as cxapi from '@aws-cdk/cx-api'; +import * as fs from 'fs-extra'; import { AssetHashType, AssetOptions } from './assets'; import { BundlingOptions } from './bundling'; import { Construct, ISynthesisSession } from './construct-compat'; @@ -171,6 +171,8 @@ export class AssetStaging extends Construct { ]; try { + // eslint-disable-next-line no-console + console.error(`Bundling asset ${this.node.path}...`); options.image._run({ command: options.command, user, diff --git a/packages/@aws-cdk/core/lib/bundling.ts b/packages/@aws-cdk/core/lib/bundling.ts index 6ec85c43af22e..7b6843fd087ed 100644 --- a/packages/@aws-cdk/core/lib/bundling.ts +++ b/packages/@aws-cdk/core/lib/bundling.ts @@ -1,4 +1,4 @@ -import { spawnSync } from 'child_process'; +import { spawnSync, SpawnSyncOptions } from 'child_process'; /** * Bundling options @@ -121,7 +121,7 @@ export class BundlingDockerImage { ...command, ]; - dockerExec(dockerArgs); + dockerExec(dockerArgs, { stdio: 'inherit' }); // show Docker output in console } } @@ -222,16 +222,19 @@ function flatten(x: string[][]) { return Array.prototype.concat([], ...x); } -function dockerExec(args: string[]) { +function dockerExec(args: string[], options?: SpawnSyncOptions) { const prog = process.env.CDK_DOCKER ?? 'docker'; - const proc = spawnSync(prog, args); + const proc = spawnSync(prog, args, options); if (proc.error) { throw proc.error; } if (proc.status !== 0) { - throw new Error(`[Status ${proc.status}] stdout: ${proc.stdout?.toString().trim()}\n\n\nstderr: ${proc.stderr?.toString().trim()}`); + if (proc.stdout || proc.stderr) { + throw new Error(`[Status ${proc.status}] stdout: ${proc.stdout?.toString().trim()}\n\n\nstderr: ${proc.stderr?.toString().trim()}`); + } + throw new Error(`${prog} exited with status ${proc.status}`); } return proc; diff --git a/packages/@aws-cdk/core/lib/cfn-fn.ts b/packages/@aws-cdk/core/lib/cfn-fn.ts index 73d0f61c03236..df5c31794fc4c 100644 --- a/packages/@aws-cdk/core/lib/cfn-fn.ts +++ b/packages/@aws-cdk/core/lib/cfn-fn.ts @@ -6,7 +6,7 @@ import { IResolvable, IResolveContext } from './resolvable'; import { captureStackTrace } from './stack-trace'; import { Token } from './token'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ /** * CloudFormation intrinsic functions. diff --git a/packages/@aws-cdk/core/lib/cfn-resource.ts b/packages/@aws-cdk/core/lib/cfn-resource.ts index deeb92e0e2456..39b60a3d248b7 100644 --- a/packages/@aws-cdk/core/lib/cfn-resource.ts +++ b/packages/@aws-cdk/core/lib/cfn-resource.ts @@ -1,7 +1,7 @@ import * as cxapi from '@aws-cdk/cx-api'; import { CfnCondition } from './cfn-condition'; // import required to be here, otherwise causes a cycle when running the generated JavaScript -// tslint:disable-next-line:ordered-imports +/* eslint-disable import/order */ import { CfnRefElement } from './cfn-element'; import { CfnCreationPolicy, CfnDeletionPolicy, CfnUpdatePolicy } from './cfn-resource-policy'; import { Construct, IConstruct } from './construct-compat'; @@ -93,9 +93,7 @@ export class CfnResource extends CfnRefElement { // path in the CloudFormation template, so it will be possible to trace // back to the actual construct path. if (this.node.tryGetContext(cxapi.PATH_METADATA_ENABLE_CONTEXT)) { - this.cfnOptions.metadata = { - [cxapi.PATH_METADATA_KEY]: this.node.path, - }; + this.addMetadata(cxapi.PATH_METADATA_KEY, this.node.path); } } @@ -238,6 +236,22 @@ export class CfnResource extends CfnRefElement { addDependency(this, target, `"${this.node.path}" depends on "${target.node.path}"`); } + /** + * Add a value to the CloudFormation Resource Metadata + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/metadata-section-structure.html + * + * Note that this is a different set of metadata from CDK node metadata; this + * metadata ends up in the stack template under the resource, whereas CDK + * node metadata ends up in the Cloud Assembly. + */ + public addMetadata(key: string, value: any) { + if (!this.cfnOptions.metadata) { + this.cfnOptions.metadata = {}; + } + + this.cfnOptions.metadata[key] = value; + } + /** * @returns a string representation of this resource */ @@ -273,7 +287,7 @@ export class CfnResource extends CfnRefElement { Type: this.cfnResourceType, Properties: ignoreEmpty(this.cfnProperties), DependsOn: ignoreEmpty(renderDependsOn(this.dependsOn)), - CreationPolicy: capitalizePropertyNames(this, renderCreationPolicy(this.cfnOptions.creationPolicy)), + CreationPolicy: capitalizePropertyNames(this, renderCreationPolicy(this.cfnOptions.creationPolicy)), UpdatePolicy: capitalizePropertyNames(this, this.cfnOptions.updatePolicy), UpdateReplacePolicy: capitalizePropertyNames(this, this.cfnOptions.updateReplacePolicy), DeletionPolicy: capitalizePropertyNames(this, this.cfnOptions.deletionPolicy), diff --git a/packages/@aws-cdk/core/lib/custom-resource-provider/nodejs-entrypoint.ts b/packages/@aws-cdk/core/lib/custom-resource-provider/nodejs-entrypoint.ts index c90bf96e684cf..e720e225787eb 100644 --- a/packages/@aws-cdk/core/lib/custom-resource-provider/nodejs-entrypoint.ts +++ b/packages/@aws-cdk/core/lib/custom-resource-provider/nodejs-entrypoint.ts @@ -134,6 +134,6 @@ async function defaultSendHttpRequest(options: https.RequestOptions, responseBod } function defaultLog(fmt: string, ...params: any[]) { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.log(fmt, ...params); } diff --git a/packages/@aws-cdk/core/lib/deps.ts b/packages/@aws-cdk/core/lib/deps.ts index c34f11ef2c5a7..e0ef970b6e359 100644 --- a/packages/@aws-cdk/core/lib/deps.ts +++ b/packages/@aws-cdk/core/lib/deps.ts @@ -35,6 +35,7 @@ export function addDependency(source: T, target: T, reason?: const sourceStage = Stage.of(sourceStack); const targetStage = Stage.of(targetStack); if (sourceStage !== targetStage) { + // eslint-disable-next-line max-len throw new Error(`You cannot add a dependency from '${source.node.path}' (in ${describeStage(sourceStage)}) to '${target.node.path}' (in ${describeStage(targetStage)}): dependency cannot cross stage boundaries`); } diff --git a/packages/@aws-cdk/core/lib/duration.ts b/packages/@aws-cdk/core/lib/duration.ts index 61070e98929f9..9039b6a6ee936 100644 --- a/packages/@aws-cdk/core/lib/duration.ts +++ b/packages/@aws-cdk/core/lib/duration.ts @@ -100,6 +100,15 @@ export class Duration { this.unit = unit; } + /** + * Add two Durations together + */ + public plus(rhs: Duration): Duration { + const targetUnit = finestUnit(this.unit, rhs.unit); + const total = convert(this.amount, this.unit, targetUnit, {}) + convert(rhs.amount, rhs.unit, targetUnit, {}); + return new Duration(total, targetUnit); + } + /** * Return the total number of milliseconds in this Duration * @@ -154,6 +163,8 @@ export class Duration { public toIsoString(): string { if (this.amount === 0) { return 'PT0S'; } switch (this.unit) { + case TimeUnit.Milliseconds: + return Duration.seconds(this.amount / 1000.0).toIsoString(); case TimeUnit.Seconds: return `PT${this.fractionDuration('S', 60, Duration.minutes)}`; case TimeUnit.Minutes: @@ -283,3 +294,10 @@ function convert(amount: number, fromUnit: TimeUnit, toUnit: TimeUnit, { integra } return value; } + +/** + * Return the time unit with highest granularity + */ +function finestUnit(a: TimeUnit, b: TimeUnit) { + return a.inMillis < b.inMillis ? a : b; +} \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/fs/fingerprint.ts b/packages/@aws-cdk/core/lib/fs/fingerprint.ts index 80f133d4b5f51..ca7da486a5d03 100644 --- a/packages/@aws-cdk/core/lib/fs/fingerprint.ts +++ b/packages/@aws-cdk/core/lib/fs/fingerprint.ts @@ -69,11 +69,10 @@ export function fingerprint(fileOrDirectory: string, options: FingerprintOptions function _contentFingerprint(file: string, stat: fs.Stats): string { const hash = crypto.createHash('sha256'); const buffer = Buffer.alloc(BUFFER_SIZE); - // tslint:disable-next-line: no-bitwise + // eslint-disable-next-line no-bitwise const fd = fs.openSync(file, fs.constants.O_DSYNC | fs.constants.O_RDONLY | fs.constants.O_SYNC); try { let read = 0; - // tslint:disable-next-line: no-conditional-assignment while ((read = fs.readSync(fd, buffer, 0, BUFFER_SIZE, null)) !== 0) { hash.update(buffer.slice(0, read)); } diff --git a/packages/@aws-cdk/core/lib/fs/utils.ts b/packages/@aws-cdk/core/lib/fs/utils.ts index 5624c3448f5ef..84c520f0f14f9 100644 --- a/packages/@aws-cdk/core/lib/fs/utils.ts +++ b/packages/@aws-cdk/core/lib/fs/utils.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; -import * as minimatch from 'minimatch'; import * as path from 'path'; +import * as minimatch from 'minimatch'; import { SymlinkFollowMode } from './options'; /** diff --git a/packages/@aws-cdk/core/lib/private/cfn-reference.ts b/packages/@aws-cdk/core/lib/private/cfn-reference.ts index 5aaf870b3ec1a..cd28a221958c5 100644 --- a/packages/@aws-cdk/core/lib/private/cfn-reference.ts +++ b/packages/@aws-cdk/core/lib/private/cfn-reference.ts @@ -96,7 +96,7 @@ export class CfnReference extends Reference { const token = this.replacementTokens.get(consumingStack); // if (!token && this.isCrossStackReference(consumingStack) && !context.preparing) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len // throw new Error(`Cross-stack reference (${context.scope.node.path} -> ${this.target.node.path}) has not been assigned a value--call prepare() first`); // } diff --git a/packages/@aws-cdk/core/lib/private/encoding.ts b/packages/@aws-cdk/core/lib/private/encoding.ts index 89b33308b65ab..e9fc0156c69d2 100644 --- a/packages/@aws-cdk/core/lib/private/encoding.ts +++ b/packages/@aws-cdk/core/lib/private/encoding.ts @@ -138,7 +138,7 @@ export function unresolved(obj: any): boolean { * * Currently not supporting BE architectures. */ -// tslint:disable-next-line:no-bitwise +// eslint-disable-next-line no-bitwise const DOUBLE_TOKEN_MARKER_BITS = 0xFBFF << 16; /** @@ -172,13 +172,13 @@ export function createTokenDouble(x: number) { const buf = new ArrayBuffer(8); const ints = new Uint32Array(buf); - // tslint:disable:no-bitwise + /* eslint-disable no-bitwise */ ints[0] = x & 0x0000FFFFFFFF; // Bottom 32 bits of number // This needs an "x >> 32" but that will make it a 32-bit number instead // of a 64-bit number. ints[1] = (shr32(x) & 0xFFFF) | DOUBLE_TOKEN_MARKER_BITS; // Top 16 bits of number and the mask - // tslint:enable:no-bitwise + /* eslint-enable no-bitwise */ return (new Float64Array(buf))[0]; } @@ -208,7 +208,7 @@ export function extractTokenDouble(encoded: number): number | undefined { const ints = new Uint32Array(buf); - // tslint:disable:no-bitwise + /* eslint-disable no-bitwise */ if ((ints[1] & 0xFFFF0000) !== DOUBLE_TOKEN_MARKER_BITS) { return undefined; } @@ -216,5 +216,5 @@ export function extractTokenDouble(encoded: number): number | undefined { // Must use + instead of | here (bitwise operations // will force 32-bits integer arithmetic, + will not). return ints[0] + shl32(ints[1] & 0xFFFF); - // tslint:enable:no-bitwise + /* eslint-enable no-bitwise */ } diff --git a/packages/@aws-cdk/core/lib/private/logical-id.ts b/packages/@aws-cdk/core/lib/private/logical-id.ts index 34044ad3db667..70b92c4a6e6ac 100644 --- a/packages/@aws-cdk/core/lib/private/logical-id.ts +++ b/packages/@aws-cdk/core/lib/private/logical-id.ts @@ -40,7 +40,7 @@ export class LogicalIDs { // If this newId has already been used, it must have been with the same oldId if (newId in this.reverse && this.reverse[newId] !== oldId) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`Two objects have been assigned the same Logical ID: '${this.reverse[newId]}' and '${oldId}' are now both named '${newId}'.`); } this.reverse[newId] = oldId; diff --git a/packages/@aws-cdk/core/lib/private/resolve.ts b/packages/@aws-cdk/core/lib/private/resolve.ts index 11cd65cc332b3..19be4917b5e5f 100644 --- a/packages/@aws-cdk/core/lib/private/resolve.ts +++ b/packages/@aws-cdk/core/lib/private/resolve.ts @@ -148,6 +148,7 @@ export function resolve(obj: any, options: IResolveOptions): any { for (const key of Object.keys(obj)) { const resolvedKey = makeContext()[0].resolve(key); if (typeof(resolvedKey) !== 'string') { + // eslint-disable-next-line max-len throw new Error(`"${key}" is used as the key in a map so must resolve to a string, but it resolves to: ${JSON.stringify(resolvedKey)}. Consider using "CfnJson" to delay resolution to deployment-time`); } diff --git a/packages/@aws-cdk/core/lib/private/runtime-info.ts b/packages/@aws-cdk/core/lib/private/runtime-info.ts index dce0ac508d71f..7edf871a5f183 100644 --- a/packages/@aws-cdk/core/lib/private/runtime-info.ts +++ b/packages/@aws-cdk/core/lib/private/runtime-info.ts @@ -1,5 +1,5 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { basename, dirname } from 'path'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { major as nodeMajorVersion } from './node-version'; // list of NPM scopes included in version reporting e.g. @aws-cdk and @aws-solutions-konstruk @@ -65,7 +65,7 @@ export function collectRuntimeInformation(): cxschema.RuntimeInfo { function findNpmPackage(fileName: string): { name: string, version: string, private?: boolean } | undefined { const mod = require.cache[fileName]; - if (!mod.paths) { + if (!mod?.paths) { // sometimes this can be undefined. for example when querying for .json modules // inside a jest runtime environment. // see https://github.com/aws/aws-cdk/issues/7657 @@ -74,7 +74,7 @@ function findNpmPackage(fileName: string): { name: string, version: string, priv } // For any path in ``mod.paths`` that is a node_modules folder, use its parent directory instead. - const paths = mod.paths.map((path: string) => basename(path) === 'node_modules' ? dirname(path) : path); + const paths = mod?.paths.map((path: string) => basename(path) === 'node_modules' ? dirname(path) : path); try { const packagePath = require.resolve( diff --git a/packages/@aws-cdk/core/lib/private/uniqueid.ts b/packages/@aws-cdk/core/lib/private/uniqueid.ts index ea6c2a142563d..c9d1d333891db 100644 --- a/packages/@aws-cdk/core/lib/private/uniqueid.ts +++ b/packages/@aws-cdk/core/lib/private/uniqueid.ts @@ -1,4 +1,3 @@ -// tslint:disable-next-line:no-var-requires import * as crypto from 'crypto'; import { unresolved } from './encoding'; diff --git a/packages/@aws-cdk/core/lib/runtime.ts b/packages/@aws-cdk/core/lib/runtime.ts index b475679338129..390708db3eccb 100644 --- a/packages/@aws-cdk/core/lib/runtime.ts +++ b/packages/@aws-cdk/core/lib/runtime.ts @@ -31,7 +31,7 @@ export function dateToCloudFormation(x?: Date): any { return undefined; } - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len return `${x.getUTCFullYear()}-${pad(x.getUTCMonth() + 1)}-${pad(x.getUTCDate())}T${pad(x.getUTCHours())}:${pad(x.getUTCMinutes())}:${pad(x.getUTCSeconds())}`; } diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts index 280a3f72d1079..5a57c423779df 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts @@ -1,5 +1,5 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as crypto from 'crypto'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { ConstructNode, IConstruct, ISynthesisSession } from '../construct-compat'; import { Stack } from '../stack'; diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts index 98111a43e0e42..5a373cd3ed061 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts @@ -1,7 +1,7 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs'; import * as path from 'path'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import * as cxapi from '@aws-cdk/cx-api'; import { DockerImageAssetLocation, DockerImageAssetSource, FileAssetLocation, FileAssetPackaging, FileAssetSource } from '../assets'; import { Fn } from '../cfn-fn'; import { ISynthesisSession } from '../construct-compat'; @@ -12,6 +12,8 @@ import { IStackSynthesizer } from './types'; export const BOOTSTRAP_QUALIFIER_CONTEXT = '@aws-cdk/core:bootstrapQualifier'; +/* eslint-disable max-len */ + /** * The minimum bootstrap stack version required by this app. */ @@ -222,7 +224,7 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { }); }; - // tslint:disable:max-line-length + /* eslint-disable max-len */ this.bucketName = specialize(this.props.fileAssetsBucketName ?? DefaultStackSynthesizer.DEFAULT_FILE_ASSETS_BUCKET_NAME); this.repositoryName = specialize(this.props.imageAssetsRepositoryName ?? DefaultStackSynthesizer.DEFAULT_IMAGE_ASSETS_REPOSITORY_NAME); this._deployRoleArn = specialize(this.props.deployRoleArn ?? DefaultStackSynthesizer.DEFAULT_DEPLOY_ROLE_ARN); @@ -230,7 +232,7 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { this.fileAssetPublishingRoleArn = specialize(this.props.fileAssetPublishingRoleArn ?? DefaultStackSynthesizer.DEFAULT_FILE_ASSET_PUBLISHING_ROLE_ARN); this.imageAssetPublishingRoleArn = specialize(this.props.imageAssetPublishingRoleArn ?? DefaultStackSynthesizer.DEFAULT_IMAGE_ASSET_PUBLISHING_ROLE_ARN); this._kmsKeyArnExportName = specialize(this.props.fileAssetKeyArnExportName ?? DefaultStackSynthesizer.DEFAULT_FILE_ASSET_KEY_ARN_EXPORT_NAME); - // tslint:enable:max-line-length + /* eslint-enable max-len */ } public addFileAsset(asset: FileAssetSource): FileAssetLocation { diff --git a/packages/@aws-cdk/core/lib/stack-trace.ts b/packages/@aws-cdk/core/lib/stack-trace.ts index 47caefa22f4a8..8337434fd3c6a 100644 --- a/packages/@aws-cdk/core/lib/stack-trace.ts +++ b/packages/@aws-cdk/core/lib/stack-trace.ts @@ -1,4 +1,3 @@ -// tslint:disable-next-line:ban-types export function captureStackTrace(below?: Function): string[] { if (process.env.CDK_DISABLE_STACK_TRACE) { return [ 'stack traces disabled' ]; diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index eeb65562837ec..6af8d3b075150 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -1,8 +1,13 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs'; import * as path from 'path'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import * as cxapi from '@aws-cdk/cx-api'; +import { Arn, ArnComponents } from './arn'; import { DockerImageAssetLocation, DockerImageAssetSource, FileAssetLocation, FileAssetSource } from './assets'; +import { CfnElement } from './cfn-element'; +import { Fn } from './cfn-fn'; +import { Aws, ScopedAws } from './cfn-pseudo'; +import { CfnResource, TagType } from './cfn-resource'; import { Construct, IConstruct, ISynthesisSession } from './construct-compat'; import { ContextProvider } from './context-provider'; import { Environment } from './environment'; @@ -338,7 +343,7 @@ export class Stack extends Construct implements ITaggable { // // Also use the new behavior if we are using the new CI/CD-ready synthesizer; that way // people only have to flip one flag. - // tslint:disable-next-line: max-line-length + // eslint-disable-next-line max-len this.artifactId = this.node.tryGetContext(cxapi.ENABLE_STACK_NAME_DUPLICATES_CONTEXT) || this.node.tryGetContext(cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT) ? this.generateStackArtifactId() : this.stackName; @@ -662,7 +667,7 @@ export class Stack extends Construct implements ITaggable { reason = reason || 'dependency added using stack.addDependency()'; const cycle = target.stackDependencyReasons(this); if (cycle !== undefined) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`'${target.node.path}' depends on '${this.node.path}' (${cycle.join(', ')}). Adding this dependency (${reason}) would create a cyclic reference.`); } @@ -677,7 +682,7 @@ export class Stack extends Construct implements ITaggable { dep.reasons.push(reason); if (process.env.CDK_DEBUG_DEPS) { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.error(`[CDK_DEBUG_DEPS] stack "${this.node.path}" depends on "${target.node.path}" because: ${reason}`); } } @@ -775,7 +780,7 @@ export class Stack extends Construct implements ITaggable { let transform: string | string[] | undefined; if (this.templateOptions.transform) { - // tslint:disable-next-line: max-line-length + // eslint-disable-next-line max-len this.node.addWarning('This stack is using the deprecated `templateOptions.transform` property. Consider switching to `addTransform()`.'); this.addTransform(this.templateOptions.transform); } @@ -1058,11 +1063,6 @@ function makeStackName(components: string[]) { } // These imports have to be at the end to prevent circular imports -import { Arn, ArnComponents } from './arn'; -import { CfnElement } from './cfn-element'; -import { Fn } from './cfn-fn'; -import { Aws, ScopedAws } from './cfn-pseudo'; -import { CfnResource, TagType } from './cfn-resource'; import { addDependency } from './deps'; import { Reference } from './reference'; import { IResolvable } from './resolvable'; diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index 4b660f4e83c42..586a00975b995 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -152,14 +152,14 @@ "license": "Apache-2.0", "devDependencies": { "@types/lodash": "^4.14.157", - "@types/node": "^10.17.26", + "@types/node": "^10.17.27", "@types/nodeunit": "^0.0.31", "@types/minimatch": "^3.0.3", "@types/sinon": "^9.0.4", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", - "fast-check": "^1.25.1", - "lodash": "^4.17.15", + "fast-check": "^1.26.0", + "lodash": "^4.17.19", "nodeunit": "^0.11.3", "pkglint": "0.0.0", "sinon": "^9.0.2", diff --git a/packages/@aws-cdk/core/test/custom-resource-provider/mock-provider/index.ts b/packages/@aws-cdk/core/test/custom-resource-provider/mock-provider/index.ts index 5a372f057593e..b83a9218a3e58 100644 --- a/packages/@aws-cdk/core/test/custom-resource-provider/mock-provider/index.ts +++ b/packages/@aws-cdk/core/test/custom-resource-provider/mock-provider/index.ts @@ -1,4 +1,4 @@ -// tslint:disable: no-console +/* eslint-disable no-console */ export function handler(event: any) { console.log('I am a custom resource'); diff --git a/packages/@aws-cdk/core/test/custom-resource-provider/test.custom-resource-provider.ts b/packages/@aws-cdk/core/test/custom-resource-provider/test.custom-resource-provider.ts index 47bdbce65e852..ca3d8e4839241 100644 --- a/packages/@aws-cdk/core/test/custom-resource-provider/test.custom-resource-provider.ts +++ b/packages/@aws-cdk/core/test/custom-resource-provider/test.custom-resource-provider.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; -import { Test } from 'nodeunit'; import * as path from 'path'; -import { CustomResourceProvider, CustomResourceProviderRuntime, Duration, Size, Stack } from '../../lib'; +import { Test } from 'nodeunit'; +import { AssetStaging, CustomResourceProvider, CustomResourceProviderRuntime, Duration, Size, Stack } from '../../lib'; import { toCloudFormation } from '../util'; const TEST_HANDLER = `${__dirname}/mock-provider`; @@ -20,6 +20,16 @@ export = { // THEN test.ok(fs.existsSync(path.join(TEST_HANDLER, '__entrypoint__.js')), 'expecting entrypoint to be copied to the handler directory'); const cfn = toCloudFormation(stack); + + // The asset hash constantly changes, so in order to not have to chase it, just look + // it up from the output. + const staging = stack.node.tryFindChild('Custom:MyResourceTypeCustomResourceProvider')?.node.tryFindChild('Staging') as AssetStaging; + const assetHash = staging.sourceHash; + const paramNames = Object.keys(cfn.Parameters); + const bucketParam = paramNames[0]; + const keyParam = paramNames[1]; + const hashParam = paramNames[2]; + test.deepEqual(cfn, { Resources: { CustomMyResourceTypeCustomResourceProviderRoleBD5E655F: { @@ -48,9 +58,7 @@ export = { Type: 'AWS::Lambda::Function', Properties: { Code: { - S3Bucket: { - Ref: 'AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226S3Bucket8B4D0E9E', - }, + S3Bucket: { Ref: bucketParam }, S3Key: { 'Fn::Join': [ '', @@ -61,9 +69,7 @@ export = { { 'Fn::Split': [ '||', - { - Ref: 'AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226S3VersionKeyDECB34FE', - }, + { Ref: keyParam }, ], }, ], @@ -74,9 +80,7 @@ export = { { 'Fn::Split': [ '||', - { - Ref: 'AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226S3VersionKeyDECB34FE', - }, + { Ref: keyParam }, ], }, ], @@ -102,17 +106,17 @@ export = { }, }, Parameters: { - AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226S3Bucket8B4D0E9E: { + [bucketParam]: { Type: 'String', - Description: 'S3 bucket for asset "925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226"', + Description: `S3 bucket for asset "${assetHash}"`, }, - AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226S3VersionKeyDECB34FE: { + [keyParam]: { Type: 'String', - Description: 'S3 key for asset version "925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226"', + Description: `S3 key for asset version "${assetHash}"`, }, - AssetParameters925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226ArtifactHashEEC400F2: { + [hashParam]: { Type: 'String', - Description: 'Artifact hash for asset "925e7fbbec7bdbf0136ef5a07b8a0fbe0b1f1bb4ea50ae2154163df78aa9f226"', + Description: `Artifact hash for asset "${assetHash}"`, }, }, }); diff --git a/packages/@aws-cdk/core/test/custom-resource-provider/test.nodejs-entrypoint.ts b/packages/@aws-cdk/core/test/custom-resource-provider/test.nodejs-entrypoint.ts index 87935b7d93599..40f3fdea54c8e 100644 --- a/packages/@aws-cdk/core/test/custom-resource-provider/test.nodejs-entrypoint.ts +++ b/packages/@aws-cdk/core/test/custom-resource-provider/test.nodejs-entrypoint.ts @@ -1,10 +1,10 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as https from 'https'; -import { Test } from 'nodeunit'; import * as os from 'os'; import * as path from 'path'; import * as url from 'url'; +import { Test } from 'nodeunit'; import * as entrypoint from '../../lib/custom-resource-provider/nodejs-entrypoint'; export = { diff --git a/packages/@aws-cdk/core/test/fs/test.fs-copy.ts b/packages/@aws-cdk/core/test/fs/test.fs-copy.ts index 47b55842a90c4..840bc94a97087 100644 --- a/packages/@aws-cdk/core/test/fs/test.fs-copy.ts +++ b/packages/@aws-cdk/core/test/fs/test.fs-copy.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; -import { Test } from 'nodeunit'; import * as os from 'os'; import * as path from 'path'; +import { Test } from 'nodeunit'; import { FileSystem, SymlinkFollowMode } from '../../lib/fs'; export = { diff --git a/packages/@aws-cdk/core/test/fs/test.fs-fingerprint.ts b/packages/@aws-cdk/core/test/fs/test.fs-fingerprint.ts index 414386a649523..3605a334f142c 100644 --- a/packages/@aws-cdk/core/test/fs/test.fs-fingerprint.ts +++ b/packages/@aws-cdk/core/test/fs/test.fs-fingerprint.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; -import { Test } from 'nodeunit'; import * as os from 'os'; import * as path from 'path'; +import { Test } from 'nodeunit'; import { FileSystem, SymlinkFollowMode } from '../../lib/fs'; export = { diff --git a/packages/@aws-cdk/core/test/fs/test.fs.ts b/packages/@aws-cdk/core/test/fs/test.fs.ts index cc6d4898c922e..11a03a653eaf5 100644 --- a/packages/@aws-cdk/core/test/fs/test.fs.ts +++ b/packages/@aws-cdk/core/test/fs/test.fs.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; -import { Test } from 'nodeunit'; import * as os from 'os'; import * as path from 'path'; +import { Test } from 'nodeunit'; import * as sinon from 'sinon'; import { FileSystem } from '../../lib/fs'; diff --git a/packages/@aws-cdk/core/test/fs/test.utils.ts b/packages/@aws-cdk/core/test/fs/test.utils.ts index c8962c7f4022f..2e43d125d597b 100644 --- a/packages/@aws-cdk/core/test/fs/test.utils.ts +++ b/packages/@aws-cdk/core/test/fs/test.utils.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; -import { Test } from 'nodeunit'; import * as path from 'path'; +import { Test } from 'nodeunit'; import { ImportMock } from 'ts-mock-imports'; import { SymlinkFollowMode } from '../../lib/fs'; import * as util from '../../lib/fs/utils'; diff --git a/packages/@aws-cdk/core/test/private/test.tree-metadata.ts b/packages/@aws-cdk/core/test/private/test.tree-metadata.ts index a99090cc0b892..3ef3966b2fd95 100644 --- a/packages/@aws-cdk/core/test/private/test.tree-metadata.ts +++ b/packages/@aws-cdk/core/test/private/test.tree-metadata.ts @@ -1,7 +1,7 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as fs from 'fs'; -import { Test } from 'nodeunit'; import * as path from 'path'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import { Test } from 'nodeunit'; import { App, CfnParameter, CfnResource, Construct, Lazy, Stack, TreeInspector } from '../../lib/index'; abstract class AbstractCfnResource extends CfnResource { diff --git a/packages/@aws-cdk/core/test/stack-synthesis/test.new-style-synthesis.ts b/packages/@aws-cdk/core/test/stack-synthesis/test.new-style-synthesis.ts index 32af9084a26d1..b64f84be913da 100644 --- a/packages/@aws-cdk/core/test/stack-synthesis/test.new-style-synthesis.ts +++ b/packages/@aws-cdk/core/test/stack-synthesis/test.new-style-synthesis.ts @@ -1,6 +1,6 @@ +import * as fs from 'fs'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; -import * as fs from 'fs'; import { Test } from 'nodeunit'; import { App, CfnResource, DefaultStackSynthesizer, FileAssetPackaging, Stack } from '../../lib'; import { evaluateCFN } from '../evaluate-cfn'; diff --git a/packages/@aws-cdk/core/test/test.app.ts b/packages/@aws-cdk/core/test/test.app.ts index e127671529712..4130fd5d2e974 100644 --- a/packages/@aws-cdk/core/test/test.app.ts +++ b/packages/@aws-cdk/core/test/test.app.ts @@ -350,6 +350,22 @@ export = { test.done(); }, + + 'application support any type in context'(test: Test) { + const app = new App({ + context: { + isString: 'string', + isNumber: 10, + isObject: { isString: 'string', isNumber: 10 }, + }, + }); + + test.ok(app.node.tryGetContext('isString') === 'string'); + test.ok(app.node.tryGetContext('isNumber') === 10); + test.deepEqual(app.node.tryGetContext('isObject'), { isString: 'string', isNumber: 10 }); + + test.done(); + }, }; class MyConstruct extends Construct { diff --git a/packages/@aws-cdk/core/test/test.arn.ts b/packages/@aws-cdk/core/test/test.arn.ts index 86210ed856bcf..0fd85741508e7 100644 --- a/packages/@aws-cdk/core/test/test.arn.ts +++ b/packages/@aws-cdk/core/test/test.arn.ts @@ -99,7 +99,7 @@ export = { 'Arn.parse(s)': { - 'fails': { + fails: { 'if doesn\'t start with "arn:"'(test: Test) { const stack = new Stack(); test.throws(() => stack.parseArn('barn:foo:x:a:1:2'), /ARNs must start with "arn:": barn:foo/); @@ -203,9 +203,9 @@ export = { test.equal(parsed.sep, '/'); - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len test.deepEqual(stack.resolve(parsed.resource), { 'Fn::Select': [ 0, { 'Fn::Split': [ '/', { 'Fn::Select': [ 5, { 'Fn::Split': [ ':', theToken ]} ]} ]} ]}); - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len test.deepEqual(stack.resolve(parsed.resourceName), { 'Fn::Select': [ 1, { 'Fn::Split': [ '/', { 'Fn::Select': [ 5, { 'Fn::Split': [ ':', theToken ]} ]} ]} ]}); test.done(); diff --git a/packages/@aws-cdk/core/test/test.bundling.ts b/packages/@aws-cdk/core/test/test.bundling.ts index 500401101d22d..eb043d1c8a3ae 100644 --- a/packages/@aws-cdk/core/test/test.bundling.ts +++ b/packages/@aws-cdk/core/test/test.bundling.ts @@ -40,7 +40,7 @@ export = { '-w', '/working-directory', 'alpine', 'cool', 'command', - ])); + ], { stdio: 'inherit' })); test.done(); }, diff --git a/packages/@aws-cdk/core/test/test.cfn-resource.ts b/packages/@aws-cdk/core/test/test.cfn-resource.ts index dec09b5c96084..41d360296ec1d 100644 --- a/packages/@aws-cdk/core/test/test.cfn-resource.ts +++ b/packages/@aws-cdk/core/test/test.cfn-resource.ts @@ -73,4 +73,28 @@ export = nodeunit.testCase({ test.done(); }, + + 'can add metadata'(test: nodeunit.Test) { + // GIVEN + const app = new core.App(); + const stack = new core.Stack(app, 'TestStack'); + const resource = new core.CfnResource(stack, 'DefaultResource', { type: 'Test::Resource::Fake' }); + + // WHEN + resource.addMetadata('Beep', 'Boop'); + + // THEN + test.deepEqual(app.synth().getStackByName(stack.stackName).template, { + Resources: { + DefaultResource: { + Type: 'Test::Resource::Fake', + Metadata: { + Beep: 'Boop', + }, + }, + }, + }); + + test.done(); + }, }); diff --git a/packages/@aws-cdk/core/test/test.construct.ts b/packages/@aws-cdk/core/test/test.construct.ts index 56ef04e1d1d5e..4e99c21d6f1ea 100644 --- a/packages/@aws-cdk/core/test/test.construct.ts +++ b/packages/@aws-cdk/core/test/test.construct.ts @@ -3,7 +3,7 @@ import { Test } from 'nodeunit'; import { App as Root, Aws, Construct, ConstructNode, ConstructOrder, IConstruct, Lazy, ValidationError } from '../lib'; import { reEnableStackTraceCollection, restoreStackTraceColection } from './util'; -// tslint:disable:variable-name +/* eslint-disable @typescript-eslint/naming-convention */ export = { 'the "Root" construct is a special construct which can be used as the root of the tree'(test: Test) { @@ -158,7 +158,7 @@ export = { test.done(); }, - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len 'construct.setContext(k,v) sets context at some level and construct.getContext(key) will return the lowermost value defined in the stack'(test: Test) { const root = new Root(); const highChild = new Construct(root, 'highChild'); @@ -333,7 +333,7 @@ export = { test.done(); }, - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len 'construct.validate() can be implemented to perform validation, ConstructNode.validate(construct.node) will return all errors from the subtree (DFS)'(test: Test) { class MyConstruct extends Construct { @@ -455,7 +455,7 @@ export = { test.done(); }, - 'defaultChild': { + defaultChild: { 'returns the child with id "Resource"'(test: Test) { const root = new Root(); new Construct(root, 'child1'); diff --git a/packages/@aws-cdk/core/test/test.cross-environment-token.ts b/packages/@aws-cdk/core/test/test.cross-environment-token.ts index 8ff06c04e8d81..cd97e4661162e 100644 --- a/packages/@aws-cdk/core/test/test.cross-environment-token.ts +++ b/packages/@aws-cdk/core/test/test.cross-environment-token.ts @@ -2,7 +2,7 @@ import { Test } from 'nodeunit'; import { App, CfnOutput, CfnResource, Construct, PhysicalName, Resource, Stack } from '../lib'; import { toCloudFormation } from './util'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ export = { 'CrossEnvironmentToken': { @@ -254,7 +254,7 @@ class MyResource extends Resource { this.arn = this.getResourceArnAttribute('simple-arn', { region: '', account: '', - resource: 'my-resource', + resource: 'my-resource', resourceName: this.physicalName, service: 'myservice', }); diff --git a/packages/@aws-cdk/core/test/test.duration.ts b/packages/@aws-cdk/core/test/test.duration.ts index 4aef37568dc15..7746591249cab 100644 --- a/packages/@aws-cdk/core/test/test.duration.ts +++ b/packages/@aws-cdk/core/test/test.duration.ts @@ -78,11 +78,13 @@ export = nodeunit.testCase({ }, 'toISOString'(test: nodeunit.Test) { + test.equal(Duration.millis(0).toISOString(), 'PT0S'); test.equal(Duration.seconds(0).toISOString(), 'PT0S'); test.equal(Duration.minutes(0).toISOString(), 'PT0S'); test.equal(Duration.hours(0).toISOString(), 'PT0S'); test.equal(Duration.days(0).toISOString(), 'PT0S'); + test.equal(Duration.millis(5).toISOString(), 'PT0.005S'); test.equal(Duration.seconds(5).toISOString(), 'PT5S'); test.equal(Duration.minutes(5).toISOString(), 'PT5M'); test.equal(Duration.hours(5).toISOString(), 'PT5H'); @@ -94,11 +96,13 @@ export = nodeunit.testCase({ }, 'toIsoString'(test: nodeunit.Test) { + test.equal(Duration.millis(0).toIsoString(), 'PT0S'); test.equal(Duration.seconds(0).toIsoString(), 'PT0S'); test.equal(Duration.minutes(0).toIsoString(), 'PT0S'); test.equal(Duration.hours(0).toIsoString(), 'PT0S'); test.equal(Duration.days(0).toIsoString(), 'PT0S'); + test.equal(Duration.millis(5).toIsoString(), 'PT0.005S'); test.equal(Duration.seconds(5).toIsoString(), 'PT5S'); test.equal(Duration.minutes(5).toIsoString(), 'PT5M'); test.equal(Duration.hours(5).toIsoString(), 'PT5H'); @@ -143,6 +147,13 @@ export = nodeunit.testCase({ test.done(); }, + + 'add two durations'(test: nodeunit.Test) { + test.equal(Duration.minutes(1).plus(Duration.seconds(30)).toSeconds(), Duration.seconds(90).toSeconds()); + test.equal(Duration.minutes(1).plus(Duration.seconds(30)).toMinutes({ integral: false }), Duration.seconds(90).toMinutes({ integral: false })); + + test.done(); + }, }); function floatEqual(test: nodeunit.Test, actual: number, expected: number) { diff --git a/packages/@aws-cdk/core/test/test.resource.ts b/packages/@aws-cdk/core/test/test.resource.ts index 38c88d26deabb..34f07c02ab56a 100644 --- a/packages/@aws-cdk/core/test/test.resource.ts +++ b/packages/@aws-cdk/core/test/test.resource.ts @@ -205,7 +205,7 @@ export = { const r1 = new CfnResource(stack, 'Resource', { type: 'Type' }); r1.cfnOptions.creationPolicy = { autoScalingCreationPolicy: { minSuccessfulInstancesPercent: 10 } }; - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len r1.cfnOptions.updatePolicy = { autoScalingScheduledAction: { ignoreUnmodifiedGroupSizeProperties: false }, autoScalingReplacingUpdate: { willReplace: true }, @@ -382,7 +382,7 @@ export = { test.done(); }, - 'overrides': { + overrides: { 'addOverride(p, v) allows assigning arbitrary values to synthesized resource definitions'(test: Test) { // GIVEN const stack = new Stack(); @@ -707,7 +707,6 @@ export = { }; interface CounterProps { - // tslint:disable-next-line:variable-name Count: number; } diff --git a/packages/@aws-cdk/core/test/test.runtime-info.ts b/packages/@aws-cdk/core/test/test.runtime-info.ts index e307633628c37..8f619ce9f991d 100644 --- a/packages/@aws-cdk/core/test/test.runtime-info.ts +++ b/packages/@aws-cdk/core/test/test.runtime-info.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; -import { Test } from 'nodeunit'; import * as os from 'os'; import * as path from 'path'; +import { Test } from 'nodeunit'; import { collectRuntimeInformation } from '../lib/private/runtime-info'; export = { diff --git a/packages/@aws-cdk/core/test/test.stack.ts b/packages/@aws-cdk/core/test/test.stack.ts index 7f8e625ee4428..f60ec0d8a2513 100644 --- a/packages/@aws-cdk/core/test/test.stack.ts +++ b/packages/@aws-cdk/core/test/test.stack.ts @@ -525,7 +525,7 @@ export = { test.throws(() => { ConstructNode.prepare(app.node); - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len }, "'Stack2' depends on 'Stack1' (Stack2/SomeParameter -> Stack1.AWS::AccountId). Adding this dependency (Stack1/SomeParameter -> Stack2.AWS::AccountId) would create a cyclic reference."); test.done(); diff --git a/packages/@aws-cdk/core/test/test.staging.ts b/packages/@aws-cdk/core/test/test.staging.ts index 98e8f665de9b7..bff677342bf7f 100644 --- a/packages/@aws-cdk/core/test/test.staging.ts +++ b/packages/@aws-cdk/core/test/test.staging.ts @@ -1,8 +1,8 @@ +import * as os from 'os'; +import * as path from 'path'; import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs-extra'; import { Test } from 'nodeunit'; -import * as os from 'os'; -import * as path from 'path'; import * as sinon from 'sinon'; import { App, AssetHashType, AssetStaging, BundlingDockerImage, Stack } from '../lib'; @@ -108,6 +108,7 @@ export = { const ensureDirSyncSpy = sinon.spy(fs, 'ensureDirSync'); const mkdtempSyncSpy = sinon.spy(fs, 'mkdtempSync'); const chmodSyncSpy = sinon.spy(fs, 'chmodSync'); + const consoleErrorSpy = sinon.spy(console, 'error'); // WHEN new AssetStaging(stack, 'Asset', { @@ -138,6 +139,9 @@ export = { test.ok(mkdtempSyncSpy.calledWith(sinon.match(path.join(stagingTmp, 'asset-bundle-')))); test.ok(chmodSyncSpy.calledWith(sinon.match(path.join(stagingTmp, 'asset-bundle-')), 0o777)); + // shows a message before bundling + test.ok(consoleErrorSpy.calledWith('Bundling asset stack/Asset...')); + test.done(); }, diff --git a/packages/@aws-cdk/core/test/test.synthesis.ts b/packages/@aws-cdk/core/test/test.synthesis.ts index c0d598c0ee9b9..d83ba978bdaa3 100644 --- a/packages/@aws-cdk/core/test/test.synthesis.ts +++ b/packages/@aws-cdk/core/test/test.synthesis.ts @@ -1,9 +1,9 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs'; -import { Test } from 'nodeunit'; import * as os from 'os'; import * as path from 'path'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import * as cxapi from '@aws-cdk/cx-api'; +import { Test } from 'nodeunit'; import * as cdk from '../lib'; function createModernApp() { diff --git a/packages/@aws-cdk/core/test/test.tag-manager.ts b/packages/@aws-cdk/core/test/test.tag-manager.ts index 1ba8484fffb4f..dcde516f048e2 100644 --- a/packages/@aws-cdk/core/test/test.tag-manager.ts +++ b/packages/@aws-cdk/core/test/test.tag-manager.ts @@ -80,8 +80,8 @@ export = { {key: 'foo', value: 'bar', propagateAtLaunch: true}, ]); test.deepEqual(keyValue.renderTags(), [ - { Key: 'asg', Value : 'only' }, - { Key: 'foo', Value : 'bar' }, + { Key: 'asg', Value: 'only' }, + { Key: 'foo', Value: 'bar' }, ]); test.deepEqual(mapper.renderTags(), { foo: 'bar', diff --git a/packages/@aws-cdk/core/test/test.tokens.ts b/packages/@aws-cdk/core/test/test.tokens.ts index 3dfe56ff07c8d..e889be85e5add 100644 --- a/packages/@aws-cdk/core/test/test.tokens.ts +++ b/packages/@aws-cdk/core/test/test.tokens.ts @@ -121,7 +121,7 @@ export = { test.done(); }, - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len 'if a resolvable object inherits from a class that is also resolvable, the "constructor" function will not get in the way (uses Object.keys instead of "for in")'(test: Test) { test.deepEqual(resolve({ prop: new DataType() }), { prop: { foo: 12, goo: 'hello' } }); test.done(); diff --git a/packages/@aws-cdk/core/test/test.util.ts b/packages/@aws-cdk/core/test/test.util.ts index b9821868df432..010f6ef33dc5b 100644 --- a/packages/@aws-cdk/core/test/test.util.ts +++ b/packages/@aws-cdk/core/test/test.util.ts @@ -28,7 +28,7 @@ export = testCase({ test.done(); }, - 'ignoreEmpty': { + ignoreEmpty: { '[]'(test: Test) { const stack = new Stack(); @@ -73,7 +73,7 @@ export = testCase({ }, }, - 'filterUnderined': { + filterUnderined: { 'is null-safe (aka treats null and undefined the same)'(test: Test) { test.deepEqual(filterUndefined({ 'a null': null, 'a not null': true }), { 'a not null': true }); test.done(); diff --git a/packages/@aws-cdk/custom-resources/.gitignore b/packages/@aws-cdk/custom-resources/.gitignore index 669114bcaf206..4da7786ccfb57 100644 --- a/packages/@aws-cdk/custom-resources/.gitignore +++ b/packages/@aws-cdk/custom-resources/.gitignore @@ -18,3 +18,5 @@ nyc.config.js lib/aws-custom-resource/sdk-api-metadata.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/.npmignore b/packages/@aws-cdk/custom-resources/.npmignore index 8e6cd9397a51c..3b641cc6c559c 100644 --- a/packages/@aws-cdk/custom-resources/.npmignore +++ b/packages/@aws-cdk/custom-resources/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/README.md b/packages/@aws-cdk/custom-resources/README.md index a4aba7be33eff..ac1f36ce744b7 100644 --- a/packages/@aws-cdk/custom-resources/README.md +++ b/packages/@aws-cdk/custom-resources/README.md @@ -30,13 +30,15 @@ the actual handler. ```ts import { CustomResource } from '@aws-cdk/core'; +import * as logs from '@aws-cdk/aws-logs'; import * as cr from '@aws-cdk/custom-resources'; const onEvent = new lambda.Function(this, 'MyHandler', { /* ... */ }); const myProvider = new cr.Provider(this, 'MyProvider', { onEventHandler: onEvent, - isCompleteHandler: isComplete // optional async "waiter" + isCompleteHandler: isComplete, // optional async "waiter" + logRetention: logs.RetentionDays.ONE_DAY // default is INFINITE }); new CustomResource(this, 'Resource1', { serviceToken: myProvider.serviceToken }); diff --git a/packages/@aws-cdk/custom-resources/jest.config.js b/packages/@aws-cdk/custom-resources/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/custom-resources/jest.config.js +++ b/packages/@aws-cdk/custom-resources/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts index 8c56de166305e..633d5d2d2e8f2 100644 --- a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts +++ b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts @@ -1,9 +1,9 @@ +import * as fs from 'fs'; +import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as logs from '@aws-cdk/aws-logs'; import * as cdk from '@aws-cdk/core'; -import * as fs from 'fs'; -import * as path from 'path'; // don't use "require" since the typescript compiler emits errors since this // file is not listed in tsconfig.json. diff --git a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts index c0a6954f8c6f8..b26226b4783e1 100644 --- a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts +++ b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts @@ -1,4 +1,4 @@ -// tslint:disable:no-console +/* eslint-disable no-console */ import { execSync } from 'child_process'; import { AwsSdkCall } from '../aws-custom-resource'; @@ -53,6 +53,10 @@ function filterKeys(object: object, pred: (key: string) => boolean) { let latestSdkInstalled = false; +export function forceSdkInstallation() { + latestSdkInstalled = false; +} + /** * Installs latest AWS SDK v2 */ @@ -60,6 +64,7 @@ function installLatestSdk(): void { console.log('Installing latest AWS SDK v2'); // Both HOME and --prefix are needed here because /tmp is the only writable location execSync('HOME=/tmp npm install aws-sdk@2 --production --no-package-lock --no-save --prefix /tmp'); + execSync('tree /tmp/node_modules'); latestSdkInstalled = true; } diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts index e420c5c6e8c38..01759f71b7e45 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts @@ -1,7 +1,8 @@ +import * as path from 'path'; import * as cfn from '@aws-cdk/aws-cloudformation'; import * as lambda from '@aws-cdk/aws-lambda'; +import * as logs from '@aws-cdk/aws-logs'; import { Construct, Duration } from '@aws-cdk/core'; -import * as path from 'path'; import * as consts from './runtime/consts'; import { calculateRetryPolicy } from './util'; import { WaiterStateMachine } from './waiter-state-machine'; @@ -59,6 +60,15 @@ export interface ProviderProps { * @default Duration.minutes(30) */ readonly totalTimeout?: Duration; + + /** + * The number of days framework log events are kept in CloudWatch Logs. When + * updating this property, unsetting it doesn't remove the log retention policy. + * To remove the retention policy, set the value to `INFINITE`. + * + * @default logs.RetentionDays.INFINITE + */ + readonly logRetention?: logs.RetentionDays; } /** @@ -85,6 +95,7 @@ export class Provider extends Construct implements cfn.ICustomResourceProvider { public readonly serviceToken: string; private readonly entrypoint: lambda.Function; + private readonly logRetention?: logs.RetentionDays; constructor(scope: Construct, id: string, props: ProviderProps) { super(scope, id); @@ -97,6 +108,8 @@ export class Provider extends Construct implements cfn.ICustomResourceProvider { this.onEventHandler = props.onEventHandler; this.isCompleteHandler = props.isCompleteHandler; + this.logRetention = props.logRetention; + const onEventFunction = this.createFunction(consts.FRAMEWORK_ON_EVENT_HANDLER_NAME); if (this.isCompleteHandler) { @@ -138,6 +151,7 @@ export class Provider extends Construct implements cfn.ICustomResourceProvider { runtime: lambda.Runtime.NODEJS_10_X, handler: `framework.${entrypoint}`, timeout: FRAMEWORK_HANDLER_TIMEOUT, + logRetention: this.logRetention, }); fn.addEnvironment(consts.USER_ON_EVENT_FUNCTION_ARN_ENV, this.onEventHandler.functionArn); diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/cfn-response.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/cfn-response.ts index 9e6daa4c6dcf9..ec6ec576aeffa 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/cfn-response.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/cfn-response.ts @@ -1,5 +1,5 @@ -// tslint:disable: max-line-length -// tslint:disable: no-console +/* eslint-disable max-len */ +/* eslint-disable no-console */ import * as url from 'url'; import { httpRequest } from './outbound'; import { log } from './util'; diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/framework.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/framework.ts index 7777bfcb2b70d..f05708b8c6a7d 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/framework.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/framework.ts @@ -1,5 +1,5 @@ -// tslint:disable: no-console -// tslint:disable: max-line-length +/* eslint-disable max-len */ +/* eslint-disable no-console */ import { IsCompleteResponse, OnEventResponse } from '../types'; import * as cfnResponse from './cfn-response'; import * as consts from './consts'; diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/outbound.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/outbound.ts index 682632fd1a40a..f3cb16ebd6f05 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/outbound.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/outbound.ts @@ -1,9 +1,9 @@ /* istanbul ignore file */ +import * as https from 'https'; // eslint-disable-next-line import/no-extraneous-dependencies import * as AWS from 'aws-sdk'; // eslint-disable-next-line import/no-extraneous-dependencies import { ConfigurationOptions } from 'aws-sdk/lib/config'; -import * as https from 'https'; const FRAMEWORK_HANDLER_TIMEOUT = 900000; // 15 minutes diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/util.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/util.ts index 062be15eb1741..be2c17118fbc9 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/util.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/util.ts @@ -1,4 +1,4 @@ -// tslint:disable: no-console +/* eslint-disable no-console */ export function getEnv(name: string): string { const value = process.env[name]; diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index 205211854907c..bb78aa17ec8f5 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -73,7 +73,7 @@ "@types/aws-lambda": "^8.10.39", "@types/fs-extra": "^8.1.0", "@types/sinon": "^9.0.4", - "aws-sdk": "^2.711.0", + "aws-sdk": "^2.715.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts index 4126fb06a15f2..336d0103d8bbe 100644 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts @@ -4,11 +4,13 @@ import * as fs from 'fs-extra'; import * as nock from 'nock'; import * as sinon from 'sinon'; import { AwsSdkCall, PhysicalResourceId } from '../../lib'; -import { flatten, handler } from '../../lib/aws-custom-resource/runtime'; +import { flatten, handler, forceSdkInstallation } from '../../lib/aws-custom-resource/runtime'; + +/* eslint-disable no-console */ AWS.setSDK(require.resolve('aws-sdk')); -console.log = jest.fn(); // tslint:disable-line no-console +console.log = jest.fn(); const eventCommon = { ServiceToken: 'token', @@ -415,7 +417,7 @@ test('flatten correctly flattens a nested object', () => { test('installs the latest SDK', async () => { const tmpPath = '/tmp/node_modules/aws-sdk'; - fs.remove(tmpPath); + await fs.remove(tmpPath); const publishFake = sinon.fake.resolves({}); @@ -442,6 +444,8 @@ test('installs the latest SDK', async () => { body.Status === 'SUCCESS', ); + // Reset to 'false' so that the next run will reinstall aws-sdk + forceSdkInstallation(); await handler(event, {} as AWSLambda.Context); expect(request.isDone()).toBeTruthy(); diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource.test.ts b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource.test.ts index 52f00d489a764..f24f04131828b 100644 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource.test.ts +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource.test.ts @@ -4,7 +4,7 @@ import * as logs from '@aws-cdk/aws-logs'; import * as cdk from '@aws-cdk/core'; import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from '../../lib'; -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ test('aws sdk js custom resource with onCreate and onDelete', () => { // GIVEN diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.ts b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.ts index 7d39277c0a569..3b241031e04c1 100644 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.ts +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.ts @@ -1,4 +1,5 @@ #!/usr/bin/env node +/// !cdk-integ pragma:ignore-assets import * as sns from '@aws-cdk/aws-sns'; import * as ssm from '@aws-cdk/aws-ssm'; import * as cdk from '@aws-cdk/core'; diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.ts b/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.ts index e628938673c2c..01ccd37e89d89 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.ts +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.ts @@ -1,5 +1,4 @@ -/// !cdk-integ * - +/// !cdk-integ * pragma:ignore-assets import * as s3 from '@aws-cdk/aws-s3'; import { App, CfnOutput, Construct, Stack } from '@aws-cdk/core'; import { S3Assert } from './integration-test-fixtures/s3-assert'; diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert.ts b/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert.ts index 06a5181053007..0823d21e05e51 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert.ts +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert.ts @@ -1,8 +1,8 @@ +import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; import { Construct, CustomResource, Duration, Stack } from '@aws-cdk/core'; -import * as path from 'path'; import * as cr from '../../../lib'; export interface S3AssertProps { diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file-handler/index.ts b/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file-handler/index.ts index b21fbdcf1a05f..7495b72cbd157 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file-handler/index.ts +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file-handler/index.ts @@ -1,4 +1,4 @@ -// tslint:disable: no-console +/* eslint-disable no-console */ import * as AWS from 'aws-sdk'; import * as api from './api'; diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file.ts b/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file.ts index a2764f97541ef..234ec567cbdbb 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file.ts +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file.ts @@ -1,8 +1,8 @@ +import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; import { Construct, CustomResource, Stack } from '@aws-cdk/core'; -import * as path from 'path'; import * as cr from '../../../lib'; import * as api from './s3-file-handler/api'; diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts b/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts index 7831d74fe11ce..e59b7bcd19822 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts @@ -1,6 +1,7 @@ +import * as path from 'path'; import * as lambda from '@aws-cdk/aws-lambda'; +import * as logs from '@aws-cdk/aws-logs'; import { Duration, Stack } from '@aws-cdk/core'; -import * as path from 'path'; import * as cr from '../../lib'; import * as util from '../../lib/provider-framework/util'; @@ -166,3 +167,53 @@ describe('retry policy', () => { })).toThrow(/Cannot determine retry count since totalTimeout=5s is not integrally dividable by queryInterval=10s/); }); }); + +describe('log retention', () => { + it('includes a log rotation lambda when present', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new cr.Provider(stack, 'MyProvider', { + onEventHandler: new lambda.Function(stack, 'MyHandler', { + code: lambda.Code.fromAsset(path.join(__dirname, './integration-test-fixtures/s3-file-handler')), + handler: 'index.onEvent', + runtime: lambda.Runtime.NODEJS_10_X, + }), + logRetention: logs.RetentionDays.ONE_WEEK, + }); + + // THEN + expect(stack).toHaveResource('Custom::LogRetention', { + LogGroupName: { + 'Fn::Join': [ + '', + [ + '/aws/lambda/', + { + Ref: 'MyProviderframeworkonEvent9AF5C387', + }, + ], + ], + }, + RetentionInDays: 7, + }); + }); + + it('does not include the log rotation lambda otherwise', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new cr.Provider(stack, 'MyProvider', { + onEventHandler: new lambda.Function(stack, 'MyHandler', { + code: lambda.Code.fromAsset(path.join(__dirname, './integration-test-fixtures/s3-file-handler')), + handler: 'index.onEvent', + runtime: lambda.Runtime.NODEJS_10_X, + }), + }); + + // THEN + expect(stack).not.toHaveResource('Custom::LogRetention'); + }); +}); diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/runtime.test.ts b/packages/@aws-cdk/custom-resources/test/provider-framework/runtime.test.ts index 911d51f234666..45cc13a460959 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/runtime.test.ts +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/runtime.test.ts @@ -1,11 +1,10 @@ -// tslint:disable: no-console -// tslint:disable: max-line-length +/* eslint-disable max-len */ +/* eslint-disable no-console */ /* eslint-disable @typescript-eslint/no-require-imports */ import cfnResponse = require('../../lib/provider-framework/runtime/cfn-response'); import framework = require('../../lib/provider-framework/runtime/framework'); import outbound = require('../../lib/provider-framework/runtime/outbound'); import mocks = require('./mocks'); -/* eslint-enable */ console.log = jest.fn(); diff --git a/packages/@aws-cdk/cx-api/.gitignore b/packages/@aws-cdk/cx-api/.gitignore index 9c86e7fa0fe0b..bb785cfb74f08 100644 --- a/packages/@aws-cdk/cx-api/.gitignore +++ b/packages/@aws-cdk/cx-api/.gitignore @@ -14,3 +14,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/.npmignore b/packages/@aws-cdk/cx-api/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/cx-api/.npmignore +++ b/packages/@aws-cdk/cx-api/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/jest.config.js b/packages/@aws-cdk/cx-api/jest.config.js index d984ff822379b..061fcef9a7451 100644 --- a/packages/@aws-cdk/cx-api/jest.config.js +++ b/packages/@aws-cdk/cx-api/jest.config.js @@ -1,4 +1,4 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = { ...baseConfig, coverageThreshold: { diff --git a/packages/@aws-cdk/cx-api/lib/artifacts/asset-manifest-artifact.ts b/packages/@aws-cdk/cx-api/lib/artifacts/asset-manifest-artifact.ts index 07414bafe4249..8d79c92e89bb9 100644 --- a/packages/@aws-cdk/cx-api/lib/artifacts/asset-manifest-artifact.ts +++ b/packages/@aws-cdk/cx-api/lib/artifacts/asset-manifest-artifact.ts @@ -1,5 +1,5 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as path from 'path'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { CloudArtifact } from '../cloud-artifact'; import { CloudAssembly } from '../cloud-assembly'; diff --git a/packages/@aws-cdk/cx-api/lib/artifacts/cloudformation-artifact.ts b/packages/@aws-cdk/cx-api/lib/artifacts/cloudformation-artifact.ts index e22bc5764a798..66a39b250ca1c 100644 --- a/packages/@aws-cdk/cx-api/lib/artifacts/cloudformation-artifact.ts +++ b/packages/@aws-cdk/cx-api/lib/artifacts/cloudformation-artifact.ts @@ -1,6 +1,6 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as fs from 'fs'; import * as path from 'path'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { CloudArtifact } from '../cloud-artifact'; import { CloudAssembly } from '../cloud-assembly'; import { Environment, EnvironmentUtils } from '../environment'; diff --git a/packages/@aws-cdk/cx-api/lib/artifacts/nested-cloud-assembly-artifact.ts b/packages/@aws-cdk/cx-api/lib/artifacts/nested-cloud-assembly-artifact.ts index bf3e378774d96..3aa57577851b0 100644 --- a/packages/@aws-cdk/cx-api/lib/artifacts/nested-cloud-assembly-artifact.ts +++ b/packages/@aws-cdk/cx-api/lib/artifacts/nested-cloud-assembly-artifact.ts @@ -1,5 +1,5 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as path from 'path'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { CloudArtifact } from '../cloud-artifact'; import { CloudAssembly } from '../cloud-assembly'; diff --git a/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts b/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts index b12c8a52ccdb6..c94b83d97f9e4 100644 --- a/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts +++ b/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts @@ -1,7 +1,7 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { CloudFormationStackArtifact } from './artifacts/cloudformation-artifact'; import { NestedCloudAssemblyArtifact } from './artifacts/nested-cloud-assembly-artifact'; import { TreeCloudArtifact } from './artifacts/tree-cloud-artifact'; @@ -85,6 +85,7 @@ export class CloudAssembly { } if (artifacts.length > 1) { + // eslint-disable-next-line max-len throw new Error(`There are multiple stacks with the stack name "${stackName}" (${artifacts.map(a => a.id).join(',')}). Use "getStackArtifact(id)" instead`); } diff --git a/packages/@aws-cdk/cx-api/package.json b/packages/@aws-cdk/cx-api/package.json index 371aebc1f65fd..8b91f0e93c974 100644 --- a/packages/@aws-cdk/cx-api/package.json +++ b/packages/@aws-cdk/cx-api/package.json @@ -53,7 +53,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^26.0.3", + "@types/jest": "^26.0.4", "@types/mock-fs": "^4.10.0", "@types/semver": "^7.3.1", "cdk-build-tools": "0.0.0", diff --git a/packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts b/packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts index c3ae0aa6609ea..fd908ebcd389f 100644 --- a/packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts +++ b/packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts @@ -1,7 +1,7 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '../lib'; test('cloud assembly builder', () => { diff --git a/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts b/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts index f3ee5f817caa6..81164d16484c9 100644 --- a/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts +++ b/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts @@ -118,6 +118,7 @@ test('stack artifacts can specify an explicit stack name that is different from test('getStackByName fails if there are multiple stacks with the same name', () => { const assembly = new CloudAssembly(path.join(FIXTURES, 'multiple-stacks-same-name')); + // eslint-disable-next-line max-len expect(() => assembly.getStackByName('the-physical-name-of-the-stack')).toThrow(/There are multiple stacks with the stack name \"the-physical-name-of-the-stack\" \(stack1\,stack2\)\. Use \"getStackArtifact\(id\)\" instead/); }); diff --git a/packages/@aws-cdk/example-construct-library/.gitignore b/packages/@aws-cdk/example-construct-library/.gitignore index c35d6e19fb425..d8a8561d50885 100644 --- a/packages/@aws-cdk/example-construct-library/.gitignore +++ b/packages/@aws-cdk/example-construct-library/.gitignore @@ -15,3 +15,5 @@ nyc.config.js *.snk !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/example-construct-library/.npmignore b/packages/@aws-cdk/example-construct-library/.npmignore index 9a6e1c30b51b7..bca0ae2513f1e 100644 --- a/packages/@aws-cdk/example-construct-library/.npmignore +++ b/packages/@aws-cdk/example-construct-library/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/example-construct-library/jest.config.js b/packages/@aws-cdk/example-construct-library/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/example-construct-library/jest.config.js +++ b/packages/@aws-cdk/example-construct-library/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/example-construct-library/test/example-resource.test.ts b/packages/@aws-cdk/example-construct-library/test/example-resource.test.ts index 5dcab5f1a8288..4d73c8be33973 100644 --- a/packages/@aws-cdk/example-construct-library/test/example-resource.test.ts +++ b/packages/@aws-cdk/example-construct-library/test/example-resource.test.ts @@ -19,7 +19,7 @@ import * as core from '@aws-cdk/core'; import * as er from '../lib'; /* We allow quotes in the object keys used for CloudFormation template assertions */ -// tslint:disable:object-literal-key-quotes +/* eslint-disable quote-props */ describe('Example Resource', () => { let stack: core.Stack; diff --git a/packages/@aws-cdk/pipelines/.eslintrc.js b/packages/@aws-cdk/pipelines/.eslintrc.js new file mode 100644 index 0000000000000..61dd8dd001f63 --- /dev/null +++ b/packages/@aws-cdk/pipelines/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/pipelines/.gitignore b/packages/@aws-cdk/pipelines/.gitignore new file mode 100644 index 0000000000000..2b54d7dd5c615 --- /dev/null +++ b/packages/@aws-cdk/pipelines/.gitignore @@ -0,0 +1,19 @@ +*.js +tsconfig.json +*.js.map +*.d.ts +*.generated.ts +dist +lib/generated/resources.ts +.jsii + +.LAST_BUILD +.nyc_output +coverage +nyc.config.js +.LAST_PACKAGE +*.snk +!.eslintrc.js + +!jest.config.js +junit.xml diff --git a/packages/@aws-cdk/pipelines/.npmignore b/packages/@aws-cdk/pipelines/.npmignore new file mode 100644 index 0000000000000..43090522285b1 --- /dev/null +++ b/packages/@aws-cdk/pipelines/.npmignore @@ -0,0 +1,27 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js + +# Include .jsii +!.jsii + +*.snk + +*.tsbuildinfo + +tsconfig.json +.eslintrc.js + +# exclude cdk artifacts +**/cdk.out +jest.config.js +junit.xml +package/node_modules diff --git a/packages/@aws-cdk/pipelines/LICENSE b/packages/@aws-cdk/pipelines/LICENSE new file mode 100644 index 0000000000000..b71ec1688783a --- /dev/null +++ b/packages/@aws-cdk/pipelines/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/pipelines/NOTICE b/packages/@aws-cdk/pipelines/NOTICE new file mode 100644 index 0000000000000..bfccac9a7f69c --- /dev/null +++ b/packages/@aws-cdk/pipelines/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/pipelines/README.md b/packages/@aws-cdk/pipelines/README.md new file mode 100644 index 0000000000000..6b171a06dea98 --- /dev/null +++ b/packages/@aws-cdk/pipelines/README.md @@ -0,0 +1,591 @@ +# CDK Pipelines + +--- + +![cdk-constructs: Developer Preview](https://img.shields.io/badge/cdk--constructs-developer--preview-informational.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are in **developer preview** before they become stable. We will only make breaking changes to address unforeseen API issues. Therefore, these APIs are not subject to [Semantic Versioning](https://semver.org/), and breaking changes will be announced in 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. + +--- + + + +A construct library for painless Continuous Delivery of CDK applications. + +![Developer Preview](https://img.shields.io/badge/developer--preview-informational.svg?style=for-the-badge) + +> This module is in **developer preview**. We may make breaking changes to address unforeseen API issues. Therefore, these APIs are not subject to [Semantic Versioning](https://semver.org/), and breaking changes will be announced in 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. + +## At a glance + +Defining a pipeline for your application is as simple as defining a subclass +of `Stage`, and calling `pipeline.addApplicationStage()` with instances of +that class. Deploying to a different account or region looks exactly the +same, the *CDK Pipelines* library takes care of the details. + +(Note that have to *bootstrap* all environments before the following code +will work, see the section **CDK Environment Bootstrapping** below). + +```ts +import { Construct, Stage } from '@aws-cdk/core'; + +/** + * Your application + * + * May consist of one or more Stacks + */ +class MyApplication extends Stage { + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + + const dbStack = new DatabaseStack(this, 'Database'); + new ComputeStack(this, 'Compute', { + table: dbStack.table, + }); + } +} + +/** + * Stack to hold the pipeline + */ +class MyPipelineStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const sourceArtifact = new codepipeline.Artifact(); + const cloudAssemblyArtifact = new codepipeline.Artifact(); + + const pipeline = new CdkPipeline(this, 'Pipeline', { + // ...source and build information here (see below) + }); + + // Do this as many times as necessary with any account and region + // Account and region may different from the pipeline's. + pipeline.addApplicationStage(new MyApplication(this, 'Prod', { + env: { + account: '123456789012', + region: 'eu-west-1', + } + })); + } +} +``` + +The pipeline is **self-mutating**, which means that if you add new +application stages in the source code, or new stacks to `MyApplication`, the +pipeline will automatically reconfigure itself to deploy those new stages and +stacks. + +## CDK Versioning + +This library uses prerelease features of the CDK framework, which can be enabled by adding the +following to `cdk.json`: + +``` +{ + ... + "context": { + "@aws-cdk/core:newStyleStackSynthesis": true + } +} +``` + +## Defining the Pipeline (Source and Synth) + +The pipeline is defined by instantiating `CdkPipeline` in a Stack. This defines the +source location for the pipeline as well as the build commands. For example, the following +defines a pipeline whose source is stored in a GitHub repository, and uses NPM +to build. The Pipeline will be provisioned in account `111111111111` and region +`eu-west-1`: + +```ts +class MyPipelineStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const sourceArtifact = new codepipeline.Artifact(); + const cloudAssemblyArtifact = new codepipeline.Artifact(); + + const pipeline = new CdkPipeline(this, 'Pipeline', { + pipelineName: 'MyAppPipeline', + cloudAssemblyArtifact, + + sourceAction: new codepipeline_actions.GitHubSourceAction({ + actionName: 'GitHub', + output: sourceArtifact, + oauthToken: SecretValue.secretsManager('GITHUB_TOKEN_NAME'), + trigger: codepipeline_actions.GitHubTrigger.POLL, + // Replace these with your actual GitHub project name + owner: 'OWNER', + repo: 'REPO', + }), + + synthAction: SimpleSynthAction.standardNpmSynth({ + sourceArtifact, + cloudAssemblyArtifact, + + // Use this if you need a build step (if you're not using ts-node + // or if you have TypeScript Lambdas that need to be compiled). + buildCommand: 'npm run build', + }), + }); + } +} + +const app = new App(); +new MyPipelineStack(this, 'PipelineStack', { + env: { + account: '111111111111', + region: 'eu-west-1', + } +}); +``` + +## Initial pipeline deployment + +You provision this pipeline by making sure the target environment has been +bootstrapped (see below), and then executing deploying the `PipelineStack` +*once*. Afterwards, the pipeline will keep itself up-to-date. + +> **Important**: be sure to `git commit` and `git push` before deploying the +> Pipeline stack using `cdk deploy`! +> +> The reason is that the pipeline will start deploying and self-mutating +> right away based on the sources in the repository, so the sources it finds +> in there should be the ones you want it to find. + +Run the following commands to get the pipeline going: + +``` +$ git commit -a +$ git push +$ cdk deploy PipelineStack +``` + +Administrative permissions to the account are only necessary up until +this point. We recommend you shed access to these credentials after doing this. + +### Sources + +Any of the regular sources from the [`@aws-cdk/aws-codepipeline-actions`](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-codepipeline-actions-readme.html#github) module can be used. + +### Synths + +You define how to build and synth the project by specifying a `synthAction`. +This can be any CodePipeline action that produces an artifact with a CDK +Cloud Assembly in it (the contents of the `cdk.out` directory created when +`cdk synth` is called). Pass the output artifact of the synth in the +Pipeline's `cloudAssemblyArtifact` property. + +`SimpleSynthAction` is available for synths that can be performed by running a couple +of simple shell commands (install, build, and synth) using AWS CodeBuild. When +using these, the source repository does not need to have a `buildspec.yml`. An example +of using `SimpleSynthAction` to run a Maven build followed by a CDK synth: + +```ts +const pipeline = new CdkPipeline(this, 'Pipeline', { + // ... + synthAction: new SimpleSynthAction({ + sourceArtifact, + cloudAssemblyArtifact, + installCommand: 'npm install -g aws-cdk', + buildCommand: 'mvn package', + synthCommand: 'cdk synth', + }) +}); +``` + +Available as factory functions on `SimpleSynthAction` are some common +convention-based synth: + +* `SimpleSynthAction.standardNpmSynth()`: build using NPM conventions. Expects a `package-lock.json`, + a `cdk.json`, and expects the CLI to be a versioned dependency in `package.json`. Does + not perform a build step by default. +* `CdkSynth.standardYarnSynth()`: build using Yarn conventions. Expects a `yarn.lock` + a `cdk.json`, and expects the CLI to be a versioned dependency in `package.json`. Does + not perform a build step by default. + +If you need a custom build/synth step that is not covered by `SimpleSynthAction`, you can +always add a custom CodeBuild project and pass a corresponding `CodeBuildAction` to the +pipeline. + +## Adding Application Stages + +To define an application that can be added to the pipeline integrally, define a subclass +of `Stage`. The `Stage` can contain one or more stack which make up your application. If +there are dependencies between the stacks, the stacks will automatically be added to the +pipeline in the right order. Stacks that don't depend on each other will be deployed in +parallel. You can add a dependency relationship between stacks by calling +`stack1.addDependency(stack2)`. + +Stages take a default `env` argument which the Stacks inside the Stage will fall back to +if no `env` is defined for them. + +An application is added to the pipeline by calling `addApplicationStage()` with instances +of the Stage. The same class can be instantiated and added to the pipeline multiple times +to define different stages of your DTAP or multi-region application pipeline: + +```ts +// Testing stage +pipeline.addApplicationStage(new MyApplication(this, 'Testing', { + env: { account: '111111111111', region: 'eu-west-1' } +})); + +// Acceptance stage +pipeline.addApplicationStage(new MyApplication(this, 'Acceptance', { + env: { account: '222222222222', region: 'eu-west-1' } +})); + +// Production stage +pipeline.addApplicationStage(new MyApplication(this, 'Production', { + env: { account: '333333333333', region: 'eu-west-1' } +})); +``` + +### More Control + +Every *Application Stage* added by `addApplicationStage()` will lead to the addition of +an individual *Pipeline Stage*, which is subsequently returned. You can add more +actions to the stage by calling `addAction()` on it. For example: + +```ts +const testingStage = pipeline.addApplicationStage(new MyApplication(this, 'Testing', { + env: { account: '111111111111', region: 'eu-west-1' } +})); + +// Add a action -- in this case, a Manual Approval action +// (for illustration purposes: testingStage.addManualApprovalAction() is a +// convenience shorthand that does the same) +testingStage.addAction(new ManualApprovalAction({ + actionName: 'ManualApproval', + runOrder: testingStage.nextSequentialRunOrder(), +})); +``` + +You can also add more than one *Application Stage* to one *Pipeline Stage*. For example: + +```ts +// Create an empty pipeline stage +const testingStage = pipeline.addStage('Testing'); + +// Add two application stages to the same pipeline stage +testingStage.addApplication(new MyApplication1(this, 'MyApp1', { + env: { account: '111111111111', region: 'eu-west-1' } +})); +testingStage.addApplication(new MyApplication2(this, 'MyApp2', { + env: { account: '111111111111', region: 'eu-west-1' } +})); +``` + +## Adding validations to the pipeline + +You can add any type of CodePipeline Action to the pipeline in order to validate +the deployments you are performing. + +The CDK Pipelines construct library comes with a `ShellScriptAction` which uses AWS CodeBuild +to run a set of shell commands (potentially running a test set that comes with your application, +using stack outputs of the deployed stacks). + +In its simplest form, adding validation actions looks like this: + +```ts +const stage = pipeline.addApplicationStage(new MyApplication(/* ... */)); + +stage.addActions(new ShellScriptAction({ + name: 'MyValidation', + commands: ['curl -Ssf https://my.webservice.com/'], + // ... more configuration ... +})); +``` + +### Using CloudFormation Stack Outputs in ShellScriptAction + +Because many CloudFormation deployments result in the generation of resources with unpredictable +names, validations have support for reading back CloudFormation Outputs after a deployment. This +makes it possible to pass (for example) the generated URL of a load balancer to the test set. + +To use Stack Outputs, expose the `CfnOutput` object you're interested in, and +call `pipeline.stackOutput()` on it: + +```ts +class MyLbApplication extends Stage { + public readonly loadBalancerAddress: CfnOutput; + + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + + const lbStack = new LoadBalancerStack(this, 'Stack'); + + // Or create this in `LoadBalancerStack` directly + this.loadBalancerAddress = new CfnOutput(lbStack, 'LbAddress', { + value: `https://${lbStack.loadBalancer.loadBalancerDnsName}/` + }); + } +} + +const lbApp = new MyLbApplication(this, 'MyApp', { + env: { /* ... */ } +}); +const stage = pipeline.addApplicationStage(lbApp); +stage.addActions(new ShellScriptAction({ + // ... + useOutputs: { + // When the test is executed, this will make $URL contain the + // load balancer address. + URL: pipeline.stackOutput(lbApp.loadBalancerAddress), + } +}); +``` + +### Using additional files in Shell Script Actions + +As part of a validation, you probably want to run a test suite that's more +elaborate than what can be expressed in a couple of lines of shell script. +You can bring additional files into the shell script validation by supplying +the `additionalArtifacts` property. + +Here are some typical examples for how you might want to bring in additional +files from several sources: + +* Directoy from the source repository +* Additional compiled artifacts from the synth step + +#### Additional files from the source repository + +Bringing in additional files from the source repository is appropriate if the +files in the source repository are directly usable in the test (for example, +if they are executable shell scripts themselves). Pass the `sourceArtifact`: + +```ts +const sourceArtifact = new codepipeline.Artifact(); + +const pipeline = new CdkPipeline(this, 'Pipeline', { + // ... +}); + +const validationAction = new ShellScriptAction({ + name: 'TestUsingSourceArtifact', + additionalArtifacts: [sourceArtifact], + + // 'test.sh' comes from the source repository + commands: ['./test.sh'], +}); +``` + +#### Additional files from the synth step + +Getting the additional files from the synth step is appropriate if your +tests need the compilation step that is done as part of synthesis. + +On the synthesis step, specify `additionalArtifacts` to package +additional subdirectories into artifacts, and use the same artifact +in the `ShellScriptAction`'s `additionalArtifacts`: + +```ts +// If you are using additional output artifacts from the synth step, +// they must be named. +const cloudAssemblyArtifact = new codepipeline.Artifact('CloudAsm'); +const integTestsArtifact = new codepipeline.Artifact('IntegTests'); + +const pipeline = new CdkPipeline(this, 'Pipeline', { + synthAction: SimpleSynthAction.standardNpmSynth({ + sourceArtifact, + cloudAssemblyArtifact, + buildCommand: 'npm run build', + additionalArtifacts: [ + { + directory: 'test', + artifact: integTestsArtifact, + } + ], + }), + // ... +}); + +const validationAction = new ShellScriptAction({ + name: 'TestUsingBuildArtifact', + additionalArtifacts: [integTestsArtifact], + // 'test.js' was produced from 'test/test.ts' during the synth step + commands: ['node ./test.js'], +}); +``` + +## CDK Environment Bootstrapping + +An *environment* is an *(account, region)* pair where you want to deploy a +CDK stack (see +[Environments](https://docs.aws.amazon.com/cdk/latest/guide/environments.html) +in the CDK Developer Guide). In a Continuous Deployment pipeline, there are +at least two environments involved: the environment where the pipeline is +provisioned, and the environment where you want to deploy the application (or +different stages of the application). These can be the same, though best +practices recommend you isolate your different application stages from each +other in different AWS accounts or regions. + +Before you can provision the pipeline, you have to *bootstrap* the environment you want +to create it in. If you are deploying your application to different environments, you +also have to bootstrap those and be sure to add a *trust* relationship. + +> This library requires a newer version of the bootstrapping stack which has +> been updated specifically to support cross-account continous delivery. In the future, +> this new bootstrapping stack will become the default, but for now it is still +> opt-in. +> +> The commands below assume you are running `cdk bootstrap` in a directory +> where `cdk.json` contains the `"@aws-cdk/core:newStyleStackSynthesis": true` +> setting in its context, which will switch to the new bootstrapping stack +> automatically. +> +> If run from another directory, be sure to run the bootstrap command with +> the environment variable `CDK_NEW_BOOTSTRAP=1` set. + +To bootstrap an environment for provisioning the pipeline: + +``` +$ env CDK_NEW_BOOTSTRAP=1 npx cdk bootstrap \ + [--profile admin-profile-1] \ + --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess \ + aws://111111111111/us-east-1 +``` + +To bootstrap a different environment for deploying CDK applications into using +a pipeline in account `111111111111`: + +``` +$ env CDK_NEW_BOOTSTRAP=1 npx cdk bootstrap \ + [--profile admin-profile-2] \ + --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess \ + --trust 11111111111 \ + aws://222222222222/us-east-2 +``` + +These command lines explained: + +* `npx`: means to use the CDK CLI from the current NPM install. If you are using + a global install of the CDK CLI, leave this out. +* `--profile`: should indicate a profile with administrator privileges that has + permissions to provision a pipeline in the indicated account. You can leave this + flag out if either the AWS default credentials or the `AWS_*` environment + variables confer these permissions. +* `--cloudformation-execution-policies`: ARN of the managed policy that future CDK + deployments should execute with. You can tailor this to the needs of your organization + and give more constrained permissions than `AdministratorAccess`. +* `--trust`: indicates which other account(s) should have permissions to deploy + CDK applications into this account. In this case we indicate the Pipeline's account, + but you could also use this for developer accounts (don't do that for production + application accounts though!). +* `aws://222222222222/us-east-2`: the account and region we're bootstrapping. + +> **Security tip**: we recommend that you use administrative credentials to an +> account only to bootstrap it and provision the initial pipeline. Otherwise, +> access to administrative credentials should be dropped as soon as possible. + +### Migrating from old bootstrap stack + +The bootstrap stack is a CloudFormation stack in your account named +**CDKToolkit** that provisions a set of resources required for the CDK +to deploy into that environment. + +The "new" bootstrap stack (obtained by running `cdk bootstrap` with +`CDK_NEW_BOOTSTRAP=1`) is slightly more elaborate than the "old" stack. It +contains: + +* An S3 bucket and ECR repository with predictable names, so that we can reference + assets in these storage locations *without* the use of CloudFormation template + parameters. +* A set of roles with permissions to access these asset locations and to execute + CloudFormation, assumeable from whatever accounts you specify under `--trust`. + +It is possible and safe to migrate from the old bootstrap stack to the new +bootstrap stack. This will create a new S3 file asset bucket in your account +and orphan the old bucket. You should manually delete the orphaned bucket +after you are sure you have redeployed all CDK applications and there are no +more references to the old asset bucket. + +## Security Tips + +It's important to stay safe while employing Continuous Delivery. The CDK Pipelines +library comes with secure defaults to the best of our ability, but by its +very nature the library cannot take care of everything. + +We therefore expect you to mind the following: + +* Maintain dependency hygiene and vet 3rd-party software you use. Any software you + run on your build machine has the ability to change the infrastructure that gets + deployed. Be careful with the software you depend on. + +* Use dependency locking to prevent accidental upgrades! The default `CdkSynths` that + come with CDK Pipelines will expect `package-lock.json` and `yarn.lock` to + ensure your dependencies are the ones you expect. + +* Credentials to production environments should be short-lived. After + bootstrapping and the initial pipeline provisioning, there is no more need for + developers to have access to any of the account credentials; all further + changes can be deployed through git. Avoid the chances of credentials leaking + by not having them in the first place! + +## Troubleshooting + +Here are some common errors you may encounter while using this library. + +### Pipeline: Internal Failure + +If you see the following error during deployment of your pipeline: + +``` +CREATE_FAILED | AWS::CodePipeline::Pipeline | Pipeline/Pipeline +Internal Failure +``` + +There's something wrong with your GitHub access token. It might be missing, or not have the +right permissions to access the repository you're trying to access. + +### Key: Policy contains a statement with one or more invalid principals + +If you see the following error during deployment of your pipeline: + +``` +CREATE_FAILED | AWS::KMS::Key | Pipeline/Pipeline/ArtifactsBucketEncryptionKey +Policy contains a statement with one or more invalid principals. +``` + +One of the target (account, region) environments has not been bootstrapped +with the new bootstrap stack. Check your target environments and make sure +they are all bootstrapped. + +### is in ROLLBACK_COMPLETE state and can not be updated. + +If you see the following error during execution of your pipeline: + +``` +Stack ... is in ROLLBACK_COMPLETE state and can not be updated. (Service: +AmazonCloudFormation; Status Code: 400; Error Code: ValidationError; Request +ID: ...) +``` + +The stack failed its previous deployment, and is in a non-retryable state. +Go into the CloudFormation console, delete the stack, and retry the deployment. + +## Current Limitations + +Limitations that we are aware of and will address: + +* **No context queries**: context queries are not supported. That means that + Vpc.fromLookup() and other functions like it will not work [#8905](https://github.com/aws/aws-cdk/issues/8905). + +## Known Issues + +There are some usability issues that are caused by underlying technology, and +cannot be remedied by CDK at this point. They are reproduced here for completeness. + +- **Console links to other accounts will not work**: the AWS CodePipeline + console will assume all links are relative to the current account. You will + not be able to use the pipeline console to click through to a CloudFormation + stack in a different account. +- **If a change set failed to apply the pipeline must restarted**: if a change + set failed to apply, it cannot be retried. The pipeline must be restarted from + the top by clicking **Release Change**. +- **A stack that failed to create must be deleted manually**: if a stack + failed to create on the first attempt, you must delete it using the + CloudFormation console before starting the pipeline again by clicking + **Release Change**. diff --git a/packages/@aws-cdk/pipelines/jest.config.js b/packages/@aws-cdk/pipelines/jest.config.js new file mode 100644 index 0000000000000..ad27b218892cb --- /dev/null +++ b/packages/@aws-cdk/pipelines/jest.config.js @@ -0,0 +1,11 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = { + ...baseConfig, + coverageThreshold: { + global: { + statements: 75, + branches: 55, + }, + }, +}; + diff --git a/packages/@aws-cdk/pipelines/lib/actions/deploy-cdk-stack-action.ts b/packages/@aws-cdk/pipelines/lib/actions/deploy-cdk-stack-action.ts new file mode 100644 index 0000000000000..790f18db764af --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/actions/deploy-cdk-stack-action.ts @@ -0,0 +1,361 @@ +import * as cfn from '@aws-cdk/aws-cloudformation'; +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as cpactions from '@aws-cdk/aws-codepipeline-actions'; +import * as events from '@aws-cdk/aws-events'; +import * as iam from '@aws-cdk/aws-iam'; +import { Arn, Construct, Fn, Stack } from '@aws-cdk/core'; +import * as cxapi from '@aws-cdk/cx-api'; +import * as path from 'path'; +import { appOf, assemblyBuilderOf } from '../private/construct-internals'; + +/** + * Customization options for a DeployCdkStackAction + */ +export interface DeployCdkStackActionOptions { + /** + * Base name of the action + * + * @default stackName + */ + readonly baseActionName?: string; + + /** + * The CodePipeline artifact that holds the Cloud Assembly. + */ + readonly cloudAssemblyInput: codepipeline.Artifact; + + /** + * Run order for the Prepare action + * + * @default 1 + */ + readonly prepareRunOrder?: number; + + /** + * Run order for the Execute action + * + * @default - prepareRunOrder + 1 + */ + readonly executeRunOrder?: number; + + /** + * Artifact to write Stack Outputs to + * + * @default - No outputs + */ + readonly output?: codepipeline.Artifact; + + /** + * Filename in output to write Stack outputs to + * + * @default - Required when 'output' is set + */ + readonly outputFileName?: string; + + /** + * Name of the change set to create and deploy + * + * @default 'PipelineChange' + */ + readonly changeSetName?: string; +} + +/** + * Properties for a DeployCdkStackAction + */ +export interface DeployCdkStackActionProps extends DeployCdkStackActionOptions { + /** + * Relative path of template in the input artifact + */ + readonly templatePath: string; + + /** + * Role for the action to assume + * + * This controls the account to deploy into + */ + readonly actionRole: iam.IRole; + + /** + * The name of the stack that should be created/updated + */ + readonly stackName: string; + + /** + * Role to execute CloudFormation under + * + * @default - Execute CloudFormation using the action role + */ + readonly cloudFormationExecutionRole?: iam.IRole; + + /** + * Region to deploy into + * + * @default - Same region as pipeline + */ + readonly region?: string; + + /** + * Artifact ID for the stack deployed here + * + * Used for pipeline order checking. + * + * @default - Order will not be checked + */ + readonly stackArtifactId?: string; + + /** + * Artifact ID for the stacks this stack depends on + * + * Used for pipeline order checking. + * + * @default - No dependencies + */ + readonly dependencyStackArtifactIds?: string[]; +} + +/** + * Options for the 'fromStackArtifact' operation + */ +export interface CdkStackActionFromArtifactOptions extends DeployCdkStackActionOptions { + /** + * The name of the stack that should be created/updated + * + * @default - Same as stack artifact + */ + readonly stackName?: string; +} + +/** + * Action to deploy a CDK Stack + * + * Adds two CodePipeline Actions to the pipeline: one to create a ChangeSet + * and one to execute it. + * + * You do not need to instantiate this action yourself -- it will automatically + * be added by the pipeline when you add stack artifacts or entire stages. + */ +export class DeployCdkStackAction implements codepipeline.IAction { + /** + * Construct a DeployCdkStackAction from a Stack artifact + */ + public static fromStackArtifact(scope: Construct, artifact: cxapi.CloudFormationStackArtifact, options: CdkStackActionFromArtifactOptions) { + if (!artifact.assumeRoleArn) { + throw new Error(`Stack '${artifact.stackName}' does not have deployment role information; use the 'DefaultStackSynthesizer' synthesizer, or set the '@aws-cdk/core:newStyleStackSynthesis' context key.`); + } + + const actionRole = roleFromPlaceholderArn(scope, artifact.assumeRoleArn); + const cloudFormationExecutionRole = roleFromPlaceholderArn(scope, artifact.cloudFormationExecutionRoleArn); + + const artRegion = artifact.environment.region; + const region = artRegion === Stack.of(scope).region || artRegion === cxapi.UNKNOWN_REGION ? undefined : artRegion; + + // We need the path of the template relative to the root Cloud Assembly + // It should be easier to get this, but for now it is what it is. + const appAsmRoot = assemblyBuilderOf(appOf(scope)).outdir; + const fullTemplatePath = path.join(artifact.assembly.directory, artifact.templateFile); + const templatePath = path.relative(appAsmRoot, fullTemplatePath); + + return new DeployCdkStackAction({ + actionRole, + cloudFormationExecutionRole, + templatePath, + region, + stackArtifactId: artifact.id, + dependencyStackArtifactIds: artifact.dependencies.filter(isStackArtifact).map(s => s.id), + stackName: options.stackName ?? artifact.stackName, + ...options, + }); + } + + /** + * The runorder for the prepare action + */ + public readonly prepareRunOrder: number; + + /** + * The runorder for the execute action + */ + public readonly executeRunOrder: number; + + /** + * Name of the deployed stack + */ + public readonly stackName: string; + + /** + * Artifact id of the artifact this action was based on + */ + public readonly stackArtifactId?: string; + + /** + * Artifact ids of the artifact this stack artifact depends on + */ + public readonly dependencyStackArtifactIds: string[]; + + private readonly prepareChangeSetAction: cpactions.CloudFormationCreateReplaceChangeSetAction; + private readonly executeChangeSetAction: cpactions.CloudFormationExecuteChangeSetAction; + + constructor(props: DeployCdkStackActionProps) { + if (props.output && !props.outputFileName) { + throw new Error('If \'output\' is set, \'outputFileName\' is also required'); + } + + this.stackArtifactId = props.stackArtifactId; + this.dependencyStackArtifactIds = props.dependencyStackArtifactIds ?? []; + + this.prepareRunOrder = props.prepareRunOrder ?? 1; + this.executeRunOrder = props.executeRunOrder ?? this.prepareRunOrder + 1; + this.stackName = props.stackName; + const baseActionName = props.baseActionName ?? this.stackName; + const changeSetName = props.changeSetName ?? 'PipelineChange'; + + this.prepareChangeSetAction = new cpactions.CloudFormationCreateReplaceChangeSetAction({ + actionName: `${baseActionName}.Prepare`, + changeSetName, + runOrder: this.prepareRunOrder, + stackName: this.stackName, + templatePath: props.cloudAssemblyInput.atPath(props.templatePath), + adminPermissions: false, + role: props.actionRole, + deploymentRole: props.cloudFormationExecutionRole, + region: props.region, + capabilities: [cfn.CloudFormationCapabilities.NAMED_IAM, cfn.CloudFormationCapabilities.AUTO_EXPAND], + }); + this.executeChangeSetAction = new cpactions.CloudFormationExecuteChangeSetAction({ + actionName: `${baseActionName}.Deploy`, + changeSetName, + runOrder: this.executeRunOrder, + stackName: this.stackName, + role: props.actionRole, + region: props.region, + outputFileName: props.outputFileName, + output: props.output, + }); + } + + /** + * Exists to implement IAction + */ + public bind(scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): + codepipeline.ActionConfig { + stage.addAction(this.prepareChangeSetAction); + + return this.executeChangeSetAction.bind(scope, stage, options); + } + + /** + * Exists to implement IAction + */ + public onStateChange(name: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule { + return this.executeChangeSetAction.onStateChange(name, target, options); + } + + /** + * Exists to implement IAction + */ + public get actionProperties(): codepipeline.ActionProperties { + return this.executeChangeSetAction.actionProperties; + } +} + +function roleFromPlaceholderArn(scope: Construct, arn: string): iam.IRole; +function roleFromPlaceholderArn(scope: Construct, arn: string | undefined): iam.IRole | undefined; +function roleFromPlaceholderArn(scope: Construct, arn: string | undefined): iam.IRole | undefined { + if (!arn) { return undefined; } + + // Use placeholdered arn as construct ID. + const id = arn; + + scope = hackyRoleScope(scope, arn); + + // https://github.com/aws/aws-cdk/issues/7255 + let existingRole = scope.node.tryFindChild(`ImmutableRole${id}`) as iam.IRole; + if (existingRole) { return existingRole; } + // For when #7255 is fixed. + existingRole = scope.node.tryFindChild(id) as iam.IRole; + if (existingRole) { return existingRole; } + + return iam.Role.fromRoleArn(scope, id, cfnExpressionFromManifestString(arn), { mutable: false }); +} + +/** + * MASSIVE HACK + * + * We have a bug in the CDK where it's only going to consider Roles that are physically in a + * different Stack object from the Pipeline "cross-account", and will add the appropriate + * Bucket/Key policies. + * https://github.com/aws/aws-cdk/pull/8280 will resolve this, but for now we fake it by hacking + * up a Stack object to root the role in! + * + * Fortunatey, we can just 'new up' an unrooted Stack (unit tests do this all the time) and toss it + * away. It will never be synthesized, but all the logic happens to work out! + */ +function hackyRoleScope(scope: Construct, arn: string): Construct { + const parts = Arn.parse(cxapi.EnvironmentPlaceholders.replace(arn, { + accountId: '', // Empty string on purpose, see below + partition: '', + region: '', + })); + return new Stack(undefined, undefined, { + env: { + // Empty string means ARN had a placeholder which means same account as pipeline stack + account: parts.account || Stack.of(scope).account, + // 'region' from an IAM ARN is always an empty string, so no point. + }, + }); +} + +/** + * Return a CloudFormation expression from a manifest string with placeholders + */ +function cfnExpressionFromManifestString(s: string) { + // This implementation relies on the fact that the manifest placeholders are + // '${AWS::Partition}' etc., and so are the same values as those that are + // trivially substituable using a `Fn.sub`. + return Fn.sub(s); +} + +/** + * Options for CdkDeployAction.fromStackArtifact + */ +export interface FromStackArtifactOptions { + /** + * The CodePipeline artifact that holds the Cloud Assembly. + */ + readonly cloudAssemblyInput: codepipeline.Artifact; + + /** + * Run order for the 2 actions that will be created + * + * @default 1 + */ + readonly prepareRunOrder?: number; + + /** + * Run order for the Execute action + * + * @default - prepareRunOrder + 1 + */ + readonly executeRunOrder?: number; + + /** + * Artifact to write Stack Outputs to + * + * @default - No outputs + */ + readonly output?: codepipeline.Artifact; + + /** + * Filename in output to write Stack outputs to + * + * @default - Required when 'output' is set + */ + readonly outputFileName?: string; +} + +function isStackArtifact(a: cxapi.CloudArtifact): a is cxapi.CloudFormationStackArtifact { + // instanceof is too risky, and we're at a too late stage to properly fix. + // return a instanceof cxapi.CloudFormationStackArtifact; + return a.constructor.name === 'CloudFormationStackArtifact'; +} diff --git a/packages/@aws-cdk/pipelines/lib/actions/index.ts b/packages/@aws-cdk/pipelines/lib/actions/index.ts new file mode 100644 index 0000000000000..834ded93472f2 --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/actions/index.ts @@ -0,0 +1,3 @@ +export * from './deploy-cdk-stack-action'; +export * from './publish-assets-action'; +export * from './update-pipeline-action'; \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts b/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts new file mode 100644 index 0000000000000..668d8f831b548 --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts @@ -0,0 +1,153 @@ +import * as codebuild from '@aws-cdk/aws-codebuild'; +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions'; +import * as events from '@aws-cdk/aws-events'; +import * as iam from '@aws-cdk/aws-iam'; +import { Construct, Lazy } from '@aws-cdk/core'; + +/** + * Type of the asset that is being published + */ +export enum AssetType { + /** + * A file + */ + FILE = 'file', + + /** + * A Docker image + */ + DOCKER_IMAGE = 'docker-image', +} + +/** + * Props for a PublishAssetsAction + */ +export interface PublishAssetsActionProps { + /** + * Name of publishing action + */ + readonly actionName: string; + + /** + * The CodePipeline artifact that holds the Cloud Assembly. + */ + readonly cloudAssemblyInput: codepipeline.Artifact; + + /** + * AssetType we're publishing + */ + readonly assetType: AssetType; + + /** + * Version of CDK CLI to 'npm install'. + * + * @default - Latest version + */ + readonly cdkCliVersion?: string; + + /** + * Name of the CodeBuild project + * + * @default - Automatically generated + */ + readonly projectName?: string; +} + +/** + * Action to publish an asset in the pipeline + * + * Creates a CodeBuild project which will use the CDK CLI + * to prepare and publish the asset. + * + * You do not need to instantiate this action -- it will automatically + * be added by the pipeline when you add stacks that use assets. + */ +export class PublishAssetsAction extends Construct implements codepipeline.IAction { + private readonly action: codepipeline.IAction; + private readonly commands = new Array(); + + constructor(scope: Construct, id: string, private readonly props: PublishAssetsActionProps) { + super(scope, id); + + const installSuffix = props.cdkCliVersion ? `@${props.cdkCliVersion}` : ''; + + const project = new codebuild.PipelineProject(this, 'Default', { + projectName: this.props.projectName, + buildSpec: codebuild.BuildSpec.fromObject({ + version: '0.2', + phases: { + install: { + commands: `npm install -g cdk-assets${installSuffix}`, + }, + build: { + commands: Lazy.listValue({ produce: () => this.commands }), + }, + }, + }), + // Needed to perform Docker builds + environment: props.assetType === AssetType.DOCKER_IMAGE ? { privileged: true } : undefined, + }); + + const rolePattern = props.assetType === AssetType.DOCKER_IMAGE + ? 'arn:*:iam::*:role/*-image-publishing-role-*' + : 'arn:*:iam::*:role/*-file-publishing-role-*'; + + project.addToRolePolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], + resources: [rolePattern], + })); + + this.action = new codepipeline_actions.CodeBuildAction({ + actionName: props.actionName, + project, + input: this.props.cloudAssemblyInput, + }); + } + + /** + * Add a single publishing command + * + * Manifest path should be relative to the root Cloud Assembly. + */ + public addPublishCommand(relativeManifestPath: string, assetSelector: string) { + const command = `cdk-assets --path "${relativeManifestPath}" --verbose publish "${assetSelector}"`; + if (!this.commands.includes(command)) { + this.commands.push(command); + } + } + + /** + * Exists to implement IAction + */ + public bind(scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): + codepipeline.ActionConfig { + return this.action.bind(scope, stage, options); + } + + /** + * Exists to implement IAction + */ + public onStateChange(name: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule { + return this.action.onStateChange(name, target, options); + } + + /** + * Exists to implement IAction + */ + public get actionProperties(): codepipeline.ActionProperties { + // FIXME: I have had to make this class a Construct, because: + // + // - It needs access to the Construct tree, because it is going to add a `PipelineProject`. + // - I would have liked to have done that in bind(), however, + // - `actionProperties` (this method) is called BEFORE bind() is called, and by that point I + // don't have the "inner" Action yet to forward the call to. + // + // I've therefore had to construct the inner CodeBuildAction in the constructor, which requires making this + // Action a Construct. + // + // Combined with how non-intuitive it is to make the "StackDeployAction", I feel there is something + // wrong with the Action abstraction here. + return this.action.actionProperties; + } +} diff --git a/packages/@aws-cdk/pipelines/lib/actions/update-pipeline-action.ts b/packages/@aws-cdk/pipelines/lib/actions/update-pipeline-action.ts new file mode 100644 index 0000000000000..e7b19ac860102 --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/actions/update-pipeline-action.ts @@ -0,0 +1,127 @@ +import * as codebuild from '@aws-cdk/aws-codebuild'; +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as cpactions from '@aws-cdk/aws-codepipeline-actions'; +import * as events from '@aws-cdk/aws-events'; +import * as iam from '@aws-cdk/aws-iam'; +import { Construct } from '@aws-cdk/core'; +import { embeddedAsmPath } from '../private/construct-internals'; + +/** + * Props for the UpdatePipelineAction + */ +export interface UpdatePipelineActionProps { + /** + * The CodePipeline artifact that holds the Cloud Assembly. + */ + readonly cloudAssemblyInput: codepipeline.Artifact; + + /** + * Name of the pipeline stack + */ + readonly pipelineStackName: string; + + /** + * Version of CDK CLI to 'npm install'. + * + * @default - Latest version + */ + readonly cdkCliVersion?: string; + + /** + * Name of the CodeBuild project + * + * @default - Automatically generated + */ + readonly projectName?: string; +} + +/** + * Action to self-mutate the pipeline + * + * Creates a CodeBuild project which will use the CDK CLI + * to deploy the pipeline stack. + * + * You do not need to instantiate this action -- it will automatically + * be added by the pipeline. + */ +export class UpdatePipelineAction extends Construct implements codepipeline.IAction { + private readonly action: codepipeline.IAction; + + constructor(scope: Construct, id: string, props: UpdatePipelineActionProps) { + super(scope, id); + + const installSuffix = props.cdkCliVersion ? `@${props.cdkCliVersion}` : ''; + + const selfMutationProject = new codebuild.PipelineProject(this, 'SelfMutation', { + projectName: props.projectName, + buildSpec: codebuild.BuildSpec.fromObject({ + version: '0.2', + phases: { + install: { + commands: `npm install -g aws-cdk${installSuffix}`, + }, + build: { + commands: [ + // Cloud Assembly is in *current* directory. + `cdk -a ${embeddedAsmPath(scope)} deploy ${props.pipelineStackName} --require-approval=never --verbose`, + ], + }, + }, + }), + }); + + // allow the self-mutating project permissions to assume the bootstrap Action role + selfMutationProject.addToRolePolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], + resources: ['arn:*:iam::*:role/*-deploy-role-*', 'arn:*:iam::*:role/*-publishing-role-*'], + })); + selfMutationProject.addToRolePolicy(new iam.PolicyStatement({ + actions: ['cloudformation:DescribeStacks'], + resources: ['*'], // this is needed to check the status of the bootstrap stack when doing `cdk deploy` + })); + // S3 checks for the presence of the ListBucket permission + selfMutationProject.addToRolePolicy(new iam.PolicyStatement({ + actions: ['s3:ListBucket'], + resources: ['*'], + })); + this.action = new cpactions.CodeBuildAction({ + actionName: 'SelfMutate', + input: props.cloudAssemblyInput, + project: selfMutationProject, + }); + } + + /** + * Exists to implement IAction + */ + public bind(scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): + codepipeline.ActionConfig { + return this.action.bind(scope, stage, options); + } + + /** + * Exists to implement IAction + */ + public onStateChange(name: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule { + return this.action.onStateChange(name, target, options); + } + + /** + * Exists to implement IAction + */ + public get actionProperties(): codepipeline.ActionProperties { + // FIXME: I have had to make this class a Construct, because: + // + // - It needs access to the Construct tree, because it is going to add a `PipelineProject`. + // - I would have liked to have done that in bind(), however, + // - `actionProperties` (this method) is called BEFORE bind() is called, and by that point I + // don't have the "inner" Action yet to forward the call to. + // + // I've therefore had to construct the inner CodeBuildAction in the constructor, which requires making this + // Action a Construct. + // + // Combined with how non-intuitive it is to make the "StackDeployAction", I feel there is something + // wrong with the Action abstraction here. + return this.action.actionProperties; + } +} diff --git a/packages/@aws-cdk/pipelines/lib/index.ts b/packages/@aws-cdk/pipelines/lib/index.ts new file mode 100644 index 0000000000000..dbe8a73291c23 --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/index.ts @@ -0,0 +1,5 @@ +export * from './pipeline'; +export * from './stage'; +export * from './synths'; +export * from './actions'; +export * from './validation'; \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/lib/pipeline.ts b/packages/@aws-cdk/pipelines/lib/pipeline.ts new file mode 100644 index 0000000000000..75fb655560c6b --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/pipeline.ts @@ -0,0 +1,313 @@ +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import { App, CfnOutput, Construct, Stack, Stage } from '@aws-cdk/core'; +import * as path from 'path'; +import { AssetType, DeployCdkStackAction, PublishAssetsAction, UpdatePipelineAction } from './actions'; +import { appOf, assemblyBuilderOf } from './private/construct-internals'; +import { AddStageOptions, AssetPublishingCommand, CdkStage, StackOutput } from './stage'; + +/** + * Properties for a CdkPipeline + */ +export interface CdkPipelineProps { + /** + * The CodePipeline action used to retrieve the CDK app's source + */ + readonly sourceAction: codepipeline.IAction; + + /** + * The CodePipeline action build and synthesis step of the CDK app + */ + readonly synthAction: codepipeline.IAction; + + /** + * The artifact you have defined to be the artifact to hold the cloudAssemblyArtifact for the synth action + */ + readonly cloudAssemblyArtifact: codepipeline.Artifact; + + /** + * Name of the pipeline + * + * @default - A name is automatically generated + */ + readonly pipelineName?: string; + + /** + * CDK CLI version to use in pipeline + * + * Some Actions in the pipeline will download and run a version of the CDK + * CLI. Specify the version here. + * + * @default - Latest version + */ + readonly cdkCliVersion?: string; +} + +/** + * A Pipeline to deploy CDK apps + * + * Defines an AWS CodePipeline-based Pipeline to deploy CDK applications. + * + * Automatically manages the following: + * + * - Stack dependency order. + * - Asset publishing. + * - Keeping the pipeline up-to-date as the CDK apps change. + * - Using stack outputs later on in the pipeline. + */ +export class CdkPipeline extends Construct { + private readonly _pipeline: codepipeline.Pipeline; + private readonly _assets: AssetPublishing; + private readonly _stages: CdkStage[] = []; + private readonly _outputArtifacts: Record = {}; + private readonly _cloudAssemblyArtifact: codepipeline.Artifact; + + constructor(scope: Construct, id: string, props: CdkPipelineProps) { + super(scope, id); + + if (!App.isApp(this.node.root)) { + throw new Error('CdkPipeline must be created under an App'); + } + + this._cloudAssemblyArtifact = props.cloudAssemblyArtifact; + const pipelineStack = Stack.of(this); + + this._pipeline = new codepipeline.Pipeline(this, 'Pipeline', { + ...props, + restartExecutionOnUpdate: true, + stages: [ + { + stageName: 'Source', + actions: [props.sourceAction], + }, + { + stageName: 'Build', + actions: [props.synthAction], + }, + { + stageName: 'UpdatePipeline', + actions: [new UpdatePipelineAction(this, 'UpdatePipeline', { + cloudAssemblyInput: this._cloudAssemblyArtifact, + pipelineStackName: pipelineStack.stackName, + cdkCliVersion: props.cdkCliVersion, + projectName: maybeSuffix(props.pipelineName, '-selfupdate'), + })], + }, + ], + }); + + this._assets = new AssetPublishing(this, 'Assets', { + cloudAssemblyInput: this._cloudAssemblyArtifact, + cdkCliVersion: props.cdkCliVersion, + pipeline: this._pipeline, + projectName: maybeSuffix(props.pipelineName, '-publish'), + }); + } + + /** + * Add pipeline stage that will deploy the given application stage + * + * The application construct should subclass `Stage` and can contain any + * number of `Stacks` inside it that may have dependency relationships + * on one another. + * + * All stacks in the application will be deployed in the appropriate order, + * and all assets found in the application will be added to the asset + * publishing stage. + */ + public addApplicationStage(appStage: Stage, options: AddStageOptions = {}): CdkStage { + const stage = this.addStage(appStage.stageName); + stage.addApplication(appStage, options); + return stage; + } + + /** + * Add a new, empty stage to the pipeline + * + * Prefer to use `addApplicationStage` if you are intended to deploy a CDK + * application, but you can use this method if you want to add other kinds of + * Actions to a pipeline. + */ + public addStage(stageName: string) { + const pipelineStage = this._pipeline.addStage({ + stageName, + }); + + const stage = new CdkStage(this, stageName, { + cloudAssemblyArtifact: this._cloudAssemblyArtifact, + pipelineStage, + stageName, + host: { + publishAsset: this._assets.addPublishAssetAction.bind(this._assets), + stackOutputArtifact: (artifactId) => this._outputArtifacts[artifactId], + }, + }); + this._stages.push(stage); + return stage; + } + + /** + * Get the StackOutput object that holds this CfnOutput's value in this pipeline + * + * `StackOutput` can be used in validation actions later in the pipeline. + */ + public stackOutput(cfnOutput: CfnOutput): StackOutput { + const stack = Stack.of(cfnOutput); + + if (!this._outputArtifacts[stack.artifactId]) { + // We should have stored the ArtifactPath in the map, but its Artifact + // property isn't publicly readable... + this._outputArtifacts[stack.artifactId] = new codepipeline.Artifact(`Artifact_${stack.artifactId}_Outputs`); + } + + return new StackOutput(this._outputArtifacts[stack.artifactId].atPath('outputs.json'), cfnOutput.logicalId); + } + + /** + * Validate that we don't have any stacks violating dependency order in the pipeline + * + * Our own convenience methods will never generate a pipeline that does that (although + * this is a nice verification), but a user can also add the stacks by hand. + */ + protected validate(): string[] { + const ret = new Array(); + + ret.push(...this.validateDeployOrder()); + ret.push(...this.validateRequestedOutputs()); + + return ret; + } + + protected onPrepare() { + super.onPrepare(); + + // TODO: Support this in a proper way in the upstream library. For now, we + // "un-add" the Assets stage if it turns out to be empty. + this._assets.removeAssetsStageIfEmpty(); + } + + /** + * Return all StackDeployActions in an ordered list + */ + private get stackActions(): DeployCdkStackAction[] { + return flatMap(this._pipeline.stages, s => s.actions.filter(isDeployAction)); + } + + private* validateDeployOrder(): IterableIterator { + const stackActions = this.stackActions; + for (const stackAction of stackActions) { + // For every dependency, it must be executed in an action before this one is prepared. + for (const depId of stackAction.dependencyStackArtifactIds) { + const depAction = stackActions.find(s => s.stackArtifactId === depId); + + if (depAction === undefined) { + this.node.addWarning(`Stack '${stackAction.stackName}' depends on stack ` + + `'${depId}', but that dependency is not deployed through the pipeline!`); + } else if (!(depAction.executeRunOrder < stackAction.prepareRunOrder)) { + yield `Stack '${stackAction.stackName}' depends on stack ` + + `'${depAction.stackName}', but is deployed before it in the pipeline!`; + } + } + } + } + + private* validateRequestedOutputs(): IterableIterator { + const artifactIds = this.stackActions.map(s => s.stackArtifactId); + + for (const artifactId of Object.keys(this._outputArtifacts)) { + if (!artifactIds.includes(artifactId)) { + yield `Trying to use outputs for Stack '${artifactId}', but Stack is not deployed in this pipeline. Add it to the pipeline.`; + } + } + } +} + +function isDeployAction(a: codepipeline.IAction): a is DeployCdkStackAction { + return a instanceof DeployCdkStackAction; +} + +function flatMap(xs: A[], f: (x: A) => B[]): B[] { + return Array.prototype.concat([], ...xs.map(f)); +} + +interface AssetPublishingProps { + readonly cloudAssemblyInput: codepipeline.Artifact; + readonly pipeline: codepipeline.Pipeline; + readonly cdkCliVersion?: string; + readonly projectName?: string; +} + +/** + * Add appropriate publishing actions to the asset publishing stage + */ +class AssetPublishing extends Construct { + private readonly publishers: Record = {}; + private readonly myCxAsmRoot: string; + + private readonly stage: codepipeline.IStage; + private _fileAssetCtr = 1; + private _dockerAssetCtr = 1; + + constructor(scope: Construct, id: string, private readonly props: AssetPublishingProps) { + super(scope, id); + this.myCxAsmRoot = path.resolve(assemblyBuilderOf(appOf(this)).outdir); + + // We MUST add the Stage immediately here, otherwise it will be in the wrong place + // in the pipeline! + this.stage = this.props.pipeline.addStage({ stageName: 'Assets' }); + } + + /** + * Make sure there is an action in the stage to publish the given asset + * + * Assets are grouped by asset ID (which represent individual assets) so all assets + * are published in parallel. For each assets, all destinations are published sequentially + * so that we can reuse expensive operations between them (mostly: building a Docker image). + */ + public addPublishAssetAction(command: AssetPublishingCommand) { + // FIXME: this is silly, we need the relative path here but no easy way to get it + const relativePath = path.relative(this.myCxAsmRoot, command.assetManifestPath); + + let action = this.publishers[command.assetId]; + if (!action) { + // The asset ID would be a logical candidate for the construct path and project names, but if the asset + // changes it leads to recreation of a number of Role/Policy/Project resources which is slower than + // necessary. Number sequentially instead. + // + // FIXME: The ultimate best solution is probably to generate a single Project per asset type + // and reuse that for all assets. + + const id = command.assetType === AssetType.FILE ? `FileAsset${this._fileAssetCtr++}` : `DockerAsset${this._dockerAssetCtr++}`; + + action = this.publishers[command.assetId] = new PublishAssetsAction(this, id, { + actionName: command.assetId, + cloudAssemblyInput: this.props.cloudAssemblyInput, + cdkCliVersion: this.props.cdkCliVersion, + assetType: command.assetType, + }); + this.stage.addAction(action); + } + + action.addPublishCommand(relativePath, command.assetSelector); + } + + /** + * Remove the Assets stage if it turns out we didn't add any Assets to publish + */ + public removeAssetsStageIfEmpty() { + if (Object.keys(this.publishers).length === 0) { + // Hacks to get access to innards of Pipeline + // Modify 'stages' array in-place to remove Assets stage if empty + const stages: codepipeline.IStage[] = (this.props.pipeline as any)._stages; + + const ix = stages.indexOf(this.stage); + if (ix > -1) { + stages.splice(ix, 1); + } + } + } +} + +function maybeSuffix(x: string | undefined, suffix: string): string | undefined { + if (x === undefined) { return undefined; } + return `${x}${suffix}`; +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/lib/private/asset-manifest.ts b/packages/@aws-cdk/pipelines/lib/private/asset-manifest.ts new file mode 100644 index 0000000000000..752c7c242bc48 --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/private/asset-manifest.ts @@ -0,0 +1,296 @@ +// FIXME: copied from `ckd-assets`, because this tool needs to read the asset manifest aswell. +import { AssetManifest, DockerImageDestination, DockerImageSource, FileDestination, FileSource, Manifest } from '@aws-cdk/cloud-assembly-schema'; +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * A manifest of assets + */ +export class AssetManifestReader { + /** + * The default name of the asset manifest in a cdk.out directory + */ + public static readonly DEFAULT_FILENAME = 'assets.json'; + + /** + * Load an asset manifest from the given file + */ + public static fromFile(fileName: string) { + try { + const obj = Manifest.loadAssetManifest(fileName); + + return new AssetManifestReader(path.dirname(fileName), obj); + } catch (e) { + throw new Error(`Canot read asset manifest '${fileName}': ${e.message}`); + } + } + + /** + * Load an asset manifest from the given file or directory + * + * If the argument given is a directoy, the default asset file name will be used. + */ + public static fromPath(filePath: string) { + let st; + try { + st = fs.statSync(filePath); + } catch (e) { + throw new Error(`Cannot read asset manifest at '${filePath}': ${e.message}`); + } + if (st.isDirectory()) { + return AssetManifestReader.fromFile(path.join(filePath, AssetManifestReader.DEFAULT_FILENAME)); + } + return AssetManifestReader.fromFile(filePath); + } + + /** + * The directory where the manifest was found + */ + public readonly directory: string; + + constructor(directory: string, private readonly manifest: AssetManifest) { + this.directory = directory; + } + + /** + * Select a subset of assets and destinations from this manifest. + * + * Only assets with at least 1 selected destination are retained. + * + * If selection is not given, everything is returned. + */ + public select(selection?: DestinationPattern[]): AssetManifestReader { + if (selection === undefined) { return this; } + + const ret: AssetManifest & Required> + = { version: this.manifest.version, dockerImages: {}, files: {} }; + + for (const assetType of ASSET_TYPES) { + for (const [assetId, asset] of Object.entries(this.manifest[assetType] || {})) { + const filteredDestinations = filterDict( + asset.destinations, + (_, destId) => selection.some(sel => sel.matches(new DestinationIdentifier(assetId, destId)))); + + if (Object.keys(filteredDestinations).length > 0) { + ret[assetType][assetId] = { + ...asset, + destinations: filteredDestinations, + }; + } + } + } + + return new AssetManifestReader(this.directory, ret); + } + + /** + * Describe the asset manifest as a list of strings + */ + public list() { + return [ + ...describeAssets('file', this.manifest.files || {}), + ...describeAssets('docker-image', this.manifest.dockerImages || {}), + ]; + + function describeAssets(type: string, assets: Record }>) { + const ret = new Array(); + for (const [assetId, asset] of Object.entries(assets || {})) { + ret.push(`${assetId} ${type} ${JSON.stringify(asset.source)}`); + + const destStrings = Object.entries(asset.destinations).map(([destId, dest]) => ` ${assetId}:${destId} ${JSON.stringify(dest)}`); + ret.push(...prefixTreeChars(destStrings, ' ')); + } + return ret; + } + } + + /** + * List of assets, splat out to destinations + */ + public get entries(): IManifestEntry[] { + return [ + ...makeEntries(this.manifest.files || {}, FileManifestEntry), + ...makeEntries(this.manifest.dockerImages || {}, DockerImageManifestEntry), + ]; + + function makeEntries( + assets: Record }>, + ctor: new (id: DestinationIdentifier, source: A, destination: B) => C): C[] { + + const ret = new Array(); + for (const [assetId, asset] of Object.entries(assets)) { + for (const [destId, destination] of Object.entries(asset.destinations)) { + ret.push(new ctor(new DestinationIdentifier(assetId, destId), asset.source, destination)); + } + } + return ret; + } + } +} + +type AssetType = 'files' | 'dockerImages'; + +const ASSET_TYPES: AssetType[] = ['files', 'dockerImages']; + +/** + * A single asset from an asset manifest' + */ +export interface IManifestEntry { + /** + * The identifier of the asset + */ + readonly id: DestinationIdentifier; + + /** + * The type of asset + */ + readonly type: string; + + /** + * Type-dependent source data + */ + readonly genericSource: unknown; + + /** + * Type-dependent destination data + */ + readonly genericDestination: unknown; +} + +/** + * A manifest entry for a file asset + */ +export class FileManifestEntry implements IManifestEntry { + public readonly genericSource: unknown; + public readonly genericDestination: unknown; + public readonly type = 'file'; + + constructor( + /** Identifier for this asset */ + public readonly id: DestinationIdentifier, + /** Source of the file asset */ + public readonly source: FileSource, + /** Destination for the file asset */ + public readonly destination: FileDestination, + ) { + this.genericSource = source; + this.genericDestination = destination; + } +} + +/** + * A manifest entry for a docker image asset + */ +export class DockerImageManifestEntry implements IManifestEntry { + public readonly genericSource: unknown; + public readonly genericDestination: unknown; + public readonly type = 'docker-image'; + + constructor( + /** Identifier for this asset */ + public readonly id: DestinationIdentifier, + /** Source of the file asset */ + public readonly source: DockerImageSource, + /** Destination for the file asset */ + public readonly destination: DockerImageDestination, + ) { + this.genericSource = source; + this.genericDestination = destination; + } +} + +/** + * Identify an asset destination in an asset manifest + */ +export class DestinationIdentifier { + /** + * Identifies the asset, by source. + */ + public readonly assetId: string; + + /** + * Identifies the destination where this asset will be published + */ + public readonly destinationId: string; + + constructor(assetId: string, destinationId: string) { + this.assetId = assetId; + this.destinationId = destinationId; + } + + /** + * Return a string representation for this asset identifier + */ + public toString() { + return this.destinationId ? `${this.assetId}:${this.destinationId}` : this.assetId; + } +} + +function filterDict(xs: Record, pred: (x: A, key: string) => boolean): Record { + const ret: Record = {}; + for (const [key, value] of Object.entries(xs)) { + if (pred(value, key)) { + ret[key] = value; + } + } + return ret; +} + +/** + * A filter pattern for an destination identifier + */ +export class DestinationPattern { + /** + * Parse a ':'-separated string into an asset/destination identifier + */ + public static parse(s: string) { + if (!s) { throw new Error('Empty string is not a valid destination identifier'); } + const parts = s.split(':').map(x => x !== '*' ? x : undefined); + if (parts.length === 1) { return new DestinationPattern(parts[0]); } + if (parts.length === 2) { return new DestinationPattern(parts[0] || undefined, parts[1] || undefined); } + throw new Error(`Asset identifier must contain at most 2 ':'-separated parts, got '${s}'`); + } + + /** + * Identifies the asset, by source. + */ + public readonly assetId?: string; + + /** + * Identifies the destination where this asset will be published + */ + public readonly destinationId?: string; + + constructor(assetId?: string, destinationId?: string) { + this.assetId = assetId; + this.destinationId = destinationId; + } + + /** + * Whether or not this pattern matches the given identifier + */ + public matches(id: DestinationIdentifier) { + return (this.assetId === undefined || this.assetId === id.assetId) + && (this.destinationId === undefined || this.destinationId === id.destinationId); + } + + /** + * Return a string representation for this asset identifier + */ + public toString() { + return `${this.assetId ?? '*'}:${this.destinationId ?? '*'}`; + } +} + +/** + * Prefix box-drawing characters to make lines look like a hanging tree + */ +function prefixTreeChars(xs: string[], prefix = '') { + const ret = new Array(); + for (let i = 0; i < xs.length; i++) { + const isLast = i === xs.length - 1; + const boxChar = isLast ? '└' : '├'; + ret.push(`${prefix}${boxChar}${xs[i]}`); + } + return ret; +} diff --git a/packages/@aws-cdk/pipelines/lib/private/construct-internals.ts b/packages/@aws-cdk/pipelines/lib/private/construct-internals.ts new file mode 100644 index 0000000000000..13b1ac7c1dd0c --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/private/construct-internals.ts @@ -0,0 +1,37 @@ +/** + * Get access to construct internals that we need but got removed from the Stages PR. + */ +import { App, IConstruct, Stage } from '@aws-cdk/core'; +import * as cxapi from '@aws-cdk/cx-api'; +import * as path from 'path'; + +export function appOf(construct: IConstruct): App { + const root = construct.node.root; + + if (!App.isApp(root)) { + throw new Error(`Construct must be created under an App, but is not: ${construct.node.path}`); + } + + return root; +} + +export function assemblyBuilderOf(stage: Stage): cxapi.CloudAssemblyBuilder { + return (stage as any)._assemblyBuilder; +} + +/** + * Return the relative path from the app assembly to the scope's (nested) assembly + */ +export function embeddedAsmPath(scope: IConstruct) { + const appAsmRoot = assemblyBuilderOf(appOf(scope)).outdir; + const stage = Stage.of(scope) ?? appOf(scope); + const stageAsmRoot = assemblyBuilderOf(stage).outdir; + return path.relative(appAsmRoot, stageAsmRoot) || '.'; +} + +/** + * Determine the directory where the cloud assembly will be written, for use in a BuildSpec + */ +export function cloudAssemblyBuildSpecDir(scope: IConstruct) { + return assemblyBuilderOf(appOf(scope)).outdir; +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/lib/private/toposort.ts b/packages/@aws-cdk/pipelines/lib/private/toposort.ts new file mode 100644 index 0000000000000..8386a6d26bb82 --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/private/toposort.ts @@ -0,0 +1,47 @@ +export type KeyFunc = (x: T) => string; +export type DepFunc = (x: T) => string[]; + +/** + * Return a topological sort of all elements of xs, according to the given dependency functions + * + * Dependencies outside the referenced set are ignored. + * + * Not a stable sort, but in order to keep the order as stable as possible, we'll sort by key + * among elements of equal precedence. + * + * Returns tranches of elements of equal precedence. + */ +export function topologicalSort(xs: Iterable, keyFn: KeyFunc, depFn: DepFunc): T[][] { + const remaining = new Map>(); + for (const element of xs) { + const key = keyFn(element); + remaining.set(key, { key, element, dependencies: depFn(element) }); + } + + const ret = new Array(); + while (remaining.size > 0) { + // All elements with no more deps in the set can be ordered + const selectable = Array.from(remaining.values()).filter(e => e.dependencies.every(d => !remaining.has(d))); + + selectable.sort((a, b) => a.key < b.key ? -1 : b.key < a.key ? 1 : 0); + + // If we didn't make any progress, we got stuck + if (selectable.length === 0) { + throw new Error(`Could not determine ordering between: ${Array.from(remaining.keys()).join(', ')}`); + } + + ret.push(selectable.map(s => s.element)); + + for (const selected of selectable) { + remaining.delete(selected.key); + } + } + + return ret; +} + +interface TopoElement { + key: string; + dependencies: string[]; + element: T; +} diff --git a/packages/@aws-cdk/pipelines/lib/stage.ts b/packages/@aws-cdk/pipelines/lib/stage.ts new file mode 100644 index 0000000000000..2441da072cede --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/stage.ts @@ -0,0 +1,388 @@ +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as cpactions from '@aws-cdk/aws-codepipeline-actions'; +import { Construct, Stage } from '@aws-cdk/core'; +import * as cxapi from '@aws-cdk/cx-api'; +import { AssetType, DeployCdkStackAction } from './actions'; +import { AssetManifestReader, DockerImageManifestEntry, FileManifestEntry } from './private/asset-manifest'; +import { topologicalSort } from './private/toposort'; + +/** + * Construction properties for a CdkStage + */ +export interface CdkStageProps { + /** + * Name of the stage that should be created + */ + readonly stageName: string; + + /** + * The underlying Pipeline Stage associated with thisCdkStage + */ + readonly pipelineStage: codepipeline.IStage; + + /** + * The CodePipeline Artifact with the Cloud Assembly + */ + readonly cloudAssemblyArtifact: codepipeline.Artifact; + + /** + * Features the Stage needs from its environment + */ + readonly host: IStageHost; +} + +/** + * Stage in a CdkPipeline + * + * You don't need to instantiate this class directly. Use + * `cdkPipeline.addStage()` instead. + */ +export class CdkStage extends Construct { + private _nextSequentialRunOrder = 1; // Must start at 1 eh + private _manualApprovalCounter = 1; + private readonly pipelineStage: codepipeline.IStage; + private readonly cloudAssemblyArtifact: codepipeline.Artifact; + private readonly stacksToDeploy = new Array(); + private readonly stageName: string; + private readonly host: IStageHost; + private _prepared = false; + + constructor(scope: Construct, id: string, props: CdkStageProps) { + super(scope, id); + + this.stageName = props.stageName; + this.pipelineStage = props.pipelineStage; + this.cloudAssemblyArtifact = props.cloudAssemblyArtifact; + this.host = props.host; + } + + /** + * Add all stacks in the application Stage to this stage + * + * The application construct should subclass `Stage` and can contain any + * number of `Stacks` inside it that may have dependency relationships + * on one another. + * + * All stacks in the application will be deployed in the appropriate order, + * and all assets found in the application will be added to the asset + * publishing stage. + */ + public addApplication(appStage: Stage, options: AddStageOptions = {}) { + const asm = appStage.synth(); + + const sortedTranches = topologicalSort(asm.stacks, + stack => stack.id, + stack => stack.dependencies.map(d => d.id)); + + for (const stacks of sortedTranches) { + const runOrder = this.nextSequentialRunOrder(2); // We need 2 actions + let executeRunOrder = runOrder + 1; + + // If we need to insert a manual approval action, then what's the executeRunOrder + // now is where we add a manual approval step, and we allocate 1 more runOrder + // for the execute. + if (options.manualApprovals) { + this.addManualApprovalAction({ runOrder: executeRunOrder }); + executeRunOrder = this.nextSequentialRunOrder(); + } + + // These don't have a dependency on each other, so can all be added in parallel + for (const stack of stacks) { + this.addStackArtifactDeployment(stack, { runOrder, executeRunOrder }); + } + } + } + + /** + * Add a deployment action based on a stack artifact + */ + public addStackArtifactDeployment(stackArtifact: cxapi.CloudFormationStackArtifact, options: AddStackOptions = {}) { + // Get all assets manifests and add the assets in 'em to the asset publishing stage. + this.publishAssetDependencies(stackArtifact); + + // Remember for later, see 'prepare()' + // We know that deploying a stack is going to take up 2 runorder slots later on. + const runOrder = options.runOrder ?? this.nextSequentialRunOrder(2); + const executeRunOrder = options.executeRunOrder ?? runOrder + 1; + this.stacksToDeploy.push({ + prepareRunOrder: runOrder, + executeRunOrder, + stackArtifact, + }); + + this.advanceRunOrderPast(runOrder); + this.advanceRunOrderPast(executeRunOrder); + } + + /** + * Add a manual approval action + * + * If you need more flexibility than what this method offers, + * use `addAction` with a `ManualApprovalAction`. + */ + public addManualApprovalAction(options: AddManualApprovalOptions = {}) { + let actionName = options.actionName; + if (!actionName) { + actionName = `ManualApproval${this._manualApprovalCounter > 1 ? this._manualApprovalCounter : ''}`; + this._manualApprovalCounter += 1; + } + + this.addActions(new cpactions.ManualApprovalAction({ + actionName, + runOrder: options.runOrder ?? this.nextSequentialRunOrder(), + })); + } + + /** + * Add one or more CodePipeline Actions + * + * You need to make sure it is created with the right runOrder. Call `nextSequentialRunOrder()` + * for every action to get actions to execute in sequence. + */ + public addActions(...actions: codepipeline.IAction[]) { + for (const action of actions) { + this.pipelineStage.addAction(action); + } + } + + /** + * Return the runOrder number necessary to run the next Action in sequence with the rest + * + * FIXME: This is here because Actions are immutable and can't be reordered + * after creation, nor is there a way to specify relative priorities, which + * is a limitation that we should take away in the base library. + */ + public nextSequentialRunOrder(count: number = 1): number { + const ret = this._nextSequentialRunOrder; + this._nextSequentialRunOrder += count; + return ret; + } + + /** + * Whether this Stage contains an action to deploy the given stack, identified by its artifact ID + */ + public deploysStack(artifactId: string) { + return this.stacksToDeploy.map(s => s.stackArtifact.id).includes(artifactId); + } + + /** + * Actually add all the DeployStack actions to the stage. + * + * We do this late because before we can render the actual DeployActions, + * we need to know whether or not we need to capture the stack outputs. + * + * FIXME: This is here because Actions are immutable and can't be reordered + * after creation, nor is there a way to specify relative priorities, which + * is a limitation that we should take away in the base library. + */ + protected prepare() { + // FIXME: Make sure this only gets run once. There seems to be an issue in the reconciliation + // loop that may trigger this more than once if it throws an error somewhere, and the exception + // that gets thrown here will then override the actual failure. + if (this._prepared) { return; } + this._prepared = true; + + for (const { prepareRunOrder: runOrder, stackArtifact } of this.stacksToDeploy) { + const artifact = this.host.stackOutputArtifact(stackArtifact.id); + + this.pipelineStage.addAction(DeployCdkStackAction.fromStackArtifact(this, stackArtifact, { + baseActionName: this.simplifyStackName(stackArtifact.stackName), + cloudAssemblyInput: this.cloudAssemblyArtifact, + output: artifact, + outputFileName: artifact ? 'outputs.json' : undefined, + prepareRunOrder: runOrder, + })); + } + } + + /** + * Advance the runorder counter so that the next sequential number is higher than the given one + */ + private advanceRunOrderPast(lastUsed: number) { + this._nextSequentialRunOrder = Math.max(lastUsed + 1, this._nextSequentialRunOrder); + } + + /** + * Simplify the stack name by removing the `Stage-` prefix if it exists. + */ + private simplifyStackName(s: string) { + return stripPrefix(s, `${this.stageName}-`); + } + + /** + * Make sure all assets depended on by this stack are published in this pipeline + * + * Taking care to exclude the stack template itself -- it is being published + * as an asset because the CLI needs to know the asset publishing role when + * pushing the template to S3, but in the case of CodePipeline we always + * reference the template from the artifact bucket. + * + * (NOTE: this is only true for top-level stacks, not nested stacks. Nested + * Stack templates are always published as assets). + */ + private publishAssetDependencies(stackArtifact: cxapi.CloudFormationStackArtifact) { + const assetManifests = stackArtifact.dependencies.filter(isAssetManifest); + + for (const manifestArtifact of assetManifests) { + const manifest = AssetManifestReader.fromFile(manifestArtifact.file); + + for (const entry of manifest.entries) { + let assetType: AssetType; + if (entry instanceof DockerImageManifestEntry) { + assetType = AssetType.DOCKER_IMAGE; + } else if (entry instanceof FileManifestEntry) { + // Don't publishg the template for this stack + if (entry.source.packaging === 'file' && entry.source.path === stackArtifact.templateFile) { + continue; + } + + assetType = AssetType.FILE; + } else { + throw new Error(`Unrecognized asset type: ${entry.type}`); + } + + this.host.publishAsset({ + assetManifestPath: manifestArtifact.file, + assetId: entry.id.assetId, + assetSelector: entry.id.toString(), + assetType, + }); + } + } + } +} + +/** + * Additional options for adding a stack deployment + */ +export interface AddStackOptions { + /** + * Base runorder + * + * @default - Next sequential runorder + */ + readonly runOrder?: number; + + /** + * Base runorder + * + * @default - runOrder + 1 + */ + readonly executeRunOrder?: number; +} + +/** + * A single output of a Stack + */ +export class StackOutput { + /** + * The artifact and file the output is stored in + */ + public readonly artifactFile: codepipeline.ArtifactPath; + + /** + * The name of the output in the JSON object in the file + */ + public readonly outputName: string; + + /** + * Build a StackOutput from a known artifact and an output name + */ + constructor(artifactFile: codepipeline.ArtifactPath, outputName: string) { + this.artifactFile = artifactFile; + this.outputName = outputName; + } +} + +function stripPrefix(s: string, prefix: string) { + return s.startsWith(prefix) ? s.substr(prefix.length) : s; +} + +function isAssetManifest(s: cxapi.CloudArtifact): s is cxapi.AssetManifestArtifact { + // instanceof is too risky, and we're at a too late stage to properly fix. + // return s instanceof cxapi.AssetManifestArtifact; + return s.constructor.name === 'AssetManifestArtifact'; +} + +/** + * Features that the Stage needs from its environment + */ +export interface IStageHost { + /** + * Make sure all the assets from the given manifest are published + */ + publishAsset(command: AssetPublishingCommand): void; + + /** + * Return the Artifact the given stack has to emit its outputs into, if any + */ + stackOutputArtifact(stackArtifactId: string): codepipeline.Artifact | undefined; +} + +/** + * Instructions to publish certain assets + */ +export interface AssetPublishingCommand { + /** + * Asset manifest path + */ + readonly assetManifestPath: string; + + /** + * Asset identifier + */ + readonly assetId: string; + + /** + * Asset selector to pass to `cdk-assets`. + */ + readonly assetSelector: string; + + /** + * Type of asset to publish + */ + readonly assetType: AssetType; +} + +/** + * Options for adding an application stage to a pipeline + */ +export interface AddStageOptions { + /** + * Add manual approvals before executing change sets + * + * This gives humans the opportunity to confirm the change set looks alright + * before deploying it. + * + * @default false + */ + readonly manualApprovals?: boolean; +} + +/** + * Options for addManualApproval + */ +export interface AddManualApprovalOptions { + /** + * The name of the manual approval action + * + * @default 'ManualApproval' with a rolling counter + */ + readonly actionName?: string; + + /** + * The runOrder for this action + * + * @default - The next sequential runOrder + */ + readonly runOrder?: number; +} + +/** + * Queued "deploy stack" command that is reified during prepare() + */ +interface DeployStackCommand { + prepareRunOrder: number; + executeRunOrder: number; + stackArtifact: cxapi.CloudFormationStackArtifact; +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/lib/synths/_util.ts b/packages/@aws-cdk/pipelines/lib/synths/_util.ts new file mode 100644 index 0000000000000..83f83bc802564 --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/synths/_util.ts @@ -0,0 +1,15 @@ +import * as codebuild from '@aws-cdk/aws-codebuild'; + +export function copyEnvironmentVariables(...names: string[]): Record { + const ret: Record = {}; + for (const name of names) { + if (process.env[name]) { + ret[name] = { value: process.env[name] }; + } + } + return ret; +} + +export function filterEmpty(xs: Array): string[] { + return xs.filter(x => x) as any; +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/lib/synths/index.ts b/packages/@aws-cdk/pipelines/lib/synths/index.ts new file mode 100644 index 0000000000000..4764f7d9647c6 --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/synths/index.ts @@ -0,0 +1 @@ +export * from './simple-synth-action'; \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts b/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts new file mode 100644 index 0000000000000..bebbed0f9f44d --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts @@ -0,0 +1,353 @@ +import * as codebuild from '@aws-cdk/aws-codebuild'; +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions'; +import * as events from '@aws-cdk/aws-events'; +import { Construct } from '@aws-cdk/core'; +import * as path from 'path'; +import { cloudAssemblyBuildSpecDir } from '../private/construct-internals'; +import { copyEnvironmentVariables, filterEmpty } from './_util'; + +/** + * Configuration options for a SimpleSynth + */ +export interface SimpleSynthOptions { + /** + * The source artifact of the CodePipeline + */ + readonly sourceArtifact: codepipeline.Artifact; + + /** + * The artifact where the CloudAssembly should be emitted + */ + readonly cloudAssemblyArtifact: codepipeline.Artifact; + + /** + * Environment variables to send into build + * + * @default - No additional environment variables + */ + readonly environmentVariables?: Record; + + /** + * Environment variables to copy over from parent env + * + * These are environment variables that are being used by the build. + * + * @default - No environment variables copied + */ + readonly copyEnvironmentVariables?: string[]; + + /** + * Name of the build action + * + * @default 'Synth' + */ + readonly actionName?: string; + + /** + * Name of the CodeBuild project + * + * @default - Automatically generated + */ + readonly projectName?: string; + + /** + * Build environment to use for CodeBuild job + * + * @default BuildEnvironment.LinuxBuildImage.STANDARD_1_0 + */ + readonly environment?: codebuild.BuildEnvironment; + + /** + * Directory inside the source where package.json and cdk.json are located + * + * @default - Repository root + */ + readonly subdirectory?: string; + + /** + * Produce additional output artifacts after the build based on the given directories + * + * Can be used to produce additional artifacts during the build step, + * separate from the cloud assembly, which can be used further on in the + * pipeline. + * + * Directories are evaluated with respect to `subdirectory`. + * + * @default - No additional artifacts generated + */ + readonly additionalArtifacts?: AdditionalArtifact[]; +} + +/** + * Construction props for SimpleSynthAction + */ +export interface SimpleSynthActionProps extends SimpleSynthOptions { + /** + * The synth command + */ + readonly synthCommand: string; + + /** + * The install command + * + * @default - No install required + */ + readonly installCommand?: string; + + /** + * The build command + * + * By default, we assume NPM projects are either written in JavaScript or are + * using `ts-node`, so don't need a build command. + * + * Otherwise, put the build command here, for example `npm run build`. + * + * @default - No build required + */ + readonly buildCommand?: string; +} + +/** + * Specification of an additional artifact to generate + */ +export interface AdditionalArtifact { + /** + * Directory to be packaged + */ + readonly directory: string; + + /** + * Artifact to represent the build directory in the pipeline + */ + readonly artifact: codepipeline.Artifact; +} + +/** + * A standard synth with a generated buildspec + */ +export class SimpleSynthAction implements codepipeline.IAction { + + /** + * Create a standard NPM synth action + * + * Uses `npm ci` to install dependencies and `npx cdk synth` to synthesize. + * + * If you need a build step, add `buildCommand: 'npm run build'`. + */ + public static standardNpmSynth(options: StandardNpmSynthOptions) { + return new SimpleSynthAction({ + ...options, + installCommand: options.installCommand ?? 'npm ci', + synthCommand: options.synthCommand ?? 'npx cdk synth', + }); + } + + /** + * Create a standard Yarn synth action + * + * Uses `yarn install --frozen-lockfile` to install dependencies and `npx cdk synth` to synthesize. + * + * If you need a build step, add `buildCommand: 'yarn build'`. + */ + public static standardYarnSynth(options: StandardYarnSynthOptions) { + return new SimpleSynthAction({ + ...options, + installCommand: options.synthCommand ?? 'yarn install --frozen-lockfile', + synthCommand: options.synthCommand ?? 'npx cdk synth', + }); + } + + private _action?: codepipeline_actions.CodeBuildAction; + private _actionProperties: codepipeline.ActionProperties; + + constructor(private readonly props: SimpleSynthActionProps) { + // A number of actionProperties get read before bind() is even called (so before we + // have made the Project and can construct the actual CodeBuildAction) + // + // - actionName + // - resource + // - region + // - category + // - role + // - owner + this._actionProperties = { + actionName: props.actionName ?? 'Synth', + category: codepipeline.ActionCategory.BUILD, + provider: 'CodeBuild', + artifactBounds: { minInputs: 0, maxInputs: 5, minOutputs: 0, maxOutputs: 5 }, + inputs: [props.sourceArtifact], + outputs: [props.cloudAssemblyArtifact, ...(props.additionalArtifacts ?? []).map(a => a.artifact)], + }; + + const addls = props.additionalArtifacts ?? []; + if (Object.keys(addls).length > 0) { + if (!props.cloudAssemblyArtifact.artifactName) { + throw new Error('You must give all output artifacts, including the \'cloudAssemblyArtifact\', names when using \'additionalArtifacts\''); + } + for (const addl of addls) { + if (!addl.artifact.artifactName) { + throw new Error('You must give all output artifacts passed to SimpleSynthAction names when using \'additionalArtifacts\''); + } + } + } + } + + /** + * Exists to implement IAction + */ + public get actionProperties(): codepipeline.ActionProperties { + return this._actionProperties; + } + + /** + * Exists to implement IAction + */ + public bind(scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): codepipeline.ActionConfig { + const buildCommand = this.props.buildCommand; + const synthCommand = this.props.synthCommand; + const installCommand = this.props.installCommand; + + const project = new codebuild.PipelineProject(scope, 'CdkBuildProject', { + projectName: this.props.projectName ?? this.props.projectName, + environment: this.props.environment, + buildSpec: codebuild.BuildSpec.fromObject({ + version: '0.2', + phases: { + pre_build: { + commands: filterEmpty([ + this.props.subdirectory ? `cd ${this.props.subdirectory}` : '', + installCommand, + ]), + }, + build: { + commands: filterEmpty([ + buildCommand, + synthCommand, + ]), + }, + }, + artifacts: renderArtifacts(this), + }), + environmentVariables: { + ...copyEnvironmentVariables(...this.props.copyEnvironmentVariables || []), + ...this.props.environmentVariables, + }, + }); + + this._action = new codepipeline_actions.CodeBuildAction({ + actionName: this.actionProperties.actionName, + input: this.props.sourceArtifact, + outputs: [this.props.cloudAssemblyArtifact, ...(this.props.additionalArtifacts ?? []).map(a => a.artifact)], + project, + }); + this._actionProperties = this._action.actionProperties; + + return this._action.bind(scope, stage, options); + + function renderArtifacts(self: SimpleSynthAction) { + // save the generated files in the output artifact + // This part of the buildspec has to look completely different depending on whether we're + // using secondary artifacts or not. + + const cloudAsmArtifactSpec = { + 'base-directory': path.join(self.props.subdirectory ?? '.', cloudAssemblyBuildSpecDir(scope)), + 'files': '**/*', + }; + + if (self.props.additionalArtifacts) { + const secondary: Record = {}; + if (!self.props.cloudAssemblyArtifact.artifactName) { + throw new Error('When using additional output artifacts, you must also name the CloudAssembly artifact'); + } + secondary[self.props.cloudAssemblyArtifact.artifactName] = cloudAsmArtifactSpec; + self.props.additionalArtifacts.forEach((art) => { + if (!art.artifact.artifactName) { + throw new Error('You must give the output artifact a name'); + } + secondary[art.artifact.artifactName] = { + 'base-directory': path.join(self.props.subdirectory ?? '.', art.directory), + 'files': '**/*', + }; + }); + + return { 'secondary-artifacts': secondary }; + } + + return cloudAsmArtifactSpec; + } + } + + /** + * Exists to implement IAction + */ + public onStateChange(name: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule { + if (!this._action) { + throw new Error('Need bind() first'); + } + + return this._action.onStateChange(name, target, options); + } +} + +/** + * Options for a convention-based synth using NPM + */ +export interface StandardNpmSynthOptions extends SimpleSynthOptions { + /** + * The install command + * + * @default 'npm ci' + */ + readonly installCommand?: string; + + /** + * The build command + * + * By default, we assume NPM projects are either written in JavaScript or are + * using `ts-node`, so don't need a build command. + * + * Otherwise, put the build command here, for example `npm run build`. + * + * @default - No build required + */ + readonly buildCommand?: string; + + /** + * The synth command + * + * @default 'npx cdk synth' + */ + readonly synthCommand?: string; +} + +/** + * Options for a convention-based synth using Yarn + */ +export interface StandardYarnSynthOptions extends SimpleSynthOptions { + /** + * The install command + * + * @default 'yarn install --frozen-lockfile' + */ + readonly installCommand?: string; + + /** + * The build command + * + * By default, we assume NPM projects are either written in JavaScript or are + * using `ts-node`, so don't need a build command. + * + * Otherwise, put the build command here, for example `npm run build`. + * + * @default - No build required + */ + readonly buildCommand?: string; + + /** + * The synth command + * + * @default 'npx cdk synth' + */ + readonly synthCommand?: string; +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/lib/validation/_files.ts b/packages/@aws-cdk/pipelines/lib/validation/_files.ts new file mode 100644 index 0000000000000..2f2bbf7be35ea --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/validation/_files.ts @@ -0,0 +1,97 @@ +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import { IGrantable } from '@aws-cdk/aws-iam'; +import * as s3assets from '@aws-cdk/aws-s3-assets'; +import { Construct } from '@aws-cdk/core'; + +/** + * Additional files to use in a shell script + */ +export abstract class Files { + /** + * Use the files from a CodePipeline artifact + */ + public static fromArtifact(artifact: codepipeline.Artifact): Files { + if (!artifact) { + // Typechecking may mess up + throw new Error('Files.fromArtifact(): input artifact is required, got undefined'); + } + + return { + bind: () => ({ artifact }), + grantRead: () => { /* Not necessary */ }, + }; + } + + /** + * Create a new asset to bundle up the files in a directory on disk + */ + public static fromDirectory(directoryPath: string): Files { + let realFiles: Files; + return { + bind(scope: Construct) { + realFiles = Files.fromAsset(new s3assets.Asset(scope, directoryPath, { + path: directoryPath, + })); + + return realFiles.bind(scope); + }, + grantRead(grantee: IGrantable) { + if (!realFiles) { + throw new Error('bind() must be called first'); + } + realFiles.grantRead(grantee); + }, + }; + } + + /** + * Use an existing asset as a file source + */ + public static fromAsset(asset: s3assets.Asset): Files { + return { + bind: () => ({ + commands: [ + `echo "Downloading additional files from ${asset.s3ObjectUrl}"`, + `aws s3 cp ${asset.s3ObjectUrl} /tmp/dl.zip`, + 'unzip /tmp/dl.zip -d .', + ], + }), + grantRead: (grantee) => asset.grantRead(grantee), + }; + } + + protected constructor() { + } + + /** + * Bind the Files to a usage location + */ + public abstract bind(scope: Construct): FilesConfig; + + /** + * Grant read permissions to the file set to the given grantable + * + * Must be called after bind(). + */ + + public abstract grantRead(grantee: IGrantable): void; +} + +/** + * Config for a Files source + */ +export interface FilesConfig { + /** + * CodePipeline artifact to add to the set of input artifacts for the project + * + * @default - No artifact + */ + readonly artifact?: codepipeline.Artifact; + + /** + * Commands to add to the set of commands for the project + * + * @default - No commands + */ + readonly commands?: string[]; +} diff --git a/packages/@aws-cdk/pipelines/lib/validation/index.ts b/packages/@aws-cdk/pipelines/lib/validation/index.ts new file mode 100644 index 0000000000000..f2751fc92af49 --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/validation/index.ts @@ -0,0 +1 @@ +export * from './shell-script-action'; \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/lib/validation/shell-script-action.ts b/packages/@aws-cdk/pipelines/lib/validation/shell-script-action.ts new file mode 100644 index 0000000000000..301e641cb15fa --- /dev/null +++ b/packages/@aws-cdk/pipelines/lib/validation/shell-script-action.ts @@ -0,0 +1,183 @@ +import * as codebuild from '@aws-cdk/aws-codebuild'; +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions'; +import * as events from '@aws-cdk/aws-events'; +import { Construct } from '@aws-cdk/core'; +import { StackOutput } from '../stage'; + +/** + * Properties for ShellScriptValidation + */ +export interface ShellScriptActionProps { + /** + * Name of the validation action in the pipeline + */ + readonly actionName: string; + + /** + * Stack outputs to make available as environment variables + * + * @default - No outputs used + */ + readonly useOutputs?: Record; + + /** + * Commands to run + */ + readonly commands: string[]; + + /** + * Bash options to set at the start of the script + * + * @default '-eu' (errexit and nounset) + */ + readonly bashOptions?: string; + + /** + * Additional artifacts to use as input for the CodeBuild project + * + * You can use these files to load more complex test sets into the + * shellscript build environment. + * + * The files artifact given here will be unpacked into the current + * working directory, the other ones will be unpacked into directories + * which are available through the environment variables + * $CODEBUILD_SRC_DIR_. + * + * The CodeBuild job must have at least one input artifact, so you + * must provide either at least one additional artifact here or one + * stack output using `useOutput`. + * + * @default - No additional artifacts + */ + readonly additionalArtifacts?: codepipeline.Artifact[]; + + /** + * RunOrder for this action + * + * Use this to sequence the shell script after the deployments. + * + * The default value is 100 so you don't have to supply the value if you just + * want to run this after the application stacks have been deployed, and you + * don't have more than 100 stacks. + * + * @default 100 + */ + readonly runOrder?: number; +} + +/** + * Validate a revision using shell commands + */ +export class ShellScriptAction implements codepipeline.IAction { + private _project?: codebuild.IProject; + + private _action?: codepipeline_actions.CodeBuildAction; + private _actionProperties: codepipeline.ActionProperties; + + constructor(private readonly props: ShellScriptActionProps) { + // A number of actionProperties get read before bind() is even called (so before we + // have made the Project and can construct the actual CodeBuildAction) + // + // - actionName + // - resource + // - region + // - category + // - role + // - owner + this._actionProperties = { + actionName: props.actionName, + category: codepipeline.ActionCategory.BUILD, + provider: 'CodeBuild', + artifactBounds: { minInputs: 0, maxInputs: 5, minOutputs: 0, maxOutputs: 5 }, + inputs: [], + outputs: [], + }; + + if (Object.keys(props.useOutputs ?? {}).length + (props.additionalArtifacts ?? []).length === 0) { + throw new Error('You must supply either \'useOutputs\' or \'additionalArtifacts\', since a CodeBuild Action must always have at least one input artifact.'); + } + } + + /** + * Exists to implement IAction + */ + public get actionProperties(): codepipeline.ActionProperties { + return this._actionProperties; + } + + /** + * Exists to implement IAction + */ + public bind(scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): codepipeline.ActionConfig { + const inputs = new Array(); + inputs.push(...this.props.additionalArtifacts ?? []); + + const envVarCommands = new Array(); + + const bashOptions = this.props.bashOptions ?? '-eu'; + if (bashOptions) { + envVarCommands.push(`set ${bashOptions}`); + } + for (const [varName, output] of Object.entries(this.props.useOutputs ?? {})) { + const outputArtifact = output.artifactFile; + + // Add the artifact to the list of inputs, if it's not in there already. Determine + // the location where CodeBuild is going to stick it based on whether it's the first (primary) + // input or an 'extra input', then parse. + let artifactIndex = inputs.findIndex(a => a.artifactName === outputArtifact.artifact.artifactName); + if (artifactIndex === -1) { + artifactIndex = inputs.push(outputArtifact.artifact) - 1; + } + const dirEnv = artifactIndex === 0 ? 'CODEBUILD_SRC_DIR' : `CODEBUILD_SRC_DIR_${outputArtifact.artifact.artifactName}`; + envVarCommands.push(`export ${varName}="$(node -pe 'require(process.env.${dirEnv} + "/${outputArtifact.fileName}")["${output.outputName}"]')"`); + } + + this._project = new codebuild.PipelineProject(scope, 'Project', { + buildSpec: codebuild.BuildSpec.fromObject({ + version: '0.2', + phases: { + build: { + commands: [ + ...envVarCommands, + ...this.props.commands, + ], + }, + }, + }), + }); + + this._action = new codepipeline_actions.CodeBuildAction({ + actionName: this.props.actionName, + input: inputs[0], + extraInputs: inputs.slice(1), + runOrder: this.props.runOrder ?? 100, + project: this._project, + }); + // Replace the placeholder actionProperties at the last minute + this._actionProperties = this._action.actionProperties; + + return this._action.bind(scope, stage, options); + } + + /** + * Project generated to run the shell script in + */ + public get project(): codebuild.IProject { + if (!this._project) { + throw new Error('Project becomes available after ShellScriptAction has been bound to a stage'); + } + return this._project; + } + + /** + * Exists to implement IAction + */ + public onStateChange(name: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule { + if (!this._action) { + throw new Error('Need bind() first'); + } + + return this._action.onStateChange(name, target, options); + } +} diff --git a/packages/@aws-cdk/pipelines/package.json b/packages/@aws-cdk/pipelines/package.json new file mode 100644 index 0000000000000..9101924d441c6 --- /dev/null +++ b/packages/@aws-cdk/pipelines/package.json @@ -0,0 +1,122 @@ +{ + "name": "@aws-cdk/pipelines", + "version": "0.0.0", + "description": "Continuous Delivery of CDK applications", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/pipelines" + }, + "bin": {}, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "build+test+package": "npm run build+test && npm run package", + "build+test": "npm run build && npm test", + "compat": "cdk-compat" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "devDependencies": { + "@aws-cdk/assert": "0.0.0", + "@types/nodeunit": "^0.0.31", + "cdk-build-tools": "0.0.0", + "cdk-integ-tools": "0.0.0", + "cfn2ts": "0.0.0", + "nodeunit": "^0.11.3", + "pkglint": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-ecr-assets": "0.0.0" + }, + "peerDependencies": { + "constructs": "^3.0.2", + "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-codebuild": "0.0.0", + "@aws-cdk/aws-codepipeline": "0.0.0", + "@aws-cdk/aws-codepipeline-actions": "0.0.0", + "@aws-cdk/aws-events": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0", + "@aws-cdk/aws-s3-assets": "0.0.0", + "@aws-cdk/cx-api": "0.0.0", + "@aws-cdk/aws-cloudformation": "0.0.0" + }, + "dependencies": { + "constructs": "^3.0.2", + "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-codebuild": "0.0.0", + "@aws-cdk/aws-codepipeline": "0.0.0", + "@aws-cdk/aws-codepipeline-actions": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0", + "@aws-cdk/aws-events": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-s3-assets": "0.0.0", + "@aws-cdk/cx-api": "0.0.0", + "@aws-cdk/aws-cloudformation": "0.0.0" + }, + "bundledDependencies": [], + "keywords": [ + "aws", + "cdk", + "constructs", + "pipelines", + "cicd", + "continuous", + "delivery" + ], + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "license": "Apache-2.0", + "stability": "experimental", + "maturity": "developer-preview", + "cdk-build": { + "jest": true + }, + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awscdk.pipelines", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "cdk-pipelines" + } + }, + "dotnet": { + "namespace": "Amazon.CDK.Pipelines", + "packageId": "Amazon.CDK.Pipelines", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-cdk.pipelines", + "module": "aws_cdk.pipelines" + } + } + }, + "awscdkio": { + "announce": false + }, + "awslint": { + "exclude": [ + "events-generic:@aws-cdk/pipelines.PublishAssetsAction", + "events-method-signature:@aws-cdk/pipelines.PublishAssetsAction.onStateChange", + "events-generic:@aws-cdk/pipelines.UpdatePipelineAction", + "events-method-signature:@aws-cdk/pipelines.UpdatePipelineAction.onStateChange" + ] + }, + "homepage": "https://github.com/aws/aws-cdk" +} diff --git a/packages/@aws-cdk/pipelines/test/builds.test.ts b/packages/@aws-cdk/pipelines/test/builds.test.ts new file mode 100644 index 0000000000000..9e8ecfc3d0522 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/builds.test.ts @@ -0,0 +1,141 @@ +import { arrayWith, deepObjectLike, encodedJson } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import { Stack } from '@aws-cdk/core'; +import * as cdkp from '../lib'; +import { PIPELINE_ENV, TestApp, TestGitHubNpmPipeline } from './testutil'; + +let app: TestApp; +let pipelineStack: Stack; +let sourceArtifact: codepipeline.Artifact; +let cloudAssemblyArtifact: codepipeline.Artifact; + +beforeEach(() => { + app = new TestApp({ outdir: 'testcdk.out' }); + pipelineStack = new Stack(app, 'PipelineStack', { env: PIPELINE_ENV }); + sourceArtifact = new codepipeline.Artifact(); + cloudAssemblyArtifact = new codepipeline.Artifact('CloudAsm'); +}); + +afterEach(() => { + app.cleanup(); +}); + +test.each([['npm'], ['yarn']])('%s build automatically determines artifact base-directory', (npmYarn) => { + // WHEN + new TestGitHubNpmPipeline(pipelineStack, 'Cdk', { + sourceArtifact, + cloudAssemblyArtifact, + synthAction: npmYarnBuild(npmYarn)({ sourceArtifact, cloudAssemblyArtifact }), + }); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Source: { + BuildSpec: encodedJson(deepObjectLike({ + artifacts: { + 'base-directory': 'testcdk.out', + }, + })), + }, + }); +}); + +test.each([['npm'], ['yarn']])('%s build respects subdirectory', (npmYarn) => { + // WHEN + new TestGitHubNpmPipeline(pipelineStack, 'Cdk', { + sourceArtifact, + cloudAssemblyArtifact, + synthAction: npmYarnBuild(npmYarn)({ + sourceArtifact, + cloudAssemblyArtifact, + subdirectory: 'subdir', + }), + }); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + pre_build: { + commands: arrayWith('cd subdir'), + }, + }, + artifacts: { + 'base-directory': 'subdir/testcdk.out', + }, + })), + }, + }); +}); + +test.each([['npm'], ['yarn']])('%s assumes no build step by default', (npmYarn) => { + // WHEN + new TestGitHubNpmPipeline(pipelineStack, 'Cdk', { + sourceArtifact, + cloudAssemblyArtifact, + synthAction: npmYarnBuild(npmYarn)({ sourceArtifact, cloudAssemblyArtifact }), + }); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + build: { + commands: ['npx cdk synth'], + }, + }, + })), + }, + }); +}); + +test('Standard (NPM) synth can output additional artifacts', () => { + // WHEN + sourceArtifact = new codepipeline.Artifact(); + cloudAssemblyArtifact = new codepipeline.Artifact('CloudAsm'); + + const addlArtifact = new codepipeline.Artifact('IntegTest'); + new TestGitHubNpmPipeline(pipelineStack, 'Cdk', { + sourceArtifact, + cloudAssemblyArtifact, + synthAction: cdkp.SimpleSynthAction.standardNpmSynth({ + sourceArtifact, + cloudAssemblyArtifact, + additionalArtifacts: [ + { + artifact: addlArtifact, + directory: 'test', + }, + ], + }), + }); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Source: { + BuildSpec: encodedJson(deepObjectLike({ + artifacts: { + 'secondary-artifacts': { + CloudAsm: { + 'base-directory': 'testcdk.out', + 'files': '**/*', + }, + IntegTest: { + 'base-directory': 'test', + 'files': '**/*', + }, + }, + }, + })), + }, + }); +}); + +function npmYarnBuild(npmYarn: string) { + if (npmYarn === 'npm') { return cdkp.SimpleSynthAction.standardNpmSynth; } + if (npmYarn === 'yarn') { return cdkp.SimpleSynthAction.standardYarnSynth; } + throw new Error(`Expecting npm|yarn: ${npmYarn}`); +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/cross-environment-infra.test.ts b/packages/@aws-cdk/pipelines/test/cross-environment-infra.test.ts new file mode 100644 index 0000000000000..71717c4dbb1a7 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/cross-environment-infra.test.ts @@ -0,0 +1,75 @@ +import { arrayWith, objectLike, stringLike } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import { Construct, Stack, Stage, StageProps } from '@aws-cdk/core'; +import * as cdkp from '../lib'; +import { BucketStack, PIPELINE_ENV, TestApp, TestGitHubNpmPipeline } from './testutil'; + +let app: TestApp; +let pipelineStack: Stack; +let pipeline: cdkp.CdkPipeline; + +beforeEach(() => { + app = new TestApp(); + pipelineStack = new Stack(app, 'PipelineStack', { env: PIPELINE_ENV }); + pipeline = new TestGitHubNpmPipeline(pipelineStack, 'Cdk'); +}); + +afterEach(() => { + app.cleanup(); +}); + +test('in a cross-account/cross-region setup, artifact bucket can be read by deploy role', () => { + // WHEN + pipeline.addApplicationStage(new TestApplication(app, 'MyApp', { + env: { account: '321elsewhere', region: 'us-elsewhere' }, + })); + + // THEN + app.synth(); + const supportStack = app.node.findAll().filter(Stack.isStack).find(s => s.stackName === 'PipelineStack-support-us-elsewhere'); + expect(supportStack).not.toBeUndefined(); + + expect(supportStack).toHaveResourceLike('AWS::S3::BucketPolicy', { + PolicyDocument: { + Statement: arrayWith(objectLike({ + Action: arrayWith('s3:GetObject*', 's3:GetBucket*', 's3:List*'), + Principal: { + AWS: { + 'Fn::Sub': stringLike('*-deploy-role-*'), + }, + }, + })), + }, + }); +}); + +test('in a cross-account/same-region setup, artifact bucket can be read by deploy role', () => { + // WHEN + pipeline.addApplicationStage(new TestApplication(app, 'MyApp', { + env: { account: '321elsewhere', region: PIPELINE_ENV.region }, + })); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::S3::BucketPolicy', { + PolicyDocument: { + Statement: arrayWith(objectLike({ + Action: ['s3:GetObject*', 's3:GetBucket*', 's3:List*'], + Principal: { + AWS: { + 'Fn::Sub': stringLike('*-deploy-role-*'), + }, + }, + })), + }, + }); +}); + +/** + * Our application + */ +class TestApplication extends Stage { + constructor(scope: Construct, id: string, props: StageProps) { + super(scope, id, props); + new BucketStack(this, 'Stack'); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json new file mode 100644 index 0000000000000..b30983fa38619 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json @@ -0,0 +1,1316 @@ +{ + "Resources": { + "PipelineUpdatePipelineSelfMutationRole57E559E8": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codebuild.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineUpdatePipelineSelfMutationRoleDefaultPolicyA225DA4E": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:test-region:12345678:log-group:/aws/codebuild/", + { + "Ref": "PipelineUpdatePipelineSelfMutationDAA41400" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:test-region:12345678:log-group:/aws/codebuild/", + { + "Ref": "PipelineUpdatePipelineSelfMutationDAA41400" + }, + ":*" + ] + ] + } + ] + }, + { + "Action": [ + "codebuild:CreateReportGroup", + "codebuild:CreateReport", + "codebuild:UpdateReport", + "codebuild:BatchPutTestCases" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codebuild:test-region:12345678:report-group/", + { + "Ref": "PipelineUpdatePipelineSelfMutationDAA41400" + }, + "-*" + ] + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": [ + "arn:*:iam::*:role/*-deploy-role-*", + "arn:*:iam::*:role/*-publishing-role-*" + ] + }, + { + "Action": "cloudformation:DescribeStacks", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "s3:ListBucket", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + } + }, + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineUpdatePipelineSelfMutationRoleDefaultPolicyA225DA4E", + "Roles": [ + { + "Ref": "PipelineUpdatePipelineSelfMutationRole57E559E8" + } + ] + } + }, + "PipelineUpdatePipelineSelfMutationDAA41400": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "CODEPIPELINE" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/standard:1.0", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "PipelineUpdatePipelineSelfMutationRole57E559E8", + "Arn" + ] + }, + "Source": { + "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"install\": {\n \"commands\": \"npm install -g aws-cdk\"\n },\n \"build\": {\n \"commands\": [\n \"cdk -a . deploy PipelineStack --require-approval=never --verbose\"\n ]\n }\n }\n}", + "Type": "CODEPIPELINE" + }, + "EncryptionKey": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + } + } + }, + "PipelineArtifactsBucketEncryptionKeyF5BF0670": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "PipelineRoleB27FAA37", + "Arn" + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "PipelineBuildSynthCdkBuildProjectRole231EEA2A", + "Arn" + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "PipelineBuildSynthCdkBuildProjectRole231EEA2A", + "Arn" + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "PipelineUpdatePipelineSelfMutationRole57E559E8", + "Arn" + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "PipelineUpdatePipelineSelfMutationRole57E559E8", + "Arn" + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "PipelinePreProdUseSourceProjectRole69B20A71", + "Arn" + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "PipelinePreProdUseSourceProjectRole69B20A71", + "Arn" + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Sub": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}" + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "PipelineArtifactsBucketAEA9A052": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "PipelineArtifactsBucketEncryptionKeyAlias94A07392": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/codepipeline-pipelinestackpipelinee95eedaa", + "TargetKeyId": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "PipelineRoleB27FAA37": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codepipeline.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineRoleDefaultPolicy7BDC1ABB": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineBuildSynthCodePipelineActionRole4E7A6C97", + "Arn" + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineUpdatePipelineSelfMutateCodePipelineActionRoleD6D4E5CF", + "Arn" + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelinePreProdUseSourceCodePipelineActionRoleA2043BDA", + "Arn" + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::Sub": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineRoleDefaultPolicy7BDC1ABB", + "Roles": [ + { + "Ref": "PipelineRoleB27FAA37" + } + ] + } + }, + "Pipeline9850B417": { + "Type": "AWS::CodePipeline::Pipeline", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "PipelineRoleB27FAA37", + "Arn" + ] + }, + "Stages": [ + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Source", + "Owner": "ThirdParty", + "Provider": "GitHub", + "Version": "1" + }, + "Configuration": { + "Owner": "OWNER", + "Repo": "REPO", + "Branch": "master", + "OAuthToken": "not-a-secret", + "PollForSourceChanges": true + }, + "Name": "GitHub", + "OutputArtifacts": [ + { + "Name": "Artifact_Source_GitHub" + } + ], + "RunOrder": 1 + } + ], + "Name": "Source" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1" + }, + "Configuration": { + "ProjectName": { + "Ref": "PipelineBuildSynthCdkBuildProject6BEFA8E6" + } + }, + "InputArtifacts": [ + { + "Name": "Artifact_Source_GitHub" + } + ], + "Name": "Synth", + "OutputArtifacts": [ + { + "Name": "CloudAsm" + }, + { + "Name": "IntegTests" + } + ], + "RoleArn": { + "Fn::GetAtt": [ + "PipelineBuildSynthCodePipelineActionRole4E7A6C97", + "Arn" + ] + }, + "RunOrder": 1 + } + ], + "Name": "Build" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1" + }, + "Configuration": { + "ProjectName": { + "Ref": "PipelineUpdatePipelineSelfMutationDAA41400" + } + }, + "InputArtifacts": [ + { + "Name": "CloudAsm" + } + ], + "Name": "SelfMutate", + "RoleArn": { + "Fn::GetAtt": [ + "PipelineUpdatePipelineSelfMutateCodePipelineActionRoleD6D4E5CF", + "Arn" + ] + }, + "RunOrder": 1 + } + ], + "Name": "UpdatePipeline" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1" + }, + "Configuration": { + "ProjectName": { + "Ref": "PipelinePreProdUseSourceProject2E711EB4" + } + }, + "InputArtifacts": [ + { + "Name": "Artifact_Source_GitHub" + } + ], + "Name": "UseSource", + "RoleArn": { + "Fn::GetAtt": [ + "PipelinePreProdUseSourceCodePipelineActionRoleA2043BDA", + "Arn" + ] + }, + "RunOrder": 100 + }, + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "StackName": "PreProd-Stack", + "Capabilities": "CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND", + "RoleArn": { + "Fn::Sub": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}" + }, + "ActionMode": "CHANGE_SET_REPLACE", + "ChangeSetName": "PipelineChange", + "TemplatePath": "CloudAsm::assembly-PipelineStack-PreProd/PipelineStackPreProdStack65A0AD1F.template.json" + }, + "InputArtifacts": [ + { + "Name": "CloudAsm" + } + ], + "Name": "Stack.Prepare", + "RoleArn": { + "Fn::Sub": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}" + }, + "RunOrder": 1 + }, + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "StackName": "PreProd-Stack", + "ActionMode": "CHANGE_SET_EXECUTE", + "ChangeSetName": "PipelineChange" + }, + "Name": "Stack.Deploy", + "RoleArn": { + "Fn::Sub": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}" + }, + "RunOrder": 2 + } + ], + "Name": "PreProd" + } + ], + "ArtifactStore": { + "EncryptionKey": { + "Id": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + }, + "Type": "KMS" + }, + "Location": { + "Ref": "PipelineArtifactsBucketAEA9A052" + }, + "Type": "S3" + }, + "RestartExecutionOnUpdate": true + }, + "DependsOn": [ + "PipelineRoleDefaultPolicy7BDC1ABB", + "PipelineRoleB27FAA37" + ] + }, + "PipelineBuildSynthCodePipelineActionRole4E7A6C97": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineBuildSynthCodePipelineActionRoleDefaultPolicy92C90290": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "codebuild:BatchGetBuilds", + "codebuild:StartBuild", + "codebuild:StopBuild" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineBuildSynthCdkBuildProject6BEFA8E6", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineBuildSynthCodePipelineActionRoleDefaultPolicy92C90290", + "Roles": [ + { + "Ref": "PipelineBuildSynthCodePipelineActionRole4E7A6C97" + } + ] + } + }, + "PipelineBuildSynthCdkBuildProjectRole231EEA2A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codebuild.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineBuildSynthCdkBuildProjectRoleDefaultPolicyFB6C941C": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:test-region:12345678:log-group:/aws/codebuild/", + { + "Ref": "PipelineBuildSynthCdkBuildProject6BEFA8E6" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:test-region:12345678:log-group:/aws/codebuild/", + { + "Ref": "PipelineBuildSynthCdkBuildProject6BEFA8E6" + }, + ":*" + ] + ] + } + ] + }, + { + "Action": [ + "codebuild:CreateReportGroup", + "codebuild:CreateReport", + "codebuild:UpdateReport", + "codebuild:BatchPutTestCases" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codebuild:test-region:12345678:report-group/", + { + "Ref": "PipelineBuildSynthCdkBuildProject6BEFA8E6" + }, + "-*" + ] + ] + } + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + } + }, + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineBuildSynthCdkBuildProjectRoleDefaultPolicyFB6C941C", + "Roles": [ + { + "Ref": "PipelineBuildSynthCdkBuildProjectRole231EEA2A" + } + ] + } + }, + "PipelineBuildSynthCdkBuildProject6BEFA8E6": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "CODEPIPELINE" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/standard:1.0", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "PipelineBuildSynthCdkBuildProjectRole231EEA2A", + "Arn" + ] + }, + "Source": { + "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"pre_build\": {\n \"commands\": [\n \"npm ci\"\n ]\n },\n \"build\": {\n \"commands\": [\n \"npx cdk synth\"\n ]\n }\n },\n \"artifacts\": {\n \"secondary-artifacts\": {\n \"CloudAsm\": {\n \"base-directory\": \"cdk-integ.out\",\n \"files\": \"**/*\"\n },\n \"IntegTests\": {\n \"base-directory\": \"test\",\n \"files\": \"**/*\"\n }\n }\n }\n}", + "Type": "CODEPIPELINE" + }, + "EncryptionKey": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + }, + "Name": "MyServicePipeline-synth" + } + }, + "PipelineUpdatePipelineSelfMutateCodePipelineActionRoleD6D4E5CF": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineUpdatePipelineSelfMutateCodePipelineActionRoleDefaultPolicyE626265B": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "codebuild:BatchGetBuilds", + "codebuild:StartBuild", + "codebuild:StopBuild" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineUpdatePipelineSelfMutationDAA41400", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineUpdatePipelineSelfMutateCodePipelineActionRoleDefaultPolicyE626265B", + "Roles": [ + { + "Ref": "PipelineUpdatePipelineSelfMutateCodePipelineActionRoleD6D4E5CF" + } + ] + } + }, + "PipelinePreProdUseSourceCodePipelineActionRoleA2043BDA": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelinePreProdUseSourceCodePipelineActionRoleDefaultPolicy9BE325AD": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "codebuild:BatchGetBuilds", + "codebuild:StartBuild", + "codebuild:StopBuild" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelinePreProdUseSourceProject2E711EB4", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelinePreProdUseSourceCodePipelineActionRoleDefaultPolicy9BE325AD", + "Roles": [ + { + "Ref": "PipelinePreProdUseSourceCodePipelineActionRoleA2043BDA" + } + ] + } + }, + "PipelinePreProdUseSourceProjectRole69B20A71": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codebuild.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelinePreProdUseSourceProjectRoleDefaultPolicy50F68DF3": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:test-region:12345678:log-group:/aws/codebuild/", + { + "Ref": "PipelinePreProdUseSourceProject2E711EB4" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:test-region:12345678:log-group:/aws/codebuild/", + { + "Ref": "PipelinePreProdUseSourceProject2E711EB4" + }, + ":*" + ] + ] + } + ] + }, + { + "Action": [ + "codebuild:CreateReportGroup", + "codebuild:CreateReport", + "codebuild:UpdateReport", + "codebuild:BatchPutTestCases" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codebuild:test-region:12345678:report-group/", + { + "Ref": "PipelinePreProdUseSourceProject2E711EB4" + }, + "-*" + ] + ] + } + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucketAEA9A052", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + } + }, + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelinePreProdUseSourceProjectRoleDefaultPolicy50F68DF3", + "Roles": [ + { + "Ref": "PipelinePreProdUseSourceProjectRole69B20A71" + } + ] + } + }, + "PipelinePreProdUseSourceProject2E711EB4": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "CODEPIPELINE" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/standard:1.0", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "PipelinePreProdUseSourceProjectRole69B20A71", + "Arn" + ] + }, + "Source": { + "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"set -eu\",\n \"cat README.md\"\n ]\n }\n }\n}", + "Type": "CODEPIPELINE" + }, + "EncryptionKey": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKeyF5BF0670", + "Arn" + ] + } + } + } + } +} diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline.ts b/packages/@aws-cdk/pipelines/test/integ.pipeline.ts new file mode 100644 index 0000000000000..f0a4da9dde073 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline.ts @@ -0,0 +1,80 @@ +/// !cdk-integ PipelineStack +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions'; +import { App, CfnResource, Construct, SecretValue, Stack, StackProps, Stage, StageProps } from '@aws-cdk/core'; +import * as cdkp from '../lib'; + +class MyStage extends Stage { + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + + const stack = new Stack(this, 'Stack'); + new CfnResource(stack, 'Resource', { + type: 'AWS::Test::SomeResource', + }); + } +} + +/** + * The stack that defines the application pipeline + */ +class CdkpipelinesDemoPipelineStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const sourceArtifact = new codepipeline.Artifact(); + const cloudAssemblyArtifact = new codepipeline.Artifact('CloudAsm'); + const integTestArtifact = new codepipeline.Artifact('IntegTests'); + + const pipeline = new cdkp.CdkPipeline(this, 'Pipeline', { + cloudAssemblyArtifact, + + // Where the source can be found + sourceAction: new codepipeline_actions.GitHubSourceAction({ + actionName: 'GitHub', + output: sourceArtifact, + oauthToken: SecretValue.plainText('not-a-secret'), + owner: 'OWNER', + repo: 'REPO', + trigger: codepipeline_actions.GitHubTrigger.POLL, + }), + + // How it will be built + synthAction: cdkp.SimpleSynthAction.standardNpmSynth({ + sourceArtifact, + cloudAssemblyArtifact, + projectName: 'MyServicePipeline-synth', + additionalArtifacts: [ + { + directory: 'test', + artifact: integTestArtifact, + }, + ], + }), + }); + + // This is where we add the application stages + // ... + const stage = pipeline.addApplicationStage(new MyStage(this, 'PreProd')); + stage.addActions( + new cdkp.ShellScriptAction({ + actionName: 'UseSource', + commands: [ + // Comes from source + 'cat README.md', + ], + additionalArtifacts: [sourceArtifact], + }), + ); + } +} + +const app = new App({ + context: { + '@aws-cdk/core:newStyleStackSynthesis': 'true', + }, +}); +new CdkpipelinesDemoPipelineStack(app, 'PipelineStack', { + env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, +}); +app.synth(); diff --git a/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts b/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts new file mode 100644 index 0000000000000..e0d4119c326db --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts @@ -0,0 +1,214 @@ +import { arrayWith, deepObjectLike, encodedJson, notMatching, objectLike, stringLike } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import * as ecr_assets from '@aws-cdk/aws-ecr-assets'; +import * as s3_assets from '@aws-cdk/aws-s3-assets'; +import { Construct, Stack, Stage, StageProps } from '@aws-cdk/core'; +import * as path from 'path'; +import * as cdkp from '../lib'; +import { BucketStack, PIPELINE_ENV, TestApp, TestGitHubNpmPipeline } from './testutil'; + +const FILE_ASSET_SOURCE_HASH = '8289faf53c7da377bb2b90615999171adef5e1d8f6b88810e5fef75e6ca09ba5'; + +let app: TestApp; +let pipelineStack: Stack; +let pipeline: cdkp.CdkPipeline; + +beforeEach(() => { + app = new TestApp(); + pipelineStack = new Stack(app, 'PipelineStack', { env: PIPELINE_ENV }); + pipeline = new TestGitHubNpmPipeline(pipelineStack, 'Cdk'); +}); + +afterEach(() => { + app.cleanup(); +}); + +test('no assets stage if the application has no assets', () => { + // WHEN + pipeline.addApplicationStage(new PlainStackApp(app, 'App')); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: notMatching(arrayWith(objectLike({ + Name: 'Assets', + }))), + }); +}); + +test('command line properly locates assets in subassembly', () => { + // WHEN + pipeline.addApplicationStage(new FileAssetApp(app, 'FileAssetApp')); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + build: { + commands: arrayWith(`cdk-assets --path "assembly-FileAssetApp/FileAssetAppStackEADD68C5.assets.json" --verbose publish "${FILE_ASSET_SOURCE_HASH}:current_account-current_region"`), + }, + }, + })), + }, + }); +}); + +test('multiple assets are published in parallel', () => { + // WHEN + pipeline.addApplicationStage(new TwoFileAssetsApp(app, 'FileAssetApp')); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'Assets', + Actions: [ + objectLike({ RunOrder: 1 }), + objectLike({ RunOrder: 1 }), + ], + }), + }); +}); + +test('assets are also published when using the lower-level addStackArtifactDeployment', () => { + // GIVEN + const asm = new FileAssetApp(app, 'FileAssetApp').synth(); + + // WHEN + pipeline.addStage('SomeStage').addStackArtifactDeployment(asm.getStackByName('FileAssetApp-Stack')); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'Assets', + Actions: [ + objectLike({ + Name: FILE_ASSET_SOURCE_HASH, + RunOrder: 1, + }), + ], + }), + }); +}); + +test('file image asset publishers do not use privilegedmode, have right AssumeRole', () => { + // WHEN + pipeline.addApplicationStage(new FileAssetApp(app, 'FileAssetApp')); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + build: { + commands: arrayWith(stringLike('cdk-assets *')), + }, + }, + })), + }, + Environment: objectLike({ + PrivilegedMode: false, + }), + }); + + expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: arrayWith({ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Resource: 'arn:*:iam::*:role/*-file-publishing-role-*', + }), + }, + }); +}); + +test('docker image asset publishers use privilegedmode, have right AssumeRole', () => { + // WHEN + pipeline.addApplicationStage(new DockerAssetApp(app, 'DockerAssetApp')); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + build: { + commands: arrayWith(stringLike('cdk-assets *')), + }, + }, + })), + }, + Environment: objectLike({ + PrivilegedMode: true, + }), + }); + expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: arrayWith({ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Resource: 'arn:*:iam::*:role/*-image-publishing-role-*', + }), + }, + }); +}); + +test('can control fix/CLI version used in pipeline selfupdate', () => { + // WHEN + const stack2 = new Stack(app, 'Stack2', { env: PIPELINE_ENV }); + const pipeline2 = new TestGitHubNpmPipeline(stack2, 'Cdk2', { + cdkCliVersion: '1.2.3', + }); + pipeline2.addApplicationStage(new FileAssetApp(stack2, 'FileAssetApp')); + + // THEN + expect(stack2).toHaveResourceLike('AWS::CodeBuild::Project', { + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + install: { + commands: 'npm install -g cdk-assets@1.2.3', + }, + }, + })), + }, + }); +}); + +class PlainStackApp extends Stage { + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + new BucketStack(this, 'Stack'); + } +} + +class FileAssetApp extends Stage { + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + const stack = new Stack(this, 'Stack'); + new s3_assets.Asset(stack, 'Asset', { + path: path.join(__dirname, 'test-file-asset.txt'), + }); + } +} + +class TwoFileAssetsApp extends Stage { + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + const stack = new Stack(this, 'Stack'); + new s3_assets.Asset(stack, 'Asset1', { + path: path.join(__dirname, 'test-file-asset.txt'), + }); + new s3_assets.Asset(stack, 'Asset2', { + path: path.join(__dirname, 'test-file-asset-two.txt'), + }); + } +} + +class DockerAssetApp extends Stage { + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + const stack = new Stack(this, 'Stack'); + new ecr_assets.DockerImageAsset(stack, 'Asset', { + directory: path.join(__dirname, 'test-docker-asset'), + }); + } +} diff --git a/packages/@aws-cdk/pipelines/test/pipeline.test.ts b/packages/@aws-cdk/pipelines/test/pipeline.test.ts new file mode 100644 index 0000000000000..08567b3741961 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/pipeline.test.ts @@ -0,0 +1,282 @@ +import { anything, arrayWith, deepObjectLike, encodedJson, objectLike, stringLike } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import { Construct, Stack, Stage, StageProps } from '@aws-cdk/core'; +import * as cdkp from '../lib'; +import { BucketStack, PIPELINE_ENV, stackTemplate, TestApp, TestGitHubNpmPipeline } from './testutil'; + +let app: TestApp; +let pipelineStack: Stack; +let pipeline: cdkp.CdkPipeline; + +beforeEach(() => { + app = new TestApp(); + pipelineStack = new Stack(app, 'PipelineStack', { env: PIPELINE_ENV }); + pipeline = new TestGitHubNpmPipeline(pipelineStack, 'Cdk'); +}); + +afterEach(() => { + app.cleanup(); +}); + +test('references stack template in subassembly', () => { + // WHEN + pipeline.addApplicationStage(new OneStackApp(app, 'App')); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'App', + Actions: arrayWith( + objectLike({ + Name: 'Stack.Prepare', + InputArtifacts: [objectLike({})], + Configuration: objectLike({ + StackName: 'App-Stack', + TemplatePath: stringLike('*::assembly-App/*.template.json'), + }), + }), + ), + }), + }); +}); + +test('action has right settings for same-env deployment', () => { + // WHEN + pipeline.addApplicationStage(new OneStackApp(app, 'Same')); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'Same', + Actions: [ + objectLike({ + Name: 'Stack.Prepare', + RoleArn: { 'Fn::Sub': 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}' }, + Configuration: objectLike({ + StackName: 'Same-Stack', + RoleArn: { 'Fn::Sub': 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}' }, + }), + }), + objectLike({ + Name: 'Stack.Deploy', + RoleArn: { 'Fn::Sub': 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}' }, + Configuration: objectLike({ + StackName: 'Same-Stack', + }), + }), + ], + }), + }); +}); + +test('action has right settings for cross-account deployment', () => { + // WHEN + pipeline.addApplicationStage(new OneStackApp(app, 'CrossAccount', { env: { account: 'you' }})); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'CrossAccount', + Actions: [ + objectLike({ + Name: 'Stack.Prepare', + RoleArn: { 'Fn::Sub': 'arn:${AWS::Partition}:iam::you:role/cdk-hnb659fds-deploy-role-you-${AWS::Region}' }, + Configuration: objectLike({ + StackName: 'CrossAccount-Stack', + RoleArn: { 'Fn::Sub': 'arn:${AWS::Partition}:iam::you:role/cdk-hnb659fds-cfn-exec-role-you-${AWS::Region}' }, + }), + }), + objectLike({ + Name: 'Stack.Deploy', + RoleArn: { 'Fn::Sub': 'arn:${AWS::Partition}:iam::you:role/cdk-hnb659fds-deploy-role-you-${AWS::Region}' }, + Configuration: objectLike({ + StackName: 'CrossAccount-Stack', + }), + }), + ], + }), + }); +}); + +test('action has right settings for cross-region deployment', () => { + // WHEN + pipeline.addApplicationStage(new OneStackApp(app, 'CrossRegion', { env: { region: 'elsewhere' }})); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'CrossRegion', + Actions: [ + objectLike({ + Name: 'Stack.Prepare', + RoleArn: { 'Fn::Sub': 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-elsewhere' }, + Region: 'elsewhere', + Configuration: objectLike({ + StackName: 'CrossRegion-Stack', + RoleArn: { 'Fn::Sub': 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-elsewhere' }, + }), + }), + objectLike({ + Name: 'Stack.Deploy', + RoleArn: { 'Fn::Sub': 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-elsewhere' }, + Region: 'elsewhere', + Configuration: objectLike({ + StackName: 'CrossRegion-Stack', + }), + }), + ], + }), + }); +}); + +test('action has right settings for cross-account/cross-region deployment', () => { + // WHEN + pipeline.addApplicationStage(new OneStackApp(app, 'CrossBoth', { env: { account: 'you', region: 'elsewhere' }})); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'CrossBoth', + Actions: [ + objectLike({ + Name: 'Stack.Prepare', + RoleArn: { 'Fn::Sub': 'arn:${AWS::Partition}:iam::you:role/cdk-hnb659fds-deploy-role-you-elsewhere' }, + Region: 'elsewhere', + Configuration: objectLike({ + StackName: 'CrossBoth-Stack', + RoleArn: { 'Fn::Sub': 'arn:${AWS::Partition}:iam::you:role/cdk-hnb659fds-cfn-exec-role-you-elsewhere' }, + }), + }), + objectLike({ + Name: 'Stack.Deploy', + RoleArn: { 'Fn::Sub': 'arn:${AWS::Partition}:iam::you:role/cdk-hnb659fds-deploy-role-you-elsewhere' }, + Region: 'elsewhere', + Configuration: objectLike({ + StackName: 'CrossBoth-Stack', + }), + }), + ], + }), + }); +}); + +test('pipeline has self-mutation stage', () => { + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'UpdatePipeline', + Actions: [ + objectLike({ + Name: 'SelfMutate', + Configuration: objectLike({ + ProjectName: { Ref: anything() }, + }), + }), + ], + }), + }); + + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + install: { + commands: 'npm install -g aws-cdk', + }, + build: { + commands: arrayWith('cdk -a . deploy PipelineStack --require-approval=never --verbose'), + }, + }, + })), + Type: 'CODEPIPELINE', + }, + }); +}); + +test('selfmutation stage correctly identifies nested assembly of pipeline stack', () => { + const pipelineStage = new Stage(app, 'PipelineStage'); + const nestedPipelineStack = new Stack(pipelineStage, 'PipelineStack', { env: PIPELINE_ENV }); + new TestGitHubNpmPipeline(nestedPipelineStack, 'Cdk'); + + // THEN + expect(stackTemplate(nestedPipelineStack)).toHaveResourceLike('AWS::CodeBuild::Project', { + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + build: { + commands: arrayWith('cdk -a assembly-PipelineStage deploy PipelineStage-PipelineStack --require-approval=never --verbose'), + }, + }, + })), + }, + }); +}); + +test('overridden stack names are respected', () => { + // WHEN + pipeline.addApplicationStage(new OneStackAppWithCustomName(app, 'App1')); + pipeline.addApplicationStage(new OneStackAppWithCustomName(app, 'App2')); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith( + { + Name: 'App1', + Actions: arrayWith(objectLike({ + Name: 'MyFancyStack.Prepare', + Configuration: objectLike({ + StackName: 'MyFancyStack', + }), + })), + }, + { + Name: 'App2', + Actions: arrayWith(objectLike({ + Name: 'MyFancyStack.Prepare', + Configuration: objectLike({ + StackName: 'MyFancyStack', + }), + })), + }, + ), + }); +}); + +test('can control fix/CLI version used in pipeline selfupdate', () => { + // WHEN + const stack2 = new Stack(app, 'Stack2', { env: PIPELINE_ENV }); + new TestGitHubNpmPipeline(stack2, 'Cdk2', { + pipelineName: 'vpipe', + cdkCliVersion: '1.2.3', + }); + + // THEN + expect(stack2).toHaveResourceLike('AWS::CodeBuild::Project', { + Name: 'vpipe-selfupdate', + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + install: { + commands: 'npm install -g aws-cdk@1.2.3', + }, + }, + })), + }, + }); +}); + +class OneStackApp extends Stage { + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + new BucketStack(this, 'Stack'); + } +} + +class OneStackAppWithCustomName extends Stage { + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + new BucketStack(this, 'Stack', { + stackName: 'MyFancyStack', + }); + } +} diff --git a/packages/@aws-cdk/pipelines/test/stack-ordering.test.ts b/packages/@aws-cdk/pipelines/test/stack-ordering.test.ts new file mode 100644 index 0000000000000..e755572c78544 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/stack-ordering.test.ts @@ -0,0 +1,83 @@ +import { arrayWith, objectLike } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import { App, Construct, Stack, Stage, StageProps } from '@aws-cdk/core'; +import * as cdkp from '../lib'; +import { sortedByRunOrder } from './testmatchers'; +import { BucketStack, PIPELINE_ENV, TestApp, TestGitHubNpmPipeline } from './testutil'; + +let app: App; +let pipelineStack: Stack; +let pipeline: cdkp.CdkPipeline; + +beforeEach(() => { + app = new TestApp(); + pipelineStack = new Stack(app, 'PipelineStack', { env: PIPELINE_ENV }); + pipeline = new TestGitHubNpmPipeline(pipelineStack, 'Cdk'); +}); + +test('interdependent stacks are in the right order', () => { + // WHEN + pipeline.addApplicationStage(new TwoStackApp(app, 'MyApp')); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'MyApp', + Actions: sortedByRunOrder([ + objectLike({ Name: 'Stack1.Prepare' }), + objectLike({ Name: 'Stack1.Deploy' }), + objectLike({ Name: 'Stack2.Prepare' }), + objectLike({ Name: 'Stack2.Deploy' }), + ]), + }), + }); +}); + +test('multiple independent stacks go in parallel', () => { + // WHEN + pipeline.addApplicationStage(new ThreeStackApp(app, 'MyApp')); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'MyApp', + Actions: sortedByRunOrder([ + // 1 and 2 in parallel + objectLike({ Name: 'Stack1.Prepare' }), + objectLike({ Name: 'Stack2.Prepare' }), + objectLike({ Name: 'Stack1.Deploy' }), + objectLike({ Name: 'Stack2.Deploy' }), + // Then 3 + objectLike({ Name: 'Stack3.Prepare' }), + objectLike({ Name: 'Stack3.Deploy' }), + ]), + }), + }); +}); + +class TwoStackApp extends Stage { + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + + const stack2 = new BucketStack(this, 'Stack2'); + const stack1 = new BucketStack(this, 'Stack1'); + + stack2.addDependency(stack1); + } +} + +/** + * Three stacks where the last one depends on the earlier 2 + */ +class ThreeStackApp extends Stage { + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + + const stack1 = new BucketStack(this, 'Stack1'); + const stack2 = new BucketStack(this, 'Stack2'); + const stack3 = new BucketStack(this, 'Stack3'); + + stack3.addDependency(stack1); + stack3.addDependency(stack2); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/test-docker-asset/Dockerfile b/packages/@aws-cdk/pipelines/test/test-docker-asset/Dockerfile new file mode 100644 index 0000000000000..d67ab4b1cc12c --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/test-docker-asset/Dockerfile @@ -0,0 +1,2 @@ +FROM scratch +RUN touch built.txt \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/test-file-asset-two.txt b/packages/@aws-cdk/pipelines/test/test-file-asset-two.txt new file mode 100644 index 0000000000000..8b1c7231bf2f4 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/test-file-asset-two.txt @@ -0,0 +1 @@ +Here's a second file asset. \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/test-file-asset.txt b/packages/@aws-cdk/pipelines/test/test-file-asset.txt new file mode 100644 index 0000000000000..95e9dcd2e3bf0 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/test-file-asset.txt @@ -0,0 +1 @@ +This is a file asset that's just here for kicks. \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/testmatchers.ts b/packages/@aws-cdk/pipelines/test/testmatchers.ts new file mode 100644 index 0000000000000..c412993d4b51d --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/testmatchers.ts @@ -0,0 +1,25 @@ +import { annotateMatcher, InspectionFailure, matcherFrom, PropertyMatcher } from '@aws-cdk/assert'; + +/** + * Sort an array (of Actions) by their RunOrder field before applying a matcher. + * + * Makes the matcher independent of the order in which the Actions get synthed + * to the template. Elements with the same RunOrder will be sorted by name. + */ +export function sortedByRunOrder(matcher: any): PropertyMatcher { + return annotateMatcher({ $sortedByRunOrder: matcher }, (value: any, failure: InspectionFailure) => { + if (!Array.isArray(value)) { + failure.failureReason = `Expected an Array, but got '${typeof value}'`; + return false; + } + + value = value.slice(); + + value.sort((a: any, b: any) => { + if (a.RunOrder !== b.RunOrder) { return a.RunOrder - b.RunOrder; } + return (a.Name as string).localeCompare(b.Name); + }); + + return matcherFrom(matcher)(value, failure); + }); +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/testutil.ts b/packages/@aws-cdk/pipelines/test/testutil.ts new file mode 100644 index 0000000000000..9c87e64c502b7 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/testutil.ts @@ -0,0 +1,106 @@ +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions'; +import * as s3 from '@aws-cdk/aws-s3'; +import { App, AppProps, Construct, Environment, SecretValue, Stack, StackProps, Stage } from '@aws-cdk/core'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as cdkp from '../lib'; +import { assemblyBuilderOf } from '../lib/private/construct-internals'; + +export const PIPELINE_ENV: Environment = { + account: '123pipeline', + region: 'us-pipeline', +}; + +export class TestApp extends App { + constructor(props?: Partial) { + super({ + context: { + '@aws-cdk/core:newStyleStackSynthesis': '1', + }, + stackTraces: false, + autoSynth: false, + runtimeInfo: false, + treeMetadata: false, + ...props, + }); + } + + public cleanup() { + rimraf(assemblyBuilderOf(this).outdir); + } +} + +export class TestGitHubNpmPipeline extends cdkp.CdkPipeline { + public readonly sourceArtifact: codepipeline.Artifact; + public readonly cloudAssemblyArtifact: codepipeline.Artifact; + + constructor(scope: Construct, id: string, props?: Partial & { readonly sourceArtifact?: codepipeline.Artifact } ) { + const sourceArtifact = props?.sourceArtifact ?? new codepipeline.Artifact(); + const cloudAssemblyArtifact = props?.cloudAssemblyArtifact ?? new codepipeline.Artifact(); + + super(scope, id, { + sourceAction: new codepipeline_actions.GitHubSourceAction({ + actionName: 'GitHub', + output: sourceArtifact, + oauthToken: SecretValue.plainText('$3kr1t'), + owner: 'test', + repo: 'test', + trigger: codepipeline_actions.GitHubTrigger.POLL, + }), + synthAction: cdkp.SimpleSynthAction.standardNpmSynth({ + sourceArtifact, + cloudAssemblyArtifact, + }), + cloudAssemblyArtifact, + ...props, + }); + + this.sourceArtifact = sourceArtifact; + this.cloudAssemblyArtifact = cloudAssemblyArtifact; + } +} + +/** + * A test stack + * + * It contains a single Bucket. Such robust. Much uptime. + */ +export class BucketStack extends Stack { + public readonly bucket: s3.IBucket; + + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + this.bucket = new s3.Bucket(this, 'Bucket'); + } +} + +/** + * rm -rf reimplementation, don't want to depend on an NPM package for this + */ +export function rimraf(fsPath: string) { + try { + const isDir = fs.lstatSync(fsPath).isDirectory(); + + if (isDir) { + for (const file of fs.readdirSync(fsPath)) { + rimraf(path.join(fsPath, file)); + } + fs.rmdirSync(fsPath); + } else { + fs.unlinkSync(fsPath); + } + } catch (e) { + // We will survive ENOENT + if (e.code !== 'ENOENT') { throw e; } + } +} + +/** + * Because 'expect(stack)' doesn't work correctly for stacks in nested assemblies + */ +export function stackTemplate(stack: Stack) { + const stage = Stage.of(stack); + if (!stage) { throw new Error('stack not in a Stage'); } + return stage.synth().getStackArtifact(stack.artifactId); +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/validation.test.ts b/packages/@aws-cdk/pipelines/test/validation.test.ts new file mode 100644 index 0000000000000..dffe91ebe4dfd --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/validation.test.ts @@ -0,0 +1,178 @@ +import { anything, arrayWith, deepObjectLike, encodedJson } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import { CfnOutput, Construct, Stack, Stage, StageProps } from '@aws-cdk/core'; +import * as cdkp from '../lib'; +import { } from './testmatchers'; +import { BucketStack, PIPELINE_ENV, TestApp, TestGitHubNpmPipeline } from './testutil'; + +let app: TestApp; +let pipelineStack: Stack; +let pipeline: cdkp.CdkPipeline; +let sourceArtifact: codepipeline.Artifact; +let cloudAssemblyArtifact: codepipeline.Artifact; +let integTestArtifact: codepipeline.Artifact; + +beforeEach(() => { + app = new TestApp(); + pipelineStack = new Stack(app, 'PipelineStack', { env: PIPELINE_ENV }); + sourceArtifact = new codepipeline.Artifact(); + cloudAssemblyArtifact = new codepipeline.Artifact('CloudAsm'); + integTestArtifact = new codepipeline.Artifact('IntegTests'); + pipeline = new TestGitHubNpmPipeline(pipelineStack, 'Cdk', { + sourceArtifact, + cloudAssemblyArtifact, + synthAction: cdkp.SimpleSynthAction.standardNpmSynth({ + sourceArtifact, + cloudAssemblyArtifact, + additionalArtifacts: [{ directory: 'test', artifact: integTestArtifact }], + }), + }); +}); + +afterEach(() => { + app.cleanup(); +}); + +test('can use stack outputs as validation inputs', () => { + // GIVEN + const stage = new AppWithStackOutput(app, 'MyApp'); + + // WHEN + const pipeStage = pipeline.addApplicationStage(stage); + pipeStage.addActions(new cdkp.ShellScriptAction({ + actionName: 'TestOutput', + useOutputs: { + BUCKET_NAME: pipeline.stackOutput(stage.output), + }, + commands: ['echo $BUCKET_NAME'], + })); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'MyApp', + Actions: arrayWith( + deepObjectLike({ + Name: 'Stack.Deploy', + OutputArtifacts: [{ Name: anything() }], + Configuration: { + OutputFileName: 'outputs.json', + }, + }), + deepObjectLike({ + ActionTypeId: { + Provider: 'CodeBuild', + }, + Configuration: { + ProjectName: anything(), + }, + InputArtifacts: [{ Name: anything() }], + Name: 'TestOutput', + }), + ), + }), + }); + + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + build: { + commands: [ + 'set -eu', + 'export BUCKET_NAME="$(node -pe \'require(process.env.CODEBUILD_SRC_DIR + "/outputs.json")["BucketName"]\')"', + 'echo $BUCKET_NAME', + ], + }, + }, + })), + Type: 'CODEPIPELINE', + }, + }); +}); + +test('can use additional files from source', () => { + // WHEN + pipeline.addStage('Test').addActions(new cdkp.ShellScriptAction({ + actionName: 'UseSources', + additionalArtifacts: [sourceArtifact], + commands: ['true'], + })); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'Test', + Actions: [ + deepObjectLike({ + Name: 'UseSources', + InputArtifacts: [ { Name: 'Artifact_Source_GitHub' } ], + }), + ], + }), + }); + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + build: { + commands: [ + 'set -eu', + 'true', + ], + }, + }, + })), + }, + }); +}); + +test('can use additional files from build', () => { + // WHEN + pipeline.addStage('Test').addActions(new cdkp.ShellScriptAction({ + actionName: 'UseBuildArtifact', + additionalArtifacts: [integTestArtifact], + commands: ['true'], + })); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'Test', + Actions: [ + deepObjectLike({ + Name: 'UseBuildArtifact', + InputArtifacts: [ { Name: 'IntegTests' } ], + }), + ], + }), + }); + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + build: { + commands: [ + 'set -eu', + 'true', + ], + }, + }, + })), + }, + }); +}); + +class AppWithStackOutput extends Stage { + public readonly output: CfnOutput; + + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + const stack = new BucketStack(this, 'Stack'); + + this.output = new CfnOutput(stack, 'BucketName', { + value: stack.bucket.bucketName, + }); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/region-info/.gitignore b/packages/@aws-cdk/region-info/.gitignore index c592ef0622ef6..cbc62ee109441 100644 --- a/packages/@aws-cdk/region-info/.gitignore +++ b/packages/@aws-cdk/region-info/.gitignore @@ -13,3 +13,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/region-info/.npmignore b/packages/@aws-cdk/region-info/.npmignore index 563a051f5ad88..d32b63723f317 100644 --- a/packages/@aws-cdk/region-info/.npmignore +++ b/packages/@aws-cdk/region-info/.npmignore @@ -20,4 +20,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts new file mode 100644 index 0000000000000..b93ca024c9094 --- /dev/null +++ b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts @@ -0,0 +1,106 @@ +export const AWS_OLDER_REGIONS = new Set([ + 'us-east-1', + 'us-west-1', + 'us-west-2', + 'ap-southeast-1', + 'ap-southeast-2', + 'ap-northeast-1', + 'sa-east-1', + 'eu-west-1', +]); + +export const AWS_CDK_METADATA = new Set([ + 'us-east-2', + 'us-east-1', + 'us-west-1', + 'us-west-2', + // 'us-gov-east-1', + // 'us-gov-west-1', + // 'us-iso-east-1', + // 'us-isob-east-1', + 'ap-south-1', + 'ap-east-1', + // 'ap-northeast-3', + 'ap-northeast-2', + 'ap-southeast-1', + 'ap-southeast-2', + 'ap-northeast-1', + 'ca-central-1', + 'cn-north-1', + 'cn-northwest-1', + 'eu-central-1', + 'eu-west-1', + 'eu-west-2', + 'eu-west-3', + 'eu-north-1', + 'me-south-1', + 'sa-east-1', +]); + +/** + * The hosted zone Id if using an alias record in Route53. + * + * @see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_website_region_endpoints + */ +export const ROUTE_53_BUCKET_WEBSITE_ZONE_IDS: { [region: string]: string } = { + 'us-east-2': 'Z2O1EMRO9K5GLX', + 'us-east-1': 'Z3AQBSTGFYJSTF', + 'us-west-1': 'Z2F56UZL2M1ACD', + 'us-west-2': 'Z3BJ6K6RIION7M', + 'us-gov-east-1': 'Z2NIFVYYW2VKV1', + 'us-gov-west-1': 'Z31GFT0UA1I2HV', + 'ap-east-1': 'ZNB98KWMFR0R6', + 'ap-south-1': 'Z11RGJOFQNVJUP', + 'ap-northeast-3': 'Z2YQB5RD63NC85', + 'ap-northeast-2': 'Z3W03O7B5YMIYP', + 'ap-southeast-1': 'Z3O0J2DXBE1FTB', + 'ap-southeast-2': 'Z1WCIGYICN2BYD', + 'ap-northeast-1': 'Z2M4EHUR26P7ZW', + 'ca-central-1': 'Z1QDHH18159H29', + 'eu-central-1': 'Z21DNDUVLTQW6Q', + 'eu-west-1': 'Z1BKCTXD74EZPE', + 'eu-west-2': 'Z3GKZC51ZF0DB4', + 'eu-west-3': 'Z3R1K369G5AVDG', + 'eu-north-1': 'Z3BAZG2TWCNX0D', + 'sa-east-1': 'Z7KQH4QJS55SO', + 'me-south-1': 'Z1MPMWCPA7YB62', +}; + +interface Region { partition: string, domainSuffix: string } + +export const PARTITION_MAP: { [region: string]: Region } = { + 'default': { partition: 'aws', domainSuffix: 'amazonaws.com' }, + 'cn-': { partition: 'aws-cn', domainSuffix: 'amazonaws.com.cn' }, + 'us-gov-': { partition: 'aws-us-gov', domainSuffix: 'amazonaws.com' }, + 'us-iso-': { partition: 'aws-iso', domainSuffix: 'c2s.ic.gov' }, + 'us-isob-': { partition: 'aws-iso-b', domainSuffix: 'sc2s.sgov.gov' }, +}; + +// https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-logging-bucket-permissions +export const ELBV2_ACCOUNTS: { [region: string]: string } = { + 'us-east-1': '127311923021', + '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', + 'ap-northeast-2': '600734575887', + 'ap-northeast-3': '383597477331', + 'ap-southeast-1': '114774131450', + 'ap-southeast-2': '783225319266', + 'ap-south-1': '718504428378', + 'me-south-1': '076674570225', + 'sa-east-1': '507241528517', + 'us-gov-west-1': '048591011584', + 'us-gov-east-1': '190560391635', + 'cn-north-1': '638102146993', + 'cn-northwest-1': '037604701340', +}; \ No newline at end of file diff --git a/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts b/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts index fedc5e8ad7583..abcc335b308d0 100644 --- a/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts +++ b/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts @@ -1,14 +1,15 @@ -import * as fs from 'fs-extra'; import * as path from 'path'; +import * as fs from 'fs-extra'; import { Default } from '../lib/default'; import { AWS_REGIONS, AWS_SERVICES } from './aws-entities'; +import { AWS_CDK_METADATA, AWS_OLDER_REGIONS, ELBV2_ACCOUNTS, PARTITION_MAP, ROUTE_53_BUCKET_WEBSITE_ZONE_IDS } from './fact-tables'; async function main(): Promise { const lines = [ "import { Fact, FactName } from './fact';", '', - '// tslint:disable:object-literal-key-quotes', - '// tslint:disable:max-line-length', + '/* eslint-disable quote-props */', + '/* eslint-disable max-len */', '', '/**', ' * Built-in regional information, re-generated by `npm run build`.', @@ -22,84 +23,6 @@ async function main(): Promise { ' public static register(): void {', ]; - const AWS_OLDER_REGIONS = new Set([ - 'us-east-1', - 'us-west-1', - 'us-west-2', - 'ap-southeast-1', - 'ap-southeast-2', - 'ap-northeast-1', - 'sa-east-1', - 'eu-west-1', - ]); - - const AWS_CDK_METADATA = new Set([ - 'us-east-2', - 'us-east-1', - 'us-west-1', - 'us-west-2', - // 'us-gov-east-1', - // 'us-gov-west-1', - // 'us-iso-east-1', - // 'us-isob-east-1', - 'ap-south-1', - 'ap-east-1', - // 'ap-northeast-3', - 'ap-northeast-2', - 'ap-southeast-1', - 'ap-southeast-2', - 'ap-northeast-1', - 'ca-central-1', - 'cn-north-1', - 'cn-northwest-1', - 'eu-central-1', - 'eu-west-1', - 'eu-west-2', - 'eu-west-3', - 'eu-north-1', - 'me-south-1', - 'sa-east-1', - ]); - - /** - * The hosted zone Id if using an alias record in Route53. - * - * @see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_website_region_endpoints - */ - const ROUTE_53_BUCKET_WEBSITE_ZONE_IDS: { [region: string]: string } = { - 'us-east-2': 'Z2O1EMRO9K5GLX', - 'us-east-1': 'Z3AQBSTGFYJSTF', - 'us-west-1': 'Z2F56UZL2M1ACD', - 'us-west-2': 'Z3BJ6K6RIION7M', - 'us-gov-east-1': 'Z2NIFVYYW2VKV1', - 'us-gov-west-1': 'Z31GFT0UA1I2HV', - 'ap-east-1': 'ZNB98KWMFR0R6', - 'ap-south-1': 'Z11RGJOFQNVJUP', - 'ap-northeast-3': 'Z2YQB5RD63NC85', - 'ap-northeast-2': 'Z3W03O7B5YMIYP', - 'ap-southeast-1': 'Z3O0J2DXBE1FTB', - 'ap-southeast-2': 'Z1WCIGYICN2BYD', - 'ap-northeast-1': 'Z2M4EHUR26P7ZW', - 'ca-central-1': 'Z1QDHH18159H29', - 'eu-central-1': 'Z21DNDUVLTQW6Q', - 'eu-west-1': 'Z1BKCTXD74EZPE', - 'eu-west-2': 'Z3GKZC51ZF0DB4', - 'eu-west-3': 'Z3R1K369G5AVDG', - 'eu-north-1': 'Z3BAZG2TWCNX0D', - 'sa-east-1': 'Z7KQH4QJS55SO', - 'me-south-1': 'Z1MPMWCPA7YB62', - }; - - interface IRegion { partition: string, domainSuffix: string } - - const PARTITION_MAP: { [region: string]: IRegion } = { - 'default': { partition: 'aws', domainSuffix: 'amazonaws.com' }, - 'cn-': { partition: 'aws-cn', domainSuffix: 'amazonaws.com.cn' }, - 'us-gov-': { partition: 'aws-us-gov', domainSuffix: 'amazonaws.com' }, - 'us-iso-': { partition: 'aws-iso', domainSuffix: 'c2s.ic.gov' }, - 'us-isob-': { partition: 'aws-iso-b', domainSuffix: 'sc2s.sgov.gov' }, - }; - const defaultMap = 'default'; for (const region of AWS_REGIONS) { @@ -124,6 +47,8 @@ async function main(): Promise { registerFact(region, 'S3_STATIC_WEBSITE_ZONE_53_HOSTED_ZONE_ID', ROUTE_53_BUCKET_WEBSITE_ZONE_IDS[region] || ''); + registerFact(region, 'ELBV2_ACCOUNT', ELBV2_ACCOUNTS[region]); + const vpcEndpointServiceNamePrefix = `${domainSuffix.split('.').reverse().join('.')}.vpce`; registerFact(region, 'VPC_ENDPOINT_SERVICE_NAME_PREFIX', vpcEndpointServiceNamePrefix); @@ -145,7 +70,7 @@ async function main(): Promise { } main().catch(e => { - // tslint:disable-next-line: no-console + // eslint-disable-next-line no-console console.error(e); process.exit(-1); }); diff --git a/packages/@aws-cdk/region-info/jest.config.js b/packages/@aws-cdk/region-info/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/region-info/jest.config.js +++ b/packages/@aws-cdk/region-info/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/region-info/lib/fact.ts b/packages/@aws-cdk/region-info/lib/fact.ts index c161efafe3229..3936f9c129449 100644 --- a/packages/@aws-cdk/region-info/lib/fact.ts +++ b/packages/@aws-cdk/region-info/lib/fact.ts @@ -51,7 +51,9 @@ export class Fact { if (fact.name in regionFacts && regionFacts[fact.name] !== fact.value && !allowReplacing) { throw new Error(`Region ${fact.region} already has a fact ${fact.name}, with value ${regionFacts[fact.name]}`); } - regionFacts[fact.name] = fact.value; + if (fact.value !== undefined) { + regionFacts[fact.name] = fact.value; + } } /** @@ -93,7 +95,7 @@ export interface IFact { /** * The value of this fact. */ - readonly value: string; + readonly value: string | undefined; } /** @@ -133,6 +135,11 @@ export class FactName { */ public static readonly VPC_ENDPOINT_SERVICE_NAME_PREFIX = 'vpcEndpointServiceNamePrefix'; + /** + * The account for ELBv2 in this region + */ + public static readonly ELBV2_ACCOUNT = 'elbv2Account'; + /** * The name of the regional service principal for a given service. * diff --git a/packages/@aws-cdk/region-info/lib/region-info.ts b/packages/@aws-cdk/region-info/lib/region-info.ts index 768d8be6c0c08..cc424bf304bf1 100644 --- a/packages/@aws-cdk/region-info/lib/region-info.ts +++ b/packages/@aws-cdk/region-info/lib/region-info.ts @@ -74,4 +74,12 @@ export class RegionInfo { public servicePrincipal(service: string): string | undefined { return Fact.find(this.name, FactName.servicePrincipal(service)); } + + /** + * The account ID for ELBv2 in this region + * + */ + public get elbv2Account(): string | undefined { + return Fact.find(this.name, FactName.ELBV2_ACCOUNT); + } } diff --git a/packages/@monocdk-experiment/assert/.gitignore b/packages/@monocdk-experiment/assert/.gitignore index 17e3ae0ad2a83..6a8dcb15f4bcf 100644 --- a/packages/@monocdk-experiment/assert/.gitignore +++ b/packages/@monocdk-experiment/assert/.gitignore @@ -19,3 +19,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@monocdk-experiment/assert/jest.config.js b/packages/@monocdk-experiment/assert/jest.config.js index f81b80f39a2aa..582b2b3040eb0 100644 --- a/packages/@monocdk-experiment/assert/jest.config.js +++ b/packages/@monocdk-experiment/assert/jest.config.js @@ -1,10 +1,10 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = { ...baseConfig, coverageThreshold: { global: { statements: 75, - branches: 65, + branches: 60, }, }, }; diff --git a/packages/@monocdk-experiment/assert/package.json b/packages/@monocdk-experiment/assert/package.json index 6a923bd5971a3..cf0bba04b3f34 100644 --- a/packages/@monocdk-experiment/assert/package.json +++ b/packages/@monocdk-experiment/assert/package.json @@ -22,9 +22,6 @@ "eslint": { "disable": true }, - "tslint": { - "disable": true - }, "pkglint": { "disable": true } @@ -36,12 +33,12 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^26.0.3", - "@types/node": "^10.17.26", + "@types/jest": "^26.0.4", + "@types/node": "^10.17.27", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", "pkglint": "0.0.0", - "ts-jest": "^26.1.1", + "ts-jest": "^26.1.3", "@monocdk-experiment/rewrite-imports": "0.0.0", "monocdk-experiment": "0.0.0", "constructs": "^3.0.2" diff --git a/packages/@monocdk-experiment/rewrite-imports/.gitignore b/packages/@monocdk-experiment/rewrite-imports/.gitignore index 864eca1bfa974..6b33b2cabe8ed 100644 --- a/packages/@monocdk-experiment/rewrite-imports/.gitignore +++ b/packages/@monocdk-experiment/rewrite-imports/.gitignore @@ -11,3 +11,5 @@ coverage nyc.config.js !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@monocdk-experiment/rewrite-imports/.npmignore b/packages/@monocdk-experiment/rewrite-imports/.npmignore index 5b887f0a51fee..1a0946f499b8d 100644 --- a/packages/@monocdk-experiment/rewrite-imports/.npmignore +++ b/packages/@monocdk-experiment/rewrite-imports/.npmignore @@ -12,4 +12,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/@monocdk-experiment/rewrite-imports/bin/rewrite-imports.ts b/packages/@monocdk-experiment/rewrite-imports/bin/rewrite-imports.ts index 6193a04f5715e..fe70da35a582b 100644 --- a/packages/@monocdk-experiment/rewrite-imports/bin/rewrite-imports.ts +++ b/packages/@monocdk-experiment/rewrite-imports/bin/rewrite-imports.ts @@ -1,4 +1,4 @@ -// tslint:disable: no-console +/* eslint-disable no-console */ import * as fs from 'fs'; import * as _glob from 'glob'; diff --git a/packages/@monocdk-experiment/rewrite-imports/jest.config.js b/packages/@monocdk-experiment/rewrite-imports/jest.config.js index f81b80f39a2aa..ac8c47076506a 100644 --- a/packages/@monocdk-experiment/rewrite-imports/jest.config.js +++ b/packages/@monocdk-experiment/rewrite-imports/jest.config.js @@ -1,4 +1,4 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = { ...baseConfig, coverageThreshold: { diff --git a/packages/@monocdk-experiment/rewrite-imports/package.json b/packages/@monocdk-experiment/rewrite-imports/package.json index a159df11280b2..d3498e7969895 100644 --- a/packages/@monocdk-experiment/rewrite-imports/package.json +++ b/packages/@monocdk-experiment/rewrite-imports/package.json @@ -32,12 +32,12 @@ "license": "Apache-2.0", "dependencies": { "glob": "^7.1.6", - "typescript": "~3.9.5" + "typescript": "~3.9.6" }, "devDependencies": { - "@types/glob": "^7.1.2", - "@types/jest": "^26.0.3", - "@types/node": "^10.17.26", + "@types/glob": "^7.1.3", + "@types/jest": "^26.0.4", + "@types/node": "^10.17.27", "cdk-build-tools": "0.0.0", "pkglint": "0.0.0" }, diff --git a/packages/aws-cdk/.gitignore b/packages/aws-cdk/.gitignore index 59c135c41f21b..d94ce1fee2e9b 100644 --- a/packages/aws-cdk/.gitignore +++ b/packages/aws-cdk/.gitignore @@ -35,3 +35,5 @@ test/integ/cli/*.d.ts !test/integ/cli-regression-patches/**/* .DS_Store + +junit.xml \ No newline at end of file diff --git a/packages/aws-cdk/.npmignore b/packages/aws-cdk/.npmignore index bb0195dca43a2..64b361e8e8b52 100644 --- a/packages/aws-cdk/.npmignore +++ b/packages/aws-cdk/.npmignore @@ -30,4 +30,5 @@ jest.config.js .DS_Store # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/aws-cdk/README.md b/packages/aws-cdk/README.md index ad31e14b4214a..863543b606d93 100644 --- a/packages/aws-cdk/README.md +++ b/packages/aws-cdk/README.md @@ -250,6 +250,9 @@ $ # Deploys only to environments foo and bar $ cdk bootstrap --app='node bin/main.js' foo bar ``` +By default, bootstrap stack will be protected from stack termination. This can be disabled using +`--termination-protection` argument. + #### `cdk doctor` Inspect the current command-line environment and configurations, and collect information that can be useful for troubleshooting problems. It is usually a good idea to include the information provided by this command when submitting diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index c40c914714187..3fd3dd1170531 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -13,13 +13,15 @@ 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, setLogLevel } from '../lib/logging'; +import { data, debug, error, print, setLogLevel } from '../lib/logging'; import { PluginHost } from '../lib/plugin'; import { serializeStructure } from '../lib/serialize'; import { Configuration, Settings } from '../lib/settings'; import * as version from '../lib/version'; -// tslint:disable:no-shadowed-variable max-line-length +/* eslint-disable max-len */ +/* eslint-disable no-shadow */ // yargs + async function parseCommandLineArguments() { // Use the following configuration for array arguments: // @@ -73,7 +75,8 @@ async function parseCommandLineArguments() { .option('execute', {type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)', default: true}) .option('trust', { type: 'array', desc: 'The AWS account IDs that should be trusted to perform deployments into this environment (may be repeated)', default: [], nargs: 1, requiresArg: true, hidden: true }) .option('cloudformation-execution-policies', { type: 'array', desc: 'The Managed Policy ARNs that should be attached to the role performing deployments into this environment. Required if --trust was passed (may be repeated)', default: [], nargs: 1, requiresArg: true, hidden: true }) - .option('force', { alias: 'f', type: 'boolean', desc: 'Always bootstrap even if it would downgrade template version', default: false }), + .option('force', { alias: 'f', type: 'boolean', desc: 'Always bootstrap even if it would downgrade template version', default: false }) + .option('termination-protection', { type: 'boolean', default: false, desc: 'Toggle CloudFormation termination protection on the bootstrap stacks' }), ) .command('deploy [STACKS..]', 'Deploys the stack(s) named STACKS into your AWS account', yargs => yargs .option('build-exclude', { type: 'array', alias: 'E', nargs: 1, desc: 'Do not rebuild asset with the given ID. Can be specified multiple times.', default: [] }) @@ -227,9 +230,20 @@ async function initCommandLine() { }); case 'bootstrap': + // Use new bootstrapping if it's requested via environment variable, or if + // new style stack synthesis has been configured in `cdk.json`. + let useNewBootstrapping = false; + if (process.env.CDK_NEW_BOOTSTRAP) { + print('CDK_NEW_BOOTSTRAP set, using new-style bootstrapping'); + useNewBootstrapping = true; + } else if (configuration.context.get(cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT)) { + print(`'${cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT}' context set, using new-style bootstrapping`); + useNewBootstrapping = true; + } + return await cli.bootstrap(args.ENVIRONMENTS, toolkitStackName, args.roleArn, - !!process.env.CDK_NEW_BOOTSTRAP, + useNewBootstrapping, argv.force, { bucketName: configuration.settings.get(['toolkitBucket', 'bucketName']), @@ -240,6 +254,7 @@ async function initCommandLine() { execute: args.execute, trustedAccounts: args.trust, cloudFormationExecutionPolicies: args.cloudformationExecutionPolicies, + terminationProtection: args.terminationProtection, }); case 'deploy': diff --git a/packages/aws-cdk/jest.config.js b/packages/aws-cdk/jest.config.js index 797dd0665910a..72f278bd0ba52 100644 --- a/packages/aws-cdk/jest.config.js +++ b/packages/aws-cdk/jest.config.js @@ -1,4 +1,4 @@ -const baseConfig = require('../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = { ...baseConfig, coverageThreshold: { diff --git a/packages/aws-cdk/lib/api/aws-auth/account-cache.ts b/packages/aws-cdk/lib/api/aws-auth/account-cache.ts index 12ca6896ea4c5..75082b9953966 100644 --- a/packages/aws-cdk/lib/api/aws-auth/account-cache.ts +++ b/packages/aws-cdk/lib/api/aws-auth/account-cache.ts @@ -1,5 +1,5 @@ -import * as fs from 'fs-extra'; import * as path from 'path'; +import * as fs from 'fs-extra'; import { debug } from '../../logging'; import { cdkCacheDir } from '../../util/directories'; import { Account } from './sdk-provider'; diff --git a/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts b/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts index dc1c5df8c8ce8..0baeaa323f81e 100644 --- a/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts +++ b/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts @@ -1,9 +1,9 @@ -import * as AWS from 'aws-sdk'; import * as child_process from 'child_process'; -import * as fs from 'fs-extra'; import * as os from 'os'; import * as path from 'path'; import * as util from 'util'; +import * as AWS from 'aws-sdk'; +import * as fs from 'fs-extra'; import { debug } from '../../logging'; import { SharedIniFile } from './sdk_ini_file'; diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts index 499a5af46beb0..8822c2603a237 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts @@ -1,10 +1,10 @@ +import * as https from 'https'; +import * as os from 'os'; +import * as path from 'path'; import * as cxapi from '@aws-cdk/cx-api'; import * as AWS from 'aws-sdk'; import { ConfigurationOptions } from 'aws-sdk/lib/config'; import * as fs from 'fs-extra'; -import * as https from 'https'; -import * as os from 'os'; -import * as path from 'path'; import { debug } from '../../logging'; import { cached } from '../../util/functions'; import { CredentialPlugins } from '../aws-auth/credential-plugins'; diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk_ini_file.ts b/packages/aws-cdk/lib/api/aws-auth/sdk_ini_file.ts index ef49fce2b789b..91c50e87c0a97 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk_ini_file.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk_ini_file.ts @@ -5,10 +5,10 @@ * region at runtime, but unfortunately it is private upstream. */ -import * as AWS from 'aws-sdk'; -import * as fs from 'fs-extra'; import * as os from 'os'; import * as path from 'path'; +import * as AWS from 'aws-sdk'; +import * as fs from 'fs-extra'; export interface SharedIniFileOptions { isConfig?: boolean; diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts index 71e85d77e9d69..03205b9e743f9 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts @@ -1,5 +1,5 @@ -import * as cxapi from '@aws-cdk/cx-api'; import * as path from 'path'; +import * as cxapi from '@aws-cdk/cx-api'; import { loadStructuredFile } from '../../serialize'; import { SdkProvider } from '../aws-auth'; import { DeployStackResult } from '../deploy-stack'; @@ -7,14 +7,14 @@ import { BootstrapEnvironmentOptions } from './bootstrap-props'; import { deployBootstrapStack } from './deploy-bootstrap'; import { legacyBootstrapTemplate } from './legacy-template'; -// tslint:disable:max-line-length +/* eslint-disable max-len */ /** * Deploy legacy bootstrap stack * * @experimental */ -export async function bootstrapEnvironment(environment: cxapi.Environment, sdkProvider: SdkProvider, options: BootstrapEnvironmentOptions): Promise { +export async function bootstrapEnvironment(environment: cxapi.Environment, sdkProvider: SdkProvider, options: BootstrapEnvironmentOptions = {}): Promise { const params = options.parameters ?? {}; if (params.trustedAccounts?.length) { @@ -43,7 +43,7 @@ export async function bootstrapEnvironment(environment: cxapi.Environment, sdkPr export async function bootstrapEnvironment2( environment: cxapi.Environment, sdkProvider: SdkProvider, - options: BootstrapEnvironmentOptions): Promise { + options: BootstrapEnvironmentOptions = {}): Promise { const params = options.parameters ?? {}; diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts index 2a90bd9033908..ff91b55227c75 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts @@ -77,4 +77,11 @@ export interface BootstrappingParameters { * @default true */ readonly publicAccessBlockConfiguration?: boolean; + + /** + * Whether the stacks created by the bootstrap process should be protected from termination. + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-protect-stacks.html + * @default true + */ + readonly terminationProtection?: boolean; } \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts index 3ced4622c74b7..c30f9cb6138d8 100644 --- a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts +++ b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts @@ -1,8 +1,8 @@ +import * as os from 'os'; +import * as path from 'path'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs-extra'; -import * as os from 'os'; -import * as path from 'path'; import { Mode, SdkProvider } from '../aws-auth'; import { deployStack, DeployStackResult } from '../deploy-stack'; import { DEFAULT_TOOLKIT_STACK_NAME, ToolkitInfo } from '../toolkit-info'; @@ -39,6 +39,7 @@ export async function deployBootstrapStack( environment: cxapi.EnvironmentUtils.format(environment.account, environment.region), properties: { templateFile, + terminationProtection: options.parameters?.terminationProtection ?? false, }, }); diff --git a/packages/aws-cdk/lib/api/bootstrap/legacy-template.ts b/packages/aws-cdk/lib/api/bootstrap/legacy-template.ts index 0b98ea3a3fbe2..2ba916f192aeb 100644 --- a/packages/aws-cdk/lib/api/bootstrap/legacy-template.ts +++ b/packages/aws-cdk/lib/api/bootstrap/legacy-template.ts @@ -5,7 +5,7 @@ export function legacyBootstrapTemplate(params: BootstrappingParameters): any { Description: 'The CDK Toolkit Stack. It was created by `cdk bootstrap` and manages resources necessary for managing your Cloud Applications with AWS CDK.', Conditions: { UsePublicAccessBlockConfiguration: { - 'Fn::Equals' : [ + 'Fn::Equals': [ params.publicAccessBlockConfiguration || params.publicAccessBlockConfiguration === undefined ? 'true' : 'false', 'true', ]}, @@ -25,7 +25,7 @@ export function legacyBootstrapTemplate(params: BootstrappingParameters): any { }], }, PublicAccessBlockConfiguration: { - 'Fn::If' : [ + 'Fn::If': [ 'UsePublicAccessBlockConfiguration', { BlockPublicAcls: true, diff --git a/packages/aws-cdk/lib/api/cxapp/environments.ts b/packages/aws-cdk/lib/api/cxapp/environments.ts index d95d44b3ea38e..f79d6de2847ad 100644 --- a/packages/aws-cdk/lib/api/cxapp/environments.ts +++ b/packages/aws-cdk/lib/api/cxapp/environments.ts @@ -7,7 +7,7 @@ export function looksLikeGlob(environment: string) { return environment.indexOf('*') > -1; } -// tslint:disable-next-line:max-line-length +// eslint-disable-next-line max-len export async function globEnvironmentsFromStacks(stacks: StackCollection, environmentGlobs: string[], sdk: SdkProvider): Promise { if (environmentGlobs.length === 0) { return []; } diff --git a/packages/aws-cdk/lib/api/cxapp/exec.ts b/packages/aws-cdk/lib/api/cxapp/exec.ts index 93ddb1264f2ff..c3b4f998617b6 100644 --- a/packages/aws-cdk/lib/api/cxapp/exec.ts +++ b/packages/aws-cdk/lib/api/cxapp/exec.ts @@ -1,8 +1,8 @@ +import * as childProcess from 'child_process'; +import * as path from 'path'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; -import * as childProcess from 'child_process'; import * as fs from 'fs-extra'; -import * as path from 'path'; import { debug } from '../../logging'; import { Configuration, PROJECT_CONFIG, USER_DEFAULTS } from '../../settings'; import { versionNumber } from '../../version'; @@ -191,7 +191,7 @@ async function guessExecutable(commandLine: string[]) { return commandLine; } - // tslint:disable-next-line:no-bitwise + // eslint-disable-next-line no-bitwise const isExecutable = (fstat.mode & fs.constants.X_OK) !== 0; const isWindows = process.platform === 'win32'; diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index bf9267986bdea..b0090ec1770e9 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -10,7 +10,7 @@ import { publishAssets } from '../util/asset-publishing'; import { contentHash } from '../util/content-hash'; import { ISDK, SdkProvider } from './aws-auth'; import { ToolkitInfo } from './toolkit-info'; -import { changeSetHasNoChanges, CloudFormationStack, StackParameters, TemplateParameters, waitForChangeSet, waitForStack } from './util/cloudformation'; +import { changeSetHasNoChanges, CloudFormationStack, StackParameters, TemplateParameters, waitForChangeSet, waitForStackDeploy, waitForStackDelete } from './util/cloudformation'; import { StackActivityMonitor } from './util/cloudformation/stack-activity-monitor'; // We need to map regions to domain suffixes, and the SDK already has a function to do this. @@ -20,7 +20,6 @@ import { StackActivityMonitor } from './util/cloudformation/stack-activity-monit // refactor that away. /* eslint-disable @typescript-eslint/no-require-imports */ -// tslint:disable-next-line: no-var-requires const regionUtil = require('aws-sdk/lib/region_config'); /* eslint-enable @typescript-eslint/no-require-imports */ @@ -176,7 +175,7 @@ export async function deployStack(options: DeployStackOptions): Promise(valueProvider: () => Promise, ti * * @returns the CloudFormation description of the ChangeSet */ -// tslint:disable-next-line:max-line-length +// eslint-disable-next-line max-len export async function waitForChangeSet(cfn: CloudFormation, stackName: string, changeSetName: string): Promise { debug('Waiting for changeset %s on stack %s to finish creating...', changeSetName, stackName); const ret = await waitFor(async () => { @@ -216,7 +216,7 @@ export async function waitForChangeSet(cfn: CloudFormation, stackName: string, c return description; } - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`Failed to create ChangeSet ${changeSetName} on ${stackName}: ${description.Status || 'NO_STATUS'}, ${description.StatusReason || 'no reason provided'}`); }); @@ -241,36 +241,62 @@ export function changeSetHasNoChanges(description: CloudFormation.DescribeChange } /** - * Waits for a CloudFormation stack to stabilize in a complete/available state. + * Waits for a CloudFormation stack to stabilize in a complete/available state + * after a delete operation is issued. * - * Fails if the stacks is not in a SUCCESSFUL state. + * Fails if the stack is in a FAILED state. Will not fail if the stack was + * already deleted. * * @param cfn a CloudFormation client - * @param stackName the name of the stack to wait for - * @param failOnDeletedStack whether to fail if the awaited stack is deleted. + * @param stackName the name of the stack to wait for after a delete * - * @returns the CloudFormation description of the stabilized stack + * @returns the CloudFormation description of the stabilized stack after the delete attempt */ -export async function waitForStack( +export async function waitForStackDelete( cfn: CloudFormation, - stackName: string, - failOnDeletedStack: boolean = true): Promise { + stackName: string): Promise { const stack = await stabilizeStack(cfn, stackName); if (!stack) { return undefined; } const status = stack.stackStatus; - if (status.isCreationFailure) { - throw new Error(`The stack named ${stackName} failed creation, it may need to be manually deleted from the AWS console: ${status}`); - } else if (!status.isSuccess) { - throw new Error(`The stack named ${stackName} is in a failed state: ${status}`); + if (status.isFailure) { + throw new Error(`The stack named ${stackName} is in a failed state. You may need to delete it from the AWS console : ${status}`); } else if (status.isDeleted) { - if (failOnDeletedStack) { throw new Error(`The stack named ${stackName} was deleted`); } return undefined; } return stack; } +/** + * Waits for a CloudFormation stack to stabilize in a complete/available state + * after an update/create operation is issued. + * + * Fails if the stack is in a FAILED state, ROLLBACK state, or DELETED state. + * + * @param cfn a CloudFormation client + * @param stackName the name of the stack to wait for after an update + * + * @returns the CloudFormation description of the stabilized stack after the update attempt + */ +export async function waitForStackDeploy( + cfn: CloudFormation, + stackName: string): Promise { + + const stack = await stabilizeStack(cfn, stackName); + if (!stack) { return undefined; } + + const status = stack.stackStatus; + + if (status.isCreationFailure) { + throw new Error(`The stack named ${stackName} failed creation, it may need to be manually deleted from the AWS console: ${status}`); + } else if (!status.isDeploySuccess) { + throw new Error(`The stack named ${stackName} failed to deploy: ${status}`); + } + + return stack; +} + /** * Wait for a stack to become stable (no longer _IN_PROGRESS), returning it */ @@ -283,8 +309,8 @@ export async function stabilizeStack(cfn: CloudFormation, stackName: string) { return null; } const status = stack.stackStatus; - if (!status.isStable) { - debug('Stack %s is still not stable (%s)', stackName, status); + if (status.isInProgress) { + debug('Stack %s has an ongoing operation in progress and is not stable (%s)', stackName, status); return undefined; } 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 7df4680c7fb3c..49025b9c8df60 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 @@ -1,8 +1,8 @@ +import * as util from 'util'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; 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, logLevel, LogLevel, setLogLevel } from '../../../logging'; import { RewritableBlock } from '../display'; diff --git a/packages/aws-cdk/lib/api/util/cloudformation/stack-status.ts b/packages/aws-cdk/lib/api/util/cloudformation/stack-status.ts index 95a8c7757ad79..e26886225eea8 100644 --- a/packages/aws-cdk/lib/api/util/cloudformation/stack-status.ts +++ b/packages/aws-cdk/lib/api/util/cloudformation/stack-status.ts @@ -25,20 +25,16 @@ export class StackStatus { return this.name.endsWith('FAILED'); } - get isRollback(): boolean { - return this.name.indexOf('ROLLBACK') !== -1; - } - - get isStable(): boolean { - return !this.name.endsWith('_IN_PROGRESS'); + get isInProgress(): boolean { + return this.name.endsWith('_IN_PROGRESS'); } get isNotFound(): boolean { return this.name === 'NOT_FOUND'; } - get isSuccess(): boolean { - return !this.isNotFound && !this.isRollback && !this.isFailure; + get isDeploySuccess(): boolean { + return !this.isNotFound && (this.name === 'CREATE_COMPLETE' || this.name === 'UPDATE_COMPLETE'); } public toString(): string { diff --git a/packages/aws-cdk/lib/assets.ts b/packages/aws-cdk/lib/assets.ts index dec8d01f52ba0..b6a8b6b0dfeb8 100644 --- a/packages/aws-cdk/lib/assets.ts +++ b/packages/aws-cdk/lib/assets.ts @@ -1,8 +1,8 @@ -// tslint:disable-next-line:max-line-length +// eslint-disable-next-line max-len +import * as path from 'path'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as colors from 'colors'; -import * as path from 'path'; import { ToolkitInfo } from './api/toolkit-info'; import { debug } from './logging'; import { AssetManifestBuilder } from './util/asset-manifest-builder'; @@ -13,7 +13,7 @@ import { AssetManifestBuilder } from './util/asset-manifest-builder'; * Returns the CloudFormation parameters that need to be sent to the template to * pass Asset coordinates. */ -// tslint:disable-next-line:max-line-length +// eslint-disable-next-line max-len export async function addMetadataAssetsToManifest(stack: cxapi.CloudFormationStackArtifact, assetManifest: AssetManifestBuilder, toolkitInfo?: ToolkitInfo, reuse?: string[]): Promise> { reuse = reuse || []; const assets = stack.assets; @@ -23,7 +23,7 @@ export async function addMetadataAssetsToManifest(stack: cxapi.CloudFormationSta } if (!toolkitInfo) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`This stack uses assets, so the toolkit stack must be deployed to the environment (Run "${colors.blue('cdk bootstrap ' + stack.environment!.name)}")`); } @@ -50,7 +50,7 @@ export async function addMetadataAssetsToManifest(stack: cxapi.CloudFormationSta return params; } -// tslint:disable-next-line:max-line-length +// eslint-disable-next-line max-len async function prepareAsset(asset: cxschema.AssetMetadataEntry, assetManifest: AssetManifestBuilder, toolkitInfo: ToolkitInfo): Promise> { switch (asset.packaging) { case 'zip': @@ -63,7 +63,7 @@ async function prepareAsset(asset: cxschema.AssetMetadataEntry, assetManifest: A case 'container-image': return await prepareDockerImageAsset(asset, assetManifest, toolkitInfo); default: - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`Unsupported packaging type: ${(asset as any).packaging}. You might need to upgrade your aws-cdk toolkit to support this asset type.`); } } diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index eaf5f3be69e6a..14071b05a0833 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -1,10 +1,10 @@ +import * as path from 'path'; +import { format } from 'util'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as colors from 'colors/safe'; import * as fs from 'fs-extra'; -import * as path from 'path'; import * as promptly from 'promptly'; -import { format } from 'util'; import { environmentsFromDescriptors, globEnvironmentsFromStacks, looksLikeGlob } from '../lib/api/cxapp/environments'; import { bootstrapEnvironment } from './api'; import { SdkProvider } from './api/aws-auth'; @@ -101,7 +101,7 @@ export class CdkToolkit { for (const stack of stacks.stackArtifacts) { stream.write(format('Stack %s\n', colors.bold(stack.displayName))); const currentTemplate = await this.props.cloudFormation.readCurrentTemplate(stack); - diffs = printStackDiff(currentTemplate, stack, strict, contextLines, stream); + diffs += printStackDiff(currentTemplate, stack, strict, contextLines, stream); } } @@ -134,7 +134,7 @@ export class CdkToolkit { for (const stack of stacks.stackArtifacts) { if (stacks.stackCount !== 1) { highlight(stack.displayName); } if (!stack.environment) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`Stack ${stack.displayName} does not define an environment, and AWS credentials could not be obtained from standard locations or no region was configured.`); } @@ -237,7 +237,7 @@ export class CdkToolkit { stacks = stacks.reversed(); if (!options.force) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len const confirmed = await promptly.confirm(`Are you sure you want to delete: ${colors.blue(stacks.stackArtifacts.map(s => s.id).join(', '))} (y/n)?`); if (!confirmed) { return; diff --git a/packages/aws-cdk/lib/commands/context.ts b/packages/aws-cdk/lib/commands/context.ts index c0cca57745fb1..748e72ba4aabe 100644 --- a/packages/aws-cdk/lib/commands/context.ts +++ b/packages/aws-cdk/lib/commands/context.ts @@ -74,7 +74,7 @@ function listContext(context: any) { print(renderTable(data, process.stdout.columns)); - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len print(`Run ${colors.blue('cdk context --reset KEY_OR_NUMBER')} to remove a context key. It will be refreshed on the next CDK synthesis run.`); } diff --git a/packages/aws-cdk/lib/commands/docs.ts b/packages/aws-cdk/lib/commands/docs.ts index d5767031377b0..ea5c34287e51d 100644 --- a/packages/aws-cdk/lib/commands/docs.ts +++ b/packages/aws-cdk/lib/commands/docs.ts @@ -1,6 +1,6 @@ import * as childProcess from 'child_process'; -import * as colors from 'colors/safe'; import * as process from 'process'; +import * as colors from 'colors/safe'; import * as yargs from 'yargs'; import { debug, print, warning } from '../../lib/logging'; import { CommandOptions } from '../command-api'; diff --git a/packages/aws-cdk/lib/commands/doctor.ts b/packages/aws-cdk/lib/commands/doctor.ts index 1c86b8dc7c33b..02a2bfcebacd0 100644 --- a/packages/aws-cdk/lib/commands/doctor.ts +++ b/packages/aws-cdk/lib/commands/doctor.ts @@ -1,6 +1,6 @@ +import * as process from 'process'; import * as cxapi from '@aws-cdk/cx-api'; import * as colors from 'colors/safe'; -import * as process from 'process'; import * as yargs from 'yargs'; import { print } from '../../lib/logging'; import * as version from '../../lib/version'; diff --git a/packages/aws-cdk/lib/context-providers/index.ts b/packages/aws-cdk/lib/context-providers/index.ts index b46717918308e..5390a210668d1 100644 --- a/packages/aws-cdk/lib/context-providers/index.ts +++ b/packages/aws-cdk/lib/context-providers/index.ts @@ -26,7 +26,7 @@ export async function provideContextValues( const key = missingContext.key; const constructor = availableContextProviders[missingContext.provider]; if (!constructor) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`Unrecognized context provider name: ${missingContext.provider}. You might need to update the toolkit to match the version of the construct library.`); } diff --git a/packages/aws-cdk/lib/context-providers/vpcs.ts b/packages/aws-cdk/lib/context-providers/vpcs.ts index a2b3dec92eedf..f91e829b4147c 100644 --- a/packages/aws-cdk/lib/context-providers/vpcs.ts +++ b/packages/aws-cdk/lib/context-providers/vpcs.ts @@ -70,7 +70,7 @@ export class VpcNetworkContextProviderPlugin implements ContextProviderPlugin { if (type === undefined) { type = SubnetType.Private; } if (!isValidSubnetType(type)) { - // tslint:disable-next-line: max-line-length + // eslint-disable-next-line max-len throw new Error(`Subnet ${subnet.SubnetArn} has invalid subnet type ${type} (must be ${SubnetType.Public}, ${SubnetType.Private} or ${SubnetType.Isolated})`); } diff --git a/packages/aws-cdk/lib/diff.ts b/packages/aws-cdk/lib/diff.ts index 866f691c203a7..0913e2bab5edd 100644 --- a/packages/aws-cdk/lib/diff.ts +++ b/packages/aws-cdk/lib/diff.ts @@ -59,7 +59,7 @@ export function printSecurityDiff(oldTemplate: any, newTemplate: cxapi.CloudForm const diff = cfnDiff.diffTemplate(oldTemplate, newTemplate.template); if (difRequiresApproval(diff, requireApproval)) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len warning(`This deployment will make potentially sensitive changes according to your current security approval level (--require-approval ${requireApproval}).`); warning('Please confirm you intend to make the following modifications:\n'); diff --git a/packages/aws-cdk/lib/init-templates/app/java/cdk.json b/packages/aws-cdk/lib/init-templates/app/java/cdk.json index 8d02402c68697..b112918622f63 100644 --- a/packages/aws-cdk/lib/init-templates/app/java/cdk.json +++ b/packages/aws-cdk/lib/init-templates/app/java/cdk.json @@ -1,3 +1,3 @@ { - "app": "mvn -e -q exec:java" + "app": "mvn -e -q compile exec:java" } diff --git a/packages/aws-cdk/lib/init-templates/sample-app/java/cdk.json b/packages/aws-cdk/lib/init-templates/sample-app/java/cdk.json index 8d02402c68697..b112918622f63 100644 --- a/packages/aws-cdk/lib/init-templates/sample-app/java/cdk.json +++ b/packages/aws-cdk/lib/init-templates/sample-app/java/cdk.json @@ -1,3 +1,3 @@ { - "app": "mvn -e -q exec:java" + "app": "mvn -e -q compile exec:java" } diff --git a/packages/aws-cdk/lib/init-templates/sample-app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py b/packages/aws-cdk/lib/init-templates/sample-app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py index afc07081c834a..910a4b838c933 100644 --- a/packages/aws-cdk/lib/init-templates/sample-app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py +++ b/packages/aws-cdk/lib/init-templates/sample-app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py @@ -6,6 +6,7 @@ core ) + class %name.PascalCased%Stack(core.Stack): def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: diff --git a/packages/aws-cdk/lib/init.ts b/packages/aws-cdk/lib/init.ts index cff039fbb9cee..c2027e596fddf 100644 --- a/packages/aws-cdk/lib/init.ts +++ b/packages/aws-cdk/lib/init.ts @@ -1,19 +1,18 @@ -import * as cxapi from '@aws-cdk/cx-api'; import * as childProcess from 'child_process'; +import * as path from 'path'; +import * as cxapi from '@aws-cdk/cx-api'; import * as colors from 'colors/safe'; import * as fs from 'fs-extra'; -import * as path from 'path'; import { error, print, warning } from './logging'; import { cdkHomeDir } from './util/directories'; export type InvokeHook = (targetDirectory: string) => Promise; -// tslint:disable:no-var-requires those libraries don't have up-to-date @types modules +/* eslint-disable @typescript-eslint/no-var-requires */ // Packages don't have @types module // eslint-disable-next-line @typescript-eslint/no-require-imports const camelCase = require('camelcase'); // eslint-disable-next-line @typescript-eslint/no-require-imports const decamelize = require('decamelize'); -// tslint:enable:no-var-requires const TEMPLATES_DIR = path.join(__dirname, 'init-templates'); diff --git a/packages/aws-cdk/lib/logging.ts b/packages/aws-cdk/lib/logging.ts index b85881886a84f..a2bb647adddef 100644 --- a/packages/aws-cdk/lib/logging.ts +++ b/packages/aws-cdk/lib/logging.ts @@ -1,6 +1,6 @@ -import * as colors from 'colors/safe'; import { Writable } from 'stream'; import * as util from 'util'; +import * as colors from 'colors/safe'; type StyleFn = (str: string) => string; const { stdout, stderr } = process; diff --git a/packages/aws-cdk/lib/plugin.ts b/packages/aws-cdk/lib/plugin.ts index be71bbdbc1004..4b4ad7956edc3 100644 --- a/packages/aws-cdk/lib/plugin.ts +++ b/packages/aws-cdk/lib/plugin.ts @@ -63,7 +63,6 @@ export class PluginHost { public load(moduleSpec: string) { try { /* eslint-disable @typescript-eslint/no-require-imports */ - // tslint:disable-next-line:no-var-requires const plugin = require(moduleSpec); /* eslint-enable */ if (!isPlugin(plugin)) { diff --git a/packages/aws-cdk/lib/serialize.ts b/packages/aws-cdk/lib/serialize.ts index dc759db0ca567..638c4f6314d9a 100644 --- a/packages/aws-cdk/lib/serialize.ts +++ b/packages/aws-cdk/lib/serialize.ts @@ -2,7 +2,6 @@ import * as fs from 'fs-extra'; import * as YAML from 'yaml'; /* eslint-disable @typescript-eslint/no-require-imports */ -// tslint:disable-next-line: no-var-requires const yamlTypes = require('yaml/types'); /* eslint-enable */ diff --git a/packages/aws-cdk/lib/settings.ts b/packages/aws-cdk/lib/settings.ts index 93e37e9d3eab3..e1db3969d0a69 100644 --- a/packages/aws-cdk/lib/settings.ts +++ b/packages/aws-cdk/lib/settings.ts @@ -1,6 +1,6 @@ -import * as fs from 'fs-extra'; import * as os from 'os'; import * as fs_path from 'path'; +import * as fs from 'fs-extra'; import { Tag } from './cdk-toolkit'; import { debug, warning } from './logging'; import * as util from './util'; @@ -329,7 +329,7 @@ export class Settings { private prohibitContextKey(key: string, fileName: string) { if (!this.settings.context) { return; } if (key in this.settings.context) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`The 'context.${key}' key was found in ${fs_path.resolve(fileName)}, but it is no longer supported. Please remove it.`); } } @@ -338,7 +338,7 @@ export class Settings { if (!this.settings.context) { return; } for (const contextKey of Object.keys(this.settings.context)) { if (contextKey.startsWith(prefix)) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len warning(`A reserved context key ('context.${prefix}') key was found in ${fs_path.resolve(fileName)}, it might cause surprising behavior and should be removed.`); } } diff --git a/packages/aws-cdk/lib/version.ts b/packages/aws-cdk/lib/version.ts index 7c8c913ee06ba..fecbe1c0e1886 100644 --- a/packages/aws-cdk/lib/version.ts +++ b/packages/aws-cdk/lib/version.ts @@ -1,9 +1,9 @@ import { exec as _exec } from 'child_process'; +import * as path from 'path'; +import { promisify } from 'util'; import * as colors from 'colors/safe'; import * as fs from 'fs-extra'; -import * as path from 'path'; import * as semver from 'semver'; -import { promisify } from 'util'; import { debug, print } from '../lib/logging'; import { formatAsBanner } from '../lib/util/console-formatters'; import { cdkCacheDir } from './util/directories'; diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 419daa5a2f315..f1bc8e6cda1d8 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -43,11 +43,11 @@ "@aws-cdk/core": "0.0.0", "@types/archiver": "^3.1.0", "@types/fs-extra": "^8.1.0", - "@types/glob": "^7.1.2", - "@types/jest": "^26.0.3", + "@types/glob": "^7.1.3", + "@types/jest": "^26.0.4", "@types/minimatch": "^3.0.3", "@types/mockery": "^1.4.29", - "@types/node": "^10.17.26", + "@types/node": "^10.17.27", "@types/promptly": "^3.0.0", "@types/semver": "^7.3.1", "@types/sinon": "^9.0.4", @@ -62,7 +62,7 @@ "mockery": "^2.1.0", "pkglint": "0.0.0", "sinon": "^9.0.2", - "ts-jest": "^26.1.1", + "ts-jest": "^26.1.3", "ts-mock-imports": "^1.2.6" }, "dependencies": { @@ -70,8 +70,8 @@ "@aws-cdk/cloudformation-diff": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/region-info": "0.0.0", - "archiver": "^4.0.1", - "aws-sdk": "^2.711.0", + "archiver": "^4.0.2", + "aws-sdk": "^2.715.0", "camelcase": "^6.0.0", "cdk-assets": "0.0.0", "colors": "^1.4.0", diff --git a/packages/aws-cdk/test/account-cache.test.ts b/packages/aws-cdk/test/account-cache.test.ts index 7238c4c3be415..c9b5682f33124 100644 --- a/packages/aws-cdk/test/account-cache.test.ts +++ b/packages/aws-cdk/test/account-cache.test.ts @@ -1,5 +1,5 @@ -import * as fs from 'fs-extra'; import * as path from 'path'; +import * as fs from 'fs-extra'; import { AccountAccessKeyCache } from '../lib/api/aws-auth/account-cache'; async function makeCache() { diff --git a/packages/aws-cdk/test/api/bootstrap.test.ts b/packages/aws-cdk/test/api/bootstrap.test.ts index adf36daa5eb4c..6d5e5474e7948 100644 --- a/packages/aws-cdk/test/api/bootstrap.test.ts +++ b/packages/aws-cdk/test/api/bootstrap.test.ts @@ -11,11 +11,13 @@ const env = { let sdk: MockSdkProvider; let executed: boolean; +let protectedTermination: boolean; let cfnMocks: jest.Mocked>; let changeSetTemplate: any | undefined; beforeEach(() => { sdk = new MockSdkProvider(); executed = false; + protectedTermination = false; cfnMocks = { describeStacks: jest.fn() @@ -47,6 +49,10 @@ beforeEach(() => { return {}; }), deleteStack: jest.fn(), + updateTerminationProtection: jest.fn(() => { + protectedTermination = true; + return {}; + }), }; sdk.stubCloudFormation(cfnMocks); }); @@ -254,4 +260,26 @@ test('even if the bootstrap stack failed to create, can still retry bootstrappin expect(ret.noOp).toBeFalsy(); expect(executed).toBeTruthy(); expect(cfnMocks.deleteStack).toHaveBeenCalled(); +}); + +test('stack is not termination protected by default', async () => { + // WHEN + await bootstrapEnvironment(env, sdk); + + // THEN + expect(executed).toBeTruthy(); + expect(protectedTermination).toBeFalsy(); +}); + +test('stack is termination protected when set', async () => { + // WHEN + await bootstrapEnvironment(env, sdk, { + parameters: { + terminationProtection: true, + }, + }); + + // THEN + expect(executed).toBeTruthy(); + expect(protectedTermination).toBeTruthy(); }); \ No newline at end of file diff --git a/packages/aws-cdk/test/api/bootstrap2.test.ts b/packages/aws-cdk/test/api/bootstrap2.test.ts index 8409a60cf2945..94dbccb2d2a42 100644 --- a/packages/aws-cdk/test/api/bootstrap2.test.ts +++ b/packages/aws-cdk/test/api/bootstrap2.test.ts @@ -116,6 +116,30 @@ describe('Bootstrapping v2', () => { ]); }); + test('stack is not termination protected by default', async () => { + await bootstrapEnvironment2(env, sdk); + + expect(mockDeployStack).toHaveBeenCalledWith(expect.objectContaining({ + stack: expect.objectContaining({ + terminationProtection: false, + }), + })); + }); + + test('stack is termination protected when option is set', async () => { + await bootstrapEnvironment2(env, sdk, { + parameters: { + terminationProtection: true, + }, + }); + + expect(mockDeployStack).toHaveBeenCalledWith(expect.objectContaining({ + stack: expect.objectContaining({ + terminationProtection: true, + }), + })); + }); + afterEach(() => { mockDeployStack.mockClear(); }); diff --git a/packages/aws-cdk/test/api/deploy-stack.test.ts b/packages/aws-cdk/test/api/deploy-stack.test.ts index 585665b3e23d9..b3e6129cbd47c 100644 --- a/packages/aws-cdk/test/api/deploy-stack.test.ts +++ b/packages/aws-cdk/test/api/deploy-stack.test.ts @@ -401,6 +401,29 @@ test('deploy not skipped if template did not change but one tag removed', async expect(cfnMocks.getTemplate).toHaveBeenCalledWith({ StackName: 'withouterrors', TemplateStage: 'Original' }); }); +test('existing stack in UPDATE_ROLLBACK_COMPLETE state can be updated', async () => { + // GIVEN + givenStackExists( + { StackStatus: 'UPDATE_ROLLBACK_COMPLETE' }, // This is for the initial check + { StackStatus: 'UPDATE_COMPLETE' }, // Poll the update + ); + givenTemplateIs({ changed: 123 }); + + // WHEN + await deployStack({ + stack: FAKE_STACK, + sdk, + sdkProvider, + resolvedEnvironment: mockResolvedEnvironment(), + }); + + // THEN + expect(cfnMocks.deleteStack).not.toHaveBeenCalled(); + expect(cfnMocks.createChangeSet).toHaveBeenCalledWith(expect.objectContaining({ + ChangeSetType: 'UPDATE', + })); +}); + test('deploy not skipped if template changed', async () => { // GIVEN givenStackExists(); diff --git a/packages/aws-cdk/test/bockfs.ts b/packages/aws-cdk/test/bockfs.ts index 24e76c52c2774..47f96f3af6f33 100644 --- a/packages/aws-cdk/test/bockfs.ts +++ b/packages/aws-cdk/test/bockfs.ts @@ -8,9 +8,9 @@ // // The big downside compared to mockfs is that you need to use bockfs.path() to translate // fake paths to real paths. -import * as fs from 'fs-extra'; import * as os from 'os'; import * as path_ from 'path'; +import * as fs from 'fs-extra'; const bockFsRoot = fs.mkdtempSync(path_.join(os.tmpdir(), 'bockfs')); let oldCwd: string | undefined; diff --git a/packages/aws-cdk/test/context.test.ts b/packages/aws-cdk/test/context.test.ts index eef3117e72e63..452a814a69324 100644 --- a/packages/aws-cdk/test/context.test.ts +++ b/packages/aws-cdk/test/context.test.ts @@ -1,6 +1,6 @@ -import * as fs from 'fs-extra'; import * as os from 'os'; import * as path from 'path'; +import * as fs from 'fs-extra'; import { Configuration, TRANSIENT_CONTEXT_KEY } from '../lib/settings'; const state: { @@ -11,14 +11,14 @@ const state: { beforeAll(async done => { state.previousWorkingDir = process.cwd(); state.tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'aws-cdk-test')); - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.log('Temporary working directory:', state.tempDir); process.chdir(state.tempDir); done(); }); afterAll(async done => { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.log('Switching back to', state.previousWorkingDir, 'cleaning up', state.tempDir); process.chdir(state.previousWorkingDir!); await fs.remove(state.tempDir!); @@ -71,16 +71,16 @@ test('clear deletes from new file', async () => { test('context is preserved in the location from which it is read', async () => { // GIVEN - await fs.writeJSON('cdk.json', { context: { 'boo:boo' : 'far' } }); + await fs.writeJSON('cdk.json', { context: { 'boo:boo': 'far' } }); const config = await new Configuration().load(); // WHEN - expect(config.context.all).toEqual({ 'boo:boo' : 'far' }); + expect(config.context.all).toEqual({ 'boo:boo': 'far' }); await config.saveContext(); // THEN expect(await fs.readJSON('cdk.context.json')).toEqual({}); - expect(await fs.readJSON('cdk.json')).toEqual({ context: { 'boo:boo' : 'far' } }); + expect(await fs.readJSON('cdk.json')).toEqual({ context: { 'boo:boo': 'far' } }); }); test('surive no context in old file', async () => { diff --git a/packages/aws-cdk/test/diff.test.ts b/packages/aws-cdk/test/diff.test.ts index 2a987915cf043..82e0a7d1c9e79 100644 --- a/packages/aws-cdk/test/diff.test.ts +++ b/packages/aws-cdk/test/diff.test.ts @@ -1,6 +1,7 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { Writable } from 'stream'; import { NodeStringDecoder, StringDecoder } from 'string_decoder'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import { CloudFormationStackArtifact } from '@aws-cdk/cx-api'; import { CloudFormationDeployments } from '../lib/api/cloudformation-deployments'; import { CdkToolkit } from '../lib/cdk-toolkit'; import { classMockOf, MockCloudExecutable } from './util'; @@ -31,6 +32,10 @@ beforeEach(() => { }, ], }, + }, + { + stackName: 'D', + template: { resource: 'D' }, }], }); @@ -44,7 +49,12 @@ beforeEach(() => { }); // Default implementations - cloudFormation.readCurrentTemplate.mockResolvedValue({}); + cloudFormation.readCurrentTemplate.mockImplementation((stackArtifact: CloudFormationStackArtifact) => { + if (stackArtifact.stackName === 'D') { + return Promise.resolve({ resource: 'D' }); + } + return Promise.resolve({}); + }); cloudFormation.deployStack.mockImplementation((options) => Promise.resolve({ noOp: true, outputs: {}, @@ -86,6 +96,21 @@ test('exits with 1 with diffs and fail set to true', async () => { expect(exitCode).toBe(1); }); +test('exits with 1 with diff in first stack, but not in second stack and fail set to true', async () => { + // GIVEN + const buffer = new StringWritable(); + + // WHEN + const exitCode = await toolkit.diff({ + stackNames: ['A', 'D'], + stream: buffer, + fail: true, + }); + + // THEN + expect(exitCode).toBe(1); +}); + test('throws an error during diffs on stack with error metadata', async () => { const buffer = new StringWritable(); diff --git a/packages/aws-cdk/test/init.test.ts b/packages/aws-cdk/test/init.test.ts index 0dc7bae16aec1..9e9ae78784a45 100644 --- a/packages/aws-cdk/test/init.test.ts +++ b/packages/aws-cdk/test/init.test.ts @@ -1,7 +1,7 @@ -import * as cxapi from '@aws-cdk/cx-api'; -import * as fs from 'fs-extra'; import * as os from 'os'; import * as path from 'path'; +import * as cxapi from '@aws-cdk/cx-api'; +import * as fs from 'fs-extra'; import { availableInitTemplates, cliInit } from '../lib/init'; cliTest('create a TypeScript library project', async (workDir) => { diff --git a/packages/aws-cdk/test/integ/cli/aws-helpers.ts b/packages/aws-cdk/test/integ/cli/aws-helpers.ts index fb54db4f60bcd..05fbc6607983c 100644 --- a/packages/aws-cdk/test/integ/cli/aws-helpers.ts +++ b/packages/aws-cdk/test/integ/cli/aws-helpers.ts @@ -36,7 +36,7 @@ async function awsCall< B extends keyof ServiceCalls, >(ctor: new (config: any) => A, call: B, request: First[B]>): Promise[B]>> { const env = await testEnv(); - const cfn = new ctor({ region: env.region }); + const cfn = new ctor({ region: env.region, maxRetries: 6, retryDelayOptions: { base: 500 } }); const response = cfn[call](request); try { return await response.promise(); @@ -93,6 +93,10 @@ export async function deleteStacks(...stackNames: string[]) { if (stackNames.length === 0) { return; } for (const stackName of stackNames) { + await cloudFormation('updateTerminationProtection', { + EnableTerminationProtection: false, + StackName: stackName, + }); await cloudFormation('deleteStack', { StackName: stackName, }); diff --git a/packages/aws-cdk/test/integ/cli/cdk-helpers.ts b/packages/aws-cdk/test/integ/cli/cdk-helpers.ts index 410b8d71d9e71..376725510d9ef 100644 --- a/packages/aws-cdk/test/integ/cli/cdk-helpers.ts +++ b/packages/aws-cdk/test/integ/cli/cdk-helpers.ts @@ -142,7 +142,8 @@ export async function deleteableStacks(prefix: string): Promise s.StackName.startsWith(prefix)) - .filter(s => statusFilter.includes(s.StackStatus)); + .filter(s => statusFilter.includes(s.StackStatus)) + .filter(s => s.RootId === undefined); // Only delete parent stacks. Nested stacks are deleted in the process } /** diff --git a/packages/aws-cdk/test/integ/cli/cli.integtest.ts b/packages/aws-cdk/test/integ/cli/cli.integtest.ts index b31bbf314d60a..e0aa76294ba2a 100644 --- a/packages/aws-cdk/test/integ/cli/cli.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/cli.integtest.ts @@ -212,6 +212,96 @@ integTest('deploy with parameters', async () => { ]); }); +integTest('update to stack in ROLLBACK_COMPLETE state will delete stack and create a new one', async () => { + // GIVEN + await expect(cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${STACK_NAME_PREFIX}@aww`, + ], + captureStderr: false, + })).rejects.toThrow('exited with error'); + + const response = await cloudFormation('describeStacks', { + StackName: fullStackName('param-test-1'), + }); + + const stackArn = response.Stacks?.[0].StackId; + expect(response.Stacks?.[0].StackStatus).toEqual('ROLLBACK_COMPLETE'); + + // WHEN + const newStackArn = await cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${STACK_NAME_PREFIX}allgood`, + ], + captureStderr: false, + }); + + const newStackResponse = await cloudFormation('describeStacks', { + StackName: newStackArn, + }); + + // THEN + expect (stackArn).not.toEqual(newStackArn); // new stack was created + expect(newStackResponse.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); + expect(newStackResponse.Stacks?.[0].Parameters).toEqual([ + { + ParameterKey: 'TopicNameParam', + ParameterValue: `${STACK_NAME_PREFIX}allgood`, + }, + ]); +}); + +integTest('stack in UPDATE_ROLLBACK_COMPLETE state can be updated', async () => { + // GIVEN + const stackArn = await cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${STACK_NAME_PREFIX}nice`, + ], + captureStderr: false, + }); + + let response = await cloudFormation('describeStacks', { + StackName: stackArn, + }); + + expect(response.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); + + // bad parameter name with @ will put stack into UPDATE_ROLLBACK_COMPLETE + await expect(cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${STACK_NAME_PREFIX}@aww`, + ], + captureStderr: false, + })).rejects.toThrow('exited with error');; + + response = await cloudFormation('describeStacks', { + StackName: stackArn, + }); + + expect(response.Stacks?.[0].StackStatus).toEqual('UPDATE_ROLLBACK_COMPLETE'); + + // WHEN + await cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${STACK_NAME_PREFIX}allgood`, + ], + captureStderr: false, + }); + + response = await cloudFormation('describeStacks', { + StackName: stackArn, + }); + + // THEN + expect(response.Stacks?.[0].StackStatus).toEqual('UPDATE_COMPLETE'); + expect(response.Stacks?.[0].Parameters).toEqual([ + { + ParameterKey: 'TopicNameParam', + ParameterValue: `${STACK_NAME_PREFIX}allgood`, + }, + ]); +}); + integTest('deploy with wildcard and parameters', async () => { await cdkDeploy('param-test-*', { options: [ @@ -336,7 +426,6 @@ integTest('deploy with role', async () => { async function deleteRole() { try { - // tslint:disable-next-line: forin for (const policyName of (await iam('listRolePolicies', { RoleName: roleName })).PolicyNames) { await iam('deleteRolePolicy', { RoleName: roleName, @@ -363,6 +452,32 @@ integTest('cdk diff', async () => { .rejects.toThrow('exited with error'); }); +integTest('cdk diff --fail on multiple stacks exits with error if any of the stacks contains a diff', async () => { + // GIVEN + const diff1 = await cdk(['diff', fullStackName('test-1')]); + expect(diff1).toContain('AWS::SNS::Topic'); + + await cdkDeploy('test-2'); + const diff2 = await cdk(['diff', fullStackName('test-2')]); + expect(diff2).toContain('There were no differences'); + + // WHEN / THEN + await expect(cdk(['diff', '--fail', fullStackName('test-1'), fullStackName('test-2')])).rejects.toThrow('exited with error'); +}); + +integTest('cdk diff --fail with multiple stack exits with if any of the stacks contains a diff', async () => { + // GIVEN + await cdkDeploy('test-1'); + const diff1 = await cdk(['diff', fullStackName('test-1')]); + expect(diff1).toContain('There were no differences'); + + const diff2 = await cdk(['diff', fullStackName('test-2')]); + expect(diff2).toContain('AWS::SNS::Topic'); + + // WHEN / THEN + await expect(cdk(['diff', '--fail', fullStackName('test-1'), fullStackName('test-2')])).rejects.toThrow('exited with error'); +}); + integTest('deploy stack with docker asset', async () => { await cdkDeploy('docker'); }); diff --git a/packages/aws-cdk/test/integ/cli/jest.config.js b/packages/aws-cdk/test/integ/cli/jest.config.js index 0f88461110a80..faed5273edbff 100644 --- a/packages/aws-cdk/test/integ/cli/jest.config.js +++ b/packages/aws-cdk/test/integ/cli/jest.config.js @@ -8,4 +8,8 @@ module.exports = { testEnvironment: "node", bail: 1, verbose: true, + reporters: [ + "default", + [ "jest-junit", { suiteName: "jest tests" } ] + ] }; diff --git a/packages/aws-cdk/test/integ/cli/test.sh b/packages/aws-cdk/test/integ/cli/test.sh index 482956df450f4..42da413a46f40 100755 --- a/packages/aws-cdk/test/integ/cli/test.sh +++ b/packages/aws-cdk/test/integ/cli/test.sh @@ -20,7 +20,7 @@ cd $scriptdir # package.json. if ! npx --no-install jest --version; then echo 'Looks like we need to install jest first. Hold on.' >& 2 - npm install --prefix . jest aws-sdk + npm install --prefix . jest jest-junit aws-sdk fi npx jest --runInBand --verbose "$@" \ No newline at end of file diff --git a/packages/aws-cdk/test/util.ts b/packages/aws-cdk/test/util.ts index 43ab781ab273c..f6966e8737711 100644 --- a/packages/aws-cdk/test/util.ts +++ b/packages/aws-cdk/test/util.ts @@ -1,7 +1,7 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs'; import * as path from 'path'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import * as cxapi from '@aws-cdk/cx-api'; import { CloudExecutable } from '../lib/api/cxapp/cloud-executable'; import { Configuration } from '../lib/settings'; import { MockSdkProvider } from './util/mock-sdk'; diff --git a/packages/aws-cdk/test/version.test.ts b/packages/aws-cdk/test/version.test.ts index 2a48a13852e89..005ccd209e609 100644 --- a/packages/aws-cdk/test/version.test.ts +++ b/packages/aws-cdk/test/version.test.ts @@ -1,8 +1,8 @@ -import * as fs from 'fs-extra'; import * as path from 'path'; -import * as sinon from 'sinon'; import { setTimeout as _setTimeout } from 'timers'; import { promisify } from 'util'; +import * as fs from 'fs-extra'; +import * as sinon from 'sinon'; import { latestVersionIfHigher, VersionCheckTTL } from '../lib/version'; const setTimeout = promisify(_setTimeout); diff --git a/packages/aws-cdk/test/yaml.test.ts b/packages/aws-cdk/test/yaml.test.ts index b440f8e2663df..aabcd090fbfea 100644 --- a/packages/aws-cdk/test/yaml.test.ts +++ b/packages/aws-cdk/test/yaml.test.ts @@ -6,7 +6,7 @@ const q = '"'; test('quote the word "ON"', () => { // NON NEGOTIABLE! If not quoted, will be interpreted as the boolean TRUE - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console const output = toYAML({ notABoolean: 'ON', }); diff --git a/packages/awslint/.eslintrc.js b/packages/awslint/.eslintrc.js new file mode 100644 index 0000000000000..61dd8dd001f63 --- /dev/null +++ b/packages/awslint/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/awslint/.gitignore b/packages/awslint/.gitignore index ca58b8b90d671..fdf7e5c6f084b 100644 --- a/packages/awslint/.gitignore +++ b/packages/awslint/.gitignore @@ -93,3 +93,6 @@ dist .LAST_BUILD *.snk nyc.config.js + +!.eslintrc.js +junit.xml \ No newline at end of file diff --git a/packages/awslint/.npmignore b/packages/awslint/.npmignore index bc9fd0e49f9a1..dd1dbf2be01be 100644 --- a/packages/awslint/.npmignore +++ b/packages/awslint/.npmignore @@ -2,4 +2,10 @@ dist .LAST_PACKAGE .LAST_BUILD -*.snk \ No newline at end of file +*.snk +# exclude cdk artifacts +**/cdk.out +*.tsbuildinfo +tsconfig.json +.eslintrc.js +junit.xml \ No newline at end of file diff --git a/packages/awslint/README.md b/packages/awslint/README.md index 585a2b79a86ed..f69074a1f0ab0 100644 --- a/packages/awslint/README.md +++ b/packages/awslint/README.md @@ -1,4 +1,14 @@ # awslint + +--- + +![cdk-constructs: Developer Preview](https://img.shields.io/badge/cdk--constructs-developer--preview-informational.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are in **developer preview** before they become stable. We will only make breaking changes to address unforeseen API issues. Therefore, these APIs are not subject to [Semantic Versioning](https://semver.org/), and breaking changes will be announced in 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. + +--- + + A linter for the AWS Construct Library's API. It reflects a construct library's module via it's `.jsii` manifest and checks that the module adheres to the [AWS diff --git a/packages/awslint/bin/awslint.ts b/packages/awslint/bin/awslint.ts index a009da634a7c9..7bcd20f0ebeda 100644 --- a/packages/awslint/bin/awslint.ts +++ b/packages/awslint/bin/awslint.ts @@ -1,9 +1,10 @@ -// tslint:disable:max-line-length +/* eslint-disable max-len */ +/* eslint-disable no-console */ import * as child_process from 'child_process'; +import * as path from 'path'; import * as colors from 'colors'; import * as fs from 'fs-extra'; import * as reflect from 'jsii-reflect'; -import * as path from 'path'; import * as yargs from 'yargs'; import { ALL_RULES_LINTER, DiagnosticLevel } from '../lib'; @@ -196,7 +197,7 @@ async function main() { // we don't have a .jsii file, and --no-compile is explicily set, then it's an error if (args.compile === false) { - throw new Error(`No .jsii file and --no-compile is set`); + throw new Error('No .jsii file and --no-compile is set'); } // compile! @@ -219,7 +220,7 @@ async function loadModule(dir: string) { // We run 'awslint' during build time, assemblies are guaranteed to be ok. if (ts.roots.length !== 1) { - throw new Error(`Expecting only a single root assembly`); + throw new Error('Expecting only a single root assembly'); } return ts.roots[0]; diff --git a/packages/awslint/lib/linter.ts b/packages/awslint/lib/linter.ts index a8b3da0b4bb0e..0dcaa75ea6e65 100644 --- a/packages/awslint/lib/linter.ts +++ b/packages/awslint/lib/linter.ts @@ -1,6 +1,6 @@ -import * as reflect from 'jsii-reflect'; -import { PrimitiveType } from '@jsii/spec'; import * as util from 'util'; +import { PrimitiveType } from '@jsii/spec'; +import * as reflect from 'jsii-reflect'; export interface LinterOptions { /** diff --git a/packages/awslint/lib/rules/api.ts b/packages/awslint/lib/rules/api.ts index 0453c021a5ddb..2a1c6d96420cf 100644 --- a/packages/awslint/lib/rules/api.ts +++ b/packages/awslint/lib/rules/api.ts @@ -141,5 +141,5 @@ apiLinter.add({ throw new Error(`invalid type reference: ${type.toString()}`); } - } + }, }); diff --git a/packages/awslint/lib/rules/attributes.ts b/packages/awslint/lib/rules/attributes.ts index bd47ded6eb9d8..3fbe132da90fa 100644 --- a/packages/awslint/lib/rules/attributes.ts +++ b/packages/awslint/lib/rules/attributes.ts @@ -24,7 +24,7 @@ attributesLinter.add({ message: 'attribute property must be readonly', eval: e => { e.assert(e.ctx.attr.property.immutable, e.ctx.fqn); - } + }, }); attributesLinter.add({ @@ -33,5 +33,5 @@ attributesLinter.add({ eval: e => { const tag = e.ctx.attr.property.docs.customTag('attribute'); e.assert(tag, e.ctx.fqn, `${e.ctx.attr.property.parentType.fqn}.${e.ctx.attr.property.name}`); - } + }, }); diff --git a/packages/awslint/lib/rules/cfn-resource.ts b/packages/awslint/lib/rules/cfn-resource.ts index 318d86dab9ab4..536d53cb4c766 100644 --- a/packages/awslint/lib/rules/cfn-resource.ts +++ b/packages/awslint/lib/rules/cfn-resource.ts @@ -10,12 +10,12 @@ export const cfnResourceLinter = new Linter(a => CfnResourceReflection.findAll(a cfnResourceLinter.add({ code: 'resource-class', - message: `every resource must have a resource class (L2), add '@resource %s' to its docstring`, + message: 'every resource must have a resource class (L2), add \'@resource %s\' to its docstring', warning: true, eval: e => { const l2 = ResourceReflection.findAll(e.ctx.classType.assembly).find(r => r.cfn.fullname === e.ctx.fullname); e.assert(l2, e.ctx.fullname, e.ctx.fullname); - } + }, }); export class CfnResourceReflection { diff --git a/packages/awslint/lib/rules/cloudwatch-events.ts b/packages/awslint/lib/rules/cloudwatch-events.ts index 5557d4851f350..c6f3909c7354d 100644 --- a/packages/awslint/lib/rules/cloudwatch-events.ts +++ b/packages/awslint/lib/rules/cloudwatch-events.ts @@ -22,33 +22,33 @@ const EVENT_RULE_FQN = '@aws-cdk/aws-events.Rule'; eventsLinter.add({ code: 'events-in-interface', - message: `'onXxx()' methods should also be defined on construct interface`, + message: '\'onXxx()\' methods should also be defined on construct interface', eval: e => { for (const method of e.ctx.directEventMethods.concat(e.ctx.cloudTrailEventMethods)) { e.assert(!e.ctx.interfaceType || e.ctx.interfaceType.allMethods.filter(m => !m.protected).some(m => m.name === method.name), `${e.ctx.fqn}.${method.name}`); } - } + }, }); eventsLinter.add({ code: 'events-generic', - message: `if there are specific 'onXxx()' methods, there should also be a generic 'onEvent()' method`, + message: 'if there are specific \'onXxx()\' methods, there should also be a generic \'onEvent()\' method', eval: e => { e.assert(e.ctx.directEventMethods.length === 0 || e.ctx.classType.allMethods.some(m => m.name === 'onEvent'), e.ctx.fqn); - } + }, }); eventsLinter.add({ code: 'events-generic-cloudtrail', - message: `if there are specific 'onCloudTrailXxx()' methods, there should also be a generic 'onCloudTrailEvent()' method`, + message: 'if there are specific \'onCloudTrailXxx()\' methods, there should also be a generic \'onCloudTrailEvent()\' method', eval: e => { e.assert(e.ctx.cloudTrailEventMethods.length === 0 || e.ctx.classType.allMethods.some(m => m.name === 'onCloudTrailEvent'), e.ctx.fqn); - } + }, }); eventsLinter.add({ code: 'events-method-signature', - message: `all 'onXxx()' methods should have the CloudWatch Events signature (id: string, options: events.OnEventOptions = {}) => events.Rule`, + message: 'all \'onXxx()\' methods should have the CloudWatch Events signature (id: string, options: events.OnEventOptions = {}) => events.Rule', eval: e => { for (const method of e.ctx.directEventMethods) { // give slack to protected methods like "onSynthesize", "onPrepare", ... @@ -60,10 +60,10 @@ eventsLinter.add({ { type: 'string' }, { type: ON_EVENT_OPTIONS_FQN, subtypeAllowed: true, optional: true }, ], - returns: EVENT_RULE_FQN + returns: EVENT_RULE_FQN, }); } - } + }, }); function isDirectEventMethod(m: reflect.Method) { diff --git a/packages/awslint/lib/rules/construct.ts b/packages/awslint/lib/rules/construct.ts index 9ec81754cfd0a..ae922413828a4 100644 --- a/packages/awslint/lib/rules/construct.ts +++ b/packages/awslint/lib/rules/construct.ts @@ -95,12 +95,12 @@ constructLinter.add({ expectedParams.push({ name: 'scope', - type: e.ctx.core.constructClass.fqn + type: e.ctx.core.constructClass.fqn, }); expectedParams.push({ name: 'id', - type: 'string' + type: 'string', }); // it's okay for a construct not to have a "props" argument so we only @@ -112,9 +112,9 @@ constructLinter.add({ } e.assertSignature(initializer, { - parameters: expectedParams + parameters: expectedParams, }); - } + }, }); constructLinter.add({ @@ -131,7 +131,7 @@ constructLinter.add({ } e.assert(e.ctx.propsType, e.ctx.interfaceFqn); - } + }, }); constructLinter.add({ @@ -145,7 +145,7 @@ constructLinter.add({ e.ctx.initializer.parameters[2].type.type === e.ctx.propsType, e.ctx.fqn, e.ctx.propsFqn); - } + }, }); constructLinter.add({ @@ -163,7 +163,7 @@ constructLinter.add({ } e.assert(e.ctx.initializer.parameters[2].optional, e.ctx.fqn); - } + }, }); constructLinter.add({ @@ -173,7 +173,7 @@ constructLinter.add({ if (!e.ctx.interfaceType) { return; } const interfaceBase = e.ctx.sys.findInterface(e.ctx.core.constructInterface.fqn); e.assert(e.ctx.interfaceType.extends(interfaceBase), e.ctx.interfaceType.fqn); - } + }, }); constructLinter.add({ @@ -184,7 +184,7 @@ constructLinter.add({ if (!e.ctx.interfaceType) { return; } const baseFqn = `${e.ctx.classType.fqn}Base`; e.assert(!e.ctx.sys.tryFindFqn(baseFqn), baseFqn); - } + }, }); constructLinter.add({ @@ -200,7 +200,7 @@ constructLinter.add({ for (const property of e.ctx.propsType.ownProperties) { e.assert(!property.type.unionOfTypes, `${e.ctx.propsFqn}.${property.name}`); } - } + }, }); constructLinter.add({ @@ -216,7 +216,7 @@ constructLinter.add({ for (const property of e.ctx.propsType.ownProperties) { e.assert(!property.name.toLowerCase().endsWith('arn'), `${e.ctx.propsFqn}.${property.name}`); } - } + }, }); constructLinter.add({ @@ -237,7 +237,7 @@ constructLinter.add({ e.assert(!(fqn === e.ctx.core.tokenInterface.fqn), `${e.ctx.propsFqn}.${property.name}`); } } - } + }, }); constructLinter.add({ @@ -258,7 +258,7 @@ constructLinter.add({ e.assert(!found.name.toLowerCase().startsWith('cfn'), `${e.ctx.propsFqn}.${property.name}`); } } - } + }, }); constructLinter.add({ @@ -272,7 +272,7 @@ constructLinter.add({ if (CoreTypes.isCfnResource(e.ctx.classType)) { return; } for (const property of e.ctx.propsType.ownProperties) { - e.assert(!property.type.isAny, `${e.ctx.propsFqn}.${property.name}`); + e.assert(!property.type.isAny, `${e.ctx.propsFqn}.${property.name}`); } - } + }, }); diff --git a/packages/awslint/lib/rules/core-types.ts b/packages/awslint/lib/rules/core-types.ts index 545d8165574b4..09be91656d446 100644 --- a/packages/awslint/lib/rules/core-types.ts +++ b/packages/awslint/lib/rules/core-types.ts @@ -1,16 +1,15 @@ import * as reflect from 'jsii-reflect'; -import { TypeSystem } from "jsii-reflect"; -import { getDocTag } from "./util"; +import { getDocTag } from './util'; -const CORE_MODULE = "@aws-cdk/core"; +const CORE_MODULE = '@aws-cdk/core'; enum CoreTypesFqn { - CfnResource = "@aws-cdk/core.CfnResource", - Construct = "@aws-cdk/core.Construct", - ConstructInterface = "@aws-cdk/core.IConstruct", - Resource = "@aws-cdk/core.Resource", - ResourceInterface = "@aws-cdk/core.IResource", - ResolvableInterface = "@aws-cdk/core.IResolvable", - PhysicalName = "@aws-cdk/core.PhysicalName" + CfnResource = '@aws-cdk/core.CfnResource', + Construct = '@aws-cdk/core.Construct', + ConstructInterface = '@aws-cdk/core.IConstruct', + Resource = '@aws-cdk/core.Resource', + ResourceInterface = '@aws-cdk/core.IResource', + ResolvableInterface = '@aws-cdk/core.IResolvable', + PhysicalName = '@aws-cdk/core.PhysicalName' } export class CoreTypes { @@ -44,7 +43,7 @@ export class CoreTypes { return false; } - if (!c.name.startsWith("Cfn")) { + if (!c.name.startsWith('Cfn')) { return false; } @@ -75,7 +74,7 @@ export class CoreTypes { */ public static isResourceClass(classType: reflect.ClassType) { const baseResource = classType.system.findClass(CoreTypesFqn.Resource); - return classType.extends(baseResource) || getDocTag(classType, "resource"); + return classType.extends(baseResource) || getDocTag(classType, 'resource'); } /** @@ -124,7 +123,7 @@ export class CoreTypes { return this.sys.findClass(CoreTypesFqn.PhysicalName); } - private readonly sys: TypeSystem; + private readonly sys: reflect.TypeSystem; constructor(sys: reflect.TypeSystem) { this.sys = sys; diff --git a/packages/awslint/lib/rules/docs.ts b/packages/awslint/lib/rules/docs.ts index 4294e2093b633..fda92170bd80a 100644 --- a/packages/awslint/lib/rules/docs.ts +++ b/packages/awslint/lib/rules/docs.ts @@ -6,10 +6,10 @@ type DocsLinterContext = { readonly assembly: reflect.Assembly; readonly errorKey: string; } & ({ readonly kind: 'type'; documentable: reflect.Type } - | { readonly kind: 'interface-property'; containingType: reflect.InterfaceType; documentable: reflect.Property } - | { readonly kind: 'class-property'; containingType: reflect.ClassType; documentable: reflect.Property } - | { readonly kind: 'method'; containingType: reflect.ReferenceType; documentable: reflect.Method } - | { readonly kind: 'enum-member'; containingType: reflect.EnumType; documentable: reflect.EnumMember } +| { readonly kind: 'interface-property'; containingType: reflect.InterfaceType; documentable: reflect.Property } +| { readonly kind: 'class-property'; containingType: reflect.ClassType; documentable: reflect.Property } +| { readonly kind: 'method'; containingType: reflect.ReferenceType; documentable: reflect.Method } +| { readonly kind: 'enum-member'; containingType: reflect.EnumType; documentable: reflect.EnumMember } ); export const docsLinter = new Linter(assembly => { @@ -21,13 +21,12 @@ export const docsLinter = new Linter(assembly => { ]), ...flatMap(assembly.interfaces, interfaceType => [ { assembly, kind: 'type', documentable: interfaceType, errorKey: interfaceType.fqn }, - // tslint:disable-next-line:max-line-length ...interfaceType.ownProperties.map(property => ({ assembly, kind: 'interface-property', containingType: interfaceType, documentable: property, errorKey: `${interfaceType.fqn}.${property.name}` })), ...interfaceType.ownMethods.map(method => ({ assembly, kind: 'method', containingType: interfaceType, documentable: method, errorKey: `${interfaceType.fqn}.${method.name}` })), ]), ...flatMap(assembly.enums, enumType => [ { assembly, kind: 'type', documentable: enumType, errorKey: enumType.fqn }, - ...enumType.members.map(member => ({ assembly, kind: 'enum-member', containingType: enumType, documentable: member, errorKey: `${enumType.fqn}.${member.name}` })) + ...enumType.members.map(member => ({ assembly, kind: 'enum-member', containingType: enumType, documentable: member, errorKey: `${enumType.fqn}.${member.name}` })), ]), ] as DocsLinterContext[]; }); @@ -43,7 +42,7 @@ docsLinter.add({ if (!e.ctx.documentable.docs.summary) { e.assert(e.ctx.documentable.docs.summary, e.ctx.errorKey); } - } + }, }); docsLinter.add({ @@ -57,43 +56,43 @@ docsLinter.add({ const property = e.ctx.documentable; e.assert(!property.optional || property.docs.docs.default !== undefined, e.ctx.errorKey); - } + }, }); docsLinter.add({ code: 'props-no-undefined-default', - message: `'@default undefined' is not helpful. Users will know the VALUE is literally 'undefined' if they don't specify it, but what is the BEHAVIOR if they do so?`, + message: '\'@default undefined\' is not helpful. Users will know the VALUE is literally \'undefined\' if they don\'t specify it, but what is the BEHAVIOR if they do so?', eval: e => { if (e.ctx.kind !== 'interface-property') { return; } if (!e.ctx.containingType.isDataType()) { return; } const property = e.ctx.documentable; e.assert(property.docs.docs.default !== 'undefined', e.ctx.errorKey); - } + }, }); function isPublic(ctx: DocsLinterContext) { switch (ctx.kind) { - case "class-property": - case "interface-property": - case "method": + case 'class-property': + case 'interface-property': + case 'method': return !ctx.documentable.protected; - case "enum-member": - case "type": + case 'enum-member': + case 'type': return true; } } function isCfnType(ctx: DocsLinterContext) { switch (ctx.kind) { - case "class-property": - case "interface-property": - case "method": - case "enum-member": + case 'class-property': + case 'interface-property': + case 'method': + case 'enum-member': return CoreTypes.isCfnType(ctx.containingType); - case "type": + case 'type': return CoreTypes.isCfnType(ctx.documentable); } } diff --git a/packages/awslint/lib/rules/exports.ts b/packages/awslint/lib/rules/exports.ts index 7d6bd5f350c73..6c5ef21bcf831 100644 --- a/packages/awslint/lib/rules/exports.ts +++ b/packages/awslint/lib/rules/exports.ts @@ -9,5 +9,5 @@ exportsLinter.add({ if (e.ctx.isClassType() || e.ctx.isInterfaceType()) { e.assert(!e.ctx.allMethods.some(m => m.name === 'export'), e.ctx.fqn); } - } + }, }); \ No newline at end of file diff --git a/packages/awslint/lib/rules/imports.ts b/packages/awslint/lib/rules/imports.ts index 1c60d2cc0db07..d69c5f2745d98 100644 --- a/packages/awslint/lib/rules/imports.ts +++ b/packages/awslint/lib/rules/imports.ts @@ -23,7 +23,7 @@ class ImportsReflection { this.fromAttributesMethod = classType.allMethods.find(x => x.name === this.fromAttributesMethodName); this.fromMethods = classType.allMethods.filter(x => - x.static + x.static && x.name.match(`^${this.prefix}[A-Z]`) && x.name !== this.fromAttributesMethodName); @@ -45,7 +45,7 @@ importsLinter.add({ eval: e => { const hasImport = e.ctx.resource.construct.classType.allMethods.find(x => x.static && x.name === 'import'); e.assert(!hasImport, e.ctx.resource.fqn + '.import'); - } + }, }); importsLinter.add({ @@ -58,7 +58,7 @@ importsLinter.add({ } e.assert(e.ctx.fromMethods.length > 0 || e.ctx.fromAttributesMethod, e.ctx.resource.fqn); - } + }, }); importsLinter.add({ @@ -74,12 +74,12 @@ importsLinter.add({ parameters: [ { name: 'scope', type: e.ctx.resource.construct.ROOT_CLASS }, { name: 'id', type: 'string' }, - { name: argName, type: 'string' } + { name: argName, type: 'string' }, ], - returns: e.ctx.resource.construct.interfaceType + returns: e.ctx.resource.construct.interfaceType, }); } - } + }, }); importsLinter.add({ @@ -94,10 +94,10 @@ importsLinter.add({ parameters: [ { name: 'scope', type: e.ctx.resource.construct.ROOT_CLASS }, { name: 'id', type: 'string' }, - { name: 'attrs', type: e.ctx.attributesStruct } - ] + { name: 'attrs', type: e.ctx.attributesStruct }, + ], }); - } + }, }); importsLinter.add({ @@ -109,5 +109,5 @@ importsLinter.add({ } e.assert(e.ctx.attributesStruct, e.ctx.attributesStructName); - } + }, }); diff --git a/packages/awslint/lib/rules/index.ts b/packages/awslint/lib/rules/index.ts index e3fe69364f09b..8777d5056ab8f 100644 --- a/packages/awslint/lib/rules/index.ts +++ b/packages/awslint/lib/rules/index.ts @@ -28,5 +28,5 @@ export const ALL_RULES_LINTER = new AggregateLinter( noUnusedTypeLinter, durationsLinter, publicStaticPropertiesLinter, - docsLinter + docsLinter, ); \ No newline at end of file diff --git a/packages/awslint/lib/rules/integrations.ts b/packages/awslint/lib/rules/integrations.ts index 708718b319b11..32e0b16d5d207 100644 --- a/packages/awslint/lib/rules/integrations.ts +++ b/packages/awslint/lib/rules/integrations.ts @@ -23,10 +23,10 @@ class IntegrationReflection { integrationLinter.add({ code: 'integ-return-type', - message: `'bind(...)' should return a type named 'XxxConfig'`, + message: '\'bind(...)\' should return a type named \'XxxConfig\'', eval: e => { const returnsFqn = e.ctx.bindMethod.returns.type.fqn; e.assert(returnsFqn && returnsFqn.endsWith('Config'), memberFqn(e.ctx.bindMethod)); - } + }, }); diff --git a/packages/awslint/lib/rules/module.ts b/packages/awslint/lib/rules/module.ts index f87ef3af8231c..4ce7e527b1fbe 100644 --- a/packages/awslint/lib/rules/module.ts +++ b/packages/awslint/lib/rules/module.ts @@ -15,7 +15,7 @@ export const moduleLinter = new Linter(assembly => { return [ { assembly, - namespace: cfnResources[0].namespace + namespace: cfnResources[0].namespace, } ]; }); @@ -27,7 +27,7 @@ moduleLinter.add( { if (!e.ctx.assembly) { return; } const namespace = overrideNamespace(e.ctx.namespace.toLocaleLowerCase().replace('::', '-')); e.assertEquals(e.ctx.assembly.name, `@aws-cdk/${namespace}`, e.ctx.assembly.name); - } + }, }); /** diff --git a/packages/awslint/lib/rules/no-unused-type.ts b/packages/awslint/lib/rules/no-unused-type.ts index 7cf59b8b03c7b..33978c6eea41d 100644 --- a/packages/awslint/lib/rules/no-unused-type.ts +++ b/packages/awslint/lib/rules/no-unused-type.ts @@ -20,7 +20,7 @@ noUnusedTypeLinter.add({ evaluation.assert(evaluation.ctx.usedTypes.has(evaluation.ctx.inspectedType.fqn), evaluation.ctx.inspectedType.fqn, _formatLocation(evaluation.ctx.inspectedType.locationInModule)); - } + }, }); function _collectUsedTypes(assm: Assembly): Set { diff --git a/packages/awslint/lib/rules/public-static-properties.ts b/packages/awslint/lib/rules/public-static-properties.ts index 4667a92973b7c..90385f01114dd 100644 --- a/packages/awslint/lib/rules/public-static-properties.ts +++ b/packages/awslint/lib/rules/public-static-properties.ts @@ -7,9 +7,9 @@ export const publicStaticPropertiesLinter = new Linter(assembly => { const result = new Array(); for (const c of assembly.classes) { for (const property of c.allProperties) { - if (property.const && property.static) { - result.push(property); - } + if (property.const && property.static) { + result.push(property); + } } } return result; @@ -21,5 +21,5 @@ publicStaticPropertiesLinter.add({ eval: e => { const name = e.ctx.name; e.assert(UPPER_SNAKE_CASE_ALLOWED_PATTERN.test(name), `${e.ctx.parentType.fqn}.${name}`); - } + }, }); \ No newline at end of file diff --git a/packages/awslint/lib/rules/resource.ts b/packages/awslint/lib/rules/resource.ts index 8be7a9a59767b..2873972d13df3 100644 --- a/packages/awslint/lib/rules/resource.ts +++ b/packages/awslint/lib/rules/resource.ts @@ -55,7 +55,7 @@ export class ResourceReflection { if (!cfn) { throw new Error(`Cannot find L1 class for L2 ${construct.fqn}. ` + `Is "${guessResourceName(construct.fqn)}" an actual CloudFormation resource. ` + - `If not, use the "@resource" doc tag to indicate the full resource name (e.g. "@resource AWS::Route53::HostedZone")`); + 'If not, use the "@resource" doc tag to indicate the full resource name (e.g. "@resource AWS::Route53::HostedZone")'); } this.core = new CoreTypes(this.sys); @@ -131,7 +131,7 @@ export class ResourceReflection { result.push({ site, cfnAttributeNames, - property + property, }); } @@ -156,11 +156,11 @@ function findDeclarationSite(prop: reflect.Property): reflect.Property { resourceLinter.add({ code: 'resource-class-extends-resource', - message: `resource classes must extend "cdk.Resource" directly or indirectly`, + message: 'resource classes must extend "cdk.Resource" directly or indirectly', eval: e => { const resourceBase = e.ctx.sys.findClass(e.ctx.core.resourceClass.fqn); e.assert(e.ctx.construct.classType.extends(resourceBase), e.ctx.construct.fqn); - } + }, }); resourceLinter.add({ @@ -169,7 +169,7 @@ resourceLinter.add({ message: 'every resource must have a resource interface', eval: e => { e.assert(e.ctx.construct.interfaceType, e.ctx.construct.fqn); - } + }, }); resourceLinter.add({ @@ -182,7 +182,7 @@ resourceLinter.add({ const resourceInterfaceFqn = e.ctx.core.resourceInterface.fqn; const interfaceBase = e.ctx.sys.findInterface(resourceInterfaceFqn); e.assert(resourceInterface.extends(interfaceBase), resourceInterface.fqn); - } + }, }); resourceLinter.add({ @@ -200,7 +200,7 @@ resourceLinter.add({ const found = e.ctx.attributes.find(a => a.cfnAttributeNames.includes(name)); e.assert(found, `${e.ctx.fqn}.${expected}`, expected); } - } + }, }); resourceLinter.add({ @@ -218,20 +218,20 @@ resourceLinter.add({ for (const grantMethod of grantMethods) { e.assertSignature(grantMethod, { - returns: grantResultType + returns: grantResultType, }); } - } + }, }); resourceLinter.add({ code: 'props-physical-name', - message: "Every Resource must have a single physical name construction property, " + - "with a name that is an ending substring of Name", + message: 'Every Resource must have a single physical name construction property, ' + + 'with a name that is an ending substring of Name', eval: e => { if (!e.ctx.construct.propsType) { return; } e.assert(e.ctx.physicalNameProp, e.ctx.construct.propsFqn); - } + }, }); resourceLinter.add({ @@ -241,7 +241,7 @@ resourceLinter.add({ if (!e.ctx.physicalNameProp) { return; } const prop = e.ctx.physicalNameProp; e.assertTypesEqual(e.ctx.sys, prop.type, 'string', `${e.ctx.construct.propsFqn}.${prop.name}`); - } + }, }); function tryResolveCfnResource(resourceClass: reflect.ClassType): CfnResourceReflection | undefined { diff --git a/packages/awslint/lib/rules/util.ts b/packages/awslint/lib/rules/util.ts index 1b51673bfaf00..658b286100d8b 100644 --- a/packages/awslint/lib/rules/util.ts +++ b/packages/awslint/lib/rules/util.ts @@ -25,7 +25,7 @@ export function getDocTag(documentable: reflect.Documentable, tag: string): stri for (const base of documentable.interfaces) { const baseTag = getDocTag(base, tag); if (baseTag) { - return baseTag; + return baseTag; } } } diff --git a/packages/awslint/package.json b/packages/awslint/package.json index 67f81ed960c6f..3ec92848866c8 100644 --- a/packages/awslint/package.json +++ b/packages/awslint/package.json @@ -3,34 +3,36 @@ "version": "0.0.0", "description": "Enforces the AWS Construct Library guidelines", "scripts": { - "build": "tsc -b && chmod +x bin/awslint", - "lint": "tslint -p . && pkglint", + "build": "tsc -b && npm run lint && chmod +x bin/awslint", + "lint": "eslint . --ext=.ts && pkglint", "test": "echo ok", "watch": "tsc -b -w", "package": "mkdir -p dist/js && mv $( npm pack ) dist/js/", "build+test+package": "npm run build+test && npm run package", - "build+test": "npm run build && npm test" + "build+test": "npm run build && npm test", + "pkglint": "pkglint -f" }, "bin": { "awslint": "bin/awslint" }, "dependencies": { - "@jsii/spec": "^1.7.0", + "@jsii/spec": "^1.9.0", "camelcase": "^6.0.0", "colors": "^1.4.0", "fs-extra": "^9.0.1", - "jsii-reflect": "^1.7.0", + "jsii-reflect": "^1.9.0", "yargs": "^15.3.1" }, "devDependencies": { "@types/fs-extra": "^8.1.0", "@types/yargs": "^15.0.5", - "tslint": "^5.20.1", - "typescript": "~3.9.5" + "pkglint": "0.0.0", + "typescript": "~3.9.6" }, "repository": { "type": "git", - "url": "https://github.com/aws/aws-cdk.git" + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/awslint" }, "homepage": "https://github.com/aws/aws-cdk", "license": "Apache-2.0", @@ -43,6 +45,8 @@ "aws", "cdk" ], + "maturity": "developer-preview", + "stability": "experimental", "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" } diff --git a/packages/awslint/tslint.yaml b/packages/awslint/tslint.yaml deleted file mode 100644 index 107978fb78786..0000000000000 --- a/packages/awslint/tslint.yaml +++ /dev/null @@ -1,56 +0,0 @@ -extends: "tslint:recommended" -rules: - semicolon: [true, "always", "ignore-interfaces"] - - # Due to VSCode syntax highlighting we're unlikely to do this wrong, and it gets annoying - # when trying to construct literal Fn::Sub() arguments. - no-invalid-template-strings: false - - # No preference for quotes (?) - quotemark: false - - # Our props struct currently don't start with "I" - interface-name: false - - # We're not Java - max-classes-per-file: false - - # We have this wrong on all classes, keep it a warning for now - member-access: - severity: warning - - # Rule is dumb, complains about aliases for interface definitions - interface-over-type-literal: false - - # File should end with a newline. Why? - eofline: false - - # Way more readable without - arrow-parens: false - - # We're using namespaces. - no-namespace: false - - # The lines with CloudFormation doc links are quite long - max-line-length: [true, 150] - - # Super annoying - object-literal-sort-keys: false - - # Trailing comma gets into a fight with itself when splitting lists over multiple lines - trailing-comma: false - - # We create Constructs for their side effect all the time - no-unused-expression: [true, "allow-new"] - - # Without this rule, _blabla would be disallowed, which is necessary to silence unused variable errors. - # Allow Pascal Case for static variables - variable-name: [true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"] - - # Unhandled promises are the source of all kinds of bugs and race conditions... - no-floating-promises: true - - no-console: false - - # Cleaner imports - no-duplicate-imports: error \ No newline at end of file diff --git a/packages/cdk-assets/.gitignore b/packages/cdk-assets/.gitignore index e27652f07d35a..4a1600cd2eca0 100644 --- a/packages/cdk-assets/.gitignore +++ b/packages/cdk-assets/.gitignore @@ -22,3 +22,5 @@ assets.json npm-shrinkwrap.json !.eslintrc.js !jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/cdk-assets/.npmignore b/packages/cdk-assets/.npmignore index 8a58a12181aaa..de7dfbff2926f 100644 --- a/packages/cdk-assets/.npmignore +++ b/packages/cdk-assets/.npmignore @@ -25,4 +25,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/cdk-assets/bin/cdk-assets.ts b/packages/cdk-assets/bin/cdk-assets.ts index 902c4d65ecc41..79b9a5a066b26 100644 --- a/packages/cdk-assets/bin/cdk-assets.ts +++ b/packages/cdk-assets/bin/cdk-assets.ts @@ -61,7 +61,7 @@ function wrapHandler(handler: (x: A) => Promi } main().catch(e => { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.error(e.stack); process.exitCode = 1; }); diff --git a/packages/cdk-assets/bin/list.ts b/packages/cdk-assets/bin/list.ts index 86acb36c3973f..e93358cd729fd 100644 --- a/packages/cdk-assets/bin/list.ts +++ b/packages/cdk-assets/bin/list.ts @@ -4,6 +4,6 @@ export async function list(args: { path: string; }) { const manifest = AssetManifest.fromPath(args.path); - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.log(manifest.list().join('\n')); } \ No newline at end of file diff --git a/packages/cdk-assets/bin/logging.ts b/packages/cdk-assets/bin/logging.ts index 8a8eea4540680..ead34deeaa70c 100644 --- a/packages/cdk-assets/bin/logging.ts +++ b/packages/cdk-assets/bin/logging.ts @@ -18,7 +18,7 @@ export function setLogThreshold(threshold: LogLevel) { export function log(level: LogLevel, message: string) { if (LOG_LEVELS[level] >= LOG_LEVELS[logThreshold]) { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.error(`${level.padEnd(7, ' ')}: ${message}`); } } \ No newline at end of file diff --git a/packages/cdk-assets/bin/publish.ts b/packages/cdk-assets/bin/publish.ts index 9f64dc7e08ed8..001d435e383b1 100644 --- a/packages/cdk-assets/bin/publish.ts +++ b/packages/cdk-assets/bin/publish.ts @@ -1,5 +1,6 @@ import * as os from 'os'; -import { AssetManifest, AssetPublishing, ClientOptions, DestinationPattern, EventType, IAws, IPublishProgress, IPublishProgressListener } from '../lib'; +import { AssetManifest, AssetPublishing, ClientOptions, DestinationPattern, EventType, IAws, + IPublishProgress, IPublishProgressListener } from '../lib'; import { Account } from '../lib/aws'; import { log, LogLevel, VERSION } from './logging'; @@ -28,7 +29,7 @@ export async function publish(args: { if (pub.hasFailures) { for (const failure of pub.failures) { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.error('Failure:', failure.error.stack); } diff --git a/packages/cdk-assets/jest.config.js b/packages/cdk-assets/jest.config.js index 07f5f6c432bb6..fc310b5014407 100644 --- a/packages/cdk-assets/jest.config.js +++ b/packages/cdk-assets/jest.config.js @@ -1,4 +1,4 @@ -const baseConfig = require('../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = { ...baseConfig, coverageThreshold: { diff --git a/packages/cdk-assets/lib/asset-manifest.ts b/packages/cdk-assets/lib/asset-manifest.ts index d08c6f5daee61..ce683485e3511 100644 --- a/packages/cdk-assets/lib/asset-manifest.ts +++ b/packages/cdk-assets/lib/asset-manifest.ts @@ -1,7 +1,7 @@ -import { AssetManifest as AssetManifestSchema, DockerImageDestination, DockerImageSource, - FileDestination, FileSource, Manifest } from '@aws-cdk/cloud-assembly-schema'; import * as fs from 'fs'; import * as path from 'path'; +import { AssetManifest as AssetManifestSchema, DockerImageDestination, DockerImageSource, + FileDestination, FileSource, Manifest } from '@aws-cdk/cloud-assembly-schema'; /** * A manifest of assets diff --git a/packages/cdk-assets/lib/private/archive.ts b/packages/cdk-assets/lib/private/archive.ts index 802a9abac90fb..ce085ec901284 100644 --- a/packages/cdk-assets/lib/private/archive.ts +++ b/packages/cdk-assets/lib/private/archive.ts @@ -1,7 +1,7 @@ -import * as archiver from 'archiver'; import { createWriteStream, promises as fs } from 'fs'; -import * as glob from 'glob'; import * as path from 'path'; +import * as archiver from 'archiver'; +import * as glob from 'glob'; export function zipDirectory(directory: string, outputFile: string): Promise { return new Promise(async (ok, fail) => { @@ -40,7 +40,7 @@ export function zipDirectory(directory: string, outputFile: string): Promise // check that mode is preserved const stat = await fs.stat(path.join(extractDir, 'executable.txt')); - // tslint:disable-next-line:no-bitwise + // eslint-disable-next-line no-bitwise const isExec = (stat.mode & constants.S_IXUSR) || (stat.mode & constants.S_IXGRP) || (stat.mode & constants.S_IXOTH); expect(isExec).toBeTruthy(); } finally { diff --git a/packages/cdk-dasm/package.json b/packages/cdk-dasm/package.json index c5e6b34be451b..b7bdbe248e5e6 100644 --- a/packages/cdk-dasm/package.json +++ b/packages/cdk-dasm/package.json @@ -26,11 +26,11 @@ }, "license": "Apache-2.0", "dependencies": { - "codemaker": "^1.7.0", + "codemaker": "^1.9.0", "yaml": "1.10.0" }, "devDependencies": { - "@types/jest": "^26.0.3", + "@types/jest": "^26.0.4", "@types/yaml": "1.9.7", "jest": "^25.5.4" }, diff --git a/packages/decdk/bin/decdk b/packages/decdk/bin/decdk index 5cf37178d3188..a606284619892 100755 --- a/packages/decdk/bin/decdk +++ b/packages/decdk/bin/decdk @@ -1,3 +1,2 @@ #!/usr/bin/env node -// tslint:disable-next-line:no-var-requires require('./decdk.js'); diff --git a/packages/decdk/bin/decdk-schema b/packages/decdk/bin/decdk-schema index 32ac3414b1006..2576c1d9ae5a4 100755 --- a/packages/decdk/bin/decdk-schema +++ b/packages/decdk/bin/decdk-schema @@ -1,3 +1,2 @@ #!/usr/bin/env node -// tslint:disable-next-line:no-var-requires -require('./decdk-schema.js'); \ No newline at end of file +require('./decdk-schema.js'); diff --git a/packages/decdk/bin/decdk-schema.ts b/packages/decdk/bin/decdk-schema.ts index 36d1340f1d2e3..4a1e66dcd3bf2 100644 --- a/packages/decdk/bin/decdk-schema.ts +++ b/packages/decdk/bin/decdk-schema.ts @@ -1,7 +1,7 @@ import { loadTypeSystem } from '../lib'; import { renderFullSchema } from '../lib/cdk-schema'; -// tslint:disable:no-console +/* eslint-disable no-console */ async function main() { const typeSystem = await loadTypeSystem(); diff --git a/packages/decdk/bin/decdk.ts b/packages/decdk/bin/decdk.ts index 676ca0ded46ff..84633a46d23e4 100644 --- a/packages/decdk/bin/decdk.ts +++ b/packages/decdk/bin/decdk.ts @@ -20,7 +20,7 @@ async function main() { } main().catch(e => { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.error(colors.red(e)); process.exit(1); }); diff --git a/packages/decdk/lib/cdk-schema.ts b/packages/decdk/lib/cdk-schema.ts index 329fcbead9e33..c653d174d66d6 100644 --- a/packages/decdk/lib/cdk-schema.ts +++ b/packages/decdk/lib/cdk-schema.ts @@ -2,7 +2,7 @@ import * as colors from 'colors/safe'; import * as jsiiReflect from 'jsii-reflect'; import { SchemaContext, schemaForTypeReference } from '../lib/jsii2schema'; -// tslint:disable:no-console +/* eslint-disable no-console */ export interface RenderSchemaOptions { warnings?: boolean; diff --git a/packages/decdk/lib/jsii2schema.ts b/packages/decdk/lib/jsii2schema.ts index a20cb30ade966..59d33ce675f7c 100644 --- a/packages/decdk/lib/jsii2schema.ts +++ b/packages/decdk/lib/jsii2schema.ts @@ -1,7 +1,7 @@ import * as jsiiReflect from 'jsii-reflect'; import * as util from 'util'; -// tslint:disable:no-console +/* eslint-disable no-console */ export class SchemaContext { public static root(definitions?: { [fqn: string]: any }): SchemaContext { diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 59b0ad777f07e..ad17de632e09a 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -28,6 +28,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-cdk/alexa-ask": "0.0.0", + "@aws-cdk/pipelines": "0.0.0", "@aws-cdk/app-delivery": "0.0.0", "@aws-cdk/assets": "0.0.0", "@aws-cdk/aws-accessanalyzer": "0.0.0", @@ -178,18 +179,18 @@ "@aws-cdk/region-info": "0.0.0", "constructs": "^3.0.2", "fs-extra": "^9.0.1", - "jsii-reflect": "^1.7.0", + "jsii-reflect": "^1.9.0", "jsonschema": "^1.2.6", "yaml": "1.9.2", "yargs": "^15.3.1" }, "devDependencies": { "@types/fs-extra": "^8.1.0", - "@types/jest": "^26.0.3", + "@types/jest": "^26.0.4", "@types/yaml": "1.9.7", "@types/yargs": "^15.0.5", "jest": "^25.5.4", - "jsii": "^1.7.0" + "jsii": "^1.9.0" }, "keywords": [ "aws", diff --git a/packages/decdk/test/schema.test.ts b/packages/decdk/test/schema.test.ts index 137cc1bb4c061..7b097dcc9fcbe 100644 --- a/packages/decdk/test/schema.test.ts +++ b/packages/decdk/test/schema.test.ts @@ -5,7 +5,7 @@ import { SchemaContext, schemaForInterface } from '../lib/jsii2schema'; const fixturedir = path.join(__dirname, 'fixture'); -// tslint:disable:no-console +/* eslint-disable no-console */ // building the decdk schema often does not complete in the default 5 second Jest timeout jest.setTimeout(60_000); diff --git a/packages/monocdk-experiment/.gitignore b/packages/monocdk-experiment/.gitignore index 9b1b4c8c4a775..129f2f8e0bc37 100644 --- a/packages/monocdk-experiment/.gitignore +++ b/packages/monocdk-experiment/.gitignore @@ -15,3 +15,5 @@ dist # Ignore barrel import entry points /*.ts + +junit.xml \ No newline at end of file diff --git a/packages/monocdk-experiment/.npmignore b/packages/monocdk-experiment/.npmignore index c044a328472e1..b5bc540300d0f 100644 --- a/packages/monocdk-experiment/.npmignore +++ b/packages/monocdk-experiment/.npmignore @@ -22,4 +22,5 @@ tsconfig.json !.jsii .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/packages/monocdk-experiment/package.json b/packages/monocdk-experiment/package.json index 71a5dd88067b3..73a4bd12d2a82 100644 --- a/packages/monocdk-experiment/package.json +++ b/packages/monocdk-experiment/package.json @@ -32,9 +32,6 @@ "eslint": { "disable": true }, - "tslint": { - "disable": true - }, "pre": [ "npm run gen" ] @@ -247,9 +244,10 @@ "@aws-cdk/core": "0.0.0", "@aws-cdk/custom-resources": "0.0.0", "@aws-cdk/cx-api": "0.0.0", + "@aws-cdk/pipelines": "0.0.0", "@aws-cdk/region-info": "0.0.0", "@types/fs-extra": "^8.1.1", - "@types/node": "^10.17.26", + "@types/node": "^10.17.27", "cdk-build-tools": "0.0.0", "fs-extra": "^9.0.1", "pkglint": "0.0.0", diff --git a/scripts/check-prerequisites.sh b/scripts/check-prerequisites.sh new file mode 100755 index 0000000000000..fb2011360eb0e --- /dev/null +++ b/scripts/check-prerequisites.sh @@ -0,0 +1,174 @@ +#!/bin/bash +set -euo pipefail + +# Testing with this to simulate different installed apps: +# docker run -it -v ~/Source/aws-cdk:/var/cdk ubuntu bash + +# Note: Don't use \d in regex, it doesn't work on Macs without GNU tools installed +# Use [0-9] instead + +app="" +app_min="" +app_v="" + +die() { echo "$*" 1>&2 ; exit 1; } +wrong_version() { echo "Found $app version $app_v. Install $app >= $app_min" 1>&2; exit 1; } + +check_which() { + local app=$1 + local min=$2 + + echo -e "Checking if $app is installed... \c" + + w=$(which ${app}) || w="" + + if [ -z $w ] || [ $w == "$app not found" ] + then + die "Missing dependency: $app. Install $app >= $min" + else + echo "Ok" + fi +} + +# [Node.js >= 10.13.0] +# ⚠️ versions `13.0.0` to `13.6.0` are not supported due to compatibility issues with our dependencies. +app="node" +app_min="v10.13.0" +check_which $app $app_min +app_v=$(node --version) + +# Check for version 10.*.* - 29.*.* +echo -e "Checking node version... \c" +if [ $(echo $app_v | grep -c -E "v[12][0-9]\.[0-9]+\.[0-9]+") -eq 1 ] +then + # Check for version 13.0 to 13.6 + if [ $(echo $app_v | grep -c -E "v13\.[0-6]\.[0-9]+") -eq 1 ] + then + die "node versions 13.0.0 to 13.6.0 are not supported due to compatibility issues with our dependencies." + else + # Check for version < 10.13 + if [ $(echo $app_v | grep -c -E "v10\.([0-9]|1[0-2])\.[0-9]+") -eq 1 ] + then + wrong_version + else + echo "Ok" + fi + fi +else + echo "Not 12" + wrong_version +fi + +# [Yarn >= 1.19.1, < 1.30] +app="yarn" +app_min="1.19.1" +check_which $app $app_min +app_v=$(${app} --version) +echo -e "Checking yarn version... \c" +if [ $(echo $app_v | grep -c -E "1\.(19|2[0-9])\.[0-9]+") -eq 1 ] +then + echo "Ok" +else + wrong_version +fi + +# [Java OpenJDK 8, 11, 14] + +# Follow "More Info" on a new Mac when trying javac, install latest Oracle: +# javac 14.0.1 + +# apt install default-jdk on Ubuntu +# javac 11.0.7 + +# Install Coretto 8 from Amazon web site +# javac --version fails +# javac -version +# javac 1.8.0_252 +# Old javac versions output version to stderr... why + +app="javac" +app_min="1.8.0" +check_which $app $app_min +app_v=$(${app} -version 2>&1) +echo -e "Checking javac version... \c" +# 1.8 +if [ $(echo $app_v | grep -c -E "1\.8\.[0-9].*") -eq 1 ] +then + echo "Ok" +else + # 11 or 14 + if [ $(echo $app_v | grep -c -E "1[14]\.[0-9]\.[0-9].*") -eq 1 ] + then + echo "Ok" + else + wrong_version + fi +fi + +# [Apache Maven >= 3.6.0, < 4.0] +app="mvn" +app_min="3.6.0" +check_which $app $app_min +app_v=$(${app} --version) +echo -e "Checking mvn version... \c" +if [ $(echo $app_v | grep -c -E "3\.[6789]\.[0-9].*") -eq 1 ] +then + echo "Ok" +else + wrong_version +fi + +# [.NET Core SDK 3.1.*] +app="dotnet" +app_min="3.1.0" +check_which $app $app_min +app_v=$(${app} --version) +echo -e "Checking $app version... \c" +if [ $(echo $app_v | grep -c -E "3\.1\.[0-9].*") -eq 1 ] +then + echo "Ok" +else + wrong_version +fi + +# [Python >= 3.6.5, < 4.0] +app="python3" +app_min="3.6.5" +check_which $app $app_min +app_v=$(${app} --version) +echo -e "Checking $app version... \c" +if [ $(echo $app_v | grep -c -E "3\.[6789]\.[0-9].*") -eq 1 ] +then + echo "Ok" +else + wrong_version +fi + +# [Ruby >= 2.5.1, < 3.0] +app="ruby" +app_min="2.5.1" +check_which $app $app_min +app_v=$(${app} --version) +echo -e "Checking $app version... \c" +if [ $(echo $app_v | grep -c -E "2\.[56789]\.[0-9].*") -eq 1 ] +then + echo "Ok" +else + wrong_version +fi + +# [Docker >= 19.03] +app="docker" +app_min="19.03.0" +check_which $app $app_min + +# Make sure docker is running +echo -e "Checking if docker is running... \c" +docker_running=$(docker ps) +if [ $? -eq 0 ] +then + echo "Ok" +else + die "Docker is not running" +fi + diff --git a/tools/cdk-build-tools/.gitignore b/tools/cdk-build-tools/.gitignore index 30325abc196ef..19127ac2d8966 100644 --- a/tools/cdk-build-tools/.gitignore +++ b/tools/cdk-build-tools/.gitignore @@ -4,4 +4,5 @@ dist *.snk -!.eslintrc.js \ No newline at end of file +!.eslintrc.js +junit.xml \ No newline at end of file diff --git a/tools/cdk-build-tools/.npmignore b/tools/cdk-build-tools/.npmignore index e73fa5f97606e..d913513263c2b 100644 --- a/tools/cdk-build-tools/.npmignore +++ b/tools/cdk-build-tools/.npmignore @@ -8,4 +8,5 @@ coverage *.snk .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/tools/cdk-build-tools/bin/cdk-build.ts b/tools/cdk-build-tools/bin/cdk-build.ts index dce6dcec1b357..b69ecc4a3c5ca 100644 --- a/tools/cdk-build-tools/bin/cdk-build.ts +++ b/tools/cdk-build-tools/bin/cdk-build.ts @@ -19,11 +19,6 @@ async function main() { desc: 'Specify a different tsc executable', defaultDescription: 'tsc provided by node dependencies', }) - .option('tslint', { - type: 'string', - desc: 'Specify a different tslint executable', - defaultDescription: 'tslint provided by node dependencies', - }) .option('eslint', { type: 'string', desc: 'Specify a different eslint executable', @@ -46,7 +41,7 @@ async function main() { await shell(['cfn2ts', ...options.cloudformation.map(scope => `--scope=${scope}`)], { timers }); } - const overrides: CompilerOverrides = { eslint: args.eslint, jsii: args.jsii, tsc: args.tsc, tslint: args.tslint }; + const overrides: CompilerOverrides = { eslint: args.eslint, jsii: args.jsii, tsc: args.tsc }; await compileCurrentPackage(timers, overrides); await lintCurrentPackage(options, overrides); diff --git a/tools/cdk-build-tools/bin/cdk-lint.ts b/tools/cdk-build-tools/bin/cdk-lint.ts index 185645175c4d9..25fc45c33944b 100644 --- a/tools/cdk-build-tools/bin/cdk-lint.ts +++ b/tools/cdk-build-tools/bin/cdk-lint.ts @@ -5,21 +5,21 @@ import { cdkBuildOptions } from '../lib/package-info'; async function main() { const args = yargs .usage('Usage: cdk-lint') - .option('tslint', { - type: 'string', - desc: 'Specify a different tslint executable', - defaultDescription: 'tslint provided by node dependencies', - }) .option('eslint', { type: 'string', desc: 'Specify a different eslint executable', defaultDescription: 'eslint provided by node dependencies', }) + .option('fix', { + type: 'boolean', + desc: 'Fix the found issues', + default: false, + }) .argv; const options = cdkBuildOptions(); - await lintCurrentPackage(options, { eslint: args.eslint, tslint: args.tslint }); + await lintCurrentPackage(options, { eslint: args.eslint, fix: args.fix }); } main().catch(e => { diff --git a/tools/cdk-build-tools/config/eslintrc.js b/tools/cdk-build-tools/config/eslintrc.js index 18cb020345437..78a71bb780800 100644 --- a/tools/cdk-build-tools/config/eslintrc.js +++ b/tools/cdk-build-tools/config/eslintrc.js @@ -1,3 +1,12 @@ +/** + * JavaScript and generic rules: + * + * https://eslint.org/docs/rules/ + * + * TypeScript-specific rules (including migrations from TSlint), see here: + * + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/ROADMAP.md + */ module.exports = { env: { jest: true, @@ -51,6 +60,106 @@ module.exports = { ], // Require all imported libraries actually resolve (!!required for import/no-extraneous-dependencies to work!!) - 'import/no-unresolved': [ 'error' ] - } + 'import/no-unresolved': [ 'error' ], + + // Require an ordering on all imports -- unfortunately a different ordering than TSLint used to + // enforce, but there are no compatible ESLint rules as far as I can tell :( + // + // WARNING for now, otherwise this will mess up all open PRs. Make it into an error after a transitionary period. + 'import/order': ['warn', { + groups: ['builtin', 'external'], + alphabetize: { order: 'asc', caseInsensitive: true }, + }], + + // Cannot import from the same module twice + 'no-duplicate-imports': ['error'], + + // Cannot shadow names + 'no-shadow': ['error'], + + // Required spacing in property declarations (copied from TSLint, defaults are good) + 'key-spacing': ['error'], + + // Require semicolons + 'semi': ['error', 'always'], + + // Don't unnecessarily quote properties + 'quote-props': ['error', 'consistent-as-needed'], + + // No multiple empty lines + 'no-multiple-empty-lines': ['error'], + + // Max line lengths + 'max-len': ['error', { + code: 150, + ignoreUrls: true, // Most common reason to disable it + ignoreStrings: true, // These are not fantastic but necessary for error messages + ignoreTemplateLiterals: true, + ignoreComments: true, + ignoreRegExpLiterals: true, + }], + + // One of the easiest mistakes to make + '@typescript-eslint/no-floating-promises': ['error'], + + // Don't leave log statements littering the premises! + 'no-console': ['error'], + + // Useless diff results + 'no-trailing-spaces': ['error'], + + // Must use foo.bar instead of foo['bar'] if possible + 'dot-notation': ['error'], + + // Must use 'import' statements (disabled because it doesn't add a lot over no-require-imports) + // '@typescript-eslint/no-var-requires': ['error'], + + // Are you sure | is not a typo for || ? + 'no-bitwise': ['error'], + + // Oh ho ho naming. Everyone's favorite topic! + // FIXME: there's no way to do this properly. The proposed tslint replacement + // works very differently, also checking names in object literals, which we use all over the + // place for configs, mockfs, nodeunit tests, etc. + // + // The maintainer does not want to change behavior. + // https://github.com/typescript-eslint/typescript-eslint/issues/1483 + // + // There is no good replacement for tslint's name checking, currently. We will have to make do + // with jsii's validation. + /* + '@typescript-eslint/naming-convention': ['error', + + // We could maybe be more specific in a number of these but I didn't want to + // spend too much effort. Knock yourself out if you feel like it. + { selector: 'enumMember', format: ['PascalCase', 'UPPER_CASE'] }, + { selector: 'variableLike', format: ['camelCase', 'UPPER_CASE'], leadingUnderscore: 'allow' }, + { selector: 'typeLike', format: ['PascalCase'], leadingUnderscore: 'allow' }, + { selector: 'memberLike', format: ['camelCase', 'PascalCase', 'UPPER_CASE'], leadingUnderscore: 'allow' }, + + // FIXME: there's no way to disable name checking in object literals. Maintainer won't have it + // https://github.com/typescript-eslint/typescript-eslint/issues/1483 + ], + */ + + // Member ordering + '@typescript-eslint/member-ordering': ['error', { + default: [ + "public-static-field", + "public-static-method", + "protected-static-field", + "protected-static-method", + "private-static-field", + "private-static-method", + + "field", + + // Constructors + "constructor", // = ["public-constructor", "protected-constructor", "private-constructor"] + + // Methods + "method", + ] + }], + }, } diff --git a/tools/cdk-build-tools/config/jest.config.js b/tools/cdk-build-tools/config/jest.config.js index b95b04eaba533..f01aaab769bb4 100644 --- a/tools/cdk-build-tools/config/jest.config.js +++ b/tools/cdk-build-tools/config/jest.config.js @@ -22,4 +22,8 @@ module.exports = { "/lib/.*\\.generated\\.[jt]s", "/test/.*\\.[jt]s", ], + reporters: [ + "default", + [ "jest-junit", { suiteName: "jest tests" } ] + ] }; diff --git a/tools/cdk-build-tools/lib/lint.ts b/tools/cdk-build-tools/lib/lint.ts index b2d3e65de2bdb..e9afd90dd5304 100644 --- a/tools/cdk-build-tools/lib/lint.ts +++ b/tools/cdk-build-tools/lib/lint.ts @@ -2,20 +2,17 @@ import * as path from 'path'; import { shell } from './os'; import { CDKBuildOptions, CompilerOverrides } from './package-info'; -export async function lintCurrentPackage(options: CDKBuildOptions, compilers: CompilerOverrides = {}): Promise { +export async function lintCurrentPackage(options: CDKBuildOptions, compilers: CompilerOverrides & { fix?: boolean } = {}): Promise { if (!options.eslint?.disable) { await shell([ compilers.eslint || require.resolve('eslint/bin/eslint'), '.', '--ext=.ts', `--resolve-plugins-relative-to=${__dirname}`, + ...compilers.fix ? ['--fix'] : [], ]); } - if (!options.tslint?.disable) { - await shell([compilers.tslint || require.resolve('tslint/bin/tslint'), '--project', '.']); - } - if (!options.pkglint?.disable) { await shell(['pkglint']); } diff --git a/tools/cdk-build-tools/lib/package-info.ts b/tools/cdk-build-tools/lib/package-info.ts index 40ee4e25eafd4..c20efb61a5cde 100644 --- a/tools/cdk-build-tools/lib/package-info.ts +++ b/tools/cdk-build-tools/lib/package-info.ts @@ -76,7 +76,6 @@ export interface CompilerOverrides { eslint?: string; jsii?: string; tsc?: string; - tslint?: string; } /** @@ -107,10 +106,6 @@ export interface CDKBuildOptions { disable?: boolean; }; - tslint?: { - disable?: boolean; - }; - pkglint?: { disable?: boolean; }; diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index 6f57ca81f4a64..449ad1f22651c 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -19,7 +19,7 @@ "cdk-lint": "bin/cdk-lint" }, "scripts": { - "build": "tsc -b && tslint -p . && chmod +x bin/cdk-build && chmod +x bin/cdk-test && chmod +x bin/cdk-watch && chmod +x bin/cdk-awslint && chmod +x bin/cdk-lint && pkglint && eslint . --ext=.ts", + "build": "tsc -b && chmod +x bin/cdk-build && chmod +x bin/cdk-test && chmod +x bin/cdk-watch && chmod +x bin/cdk-awslint && chmod +x bin/cdk-lint && pkglint && eslint . --ext=.ts", "watch": "tsc -b -w", "pkglint": "pkglint -f", "test": "echo success", @@ -34,12 +34,12 @@ "license": "Apache-2.0", "devDependencies": { "@types/fs-extra": "^8.1.0", - "@types/jest": "^26.0.3", + "@types/jest": "^26.0.4", "@types/yargs": "^15.0.5", "pkglint": "0.0.0" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^3.6.0", + "@typescript-eslint/eslint-plugin": "^3.6.1", "@typescript-eslint/parser": "^2.19.2", "awslint": "0.0.0", "colors": "^1.4.0", @@ -49,13 +49,12 @@ "eslint-plugin-import": "^2.22.0", "fs-extra": "^9.0.1", "jest": "^25.5.4", - "jsii": "^1.7.0", - "jsii-pacmak": "^1.7.0", + "jsii": "^1.9.0", + "jsii-pacmak": "^1.9.0", "nodeunit": "^0.11.3", "nyc": "^15.1.0", - "ts-jest": "^26.1.1", - "tslint": "^5.20.1", - "typescript": "~3.9.5", + "ts-jest": "^26.1.3", + "typescript": "~3.9.6", "yargs": "^15.3.1", "yarn-cling": "0.0.0" }, diff --git a/tools/cdk-integ-tools/.gitignore b/tools/cdk-integ-tools/.gitignore index 7c1bf57e876fc..b609960ce03fb 100644 --- a/tools/cdk-integ-tools/.gitignore +++ b/tools/cdk-integ-tools/.gitignore @@ -9,3 +9,5 @@ dist coverage nyc.config.js !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/tools/cdk-integ-tools/.npmignore b/tools/cdk-integ-tools/.npmignore index 7025b934b52f4..9aca5ee7d9678 100644 --- a/tools/cdk-integ-tools/.npmignore +++ b/tools/cdk-integ-tools/.npmignore @@ -10,4 +10,5 @@ coverage .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/tools/cdk-integ-tools/bin/cdk-integ-assert.ts b/tools/cdk-integ-tools/bin/cdk-integ-assert.ts index 300c5ddc75bf4..758a8288fb0d6 100644 --- a/tools/cdk-integ-tools/bin/cdk-integ-assert.ts +++ b/tools/cdk-integ-tools/bin/cdk-integ-assert.ts @@ -1,9 +1,12 @@ #!/usr/bin/env node // Verify that all integration tests still match their expected output +import { canonicalizeTemplate } from '@aws-cdk/assert'; import { diffTemplate, formatDifferences } from '@aws-cdk/cloudformation-diff'; import { DEFAULT_SYNTH_OPTIONS, IntegrationTests } from '../lib/integ-helpers'; -// tslint:disable:no-console +/* eslint-disable no-console */ + +const IGNORE_ASSETS_PRAGMA = 'pragma:ignore-assets'; async function main() { const tests = await new IntegrationTests('test').fromCliArgs(); // always assert all tests @@ -16,9 +19,13 @@ async function main() { throw new Error(`No such file: ${test.expectedFileName}. Run 'npm run integ'.`); } - const expected = await test.readExpected(); + let expected = await test.readExpected(); + let actual = await test.cdkSynthFast(DEFAULT_SYNTH_OPTIONS); - const actual = await test.cdkSynthFast(DEFAULT_SYNTH_OPTIONS); + if ((await test.pragmas()).includes(IGNORE_ASSETS_PRAGMA)) { + expected = canonicalizeTemplate(expected); + actual = canonicalizeTemplate(actual); + } const diff = diffTemplate(expected, actual); @@ -32,7 +39,7 @@ async function main() { } if (failures.length > 0) { - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len throw new Error(`Some stacks have changed. To verify that they still deploy successfully, run: 'npm run integ ${failures.join(' ')}'`); } } diff --git a/tools/cdk-integ-tools/bin/cdk-integ.ts b/tools/cdk-integ-tools/bin/cdk-integ.ts index 5003d1fc79eea..522aaf750ff67 100644 --- a/tools/cdk-integ-tools/bin/cdk-integ.ts +++ b/tools/cdk-integ-tools/bin/cdk-integ.ts @@ -3,7 +3,7 @@ import * as yargs from 'yargs'; import { DEFAULT_SYNTH_OPTIONS, IntegrationTests } from '../lib/integ-helpers'; -// tslint:disable:no-console +/* eslint-disable no-console */ async function main() { const argv = yargs diff --git a/tools/cdk-integ-tools/lib/integ-helpers.ts b/tools/cdk-integ-tools/lib/integ-helpers.ts index d264f545acde8..ec20b2bbb9995 100644 --- a/tools/cdk-integ-tools/lib/integ-helpers.ts +++ b/tools/cdk-integ-tools/lib/integ-helpers.ts @@ -1,10 +1,13 @@ // Helper functions for integration tests import { spawnSync } from 'child_process'; -import * as fs from 'fs-extra'; import * as path from 'path'; +import * as fs from 'fs-extra'; import { AVAILABILITY_ZONE_FALLBACK_CONTEXT_KEY } from '../../../packages/@aws-cdk/cx-api/lib'; +const CDK_OUTDIR = 'cdk-integ.out'; + const CDK_INTEG_STACK_PRAGMA = '/// !cdk-integ'; +const PRAGMA_PREFIX = 'pragma:'; export class IntegrationTests { constructor(private readonly directory: string) { @@ -95,58 +98,63 @@ export class IntegrationTest { const context = { ...options.context, }; - await exec(['node', `${this.name}`], { - cwd: this.directory, - env: { - ...options.env, - CDK_CONTEXT_JSON: JSON.stringify(context), - CDK_DEFAULT_ACCOUNT: '12345678', - CDK_DEFAULT_REGION: 'test-region', - CDK_OUTDIR: 'cdk.out', - CDK_CLI_ASM_VERSION: '5.0.0', - }, - }); - - // Interpret the cloud assembly directly here. Not great, but I'm wary - // adding dependencies on the libraries that model it. - // - // FIXME: Refactor later if it doesn't introduce dependency cycles - const cloudManifest = await fs.readJSON(path.resolve(this.directory, 'cdk.out', 'manifest.json')); - const stacks: Record = {}; - for (const [artifactId, artifact] of Object.entries(cloudManifest.artifacts ?? {}) as Array<[string, any]>) { - if (artifact.type !== 'aws:cloudformation:stack') { continue; } - - const template = await fs.readJSON(path.resolve(this.directory, 'cdk.out', artifact.properties.templateFile)); - stacks[artifactId] = template; - } - const stacksToDiff = await this.readStackPragma(); + try { + await exec(['node', `${this.name}`], { + cwd: this.directory, + env: { + ...options.env, + CDK_CONTEXT_JSON: JSON.stringify(context), + CDK_DEFAULT_ACCOUNT: '12345678', + CDK_DEFAULT_REGION: 'test-region', + CDK_OUTDIR, + CDK_CLI_ASM_VERSION: '5.0.0', + }, + }); - if (stacksToDiff.length > 0) { - // This is a monster. I'm sorry. :( - const templates = stacksToDiff.length === 1 && stacksToDiff[0] === '*' - ? Object.values(stacks) - : stacksToDiff.map(templateForStackName); + // Interpret the cloud assembly directly here. Not great, but I'm wary + // adding dependencies on the libraries that model it. + // + // FIXME: Refactor later if it doesn't introduce dependency cycles + const cloudManifest = await fs.readJSON(path.resolve(this.directory, CDK_OUTDIR, 'manifest.json')); + const stacks: Record = {}; + for (const [artifactId, artifact] of Object.entries(cloudManifest.artifacts ?? {}) as Array<[string, any]>) { + if (artifact.type !== 'aws:cloudformation:stack') { continue; } + + const template = await fs.readJSON(path.resolve(this.directory, CDK_OUTDIR, artifact.properties.templateFile)); + stacks[artifactId] = template; + } - // We're supposed to just return *a* template (which is an object), but there's a crazy - // case in which we diff multiple templates at once and then they're an array. And it works somehow. - return templates.length === 1 ? templates[0] : templates; - } else { - const names = Object.keys(stacks); - if (names.length !== 1) { - throw new Error('"cdk-integ" can only operate on apps with a single stack.\n\n' + - ' If your app has multiple stacks, specify which stack to select by adding this to your test source:\n\n' + - ` ${CDK_INTEG_STACK_PRAGMA} STACK ...\n\n` + - ` Available stacks: ${names.join(' ')} (wildcards are also supported)\n`); + const stacksToDiff = await this.readStackPragma(); + + if (stacksToDiff.length > 0) { + // This is a monster. I'm sorry. :( + const templates = stacksToDiff.length === 1 && stacksToDiff[0] === '*' + ? Object.values(stacks) + : stacksToDiff.map(templateForStackName); + + // We're supposed to just return *a* template (which is an object), but there's a crazy + // case in which we diff multiple templates at once and then they're an array. And it works somehow. + return templates.length === 1 ? templates[0] : templates; + } else { + const names = Object.keys(stacks); + if (names.length !== 1) { + throw new Error('"cdk-integ" can only operate on apps with a single stack.\n\n' + + ' If your app has multiple stacks, specify which stack to select by adding this to your test source:\n\n' + + ` ${CDK_INTEG_STACK_PRAGMA} STACK ...\n\n` + + ` Available stacks: ${names.join(' ')} (wildcards are also supported)\n`); + } + return stacks[names[0]]; } - return stacks[names[0]]; - } - function templateForStackName(name: string) { - if (!stacks[name]) { - throw new Error(`No such stack in output: ${name}`); + function templateForStackName(name: string) { + if (!stacks[name]) { + throw new Error(`No such stack in output: ${name}`); + } + return stacks[name]; } - return stacks[name]; + } finally { + this.cleanupTemporaryFiles(); } } @@ -159,7 +167,7 @@ export class IntegrationTest { if (options.context) { await this.writeCdkContext(options.context); } else { - this.deleteCdkContext(); + this.cleanupTemporaryFiles(); } const cliSwitches = [ @@ -170,6 +178,8 @@ export class IntegrationTest { '--no-asset-metadata', // save a copy step by not staging assets '--no-staging', + // Different output directory + '-o', CDK_OUTDIR, ]; try { @@ -181,7 +191,7 @@ export class IntegrationTest { env: options.env, }); } finally { - this.deleteCdkContext(); + this.cleanupTemporaryFiles(); } } @@ -225,26 +235,57 @@ export class IntegrationTest { await fs.writeFile(this.expectedFilePath, JSON.stringify(actual, undefined, 2), { encoding: 'utf-8' }); } + /** + * Return the non-stack pragmas + * + * These are all pragmas that start with "pragma:". + * + * For backwards compatibility reasons, all pragmas that DON'T start with this + * string are considered to be stack names. + */ + public async pragmas(): Promise { + return (await this.readIntegPragma()).filter(p => p.startsWith(PRAGMA_PREFIX)); + } + private async writeCdkContext(config: any) { await fs.writeFile(this.cdkContextPath, JSON.stringify(config, undefined, 2), { encoding: 'utf-8' }); } - private deleteCdkContext() { + private cleanupTemporaryFiles() { if (fs.existsSync(this.cdkContextPath)) { fs.unlinkSync(this.cdkContextPath); } - const cdkOutPath = path.join(this.directory, 'cdk.out'); + const cdkOutPath = path.join(this.directory, CDK_OUTDIR); if (fs.existsSync(cdkOutPath)) { fs.removeSync(cdkOutPath); } } /** + * Reads stack names from the "!cdk-integ" pragma. + * + * Every word that's NOT prefixed by "pragma:" is considered a stack name. + * + * @example + * + * /// !cdk-integ + */ + private async readStackPragma(): Promise { + return (await this.readIntegPragma()).filter(p => !p.startsWith(PRAGMA_PREFIX)); + } + + /** + * Read arbitrary cdk-integ pragma directives + * * Reads the test source file and looks for the "!cdk-integ" pragma. If it exists, returns it's * contents. This allows integ tests to supply custom command line arguments to "cdk deploy" and "cdk synth". + * + * @example + * + * /// !cdk-integ [...] */ - private async readStackPragma(): Promise { + private async readIntegPragma(): Promise { const source = await fs.readFile(this.sourceFilePath, { encoding: 'utf-8' }); const pragmaLine = source.split('\n').find(x => x.startsWith(CDK_INTEG_STACK_PRAGMA + ' ')); if (!pragmaLine) { @@ -253,7 +294,7 @@ export class IntegrationTest { const args = pragmaLine.substring(CDK_INTEG_STACK_PRAGMA.length).trim().split(' '); if (args.length === 0) { - throw new Error(`Invalid syntax for cdk-integ pragma. Usage: "${CDK_INTEG_STACK_PRAGMA} STACK ..."`); + throw new Error(`Invalid syntax for cdk-integ pragma. Usage: "${CDK_INTEG_STACK_PRAGMA} [STACK] [pragma:PRAGMA] [...]"`); } return args; } @@ -268,7 +309,7 @@ export const DEFAULT_SYNTH_OPTIONS = { 'ssm:account=12345678:parameterName=/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2:region=test-region': 'ami-1234', 'ssm:account=12345678:parameterName=/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2:region=test-region': 'ami-1234', 'ssm:account=12345678:parameterName=/aws/service/ecs/optimized-ami/amazon-linux/recommended:region=test-region': '{"image_id": "ami-1234"}', - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len 'ami:account=12345678:filters.image-type.0=machine:filters.name.0=amzn-ami-vpc-nat-*:filters.state.0=available:owners.0=amazon:region=test-region': 'ami-1234', 'vpc-provider:account=12345678:filter.isDefault=true:region=test-region:returnAsymmetricSubnets=true': { vpcId: 'vpc-60900905', @@ -335,7 +376,7 @@ function exec(commandLine: string[], options: { cwd?: string, json?: boolean, ve } return output; } catch (e) { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.error('Not JSON: ' + output); throw new Error('Command output is not JSON'); } diff --git a/tools/cdk-integ-tools/package.json b/tools/cdk-integ-tools/package.json index 9dfb2ad3eda96..23b74c1489650 100644 --- a/tools/cdk-integ-tools/package.json +++ b/tools/cdk-integ-tools/package.json @@ -37,6 +37,7 @@ "dependencies": { "@aws-cdk/cloudformation-diff": "0.0.0", "@aws-cdk/cx-api": "0.0.0", + "@aws-cdk/assert": "0.0.0", "aws-cdk": "0.0.0", "fs-extra": "^9.0.1", "yargs": "^15.3.1" @@ -48,5 +49,8 @@ "homepage": "https://github.com/aws/aws-cdk", "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "peerDependencies": { + "@aws-cdk/assert": "0.0.0" } } diff --git a/tools/cfn2ts/.gitignore b/tools/cfn2ts/.gitignore index d5452c10cfe55..443f4c7e29f34 100644 --- a/tools/cfn2ts/.gitignore +++ b/tools/cfn2ts/.gitignore @@ -9,3 +9,5 @@ coverage nyc.config.js *.snk !.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/tools/cfn2ts/.npmignore b/tools/cfn2ts/.npmignore index 7025b934b52f4..9aca5ee7d9678 100644 --- a/tools/cfn2ts/.npmignore +++ b/tools/cfn2ts/.npmignore @@ -10,4 +10,5 @@ coverage .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/tools/cfn2ts/bin/cfn2ts.ts b/tools/cfn2ts/bin/cfn2ts.ts index bc172c8952cbb..dd3daf6aee6f6 100755 --- a/tools/cfn2ts/bin/cfn2ts.ts +++ b/tools/cfn2ts/bin/cfn2ts.ts @@ -3,8 +3,8 @@ import * as fs from 'fs-extra'; import * as yargs from 'yargs'; import generate from '../lib'; -// tslint:disable:no-console -// tslint:disable:max-line-length +/* eslint-disable no-console */ +/* eslint-disable max-len */ async function main() { const argv = yargs.usage('Usage: cfn2ts') diff --git a/tools/cfn2ts/lib/augmentation-generator.ts b/tools/cfn2ts/lib/augmentation-generator.ts index 48b3b74c02861..0ac3540a103d8 100644 --- a/tools/cfn2ts/lib/augmentation-generator.ts +++ b/tools/cfn2ts/lib/augmentation-generator.ts @@ -13,7 +13,7 @@ export class AugmentationGenerator { this.code.line(`// Copyright 2012-${new Date().getFullYear()} Amazon.com, Inc. or its affiliates. All Rights Reserved.`); this.code.line(); - this.code.line('// tslint:disable:max-line-length | This is generated code - line lengths are difficult to control'); + this.code.line('/* eslint-disable max-len */ // This is generated code - line lengths are difficult to control'); } public emitCode(): boolean { diff --git a/tools/cfn2ts/lib/codegen.ts b/tools/cfn2ts/lib/codegen.ts index ff87f6a178222..a00fee69cbb8e 100644 --- a/tools/cfn2ts/lib/codegen.ts +++ b/tools/cfn2ts/lib/codegen.ts @@ -55,7 +55,7 @@ export default class CodeGenerator { this.code.line('// See: docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-resource-specification.html'); this.code.line(`// @cfn2ts:meta@ ${JSON.stringify(meta)}`); this.code.line(); - this.code.line('// tslint:disable:max-line-length | This is generated code - line lengths are difficult to control'); + this.code.line('/* eslint-disable max-len */ // This is generated code - line lengths are difficult to control'); this.code.line(); this.code.line(`import * as ${CORE} from '${coreImport}';`); // explicitly import the cfn-parse.ts file from @core, which is not part of the public API of the module @@ -232,6 +232,7 @@ export default class CodeGenerator { this.code.line(' *'); this.code.line(' * @experimental'); this.code.line(' */'); + // eslint-disable-next-line max-len this.code.openBlock(`public static fromCloudFormation(scope: ${CONSTRUCT_CLASS}, id: string, resourceAttributes: any, options: ${CORE}.FromCloudFormationOptions): ` + `${resourceName.className}`); this.code.line('resourceAttributes = resourceAttributes || {};'); @@ -262,6 +263,7 @@ export default class CodeGenerator { this.code.line('// handle DependsOn'); // DependsOn can be either a single string, or an array of strings this.code.line('resourceAttributes.DependsOn = resourceAttributes.DependsOn ?? [];'); + // eslint-disable-next-line max-len this.code.line('const dependencies: string[] = Array.isArray(resourceAttributes.DependsOn) ? resourceAttributes.DependsOn : [resourceAttributes.DependsOn];'); this.code.openBlock('for (const dep of dependencies)'); this.code.line('const depResource = options.finder.findResource(dep);'); @@ -519,7 +521,7 @@ export default class CodeGenerator { const scalarValidator = `${CORE}.unionValidator(${validatorNames})`; const listValidator = `${CORE}.listValidator(${CORE}.unionValidator(${itemValidatorNames}))`; const scalarMapper = `${CORE}.unionMapper([${validatorNames}], [${types.map(type => this.visitAtom(type)).join(', ')}])`; - // tslint:disable-next-line:max-line-length + // eslint-disable-next-line max-len const listMapper = `${CORE}.listMapper(${CORE}.unionMapper([${itemValidatorNames}], [${itemTypes.map(type => this.visitAtom(type)).join(', ')}]))`; return `${CORE}.unionMapper([${scalarValidator}, ${listValidator}], [${scalarMapper}, ${listMapper}])`; @@ -789,9 +791,11 @@ export default class CodeGenerator { this.beginNamespace(typeName); this.docLink(propTypeSpec.Documentation, '@stability external'); + /* if (!propTypeSpec.Properties || Object.keys(propTypeSpec.Properties).length === 0) { - this.code.line('// tslint:disable-next-line:no-empty-interface | A genuine empty-object type'); + this.code.line('// eslint-disable-next-line somethingsomething | A genuine empty-object type'); } + */ this.code.openBlock(`export interface ${typeName.className}`); const conversionTable: Dictionary = {}; if (propTypeSpec.Properties) { diff --git a/tools/cfn2ts/lib/genspec.ts b/tools/cfn2ts/lib/genspec.ts index 63df05ef82115..ae362934d01be 100644 --- a/tools/cfn2ts/lib/genspec.ts +++ b/tools/cfn2ts/lib/genspec.ts @@ -43,7 +43,7 @@ export class CodeName { return new CodeName('', '', primitiveName); } - // tslint:disable:no-shadowed-variable + /* eslint-disable no-shadow */ constructor( readonly packageName: string, readonly namespace: string, @@ -51,7 +51,7 @@ export class CodeName { readonly specName?: SpecName, readonly methodName?: string) { } - // tslint:enable:no-shadowed-variable + /* eslint-enable no-shadow */ /** * Alias for className @@ -206,7 +206,7 @@ export function attributeDefinition(attributeName: string, spec: schema.Attribut } else if ('Type' in spec && 'PrimitiveItemType' in spec && spec.Type === 'List' && spec.PrimitiveItemType === 'String') { attrType = 'string[]'; } else { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.error(`WARNING: Unable to represent attribute type ${JSON.stringify(spec)} as a native type`); attrType = TOKEN_NAME.fqn; } @@ -228,6 +228,7 @@ export function cloudFormationToScriptName(name: string): string { if (name === 'VPCs') { return 'vpcs'; } const ret = codemaker.toCamelCase(name); + // eslint-disable-next-line @typescript-eslint/naming-convention const suffixes: { [key: string]: string } = { ARNs: 'Arns', MBs: 'MBs', AZs: 'AZs' }; for (const suffix of Object.keys(suffixes)) { diff --git a/tools/cfn2ts/package.json b/tools/cfn2ts/package.json index d302cd2aa01be..068edda71fd0b 100644 --- a/tools/cfn2ts/package.json +++ b/tools/cfn2ts/package.json @@ -30,14 +30,14 @@ "license": "Apache-2.0", "dependencies": { "@aws-cdk/cfnspec": "0.0.0", - "codemaker": "^1.7.0", + "codemaker": "^1.9.0", "fast-json-patch": "^3.0.0-1", "fs-extra": "^9.0.1", "yargs": "^15.3.1" }, "devDependencies": { "@types/fs-extra": "^8.1.0", - "@types/jest": "^26.0.3", + "@types/jest": "^26.0.4", "@types/yargs": "^15.0.5", "cdk-build-tools": "0.0.0", "jest": "^25.5.4", diff --git a/tools/nodeunit-shim/package.json b/tools/nodeunit-shim/package.json index 5037cce6b7c15..9ae1f34aec37a 100644 --- a/tools/nodeunit-shim/package.json +++ b/tools/nodeunit-shim/package.json @@ -12,9 +12,9 @@ "build+test": "npm run build && npm test" }, "devDependencies": { - "@types/node": "^10.17.26", - "typescript": "~3.9.5", - "@types/jest": "^26.0.3" + "@types/node": "^10.17.27", + "typescript": "~3.9.6", + "@types/jest": "^26.0.4" }, "dependencies": { "jest": "^25.5.4" diff --git a/tools/pkglint/.eslintrc.js b/tools/pkglint/.eslintrc.js new file mode 100644 index 0000000000000..61dd8dd001f63 --- /dev/null +++ b/tools/pkglint/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/tools/pkglint/.gitignore b/tools/pkglint/.gitignore index 82521bd0dc9a1..bdb8ee6483a17 100644 --- a/tools/pkglint/.gitignore +++ b/tools/pkglint/.gitignore @@ -4,3 +4,4 @@ dist lib/generated/resources.ts !jest.config.js +!.eslintrc.js diff --git a/tools/pkglint/bin/pkglint.ts b/tools/pkglint/bin/pkglint.ts index d6b9df4fe6441..c6ed94edb5c59 100644 --- a/tools/pkglint/bin/pkglint.ts +++ b/tools/pkglint/bin/pkglint.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import * as yargs from 'yargs'; import { findPackageJsons, ValidationRule } from '../lib'; -// tslint:disable:no-shadowed-variable +/* eslint-disable no-shadow */ const argv = yargs .usage('$0 [directory]') .option('fix', { type: 'boolean', alias: 'f', desc: 'Fix package.json in addition to reporting mistakes'}) @@ -15,7 +15,7 @@ const directory = argv._[0] || '.'; argv.directory = path.resolve(directory, process.cwd()); async function main(): Promise { - const ruleClasses = require('../lib/rules'); + const ruleClasses = require('../lib/rules'); // eslint-disable-line @typescript-eslint/no-require-imports const rules: ValidationRule[] = Object.keys(ruleClasses).map(key => new ruleClasses[key]()).filter(obj => obj instanceof ValidationRule); const pkgs = findPackageJsons(directory); @@ -35,7 +35,7 @@ async function main(): Promise { } main().catch((e) => { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.error(e); process.exit(1); }); diff --git a/tools/pkglint/jest.config.js b/tools/pkglint/jest.config.js index 896018e27914b..7bb32293f74e2 100644 --- a/tools/pkglint/jest.config.js +++ b/tools/pkglint/jest.config.js @@ -2,4 +2,4 @@ module.exports = { testMatch: [ "**/?(*.)+(test).js", ], -}; \ No newline at end of file +}; diff --git a/tools/pkglint/lib/packagejson.ts b/tools/pkglint/lib/packagejson.ts index 1ec46e6b2bdc8..dabd3c67be6de 100644 --- a/tools/pkglint/lib/packagejson.ts +++ b/tools/pkglint/lib/packagejson.ts @@ -1,6 +1,6 @@ +import * as path from 'path'; 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']; diff --git a/tools/pkglint/lib/rules.ts b/tools/pkglint/lib/rules.ts index f47154f7c1181..a7d9ef5bf735a 100644 --- a/tools/pkglint/lib/rules.ts +++ b/tools/pkglint/lib/rules.ts @@ -1,7 +1,7 @@ -import * as caseUtils from 'case'; import * as fs from 'fs'; -import * as glob from 'glob'; import * as path from 'path'; +import * as caseUtils from 'case'; +import * as glob from 'glob'; import * as semver from 'semver'; import { LICENSE, NOTICE } from './licensing'; import { PackageJson, ValidationRule } from './packagejson'; @@ -14,8 +14,7 @@ import { monoRepoRoot, } from './util'; -// tslint:disable-next-line: no-var-requires -const AWS_SERVICE_NAMES = require('./aws-service-official-names.json'); +const AWS_SERVICE_NAMES = require('./aws-service-official-names.json'); // eslint-disable-line @typescript-eslint/no-require-imports /** * Verify that the package name matches the directory name @@ -27,8 +26,8 @@ export class PackageNameMatchesDirectoryName extends ValidationRule { const parts = pkg.packageRoot.split(path.sep); const expectedName = parts[parts.length - 2].startsWith('@') - ? parts.slice(parts.length - 2).join('/') - : parts[parts.length - 1]; + ? parts.slice(parts.length - 2).join('/') + : parts[parts.length - 1]; expectJSON(this.name, pkg, 'name', expectedName); } @@ -69,8 +68,8 @@ export class CdkOutMustBeNpmIgnored extends ValidationRule { message: `${npmIgnorePath}: Must exclude **/cdk.out`, fix: () => fs.writeFileSync( npmIgnorePath, - `${npmIgnore}\n# exclude cdk artifacts\n**/cdk.out` - ) + `${npmIgnore}\n# exclude cdk artifacts\n**/cdk.out`, + ), }); } } @@ -178,9 +177,9 @@ export class ReadmeFile extends ValidationRule { readmeFile, [ `## ${headline || pkg.json.description}`, - 'This module is part of the[AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project.' - ].join('\n') - ) + 'This module is part of the[AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project.', + ].join('\n'), + ), }); } else if (headline) { const requiredFirstLine = `## ${headline}`; @@ -444,7 +443,7 @@ export class CDKKeywords extends ValidationRule { pkg.report({ ruleName: this.name, message: 'Must have keywords', - fix: () => { pkg.json.keywords = []; } + fix: () => { pkg.json.keywords = []; }, }); } @@ -454,7 +453,7 @@ export class CDKKeywords extends ValidationRule { pkg.report({ ruleName: this.name, message: 'Keywords must mention CDK', - fix: () => { pkg.json.keywords.splice(0, 0, 'cdk'); } + fix: () => { pkg.json.keywords.splice(0, 0, 'cdk'); }, }); } @@ -462,7 +461,7 @@ export class CDKKeywords extends ValidationRule { pkg.report({ ruleName: this.name, message: 'Keywords must mention AWS', - fix: () => { pkg.json.keywords.splice(0, 0, 'aws'); } + fix: () => { pkg.json.keywords.splice(0, 0, 'aws'); }, }); } } @@ -491,7 +490,7 @@ export class JSIIJavaPackageIsRequired extends ValidationRule { pkg.report({ ruleName: this.name, message: `JSII "java" package must share the first 3 elements of the expected one: ${expectedPrefix} vs ${actualPrefix}`, - fix: () => deepSet(pkg.json, ['jsii', 'targets', 'java', 'package'], moduleName.javaPackage) + fix: () => deepSet(pkg.json, ['jsii', 'targets', 'java', 'package'], moduleName.javaPackage), }); } } @@ -520,6 +519,7 @@ export class CDKPackage extends ValidationRule { const merkleMarker = '.LAST_PACKAGE'; + if (!shouldUseCDKBuildTools(pkg)) { return; } expectJSON(this.name, pkg, 'scripts.package', 'cdk-package'); const outdir = 'dist'; @@ -591,7 +591,7 @@ export class NoJsiiDep extends ValidationRule { pkg.report({ ruleName: this.name, message: 'packages should not have a devDep on jsii since it is defined at the repo level', - fix: () => pkg.removeDevDependency(predicate) + fix: () => pkg.removeDevDependency(predicate), }); } } @@ -609,7 +609,7 @@ export class NodeCompatibility extends ValidationRule { pkg.report({ ruleName: this.name, message: `packages must support node version 10 and up, but ${atTypesNode} is declared`, - fix: () => pkg.addDevDependency('@types/node', '^10.17.5') + fix: () => pkg.addDevDependency('@types/node', '^10.17.5'), }); } } @@ -630,7 +630,7 @@ export class NoAtTypesInDependencies extends ValidationRule { fix: () => { pkg.addDevDependency(dependency.name, dependency.version); pkg.removeDependency(predicate); - } + }, }); } } @@ -654,7 +654,7 @@ function cdkModuleName(name: string) { .map(s => s === 'aws' ? 'AWS' : caseUtils.pascal(s)) .join('.'); - const pythonName = name.replace(/^@/g, "").replace(/\//g, ".").split(".").map(caseUtils.kebab).join("."); + const pythonName = name.replace(/^@/g, '').replace(/\//g, '.').split('.').map(caseUtils.kebab).join('.'); return { javaPackage: `software.amazon.awscdk${isLegacyCdkPkg ? '' : `.${name.replace(/^aws-/, 'services-').replace(/-/g, '.')}`}`, @@ -667,7 +667,7 @@ function cdkModuleName(name: string) { dotnetNamespace: `Amazon.CDK${isCdkPkg ? '' : `.${dotnetSuffix}`}`, python: { distName: `aws-cdk.${pythonName}`, - module: `aws_cdk.${pythonName.replace(/-/g, "_")}`, + module: `aws_cdk.${pythonName.replace(/-/g, '_')}`, }, }; } @@ -696,7 +696,7 @@ export class JSIIDotNetNamespaceIsRequired extends ValidationRule { pkg.report({ ruleName: this.name, message: `.NET namespace must share the first two segments of the default namespace, '${expectedPrefix}' vs '${actualPrefix}'`, - fix: () => deepSet(pkg.json, ['jsii', 'targets', 'dotnet', 'namespace'], moduleName.dotnetNamespace) + fix: () => deepSet(pkg.json, ['jsii', 'targets', 'dotnet', 'namespace'], moduleName.dotnetNamespace), }); } } @@ -735,18 +735,18 @@ export class JSIIDotNetStrongNameIsRequired extends ValidationRule { if (signAssembly !== signAssemblyExpected) { pkg.report({ ruleName: this.name, - message: `.NET packages must have strong-name signing enabled.`, - fix: () => deepSet(pkg.json, ['jsii', 'targets', 'dotnet', 'signAssembly'], signAssemblyExpected) + message: '.NET packages must have strong-name signing enabled.', + fix: () => deepSet(pkg.json, ['jsii', 'targets', 'dotnet', 'signAssembly'], signAssemblyExpected), }); } const assemblyOriginatorKeyFile = deepGet(pkg.json, ['jsii', 'targets', 'dotnet', 'assemblyOriginatorKeyFile']) as string | undefined; - const assemblyOriginatorKeyFileExpected = "../../key.snk"; + const assemblyOriginatorKeyFileExpected = '../../key.snk'; if (assemblyOriginatorKeyFile !== assemblyOriginatorKeyFileExpected) { pkg.report({ ruleName: this.name, - message: `.NET packages must use the strong name key fetched by fetch-dotnet-snk.sh`, - fix: () => deepSet(pkg.json, ['jsii', 'targets', 'dotnet', 'assemblyOriginatorKeyFile'], assemblyOriginatorKeyFileExpected) + message: '.NET packages must use the strong name key fetched by fetch-dotnet-snk.sh', + fix: () => deepSet(pkg.json, ['jsii', 'targets', 'dotnet', 'assemblyOriginatorKeyFile'], assemblyOriginatorKeyFileExpected), }); } } @@ -764,7 +764,7 @@ export class MustDependOnBuildTools extends ValidationRule { expectDevDependency(this.name, pkg, 'cdk-build-tools', - `${require('../../cdk-build-tools/package.json').version}`); + `${require('../../cdk-build-tools/package.json').version}`); // eslint-disable-line @typescript-eslint/no-require-imports } } @@ -815,7 +815,7 @@ export class RegularDependenciesMustSatisfyPeerDependencies extends ValidationRu pkg.report({ ruleName: this.name, message: `dependency ${depName}: concrete version ${depVersion} does not match peer version '${peerVersion}'`, - fix: () => pkg.addPeerDependency(depName, depVersion) + fix: () => pkg.addPeerDependency(depName, depVersion), }); } } @@ -835,13 +835,13 @@ export class MustDependonCdkByPointVersions extends ValidationRule { // across the repo: in local builds, this should be 0.0.0 and in CI builds // this would be the actual version of the repo after it's been aligned // using scripts/align-version.sh - const expectedVersion = require('../../../package.json').version; + const expectedVersion = require('../../../package.json').version; // eslint-disable-line @typescript-eslint/no-require-imports const ignore = [ '@aws-cdk/cloudformation-diff', '@aws-cdk/cfnspec', '@aws-cdk/cx-api', '@aws-cdk/cloud-assembly-schema', - '@aws-cdk/region-info' + '@aws-cdk/region-info', ]; for (const [depName, depVersion] of Object.entries(pkg.dependencies)) { @@ -854,7 +854,7 @@ export class MustDependonCdkByPointVersions extends ValidationRule { pkg.report({ ruleName: this.name, message: `dependency ${depName} must also appear in peerDependencies`, - fix: () => pkg.addPeerDependency(depName, expectedVersion) + fix: () => pkg.addPeerDependency(depName, expectedVersion), }); } @@ -862,7 +862,7 @@ export class MustDependonCdkByPointVersions extends ValidationRule { pkg.report({ ruleName: this.name, message: `peer dependency ${depName} should have the version ${expectedVersion}`, - fix: () => pkg.addPeerDependency(depName, expectedVersion) + fix: () => pkg.addPeerDependency(depName, expectedVersion), }); } @@ -870,7 +870,7 @@ export class MustDependonCdkByPointVersions extends ValidationRule { pkg.report({ ruleName: this.name, message: `dependency ${depName}: dependency version must be ${expectedVersion}`, - fix: () => pkg.addDependency(depName, expectedVersion) + fix: () => pkg.addDependency(depName, expectedVersion), }); } } @@ -886,6 +886,15 @@ export class MustIgnoreSNK extends ValidationRule { } } +export class MustIgnoreJunitXml extends ValidationRule { + public readonly name = 'ignore/junit'; + + public validate(pkg: PackageJson): void { + fileShouldContain(this.name, pkg, '.npmignore', 'junit.xml'); + fileShouldContain(this.name, pkg, '.gitignore', 'junit.xml'); + } +} + export class NpmIgnoreForJsiiModules extends ValidationRule { public readonly name = 'ignore/jsii'; @@ -962,7 +971,7 @@ export class MustHaveIntegCommand extends ValidationRule { expectDevDependency(this.name, pkg, 'cdk-integ-tools', - `${require('../../cdk-integ-tools/package.json').version}`); + `${require('../../cdk-integ-tools/package.json').version}`); // eslint-disable-line @typescript-eslint/no-require-imports } } @@ -985,13 +994,13 @@ export class PkgLintAsScript extends ValidationRule { public validate(pkg: PackageJson): void { const script = 'pkglint -f'; - expectDevDependency(this.name, pkg, 'pkglint', `${require('../package.json').version}`); + expectDevDependency(this.name, pkg, 'pkglint', `${require('../package.json').version}`); // eslint-disable-line @typescript-eslint/no-require-imports if (!pkg.npmScript('pkglint')) { pkg.report({ ruleName: this.name, message: 'a script called "pkglint" must be included to allow fixing package linting issues', - fix: () => pkg.changeNpmScript('pkglint', () => script) + fix: () => pkg.changeNpmScript('pkglint', () => script), }); } @@ -999,7 +1008,7 @@ export class PkgLintAsScript extends ValidationRule { pkg.report({ ruleName: this.name, message: 'the pkglint script should be: ' + script, - fix: () => pkg.changeNpmScript('pkglint', () => script) + fix: () => pkg.changeNpmScript('pkglint', () => script), }); } } @@ -1018,7 +1027,7 @@ export class NoStarDeps extends ValidationRule { if (deps[d] === '*') { pkg.report({ ruleName, - message: `star dependency not allowed for ${d}` + message: `star dependency not allowed for ${d}`, }); } }); @@ -1151,7 +1160,7 @@ export class PackageInJsiiPackageNoRuntimeDeps extends ValidationRule { if (Object.keys(innerPkg.dependencies).length > 0) { pkg.report({ ruleName: `${this.name}:1`, - message: `NPM Package '${innerPkg.packageName}' inside jsii package can only have devDepencencies` + message: `NPM Package '${innerPkg.packageName}' inside jsii package can only have devDepencencies`, }); } @@ -1191,7 +1200,7 @@ export class YarnNohoistBundledDependencies extends ValidationRule { const repoPackageJson = path.resolve(__dirname, '../../../package.json'); - const nohoist: string[] = require(repoPackageJson).workspaces.nohoist; + const nohoist: string[] = require(repoPackageJson).workspaces.nohoist; // eslint-disable-line @typescript-eslint/no-require-imports const missing = new Array(); for (const dep of bundled) { @@ -1206,7 +1215,7 @@ export class YarnNohoistBundledDependencies extends ValidationRule { ruleName: this.name, message: `Repository-level 'workspaces.nohoist' directive is missing: ${missing.join(', ')}`, fix: () => { - const packageJson = require(repoPackageJson); + const packageJson = require(repoPackageJson); // eslint-disable-line @typescript-eslint/no-require-imports packageJson.workspaces.nohoist = [...packageJson.workspaces.nohoist, ...missing].sort(); fs.writeFileSync(repoPackageJson, `${JSON.stringify(packageJson, null, 2)}\n`, { encoding: 'utf8' }); }, @@ -1227,7 +1236,7 @@ export class ConstructsDependency extends ValidationRule { message: `"constructs" must have a version requirement ${REQUIRED_VERSION}`, fix: () => { pkg.addDevDependency('constructs', REQUIRED_VERSION); - } + }, }); } @@ -1237,7 +1246,7 @@ export class ConstructsDependency extends ValidationRule { message: `"constructs" must have a version requirement ${REQUIRED_VERSION}`, fix: () => { pkg.addDependency('constructs', REQUIRED_VERSION); - } + }, }); if (!pkg.peerDependencies.constructs || pkg.peerDependencies.constructs !== REQUIRED_VERSION) { @@ -1246,7 +1255,7 @@ export class ConstructsDependency extends ValidationRule { message: `"constructs" must have a version requirement ${REQUIRED_VERSION} in peerDependencies`, fix: () => { pkg.addPeerDependency('constructs', REQUIRED_VERSION); - } + }, }); } } @@ -1269,11 +1278,11 @@ export class DoNotAnnounceInCatalog extends ValidationRule { if (pkg.json.awscdkio?.announce !== false) { pkg.report({ ruleName: this.name, - message: `missing "awscdkio.announce: false" in package.json`, + message: 'missing "awscdkio.announce: false" in package.json', fix: () => { pkg.json.awscdkio = pkg.json.awscdkio ?? { }; pkg.json.awscdkio.announce = false; - } + }, }); } } @@ -1295,10 +1304,10 @@ export class EslintSetup extends ValidationRule { [ `const baseConfig = require('${rootRelative}/tools/cdk-build-tools/config/eslintrc');`, "baseConfig.parserOptions.project = __dirname + '/tsconfig.json';", - 'module.exports = baseConfig;' - ].join('\n') + '\n' + 'module.exports = baseConfig;', + ].join('\n') + '\n', ); - } + }, }); } fileShouldContain(this.name, pkg, '.gitignore', '!.eslintrc.js'); @@ -1397,7 +1406,7 @@ function hasIntegTests(pkg: PackageJson) { function shouldUseCDKBuildTools(pkg: PackageJson) { // The packages that DON'T use CDKBuildTools are the package itself // and the packages used by it. - return pkg.packageName !== 'cdk-build-tools' && pkg.packageName !== 'merkle-build'; + return pkg.packageName !== 'cdk-build-tools' && pkg.packageName !== 'merkle-build' && pkg.packageName !== 'awslint'; } function repoRoot(dir: string) { diff --git a/tools/pkglint/lib/util.ts b/tools/pkglint/lib/util.ts index 35966d3809b22..13ecfc06f5d9d 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, PKGLINT_IGNORES } from "./packagejson"; +import { PackageJson, PKGLINT_IGNORES } from './packagejson'; /** * Expect a particular JSON key to be a given value @@ -12,7 +12,7 @@ export function expectJSON(ruleName: string, pkg: PackageJson, jsonPath: string, pkg.report({ ruleName, message: `${jsonPath} should be ${JSON.stringify(expected)}${ignore ? ` (ignoring ${ignore})` : ''}, is ${JSON.stringify(actual)}`, - fix: () => { deepSet(pkg.json, parts, expected); } + fix: () => { deepSet(pkg.json, parts, expected); }, }); } @@ -39,7 +39,7 @@ export function fileShouldContain(ruleName: string, pkg: PackageJson, fileName: pkg.report({ ruleName, message: `${fileName} should contain '${line}'`, - fix: () => pkg.addToFileSync(fileName, line) + fix: () => pkg.addToFileSync(fileName, line), }); } } @@ -52,7 +52,7 @@ export function fileShouldNotContain(ruleName: string, pkg: PackageJson, fileNam pkg.report({ ruleName, message: `${fileName} should NOT contain '${line}'`, - fix: () => pkg.removeFromFileSync(fileName, line) + fix: () => pkg.removeFromFileSync(fileName, line), }); } } @@ -67,7 +67,7 @@ export function fileShouldBe(ruleName: string, pkg: PackageJson, fileName: strin pkg.report({ ruleName, message: `${fileName} should contain exactly '${content}'`, - fix: () => pkg.writeFileSync(fileName, content) + fix: () => pkg.writeFileSync(fileName, content), }); } } @@ -81,7 +81,7 @@ export function expectDevDependency(ruleName: string, pkg: PackageJson, packageN pkg.report({ ruleName, message: `Missing devDependency: ${packageName} @ ${version}`, - fix: () => pkg.addDevDependency(packageName, version) + fix: () => pkg.addDevDependency(packageName, version), }); } } diff --git a/tools/pkglint/package.json b/tools/pkglint/package.json index 1c2037573215e..4b25b04f1e009 100644 --- a/tools/pkglint/package.json +++ b/tools/pkglint/package.json @@ -16,12 +16,12 @@ "pkglint": "bin/pkglint" }, "scripts": { - "build": "tsc -b && tslint -p . && chmod +x bin/pkglint", + "build": "tsc -b && eslint . --ext=.ts && chmod +x bin/pkglint", "test": "jest", "build+test": "npm run build && npm test", "build+test+package": "npm run build && npm test", "watch": "tsc -b -w", - "lint": "tsc -b && tslint -p . --force" + "lint": "tsc -b && eslint . --ext=.ts" }, "keywords": [ "aws", @@ -39,12 +39,13 @@ "@types/semver": "^7.3.1", "@types/yargs": "^15.0.5", "jest": "^25.5.4", - "typescript": "~3.9.5" + "typescript": "~3.9.6" }, "dependencies": { "case": "^1.6.3", "colors": "^1.4.0", "fs-extra": "^9.0.1", + "glob": "^7.1.6", "semver": "^7.2.2", "yargs": "^15.3.1" } diff --git a/tools/pkglint/test/rules.test.ts b/tools/pkglint/test/rules.test.ts index 3494db23467df..c2629c959226b 100644 --- a/tools/pkglint/test/rules.test.ts +++ b/tools/pkglint/test/rules.test.ts @@ -1,6 +1,6 @@ -import * as fs from 'fs-extra'; import * as os from 'os'; import * as path from 'path'; +import * as fs from 'fs-extra'; import { PackageJson } from '../lib/packagejson'; import * as rules from '../lib/rules'; @@ -12,7 +12,7 @@ describe('FeatureStabilityRule', () => { { name: 'Stable Feature', stability: 'Stable' }, { name: 'Dev Preview Feature', stability: 'Developer Preview' }, { name: 'Not Implemented Feature', stability: 'Not Implemented' }, - ] + ], }); const rule = new rules.FeatureStabilityRule(); @@ -65,7 +65,7 @@ describe('FeatureStabilityRule', () => { const dirPath = await fakeModuleDir({ features: [ { name: 'Feature', stability: 'Experimental' }, - ] + ], }); const rule = new rules.FeatureStabilityRule(); @@ -83,7 +83,7 @@ describe('FeatureStabilityRule', () => { const dirPath = await fakeModuleDir({ features: [ { name: 'Feature', stability: 'Developer Preview' }, - ] + ], }); const rule = new rules.FeatureStabilityRule(); @@ -101,7 +101,7 @@ describe('FeatureStabilityRule', () => { const dirPath = await fakeModuleDir({ features: [ { name: 'Feature', stability: 'Stable' }, - ] + ], }); const rule = new rules.FeatureStabilityRule(); @@ -121,7 +121,7 @@ describe('FeatureStabilityRule', () => { private: true, features: [ { name: 'Experimental Feature', stability: 'Experimental' }, - ] + ], }); const rule = new rules.FeatureStabilityRule(); @@ -145,7 +145,7 @@ describe('FeatureStabilityRule', () => { const dirPath = await fakeModuleDir({ features: [ { name: 'Experimental Feature', stability: 'Experimental' }, - ] + ], }, false); const rule = new rules.FeatureStabilityRule(); diff --git a/tools/pkgtools/.gitignore b/tools/pkgtools/.gitignore index 9455f3f0c19cd..fd89c97c8fec7 100644 --- a/tools/pkgtools/.gitignore +++ b/tools/pkgtools/.gitignore @@ -9,4 +9,5 @@ dist .nyc_output coverage -nyc.config.js \ No newline at end of file +nyc.config.js +junit.xml \ No newline at end of file diff --git a/tools/pkgtools/.npmignore b/tools/pkgtools/.npmignore index 7025b934b52f4..9aca5ee7d9678 100644 --- a/tools/pkgtools/.npmignore +++ b/tools/pkgtools/.npmignore @@ -10,4 +10,5 @@ coverage .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/tools/pkgtools/bin/find-jsii-packages.ts b/tools/pkgtools/bin/find-jsii-packages.ts index ed1249a3e3f0d..c240e59fec10f 100644 --- a/tools/pkgtools/bin/find-jsii-packages.ts +++ b/tools/pkgtools/bin/find-jsii-packages.ts @@ -5,7 +5,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as yargs from 'yargs'; -// tslint:disable:no-shadowed-variable +/* eslint-disable no-shadow */ const argv = yargs .usage('$0') .option('verbose', { alias: 'v', type: 'boolean', desc: 'Turn on verbose logging' }) @@ -107,13 +107,13 @@ function enumeratePackages(root: string, pred: PackagePredicate): JSIIPackage[] * a build tool and not shipped, I'm fine with it for now. */ function findPackageFrom(packageName: string, relativeTo: string) { - // tslint:disable-next-line:variable-name + // eslint-disable-next-line @typescript-eslint/naming-convention const Module = module.constructor as any; const searchDirs: string[] = Module._nodeModulePaths(relativeTo).concat(Module.globalPaths); const ret = Module._findPath(packageName, searchDirs, false); if (ret === false) { - // tslint:disable-next-line:no-console + /* eslint-disable-next-line no-console */ console.warn(`Could not find package ${packageName} in scope of ${relativeTo}`); return undefined; } @@ -143,14 +143,14 @@ function findPackageRoot(rootFile: string) { * Returns undefined if any part of the path was unset or * not an object. */ -function deepGet(x: any, path: string[]): any { - path = path.slice(); +function deepGet(x: any, keyPath: string[]): any { + keyPath = keyPath.slice(); - while (path.length > 0 && typeof x === 'object' && x !== null) { - const key = path.shift()!; + while (keyPath.length > 0 && typeof x === 'object' && x !== null) { + const key = keyPath.shift()!; x = x[key]; } - return path.length === 0 ? x : undefined; + return keyPath.length === 0 ? x : undefined; } function debug(s: string) { diff --git a/tools/yarn-cling/.gitignore b/tools/yarn-cling/.gitignore index 2d8e8a2d36377..f12543313a31d 100644 --- a/tools/yarn-cling/.gitignore +++ b/tools/yarn-cling/.gitignore @@ -10,4 +10,5 @@ dist .nyc_output coverage nyc.config.js -!.eslintrc.js \ No newline at end of file +!.eslintrc.js +junit.xml \ No newline at end of file diff --git a/tools/yarn-cling/.npmignore b/tools/yarn-cling/.npmignore index 4d83036ddf1ae..9b1e13f2bbb8c 100644 --- a/tools/yarn-cling/.npmignore +++ b/tools/yarn-cling/.npmignore @@ -11,4 +11,5 @@ jest.config.js .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +junit.xml \ No newline at end of file diff --git a/tools/yarn-cling/bin/yarn-cling.ts b/tools/yarn-cling/bin/yarn-cling.ts index 844c852b15ebe..78d0777a0384b 100644 --- a/tools/yarn-cling/bin/yarn-cling.ts +++ b/tools/yarn-cling/bin/yarn-cling.ts @@ -7,12 +7,12 @@ async function main() { outputFile: 'npm-shrinkwrap.json', }); - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.error('Generated npm-shrinkwrap.json'); } main().catch(e => { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console console.error(e); process.exitCode = 1; }); \ No newline at end of file diff --git a/tools/yarn-cling/jest.config.js b/tools/yarn-cling/jest.config.js index 649ee2e5f5d84..49948395be5ab 100644 --- a/tools/yarn-cling/jest.config.js +++ b/tools/yarn-cling/jest.config.js @@ -1,4 +1,30 @@ -const baseConfig = require('../../tools/cdk-build-tools/config/jest.config'); +// Cannot depend on cdk-build-tools, cdk-build-tools depends on this +const baseConfig = { + moduleFileExtensions: [ + "js", + ], + testMatch: [ + "**/?(*.)+(test).js", + ], + testEnvironment: "node", + coverageThreshold: { + global: { + branches: 80, + statements: 80, + }, + }, + collectCoverage: true, + coverageReporters: [ + "lcov", + "html", + "text-summary", + ], + coveragePathIgnorePatterns: [ + "/lib/.*\\.generated\\.[jt]s", + "/test/.*\\.[jt]s", + ], +}; + module.exports = { ...baseConfig, coverageThreshold: { diff --git a/tools/yarn-cling/lib/index.ts b/tools/yarn-cling/lib/index.ts index eabbf390c5207..44a09a09fcb8d 100644 --- a/tools/yarn-cling/lib/index.ts +++ b/tools/yarn-cling/lib/index.ts @@ -58,7 +58,7 @@ async function generateLockFile(pkgJson: PackageJson, yarnLock: YarnLock, rootDi }; } -// tslint:disable-next-line:max-line-length +// eslint-disable-next-line max-len async function dependenciesFor(deps: Record, yarnLock: YarnLock, rootDir: string): Promise> { const ret: Record = {}; @@ -143,7 +143,7 @@ export function formatPackageLock(entry: PackageLockEntry) { function recurse(names: string[], thisEntry: PackageLockEntry) { if (names.length > 0) { - // tslint:disable-next-line:no-console + // eslint-disable-next-line no-console lines.push(`${names.join(' -> ')} @ ${thisEntry.version}`); } for (const [depName, depEntry] of Object.entries(thisEntry.dependencies || {})) { diff --git a/tools/yarn-cling/package.json b/tools/yarn-cling/package.json index fe1b5490e0967..b9b25843bbb0c 100644 --- a/tools/yarn-cling/package.json +++ b/tools/yarn-cling/package.json @@ -39,10 +39,10 @@ }, "devDependencies": { "@types/yarnpkg__lockfile": "^1.1.3", - "@types/jest": "^26.0.3", + "@types/jest": "^26.0.4", "jest": "^25.5.4", - "@types/node": "^10.17.26", - "typescript": "~3.9.5", + "@types/node": "^10.17.27", + "typescript": "~3.9.6", "pkglint": "0.0.0" }, "dependencies": { diff --git a/tslint.yaml b/tslint.yaml deleted file mode 100644 index 53a5af55dab93..0000000000000 --- a/tslint.yaml +++ /dev/null @@ -1,57 +0,0 @@ -extends: "tslint:recommended" -rules: - semicolon: [true, "always", "ignore-interfaces"] - - # Due to VSCode syntax highlighting we're unlikely to do this wrong, and it gets annoying - # when trying to construct literal Fn::Sub() arguments. - no-invalid-template-strings: false - - # No preference for quotes (?) - quotemark: false - - # Our props struct currently don't start with "I" - interface-name: false - - # We're not Java - max-classes-per-file: false - - # We have this wrong on all classes, keep it a warning for now - member-access: - severity: warning - - # Rule is dumb, complains about aliases for interface definitions - interface-over-type-literal: false - - # File should end with a newline. Why? - eofline: false - - # Way more readable without - arrow-parens: false - - # We're using namespaces. - no-namespace: false - - # The lines with CloudFormation doc links are quite long - max-line-length: [true, 150] - - # Super annoying - object-literal-sort-keys: false - - # Trailing comma gets into a fight with itself when splitting lists over multiple lines - trailing-comma: false - - # We create Constructs for their side effect all the time - no-unused-expression: [true, "allow-new"] - - # Without this rule, _blabla would be disallowed, which is necessary to silence unused variable errors. - # Allow Pascal Case for static variables - variable-name: [true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"] - - # Unhandled promises are the source of all kinds of bugs and race conditions... - no-floating-promises: ["Promise"] - - # We require empty interfaces for AWS construct guideline compliance - no-empty-interface: false - - # Cleaner imports - no-duplicate-imports: error \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f1f6190427536..ed5cf3f1e9b63 100644 --- a/yarn.lock +++ b/yarn.lock @@ -529,10 +529,20 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" -"@jsii/spec@^1.7.0", "@jsii/spec@^1.8.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.8.0.tgz#6860d3ca3461bfc6a568a7418b11d8fa52b98888" - integrity sha512-c9pLt+vYCwHP/lXoVK10nE572ekNo1e1U9osMOlXODW188Ca/ytP12VLCReypt3H8OMqebyK7ea7QdcszR+19w== +"@jest/types@^26.1.0": + version "26.1.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.1.0.tgz#f8afaaaeeb23b5cad49dd1f7779689941dcb6057" + integrity sha512-GXigDDsp6ZlNMhXQDeuy/iYCDsRIHJabWtDzvnn36+aqFfG14JmFV0e/iXxY4SP9vbXSiPNOWdehU5MeqrYHBQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + +"@jsii/spec@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.9.0.tgz#6aad644f106f77aa757b005a744d0d17c24a34a5" + integrity sha512-UrRjk4F+HPr3MtttgbxwJ9AG2hhZCjxsPOuqGQl+8iJ7C1nVNLOAbxZKKbQKd8vu6B04Gl1I/9yu2iTiF2Zr5w== dependencies: jsonschema "^1.2.6" @@ -1427,11 +1437,6 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== -"@types/events@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" - integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== - "@types/fs-extra@^8.1.0": version "8.1.0" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.0.tgz#1114834b53c3914806cd03b3304b37b3bd221a4d" @@ -1446,19 +1451,10 @@ dependencies: "@types/node" "*" -"@types/glob@*", "@types/glob@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" - integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== - dependencies: - "@types/events" "*" - "@types/minimatch" "*" - "@types/node" "*" - -"@types/glob@^7.1.2": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.2.tgz#06ca26521353a545d94a0adc74f38a59d232c987" - integrity sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA== +"@types/glob@*", "@types/glob@^7.1.1", "@types/glob@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" + integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== dependencies: "@types/minimatch" "*" "@types/node" "*" @@ -1490,18 +1486,10 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" -"@types/jest@^25.2.3": - version "25.2.3" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.2.3.tgz#33d27e4c4716caae4eced355097a47ad363fdcaf" - integrity sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw== - dependencies: - jest-diff "^25.2.1" - pretty-format "^25.2.1" - -"@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== +"@types/jest@^26.0.4": + version "26.0.4" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.4.tgz#d2e513e85aca16992816f192582b5e67b0b15efb" + integrity sha512-4fQNItvelbNA9+sFgU+fhJo8ZFF+AS4Egk3GWwCW2jFtViukXbnztccafAdLhzE/0EiCogljtQQXP8aQ9J7sFg== dependencies: jest-diff "^25.2.1" pretty-format "^25.2.1" @@ -1562,10 +1550,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.0.tgz#30d2d09f623fe32cde9cb582c7a6eda2788ce4a8" integrity sha512-WE4IOAC6r/yBZss1oQGM5zs2D7RuKR6Q+w+X2SouPofnWn+LbCqClRyhO3ZE7Ix8nmFgo/oVuuE01cJT2XB13A== -"@types/node@^10.17.26": - version "10.17.26" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.26.tgz#a8a119960bff16b823be4c617da028570779bcfd" - integrity sha512-myMwkO2Cr82kirHY8uknNRHEVtn0wV3DTQfkrjx17jmkstDRZ24gNUdl8AHXVyVclTYI/bNjgTPTAWvWLqXqkw== +"@types/node@^10.17.27": + version "10.17.27" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.27.tgz#391cb391c75646c8ad2a7b6ed3bbcee52d1bdf19" + integrity sha512-J0oqm9ZfAXaPdwNXMMgAhylw5fhmXkToJd06vuDUSAgEDZ/n/69/69UmyBZbc+zT34UnShuDSBqvim3SPnozJg== "@types/nodeunit@^0.0.31": version "0.0.31" @@ -1669,12 +1657,12 @@ resolved "https://registry.yarnpkg.com/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.3.tgz#38fb31d82ed07dea87df6bd565721d11979fd761" integrity sha512-mhdQq10tYpiNncMkg1vovCud5jQm+rWeRVz6fxjCJlY6uhDlAn9GnMSmBa2DQwqPf/jS5YR0K/xChDEh1jdOQg== -"@typescript-eslint/eslint-plugin@^3.6.0": - version "3.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.6.0.tgz#ba2b6cae478b8fca3f2e58ff1313e4198eea2d8a" - integrity sha512-ubHlHVt1lsPQB/CZdEov9XuOFhNG9YRC//kuiS1cMQI6Bs1SsqKrEmZnpgRwthGR09/kEDtr9MywlqXyyYd8GA== +"@typescript-eslint/eslint-plugin@^3.6.1": + version "3.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.6.1.tgz#5ced8fd2087fbb83a76973dea4a0d39d9cb4a642" + integrity sha512-06lfjo76naNeOMDl+mWG9Fh/a0UHKLGhin+mGaIw72FUMbMGBkdi/FEJmgEDzh4eE73KIYzHWvOCYJ0ak7nrJQ== dependencies: - "@typescript-eslint/experimental-utils" "3.6.0" + "@typescript-eslint/experimental-utils" "3.6.1" debug "^4.1.1" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" @@ -1691,14 +1679,14 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/experimental-utils@3.6.0": - version "3.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.6.0.tgz#0138152d66e3e53a6340f606793fb257bf2d76a1" - integrity sha512-4Vdf2hvYMUnTdkCNZu+yYlFtL2v+N2R7JOynIOkFbPjf9o9wQvRwRkzUdWlFd2YiiUwJLbuuLnl5civNg5ykOQ== +"@typescript-eslint/experimental-utils@3.6.1": + version "3.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.6.1.tgz#b5a2738ebbceb3fa90c5b07d50bb1225403c4a54" + integrity sha512-oS+hihzQE5M84ewXrTlVx7eTgc52eu+sVmG7ayLfOhyZmJ8Unvf3osyFQNADHP26yoThFfbxcibbO0d2FjnYhg== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/types" "3.6.0" - "@typescript-eslint/typescript-estree" "3.6.0" + "@typescript-eslint/types" "3.6.1" + "@typescript-eslint/typescript-estree" "3.6.1" eslint-scope "^5.0.0" eslint-utils "^2.0.0" @@ -1712,10 +1700,10 @@ "@typescript-eslint/typescript-estree" "2.28.0" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/types@3.6.0": - version "3.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.6.0.tgz#4bd6eee55d2f9d35a4b36c4804be1880bf68f7bc" - integrity sha512-JwVj74ohUSt0ZPG+LZ7hb95fW8DFOqBuR6gE7qzq55KDI3BepqsCtHfBIoa0+Xi1AI7fq5nCu2VQL8z4eYftqg== +"@typescript-eslint/types@3.6.1": + version "3.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.6.1.tgz#87600fe79a1874235d3cc1cf5c7e1a12eea69eee" + integrity sha512-NPxd5yXG63gx57WDTW1rp0cF3XlNuuFFB5G+Kc48zZ+51ZnQn9yjDEsjTPQ+aWM+V+Z0I4kuTFKjKvgcT1F7xQ== "@typescript-eslint/typescript-estree@2.28.0": version "2.28.0" @@ -1730,13 +1718,13 @@ semver "^6.3.0" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@3.6.0": - version "3.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.6.0.tgz#9b4cab43f1192b64ff51530815b8919f166ce177" - integrity sha512-G57NDSABHjvob7zVV09ehWyD1K6/YUKjz5+AufObFyjNO4DVmKejj47MHjVHHlZZKgmpJD2yyH9lfCXHrPITFg== +"@typescript-eslint/typescript-estree@3.6.1": + version "3.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.6.1.tgz#a5c91fcc5497cce7922ff86bc37d5e5891dcdefa" + integrity sha512-G4XRe/ZbCZkL1fy09DPN3U0mR6SayIv1zSeBNquRFRk7CnVLgkC2ZPj8llEMJg5Y8dJ3T76SvTGtceytniaztQ== dependencies: - "@typescript-eslint/types" "3.6.0" - "@typescript-eslint/visitor-keys" "3.6.0" + "@typescript-eslint/types" "3.6.1" + "@typescript-eslint/visitor-keys" "3.6.1" debug "^4.1.1" glob "^7.1.6" is-glob "^4.0.1" @@ -1744,10 +1732,10 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/visitor-keys@3.6.0": - version "3.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.6.0.tgz#44185eb0cc47651034faa95c5e2e8b314ecebb26" - integrity sha512-p1izllL2Ubwunite0ITjubuMQRBGgjdVYwyG7lXPX8GbrA6qF0uwSRz9MnXZaHMxID4948gX0Ez8v9tUDi/KfQ== +"@typescript-eslint/visitor-keys@3.6.1": + version "3.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.6.1.tgz#5c57a7772f4dd623cfeacc219303e7d46f963b37" + integrity sha512-qC8Olwz5ZyMTZrh4Wl3K4U6tfms0R/mzU4/5W3XeUZptVraGVmbptJbn6h2Ey6Rb3hOs3zWoAUebZk8t47KGiQ== dependencies: eslint-visitor-keys "^1.1.0" @@ -1963,13 +1951,13 @@ archiver-utils@^2.1.0: normalize-path "^3.0.0" readable-stream "^2.0.0" -archiver@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-4.0.1.tgz#3f722b121777e361ca9fad374ecda38e77e63c7f" - integrity sha512-/YV1pU4Nhpf/rJArM23W6GTUjT0l++VbjykrCRua1TSXrn+yM8Qs7XvtwSiRse0iCe49EPNf7ktXnPsWuSb91Q== +archiver@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-4.0.2.tgz#43c72865eadb4ddaaa2fb74852527b6a450d927c" + integrity sha512-B9IZjlGwaxF33UN4oPbfBkyA4V1SxNLeIhR1qY8sRXSsbdUkEHrrOvwlYFPx+8uQeCe9M+FG6KgO+imDmQ79CQ== dependencies: archiver-utils "^2.1.0" - async "^2.6.3" + async "^3.2.0" buffer-crc32 "^0.2.1" glob "^7.1.6" readable-stream "^3.6.0" @@ -2117,12 +2105,10 @@ astral-regex@^1.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== -async@^2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== - dependencies: - lodash "^4.17.14" +async@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" + integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== asynckit@^0.4.0: version "0.4.0" @@ -2160,10 +2146,10 @@ aws-sdk-mock@^5.1.0: sinon "^9.0.1" traverse "^0.6.6" -aws-sdk@^2.637.0, aws-sdk@^2.711.0: - version "2.711.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.711.0.tgz#e4223b676d05a22dc21e17b2de74036e8405704c" - integrity sha512-u2dt1k7vBE5sIzzMNRB+xCSHJ8vNmqeF8/KALdzoFtbBSxM6zKl8My3aV24rNiEC2lEA0PWTQEe9130hulOr2Q== +aws-sdk@^2.637.0, aws-sdk@^2.715.0: + version "2.715.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.715.0.tgz#b890892098e0a4d9e7189ed341267d4a9a6e856b" + integrity sha512-O6ytb66IXFCowp0Ng2bSPM6v/cZVOhjJWFTR1CG4ieG4IroAaVgB3YQKkfPKA0Cy9B/Ovlsm7B737iuroKsd0w== dependencies: buffer "4.9.2" events "1.1.1" @@ -2393,11 +2379,6 @@ buffer@^5.1.0, buffer@^5.5.0: base64-js "^1.0.2" ieee754 "^1.1.4" -builtin-modules@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= - builtins@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" @@ -2576,7 +2557,7 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@2.4.2, chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2593,6 +2574,14 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -2715,10 +2704,10 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= -codemaker@^1.7.0, codemaker@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/codemaker/-/codemaker-1.8.0.tgz#84e1fca0526cce078f04f2aa05ac4751a8ea4543" - integrity sha512-6ND1K9SIBgKVL2qXSZy0HdKKqvmpBQ71U5fwuHasmdFfzyylb8mx/hhVBkzBYpBH3EVecSqh3LUyZu1YZuGNwA== +codemaker@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/codemaker/-/codemaker-1.9.0.tgz#0c4a56b15be0dc4748429cfb8a587d70efb0a831" + integrity sha512-RQCMZB5TputppZELnV7NQRgFStOLTBcfKlXbgwwuVjSieZnKR/zrXnjqouLRQyeuUIC/9BIcRYNh3biHd4iS1Q== dependencies: camelcase "^6.0.0" decamelize "^4.0.0" @@ -2786,7 +2775,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.12.1, commander@~2.20.3: +commander@~2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -2885,7 +2874,7 @@ conventional-changelog-angular@^5.0.10: compare-func "^1.3.1" q "^1.5.1" -conventional-changelog-angular@^5.0.3, conventional-changelog-angular@^5.0.6: +conventional-changelog-angular@^5.0.3: version "5.0.6" resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.6.tgz#269540c624553aded809c29a3508fdc2b544c059" integrity sha512-QDEmLa+7qdhVIv8sFZfVxU1VSyVvnXPsxq8Vam49mKUcO1Z8VTLEJk9uI21uiJUsnmm0I4Hrsdc9TgkOQo9WSA== @@ -2893,13 +2882,6 @@ conventional-changelog-angular@^5.0.3, conventional-changelog-angular@^5.0.6: compare-func "^1.3.1" q "^1.5.1" -conventional-changelog-atom@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/conventional-changelog-atom/-/conventional-changelog-atom-2.0.3.tgz#3bd14280aa09fe3ec49a0e8fe97b5002db02aad4" - integrity sha512-szZe2ut97qNO6vCCMkm1I/tWu6ol4Rr8a9Lx0y/VlpDnpY0PNp+oGpFgU55lplhx+I3Lro9Iv4/gRj0knfgjzg== - dependencies: - q "^1.5.1" - conventional-changelog-atom@^2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/conventional-changelog-atom/-/conventional-changelog-atom-2.0.7.tgz#221575253a04f77a2fd273eb2bf29a138f710abf" @@ -2918,13 +2900,6 @@ conventional-changelog-cli@^2.0.34: meow "^7.0.0" tempfile "^3.0.0" -conventional-changelog-codemirror@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.3.tgz#ebc088154684f8f5171446b8d546ba6b460d46f2" - integrity sha512-t2afackdgFV2yBdHhWPqrKbpaQeVnz2hSJKdWqjasPo5EpIB6TBL0er3cOP1mnGQmuzk9JSvimNSuqjWGDtU5Q== - dependencies: - q "^1.5.1" - conventional-changelog-codemirror@^2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.7.tgz#d6b6a8ce2707710c5a036e305037547fb9e15bfb" @@ -2937,16 +2912,7 @@ conventional-changelog-config-spec@2.1.0: resolved "https://registry.yarnpkg.com/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz#874a635287ef8b581fd8558532bf655d4fb59f2d" integrity sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ== -conventional-changelog-conventionalcommits@4.2.3, conventional-changelog-conventionalcommits@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.2.3.tgz#22855b32d57d0328951c1c2dc01b172a5f24ea37" - integrity sha512-atGa+R4vvEhb8N/8v3IoW59gCBJeeFiX6uIbPu876ENAmkMwsenyn0R21kdDHJFLQdy6zW4J6b4xN8KI3b9oww== - dependencies: - compare-func "^1.3.1" - lodash "^4.17.15" - q "^1.5.1" - -conventional-changelog-conventionalcommits@^4.3.0: +conventional-changelog-conventionalcommits@4.3.0, conventional-changelog-conventionalcommits@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.3.0.tgz#c4205a659f7ca9d7881f29ee78a4e7d6aeb8b3c2" integrity sha512-oYHydvZKU+bS8LnGqTMlNrrd7769EsuEHKy4fh1oMdvvDi7fem8U+nvfresJ1IDB8K00Mn4LpiA/lR+7Gs6rgg== @@ -2974,26 +2940,6 @@ conventional-changelog-core@^3.1.6: read-pkg-up "^3.0.0" through2 "^3.0.0" -conventional-changelog-core@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-4.1.4.tgz#39be27fca6ef20a0f998d7a3a1e97cfa8a055cb6" - integrity sha512-LO58ZbEpp1Ul+y/vOI8rJRsWkovsYkCFbOCVgi6UnVfU8WC0F8K8VQQwaBZWWUpb6JvEiN4GBR5baRP2txZ+Vg== - dependencies: - add-stream "^1.0.0" - conventional-changelog-writer "^4.0.11" - conventional-commits-parser "^3.0.8" - dateformat "^3.0.0" - get-pkg-repo "^1.0.0" - git-raw-commits "2.0.0" - git-remote-origin-url "^2.0.0" - git-semver-tags "^3.0.1" - lodash "^4.17.15" - normalize-package-data "^2.3.5" - q "^1.5.1" - read-pkg "^3.0.0" - read-pkg-up "^3.0.0" - through2 "^3.0.0" - conventional-changelog-core@^4.1.7: version "4.1.7" resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-4.1.7.tgz#6b5cdadda4430895cc4a75a73dd8b36e322ab346" @@ -3015,13 +2961,6 @@ conventional-changelog-core@^4.1.7: shelljs "^0.8.3" through2 "^3.0.0" -conventional-changelog-ember@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/conventional-changelog-ember/-/conventional-changelog-ember-2.0.4.tgz#c29b78e4af7825cbecb6c3fd6086ca5c09471ac1" - integrity sha512-q1u73sO9uCnxN4TSw8xu6MRU8Y1h9kpwtcdJuNRwu/LSKI1IE/iuNSH5eQ6aLlQ3HTyrIpTfUuVybW4W0F17rA== - dependencies: - q "^1.5.1" - conventional-changelog-ember@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/conventional-changelog-ember/-/conventional-changelog-ember-2.0.8.tgz#f0f04eb7ff3c885af97db100865ab95dcfa9917f" @@ -3029,13 +2968,6 @@ conventional-changelog-ember@^2.0.8: dependencies: q "^1.5.1" -conventional-changelog-eslint@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.4.tgz#8f4736a23e0cd97e890e76fccc287db2f205f2ff" - integrity sha512-CPwTUENzhLGl3auunrJxiIEWncAGaby7gOFCdj2gslIuOFJ0KPJVOUhRz4Da/I53sdo/7UncUJkiLg94jEsjxg== - dependencies: - q "^1.5.1" - conventional-changelog-eslint@^3.0.8: version "3.0.8" resolved "https://registry.yarnpkg.com/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.8.tgz#f8b952b7ed7253ea0ac0b30720bb381f4921b46c" @@ -3043,13 +2975,6 @@ conventional-changelog-eslint@^3.0.8: dependencies: q "^1.5.1" -conventional-changelog-express@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/conventional-changelog-express/-/conventional-changelog-express-2.0.1.tgz#fea2231d99a5381b4e6badb0c1c40a41fcacb755" - integrity sha512-G6uCuCaQhLxdb4eEfAIHpcfcJ2+ao3hJkbLrw/jSK/eROeNfnxCJasaWdDAfFkxsbpzvQT4W01iSynU3OoPLIw== - dependencies: - q "^1.5.1" - conventional-changelog-express@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/conventional-changelog-express/-/conventional-changelog-express-2.0.5.tgz#6e93705acdad374516ca125990012a48e710f8de" @@ -3064,21 +2989,6 @@ conventional-changelog-jquery@^3.0.10: dependencies: q "^1.5.1" -conventional-changelog-jquery@^3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.6.tgz#460236ad8fb1d29ff932a14fe4e3a45379b63c5e" - integrity sha512-gHAABCXUNA/HjnZEm+vxAfFPJkgtrZvCDIlCKfdPVXtCIo/Q0lN5VKpx8aR5p8KdVRQFF3OuTlvv5kv6iPuRqA== - dependencies: - q "^1.5.1" - -conventional-changelog-jshint@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.3.tgz#ef6e2caf2ee6ffdfda78fcdf7ce87cf6c512d728" - integrity sha512-Pc2PnMPcez634ckzr4EOWviwRSpZcURaK7bjyD9oK6N5fsC/a+3G7LW5m/JpcHPhA9ZxsfIbm7uqZ3ZDGsQ/sw== - dependencies: - compare-func "^1.3.1" - q "^1.5.1" - conventional-changelog-jshint@^2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.7.tgz#955a69266951cd31e8afeb3f1c55e0517fdca943" @@ -3087,7 +2997,7 @@ conventional-changelog-jshint@^2.0.7: compare-func "^1.3.1" q "^1.5.1" -conventional-changelog-preset-loader@^2.1.1, conventional-changelog-preset-loader@^2.3.0: +conventional-changelog-preset-loader@^2.1.1: version "2.3.0" resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.0.tgz#580fa8ab02cef22c24294d25e52d7ccd247a9a6a" integrity sha512-/rHb32J2EJnEXeK4NpDgMaAVTFZS3o1ExmjKMtYVgIC4MQn0vkNSbYpdGRotkfGGRWiqk3Ri3FBkiZGbAfIfOQ== @@ -3097,56 +3007,39 @@ conventional-changelog-preset-loader@^2.3.4: resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz#14a855abbffd59027fd602581f1f34d9862ea44c" integrity sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g== -conventional-changelog-writer@^4.0.11, conventional-changelog-writer@^4.0.6: - version "4.0.11" - resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.0.11.tgz#9f56d2122d20c96eb48baae0bf1deffaed1edba4" - integrity sha512-g81GQOR392I+57Cw3IyP1f+f42ME6aEkbR+L7v1FBBWolB0xkjKTeCWVguzRrp6UiT1O6gBpJbEy2eq7AnV1rw== +conventional-changelog-writer@^4.0.16: + version "4.0.16" + resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.0.16.tgz#ca10f2691a8ea6d3c2eb74bd35bcf40aa052dda5" + integrity sha512-jmU1sDJDZpm/dkuFxBeRXvyNcJQeKhGtVcFFkwTphUAzyYWcwz2j36Wcv+Mv2hU3tpvLMkysOPXJTLO55AUrYQ== dependencies: compare-func "^1.3.1" - conventional-commits-filter "^2.0.2" + conventional-commits-filter "^2.0.6" dateformat "^3.0.0" - handlebars "^4.4.0" + handlebars "^4.7.6" json-stringify-safe "^5.0.1" lodash "^4.17.15" - meow "^5.0.0" + meow "^7.0.0" semver "^6.0.0" split "^1.0.0" through2 "^3.0.0" -conventional-changelog-writer@^4.0.16: - version "4.0.16" - resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.0.16.tgz#ca10f2691a8ea6d3c2eb74bd35bcf40aa052dda5" - integrity sha512-jmU1sDJDZpm/dkuFxBeRXvyNcJQeKhGtVcFFkwTphUAzyYWcwz2j36Wcv+Mv2hU3tpvLMkysOPXJTLO55AUrYQ== +conventional-changelog-writer@^4.0.6: + version "4.0.11" + resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.0.11.tgz#9f56d2122d20c96eb48baae0bf1deffaed1edba4" + integrity sha512-g81GQOR392I+57Cw3IyP1f+f42ME6aEkbR+L7v1FBBWolB0xkjKTeCWVguzRrp6UiT1O6gBpJbEy2eq7AnV1rw== dependencies: compare-func "^1.3.1" - conventional-commits-filter "^2.0.6" + conventional-commits-filter "^2.0.2" dateformat "^3.0.0" - handlebars "^4.7.6" + handlebars "^4.4.0" json-stringify-safe "^5.0.1" lodash "^4.17.15" - meow "^7.0.0" + meow "^5.0.0" semver "^6.0.0" split "^1.0.0" through2 "^3.0.0" -conventional-changelog@3.1.18: - version "3.1.18" - resolved "https://registry.yarnpkg.com/conventional-changelog/-/conventional-changelog-3.1.18.tgz#7da0a5ab34a604b920b8bf71c6cf5d952f0e805e" - integrity sha512-aN6a3rjgV8qwAJj3sC/Lme2kvswWO7fFSGQc32gREcwIOsaiqBaO6f2p0NomFaPDnTqZ+mMZFLL3hlzvEnZ0mQ== - dependencies: - conventional-changelog-angular "^5.0.6" - conventional-changelog-atom "^2.0.3" - conventional-changelog-codemirror "^2.0.3" - conventional-changelog-conventionalcommits "^4.2.3" - conventional-changelog-core "^4.1.4" - conventional-changelog-ember "^2.0.4" - conventional-changelog-eslint "^3.0.4" - conventional-changelog-express "^2.0.1" - conventional-changelog-jquery "^3.0.6" - conventional-changelog-jshint "^2.0.3" - conventional-changelog-preset-loader "^2.3.0" - -conventional-changelog@^3.1.21: +conventional-changelog@3.1.21, conventional-changelog@^3.1.21: version "3.1.21" resolved "https://registry.yarnpkg.com/conventional-changelog/-/conventional-changelog-3.1.21.tgz#4a774e6bf503acfd7e4685bb750da8c0eccf1e0d" integrity sha512-ZGecVZPEo3aC75VVE4nu85589dDhpMyqfqgUM5Myq6wfKWiNqhDJLSDMsc8qKXshZoY7dqs1hR0H/15kI/G2jQ== @@ -3179,7 +3072,7 @@ conventional-commits-filter@^2.0.6: lodash.ismatch "^4.4.0" modify-values "^1.0.0" -conventional-commits-parser@^3.0.3, conventional-commits-parser@^3.0.8: +conventional-commits-parser@^3.0.3: version "3.0.8" resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.0.8.tgz#23310a9bda6c93c874224375e72b09fb275fe710" integrity sha512-YcBSGkZbYp7d+Cr3NWUeXbPDFUN6g3SaSIzOybi8bjHL5IJ5225OSCxJJ4LgziyEJ7AaJtE9L2/EU6H7Nt/DDQ== @@ -3205,18 +3098,18 @@ conventional-commits-parser@^3.1.0: through2 "^3.0.0" trim-off-newlines "^1.0.0" -conventional-recommended-bump@6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-6.0.5.tgz#be7ec24b43bef57108042ea1d49758b58beabc03" - integrity sha512-srkferrB4kACPEbKYltZwX1CQZAEqbQkabKN444mavLRVMetzwJFJf23/+pwvtMsWbd+cc4HaleV1nHke0f8Rw== +conventional-recommended-bump@6.0.9: + version "6.0.9" + resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-6.0.9.tgz#49ee74f52fbafcc63e89e2297d020279fea318f0" + integrity sha512-DpRmW1k8CpRrcsXHOPGgHgOd4BMGiq2gtXAveGM8B9pSd9b4r4WKnqp1Fd0vkDtk8l973mIk8KKKUYnKRr9SFw== dependencies: concat-stream "^2.0.0" - conventional-changelog-preset-loader "^2.3.0" - conventional-commits-filter "^2.0.2" - conventional-commits-parser "^3.0.8" + conventional-changelog-preset-loader "^2.3.4" + conventional-commits-filter "^2.0.6" + conventional-commits-parser "^3.1.0" git-raw-commits "2.0.0" - git-semver-tags "^3.0.1" - meow "^5.0.0" + git-semver-tags "^4.0.0" + meow "^7.0.0" q "^1.5.1" conventional-recommended-bump@^5.0.0: @@ -3589,26 +3482,26 @@ deprecation@^2.0.0, deprecation@^2.3.1: resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== -detect-indent@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd" - integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA== - detect-indent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= -detect-newline@3.1.0, detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +detect-indent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd" + integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA== detect-newline@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= +detect-newline@^3.0.0, detect-newline@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + dezalgo@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" @@ -3682,7 +3575,7 @@ dot-prop@^4.2.0: dependencies: is-obj "^1.0.0" -dotgitignore@2.1.0: +dotgitignore@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/dotgitignore/-/dotgitignore-2.1.0.tgz#a4b15a4e4ef3cf383598aaf1dfa4a04bcc089b7b" integrity sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA== @@ -4173,10 +4066,10 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-check@^1.25.1: - version "1.25.1" - resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-1.25.1.tgz#aab6e34496a23ba7d7d20188699d9abdcaaa2dcc" - integrity sha512-4lyIDY2YKpSiPXpceCQBTfDxLh/7/C3OHgvzToea3y1YAlv38Wz9mfIsu+MD4go0NX3ow/g98kEmlW00+CoH3w== +fast-check@^1.26.0: + version "1.26.0" + resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-1.26.0.tgz#3a85998a9c30ed7f58976276e06046645e0de18a" + integrity sha512-B1AjSfe0bmi6FdFIzmrrGSjrsF6e2MCmZiM6zJaRbBMP+gIvdNakle5FIMKi0xbS9KlN9BZho1R7oB/qoNIQuA== dependencies: pure-rand "^2.0.0" tslib "^2.0.0" @@ -4242,13 +4135,6 @@ figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== -figures@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.1.0.tgz#4b198dd07d8d71530642864af2d45dd9e459c4ec" - integrity sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg== - dependencies: - escape-string-regexp "^1.0.5" - figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -4256,7 +4142,7 @@ figures@^2.0.0: dependencies: escape-string-regexp "^1.0.5" -figures@^3.0.0: +figures@^3.0.0, figures@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== @@ -4318,14 +4204,6 @@ find-cache-dir@^3.2.0: make-dir "^3.0.2" pkg-dir "^4.1.0" -find-up@4.1.0, find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -4348,6 +4226,14 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + flat-cache@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" @@ -4437,7 +4323,7 @@ fromentries@^1.2.0: resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.2.0.tgz#e6aa06f240d6267f913cea422075ef88b63e7897" integrity sha512-33X7H/wdfO99GdRLLgkjUrD4geAFdq/Uv0kl3HD4da6HDixd2GUg8Mw7dahLCV9r/EARkmtYBB6Tch4EEokFTQ== -fs-access@1.0.1: +fs-access@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a" integrity sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o= @@ -4640,14 +4526,6 @@ git-remote-origin-url@^2.0.0: gitconfiglocal "^1.0.0" pify "^2.3.0" -git-semver-tags@3.0.1, git-semver-tags@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-3.0.1.tgz#9cb9e4974437de1f71f32da3bfe74f4d35afb1b9" - integrity sha512-Hzd1MOHXouITfCasrpVJbRDg9uvW7LfABk3GQmXYZByerBDrfrEMP9HXpNT7RxAbieiocP6u+xq20DkvjwxnCA== - dependencies: - meow "^5.0.0" - semver "^6.0.0" - git-semver-tags@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-2.0.3.tgz#48988a718acf593800f99622a952a77c405bfa34" @@ -5755,6 +5633,16 @@ jest-jasmine2@^25.5.4: pretty-format "^25.5.0" throat "^5.0.0" +jest-junit@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-11.0.1.tgz#944b997b7318efd1f021b4f0fce4937f8d66f392" + integrity sha512-stgc0mBoiSg/F9qWd4KkmR3K7Nk2u+M/dc1oup7gxz9mrzGcEaU2YL9/0QscVqqg3IOaA1P5ZXtozG/XR6j6nw== + dependencies: + mkdirp "^1.0.4" + strip-ansi "^5.2.0" + uuid "^3.3.3" + xml "^1.0.1" + jest-leak-detector@^25.5.0: version "25.5.0" resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-25.5.0.tgz#2291c6294b0ce404241bb56fe60e2d0c3e34f0bb" @@ -5913,6 +5801,17 @@ jest-snapshot@^25.5.1: pretty-format "^25.5.0" semver "^6.3.0" +jest-util@26.x: + version "26.1.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.1.0.tgz#80e85d4ba820decacf41a691c2042d5276e5d8d8" + integrity sha512-rNMOwFQevljfNGvbzNQAxdmXQ+NawW/J72dmddsK0E8vgxXCMtwQ/EH0BiWEIxh0hhMcTsxwAxINt7Lh46Uzbg== + dependencies: + "@jest/types" "^26.1.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" + jest-util@^25.5.0: version "25.5.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0" @@ -6030,65 +5929,65 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -jsii-diff@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/jsii-diff/-/jsii-diff-1.8.0.tgz#d913a845b5b80d0a134f664e10a99069542c9ccf" - integrity sha512-yucKS75pidO3hRMK1ZzUZ1lBrzdI+GI6ij5BilrkT/fmmQXlIi+OQrHu4TUkMTjl6S+s8W/cxnNC+cUOqzoIAw== +jsii-diff@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/jsii-diff/-/jsii-diff-1.9.0.tgz#56102a1983b32c4a25d0b062fa4ffc5cdf5e8c96" + integrity sha512-kBnAZ4oTRDthPRwpM60XWsAE2O6lUw2+WZgLiPRKWgMK1t5wnXeZvj+W8oeBt/SzRM6trcEHPzkL3rKaJFi7+A== dependencies: - "@jsii/spec" "^1.8.0" + "@jsii/spec" "^1.9.0" fs-extra "^9.0.1" - jsii-reflect "^1.8.0" + jsii-reflect "^1.9.0" log4js "^6.3.0" typescript "~3.9.6" - yargs "^15.3.1" + yargs "^15.4.0" -jsii-pacmak@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/jsii-pacmak/-/jsii-pacmak-1.8.0.tgz#de463913ac694298aa81ad08ce2e4061b85cbd4b" - integrity sha512-vR6A5ulEjvWLmY4Fuvrw978Yv+STeTFKH8yU6OLO4q05BC9dRXo32l0h30GGJ+Uyi0YaDYtm5BnFk4p4vOVhWQ== +jsii-pacmak@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/jsii-pacmak/-/jsii-pacmak-1.9.0.tgz#4acc92991e65a7d308a5d029c39fe78b0050abf5" + integrity sha512-rfOslRr+w0EcN4GnT8CqAOHnRf9DRQLH4QAPUuyHKfzXESARn4BIZwL7fbuGxzvuFSM53MoXd1fBvvUfx8mL5g== dependencies: - "@jsii/spec" "^1.8.0" + "@jsii/spec" "^1.9.0" clone "^2.1.2" - codemaker "^1.8.0" + codemaker "^1.9.0" commonmark "^0.29.1" escape-string-regexp "^4.0.0" fs-extra "^9.0.1" - jsii-reflect "^1.8.0" - jsii-rosetta "^1.8.0" + jsii-reflect "^1.9.0" + jsii-rosetta "^1.9.0" semver "^7.3.2" spdx-license-list "^6.2.0" xmlbuilder "^15.1.1" - yargs "^15.3.1" + yargs "^15.4.0" -jsii-reflect@^1.7.0, jsii-reflect@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/jsii-reflect/-/jsii-reflect-1.8.0.tgz#edea43fa83903b46c65c9f2d22b6246610460568" - integrity sha512-M+ai0nQNE0I/osOW6n1UywRFMJWMv5NI49XZUfUknBoO6c6tlDTiKLRmqrxzpJx9f7yoaV2tbf0kAMYNRedwGw== +jsii-reflect@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/jsii-reflect/-/jsii-reflect-1.9.0.tgz#57a8dcf867b9f329b3bb62057d62aef5f2743762" + integrity sha512-jvay0EzZT1VvqK0h6AlvyTstQd1SVKTN4jpi9ZdDzyb6/nJ4sKUZYJPTFNzZv/+2ee/eH/ZuYiZ9Keu0STWq8A== dependencies: - "@jsii/spec" "^1.8.0" + "@jsii/spec" "^1.9.0" colors "^1.4.0" fs-extra "^9.0.1" - oo-ascii-tree "^1.8.0" - yargs "^15.3.1" + oo-ascii-tree "^1.9.0" + yargs "^15.4.0" -jsii-rosetta@^1.7.0, jsii-rosetta@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/jsii-rosetta/-/jsii-rosetta-1.8.0.tgz#0dd8e4fc7a862c6655c99a7c1f6392e20f256ed6" - integrity sha512-04DCDDO3jD4/xBDYsUlAh4x+BlYehtkyvhtvDUKZvGVjKvpU0iK7P/6258/8C19mpRYhBeFO6Ot1UW6NGxVYBA== +jsii-rosetta@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/jsii-rosetta/-/jsii-rosetta-1.9.0.tgz#fbb2017b2fba87bf0050662a369a68bea316861a" + integrity sha512-xtFTxxg/RimWSOehxcIEq/lPEPEGxrLQwTzCbsDMDPO/+gK+foojz+P0ACyo6ZiR3LhXs6y66JR9NCQcy8YixQ== dependencies: - "@jsii/spec" "^1.8.0" + "@jsii/spec" "^1.9.0" commonmark "^0.29.1" fs-extra "^9.0.1" typescript "~3.9.6" xmldom "^0.3.0" - yargs "^15.3.1" + yargs "^15.4.0" -jsii@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/jsii/-/jsii-1.8.0.tgz#fc12a7a727879132b5eac1459c69f565c4ac4b1e" - integrity sha512-kvd0Afm50QnKOlGNeNnTHsKWXTeX+QTqHjRM2GkQYzKK7U/vVKUAU/eahbYUWyEHIC8VJd8rnUa0JLfOL/1jCA== +jsii@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/jsii/-/jsii-1.9.0.tgz#3a039b4fb9838460d9f996484b48cf89283de664" + integrity sha512-vtzzgYDF6WGnAbTP4+ko4QUmz3O4rS8BN5iCYOf5Kh87VVhb3+vkdijSR7esnWJCT7/a+rJfMX5pztec8O/mvw== dependencies: - "@jsii/spec" "^1.8.0" + "@jsii/spec" "^1.9.0" case "^1.6.3" colors "^1.4.0" deep-equal "^2.0.3" @@ -6099,7 +5998,7 @@ jsii@^1.7.0: sort-json "^2.0.0" spdx-license-list "^6.2.0" typescript "~3.9.6" - yargs "^15.3.1" + yargs "^15.4.0" json-diff@^0.5.4: version "0.5.4" @@ -6474,10 +6373,10 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.2.1: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.2.1: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== log-driver@^1.2.7: version "1.2.7" @@ -6716,14 +6615,6 @@ merge2@^1.2.3: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== -micromatch@4.x, micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== - dependencies: - braces "^3.0.1" - picomatch "^2.0.5" - micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -6743,6 +6634,14 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + mime-db@1.43.0: version "1.43.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" @@ -6851,7 +6750,7 @@ mkdirp-promise@^5.0.1: dependencies: mkdirp "*" -mkdirp@*, mkdirp@1.x: +mkdirp@*, mkdirp@1.x, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -7361,10 +7260,10 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -oo-ascii-tree@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/oo-ascii-tree/-/oo-ascii-tree-1.8.0.tgz#47c040a99045bb162281c7603dea0c3fe22591fd" - integrity sha512-vo/cRakWcK/UeGGYQ7ByvgVWzCTchYkogldL3i2ZiHZt1etw/yh1YwimCfUM9rjf/pgBoy1Xe0pJuB+WQ3Ojcw== +oo-ascii-tree@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/oo-ascii-tree/-/oo-ascii-tree-1.9.0.tgz#e4612f188a77b45ce68b44ed66d6aa7db6db1886" + integrity sha512-IuxxWmKSuzVNxHsAmLQTTj0tN9TdxQww2lrNeqObhXKjkjvfQGAdSO9ft6prieR0BDyfowZv3ntnmeaUlqM0Tg== opener@^1.5.1: version "1.5.1" @@ -8510,17 +8409,12 @@ semver-intersect@^1.4.0: dependencies: semver "^5.0.0" -"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.1.tgz#29104598a197d6cbe4733eeecbe968f7b43a9667" - integrity sha512-WfuG+fl6eh3eZ2qAf6goB7nhiCd7NPXhmyFxigB/TOkQyeLP8w8GsVehvtGNtnNmyboz4TgeK40B1Kbql/8c5A== - -semver@7.x, semver@^7.2.2, semver@^7.3.2: +semver@7.x, semver@^7.1.1, semver@^7.2.2, semver@^7.3.2: version "7.3.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== @@ -8869,26 +8763,26 @@ stack-utils@^1.0.1, stack-utils@^1.0.2: resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== -standard-version@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/standard-version/-/standard-version-8.0.0.tgz#3bb1ab495702cf01a9dc602b2b91df1ade6f008e" - integrity sha512-cS/U9yhYPHfyokFce6e/H3U8MaKwZKSGzH25J776sChrae/doDQjsl3vCQ0hW1MSzdrUTb7pir4ApjnbDt/TAg== +standard-version@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/standard-version/-/standard-version-8.0.2.tgz#02ed7131f83046bd04358dc54f97d42c4b2fd828" + integrity sha512-L8X9KFq2SmVmaeZgUmWHFJMOsEWpjgFAwqic6yIIoveM1kdw1vH4Io03WWxUDjypjGqGU6qUtcJoR8UvOv5w3g== dependencies: - chalk "2.4.2" - conventional-changelog "3.1.18" + chalk "^2.4.2" + conventional-changelog "3.1.21" conventional-changelog-config-spec "2.1.0" - conventional-changelog-conventionalcommits "4.2.3" - conventional-recommended-bump "6.0.5" - detect-indent "6.0.0" - detect-newline "3.1.0" - dotgitignore "2.1.0" - figures "3.1.0" - find-up "4.1.0" - fs-access "1.0.1" - git-semver-tags "3.0.1" - semver "7.1.1" - stringify-package "1.0.1" - yargs "15.3.1" + conventional-changelog-conventionalcommits "4.3.0" + conventional-recommended-bump "6.0.9" + detect-indent "^6.0.0" + detect-newline "^3.1.0" + dotgitignore "^2.1.0" + figures "^3.1.0" + find-up "^4.1.0" + fs-access "^1.0.1" + git-semver-tags "^4.0.0" + semver "^7.1.1" + stringify-package "^1.0.1" + yargs "^15.3.1" static-extend@^0.1.1: version "0.1.2" @@ -9031,7 +8925,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -stringify-package@1.0.1: +stringify-package@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85" integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg== @@ -9503,18 +9397,18 @@ trivial-deferred@^1.0.1: resolved "https://registry.yarnpkg.com/trivial-deferred/-/trivial-deferred-1.0.1.tgz#376d4d29d951d6368a6f7a0ae85c2f4d5e0658f3" integrity sha1-N21NKdlR1jaKb3oK6FwvTV4GWPM= -ts-jest@^26.1.1: - version "26.1.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.1.1.tgz#b98569b8a4d4025d966b3d40c81986dd1c510f8d" - integrity sha512-Lk/357quLg5jJFyBQLnSbhycnB3FPe+e9i7ahxokyXxAYoB0q1pPmqxxRPYr4smJic1Rjcf7MXDBhZWgxlli0A== +ts-jest@^26.1.3: + version "26.1.3" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.1.3.tgz#aac928a05fdf13e3e6dfbc8caec3847442667894" + integrity sha512-beUTSvuqR9SmKQEylewqJdnXWMVGJRFqSz2M8wKJe7GBMmLZ5zw6XXKSJckbHNMxn+zdB3guN2eOucSw2gBMnw== dependencies: bs-logger "0.x" buffer-from "1.x" fast-json-stable-stringify "2.x" + jest-util "26.x" json5 "2.x" lodash.memoize "4.x" make-error "1.x" - micromatch "4.x" mkdirp "1.x" semver "7.x" yargs-parser "18.x" @@ -9561,7 +9455,7 @@ tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: +tslib@^1.8.1, tslib@^1.9.0: version "1.11.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== @@ -9571,32 +9465,6 @@ tslib@^2.0.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.0.tgz#18d13fc2dce04051e20f074cc8387fd8089ce4f3" integrity sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g== -tslint@^5.20.1: - version "5.20.1" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d" - integrity sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg== - dependencies: - "@babel/code-frame" "^7.0.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^4.0.1" - glob "^7.1.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - mkdirp "^0.5.1" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.8.0" - tsutils "^2.29.0" - -tsutils@^2.29.0: - version "2.29.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== - dependencies: - tslib "^1.8.1" - tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -9681,7 +9549,7 @@ typescript@^3.3.3, typescript@^3.5.3, typescript@~3.8.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== -typescript@~3.9.5, typescript@~3.9.6: +typescript@~3.9.6: version "3.9.6" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a" integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw== @@ -10130,6 +9998,11 @@ xml2js@0.4.19: sax ">=0.6.0" xmlbuilder "~9.0.1" +xml@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= + xmlbuilder@^15.1.1: version "15.1.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" @@ -10202,7 +10075,7 @@ yapool@^1.0.0: resolved "https://registry.yarnpkg.com/yapool/-/yapool-1.0.0.tgz#f693f29a315b50d9a9da2646a7a6645c96985b6a" integrity sha1-9pPymjFbUNmp2iZGp6ZkXJaYW2o= -yargs-parser@18.x, yargs-parser@^18.1.1, yargs-parser@^18.1.3: +yargs-parser@18.x, yargs-parser@^18.1.1, yargs-parser@^18.1.2, yargs-parser@^18.1.3: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== @@ -10241,23 +10114,6 @@ yargs-parser@^2.4.1: camelcase "^3.0.0" lodash.assign "^4.0.6" -yargs@15.3.1, yargs@^15.0.2, yargs@^15.3.1: - version "15.3.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" - integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.1" - yargs@^13.2.2: version "13.3.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" @@ -10291,6 +10147,40 @@ yargs@^14.0.0, yargs@^14.2.2: y18n "^4.0.0" yargs-parser "^15.0.1" +yargs@^15.0.2, yargs@^15.3.1: + version "15.3.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" + integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.1" + +yargs@^15.4.0: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + yargs@^4.7.1: version "4.8.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0"