diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e55e6526b886..fd5acc5903886 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,69 @@ 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. +## [0.22.0](https://github.com/awslabs/aws-cdk/compare/v0.21.0...v0.22.0) (2019-01-10) + +This is a major release with multiple breaking changes in the core layers. +Please consult the __breaking changes__ section below for details. + +We are focusing these days on finalizing the common patterns and APIs of the CDK +framework and the AWS Construct Library, which is why you are seeing all these +breaking changes. Expect a few more releases with changes of that nature as we +stabilize these APIs, so you might want to hold off with upgrading. We will +communicate when this foundational work is complete. + +### Bug Fixes + +* **core:** automatic cross-stack refs for CFN resources ([#1510](https://github.com/awslabs/aws-cdk/issues/1510)) ([ca5ee35](https://github.com/awslabs/aws-cdk/commit/ca5ee35)) +* **ecs:** correct typo and other minor mistakes in ecs readme ([#1448](https://github.com/awslabs/aws-cdk/issues/1448)) ([9c91b20](https://github.com/awslabs/aws-cdk/commit/9c91b20)) +* **elbv2:** unable to specify load balancer name ([#1486](https://github.com/awslabs/aws-cdk/issues/1486)) ([5b24583](https://github.com/awslabs/aws-cdk/commit/5b24583)), closes [#973](https://github.com/awslabs/aws-cdk/issues/973) [#1481](https://github.com/awslabs/aws-cdk/issues/1481) +* **lambda:** use IRole instead of Role to allow imports ([#1509](https://github.com/awslabs/aws-cdk/issues/1509)) ([b909dcd](https://github.com/awslabs/aws-cdk/commit/b909dcd)) +* **toolkit:** fix typo in --rename option description ([#1438](https://github.com/awslabs/aws-cdk/issues/1438)) ([1dd56d4](https://github.com/awslabs/aws-cdk/commit/1dd56d4)) +* **toolkit:** support multiple toolkit stacks in the same environment ([#1427](https://github.com/awslabs/aws-cdk/issues/1427)) ([095da14](https://github.com/awslabs/aws-cdk/commit/095da14)), closes [#1416](https://github.com/awslabs/aws-cdk/issues/1416) + +### Features + +* **apigateway:** add tracingEnabled property to APIGW Stage ([#1482](https://github.com/awslabs/aws-cdk/issues/1482)) ([fefa764](https://github.com/awslabs/aws-cdk/commit/fefa764)) +* **assets:** enable local tooling scenarios such as lambda debugging ([#1433](https://github.com/awslabs/aws-cdk/issues/1433)) ([0d2b633](https://github.com/awslabs/aws-cdk/commit/0d2b633)), closes [#1432](https://github.com/awslabs/aws-cdk/issues/1432) +* **aws-cdk:** better stack dependency handling ([#1511](https://github.com/awslabs/aws-cdk/issues/1511)) ([b4bbaf0](https://github.com/awslabs/aws-cdk/commit/b4bbaf0)), closes [#1508](https://github.com/awslabs/aws-cdk/issues/1508) [#1505](https://github.com/awslabs/aws-cdk/issues/1505) +* **aws-codepipeline:** jenkins build and test actions ([#1216](https://github.com/awslabs/aws-cdk/issues/1216)) ([471e8eb](https://github.com/awslabs/aws-cdk/commit/471e8eb)) +* **aws-codepipeline:** support notifications on the ManualApprovalAction ([#1368](https://github.com/awslabs/aws-cdk/issues/1368)) ([068fa46](https://github.com/awslabs/aws-cdk/commit/068fa46)), closes [#1222](https://github.com/awslabs/aws-cdk/issues/1222) +* **aws-ecs:** add support Amazon Linux 2 ([#1484](https://github.com/awslabs/aws-cdk/issues/1484)) ([82ec0ff](https://github.com/awslabs/aws-cdk/commit/82ec0ff)), closes [#1483](https://github.com/awslabs/aws-cdk/issues/1483) +* **aws-kms:** allow tagging kms keys ([#1485](https://github.com/awslabs/aws-cdk/issues/1485)) ([f43b4d4](https://github.com/awslabs/aws-cdk/commit/f43b4d4)) +* **aws-lambda:** add input and output artifacts to the CodePipeline action ([#1390](https://github.com/awslabs/aws-cdk/issues/1390)) ([fbd7728](https://github.com/awslabs/aws-cdk/commit/fbd7728)), closes [#1384](https://github.com/awslabs/aws-cdk/issues/1384) +* **cdk:** transparently use constructs from another stack ([d7371f0](https://github.com/awslabs/aws-cdk/commit/d7371f0)), closes [#1324](https://github.com/awslabs/aws-cdk/issues/1324) +* **cli:** allow specifying options using env vars ([#1447](https://github.com/awslabs/aws-cdk/issues/1447)) ([7cd84a0](https://github.com/awslabs/aws-cdk/commit/7cd84a0)) +* aws resource api linting (breaking changes) ([#1434](https://github.com/awslabs/aws-cdk/issues/1434)) ([8c17ca7](https://github.com/awslabs/aws-cdk/commit/8c17ca7)), closes [#742](https://github.com/awslabs/aws-cdk/issues/742) [#1428](https://github.com/awslabs/aws-cdk/issues/1428) +* **core:** cloudformation condition chaining ([#1494](https://github.com/awslabs/aws-cdk/issues/1494)) ([2169015](https://github.com/awslabs/aws-cdk/commit/2169015)), closes [#1457](https://github.com/awslabs/aws-cdk/issues/1457) +* **diff:** better diff of arbitrary json objects ([#1488](https://github.com/awslabs/aws-cdk/issues/1488)) ([607f997](https://github.com/awslabs/aws-cdk/commit/607f997)) +* **route53:** support cname records ([#1487](https://github.com/awslabs/aws-cdk/issues/1487)) ([17eddd1](https://github.com/awslabs/aws-cdk/commit/17eddd1)), closes [#1420](https://github.com/awslabs/aws-cdk/issues/1420) +* **step-functions:** support parameters option ([#1492](https://github.com/awslabs/aws-cdk/issues/1492)) ([935054a](https://github.com/awslabs/aws-cdk/commit/935054a)), closes [#1480](https://github.com/awslabs/aws-cdk/issues/1480) +* **core:** construct base class changes (breaking) ([#1444](https://github.com/awslabs/aws-cdk/issues/1444)) ([fb22a32](https://github.com/awslabs/aws-cdk/commit/fb22a32)), closes [#1431](https://github.com/awslabs/aws-cdk/issues/1431) [#1441](https://github.com/awslabs/aws-cdk/issues/1441) [#189](https://github.com/awslabs/aws-cdk/issues/189) [#1441](https://github.com/awslabs/aws-cdk/issues/1441) [#1431](https://github.com/awslabs/aws-cdk/issues/1431) +* **core:** idiomize cloudformation intrinsics functions ([#1428](https://github.com/awslabs/aws-cdk/issues/1428)) ([04217a5](https://github.com/awslabs/aws-cdk/commit/04217a5)), closes [#202](https://github.com/awslabs/aws-cdk/issues/202) +* **cloudformation:** no more generated attribute types in CFN layer (L1) ([#1489](https://github.com/awslabs/aws-cdk/issues/1489)) ([4d6d5ca](https://github.com/awslabs/aws-cdk/commit/4d6d5ca)), closes [#1455](https://github.com/awslabs/aws-cdk/issues/1455) [#1406](https://github.com/awslabs/aws-cdk/issues/1406) +* **cloudformation:** stop generating legacy cloudformation resources ([#1493](https://github.com/awslabs/aws-cdk/issues/1493)) ([81b4174](https://github.com/awslabs/aws-cdk/commit/81b4174)) + + +### BREAKING CHANGES + +* **Cross-stack references:** if you are using `export()` and `import()` to share constructs between stacks, you can stop doing that, instead of `FooImportProps` accept an `IFoo` directly on the consuming stack, and use that object as usual. +* `ArnUtils.fromComponents()` and `ArnUtils.parse()` have been moved onto `Stack`. +* All CloudFormation pseudo-parameter (such as `AWS::AccountId` etc) are now also accessible via `Stack`, as `stack.accountId` etc. +* All CloudFormation intrinsic functions are now represented as static methods under the `Fn` class (e.g. `Fn.join(...)` instead of `new FnJoin(...).toString()`) +* `resolve()` has been moved to `this.node.resolve()`. +* `CloudFormationJSON.stringify()` has been moved to `this.node.stringifyJson()`. `validate()` now should be `protected`. +* The deprecated `cloudformation.XxxResource` classes have been removed. Use the `CfnXxx` classes instead. +* Any `CfnXxx` resource attributes that represented a list of strings are now typed as `string[]`s (via #1144). Attributes that represent strings, are still typed as `string` (#712) and all other attribute types are represented as `cdk.Token`. +* **route53:** The `route53.TXTRecord` class was renamed to `route53.TxtRecord`. +* **route53:** record classes now require a `zone` when created (not assuming zone is the parent construct). +* **lambda:** the static "metric" methods moved from `lambda.FunctionRef` to `lambda.Function`. +* Many AWS resource classes have been changed to conform to API guidelines: + - `XxxRef` abstract classes are now `IXxx` interfaces + - `XxxRefProps` are now `XxxImportProps` + - `XxxRef.import(...)` are now `Xxx.import(...)` accept `XxxImportProps` and return `IXxx` + - `export(): XxxImportProps` is now defined in `IXxx` and implemented by imported resources + + # [0.21.0](https://github.com/awslabs/aws-cdk/compare/v0.20.0...v0.21.0) (2018-12-20) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ccd508dab5efd..af75f4ac59e14 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -156,6 +156,22 @@ Here are a few useful commands: evaluate only the rule specified [awslint README](./tools/awslint/README.md) for details on include/exclude rule patterns. +### cfn2ts + +This tool is used to generate our low-level CloudFormation resources +(L1/`CfnFoo`). It is executed as part of the build step of all modules in the +AWS Construct Library. + +The tool consults the `cdk-build.cloudformation` key in `package.json` to +determine which CloudFormation namespace this library represents (e.g. +`AWS::EC2` is the namespace for `aws-ec2`). We maintain strict 1:1 relationship +between those. + +Each module also has an npm script called `cfn2ts`: + +* `npm run cfn2ts`: generates L1 for a specific module +* `lerna run cfn2ts`: generates L1 for the entire repo + ## Development Workflows ### Full clean build @@ -249,6 +265,13 @@ $ cd docs $ BUILD_DOCS_DEV=1 ./build-docs.sh ``` +### Tooling Assists +#### Jetbrains (WebStorm/IntelliJ) +This project uses lerna and utilizes symlinks inside nested node_modules directories. You may encounter an issue during +indexing where the IDE attempts to index these directories and keeps following links until the process runs out of +available memory and crashes. To fix this, you can run ```node ./scripts/jetbrains-remove-node-modules.js``` to exclude +these directories. + ## Dependencies ### Adding Dependencies diff --git a/build-docs.sh b/build-docs.sh index 585f48174f950..127f7177b2a39 100755 --- a/build-docs.sh +++ b/build-docs.sh @@ -4,6 +4,8 @@ set -euo pipefail # render docs to docs/dist (cd docs && bash ./build-docs.sh) +lerna ls > docs/dist/packages.txt + # deploy output to 'pack/docs' rm -fr dist/docs mkdir dist/docs diff --git a/design/aws-guidelines.md b/design/aws-guidelines.md index e03a689de5a38..879dcee75cf20 100644 --- a/design/aws-guidelines.md +++ b/design/aws-guidelines.md @@ -204,7 +204,7 @@ 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.ArnUtils.parse`), +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 diff --git a/docs/src/cloudformation.rst b/docs/src/cloudformation.rst index 70556fd1a0ad4..688adc4e0ed64 100644 --- a/docs/src/cloudformation.rst +++ b/docs/src/cloudformation.rst @@ -149,18 +149,29 @@ Outputs Conditions ---------- -.. NEEDS SOME INTRO TEXT +`cdk.Condition` can be used to define CloudFormation "Condition" elements in the template. +The `cdk.Fn.conditionXx()` static methods can be used to produce "condition expressions". .. code-block:: js import sqs = require('@aws-cdk/aws-sqs'); import cdk = require('@aws-cdk/cdk'); + + const param = new cdk.Parameter(this, 'Param1', { type: 'String' }); + const cond1 = new cdk.Condition(this, 'Condition1', { expression: cdk.Fn.conditionEquals("a", "b") }); + const cond2 = new cdk.Condition(this, 'Condition2', { expression: cdk.Fn.conditionContains([ "a", "b", "c" ], "c") }); + const cond3 = new cdk.Condition(this, 'Condition3', { expression: cdk.Fn.conditionEquals(param, "hello") }); + + const cond4 = new cdk.Condition(this, 'Condition4', { + expression: cdk.Fn.conditionOr(cond1, cond2, cdk.Fn.conditionNot(cond3)) + }); + const cond = new cdk.Condition(this, 'MyCondition', { expression: new cdk.FnIf(...) }); const queue = new sqs.CfnQueue(this, 'MyQueue'); - queue.options.condition = cond; + queue.options.condition = cond4; .. _intrinsic_functions: diff --git a/examples/cdk-examples-java/package.json b/examples/cdk-examples-java/package.json index b1fb2de14b4ab..7ec8f9c8d191c 100644 --- a/examples/cdk-examples-java/package.json +++ b/examples/cdk-examples-java/package.json @@ -1,6 +1,6 @@ { "name": "cdk-examples-java", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK examples in Java", "private": true, "repository": { @@ -22,7 +22,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "aws-cdk": "^0.21.0", - "pkgtools": "^0.21.0" + "aws-cdk": "^0.22.0", + "pkgtools": "^0.22.0" } } diff --git a/examples/cdk-examples-typescript/advanced-usage/index.ts b/examples/cdk-examples-typescript/advanced-usage/index.ts index 20fb650ebbe2c..0bd33a422e47e 100644 --- a/examples/cdk-examples-typescript/advanced-usage/index.ts +++ b/examples/cdk-examples-typescript/advanced-usage/index.ts @@ -157,7 +157,7 @@ class CloudFormationExample extends cdk.Stack { // outputs are constructs the synthesize into the template's "Outputs" section new cdk.Output(this, 'Output', { description: 'This is an output of the template', - value: `${new cdk.AwsAccountId()}/${param.ref}` + value: `${this.accountId}/${param.ref}` }); // stack.templateOptions can be used to specify template-level options @@ -166,14 +166,13 @@ class CloudFormationExample extends cdk.Stack { // all CloudFormation's pseudo-parameters are supported via the `cdk.AwsXxx` classes PseudoParameters: [ - new cdk.AwsAccountId(), - new cdk.AwsDomainSuffix(), - new cdk.AwsNotificationARNs(), - new cdk.AwsNoValue(), - new cdk.AwsPartition(), - new cdk.AwsRegion(), - new cdk.AwsStackId(), - new cdk.AwsStackName(), + this.accountId, + this.urlSuffix, + this.notificationArns, + this.partition, + this.region, + this.stackId, + this.stackName, ], // all CloudFormation's intrinsic functions are supported via the `cdk.Fn.xxx` static methods. diff --git a/examples/cdk-examples-typescript/package.json b/examples/cdk-examples-typescript/package.json index ab0beb9728c49..91f5dc2d3f5b0 100644 --- a/examples/cdk-examples-typescript/package.json +++ b/examples/cdk-examples-typescript/package.json @@ -1,6 +1,6 @@ { "name": "cdk-examples-typescript", - "version": "0.21.0", + "version": "0.22.0", "description": "A bunch of CDK examples", "private": true, "scripts": { @@ -18,28 +18,28 @@ }, "license": "Apache-2.0", "devDependencies": { - "aws-cdk": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "pkglint": "^0.21.0" + "aws-cdk": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-autoscaling": "^0.21.0", - "@aws-cdk/aws-cloudformation": "^0.21.0", - "@aws-cdk/aws-cognito": "^0.21.0", - "@aws-cdk/aws-dynamodb": "^0.21.0", - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/aws-ecs": "^0.21.0", - "@aws-cdk/aws-elasticloadbalancing": "^0.21.0", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-lambda": "^0.21.0", - "@aws-cdk/aws-neptune": "^0.21.0", - "@aws-cdk/aws-rds": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/aws-sns": "^0.21.0", - "@aws-cdk/aws-sqs": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0", - "@aws-cdk/runtime-values": "^0.21.0" + "@aws-cdk/aws-autoscaling": "^0.22.0", + "@aws-cdk/aws-cloudformation": "^0.22.0", + "@aws-cdk/aws-cognito": "^0.22.0", + "@aws-cdk/aws-dynamodb": "^0.22.0", + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/aws-ecs": "^0.22.0", + "@aws-cdk/aws-elasticloadbalancing": "^0.22.0", + "@aws-cdk/aws-elasticloadbalancingv2": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-lambda": "^0.22.0", + "@aws-cdk/aws-neptune": "^0.22.0", + "@aws-cdk/aws-rds": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/aws-sns": "^0.22.0", + "@aws-cdk/aws-sqs": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0", + "@aws-cdk/runtime-values": "^0.22.0" }, "repository": { "url": "https://github.com/awslabs/aws-cdk.git", diff --git a/lerna.json b/lerna.json index 9442e719170f8..f4eb639d2afc1 100644 --- a/lerna.json +++ b/lerna.json @@ -14,5 +14,5 @@ } }, "rejectCycles": "true", - "version": "0.21.0" + "version": "0.22.0" } diff --git a/packages/@aws-cdk/alexa-ask/package.json b/packages/@aws-cdk/alexa-ask/package.json index 1b7912a0799e0..bb90ccb028d26 100644 --- a/packages/@aws-cdk/alexa-ask/package.json +++ b/packages/@aws-cdk/alexa-ask/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/alexa-ask", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for Alexa::ASK", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -36,7 +36,8 @@ "pkglint": "pkglint -f", "test": "cdk-test", "watch": "cdk-watch", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "Alexa::ASK" @@ -54,18 +55,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts index e784850965d7a..4b9873f6b48b2 100644 --- a/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts +++ b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts @@ -140,16 +140,6 @@ export class PipelineDeployStackAction extends cdk.Construct { }); } - public validate(): string[] { - const result = super.validate(); - const assets = this.stack.node.metadata.filter(md => md.type === cxapi.ASSET_METADATA); - if (assets.length > 0) { - // FIXME: Implement the necessary actions to publish assets - result.push(`Cannot deploy the stack ${this.stack.name} because it references ${assets.length} asset(s)`); - } - return result; - } - /** * Add policy statements to the role deploying the stack. * @@ -162,6 +152,16 @@ export class PipelineDeployStackAction extends cdk.Construct { public addToRolePolicy(statement: iam.PolicyStatement) { this.role.addToPolicy(statement); } + + protected validate(): string[] { + const result = super.validate(); + const assets = this.stack.node.metadata.filter(md => md.type === cxapi.ASSET_METADATA); + if (assets.length > 0) { + // FIXME: Implement the necessary actions to publish assets + result.push(`Cannot deploy the stack ${this.stack.name} because it references ${assets.length} asset(s)`); + } + return result; + } } function cfnCapabilities(adminPermissions: boolean, capabilities?: cfn.CloudFormationCapabilities): cfn.CloudFormationCapabilities { diff --git a/packages/@aws-cdk/app-delivery/package.json b/packages/@aws-cdk/app-delivery/package.json index f14ae9a9488f6..650f20cc14110 100644 --- a/packages/@aws-cdk/app-delivery/package.json +++ b/packages/@aws-cdk/app-delivery/package.json @@ -1,7 +1,7 @@ { "name": "@aws-cdk/app-delivery", "description": "Continuous Integration / Continuous Delivery for CDK Applications", - "version": "0.21.0", + "version": "0.22.0", "main": "lib/index.js", "types": "lib/index.d.ts", "jsii": { @@ -33,21 +33,21 @@ "awslint": "cdk-awslint" }, "dependencies": { - "@aws-cdk/aws-cloudformation": "^0.21.0", - "@aws-cdk/aws-codebuild": "^0.21.0", - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0", - "@aws-cdk/cx-api": "^0.21.0" + "@aws-cdk/aws-cloudformation": "^0.22.0", + "@aws-cdk/aws-codebuild": "^0.22.0", + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0", + "@aws-cdk/cx-api": "^0.22.0" }, "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "@aws-cdk/aws-codepipeline": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", + "@aws-cdk/assert": "^0.22.0", + "@aws-cdk/aws-codepipeline": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", "fast-check": "^1.7.0", - "pkglint": "^0.21.0" + "pkglint": "^0.22.0" }, "repository": { "type": "git", @@ -65,12 +65,12 @@ "cdk" ], "peerDependencies": { - "@aws-cdk/aws-cloudformation": "^0.21.0", - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-cloudformation": "^0.22.0", + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts index 3a73933d135e9..25f41261ec849 100644 --- a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts +++ b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts @@ -274,7 +274,7 @@ export = nodeunit.testCase({ for (let i = 0 ; i < assetCount ; i++) { deployedStack.node.addMetadata(cxapi.ASSET_METADATA, {}); } - test.deepEqual(action.validate(), + test.deepEqual(action.node.validateTree().map(x => x.message), [`Cannot deploy the stack DeployedStack because it references ${assetCount} asset(s)`]); } ) diff --git a/packages/@aws-cdk/applet-js/package.json b/packages/@aws-cdk/applet-js/package.json index d2eff5316947d..a2842d91d1cef 100644 --- a/packages/@aws-cdk/applet-js/package.json +++ b/packages/@aws-cdk/applet-js/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/applet-js", - "version": "0.21.0", + "version": "0.22.0", "description": "Javascript CDK applet host program", "main": "bin/cdk-applet-js.js", "types": "bin/cdk-applet-js.d.ts", @@ -24,11 +24,11 @@ "devDependencies": { "@types/fs-extra": "^5.0.4", "@types/yaml": "^1.0.0", - "cdk-build-tools": "^0.21.0", - "pkglint": "^0.21.0" + "cdk-build-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0", + "@aws-cdk/cdk": "^0.22.0", "fs-extra": "^7.0.0", "source-map-support": "^0.5.6", "yaml": "^1.1.0" diff --git a/packages/@aws-cdk/assert/lib/expect.ts b/packages/@aws-cdk/assert/lib/expect.ts index a98a18abdc389..13112fbf4b6cd 100644 --- a/packages/@aws-cdk/assert/lib/expect.ts +++ b/packages/@aws-cdk/assert/lib/expect.ts @@ -9,6 +9,9 @@ export function expect(stack: api.SynthesizedStack | cdk.Stack, skipValidation = if (isStackClassInstance(stack)) { if (!skipValidation) { + // Do a prepare-and-validate run over the given stack + stack.node.prepareTree(); + const errors = stack.node.validateTree(); if (errors.length > 0) { throw new Error(`Stack validation failed:\n${errors.map(e => `${e.message} at: ${e.source.node.scope}`).join('\n')}`); @@ -34,4 +37,4 @@ export function expect(stack: api.SynthesizedStack | cdk.Stack, skipValidation = function isStackClassInstance(x: api.SynthesizedStack | cdk.Stack): x is cdk.Stack { return 'toCloudFormation' in x; -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/assert/package.json b/packages/@aws-cdk/assert/package.json index d04f6941dd96f..98ecdb8bf4f44 100644 --- a/packages/@aws-cdk/assert/package.json +++ b/packages/@aws-cdk/assert/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/assert", - "version": "0.21.0", + "version": "0.22.0", "description": "An assertion library for use with CDK Apps", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -23,13 +23,13 @@ }, "license": "Apache-2.0", "devDependencies": { - "cdk-build-tools": "^0.21.0", - "pkglint": "^0.21.0" + "cdk-build-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0", - "@aws-cdk/cloudformation-diff": "^0.21.0", - "@aws-cdk/cx-api": "^0.21.0", + "@aws-cdk/cdk": "^0.22.0", + "@aws-cdk/cloudformation-diff": "^0.22.0", + "@aws-cdk/cx-api": "^0.22.0", "source-map-support": "^0.5.6" }, "repository": { diff --git a/packages/@aws-cdk/assets-docker/lib/adopted-repository.ts b/packages/@aws-cdk/assets-docker/lib/adopted-repository.ts index e8948eb436c0a..c8f15a7fc8dbe 100644 --- a/packages/@aws-cdk/assets-docker/lib/adopted-repository.ts +++ b/packages/@aws-cdk/assets-docker/lib/adopted-repository.ts @@ -42,7 +42,7 @@ export class AdoptedRepository extends ecr.RepositoryBase { }); fn.addToRolePolicy(new iam.PolicyStatement() - .addResource(ecr.Repository.arnForLocalRepository(props.repositoryName)) + .addResource(ecr.Repository.arnForLocalRepository(props.repositoryName, this)) .addActions( 'ecr:GetRepositoryPolicy', 'ecr:SetRepositoryPolicy', @@ -67,7 +67,7 @@ export class AdoptedRepository extends ecr.RepositoryBase { // this this repository is "local" to the stack (in the same region/account) // we can render it's ARN from it's name. - this.repositoryArn = ecr.Repository.arnForLocalRepository(this.repositoryName); + this.repositoryArn = ecr.Repository.arnForLocalRepository(this.repositoryName, this); } /** diff --git a/packages/@aws-cdk/assets-docker/package.json b/packages/@aws-cdk/assets-docker/package.json index 6fad14ead16d4..f44ca1d43c220 100644 --- a/packages/@aws-cdk/assets-docker/package.json +++ b/packages/@aws-cdk/assets-docker/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/assets-docker", - "version": "0.21.0", + "version": "0.22.0", "description": "Docker image assets", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -51,31 +51,31 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", + "@aws-cdk/assert": "^0.22.0", "@types/proxyquire": "^1.3.28", - "aws-cdk": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "pkglint": "^0.21.0", + "aws-cdk": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "pkglint": "^0.22.0", "proxyquire": "^2.1.0" }, "dependencies": { - "@aws-cdk/aws-cloudformation": "^0.21.0", - "@aws-cdk/aws-ecr": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-lambda": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0", - "@aws-cdk/cx-api": "^0.21.0" + "@aws-cdk/aws-cloudformation": "^0.22.0", + "@aws-cdk/aws-ecr": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-lambda": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0", + "@aws-cdk/cx-api": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-ecr": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-ecr": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/assets/package.json b/packages/@aws-cdk/assets/package.json index 72a8af48cbb64..80e5eb76a952f 100644 --- a/packages/@aws-cdk/assets/package.json +++ b/packages/@aws-cdk/assets/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/assets", - "version": "0.21.0", + "version": "0.22.0", "description": "Integration of CDK apps with local assets", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -50,25 +50,25 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "aws-cdk": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "aws-cdk": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0", - "@aws-cdk/cx-api": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0", + "@aws-cdk/cx-api": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-amazonmq/package.json b/packages/@aws-cdk/aws-amazonmq/package.json index aab237179c15b..53a4946e08553 100644 --- a/packages/@aws-cdk/aws-amazonmq/package.json +++ b/packages/@aws-cdk/aws-amazonmq/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-amazonmq", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::AmazonMQ", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -36,7 +36,8 @@ "pkglint": "pkglint -f", "test": "cdk-test", "watch": "cdk-watch", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::AmazonMQ" @@ -54,18 +55,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/lib/deployment.ts b/packages/@aws-cdk/aws-apigateway/lib/deployment.ts index 2e7b45c73533c..4aef0dd03306a 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/deployment.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/deployment.ts @@ -107,6 +107,7 @@ class LatestDeploymentResource extends CfnDeployment { private originalLogicalId?: string; private lazyLogicalIdRequired: boolean; private lazyLogicalId?: string; + private logicalIdToken: cdk.Token; private hashComponents = new Array(); constructor(scope: cdk.Construct, id: string, props: CfnDeploymentProps) { @@ -114,6 +115,8 @@ class LatestDeploymentResource extends CfnDeployment { // from this point, don't allow accessing logical ID before synthesis this.lazyLogicalIdRequired = true; + + this.logicalIdToken = new cdk.Token(() => this.lazyLogicalId); } /** @@ -124,11 +127,7 @@ class LatestDeploymentResource extends CfnDeployment { return this.originalLogicalId!; } - if (!this.lazyLogicalId) { - throw new Error('This resource has a lazy logical ID which is calculated just before synthesis. Use a cdk.Token to evaluate'); - } - - return this.lazyLogicalId; + return this.logicalIdToken.toString(); } /** @@ -170,7 +169,7 @@ class LatestDeploymentResource extends CfnDeployment { * Hooks into synthesis to calculate a logical ID that hashes all the components * add via `addToLogicalId`. */ - public validate() { + protected prepare() { // if hash components were added to the deployment, we use them to calculate // a logical ID for the deployment resource. if (this.hashComponents.length === 0) { @@ -178,12 +177,10 @@ class LatestDeploymentResource extends CfnDeployment { } else { const md5 = crypto.createHash('md5'); this.hashComponents - .map(c => cdk.resolve(c)) + .map(c => this.node.resolve(c)) .forEach(c => md5.update(JSON.stringify(c))); this.lazyLogicalId = this.originalLogicalId + md5.digest("hex"); } - - return []; } } diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts index 70d13b641c2f0..341bbc88b40fb 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts @@ -1,5 +1,6 @@ import cdk = require('@aws-cdk/cdk'); import { Integration, IntegrationOptions, IntegrationType } from '../integration'; +import { Method } from '../method'; import { parseAwsApiCall } from '../util'; export interface AwsIntegrationProps { @@ -61,6 +62,8 @@ export interface AwsIntegrationProps { * technology. */ export class AwsIntegration extends Integration { + private scope?: cdk.IConstruct; + constructor(props: AwsIntegrationProps) { const backend = props.subdomain ? `${props.subdomain}.${props.service}` : props.service; const type = props.proxy ? IntegrationType.AwsProxy : IntegrationType.Aws; @@ -68,14 +71,21 @@ export class AwsIntegration extends Integration { super({ type, integrationHttpMethod: 'POST', - uri: cdk.ArnUtils.fromComponents({ - service: 'apigateway', - account: backend, - resource: apiType, - sep: '/', - resourceName: apiValue, + uri: new cdk.Token(() => { + if (!this.scope) { throw new Error('AwsIntegration must be used in API'); } + return cdk.Stack.find(this.scope).formatArn({ + service: 'apigateway', + account: backend, + resource: apiType, + sep: '/', + resourceName: apiValue, + }); }), options: props.options, }); } + + public bind(method: Method) { + this.scope = method; + } } diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts index 1970b71923455..10ba40547523d 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts @@ -52,6 +52,7 @@ export class LambdaIntegration extends AwsIntegration { } public bind(method: Method) { + super.bind(method); const principal = new iam.ServicePrincipal('apigateway.amazonaws.com'); const desc = `${method.httpMethod}.${method.resource.resourcePath.replace(/\//g, '.')}`; diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index 08dc1841ee4ff..3822c56ee85e3 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -158,7 +158,8 @@ export class Method extends cdk.Construct { credentials = options.credentialsRole.roleArn; } else if (options.credentialsPassthrough) { // arn:aws:iam::*:user/* - credentials = cdk.ArnUtils.fromComponents({ service: 'iam', region: '', account: '*', resource: 'user', sep: '/', resourceName: '*' }); + // tslint:disable-next-line:max-line-length + credentials = cdk.Stack.find(this).formatArn({ service: 'iam', region: '', account: '*', resource: 'user', sep: '/', resourceName: '*' }); } return { diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index b4cdda1805f1a..ea6ed72213356 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -301,7 +301,7 @@ export class RestApi extends cdk.Construct implements cdk.IDependable, IRestApi method = '*'; } - return cdk.ArnUtils.fromComponents({ + return cdk.Stack.find(this).formatArn({ service: 'execute-api', resource: this.restApiId, sep: '/', @@ -309,10 +309,18 @@ export class RestApi extends cdk.Construct implements cdk.IDependable, IRestApi }); } + /** + * Internal API used by `Method` to keep an inventory of methods at the API + * level for validation purposes. + */ + public _attachMethod(method: Method) { + this.methods.push(method); + } + /** * Performs validation of the REST API. */ - public validate() { + protected validate() { if (this.methods.length === 0) { return [ `The REST API doesn't contain any methods` ]; } @@ -320,14 +328,6 @@ export class RestApi extends cdk.Construct implements cdk.IDependable, IRestApi return []; } - /** - * Internal API used by `Method` to keep an inventory of methods at the API - * level for validation purposes. - */ - public _attachMethod(method: Method) { - this.methods.push(method); - } - private configureDeployment(props: RestApiProps) { const deploy = props.deploy === undefined ? true : props.deploy; if (deploy) { @@ -358,7 +358,7 @@ export class RestApi extends cdk.Construct implements cdk.IDependable, IRestApi private configureCloudWatchRole(apiResource: CfnRestApi) { const role = new iam.Role(this, 'CloudWatchRole', { assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), - managedPolicyArns: [ cdk.ArnUtils.fromComponents({ + managedPolicyArns: [ cdk.Stack.find(this).formatArn({ service: 'iam', region: '', account: 'aws', @@ -405,8 +405,6 @@ export enum EndpointType { Private = 'PRIVATE' } -export class RestApiUrl extends cdk.CloudFormationToken { } - class ImportedRestApi extends cdk.Construct implements IRestApi { public restApiId: string; diff --git a/packages/@aws-cdk/aws-apigateway/lib/stage.ts b/packages/@aws-cdk/aws-apigateway/lib/stage.ts index 4ed4ab4dab2e4..45bad2898c1eb 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/stage.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/stage.ts @@ -1,4 +1,5 @@ import cdk = require('@aws-cdk/cdk'); +import { Stack } from '@aws-cdk/cdk'; import { CfnStage } from './apigateway.generated'; import { Deployment } from './deployment'; import { IRestApi } from './restapi'; @@ -13,6 +14,12 @@ export interface StageOptions extends MethodDeploymentOptions { */ stageName?: string; + /** + * Specifies whether Amazon X-Ray tracing is enabled for this method. + * @default false + */ + tracingEnabled?: boolean; + /** * Indicates whether cache clustering is enabled for the stage. */ @@ -157,6 +164,7 @@ export class Stage extends cdk.Construct implements cdk.IDependable { description: props.description, documentationVersion: props.documentationVersion, variables: props.variables, + tracingEnabled: props.tracingEnabled, methodSettings, }); @@ -173,7 +181,8 @@ export class Stage extends cdk.Construct implements cdk.IDependable { if (!path.startsWith('/')) { throw new Error(`Path must begin with "/": ${path}`); } - return `https://${this.restApi.restApiId}.execute-api.${new cdk.AwsRegion()}.amazonaws.com/${this.stageName}${path}`; + const stack = Stack.find(this); + return `https://${this.restApi.restApiId}.execute-api.${stack.region}.${stack.urlSuffix}/${this.stageName}${path}`; } private renderMethodSettings(props: StageProps): CfnStage.MethodSettingProperty[] | undefined { diff --git a/packages/@aws-cdk/aws-apigateway/package.json b/packages/@aws-cdk/aws-apigateway/package.json index e3492e31d6eb4..3d4cdaa5ee95d 100644 --- a/packages/@aws-cdk/aws-apigateway/package.json +++ b/packages/@aws-cdk/aws-apigateway/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-apigateway", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::ApiGateway", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ApiGateway" @@ -53,22 +54,22 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-lambda": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-lambda": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-lambda": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-lambda": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" @@ -78,4 +79,4 @@ "resource-attribute:@aws-cdk/aws-apigateway.IRestApi.restApiRootResourceId" ] } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json index e76050bfba5a0..acdcd09664d43 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json @@ -836,7 +836,9 @@ { "Ref": "AWS::Region" }, - ".amazonaws.com/", + ".", + { "Ref": "AWS::URLSuffix" }, + "/", { "Ref": "booksapiDeploymentStageprod55D8E03E" }, diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.defaults.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.defaults.expected.json index aac6799e40c09..888fa29a306be 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.defaults.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.defaults.expected.json @@ -109,7 +109,9 @@ { "Ref": "AWS::Region" }, - ".amazonaws.com/", + ".", + { "Ref": "AWS::URLSuffix" }, + "/", { "Ref": "myapiDeploymentStageprod298F01AF" }, diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json index 5d15ff5c7b253..3af25751a5649 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json @@ -601,7 +601,9 @@ { "Ref": "AWS::Region" }, - ".amazonaws.com/", + ".", + { "Ref": "AWS::URLSuffix" }, + "/", { "Ref": "myapiDeploymentStagebeta96434BEB" }, diff --git a/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts b/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts index f9b927af1bd2a..aaec885005146 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts @@ -152,7 +152,7 @@ export = { test.done(); function synthesize() { - stack.node.validateTree(); + stack.node.prepareTree(); return stack.toCloudFormation(); } }, diff --git a/packages/@aws-cdk/aws-apigateway/test/test.method.ts b/packages/@aws-cdk/aws-apigateway/test/test.method.ts index 2f2d94f59d55d..2a377a139f661 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.method.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.method.ts @@ -123,7 +123,7 @@ export = { }); // THEN - test.deepEqual(cdk.resolve(method.methodArn), { + test.deepEqual(method.node.resolve(method.methodArn), { "Fn::Join": [ "", [ @@ -157,7 +157,7 @@ export = { }); // THEN - test.deepEqual(cdk.resolve(method.testMethodArn), { + test.deepEqual(method.node.resolve(method.testMethodArn), { "Fn::Join": [ "", [ diff --git a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts index a0917ec673025..0810e8938c67b 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts @@ -125,7 +125,9 @@ export = { { Ref: "AWS::Region" }, - ".amazonaws.com/", + ".", + { Ref: "AWS::URLSuffix" }, + "/", { Ref: "myapiDeploymentStageprod298F01AF" }, @@ -386,13 +388,13 @@ export = { const exported = api.export(); // THEN - stack.node.validateTree(); + stack.node.prepareTree(); test.deepEqual(stack.toCloudFormation().Outputs.MyRestApiRestApiIdB93C5C2D, { Value: { Ref: 'MyRestApi2D1F47A9' }, Export: { Name: 'MyRestApiRestApiIdB93C5C2D' } }); - test.deepEqual(cdk.resolve(imported.restApiId), 'api-rxt4498f'); - test.deepEqual(cdk.resolve(exported), { restApiId: { 'Fn::ImportValue': 'MyRestApiRestApiIdB93C5C2D' } }); + test.deepEqual(imported.node.resolve(imported.restApiId), 'api-rxt4498f'); + test.deepEqual(imported.node.resolve(exported), { restApiId: { 'Fn::ImportValue': 'MyRestApiRestApiIdB93C5C2D' } }); test.done(); }, @@ -403,22 +405,26 @@ export = { api.root.addMethod('GET'); // THEN - test.deepEqual(cdk.resolve(api.url), { 'Fn::Join': + test.deepEqual(api.node.resolve(api.url), { 'Fn::Join': [ '', [ 'https://', { Ref: 'apiC8550315' }, '.execute-api.', { Ref: 'AWS::Region' }, - '.amazonaws.com/', + ".", + { Ref: "AWS::URLSuffix" }, + "/", { Ref: 'apiDeploymentStageprod896C8101' }, '/' ] ] }); - test.deepEqual(cdk.resolve(api.urlForPath('/foo/bar')), { 'Fn::Join': + test.deepEqual(api.node.resolve(api.urlForPath('/foo/bar')), { 'Fn::Join': [ '', [ 'https://', { Ref: 'apiC8550315' }, '.execute-api.', { Ref: 'AWS::Region' }, - '.amazonaws.com/', + ".", + { Ref: "AWS::URLSuffix" }, + "/", { Ref: 'apiDeploymentStageprod896C8101' }, '/foo/bar' ] ] }); test.done(); @@ -457,7 +463,7 @@ export = { const arn = api.executeApiArn('method', '/path', 'stage'); // THEN - test.deepEqual(cdk.resolve(arn), { 'Fn::Join': + test.deepEqual(api.node.resolve(arn), { 'Fn::Join': [ '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -490,7 +496,7 @@ export = { const method = api.root.addMethod('ANY'); // THEN - test.deepEqual(cdk.resolve(method.methodArn), { 'Fn::Join': + test.deepEqual(api.node.resolve(method.methodArn), { 'Fn::Join': [ '', [ 'arn:', { Ref: 'AWS::Partition' }, diff --git a/packages/@aws-cdk/aws-applicationautoscaling/package.json b/packages/@aws-cdk/aws-applicationautoscaling/package.json index 610eeef609f98..62062e9237b8c 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/package.json +++ b/packages/@aws-cdk/aws-applicationautoscaling/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-applicationautoscaling", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::ApplicationAutoScaling", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ApplicationAutoScaling" @@ -53,25 +54,25 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", "fast-check": "^1.7.0", - "pkglint": "^0.21.0" + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-autoscaling-common": "^0.21.0", - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-autoscaling-common": "^0.22.0", + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appstream/package.json b/packages/@aws-cdk/aws-appstream/package.json index 284f90a5c0b93..2c5e021a51746 100644 --- a/packages/@aws-cdk/aws-appstream/package.json +++ b/packages/@aws-cdk/aws-appstream/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-appstream", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::AppStream", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -36,7 +36,8 @@ "pkglint": "pkglint -f", "test": "cdk-test", "watch": "cdk-watch", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::AppStream" @@ -54,18 +55,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/package.json b/packages/@aws-cdk/aws-appsync/package.json index 8616917b69ee2..77a5c55d5bdc7 100644 --- a/packages/@aws-cdk/aws-appsync/package.json +++ b/packages/@aws-cdk/aws-appsync/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-appsync", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::AppSync", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::AppSync" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-athena/package.json b/packages/@aws-cdk/aws-athena/package.json index a098df2cd14b5..c5aef6b376ae9 100644 --- a/packages/@aws-cdk/aws-athena/package.json +++ b/packages/@aws-cdk/aws-athena/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-athena", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::Athena", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -38,7 +38,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "keywords": [ "aws", @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-api/package.json b/packages/@aws-cdk/aws-autoscaling-api/package.json index 5d0b8c19f571c..c0d9c6c877fe1 100644 --- a/packages/@aws-cdk/aws-autoscaling-api/package.json +++ b/packages/@aws-cdk/aws-autoscaling-api/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-autoscaling-api", - "version": "0.21.0", + "version": "0.22.0", "description": "API package for @aws-cdk/aws-autoscaling", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -60,21 +60,21 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ 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 9141b40826e2a..8f4d51ea6aba1 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/package.json +++ b/packages/@aws-cdk/aws-autoscaling-common/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-autoscaling-common", - "version": "0.21.0", + "version": "0.22.0", "description": "Common implementation package for @aws-cdk/aws-autoscaling and @aws-cdk/aws-applicationautoscaling", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -50,22 +50,22 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", "fast-check": "^1.7.0", - "pkglint": "^0.21.0" + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts index 19f50f187bfed..dbd85436c8000 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -222,7 +222,7 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup // use delayed evaluation const machineImage = props.machineImage.getImage(this); - const userDataToken = new cdk.Token(() => cdk.Fn.base64((machineImage.os.createUserData(this.userDataLines)))); + const userDataToken = new cdk.Token(() => cdk.Fn.base64((machineImage.os.createUserData(this.userDataLines)))).toString(); const securityGroupsToken = new cdk.Token(() => this.securityGroups.map(sg => sg.securityGroupId)); const launchConfig = new CfnLaunchConfiguration(this, 'LaunchConfig', { diff --git a/packages/@aws-cdk/aws-autoscaling/package.json b/packages/@aws-cdk/aws-autoscaling/package.json index 15d817bf5d798..b1370103e8380 100644 --- a/packages/@aws-cdk/aws-autoscaling/package.json +++ b/packages/@aws-cdk/aws-autoscaling/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-autoscaling", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::AutoScaling", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::AutoScaling" @@ -53,33 +54,33 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-autoscaling-api": "^0.21.0", - "@aws-cdk/aws-autoscaling-common": "^0.21.0", - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/aws-elasticloadbalancing": "^0.21.0", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-sns": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-autoscaling-api": "^0.22.0", + "@aws-cdk/aws-autoscaling-common": "^0.22.0", + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/aws-elasticloadbalancing": "^0.22.0", + "@aws-cdk/aws-elasticloadbalancingv2": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-sns": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-autoscaling-api": "^0.21.0", - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/aws-elasticloadbalancing": "^0.21.0", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-sns": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-autoscaling-api": "^0.22.0", + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/aws-elasticloadbalancing": "^0.22.0", + "@aws-cdk/aws-elasticloadbalancingv2": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-sns": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" @@ -91,4 +92,4 @@ "export:@aws-cdk/aws-autoscaling.IAutoScalingGroup" ] } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscalingplans/package.json b/packages/@aws-cdk/aws-autoscalingplans/package.json index ee7cc4713ee65..26138b97eb832 100644 --- a/packages/@aws-cdk/aws-autoscalingplans/package.json +++ b/packages/@aws-cdk/aws-autoscalingplans/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-autoscalingplans", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::AutoScalingPlans", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::AutoScalingPlans" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-batch/package.json b/packages/@aws-cdk/aws-batch/package.json index fce8f30ba8c47..365c62ce0809f 100644 --- a/packages/@aws-cdk/aws-batch/package.json +++ b/packages/@aws-cdk/aws-batch/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-batch", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::Batch", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Batch" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-budgets/package.json b/packages/@aws-cdk/aws-budgets/package.json index 5d959d0188452..a1428b4776f0f 100644 --- a/packages/@aws-cdk/aws-budgets/package.json +++ b/packages/@aws-cdk/aws-budgets/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-budgets", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::Budgets", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Budgets" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-certificatemanager/package.json b/packages/@aws-cdk/aws-certificatemanager/package.json index 6b1e6d57d051e..b95ca2b99caec 100644 --- a/packages/@aws-cdk/aws-certificatemanager/package.json +++ b/packages/@aws-cdk/aws-certificatemanager/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-certificatemanager", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::CertificateManager", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CertificateManager" @@ -53,20 +54,20 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloud9/package.json b/packages/@aws-cdk/aws-cloud9/package.json index d995030c736b0..570a0d128b405 100644 --- a/packages/@aws-cdk/aws-cloud9/package.json +++ b/packages/@aws-cdk/aws-cloud9/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloud9", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::Cloud9", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Cloud9" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts b/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts index f38df0a6c4d22..1c3ee7be6767f 100644 --- a/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts @@ -203,7 +203,7 @@ export abstract class PipelineCloudFormationDeployAction extends PipelineCloudFo // None evaluates to empty string which is falsey and results in undefined Capabilities: (capabilities && capabilities.toString()) || undefined, RoleArn: new cdk.Token(() => this.role.roleArn), - ParameterOverrides: cdk.CloudFormationJSON.stringify(props.parameterOverrides), + ParameterOverrides: new cdk.Token(() => this.node.stringifyJson(props.parameterOverrides)), TemplateConfiguration: props.templateConfiguration ? props.templateConfiguration.location : undefined, StackName: props.stackName, }); @@ -410,7 +410,7 @@ class SingletonPolicy extends cdk.Construct { this.statementFor({ actions: ['cloudformation:ExecuteChangeSet'], conditions: { StringEquals: { 'cloudformation:ChangeSetName': props.changeSetName } }, - }).addResource(stackArnFromProps(props)); + }).addResource(this.stackArnFromProps(props)); } public grantCreateReplaceChangeSet(props: { stackName: string, changeSetName: string, region?: string }): void { @@ -422,7 +422,7 @@ class SingletonPolicy extends cdk.Construct { 'cloudformation:DescribeStacks', ], conditions: { StringEqualsIfExists: { 'cloudformation:ChangeSetName': props.changeSetName } }, - }).addResource(stackArnFromProps(props)); + }).addResource(this.stackArnFromProps(props)); } public grantCreateUpdateStack(props: { stackName: string, replaceOnFailure?: boolean, region?: string }): void { @@ -438,7 +438,7 @@ class SingletonPolicy extends cdk.Construct { if (props.replaceOnFailure) { actions.push('cloudformation:DeleteStack'); } - this.statementFor({ actions }).addResource(stackArnFromProps(props)); + this.statementFor({ actions }).addResource(this.stackArnFromProps(props)); } public grantDeleteStack(props: { stackName: string, region?: string }): void { @@ -447,7 +447,7 @@ class SingletonPolicy extends cdk.Construct { 'cloudformation:DescribeStack*', 'cloudformation:DeleteStack', ] - }).addResource(stackArnFromProps(props)); + }).addResource(this.stackArnFromProps(props)); } public grantPassRole(role: iam.IRole): void { @@ -485,6 +485,15 @@ class SingletonPolicy extends cdk.Construct { } } } + + private stackArnFromProps(props: { stackName: string, region?: string }): string { + return cdk.Stack.find(this).formatArn({ + region: props.region, + service: 'cloudformation', + resource: 'stack', + resourceName: `${props.stackName}/*` + }); + } } interface StatementTemplate { @@ -492,13 +501,4 @@ interface StatementTemplate { conditions?: StatementCondition; } -type StatementCondition = { [op: string]: { [attribute: string]: string } }; - -function stackArnFromProps(props: { stackName: string, region?: string }): string { - return cdk.ArnUtils.fromComponents({ - region: props.region, - service: 'cloudformation', - resource: 'stack', - resourceName: `${props.stackName}/*` - }); -} +type StatementCondition = { [op: string]: { [attribute: string]: string } }; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/package.json b/packages/@aws-cdk/aws-cloudformation/package.json index 0c584f7137903..8cd41a7ca1360 100644 --- a/packages/@aws-cdk/aws-cloudformation/package.json +++ b/packages/@aws-cdk/aws-cloudformation/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloudformation", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS CloudFormation", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CloudFormation" @@ -57,29 +58,29 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "@aws-cdk/aws-events": "^0.21.0", + "@aws-cdk/assert": "^0.22.0", + "@aws-cdk/aws-events": "^0.22.0", "@types/lodash": "^4.14.118", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", "lodash": "^4.17.11", - "pkglint": "^0.21.0" + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-lambda": "^0.21.0", - "@aws-cdk/aws-sns": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-lambda": "^0.22.0", + "@aws-cdk/aws-sns": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-lambda": "^0.21.0", - "@aws-cdk/aws-sns": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-lambda": "^0.22.0", + "@aws-cdk/aws-sns": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" @@ -90,4 +91,4 @@ "construct-ctor:@aws-cdk/aws-cloudformation.PipelineCloudFormationDeployAction." ] } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts b/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts index 001799de59a13..2e512336c0ab5 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts @@ -11,7 +11,7 @@ export = nodeunit.testCase({ 'works'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const stage = new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) }); + const stage = new StageDouble({ pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }) }); const artifact = new cpapi.Artifact(stack as any, 'TestArtifact'); const action = new cloudformation.PipelineCreateReplaceChangeSetAction(stack, 'Action', { stage, @@ -23,7 +23,7 @@ export = nodeunit.testCase({ _assertPermissionGranted(test, pipelineRole.statements, 'iam:PassRole', action.role.roleArn); - const stackArn = _stackArn('MyStack'); + const stackArn = _stackArn('MyStack', stack); const changeSetCondition = { StringEqualsIfExists: { 'cloudformation:ChangeSetName': 'MyChangeSet' } }; _assertPermissionGranted(test, pipelineRole.statements, 'cloudformation:DescribeStacks', stackArn, changeSetCondition); _assertPermissionGranted(test, pipelineRole.statements, 'cloudformation:DescribeChangeSet', stackArn, changeSetCondition); @@ -45,7 +45,7 @@ export = nodeunit.testCase({ 'uses a single permission statement if the same ChangeSet name is used'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const stage = new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) }); + const stage = new StageDouble({ pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }) }); const artifact = new cpapi.Artifact(stack as any, 'TestArtifact'); new cloudformation.PipelineCreateReplaceChangeSetAction(stack, 'ActionA', { stage, @@ -64,7 +64,7 @@ export = nodeunit.testCase({ }); test.deepEqual( - cdk.resolve(pipelineRole.statements), + stack.node.resolve(pipelineRole.statements), [ { Action: 'iam:PassRole', @@ -101,14 +101,14 @@ export = nodeunit.testCase({ 'works'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const stage = new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) }); + const stage = new StageDouble({ pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }) }); new cloudformation.PipelineExecuteChangeSetAction(stack, 'Action', { stage, changeSetName: 'MyChangeSet', stackName: 'MyStack', }); - const stackArn = _stackArn('MyStack'); + const stackArn = _stackArn('MyStack', stack); _assertPermissionGranted(test, pipelineRole.statements, 'cloudformation:ExecuteChangeSet', stackArn, { StringEquals: { 'cloudformation:ChangeSetName': 'MyChangeSet' } }); @@ -124,7 +124,7 @@ export = nodeunit.testCase({ 'uses a single permission statement if the same ChangeSet name is used'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const stage = new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) }); + const stage = new StageDouble({ pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }) }); new cloudformation.PipelineExecuteChangeSetAction(stack, 'ActionA', { stage, changeSetName: 'MyChangeSet', @@ -138,7 +138,7 @@ export = nodeunit.testCase({ }); test.deepEqual( - cdk.resolve(pipelineRole.statements), + stack.node.resolve(pipelineRole.statements), [ { Action: 'cloudformation:ExecuteChangeSet', @@ -162,13 +162,13 @@ export = nodeunit.testCase({ const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); const action = new cloudformation.PipelineCreateUpdateStackAction(stack, 'Action', { - stage: new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) }), + stage: new StageDouble({ pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }) }), templatePath: new cpapi.Artifact(stack as any, 'TestArtifact').atPath('some/file'), stackName: 'MyStack', adminPermissions: false, replaceOnFailure: true, }); - const stackArn = _stackArn('MyStack'); + const stackArn = _stackArn('MyStack', stack); _assertPermissionGranted(test, pipelineRole.statements, 'cloudformation:DescribeStack*', stackArn); _assertPermissionGranted(test, pipelineRole.statements, 'cloudformation:CreateStack', stackArn); @@ -184,11 +184,11 @@ export = nodeunit.testCase({ const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); const action = new cloudformation.PipelineDeleteStackAction(stack, 'Action', { - stage: new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) }), + stage: new StageDouble({ pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }) }), adminPermissions: false, stackName: 'MyStack', }); - const stackArn = _stackArn('MyStack'); + const stackArn = _stackArn('MyStack', stack); _assertPermissionGranted(test, pipelineRole.statements, 'cloudformation:DescribeStack*', stackArn); _assertPermissionGranted(test, pipelineRole.statements, 'cloudformation:DeleteStack', stackArn); @@ -213,10 +213,10 @@ function _assertActionMatches(test: nodeunit.Test, category: string, configuration?: { [key: string]: any }) { const configurationStr = configuration - ? `configuration including ${JSON.stringify(cdk.resolve(configuration), null, 2)}` + ? `configuration including ${JSON.stringify(resolve(configuration), null, 2)}` : ''; const actionsStr = JSON.stringify(actions.map(a => - ({ owner: a.owner, provider: a.provider, category: a.category, configuration: cdk.resolve(a.configuration) }) + ({ owner: a.owner, provider: a.provider, category: a.category, configuration: resolve(a.configuration) }) ), null, 2); test.ok(_hasAction(actions, owner, provider, category, configuration), `Expected to find an action with owner ${owner}, provider ${provider}, category ${category}${configurationStr}, but found ${actionsStr}`); @@ -230,7 +230,7 @@ function _hasAction(actions: cpapi.Action[], owner: string, provider: string, ca if (configuration && !action.configuration) { continue; } if (configuration) { for (const key of Object.keys(configuration)) { - if (!_.isEqual(cdk.resolve(action.configuration[key]), cdk.resolve(configuration[key]))) { + if (!_.isEqual(resolve(action.configuration[key]), resolve(configuration[key]))) { continue; } } @@ -242,12 +242,12 @@ function _hasAction(actions: cpapi.Action[], owner: string, provider: string, ca function _assertPermissionGranted(test: nodeunit.Test, statements: iam.PolicyStatement[], action: string, resource: string, conditions?: any) { const conditionStr = conditions - ? ` with condition(s) ${JSON.stringify(cdk.resolve(conditions))}` + ? ` with condition(s) ${JSON.stringify(resolve(conditions))}` : ''; - const resolvedStatements = cdk.resolve(statements); + const resolvedStatements = resolve(statements); const statementsStr = JSON.stringify(resolvedStatements, null, 2); test.ok(_grantsPermission(resolvedStatements, action, resource, conditions), - `Expected to find a statement granting ${action} on ${JSON.stringify(cdk.resolve(resource))}${conditionStr}, found:\n${statementsStr}`); + `Expected to find a statement granting ${action} on ${JSON.stringify(resolve(resource))}${conditionStr}, found:\n${statementsStr}`); } function _grantsPermission(statements: PolicyStatementJson[], action: string, resource: string, conditions?: any) { @@ -261,8 +261,8 @@ function _grantsPermission(statements: PolicyStatementJson[], action: string, re } function _isOrContains(entity: string | string[], value: string): boolean { - const resolvedValue = cdk.resolve(value); - const resolvedEntity = cdk.resolve(entity); + const resolvedValue = resolve(value); + const resolvedEntity = resolve(entity); if (_.isEqual(resolvedEntity, resolvedValue)) { return true; } if (!Array.isArray(resolvedEntity)) { return false; } for (const tested of entity) { @@ -271,26 +271,23 @@ function _isOrContains(entity: string | string[], value: string): boolean { return false; } -function _stackArn(stackName: string): string { - return cdk.ArnUtils.fromComponents({ +function _stackArn(stackName: string, scope: cdk.IConstruct): string { + return cdk.Stack.find(scope).formatArn({ service: 'cloudformation', resource: 'stack', resourceName: `${stackName}/*`, }); } -class PipelineDouble implements cpapi.IPipeline { +class PipelineDouble extends cdk.Construct implements cpapi.IPipeline { public readonly pipelineName: string; public readonly pipelineArn: string; public readonly role: iam.Role; - public get node(): cdk.ConstructNode { - throw new Error('this is not a real construct'); - } - - constructor({ pipelineName, role }: { pipelineName?: string, role: iam.Role }) { + constructor(scope: cdk.Construct, id: string, { pipelineName, role }: { pipelineName?: string, role: iam.Role }) { + super(scope, id); this.pipelineName = pipelineName || 'TestPipeline'; - this.pipelineArn = cdk.ArnUtils.fromComponents({ service: 'codepipeline', resource: 'pipeline', resourceName: this.pipelineName }); + this.pipelineArn = cdk.Stack.find(this).formatArn({ service: 'codepipeline', resource: 'pipeline', resourceName: this.pipelineName }); this.role = role; } @@ -352,3 +349,7 @@ class RoleDouble extends iam.Role { this.statements.push(statement); } } + +function resolve(x: any): any { + return new cdk.Stack().node.resolve(x); +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index 26c0fdc8fcf48..2cd7ebb56e858 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloudfront", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS CloudFront", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CloudFront" @@ -53,28 +54,28 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", + "@aws-cdk/assert": "^0.22.0", "aws-sdk": "^2.259.1", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-certificatemanager": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-kms": "^0.21.0", - "@aws-cdk/aws-route53": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-certificatemanager": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-kms": "^0.22.0", + "@aws-cdk/aws-route53": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-route53": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-route53": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudtrail/lib/index.ts b/packages/@aws-cdk/aws-cloudtrail/lib/index.ts index fc8f08883cd13..4b0da728f7e57 100644 --- a/packages/@aws-cdk/aws-cloudtrail/lib/index.ts +++ b/packages/@aws-cdk/aws-cloudtrail/lib/index.ts @@ -132,13 +132,15 @@ export class CloudTrail extends cdk.Construct { const s3bucket = new s3.Bucket(this, 'S3', {encryption: s3.BucketEncryption.Unencrypted}); const cloudTrailPrincipal = "cloudtrail.amazonaws.com"; + const stack = cdk.Stack.find(this); + s3bucket.addToResourcePolicy(new iam.PolicyStatement() .addResource(s3bucket.bucketArn) .addActions('s3:GetBucketAcl') .addServicePrincipal(cloudTrailPrincipal)); s3bucket.addToResourcePolicy(new iam.PolicyStatement() - .addResource(s3bucket.arnForObjects(`AWSLogs/${new cdk.AwsAccountId()}/*`)) + .addResource(s3bucket.arnForObjects(`AWSLogs/${stack.accountId}/*`)) .addActions("s3:PutObject") .addServicePrincipal(cloudTrailPrincipal) .setCondition("StringEquals", {'s3:x-amz-acl': "bucket-owner-full-control"})); diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index b8e075bbab1cb..2c90c17439283 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloudtrail", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS CloudTrail", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -34,7 +34,8 @@ "test": "cdk-test", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CloudTrail" @@ -52,26 +53,26 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", + "@aws-cdk/assert": "^0.22.0", "aws-sdk": "^2.259.1", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", "colors": "^1.2.1", - "pkglint": "^0.21.0" + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-kms": "^0.21.0", - "@aws-cdk/aws-logs": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-kms": "^0.22.0", + "@aws-cdk/aws-logs": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-kms": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-kms": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts b/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts index 59783aee5764f..ae6066ec43ff4 100644 --- a/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts +++ b/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts @@ -40,11 +40,7 @@ const ExpectedBucketPolicyProperties = { "Arn" ] }, - "/AWSLogs/", - { - Ref: "AWS::AccountId" - }, - "/*" + "/AWSLogs/123456789012/*", ] ] } diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts index 0f08dde461215..3974af3f5caf2 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts @@ -1,4 +1,4 @@ -import { CloudFormationJSON, Construct, Stack, Token } from "@aws-cdk/cdk"; +import { Construct, Stack, Token } from "@aws-cdk/cdk"; import { CfnDashboard } from './cloudwatch.generated'; import { Column, Row } from "./layout"; import { IWidget } from "./widget"; @@ -26,15 +26,15 @@ export class Dashboard extends Construct { // This is a bug in CloudFormation, but we don't want CDK users to have a bad // experience. We'll generate a name here if you did not supply one. // See: https://github.com/awslabs/aws-cdk/issues/213 - const dashboardName = (props && props.dashboardName) || new Token(() => this.generateDashboardName()); + const dashboardName = (props && props.dashboardName) || new Token(() => this.generateDashboardName()).toString(); this.dashboard = new CfnDashboard(this, 'Resource', { dashboardName, dashboardBody: new Token(() => { const column = new Column(...this.rows); column.position(0, 0); - return CloudFormationJSON.stringify({ widgets: column.toJson() }); - }) + return this.node.stringifyJson({ widgets: column.toJson() }); + }).toString() }); } diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts b/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts index 27dc9c4cb44b4..55b4808b25bd1 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts @@ -1,4 +1,4 @@ -import { AwsRegion } from "@aws-cdk/cdk"; +import cdk = require('@aws-cdk/cdk'); import { Alarm } from "./alarm"; import { Metric } from "./metric"; import { parseStatistic } from './util.statistic'; @@ -73,7 +73,7 @@ export class AlarmWidget extends ConcreteWidget { properties: { view: 'timeSeries', title: this.props.title, - region: this.props.region || new AwsRegion(), + region: this.props.region || new cdk.Aws().region, annotations: { alarms: [this.props.alarm.alarmArn] }, @@ -150,7 +150,7 @@ export class GraphWidget extends ConcreteWidget { properties: { view: 'timeSeries', title: this.props.title, - region: this.props.region || new AwsRegion(), + region: this.props.region || new cdk.Aws().region, metrics: (this.props.left || []).map(m => metricJson(m, 'left')).concat( (this.props.right || []).map(m => metricJson(m, 'right'))), annotations: { @@ -197,7 +197,7 @@ export class SingleValueWidget extends ConcreteWidget { properties: { view: 'singleValue', title: this.props.title, - region: this.props.region || new AwsRegion(), + region: this.props.region || new cdk.Aws().region, metrics: this.props.metrics.map(m => metricJson(m, 'left')) } }]; diff --git a/packages/@aws-cdk/aws-cloudwatch/package.json b/packages/@aws-cdk/aws-cloudwatch/package.json index c4ef501d64a47..969dacf8f307d 100644 --- a/packages/@aws-cdk/aws-cloudwatch/package.json +++ b/packages/@aws-cdk/aws-cloudwatch/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloudwatch", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS CloudWatch", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CloudWatch" @@ -53,22 +54,22 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts b/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts index 3b70ad7ca60df..8a3073ffd0750 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts @@ -1,10 +1,11 @@ -import { resolve, Stack } from '@aws-cdk/cdk'; +import { Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { AlarmWidget, GraphWidget, Metric, Shading, SingleValueWidget } from '../lib'; export = { 'add metrics to graphs on either axis'(test: Test) { // WHEN + const stack = new Stack(); const widget = new GraphWidget({ title: 'My fancy graph', left: [ @@ -16,7 +17,7 @@ export = { }); // THEN - test.deepEqual(resolve(widget.toJson()), [{ + test.deepEqual(stack.node.resolve(widget.toJson()), [{ type: 'metric', width: 6, height: 6, @@ -38,12 +39,13 @@ export = { 'label and color are respected in constructor'(test: Test) { // WHEN + const stack = new Stack(); const widget = new GraphWidget({ left: [new Metric({ namespace: 'CDK', metricName: 'Test', label: 'MyMetric', color: '000000' }) ], }); // THEN - test.deepEqual(resolve(widget.toJson()), [{ + test.deepEqual(stack.node.resolve(widget.toJson()), [{ type: 'metric', width: 6, height: 6, @@ -63,6 +65,7 @@ export = { 'singlevalue widget'(test: Test) { // GIVEN + const stack = new Stack(); const metric = new Metric({ namespace: 'CDK', metricName: 'Test' }); // WHEN @@ -71,7 +74,7 @@ export = { }); // THEN - test.deepEqual(resolve(widget.toJson()), [{ + test.deepEqual(stack.node.resolve(widget.toJson()), [{ type: 'metric', width: 6, height: 3, @@ -102,7 +105,7 @@ export = { }); // THEN - test.deepEqual(resolve(widget.toJson()), [{ + test.deepEqual(stack.node.resolve(widget.toJson()), [{ type: 'metric', width: 6, height: 6, @@ -121,6 +124,7 @@ export = { 'add annotations to graph'(test: Test) { // WHEN + const stack = new Stack(); const widget = new GraphWidget({ title: 'My fancy graph', left: [ @@ -135,7 +139,7 @@ export = { }); // THEN - test.deepEqual(resolve(widget.toJson()), [{ + test.deepEqual(stack.node.resolve(widget.toJson()), [{ type: 'metric', width: 6, height: 6, @@ -178,7 +182,7 @@ export = { }); // THEN - test.deepEqual(resolve(widget.toJson()), [{ + test.deepEqual(stack.node.resolve(widget.toJson()), [{ type: 'metric', width: 6, height: 6, diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 42defa04578b7..2ea98fa5e0c15 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -436,7 +436,7 @@ class ImportedProject extends ProjectBase { constructor(scope: cdk.Construct, id: string, private readonly props: ProjectImportProps) { super(scope, id); - this.projectArn = cdk.ArnUtils.fromComponents({ + this.projectArn = cdk.Stack.find(this).formatArn({ service: 'codebuild', resource: 'project', resourceName: props.projectName, @@ -705,24 +705,6 @@ export class Project extends ProjectBase { this.addToRolePolicy(this.createLoggingPermission()); } - /** - * @override - */ - public validate(): string[] { - const ret = new Array(); - if (this.source.type === SourceType.CodePipeline) { - if (this._secondarySources.length > 0) { - ret.push('A Project with a CodePipeline Source cannot have secondary sources. ' + - "Use the CodeBuild Pipeline Actions' `additionalInputArtifacts` property instead"); - } - if (this._secondaryArtifacts.length > 0) { - ret.push('A Project with a CodePipeline Source cannot have secondary artifacts. ' + - "Use the CodeBuild Pipeline Actions' `additionalOutputArtifactNames` property instead"); - } - } - return ret; - } - /** * Export this Project. Allows referencing this Project in a different CDK Stack. */ @@ -770,8 +752,26 @@ export class Project extends ProjectBase { this._secondaryArtifacts.push(secondaryArtifact); } + /** + * @override + */ + protected validate(): string[] { + const ret = new Array(); + if (this.source.type === SourceType.CodePipeline) { + if (this._secondarySources.length > 0) { + ret.push('A Project with a CodePipeline Source cannot have secondary sources. ' + + "Use the CodeBuild Pipeline Actions' `additionalInputArtifacts` property instead"); + } + if (this._secondaryArtifacts.length > 0) { + ret.push('A Project with a CodePipeline Source cannot have secondary artifacts. ' + + "Use the CodeBuild Pipeline Actions' `additionalOutputArtifactNames` property instead"); + } + } + return ret; + } + private createLoggingPermission() { - const logGroupArn = cdk.ArnUtils.fromComponents({ + const logGroupArn = cdk.Stack.find(this).formatArn({ service: 'logs', resource: 'log-group', sep: ':', diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index 356862d08abea..657ad766b7d78 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codebuild", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS CodeBuild", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CodeBuild" @@ -56,43 +57,43 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "@aws-cdk/aws-sns": "^0.21.0", - "@aws-cdk/aws-sqs": "^0.21.0", + "@aws-cdk/assert": "^0.22.0", + "@aws-cdk/aws-sns": "^0.22.0", + "@aws-cdk/aws-sqs": "^0.22.0", "aws-sdk": "^2.259.1", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/assets": "^0.21.0", - "@aws-cdk/assets-docker": "^0.21.0", - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-codecommit": "^0.21.0", - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-ecr": "^0.21.0", - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-kms": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/assets": "^0.22.0", + "@aws-cdk/assets-docker": "^0.22.0", + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-codecommit": "^0.22.0", + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-ecr": "^0.22.0", + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-kms": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/assets": "^0.21.0", - "@aws-cdk/assets-docker": "^0.21.0", - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-codecommit": "^0.21.0", - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-ecr": "^0.21.0", - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-kms": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/assets": "^0.22.0", + "@aws-cdk/assets-docker": "^0.22.0", + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-codecommit": "^0.22.0", + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-ecr": "^0.22.0", + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-kms": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts index 187b72cbcacb7..f4a58d9d7d40e 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts @@ -857,7 +857,7 @@ export = { environment: { environmentVariables: { FOO: { value: '1234' }, - BAR: { value: `111${new cdk.CloudFormationToken({ twotwotwo: '222' })}`, type: codebuild.BuildEnvironmentVariableType.ParameterStore } + BAR: { value: `111${new cdk.Token({ twotwotwo: '222' })}`, type: codebuild.BuildEnvironmentVariableType.ParameterStore } } }, environmentVariables: { diff --git a/packages/@aws-cdk/aws-codecommit/lib/repository.ts b/packages/@aws-cdk/aws-codecommit/lib/repository.ts index 086990af2bd25..80bb733212c59 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/repository.ts +++ b/packages/@aws-cdk/aws-codecommit/lib/repository.ts @@ -244,7 +244,7 @@ class ImportedRepository extends RepositoryBase { constructor(scope: cdk.Construct, id: string, private readonly props: RepositoryImportProps) { super(scope, id); - this.repositoryArn = cdk.ArnUtils.fromComponents({ + this.repositoryArn = cdk.Stack.find(this).formatArn({ service: 'codecommit', resource: props.repositoryName, }); @@ -264,7 +264,8 @@ class ImportedRepository extends RepositoryBase { } private repositoryCloneUrl(protocol: 'https' | 'ssh'): string { - return `${protocol}://git-codecommit.${new cdk.AwsRegion()}.${new cdk.AwsURLSuffix()}/v1/repos/${this.repositoryName}`; + const stack = cdk.Stack.find(this); + return `${protocol}://git-codecommit.${stack.region}.${stack.urlSuffix}/v1/repos/${this.repositoryName}`; } } diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index 98d1de35f1f78..875694c99bdff 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codecommit", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS CodeCommit", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CodeCommit" @@ -57,27 +58,27 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "@aws-cdk/aws-sns": "^0.21.0", + "@aws-cdk/assert": "^0.22.0", + "@aws-cdk/aws-sns": "^0.22.0", "aws-sdk": "^2.259.1", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codedeploy-api/package.json b/packages/@aws-cdk/aws-codedeploy-api/package.json index e02b8c3dced0d..900ae95113903 100644 --- a/packages/@aws-cdk/aws-codedeploy-api/package.json +++ b/packages/@aws-cdk/aws-codedeploy-api/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codedeploy-api", - "version": "0.21.0", + "version": "0.22.0", "description": "Load Balancer API for AWS CodeDeploy", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -50,13 +50,13 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "engines": { diff --git a/packages/@aws-cdk/aws-codedeploy/lib/application.ts b/packages/@aws-cdk/aws-codedeploy/lib/application.ts index 3c8bfeee3e806..e2aeb6ce4087b 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/application.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/application.ts @@ -41,7 +41,7 @@ class ImportedServerApplication extends cdk.Construct implements IServerApplicat super(scope, id); this.applicationName = props.applicationName; - this.applicationArn = applicationName2Arn(this.applicationName); + this.applicationArn = applicationNameToArn(this.applicationName, this); } public export() { @@ -90,7 +90,7 @@ export class ServerApplication extends cdk.Construct implements IServerApplicati }); this.applicationName = resource.ref; - this.applicationArn = applicationName2Arn(this.applicationName); + this.applicationArn = applicationNameToArn(this.applicationName, this); } public export(): ServerApplicationImportProps { @@ -100,8 +100,8 @@ export class ServerApplication extends cdk.Construct implements IServerApplicati } } -function applicationName2Arn(applicationName: string): string { - return cdk.ArnUtils.fromComponents({ +function applicationNameToArn(applicationName: string, scope: cdk.IConstruct): string { + return cdk.Stack.find(scope).formatArn({ service: 'codedeploy', resource: 'application', resourceName: applicationName, diff --git a/packages/@aws-cdk/aws-codedeploy/lib/deployment-config.ts b/packages/@aws-cdk/aws-codedeploy/lib/deployment-config.ts index 6783ecb4114f8..dfca704574f25 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/deployment-config.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/deployment-config.ts @@ -10,7 +10,7 @@ import { CfnDeploymentConfig } from './codedeploy.generated'; */ export interface IServerDeploymentConfig { readonly deploymentConfigName: string; - readonly deploymentConfigArn: string; + deploymentConfigArn(scope: cdk.IConstruct): string; export(): ServerDeploymentConfigImportProps; } @@ -30,13 +30,15 @@ export interface ServerDeploymentConfigImportProps { class ImportedServerDeploymentConfig extends cdk.Construct implements IServerDeploymentConfig { public readonly deploymentConfigName: string; - public readonly deploymentConfigArn: string; constructor(scope: cdk.Construct, id: string, private readonly props: ServerDeploymentConfigImportProps) { super(scope, id); this.deploymentConfigName = props.deploymentConfigName; - this.deploymentConfigArn = arnForDeploymentConfigName(this.deploymentConfigName); + } + + public deploymentConfigArn(scope: cdk.IConstruct): string { + return arnForDeploymentConfigName(this.deploymentConfigName, scope); } public export() { @@ -46,11 +48,13 @@ class ImportedServerDeploymentConfig extends cdk.Construct implements IServerDep class DefaultServerDeploymentConfig implements IServerDeploymentConfig { public readonly deploymentConfigName: string; - public readonly deploymentConfigArn: string; constructor(deploymentConfigName: string) { this.deploymentConfigName = deploymentConfigName; - this.deploymentConfigArn = arnForDeploymentConfigName(this.deploymentConfigName); + } + + public deploymentConfigArn(scope: cdk.IConstruct): string { + return arnForDeploymentConfigName(this.deploymentConfigName, scope); } public export(): ServerDeploymentConfigImportProps { @@ -110,7 +114,6 @@ export class ServerDeploymentConfig extends cdk.Construct implements IServerDepl } public readonly deploymentConfigName: string; - public readonly deploymentConfigArn: string; constructor(scope: cdk.Construct, id: string, props: ServerDeploymentConfigProps) { super(scope, id); @@ -121,7 +124,10 @@ export class ServerDeploymentConfig extends cdk.Construct implements IServerDepl }); this.deploymentConfigName = resource.ref.toString(); - this.deploymentConfigArn = arnForDeploymentConfigName(this.deploymentConfigName); + } + + public deploymentConfigArn(scope: cdk.IConstruct): string { + return arnForDeploymentConfigName(this.deploymentConfigName, scope); } public export(): ServerDeploymentConfigImportProps { @@ -150,8 +156,8 @@ export class ServerDeploymentConfig extends cdk.Construct implements IServerDepl } } -function arnForDeploymentConfigName(name: string): string { - return cdk.ArnUtils.fromComponents({ +function arnForDeploymentConfigName(name: string, scope: cdk.IConstruct): string { + return cdk.Stack.find(scope).formatArn({ service: 'codedeploy', resource: 'deploymentconfig', resourceName: name, diff --git a/packages/@aws-cdk/aws-codedeploy/lib/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/deployment-group.ts index e24c68cacaebb..083cb853653ad 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/deployment-group.ts @@ -104,8 +104,8 @@ class ImportedServerDeploymentGroup extends ServerDeploymentGroupBase { this.application = props.application; this.deploymentGroupName = props.deploymentGroupName; - this.deploymentGroupArn = deploymentGroupName2Arn(props.application.applicationName, - props.deploymentGroupName); + this.deploymentGroupArn = deploymentGroupNameToArn(props.application.applicationName, + props.deploymentGroupName, this); } public export() { @@ -310,9 +310,9 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { this._autoScalingGroups = props.autoScalingGroups || []; this.installAgent = props.installAgent === undefined ? true : props.installAgent; - const region = new cdk.AwsRegion().toString(); + const stack = cdk.Stack.find(this); this.codeDeployBucket = s3.Bucket.import(this, 'CodeDeployBucket', { - bucketName: `aws-codedeploy-${region}`, + bucketName: `aws-codedeploy-${stack.region}`, }); for (const asg of this._autoScalingGroups) { this.addCodeDeployAgentInstallUserData(asg); @@ -343,8 +343,8 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { }); this.deploymentGroupName = resource.deploymentGroupName; - this.deploymentGroupArn = deploymentGroupName2Arn(this.application.applicationName, - this.deploymentGroupName); + this.deploymentGroupArn = deploymentGroupNameToArn(this.application.applicationName, + this.deploymentGroupName, this); } public export(): ServerDeploymentGroupImportProps { @@ -387,7 +387,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { this.codeDeployBucket.grantRead(asg.role, 'latest/*'); - const region = (new cdk.AwsRegion()).toString(); + const stack = cdk.Stack.find(this); switch (asg.osType) { case ec2.OperatingSystemType.Linux: asg.addUserData( @@ -405,7 +405,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { '$PKG_CMD install -y awscli', 'TMP_DIR=`mktemp -d`', 'cd $TMP_DIR', - `aws s3 cp s3://aws-codedeploy-${region}/latest/install . --region ${region}`, + `aws s3 cp s3://aws-codedeploy-${stack.region}/latest/install . --region ${stack.region}`, 'chmod +x ./install', './install auto', 'rm -fr $TMP_DIR', @@ -414,7 +414,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { case ec2.OperatingSystemType.Windows: asg.addUserData( 'Set-Variable -Name TEMPDIR -Value (New-TemporaryFile).DirectoryName', - `aws s3 cp s3://aws-codedeploy-${region}/latest/codedeploy-agent.msi $TEMPDIR\\codedeploy-agent.msi`, + `aws s3 cp s3://aws-codedeploy-${stack.region}/latest/codedeploy-agent.msi $TEMPDIR\\codedeploy-agent.msi`, '$TEMPDIR\\codedeploy-agent.msi /quiet /l c:\\temp\\host-agent-install-log.txt', ); break; @@ -560,8 +560,8 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { } } -function deploymentGroupName2Arn(applicationName: string, deploymentGroupName: string): string { - return cdk.ArnUtils.fromComponents({ +function deploymentGroupNameToArn(applicationName: string, deploymentGroupName: string, scope: cdk.IConstruct): string { + return cdk.Stack.find(scope).formatArn({ service: 'codedeploy', resource: 'deploymentgroup', resourceName: `${applicationName}/${deploymentGroupName}`, diff --git a/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts b/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts index 8461eb85a6734..27bd3485d3ba0 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts @@ -60,7 +60,7 @@ export class PipelineDeployAction extends codepipeline.DeployAction { )); props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() - .addResource(props.deploymentGroup.deploymentConfig.deploymentConfigArn) + .addResource(props.deploymentGroup.deploymentConfig.deploymentConfigArn(this)) .addActions( 'codedeploy:GetDeploymentConfig', )); diff --git a/packages/@aws-cdk/aws-codedeploy/package.json b/packages/@aws-cdk/aws-codedeploy/package.json index f28057db5731b..a925017b2b722 100644 --- a/packages/@aws-cdk/aws-codedeploy/package.json +++ b/packages/@aws-cdk/aws-codedeploy/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codedeploy", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::CodeDeploy", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CodeDeploy" @@ -53,32 +54,32 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/aws-elasticloadbalancing": "^0.21.0", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/aws-elasticloadbalancing": "^0.22.0", + "@aws-cdk/aws-elasticloadbalancingv2": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-autoscaling": "^0.21.0", - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-codedeploy-api": "^0.21.0", - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-autoscaling": "^0.22.0", + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-codedeploy-api": "^0.22.0", + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-autoscaling": "^0.21.0", - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-codedeploy-api": "^0.21.0", - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-autoscaling": "^0.22.0", + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-codedeploy-api": "^0.22.0", + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" @@ -88,4 +89,4 @@ "construct-ctor:@aws-cdk/aws-codedeploy.ServerDeploymentGroupBase..params[2]" ] } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts index 81aa0cb18095b..0e1bbf2344590 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts @@ -239,14 +239,6 @@ export abstract class Action extends cdk.Construct { this.stage._internal._attachAction(this); } - public validate(): string[] { - return validation.validateArtifactBounds('input', this._actionInputArtifacts, this.artifactBounds.minInputs, - this.artifactBounds.maxInputs, this.category, this.provider) - .concat(validation.validateArtifactBounds('output', this._actionOutputArtifacts, this.artifactBounds.minOutputs, - this.artifactBounds.maxOutputs, this.category, this.provider) - ); - } - public onStateChange(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps) { const rule = new events.EventRule(this, name, options); rule.addTarget(target); @@ -270,6 +262,14 @@ export abstract class Action extends cdk.Construct { return this._actionOutputArtifacts.slice(); } + protected validate(): string[] { + return validation.validateArtifactBounds('input', this._actionInputArtifacts, this.artifactBounds.minInputs, + this.artifactBounds.maxInputs, this.category, this.provider) + .concat(validation.validateArtifactBounds('output', this._actionOutputArtifacts, this.artifactBounds.minOutputs, + this.artifactBounds.maxOutputs, this.category, this.provider) + ); + } + protected addOutputArtifact(name: string = this.stage._internal._generateOutputArtifactName(this)): Artifact { const artifact = new Artifact(this, name); this._actionOutputArtifacts.push(artifact); @@ -310,55 +310,3 @@ export abstract class Action extends cdk.Construct { // }); // } // } - -/* - TODO: A Jenkins build needs a corresponding custom action for each "Jenkins provider". - This should be created automatically. - - Example custom action created to execute Jenkins: - { - "id": { - "category": "Test", - "provider": "", - "owner": "Custom", - "version": "1" - }, - "outputArtifactDetails": { - "minimumCount": 0, - "maximumCount": 5 - }, - "settings": { - "executionUrlTemplate": "https://www.google.com/job/{Config:ProjectName}/{ExternalExecutionId}", - "entityUrlTemplate": "https://www.google.com/job/{Config:ProjectName}" - }, - "actionConfigurationProperties": [ - { - "queryable": true, - "key": true, - "name": "ProjectName", - "required": true, - "secret": false - } - ], - "inputArtifactDetails": { - "minimumCount": 0, - "maximumCount": 5 - } - } -*/ - -// export class JenkinsBuild extends BuildAction { -// constructor(scope: Stage, id: string, jenkinsProvider: string, project: string) { -// super(scope, id, jenkinsProvider, DefaultBounds(), { -// ProjectName: project -// }); -// } -// } - -// export class JenkinsTest extends TestAction { -// constructor(scope: Stage, id: string, jenkinsProvider: string, project: string) { -// super(scope, id, jenkinsProvider, DefaultBounds(), { -// ProjectName: project -// }); -// } -// } diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/artifact.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/artifact.ts index 4269b38b0fa6d..b236622d33f62 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/artifact.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/artifact.ts @@ -1,4 +1,4 @@ -import { CloudFormationToken, Construct } from "@aws-cdk/cdk"; +import { Construct, Token } from "@aws-cdk/cdk"; import { Action } from "./action"; /** @@ -72,9 +72,9 @@ export class ArtifactPath { } function artifactAttribute(artifact: Artifact, attributeName: string) { - return new CloudFormationToken(() => ({ 'Fn::GetArtifactAtt': [artifact.name, attributeName] })).toString(); + return new Token(() => ({ 'Fn::GetArtifactAtt': [artifact.name, attributeName] })).toString(); } function artifactGetParam(artifact: Artifact, jsonFile: string, keyName: string) { - return new CloudFormationToken(() => ({ 'Fn::GetParam': [artifact.name, jsonFile, keyName] })).toString(); + return new Token(() => ({ 'Fn::GetParam': [artifact.name, jsonFile, keyName] })).toString(); } diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/build-action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/build-action.ts index 8f3cc332d9b23..b3c3330aa4a05 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/build-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/build-action.ts @@ -22,12 +22,19 @@ export interface BuildActionProps extends CommonActionProps, CommonActionConstru artifactBounds: ActionArtifactBounds; /** - * The source action owner (could be 'AWS', 'ThirdParty' or 'Custom'). + * The build Action owner (could be 'AWS', 'ThirdParty' or 'Custom'). * * @default 'AWS' */ owner?: string; + /** + * The build Action version. + * + * @default '1' + */ + version?: string; + /** * The name of the build's output artifact. */ diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/source-action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/source-action.ts index 2b699c41e2050..b18984fefb47a 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/source-action.ts @@ -14,7 +14,7 @@ export interface SourceActionProps extends CommonActionProps, CommonActionConstr owner?: string; /** - * The source action verison. + * The source Action version. * * @default "1" */ diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/test-action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/test-action.ts index 9c9b91bdfa422..b77b74e71fb9e 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/test-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/test-action.ts @@ -36,12 +36,19 @@ export interface TestActionProps extends CommonActionProps, CommonActionConstruc artifactBounds: ActionArtifactBounds; /** - * The source action owner (could be 'AWS', 'ThirdParty' or 'Custom'). + * The test Action owner (could be 'AWS', 'ThirdParty' or 'Custom'). * * @default 'AWS' */ owner?: string; + /** + * The test Action version. + * + * @default '1' + */ + version?: string; + /** * The action's configuration. These are key-value pairs that specify input values for an action. * For more information, see the AWS CodePipeline User Guide. diff --git a/packages/@aws-cdk/aws-codepipeline-api/package.json b/packages/@aws-cdk/aws-codepipeline-api/package.json index 3998d2b5fd1b4..312ed5295345d 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/package.json +++ b/packages/@aws-cdk/aws-codepipeline-api/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codepipeline-api", - "version": "0.21.0", + "version": "0.22.0", "description": "Actions API for AWS Code Pipeline", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -53,21 +53,21 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" @@ -78,4 +78,4 @@ "construct-ctor:@aws-cdk/aws-codepipeline-api.Artifact..params[1]" ] } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline/README.md b/packages/@aws-cdk/aws-codepipeline/README.md index b86bad6267dff..8a1ae513e972e 100644 --- a/packages/@aws-cdk/aws-codepipeline/README.md +++ b/packages/@aws-cdk/aws-codepipeline/README.md @@ -95,6 +95,61 @@ but `notifyEmails` were, a new SNS Topic will be created (and accessible through the `notificationTopic` property of the Action). +#### Jenkins Actions + +In order to use Jenkins Actions in the Pipeline, +you first need to create a `JenkinsProvider`: + +```ts +const jenkinsProvider = new codepipeline.JenkinsProvider(this, 'JenkinsProvider', { + providerName: 'MyJenkinsProvider', + serverUrl: 'http://my-jenkins.com:8080', + version: '2', // optional, default: '1' +}); +``` + +If you've registered a Jenkins provider in a different CDK app, +or outside the CDK (in the CodePipeline AWS Console, for example), +you can import it: + +```ts +const jenkinsProvider = codepipeline.JenkinsProvider.import(this, 'JenkinsProvider', { + providerName: 'MyJenkinsProvider', + serverUrl: 'http://my-jenkins.com:8080', + version: '2', // optional, default: '1' +}); +``` + +Note that a Jenkins provider +(identified by the provider name-category(build/test)-version tuple) +must always be registered in the given account, in the given AWS region, +before it can be used in CodePipeline. + +With a `JenkinsProvider`, +we can create a Jenkins Action: + +```ts +const buildAction = new codepipeline.JenkinsBuildAction(this, 'JenkinsBuild', { + stage: buildStage, + jenkinsProvider: jenkinsProvider, + projectName: 'MyProject', +}); +// there's also a JenkinsTestAction that works identically +``` + +You can also add the Action to the Pipeline directly: + +```ts +// equivalent to the code above: +const buildAction = jenkinsProvider.addToPipeline(buildStage, 'JenkinsBuild', { + projectName: 'MyProject', +}); + +const testAction = jenkinsProvider.addToPipelineAsTest(buildStage, 'JenkinsTest', { + projectName: 'MyProject', +}); +``` + ### Cross-region CodePipelines You can also use the cross-region feature to deploy resources diff --git a/packages/@aws-cdk/aws-codepipeline/lib/custom-action-registration.ts b/packages/@aws-cdk/aws-codepipeline/lib/custom-action-registration.ts new file mode 100644 index 0000000000000..d1920ac6e7195 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/lib/custom-action-registration.ts @@ -0,0 +1,145 @@ +import cpapi = require('@aws-cdk/aws-codepipeline-api'); +import cdk = require('@aws-cdk/cdk'); +import { CfnCustomActionType } from './codepipeline.generated'; + +/** + * The creation attributes used for defining a configuration property + * of a custom Action. + */ +export interface CustomActionProperty { + /** + * The name of the property. + * You use this name in the `configuration` attribute when defining your custom Action class. + */ + name: string; + + /** + * The description of the property. + * + * @default the description will be empty + */ + description?: string; + + // because of @see URLs + // tslint:disable:max-line-length + + /** + * Whether this property is a key. + * + * @default false + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codepipeline-customactiontype-configurationproperties.html#cfn-codepipeline-customactiontype-configurationproperties-key + */ + key?: boolean; + + /** + * Whether this property is queryable. + * Note that only a single property of a custom Action can be queryable. + * + * @default false + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codepipeline-customactiontype-configurationproperties.html#cfn-codepipeline-customactiontype-configurationproperties-queryable + */ + queryable?: boolean; + + // tslint:enable:max-line-length + + /** + * Whether this property is required. + */ + required: boolean; + + /** + * Whether this property is secret, + * like a password, or access key. + * + * @default false + */ + secret?: boolean; + + /** + * The type of the property, + * like 'String', 'Number', or 'Boolean'. + * + * @default 'String' + */ + type?: string; +} + +/** + * Properties of registering a custom Action. + */ +export interface CustomActionRegistrationProps { + /** + * The category of the Action. + */ + category: cpapi.ActionCategory; + + /** + * The artifact bounds of the Action. + */ + artifactBounds: cpapi.ActionArtifactBounds; + + /** + * The provider of the Action. + */ + provider: string; + + /** + * The version of your Action. + * + * @default '1' + */ + version?: string; + + /** + * The URL shown for the entire Action in the Pipeline UI. + */ + entityUrl?: string; + + /** + * The URL shown for a particular execution of an Action in the Pipeline UI. + */ + executionUrl?: string; + + /** + * The properties used for customizing the instance of your Action. + * + * @default [] + */ + actionProperties?: CustomActionProperty[]; +} + +/** + * The resource representing registering a custom Action with CodePipeline. + * For the Action to be usable, it has to be registered for every region and every account it's used in. + * In addition to this class, you should most likely also provide your clients a class + * representing your custom Action, extending the Action class, + * and taking the `actionProperties` as properly typed, construction properties. + */ +export class CustomActionRegistration extends cdk.Construct { + constructor(parent: cdk.Construct, id: string, props: CustomActionRegistrationProps) { + super(parent, id); + + new CfnCustomActionType(this, 'Resource', { + category: props.category, + inputArtifactDetails: { + minimumCount: props.artifactBounds.minInputs, + maximumCount: props.artifactBounds.maxInputs, + }, + outputArtifactDetails: { + minimumCount: props.artifactBounds.minOutputs, + maximumCount: props.artifactBounds.maxOutputs, + }, + provider: props.provider, + version: props.version, + settings: { + entityUrlTemplate: props.entityUrl, + executionUrlTemplate: props.executionUrl, + }, + configurationProperties: props.actionProperties === undefined ? undefined : props.actionProperties.map((ap) => { return { + key: ap.key || false, + secret: ap.secret || false, + ...ap, + }; }), + }); + } +} diff --git a/packages/@aws-cdk/aws-codepipeline/lib/github-source-action.ts b/packages/@aws-cdk/aws-codepipeline/lib/github-source-action.ts index 652b13e6bbcfa..c70c2d048ddf7 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/github-source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/github-source-action.ts @@ -76,7 +76,7 @@ export class GitHubSourceAction extends actions.SourceAction { new CfnWebhook(this, 'WebhookResource', { authentication: 'GITHUB_HMAC', authenticationConfiguration: { - secretToken: props.oauthToken, + secretToken: props.oauthToken.toString(), }, filters: [ { diff --git a/packages/@aws-cdk/aws-codepipeline/lib/index.ts b/packages/@aws-cdk/aws-codepipeline/lib/index.ts index ca18a2780392d..f104e7d9cc54b 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/index.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/index.ts @@ -1,5 +1,7 @@ export * from './cross-region-scaffold-stack'; export * from './github-source-action'; +export * from './jenkins-actions'; +export * from './jenkins-provider'; export * from './manual-approval-action'; export * from './pipeline'; export * from './stage'; diff --git a/packages/@aws-cdk/aws-codepipeline/lib/jenkins-actions.ts b/packages/@aws-cdk/aws-codepipeline/lib/jenkins-actions.ts new file mode 100644 index 0000000000000..8de7c426739f3 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/lib/jenkins-actions.ts @@ -0,0 +1,120 @@ +import cpapi = require('@aws-cdk/aws-codepipeline-api'); +import cdk = require('@aws-cdk/cdk'); +import { IJenkinsProvider, jenkinsArtifactsBounds } from "./jenkins-provider"; + +/** + * Common construction properties of all Jenkins Pipeline Actions. + */ +export interface BasicJenkinsActionProps extends cpapi.CommonActionProps { + /** + * The name of the project (sometimes also called job, or task) + * on your Jenkins installation that will be invoked by this Action. + * + * @example 'MyJob' + */ + projectName: string; + + /** + * The source to use as input for this build. + * + * @default CodePipeline will use the output of the last Action from a previous Stage as input + */ + inputArtifact?: cpapi.Artifact; +} + +/** + * Common properties for creating {@link JenkinsBuildAction} - + * either directly, through its constructor, + * or through {@link JenkinsProvider#addToPipeline}. + */ +export interface BasicJenkinsBuildActionProps extends BasicJenkinsActionProps { + /** + * The name of the build's output artifact. + * + * @default an auto-generated name will be used + */ + outputArtifactName?: string; +} + +/** + * Construction properties of {@link JenkinsBuildAction}. + */ +export interface JenkinsBuildActionProps extends BasicJenkinsBuildActionProps, + cpapi.CommonActionConstructProps { + /** + * The Jenkins Provider for this Action. + */ + jenkinsProvider: IJenkinsProvider; +} + +/** + * Jenkins build CodePipeline Action. + * + * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/tutorials-four-stage-pipeline.html + */ +export class JenkinsBuildAction extends cpapi.BuildAction { + constructor(scope: cdk.Construct, id: string, props: JenkinsBuildActionProps) { + super(scope, id, { + provider: props.jenkinsProvider.providerName, + owner: 'Custom', + artifactBounds: jenkinsArtifactsBounds, + version: props.jenkinsProvider.version, + configuration: { + ProjectName: props.projectName, + }, + ...props, + }); + + props.jenkinsProvider._registerBuildProvider(); + } +} + +/** + * Common properties for creating {@link JenkinsTestAction} - + * either directly, through its constructor, + * or through {@link JenkinsProvider#addToPipelineAsTest}. + */ +export interface BasicJenkinsTestActionProps extends BasicJenkinsActionProps { + /** + * The optional name of the primary output artifact. + * If you provide a value here, + * then the `outputArtifact` property of your Action will be non-null. + * If you don't, `outputArtifact` will be `null`. + * + * @default the Action will not have an output artifact + */ + outputArtifactName?: string; +} + +/** + * Construction properties of {@link JenkinsTestAction}. + */ +export interface JenkinsTestActionProps extends BasicJenkinsTestActionProps, + cpapi.CommonActionConstructProps { + /** + * The Jenkins Provider for this Action. + */ + jenkinsProvider: IJenkinsProvider; +} + +/** + * Jenkins test CodePipeline Action. + * + * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/tutorials-four-stage-pipeline.html + */ +export class JenkinsTestAction extends cpapi.TestAction { + constructor(scope: cdk.Construct, id: string, props: JenkinsTestActionProps) { + super(scope, id, { + provider: props.jenkinsProvider.providerName, + owner: 'Custom', + artifactBounds: jenkinsArtifactsBounds, + version: props.jenkinsProvider.version, + configuration: { + ProjectName: props.projectName, + }, + ...props, + }); + + props.jenkinsProvider._registerTestProvider(); + } +} diff --git a/packages/@aws-cdk/aws-codepipeline/lib/jenkins-provider.ts b/packages/@aws-cdk/aws-codepipeline/lib/jenkins-provider.ts new file mode 100644 index 0000000000000..b6315e5b8b4e4 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/lib/jenkins-provider.ts @@ -0,0 +1,277 @@ +import cpapi = require('@aws-cdk/aws-codepipeline-api'); +import cdk = require('@aws-cdk/cdk'); +import { CustomActionRegistration } from "./custom-action-registration"; +import { + BasicJenkinsBuildActionProps, + BasicJenkinsTestActionProps, + JenkinsBuildAction, + JenkinsTestAction +} from "./jenkins-actions"; + +/** + * A Jenkins provider. + * + * If you want to create a new Jenkins provider managed alongside your CDK code, + * instantiate the {@link JenkinsProvider} class directly. + * + * If you want to reference an already registered provider, + * use the {@link JenkinsProvider#import} method. + */ +export interface IJenkinsProvider { + readonly providerName: string; + readonly serverUrl: string; + readonly version: string; + + /** + * Convenience method for creating a new {@link JenkinsBuildAction}, + * and adding it to the given Stage. + * + * @param stage the Pipeline Stage to add the new Action to + * @param name the name of the newly created Action + * @param props construction properties of the new Action + * @returns the newly created {@link JenkinsBuildAction} + */ + addToPipeline(stage: cpapi.IStage, name: string, props: BasicJenkinsBuildActionProps): + JenkinsBuildAction; + + /** + * Convenience method for creating a new {@link JenkinsTestAction}, + * and adding it to the given Stage. + * + * @param stage the Pipeline Stage to add the new Action to + * @param name the name of the newly created Action + * @param props construction properties of the new Action + * @returns the newly created {@link JenkinsTestAction} + */ + addToPipelineAsTest(stage: cpapi.IStage, name: string, props: BasicJenkinsTestActionProps): + JenkinsTestAction; + + /** + * Registers a Jenkins Provider for the build category. + * This method will be automatically called when creating + * a {@link JenkinsBuildAction}, + * so you should never need to call it explicitly. + */ + _registerBuildProvider(): void; + + /** + * Registers a Jenkins Provider for the test category. + * This method will be automatically called when creating + * a {@link JenkinsTestAction}, + * so you should never need to call it explicitly. + */ + _registerTestProvider(): void; +} + +/** + * Properties for importing an existing Jenkins provider. + */ +export interface JenkinsProviderImportProps { + /** + * The name of the Jenkins provider that you set in the AWS CodePipeline plugin configuration of your Jenkins project. + * + * @example 'MyJenkinsProvider' + */ + providerName: string; + + /** + * The base URL of your Jenkins server. + * + * @example 'http://myjenkins.com:8080' + */ + serverUrl: string; + + /** + * The version of your provider. + * + * @default '1' + */ + version?: string; +} + +export interface JenkinsProviderProps { + /** + * The name of the Jenkins provider that you set in the AWS CodePipeline plugin configuration of your Jenkins project. + * + * @example 'MyJenkinsProvider' + */ + providerName: string; + + /** + * The base URL of your Jenkins server. + * + * @example 'http://myjenkins.com:8080' + */ + serverUrl: string; + + /** + * The version of your provider. + * + * @default '1' + */ + version?: string; + + /** + * Whether to immediately register a Jenkins Provider for the build category. + * The Provider will always be registered if you create a {@link JenkinsBuildAction}. + */ + forBuild?: boolean; + + /** + * Whether to immediately register a Jenkins Provider for the test category. + * The Provider will always be registered if you create a {@link JenkinsTestAction}. + */ + forTest?: boolean; +} + +export abstract class BaseJenkinsProvider extends cdk.Construct implements IJenkinsProvider { + public abstract readonly providerName: string; + public abstract readonly serverUrl: string; + public readonly version: string; + + protected constructor(scope: cdk.Construct, id: string, version?: string) { + super(scope, id); + + this.version = version || '1'; + } + + public export(): JenkinsProviderImportProps { + return { + providerName: new cdk.Output(this, 'JenkinsProviderName', { + value: this.providerName, + }).makeImportValue().toString(), + serverUrl: new cdk.Output(this, 'JenkinsServerUrl', { + value: this.serverUrl, + }).makeImportValue().toString(), + version: new cdk.Output(this, 'JenkinsProviderVersion', { + value: this.version, + }).makeImportValue().toString(), + }; + } + + public addToPipeline(stage: cpapi.IStage, name: string, props: BasicJenkinsBuildActionProps): + JenkinsBuildAction { + return new JenkinsBuildAction(this, name, { + stage, + jenkinsProvider: this, + ...props, + }); + } + + public addToPipelineAsTest(stage: cpapi.IStage, name: string, props: BasicJenkinsTestActionProps): + JenkinsTestAction { + return new JenkinsTestAction(this, name, { + stage, + jenkinsProvider: this, + ...props, + }); + } + + public abstract _registerBuildProvider(): void; + public abstract _registerTestProvider(): void; +} + +/** + * A class representing Jenkins providers. + * + * @see #import + */ +export class JenkinsProvider extends BaseJenkinsProvider { + /** + * Import a Jenkins provider registered either outside the CDK, + * or in a different CDK Stack. + * + * @param scope the parent Construct for the new provider + * @param id the identifier of the new provider Construct + * @param props the properties used to identify the existing provider + * @returns a new Construct representing a reference to an existing Jenkins provider + */ + public static import(scope: cdk.Construct, id: string, props: JenkinsProviderImportProps): IJenkinsProvider { + return new ImportedJenkinsProvider(scope, id, props); + } + + public readonly providerName: string; + public readonly serverUrl: string; + private buildIncluded = false; + private testIncluded = false; + + constructor(scope: cdk.Construct, id: string, props: JenkinsProviderProps) { + super(scope, id, props.version); + + this.providerName = props.providerName; + this.serverUrl = props.serverUrl; + + if (props.forBuild === true) { + this._registerBuildProvider(); + } + if (props.forTest === true) { + this._registerTestProvider(); + } + } + + public _registerBuildProvider(): void { + if (this.buildIncluded) { + return; + } + this.buildIncluded = true; + this.registerJenkinsCustomAction('JenkinsBuildProviderResource', cpapi.ActionCategory.Build); + } + + public _registerTestProvider(): void { + if (this.testIncluded) { + return; + } + this.testIncluded = true; + this.registerJenkinsCustomAction('JenkinsTestProviderResource', cpapi.ActionCategory.Test); + } + + private registerJenkinsCustomAction(id: string, category: cpapi.ActionCategory) { + new CustomActionRegistration(this, id, { + category, + artifactBounds: jenkinsArtifactsBounds, + provider: this.providerName, + version: this.version, + entityUrl: appendToUrl(this.serverUrl, 'job/{Config:ProjectName}'), + executionUrl: appendToUrl(this.serverUrl, 'job/{Config:ProjectName}/{ExternalExecutionId}'), + actionProperties: [ + { + name: 'ProjectName', + required: true, + key: true, + queryable: true, + }, + ], + }); + } +} + +class ImportedJenkinsProvider extends BaseJenkinsProvider { + public readonly providerName: string; + public readonly serverUrl: string; + + constructor(scope: cdk.Construct, id: string, props: JenkinsProviderImportProps) { + super(scope, id, props.version); + + this.providerName = props.providerName; + this.serverUrl = props.serverUrl; + } + + public _registerBuildProvider(): void { + // do nothing + } + + public _registerTestProvider(): void { + // do nothing + } +} + +function appendToUrl(baseUrl: string, path: string): string { + return baseUrl.endsWith('/') ? baseUrl + path : `${baseUrl}/${path}`; +} + +export const jenkinsArtifactsBounds: cpapi.ActionArtifactBounds = { + minInputs: 0, + maxInputs: 5, + minOutputs: 0, + maxOutputs: 5 +}; diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index 81437ec3eeb0a..7c29de60f231a 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -127,7 +127,7 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { this.artifactStores = {}; // Does not expose a Fn::GetAtt for the ARN so we'll have to make it ourselves - this.pipelineArn = cdk.ArnUtils.fromComponents({ + this.pipelineArn = cdk.Stack.find(this).formatArn({ service: 'codepipeline', resource: this.pipelineName }); @@ -212,21 +212,6 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { return rule; } - /** - * Validate the pipeline structure - * - * Validation happens according to the rules documented at - * - * https://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html#pipeline-requirements - * @override - */ - public validate(): string[] { - return [ - ...this.validateHasStages(), - ...this.validateSourceActionLocations() - ]; - } - /** * Get the number of Stages in this Pipeline. */ @@ -254,6 +239,21 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { return ret; } + /** + * Validate the pipeline structure + * + * Validation happens according to the rules documented at + * + * https://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html#pipeline-requirements + * @override + */ + protected validate(): string[] { + return [ + ...this.validateHasStages(), + ...this.validateSourceActionLocations() + ]; + } + /** * Adds a Stage to this Pipeline. * This is an internal operation - diff --git a/packages/@aws-cdk/aws-codepipeline/lib/stage.ts b/packages/@aws-cdk/aws-codepipeline/lib/stage.ts index ee51f5c5fa33b..1a1eb46e6e4ea 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/stage.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/stage.ts @@ -105,10 +105,6 @@ export class Stage extends cdk.Construct implements cpapi.IStage, cpapi.IInterna return this._actions.slice(); } - public validate(): string[] { - return this.validateHasActions(); - } - public render(): CfnPipeline.StageDeclarationProperty { return { name: this.node.id, @@ -149,6 +145,10 @@ export class Stage extends cdk.Construct implements cpapi.IStage, cpapi.IInterna return (this.pipeline as any)._findInputArtifact(this, action); } + protected validate(): string[] { + return this.validateHasActions(); + } + private renderAction(action: cpapi.Action): CfnPipeline.ActionDeclarationProperty { return { name: action.node.id, diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index f0249b590a9a0..d13919c2900b5 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codepipeline", - "version": "0.21.0", + "version": "0.22.0", "description": "Better interface to AWS Code Pipeline", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CodePipeline" @@ -59,34 +60,34 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "@aws-cdk/aws-cloudformation": "^0.21.0", - "@aws-cdk/aws-codebuild": "^0.21.0", - "@aws-cdk/aws-codecommit": "^0.21.0", - "@aws-cdk/aws-codedeploy": "^0.21.0", - "@aws-cdk/aws-ecr": "^0.21.0", - "@aws-cdk/aws-lambda": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "@aws-cdk/aws-cloudformation": "^0.22.0", + "@aws-cdk/aws-codebuild": "^0.22.0", + "@aws-cdk/aws-codecommit": "^0.22.0", + "@aws-cdk/aws-codedeploy": "^0.22.0", + "@aws-cdk/aws-ecr": "^0.22.0", + "@aws-cdk/aws-lambda": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/aws-sns": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/aws-sns": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/aws-sns": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/aws-sns": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" @@ -97,4 +98,4 @@ "construct-ctor:@aws-cdk/aws-codepipeline.CrossRegionScaffoldStack..params[1]" ] } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.expected.json b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.expected.json index f2f25bf9fcd04..99f7e7886620d 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.expected.json +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.expected.json @@ -122,11 +122,7 @@ { "Ref": "AWS::Partition" }, - ":cloudformation:us-west-2:", - { - "Ref": "AWS::AccountId" - }, - ":stack/aws-cdk-codepipeline-cross-region-deploy-stack/*" + ":cloudformation:us-west-2:12345678:stack/aws-cdk-codepipeline-cross-region-deploy-stack/*" ] ] } diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.expected.json b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.expected.json new file mode 100644 index 0000000000000..56d66c4978305 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.expected.json @@ -0,0 +1,284 @@ +{ + "Resources": { + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "DeletionPolicy": "Retain", + "Properties": { + "VersioningConfiguration": { + "Status": "Enabled" + } + } + }, + "PipelineRoleD68726F7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codepipeline.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineRoleDefaultPolicyC7A05455": { + "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" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineRoleDefaultPolicyC7A05455", + "Roles": [ + { + "Ref": "PipelineRoleD68726F7" + } + ] + } + }, + "PipelineC660917D": { + "Type": "AWS::CodePipeline::Pipeline", + "Properties": { + "ArtifactStore": { + "Location": { + "Ref": "MyBucketF68F3FF0" + }, + "Type": "S3" + }, + "RoleArn": { + "Fn::GetAtt": [ + "PipelineRoleD68726F7", + "Arn" + ] + }, + "Stages": [ + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", + "Provider": "S3", + "Version": "1" + }, + "Configuration": { + "S3Bucket": { + "Ref": "MyBucketF68F3FF0" + }, + "S3ObjectKey": "some/path", + "PollForSourceChanges": true + }, + "InputArtifacts": [], + "Name": "S3", + "OutputArtifacts": [ + { + "Name": "Artifact_awscdkcodepipelinejenkinsMyBucketS32154568D" + } + ], + "RunOrder": 1 + } + ], + "Name": "Source" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Build", + "Owner": "Custom", + "Provider": "JenkinsProvider", + "Version": "2" + }, + "Configuration": { + "ProjectName": "JenkinsProject1" + }, + "InputArtifacts": [ + { + "Name": "Artifact_awscdkcodepipelinejenkinsMyBucketS32154568D" + } + ], + "Name": "JenkinsBuild", + "OutputArtifacts": [ + { + "Name": "Artifact_awscdkcodepipelinejenkinsJenkinsProviderJenkinsBuild6007E9FD" + } + ], + "RunOrder": 1 + }, + { + "ActionTypeId": { + "Category": "Test", + "Owner": "Custom", + "Provider": "JenkinsProvider", + "Version": "2" + }, + "Configuration": { + "ProjectName": "JenkinsProject2" + }, + "InputArtifacts": [ + { + "Name": "Artifact_awscdkcodepipelinejenkinsMyBucketS32154568D" + } + ], + "Name": "JenkinsTest", + "OutputArtifacts": [], + "RunOrder": 1 + }, + { + "ActionTypeId": { + "Category": "Test", + "Owner": "Custom", + "Provider": "JenkinsProvider", + "Version": "2" + }, + "Configuration": { + "ProjectName": "JenkinsProject3" + }, + "InputArtifacts": [ + { + "Name": "Artifact_awscdkcodepipelinejenkinsMyBucketS32154568D" + } + ], + "Name": "JenkinsTest2", + "OutputArtifacts": [], + "RunOrder": 1 + } + ], + "Name": "Build" + } + ] + }, + "DependsOn": [ + "PipelineRoleD68726F7", + "PipelineRoleDefaultPolicyC7A05455" + ] + }, + "JenkinsProviderJenkinsBuildProviderResourceD9231CAC": { + "Type": "AWS::CodePipeline::CustomActionType", + "Properties": { + "Category": "Build", + "InputArtifactDetails": { + "MaximumCount": 5, + "MinimumCount": 0 + }, + "OutputArtifactDetails": { + "MaximumCount": 5, + "MinimumCount": 0 + }, + "Provider": "JenkinsProvider", + "ConfigurationProperties": [ + { + "Key": true, + "Name": "ProjectName", + "Queryable": true, + "Required": true, + "Secret": false + } + ], + "Settings": { + "EntityUrlTemplate": "http://myjenkins.com:8080/job/{Config:ProjectName}", + "ExecutionUrlTemplate": "http://myjenkins.com:8080/job/{Config:ProjectName}/{ExternalExecutionId}" + }, + "Version": "2" + } + }, + "JenkinsProviderJenkinsTestProviderResourceF0CF8F0E": { + "Type": "AWS::CodePipeline::CustomActionType", + "Properties": { + "Category": "Test", + "InputArtifactDetails": { + "MaximumCount": 5, + "MinimumCount": 0 + }, + "OutputArtifactDetails": { + "MaximumCount": 5, + "MinimumCount": 0 + }, + "Provider": "JenkinsProvider", + "ConfigurationProperties": [ + { + "Key": true, + "Name": "ProjectName", + "Queryable": true, + "Required": true, + "Secret": false + } + ], + "Settings": { + "EntityUrlTemplate": "http://myjenkins.com:8080/job/{Config:ProjectName}", + "ExecutionUrlTemplate": "http://myjenkins.com:8080/job/{Config:ProjectName}/{ExternalExecutionId}" + }, + "Version": "2" + } + } + } +} diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.ts new file mode 100644 index 0000000000000..5a1c6c7847101 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.ts @@ -0,0 +1,39 @@ +import s3 = require('@aws-cdk/aws-s3'); +import cdk = require('@aws-cdk/cdk'); +import codepipeline = require('../lib'); + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-jenkins'); + +const bucket = new s3.Bucket(stack, 'MyBucket', { + versioned: true, +}); + +const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { + artifactBucket: bucket, +}); + +const sourceStage = pipeline.addStage('Source'); +bucket.addToPipeline(sourceStage, 'S3', { + bucketKey: 'some/path', +}); + +const jenkinsProvider = new codepipeline.JenkinsProvider(stack, 'JenkinsProvider', { + providerName: 'JenkinsProvider', + serverUrl: 'http://myjenkins.com:8080', + version: '2', +}); + +const buildStage = pipeline.addStage('Build'); +jenkinsProvider.addToPipeline(buildStage, 'JenkinsBuild', { + projectName: 'JenkinsProject1', +}); +jenkinsProvider.addToPipelineAsTest(buildStage, 'JenkinsTest', { + projectName: 'JenkinsProject2', +}); +jenkinsProvider.addToPipelineAsTest(buildStage, 'JenkinsTest2', { + projectName: 'JenkinsProject3', +}); + +app.run(); diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts b/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts index f10c842b09e7e..a152b39a76579 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts @@ -37,7 +37,7 @@ export = { 'should fail if Stage has no Actions'(test: Test) { const stage = stageForTesting(); - test.deepEqual(stage.validate().length, 1); + test.deepEqual(stage.node.validateTree().length, 1); test.done(); } @@ -48,7 +48,7 @@ export = { const stack = new cdk.Stack(); const pipeline = new Pipeline(stack, 'Pipeline'); - test.deepEqual(pipeline.validate().length, 1); + test.deepEqual(pipeline.node.validateTree().length, 1); test.done(); }, @@ -73,7 +73,7 @@ export = { bucketKey: 'key', }); - test.deepEqual(pipeline.validate().length, 1); + test.deepEqual(pipeline.node.validateTree().length, 1); test.done(); } diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts index 5a29a47764a2f..de1b69b01c5be 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts @@ -38,7 +38,7 @@ export = { }); test.notDeepEqual(stack.toCloudFormation(), {}); - test.deepEqual([], pipeline.validate()); + test.deepEqual([], pipeline.node.validateTree()); test.done(); }, @@ -127,7 +127,7 @@ export = { ] })); - test.deepEqual([], p.validate()); + test.deepEqual([], p.node.validateTree()); test.done(); }, @@ -211,7 +211,7 @@ export = { ] })); - test.deepEqual([], pipeline.validate()); + test.deepEqual([], pipeline.node.validateTree()); test.done(); }, diff --git a/packages/@aws-cdk/aws-cognito/package.json b/packages/@aws-cdk/aws-cognito/package.json index e285692352c86..de6592a5f5600 100644 --- a/packages/@aws-cdk/aws-cognito/package.json +++ b/packages/@aws-cdk/aws-cognito/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cognito", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::Cognito", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Cognito" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-config/package.json b/packages/@aws-cdk/aws-config/package.json index 69fee10b973ab..f75b102332131 100644 --- a/packages/@aws-cdk/aws-config/package.json +++ b/packages/@aws-cdk/aws-config/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-config", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::Config", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Config" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-datapipeline/package.json b/packages/@aws-cdk/aws-datapipeline/package.json index 90f0516eb8394..91c182c300c58 100644 --- a/packages/@aws-cdk/aws-datapipeline/package.json +++ b/packages/@aws-cdk/aws-datapipeline/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-datapipeline", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::DataPipeline", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::DataPipeline" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dax/package.json b/packages/@aws-cdk/aws-dax/package.json index f01c994a94040..f4a6bcfef7fb6 100644 --- a/packages/@aws-cdk/aws-dax/package.json +++ b/packages/@aws-cdk/aws-dax/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-dax", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::DAX", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::DAX" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-directoryservice/package.json b/packages/@aws-cdk/aws-directoryservice/package.json index 6937576ef8aba..033afbafe6ab3 100644 --- a/packages/@aws-cdk/aws-directoryservice/package.json +++ b/packages/@aws-cdk/aws-directoryservice/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-directoryservice", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::DirectoryService", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::DirectoryService" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dlm/package.json b/packages/@aws-cdk/aws-dlm/package.json index d453dc09e29d9..f56b5983b7b07 100644 --- a/packages/@aws-cdk/aws-dlm/package.json +++ b/packages/@aws-cdk/aws-dlm/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-dlm", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::DLM", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -36,7 +36,8 @@ "pkglint": "pkglint -f", "test": "cdk-test", "watch": "cdk-watch", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::DLM" @@ -54,18 +55,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dms/package.json b/packages/@aws-cdk/aws-dms/package.json index cb87037905226..cefb09c72ea77 100644 --- a/packages/@aws-cdk/aws-dms/package.json +++ b/packages/@aws-cdk/aws-dms/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-dms", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::DMS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::DMS" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index 7f3d8e1685344..cd8ebef7be464 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -474,7 +474,7 @@ export class Table extends Construct { * * @returns an array of validation error message */ - public validate(): string[] { + protected validate(): string[] { const errors = new Array(); if (!this.tablePartitionKey) { @@ -614,7 +614,7 @@ export class Table extends Construct { private makeScalingRole(): iam.IRole { // Use a Service Linked Role. return iam.Role.import(this, 'ScalingRole', { - roleArn: cdk.ArnUtils.fromComponents({ + roleArn: cdk.Stack.find(this).formatArn({ // https://docs.aws.amazon.com/autoscaling/application/userguide/application-auto-scaling-service-linked-roles.html service: 'iam', resource: 'role/aws-service-role/dynamodb.application-autoscaling.amazonaws.com', diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index 1398aeabfb9b1..9a449fedc49a1 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-dynamodb", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS DynamoDB", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::DynamoDB" @@ -53,24 +54,24 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-applicationautoscaling": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-applicationautoscaling": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-applicationautoscaling": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-applicationautoscaling": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts b/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts index b5e5ab4c32e4b..c19eaeaf6521b 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts @@ -1175,10 +1175,10 @@ export = { sortKey: LSI_SORT_KEY }); - const errors = table.validate(); + const errors = table.node.validateTree(); test.strictEqual(1, errors.length); - test.strictEqual('a sort key of the table must be specified to add local secondary indexes', errors[0]); + test.strictEqual('a sort key of the table must be specified to add local secondary indexes', errors[0].message); test.done(); }, diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index f92f0f6447aea..e7c696de6bd26 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -1,4 +1,4 @@ -import { Construct, IConstruct, IDependable } from "@aws-cdk/cdk"; +import { Construct, IConstruct, IDependable, Stack } from "@aws-cdk/cdk"; import { subnetName } from './util'; export interface IVpcSubnet extends IConstruct, IDependable { @@ -44,6 +44,11 @@ export interface IVpcNetwork extends IConstruct, IDependable { */ readonly availabilityZones: string[]; + /** + * Region where this VPC is located + */ + readonly vpcRegion: string; + /** * Take a dependency on internet connectivity having been added to this VPC * @@ -240,6 +245,14 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { public internetDependency(): IDependable { return new DependencyList(this.internetDependencies); } + + /** + * The region where this VPC is defined + */ + public get vpcRegion(): string { + return Stack.find(this).region; + } + } /** diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 6c25430b6cca5..875d22ba24483 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -5,6 +5,7 @@ import { NetworkBuilder } from './network-util'; import { DEFAULT_SUBNET_NAME, ExportSubnetGroup, ImportSubnetGroup, subnetId } from './util'; import { VpcNetworkProvider, VpcNetworkProviderProps } from './vpc-network-provider'; import { IVpcNetwork, IVpcSubnet, SubnetType, VpcNetworkBase, VpcNetworkImportProps, VpcPlacementStrategy, VpcSubnetImportProps } from './vpc-ref'; + /** * Name tag constant */ diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index ab5f3163e6a30..3032b95da497d 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-ec2", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS EC2", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::EC2" @@ -53,21 +54,21 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0", - "@aws-cdk/cx-api": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0", + "@aws-cdk/cx-api": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0", - "@aws-cdk/cx-api": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0", + "@aws-cdk/cx-api": "^0.22.0" }, "engines": { "node": ">= 8.10.0" @@ -77,4 +78,4 @@ "resource-attribute:@aws-cdk/aws-ec2.ISecurityGroup.securityGroupVpcId" ] } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index dcc3ecd5d3827..afa4c91f45177 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -1,5 +1,5 @@ import { countResources, expect, haveResource, haveResourceLike, isSuperObject } from '@aws-cdk/assert'; -import { AvailabilityZoneProvider, Construct, resolve, Stack, Tags } from '@aws-cdk/cdk'; +import { AvailabilityZoneProvider, Construct, Stack, Tags } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { DefaultInstanceTenancy, IVpcNetwork, SubnetType, VpcNetwork } from '../lib'; @@ -10,7 +10,7 @@ export = { "vpc.vpcId returns a token to the VPC ID"(test: Test) { const stack = getTestStack(); const vpc = new VpcNetwork(stack, 'TheVPC'); - test.deepEqual(resolve(vpc.vpcId), {Ref: 'TheVPC92636AB0' } ); + test.deepEqual(stack.node.resolve(vpc.vpcId), {Ref: 'TheVPC92636AB0' } ); test.done(); }, @@ -68,7 +68,7 @@ export = { const zones = new AvailabilityZoneProvider(stack).availabilityZones.length; test.equal(vpc.publicSubnets.length, zones); test.equal(vpc.privateSubnets.length, zones); - test.deepEqual(resolve(vpc.vpcId), { Ref: 'TheVPC92636AB0' }); + test.deepEqual(stack.node.resolve(vpc.vpcId), { Ref: 'TheVPC92636AB0' }); test.done(); }, @@ -442,7 +442,7 @@ export = { }); // THEN - test.deepEqual(resolve(vpc2.vpcId), { + test.deepEqual(vpc2.node.resolve(vpc2.vpcId), { 'Fn::ImportValue': 'TestStack:TheVPCVpcIdD346CDBA' }); @@ -461,7 +461,7 @@ export = { }); // THEN - test.deepEqual(resolve(imported.vpcId), { + test.deepEqual(imported.node.resolve(imported.vpcId), { 'Fn::ImportValue': 'TestStack:TheVPCVpcIdD346CDBA' }); diff --git a/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts b/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts index f85be5d0db917..ad7e8870f7ac1 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts @@ -122,8 +122,8 @@ export abstract class RepositoryBase extends cdk.Construct implements IRepositor * Returns an ECR ARN for a repository that resides in the same account/region * as the current stack. */ - public static arnForLocalRepository(repositoryName: string): string { - return cdk.ArnUtils.fromComponents({ + public static arnForLocalRepository(repositoryName: string, scope: cdk.IConstruct): string { + return cdk.Stack.find(scope).formatArn({ service: 'ecr', resource: 'repository', resourceName: repositoryName @@ -164,7 +164,7 @@ export abstract class RepositoryBase extends cdk.Construct implements IRepositor */ public repositoryUriForTag(tag?: string): string { const tagSuffix = tag ? `:${tag}` : ''; - const parts = cdk.ArnUtils.parse(this.repositoryArn); + const parts = cdk.Stack.find(this).parseArn(this.repositoryArn); return `${parts.account}.dkr.ecr.${parts.region}.amazonaws.com/${this.repositoryName}${tagSuffix}`; } @@ -265,7 +265,7 @@ class ImportedRepository extends RepositoryBase { 'which also implies that the repository resides in the same region/account as this stack'); } - this.repositoryArn = RepositoryBase.arnForLocalRepository(props.repositoryName); + this.repositoryArn = RepositoryBase.arnForLocalRepository(props.repositoryName, this); } if (props.repositoryName) { diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index 98ea1f4697752..c7346889b8177 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -126,7 +126,7 @@ export class Repository extends RepositoryBase { if (this.lifecycleRules.length === 0 && !this.registryId) { return undefined; } if (this.lifecycleRules.length > 0) { - lifecyclePolicyText = JSON.stringify(cdk.resolve({ + lifecyclePolicyText = JSON.stringify(this.node.resolve({ rules: this.orderedLifecycleRules().map(renderLifecycleRule), })); } diff --git a/packages/@aws-cdk/aws-ecr/package.json b/packages/@aws-cdk/aws-ecr/package.json index 66ecea755a6e6..3293a5b602b2e 100644 --- a/packages/@aws-cdk/aws-ecr/package.json +++ b/packages/@aws-cdk/aws-ecr/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-ecr", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::ECR", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ECR" @@ -53,24 +54,24 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" @@ -80,4 +81,4 @@ "import:@aws-cdk/aws-ecr.Repository" ] } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/test/test.repository.ts b/packages/@aws-cdk/aws-ecr/test/test.repository.ts index e3bfa4ffcc657..0869fbb59fe0e 100644 --- a/packages/@aws-cdk/aws-ecr/test/test.repository.ts +++ b/packages/@aws-cdk/aws-ecr/test/test.repository.ts @@ -137,7 +137,7 @@ export = { // THEN const arnSplit = { 'Fn::Split': [ ':', { 'Fn::GetAtt': [ 'Repo02AC86CF', 'Arn' ] } ] }; - test.deepEqual(cdk.resolve(uri), { 'Fn::Join': [ '', [ + test.deepEqual(repo.node.resolve(uri), { 'Fn::Join': [ '', [ { 'Fn::Select': [ 4, arnSplit ] }, '.dkr.ecr.', { 'Fn::Select': [ 3, arnSplit ] }, @@ -159,11 +159,11 @@ export = { const repo2 = ecr.Repository.import(stack2, 'Repo', repo1.export()); // THEN - test.deepEqual(cdk.resolve(repo2.repositoryArn), { + test.deepEqual(repo2.node.resolve(repo2.repositoryArn), { 'Fn::ImportValue': 'RepoRepositoryArn7F2901C9' }); - test.deepEqual(cdk.resolve(repo2.repositoryName), { + test.deepEqual(repo2.node.resolve(repo2.repositoryName), { 'Fn::ImportValue': 'RepoRepositoryName58A7E467' }); @@ -182,9 +182,9 @@ export = { const exportImport = repo2.export(); // THEN - test.deepEqual(cdk.resolve(repo2.repositoryArn), 'arn:aws:ecr:us-east-1:585695036304:repository/foo/bar/foo/fooo'); - test.deepEqual(cdk.resolve(repo2.repositoryName), 'foo/bar/foo/fooo'); - test.deepEqual(cdk.resolve(exportImport), { repositoryArn: 'arn:aws:ecr:us-east-1:585695036304:repository/foo/bar/foo/fooo' }); + test.deepEqual(repo2.node.resolve(repo2.repositoryArn), 'arn:aws:ecr:us-east-1:585695036304:repository/foo/bar/foo/fooo'); + test.deepEqual(repo2.node.resolve(repo2.repositoryName), 'foo/bar/foo/fooo'); + test.deepEqual(repo2.node.resolve(exportImport), { repositoryArn: 'arn:aws:ecr:us-east-1:585695036304:repository/foo/bar/foo/fooo' }); test.done(); }, @@ -212,8 +212,8 @@ export = { }); // THEN - test.deepEqual(cdk.resolve(repo.repositoryArn), { 'Fn::GetAtt': [ 'Boom', 'Arn' ] }); - test.deepEqual(cdk.resolve(repo.repositoryName), { 'Fn::GetAtt': [ 'Boom', 'Name' ] }); + test.deepEqual(repo.node.resolve(repo.repositoryArn), { 'Fn::GetAtt': [ 'Boom', 'Arn' ] }); + test.deepEqual(repo.node.resolve(repo.repositoryName), { 'Fn::GetAtt': [ 'Boom', 'Name' ] }); test.done(); }, @@ -227,7 +227,7 @@ export = { }); // THEN - test.deepEqual(cdk.resolve(repo.repositoryArn), { + test.deepEqual(repo.node.resolve(repo.repositoryArn), { 'Fn::Join': [ '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -238,7 +238,7 @@ export = { ':repository/my-repo' ] ] }); - test.deepEqual(cdk.resolve(repo.repositoryName), 'my-repo'); + test.deepEqual(repo.node.resolve(repo.repositoryName), 'my-repo'); test.done(); }, @@ -249,13 +249,13 @@ export = { // WHEN const repo = ecr.Repository.import(stack, 'Repo', { - repositoryArn: ecr.Repository.arnForLocalRepository(repoName), + repositoryArn: ecr.Repository.arnForLocalRepository(repoName, stack), repositoryName: repoName }); // THEN - test.deepEqual(cdk.resolve(repo.repositoryName), { 'Fn::GetAtt': [ 'Boom', 'Name' ] }); - test.deepEqual(cdk.resolve(repo.repositoryArn), { + test.deepEqual(repo.node.resolve(repo.repositoryName), { 'Fn::GetAtt': [ 'Boom', 'Name' ] }); + test.deepEqual(repo.node.resolve(repo.repositoryArn), { 'Fn::Join': [ '', [ 'arn:', { Ref: 'AWS::Partition' }, diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 83479b96eb56c..6fa8e9f251116 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -88,6 +88,8 @@ const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'ASG', { vpc, instanceType: new ec2.InstanceType('t2.xlarge'), machineImage: new EcsOptimizedAmi(), + // Or use ECS-Optimized Amazon Linux 2 AMI + // machineImage: new EcsOptimizedAmi({ generation: ec2.AmazonLinuxGeneration.AmazonLinux2 }), desiredCapacity: 3, // ... other options here ... }); 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 9ccfe00335f60..cc3e5cefd8e48 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -228,7 +228,7 @@ export abstract class BaseService extends cdk.Construct private makeAutoScalingRole(): iam.IRole { // Use a Service Linked Role. return iam.Role.import(this, 'ScalingRole', { - roleArn: cdk.ArnUtils.fromComponents({ + roleArn: cdk.Stack.find(this).formatArn({ service: 'iam', resource: 'role/aws-service-role/ecs.application-autoscaling.amazonaws.com', resourceName: 'AWSServiceRoleForApplicationAutoScaling_ECSService', 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 f1a96595e6640..f63fe555c3def 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -188,7 +188,7 @@ export class TaskDefinition extends cdk.Construct { const taskDef = new CfnTaskDefinition(this, 'Resource', { containerDefinitions: new cdk.Token(() => this.containers.map(x => x.renderContainerDefinition())), volumes: new cdk.Token(() => this.volumes), - executionRoleArn: new cdk.Token(() => this.executionRole && this.executionRole.roleArn), + executionRoleArn: new cdk.Token(() => this.executionRole && this.executionRole.roleArn).toString(), family: this.family, taskRoleArn: this.taskRole.roleArn, requiresCompatibilities: [ @@ -242,25 +242,6 @@ export class TaskDefinition extends cdk.Construct { this.volumes.push(volume); } - /** - * Validate this task definition - */ - public validate(): string[] { - const ret = super.validate(); - - if (isEc2Compatible(this.compatibility)) { - // EC2 mode validations - - // Container sizes - for (const container of this.containers) { - if (!container.memoryLimitSpecified) { - ret.push(`ECS Container ${container.node.id} must have at least one of 'memoryLimitMiB' or 'memoryReservationMiB' specified`); - } - } - } - return ret; - } - /** * Constrain where tasks can be placed */ @@ -294,6 +275,25 @@ export class TaskDefinition extends cdk.Construct { return this.executionRole; } + /** + * Validate this task definition + */ + protected validate(): string[] { + const ret = super.validate(); + + if (isEc2Compatible(this.compatibility)) { + // EC2 mode validations + + // Container sizes + for (const container of this.containers) { + if (!container.memoryLimitSpecified) { + ret.push(`ECS Container ${container.node.id} must have at least one of 'memoryLimitMiB' or 'memoryReservationMiB' specified`); + } + } + } + return ret; + } + /** * Render the placement constraints */ @@ -423,4 +423,4 @@ export interface ITaskDefinitionExtension { * Apply the extension to the given TaskDefinition */ extend(taskDefinition: TaskDefinition): void; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index ac90dc3660d60..dfc6d5cc3fe0f 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -184,18 +184,37 @@ export class Cluster extends cdk.Construct implements ICluster { } } +export interface EcsOptimizedAmiProps { + /** + * What generation of Amazon Linux to use + * + * @default AmazonLinux + */ + generation?: ec2.AmazonLinuxGeneration; +} + /** * Construct a Linux machine image from the latest ECS Optimized AMI published in SSM */ -export class EcsOptimizedAmi implements ec2.IMachineImageSource { - private static AmiParameterName = "/aws/service/ecs/optimized-ami/amazon-linux/recommended"; +export class EcsOptimizedAmi implements ec2.IMachineImageSource { + private readonly generation: ec2.AmazonLinuxGeneration; + private readonly amiParameterName: string; + + constructor(props?: EcsOptimizedAmiProps) { + this.generation = (props && props.generation) || ec2.AmazonLinuxGeneration.AmazonLinux; + if (this.generation === ec2.AmazonLinuxGeneration.AmazonLinux2) { + this.amiParameterName = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended"; + } else { + this.amiParameterName = "/aws/service/ecs/optimized-ami/amazon-linux/recommended"; + } + } /** * Return the correct image */ public getImage(scope: cdk.Construct): ec2.MachineImage { const ssmProvider = new cdk.SSMParameterProvider(scope, { - parameterName: EcsOptimizedAmi.AmiParameterName + parameterName: this.amiParameterName }); const json = ssmProvider.parameterValue("{\"image_id\": \"\"}"); diff --git a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts index 478ff70763f05..13bb3fb321616 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts @@ -59,7 +59,7 @@ export interface ContainerDefinitionProps { * * @default No labels */ - dockerLabels?: {[key: string]: string }; + dockerLabels?: { [key: string]: string }; /** * A list of custom labels for SELinux and AppArmor multi-level security systems. @@ -81,7 +81,7 @@ export interface ContainerDefinitionProps { * * @default No environment variables */ - environment?: {[key: string]: string}; + environment?: { [key: string]: string }; /** * Indicates whether the task stops if this container fails. @@ -101,7 +101,7 @@ export interface ContainerDefinitionProps { * * @default No extra hosts */ - extraHosts?: {[name: string]: string}; + extraHosts?: { [name: string]: string }; /** * Container health check. @@ -434,7 +434,7 @@ export interface HealthCheck { timeout?: number; } -function renderKV(env: {[key: string]: string}, keyName: string, valueName: string): any { +function renderKV(env: { [key: string]: string }, keyName: string, valueName: string): any { const ret = []; for (const [key, value] of Object.entries(env)) { ret.push({ [keyName]: key, [valueName]: value }); @@ -577,16 +577,16 @@ function renderPortMapping(pm: PortMapping): CfnTaskDefinition.PortMappingProper } export interface ScratchSpace { - containerPath: string, - readOnly: boolean, - sourcePath: string - name: string, + containerPath: string, + readOnly: boolean, + sourcePath: string + name: string, } export interface MountPoint { - containerPath: string, - readOnly: boolean, - sourceVolume: string, + containerPath: string, + readOnly: boolean, + sourceVolume: string, } function renderMountPoint(mp: MountPoint): CfnTaskDefinition.MountPointProperty { diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index 80e85bf387778..80628ad98ccb6 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -231,7 +231,7 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { /** * Validate this Ec2Service */ - public validate(): string[] { + protected validate(): string[] { const ret = super.validate(); if (!this.cluster.hasEc2Capacity) { ret.push('Cluster for this service needs Ec2 capacity. Call addXxxCapacity() on the cluster.'); diff --git a/packages/@aws-cdk/aws-ecs/lib/load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs/lib/load-balanced-ecs-service.ts index bbf9c2e01b4c5..8f227b4fe564a 100644 --- a/packages/@aws-cdk/aws-ecs/lib/load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/load-balanced-ecs-service.ts @@ -61,6 +61,13 @@ export interface LoadBalancedEc2ServiceProps { * @default 1 */ desiredCount?: number; + + /** + * Environment variables to pass to the container + * + * @default No environment variables + */ + environment?: { [key: string]: string }; } /** @@ -80,6 +87,7 @@ export class LoadBalancedEc2Service extends cdk.Construct { const container = taskDefinition.addContainer('web', { image: props.image, memoryLimitMiB: props.memoryLimitMiB, + environment: props.environment, }); container.addPortMappings({ diff --git a/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts b/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts index ca019b69f93f9..17a93d266e823 100644 --- a/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts +++ b/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts @@ -95,6 +95,13 @@ export interface LoadBalancedFargateServiceAppletProps extends cdk.StackProps { * Setting this option will set the load balancer port to 443. */ certificate?: string; + + /** + * Environment variables to pass to the container + * + * @default No environment variables + */ + environment?: { [key: string]: string }; } /** @@ -127,6 +134,7 @@ export class LoadBalancedFargateServiceApplet extends cdk.Stack { publicTasks: props.publicTasks, image: ContainerImage.fromDockerHub(props.image), desiredCount: props.desiredCount, + environment: props.environment, certificate, domainName: props.domainName, domainZone diff --git a/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service.ts index c90ff69435add..3124544781387 100644 --- a/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service.ts @@ -108,6 +108,14 @@ export interface LoadBalancedFargateServiceProps { * @default true */ createLogs?: boolean; + + /** + * Environment variables to pass to the container + * + * @default No environment variables + */ + environment?: { [key: string]: string }; + } /** @@ -132,7 +140,8 @@ export class LoadBalancedFargateService extends cdk.Construct { const container = taskDefinition.addContainer('web', { image: props.image, - logging: optIn ? this.createAWSLogDriver(this.node.id) : undefined + logging: optIn ? this.createAWSLogDriver(this.node.id) : undefined, + environment: props.environment }); container.addPortMappings({ diff --git a/packages/@aws-cdk/aws-ecs/lib/log-drivers/aws-log-driver.ts b/packages/@aws-cdk/aws-ecs/lib/log-drivers/aws-log-driver.ts index b97b4f9616714..0614626947f7f 100644 --- a/packages/@aws-cdk/aws-ecs/lib/log-drivers/aws-log-driver.ts +++ b/packages/@aws-cdk/aws-ecs/lib/log-drivers/aws-log-driver.ts @@ -73,12 +73,13 @@ export class AwsLogDriver extends LogDriver { * Return the log driver CloudFormation JSON */ public renderLogDriver(): CfnTaskDefinition.LogConfigurationProperty { + const stack = cdk.Stack.find(this); return { logDriver: 'awslogs', options: removeEmpty({ 'awslogs-group': this.logGroup.logGroupName, 'awslogs-stream-prefix': this.props.streamPrefix, - 'awslogs-region': `${new cdk.AwsRegion()}`, + 'awslogs-region': stack.region, 'awslogs-datetime-format': this.props.datetimeFormat, 'awslogs-multiline-pattern': this.props.multilinePattern, }), diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index cb5e391e19407..d7d4c2daf22ec 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-ecs", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::ECS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ECS" @@ -53,48 +54,48 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", + "@aws-cdk/assert": "^0.22.0", "@types/proxyquire": "^1.3.28", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0", "proxyquire": "^2.1.0" }, "dependencies": { - "@aws-cdk/assets-docker": "^0.21.0", - "@aws-cdk/aws-applicationautoscaling": "^0.21.0", - "@aws-cdk/aws-autoscaling": "^0.21.0", - "@aws-cdk/aws-certificatemanager": "^0.21.0", - "@aws-cdk/aws-cloudformation": "^0.21.0", - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/aws-ecr": "^0.21.0", - "@aws-cdk/aws-elasticloadbalancing": "^0.21.0", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-lambda": "^0.21.0", - "@aws-cdk/aws-logs": "^0.21.0", - "@aws-cdk/aws-route53": "^0.21.0", - "@aws-cdk/aws-sns": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0", - "@aws-cdk/cx-api": "^0.21.0" + "@aws-cdk/assets-docker": "^0.22.0", + "@aws-cdk/aws-applicationautoscaling": "^0.22.0", + "@aws-cdk/aws-autoscaling": "^0.22.0", + "@aws-cdk/aws-certificatemanager": "^0.22.0", + "@aws-cdk/aws-cloudformation": "^0.22.0", + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/aws-ecr": "^0.22.0", + "@aws-cdk/aws-elasticloadbalancing": "^0.22.0", + "@aws-cdk/aws-elasticloadbalancingv2": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-lambda": "^0.22.0", + "@aws-cdk/aws-logs": "^0.22.0", + "@aws-cdk/aws-route53": "^0.22.0", + "@aws-cdk/aws-sns": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0", + "@aws-cdk/cx-api": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/assets-docker": "^0.21.0", - "@aws-cdk/aws-applicationautoscaling": "^0.21.0", - "@aws-cdk/aws-autoscaling": "^0.21.0", - "@aws-cdk/aws-certificatemanager": "^0.21.0", - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/aws-ecr": "^0.21.0", - "@aws-cdk/aws-elasticloadbalancing": "^0.21.0", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-logs": "^0.21.0", - "@aws-cdk/aws-route53": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/assets-docker": "^0.22.0", + "@aws-cdk/aws-applicationautoscaling": "^0.22.0", + "@aws-cdk/aws-autoscaling": "^0.22.0", + "@aws-cdk/aws-certificatemanager": "^0.22.0", + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/aws-ecr": "^0.22.0", + "@aws-cdk/aws-elasticloadbalancing": "^0.22.0", + "@aws-cdk/aws-elasticloadbalancingv2": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-logs": "^0.22.0", + "@aws-cdk/aws-route53": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" @@ -107,4 +108,4 @@ "construct-ctor:@aws-cdk/aws-ecs.LoadBalancedFargateServiceApplet..params[0]" ] } -} +} \ No newline at end of file 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 04042794afbc2..feee1b254f7b0 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 @@ -232,7 +232,7 @@ export = { // THEN expect(stack).to(haveResourceLike("AWS::ECS::TaskDefinition", { - TaskRoleArn: cdk.resolve(taskDefinition.taskRole.roleArn) + TaskRoleArn: stack.node.resolve(taskDefinition.taskRole.roleArn) })); test.done(); diff --git a/packages/@aws-cdk/aws-ecs/test/test.container-definition.ts b/packages/@aws-cdk/aws-ecs/test/test.container-definition.ts index f2e585cd67b77..393613d1aa9ab 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.container-definition.ts @@ -253,6 +253,37 @@ export = { }, }, + 'can add environment variables to the container definition'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + + // WHEN + taskDefinition.addContainer('cont', { + image: ecs.ContainerImage.fromDockerHub('test'), + memoryLimitMiB: 1024, + environment: { + TEST_ENVIRONMENT_VARIABLE: "test environment variable value" + } + }); + + // THEN + // THEN + expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Environment: [{ + Name: "TEST_ENVIRONMENT_VARIABLE", + Value: "test environment variable value" + }] + } + ] + })); + + test.done(); + + }, + 'can add AWS logging to container definition'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -285,9 +316,9 @@ export = { PolicyDocument: { Statement: [ { - Action: [ "logs:CreateLogStream", "logs:PutLogEvents" ], + Action: ["logs:CreateLogStream", "logs:PutLogEvents"], Effect: "Allow", - Resource: { "Fn::GetAtt": [ "LoggingLogGroupC6B8E20B", "Arn" ] } + Resource: { "Fn::GetAtt": ["LoggingLogGroupC6B8E20B", "Arn"] } } ], Version: "2012-10-17" diff --git a/packages/@aws-cdk/aws-ecs/test/test.l3s.ts b/packages/@aws-cdk/aws-ecs/test/test.l3s.ts index df6d3957837a8..70413e120e296 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.l3s.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.l3s.ts @@ -19,7 +19,11 @@ export = { cluster, memoryLimitMiB: 1024, image: ecs.ContainerImage.fromDockerHub('test'), - desiredCount: 2 + desiredCount: 2, + environment: { + TEST_ENVIRONMENT_VARIABLE1: "test environment variable 1 value", + TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value" + } }); // THEN - stack containers a load balancer and a service @@ -30,6 +34,23 @@ export = { LaunchType: "EC2", })); + expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Environment: [ + { + Name: "TEST_ENVIRONMENT_VARIABLE1", + Value: "test environment variable 1 value" + }, + { + Name: "TEST_ENVIRONMENT_VARIABLE2", + Value: "test environment variable 2 value" + } + ], + } + ] + })); + test.done(); }, @@ -43,7 +64,11 @@ export = { new ecs.LoadBalancedFargateService(stack, 'Service', { cluster, image: ecs.ContainerImage.fromDockerHub('test'), - desiredCount: 2 + desiredCount: 2, + environment: { + TEST_ENVIRONMENT_VARIABLE1: "test environment variable 1 value", + TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value" + } }); // THEN - stack contains a load balancer and a service @@ -51,6 +76,16 @@ export = { expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { ContainerDefinitions: [ { + Environment: [ + { + Name: "TEST_ENVIRONMENT_VARIABLE1", + Value: "test environment variable 1 value" + }, + { + Name: "TEST_ENVIRONMENT_VARIABLE2", + Value: "test environment variable 2 value" + } + ], LogConfiguration: { LogDriver: "awslogs", Options: { @@ -86,13 +121,27 @@ export = { cluster, image: ecs.ContainerImage.fromDockerHub('test'), desiredCount: 2, - createLogs: false + createLogs: false, + environment: { + TEST_ENVIRONMENT_VARIABLE1: "test environment variable 1 value", + TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value" + } }); // THEN - stack contains a load balancer and a service expect(stack).notTo(haveResource('AWS::ECS::TaskDefinition', { ContainerDefinitions: [ { + Environment: [ + { + Name: "TEST_ENVIRONMENT_VARIABLE1", + Value: "test environment variable 1 value" + }, + { + Name: "TEST_ENVIRONMENT_VARIABLE2", + Value: "test environment variable 2 value" + } + ], LogConfiguration: { LogDriver: "awslogs", Options: { @@ -145,8 +194,8 @@ export = { }, Type: 'A', AliasTarget: { - HostedZoneId: { 'Fn::GetAtt': [ 'ServiceLBE9A1ADBC', 'CanonicalHostedZoneID' ] }, - DNSName: { 'Fn::GetAtt': [ 'ServiceLBE9A1ADBC', 'DNSName' ] }, + HostedZoneId: { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'CanonicalHostedZoneID'] }, + DNSName: { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'DNSName'] }, } })); @@ -176,7 +225,11 @@ export = { const app = new cdk.App(); const stack = new ecs.LoadBalancedFargateServiceApplet(app, 'Service', { image: 'test', - desiredCount: 2 + desiredCount: 2, + environment: { + TEST_ENVIRONMENT_VARIABLE1: "test environment variable 1 value", + TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value" + } }); // THEN - stack contains a load balancer and a service @@ -187,6 +240,23 @@ export = { LaunchType: "FARGATE", })); + expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Environment: [ + { + Name: "TEST_ENVIRONMENT_VARIABLE1", + Value: "test environment variable 1 value" + }, + { + Name: "TEST_ENVIRONMENT_VARIABLE2", + Value: "test environment variable 2 value" + } + ], + } + ] + })); + test.done(); }, @@ -221,8 +291,8 @@ export = { HostedZoneId: "/hostedzone/DUMMY", Type: 'A', AliasTarget: { - HostedZoneId: { 'Fn::GetAtt': [ 'FargateServiceLBB353E155', 'CanonicalHostedZoneID' ] }, - DNSName: { 'Fn::GetAtt': [ 'FargateServiceLBB353E155', 'DNSName' ] }, + HostedZoneId: { 'Fn::GetAtt': ['FargateServiceLBB353E155', 'CanonicalHostedZoneID'] }, + DNSName: { 'Fn::GetAtt': ['FargateServiceLBB353E155', 'DNSName'] }, } })); diff --git a/packages/@aws-cdk/aws-efs/package.json b/packages/@aws-cdk/aws-efs/package.json index 0e1e0503cfbc1..d1d4c917bafee 100644 --- a/packages/@aws-cdk/aws-efs/package.json +++ b/packages/@aws-cdk/aws-efs/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-efs", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::EFS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::EFS" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index 60b99e7ba34a5..2528687649fb2 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-eks", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::EKS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::EKS" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticache/package.json b/packages/@aws-cdk/aws-elasticache/package.json index db71b6ceea00e..f4eae49899bc6 100644 --- a/packages/@aws-cdk/aws-elasticache/package.json +++ b/packages/@aws-cdk/aws-elasticache/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-elasticache", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::ElastiCache", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ElastiCache" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticbeanstalk/package.json b/packages/@aws-cdk/aws-elasticbeanstalk/package.json index 8485af3458425..843af0d10e0e1 100644 --- a/packages/@aws-cdk/aws-elasticbeanstalk/package.json +++ b/packages/@aws-cdk/aws-elasticbeanstalk/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-elasticbeanstalk", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::ElasticBeanstalk", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ElasticBeanstalk" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/package.json b/packages/@aws-cdk/aws-elasticloadbalancing/package.json index 3db81523cd10c..f41d0e68b5cd7 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancing/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-elasticloadbalancing", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS ElasticLoadBalancing", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ElasticLoadBalancing" @@ -53,24 +54,24 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-codedeploy-api": "^0.21.0", - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-codedeploy-api": "^0.22.0", + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-codedeploy-api": "^0.21.0", - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-codedeploy-api": "^0.22.0", + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts index cbe634e20b6c6..b6857f0fdbfa0 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts @@ -109,16 +109,6 @@ export class ApplicationListenerRule extends cdk.Construct implements cdk.IDepen this.conditions[field] = values; } - /** - * Validate the rule - */ - public validate() { - if (this.actions.length === 0) { - return ['Listener rule needs at least one action']; - } - return []; - } - /** * Add a TargetGroup to load balance to */ @@ -130,6 +120,16 @@ export class ApplicationListenerRule extends cdk.Construct implements cdk.IDepen targetGroup.registerListener(this.listener, this); } + /** + * Validate the rule + */ + protected validate() { + if (this.actions.length === 0) { + return ['Listener rule needs at least one action']; + } + return []; + } + /** * Render the conditions for this rule */ @@ -142,4 +142,4 @@ export class ApplicationListenerRule extends cdk.Construct implements cdk.IDepen } return ret; } -} \ 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 60c2033f1e153..eb32e0264f6b1 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -224,17 +224,6 @@ export class ApplicationListener extends BaseListener implements IApplicationLis this.connections.allowTo(connectable, portRange, 'Load balancer to target'); } - /** - * Validate this listener. - */ - public validate(): string[] { - const errors = super.validate(); - if (this.protocol === ApplicationProtocol.Https && this.certificateArns.length === 0) { - errors.push('HTTPS Listener needs at least one certificate (call addCertificateArns)'); - } - return errors; - } - /** * Export this listener */ @@ -246,6 +235,17 @@ export class ApplicationListener extends BaseListener implements IApplicationLis }; } + /** + * Validate this listener. + */ + protected validate(): string[] { + const errors = super.validate(); + if (this.protocol === ApplicationProtocol.Https && this.certificateArns.length === 0) { + errors.push('HTTPS Listener needs at least one certificate (call addCertificateArns)'); + } + return errors; + } + /** * Add a default TargetGroup */ diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts index a4043438529b8..44b8299d5cb9b 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts @@ -25,7 +25,7 @@ export abstract class BaseListener extends cdk.Construct implements cdk.IDependa /** * Validate this listener */ - public validate(): string[] { + protected validate(): string[] { if (this.defaultActions.length === 0) { return ['Listener needs at least one default target group (call addTargetGroups)']; } 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 a70ef98fcd4d2..0e98adc7123d0 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 @@ -104,7 +104,7 @@ export abstract class BaseLoadBalancer extends cdk.Construct implements route53. this.vpc = baseProps.vpc; const resource = new CfnLoadBalancer(this, 'Resource', { - loadBalancerName: baseProps.loadBalancerName, + name: baseProps.loadBalancerName, subnets: subnets.map(s => s.subnetId), scheme: internetFacing ? 'internet-facing' : 'internal', loadBalancerAttributes: new cdk.Token(() => renderAttributes(this.attributes)), diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts index 32cd72a8ad4a3..2b71ef19d39d1 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts @@ -234,7 +234,7 @@ export abstract class TargetGroupBase extends cdk.Construct implements ITargetGr ...additionalProps }); - this.targetGroupLoadBalancerArns = this.resource.targetGroupLoadBalancerArns.toList(); + this.targetGroupLoadBalancerArns = this.resource.targetGroupLoadBalancerArns; this.targetGroupArn = this.resource.ref; this.targetGroupFullName = this.resource.targetGroupFullName; this.loadBalancerArns = this.resource.targetGroupLoadBalancerArns.toString(); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json index f0c32b4097d58..180bcdca5e112 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-elasticloadbalancingv2", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::ElasticLoadBalancingV2", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ElasticLoadBalancingV2" @@ -53,29 +54,29 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-codedeploy-api": "^0.21.0", - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-route53": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-codedeploy-api": "^0.22.0", + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-route53": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-codedeploy-api": "^0.21.0", - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/aws-route53": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-codedeploy-api": "^0.22.0", + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/aws-route53": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" @@ -87,4 +88,4 @@ "construct-ctor:@aws-cdk/aws-elasticloadbalancingv2.TargetGroupBase." ] } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts index 2bc6f79866622..2311081a79f3a 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts @@ -412,7 +412,7 @@ export = { test.equal('AWS/ApplicationELB', metric.namespace); const loadBalancerArn = { Ref: "LBSomeListenerCA01F1A0" }; - test.deepEqual(cdk.resolve(metric.dimensions), { + test.deepEqual(lb.node.resolve(metric.dimensions), { TargetGroup: { 'Fn::GetAtt': [ 'TargetGroup3D7CD9B8', 'TargetGroupFullName' ] }, LoadBalancer: { 'Fn::Join': [ '', diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts index ea995361ae531..6a566df599ec3 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts @@ -2,6 +2,7 @@ import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; import ec2 = require('@aws-cdk/aws-ec2'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); +import { Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import elbv2 = require('../../lib'); @@ -179,11 +180,29 @@ export = { for (const metric of metrics) { test.equal('AWS/ApplicationELB', metric.namespace); - test.deepEqual(cdk.resolve(metric.dimensions), { + test.deepEqual(stack.node.resolve(metric.dimensions), { LoadBalancer: { 'Fn::GetAtt': ['LB8A12904C', 'LoadBalancerFullName'] } }); } test.done(); }, + + 'loadBalancerName'(test: Test) { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + + // WHEN + new elbv2.ApplicationLoadBalancer(stack, 'ALB', { + loadBalancerName: 'myLoadBalancer', + vpc + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Name: 'myLoadBalancer' + })); + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts index bc69a591ad94c..5cfa73c785550 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts @@ -75,4 +75,23 @@ export = { test.done(); }, + + 'loadBalancerName'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + + // WHEN + new elbv2.NetworkLoadBalancer(stack, 'ALB', { + loadBalancerName: 'myLoadBalancer', + vpc + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Name: 'myLoadBalancer' + })); + test.done(); + } + }; diff --git a/packages/@aws-cdk/aws-elasticsearch/package.json b/packages/@aws-cdk/aws-elasticsearch/package.json index 17362517d46b4..9f85132ace727 100644 --- a/packages/@aws-cdk/aws-elasticsearch/package.json +++ b/packages/@aws-cdk/aws-elasticsearch/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-elasticsearch", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::Elasticsearch", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Elasticsearch" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-emr/package.json b/packages/@aws-cdk/aws-emr/package.json index 26d0f0d760ddc..3edff603a78b9 100644 --- a/packages/@aws-cdk/aws-emr/package.json +++ b/packages/@aws-cdk/aws-emr/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-emr", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::EMR", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::EMR" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/lib/rule.ts b/packages/@aws-cdk/aws-events/lib/rule.ts index b04ab872c860c..56b58e73c96c5 100644 --- a/packages/@aws-cdk/aws-events/lib/rule.ts +++ b/packages/@aws-cdk/aws-events/lib/rule.ts @@ -84,7 +84,7 @@ export class EventRule extends Construct implements IEventRule { name: props.ruleName, description: props.description, state: props.enabled == null ? 'ENABLED' : (props.enabled ? 'ENABLED' : 'DISABLED'), - scheduleExpression: new Token(() => this.scheduleExpression), + scheduleExpression: new Token(() => this.scheduleExpression).toString(), eventPattern: new Token(() => this.renderEventPattern()), targets: new Token(() => this.renderTargets()) }); @@ -199,7 +199,7 @@ export class EventRule extends Construct implements IEventRule { mergeEventPattern(this.eventPattern, eventPattern); } - public validate() { + protected validate() { if (Object.keys(this.eventPattern).length === 0 && !this.scheduleExpression) { return [ `Either 'eventPattern' or 'scheduleExpression' must be defined` ]; } diff --git a/packages/@aws-cdk/aws-events/package.json b/packages/@aws-cdk/aws-events/package.json index c090901545031..57b68709bbf07 100644 --- a/packages/@aws-cdk/aws-events/package.json +++ b/packages/@aws-cdk/aws-events/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-events", - "version": "0.21.0", + "version": "0.22.0", "description": "AWS CloudWatch Events Construct Library", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Events" @@ -54,20 +55,20 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ 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 3e0001c455d9d..870194bde5ead 100644 --- a/packages/@aws-cdk/aws-events/test/test.rule.ts +++ b/packages/@aws-cdk/aws-events/test/test.rule.ts @@ -1,6 +1,6 @@ import { expect, haveResource } from '@aws-cdk/assert'; import cdk = require('@aws-cdk/cdk'); -import { resolve, Stack } from '@aws-cdk/cdk'; +import { Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { IEventRuleTarget } from '../lib'; import { EventRule } from '../lib/rule'; @@ -329,7 +329,7 @@ export = { const rule = new EventRule(stack, 'EventRule'); rule.addTarget(t1); - test.deepEqual(resolve(receivedRuleArn), resolve(rule.ruleArn)); + test.deepEqual(stack.node.resolve(receivedRuleArn), stack.node.resolve(rule.ruleArn)); test.deepEqual(receivedRuleId, rule.node.uniqueId); test.done(); }, @@ -347,7 +347,7 @@ export = { }); // THEN - test.deepEqual(cdk.resolve(exportedRule), { eventRuleArn: { 'Fn::ImportValue': 'MyRuleRuleArnDB13ADB1' } }); + test.deepEqual(stack.node.resolve(exportedRule), { eventRuleArn: { 'Fn::ImportValue': 'MyRuleRuleArnDB13ADB1' } }); test.deepEqual(importedRule.ruleArn, 'arn:of:rule'); test.done(); diff --git a/packages/@aws-cdk/aws-gamelift/package.json b/packages/@aws-cdk/aws-gamelift/package.json index 63c5e341eb8f8..ca58d813de56e 100644 --- a/packages/@aws-cdk/aws-gamelift/package.json +++ b/packages/@aws-cdk/aws-gamelift/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-gamelift", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::GameLift", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::GameLift" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-glue/package.json b/packages/@aws-cdk/aws-glue/package.json index a7aab0cc26e8a..5385391b02fdf 100644 --- a/packages/@aws-cdk/aws-glue/package.json +++ b/packages/@aws-cdk/aws-glue/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-glue", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::Glue", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Glue" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-guardduty/package.json b/packages/@aws-cdk/aws-guardduty/package.json index 86dc0a10afadc..d268b6ead172c 100644 --- a/packages/@aws-cdk/aws-guardduty/package.json +++ b/packages/@aws-cdk/aws-guardduty/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-guardduty", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::GuardDuty", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::GuardDuty" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/lib/managed-policy.ts b/packages/@aws-cdk/aws-iam/lib/managed-policy.ts index 64d78f4c2fe2e..531080b80ac4b 100644 --- a/packages/@aws-cdk/aws-iam/lib/managed-policy.ts +++ b/packages/@aws-cdk/aws-iam/lib/managed-policy.ts @@ -10,7 +10,7 @@ import cdk = require('@aws-cdk/cdk'); * prefix when constructing this object. */ export class AwsManagedPolicy { - constructor(private readonly managedPolicyName: string) { + constructor(private readonly managedPolicyName: string, private readonly scope: cdk.IConstruct) { } /** @@ -18,7 +18,7 @@ export class AwsManagedPolicy { */ public get policyArn(): string { // the arn is in the form of - arn:aws:iam::aws:policy/ - return cdk.ArnUtils.fromComponents({ + return cdk.Stack.find(this.scope).formatArn({ service: "iam", region: "", // no region for managed policy account: "aws", // the account for a managed policy is 'aws' diff --git a/packages/@aws-cdk/aws-iam/lib/policy-document.ts b/packages/@aws-cdk/aws-iam/lib/policy-document.ts index 5022a6f7445ca..3a30676b18a59 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy-document.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy-document.ts @@ -1,6 +1,6 @@ -import { AwsAccountId, AwsPartition, Token } from '@aws-cdk/cdk'; +import cdk = require('@aws-cdk/cdk'); -export class PolicyDocument extends Token { +export class PolicyDocument extends cdk.Token { private statements = new Array(); /** @@ -12,7 +12,7 @@ export class PolicyDocument extends Token { super(); } - public resolve(): any { + public resolve(_context: cdk.ResolveContext): any { if (this.isEmpty) { return undefined; } @@ -82,7 +82,7 @@ export class ArnPrincipal extends PolicyPrincipal { export class AccountPrincipal extends ArnPrincipal { constructor(public readonly accountId: any) { - super(`arn:${new AwsPartition()}:iam::${accountId}:root`); + super(new StackDependentToken(stack => `arn:${stack.partition}:iam::${accountId}:root`).toString()); } } @@ -137,7 +137,7 @@ export class FederatedPrincipal extends PolicyPrincipal { export class AccountRootPrincipal extends AccountPrincipal { constructor() { - super(new AwsAccountId()); + super(new StackDependentToken(stack => stack.accountId).toString()); } } @@ -201,7 +201,7 @@ export class CompositePrincipal extends PolicyPrincipal { /** * Represents a statement in an IAM policy document. */ -export class PolicyStatement extends Token { +export class PolicyStatement extends cdk.Token { private action = new Array(); private principal: { [key: string]: any[] } = {}; private resource = new Array(); @@ -250,14 +250,14 @@ export class PolicyStatement extends Token { return this.addPrincipal(new ArnPrincipal(arn)); } - public addArnPrincipal(arn: string): this { - return this.addAwsPrincipal(arn); - } - public addAwsAccountPrincipal(accountId: string): this { return this.addPrincipal(new AccountPrincipal(accountId)); } + public addArnPrincipal(arn: string): this { + return this.addAwsPrincipal(arn); + } + public addServicePrincipal(service: string): this { return this.addPrincipal(new ServicePrincipal(service)); } @@ -363,7 +363,7 @@ export class PolicyStatement extends Token { } public limitToAccount(accountId: string): PolicyStatement { - return this.addCondition('StringEquals', new Token(() => { + return this.addCondition('StringEquals', new cdk.Token(() => { return { 'sts:ExternalId': accountId }; })); } @@ -371,8 +371,7 @@ export class PolicyStatement extends Token { // // Serialization // - - public resolve(): any { + public resolve(_context: cdk.ResolveContext): any { return this.toJson(); } @@ -450,3 +449,17 @@ function mergePrincipal(target: { [key: string]: string[] }, source: { [key: str return target; } + +/** + * A lazy token that requires an instance of Stack to evaluate + */ +class StackDependentToken extends cdk.Token { + constructor(private readonly fn: (stack: cdk.Stack) => any) { + super(); + } + + public resolve(context: cdk.ResolveContext) { + const stack = cdk.Stack.find(context.scope); + return this.fn(stack); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/lib/policy.ts b/packages/@aws-cdk/aws-iam/lib/policy.ts index 334d80205116c..6c5208a7b9dd9 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy.ts @@ -108,7 +108,7 @@ export class Policy extends Construct implements IDependable { const resource = new CfnPolicy(this, 'Resource', { policyDocument: this.document, - policyName: new Token(() => this.policyName), + policyName: new Token(() => this.policyName).toString(), roles: undefinedIfEmpty(() => this.roles.map(r => r.roleName)), users: undefinedIfEmpty(() => this.users.map(u => u.userName)), groups: undefinedIfEmpty(() => this.groups.map(g => g.groupName)), @@ -171,7 +171,7 @@ export class Policy extends Construct implements IDependable { group.attachInlinePolicy(this); } - public validate(): string[] { + protected validate(): string[] { const result = new Array(); // validate that the policy document is not empty diff --git a/packages/@aws-cdk/aws-iam/lib/util.ts b/packages/@aws-cdk/aws-iam/lib/util.ts index 9acb4a71747bc..aeb0f2c39652f 100644 --- a/packages/@aws-cdk/aws-iam/lib/util.ts +++ b/packages/@aws-cdk/aws-iam/lib/util.ts @@ -1,10 +1,10 @@ -import { CloudFormationToken } from '@aws-cdk/cdk'; +import { Token } from '@aws-cdk/cdk'; import { Policy } from './policy'; const MAX_POLICY_NAME_LEN = 128; -export function undefinedIfEmpty(f: () => T[]): CloudFormationToken { - return new CloudFormationToken(() => { +export function undefinedIfEmpty(f: () => T[]): Token { + return new Token(() => { const array = f(); return (array && array.length > 0) ? array : undefined; }); diff --git a/packages/@aws-cdk/aws-iam/package.json b/packages/@aws-cdk/aws-iam/package.json index 57e6a9f2d37c1..6a72aeba73b75 100644 --- a/packages/@aws-cdk/aws-iam/package.json +++ b/packages/@aws-cdk/aws-iam/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-iam", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK routines for easily assigning correct and minimal IAM permissions", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "pkglint": "pkglint -f", "integ": "cdk-integ", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::IAM" @@ -55,20 +56,20 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/test.auto-cross-stack-refs.ts b/packages/@aws-cdk/aws-iam/test/test.auto-cross-stack-refs.ts new file mode 100644 index 0000000000000..ffd1ea94c6d7c --- /dev/null +++ b/packages/@aws-cdk/aws-iam/test/test.auto-cross-stack-refs.ts @@ -0,0 +1,52 @@ +import { expect } from '@aws-cdk/assert'; +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import iam = require('../lib'); + +export = { + 'automatic exports are created when attributes are referneced across stacks'(test: Test) { + // GIVEN + const stackWithUser = new cdk.Stack(); + const stackWithGroup = new cdk.Stack(); + const user = new iam.User(stackWithUser, 'User'); + const group = new iam.Group(stackWithGroup, 'Group'); + + // WHEN + group.addUser(user); + + // + // `group.addUser` adds the group to the user resource definition, so we expect + // that an automatic export will be created for the group and the user's stack + // to use ImportValue to import it. + // note that order of "expect"s matters. we first need to synthesize the user's + // stack so that the cross stack reference will be reported and only then the + // group's stack. in the real world, App will take care of this. + // + + // THEN + expect(stackWithUser).toMatch({ + Resources: { + User00B015A1: { + Type: "AWS::IAM::User", + Properties: { + Groups: [ { "Fn::ImportValue": "ExportsOutputRefGroupC77FDACD8CF7DD5B" } ] + } + } + } + }); + expect(stackWithGroup).toMatch({ + Outputs: { + ExportsOutputRefGroupC77FDACD8CF7DD5B: { + Value: { Ref: "GroupC77FDACD" }, + Export: { Name: "ExportsOutputRefGroupC77FDACD8CF7DD5B" } + } + }, + Resources: { + GroupC77FDACD: { + Type: "AWS::IAM::Group" + } + } + }); + test.done(); + } +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/test.legacy-resources.ts b/packages/@aws-cdk/aws-iam/test/test.legacy-resources.ts deleted file mode 100644 index c47da3d7a7215..0000000000000 --- a/packages/@aws-cdk/aws-iam/test/test.legacy-resources.ts +++ /dev/null @@ -1,24 +0,0 @@ -import cdk = require('@aws-cdk/cdk'); -import { Test } from 'nodeunit'; -import iam = require('../lib'); - -export = { - 'cloudformation XxxResource emits a warning'(test: Test) { - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'test-stack'); - - new iam.cloudformation.UserResource(stack, 'LegacyResource', { - userName: 'MyUserName' - }); - - const out = app.synthesizeStack('test-stack'); - - const warnings = out.metadata['/test-stack/LegacyResource'].filter(md => md.type === 'aws:cdk:warning'); - test.deepEqual(warnings.length, 1); - test.deepEqual(warnings[0].data, - 'DEPRECATION: \"cloudformation.UserResource\" will be deprecated in a future release in ' + - 'favor of \"CfnUser\" (see https://github.com/awslabs/aws-cdk/issues/878)'); - - test.done(); - } -}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/test.managed-policy.ts b/packages/@aws-cdk/aws-iam/test/test.managed-policy.ts index 49989eecc870a..142a239febaee 100644 --- a/packages/@aws-cdk/aws-iam/test/test.managed-policy.ts +++ b/packages/@aws-cdk/aws-iam/test/test.managed-policy.ts @@ -4,9 +4,10 @@ import { AwsManagedPolicy } from '../lib'; export = { 'simple managed policy'(test: Test) { - const mp = new AwsManagedPolicy("service-role/SomePolicy"); + const stack = new cdk.Stack(); + const mp = new AwsManagedPolicy("service-role/SomePolicy", stack); - test.deepEqual(cdk.resolve(mp.policyArn), { + test.deepEqual(stack.node.resolve(mp.policyArn), { "Fn::Join": ['', [ 'arn:', { Ref: 'AWS::Partition' }, diff --git a/packages/@aws-cdk/aws-iam/test/test.policy-document.ts b/packages/@aws-cdk/aws-iam/test/test.policy-document.ts index 7cb0e3997aa94..97eeef42f95aa 100644 --- a/packages/@aws-cdk/aws-iam/test/test.policy-document.ts +++ b/packages/@aws-cdk/aws-iam/test/test.policy-document.ts @@ -1,10 +1,12 @@ -import { resolve, Token } from '@aws-cdk/cdk'; +import { Stack, Token } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { Anyone, AnyPrincipal, CanonicalUserPrincipal, PolicyDocument, PolicyPrincipal, PolicyStatement } from '../lib'; import { ArnPrincipal, CompositePrincipal, FederatedPrincipal, PrincipalPolicyFragment, ServicePrincipal } from '../lib'; export = { 'the Permission class is a programming model for iam'(test: Test) { + const stack = new Stack(); + const p = new PolicyStatement(); p.addAction('sqs:SendMessage'); p.addActions('dynamodb:CreateTable', 'dynamodb:DeleteTable'); @@ -15,7 +17,7 @@ export = { p.addAwsAccountPrincipal(`my${new Token({ account: 'account' })}name`); p.limitToAccount('12221121221'); - test.deepEqual(resolve(p), { Action: + test.deepEqual(stack.node.resolve(p), { Action: [ 'sqs:SendMessage', 'dynamodb:CreateTable', 'dynamodb:DeleteTable' ], @@ -36,6 +38,7 @@ export = { }, 'the PolicyDocument class is a dom for iam policy documents'(test: Test) { + const stack = new Stack(); const doc = new PolicyDocument(); const p1 = new PolicyStatement(); p1.addAction('sqs:SendMessage'); @@ -48,7 +51,7 @@ export = { doc.addStatement(p1); doc.addStatement(p2); - test.deepEqual(resolve(doc), { + test.deepEqual(stack.node.resolve(doc), { Version: '2012-10-17', Statement: [ { Effect: 'Allow', Action: 'sqs:SendMessage', Resource: '*' }, @@ -58,6 +61,7 @@ export = { }, 'A PolicyDocument can be initialized with an existing policy, which is merged upon serialization'(test: Test) { + const stack = new Stack(); const base = { Version: 'Foo', Something: 123, @@ -69,7 +73,7 @@ export = { const doc = new PolicyDocument(base); doc.addStatement(new PolicyStatement().addResource('resource').addAction('action')); - test.deepEqual(resolve(doc), { Version: 'Foo', + test.deepEqual(stack.node.resolve(doc), { Version: 'Foo', Something: 123, Statement: [ { Statement1: 1 }, @@ -79,8 +83,9 @@ export = { }, 'Permission allows specifying multiple actions upon construction'(test: Test) { + const stack = new Stack(); const perm = new PolicyStatement().addResource('MyResource').addActions('Action1', 'Action2', 'Action3'); - test.deepEqual(resolve(perm), { + test.deepEqual(stack.node.resolve(perm), { Effect: 'Allow', Action: [ 'Action1', 'Action2', 'Action3' ], Resource: 'MyResource' }); @@ -88,16 +93,18 @@ export = { }, 'PolicyDoc resolves to undefined if there are no permissions'(test: Test) { + const stack = new Stack(); const p = new PolicyDocument(); - test.deepEqual(resolve(p), undefined); + test.deepEqual(stack.node.resolve(p), undefined); test.done(); }, 'canonicalUserPrincipal adds a principal to a policy with the passed canonical user id'(test: Test) { + const stack = new Stack(); const p = new PolicyStatement(); const canoncialUser = "averysuperduperlongstringfor"; p.addPrincipal(new CanonicalUserPrincipal(canoncialUser)); - test.deepEqual(resolve(p), { + test.deepEqual(stack.node.resolve(p), { Effect: "Allow", Principal: { CanonicalUser: canoncialUser @@ -107,9 +114,11 @@ export = { }, 'addAccountRootPrincipal adds a principal with the current account root'(test: Test) { + const stack = new Stack(); + const p = new PolicyStatement(); p.addAccountRootPrincipal(); - test.deepEqual(resolve(p), { + test.deepEqual(stack.node.resolve(p), { Effect: "Allow", Principal: { AWS: { @@ -130,9 +139,10 @@ export = { }, 'addFederatedPrincipal adds a Federated principal with the passed value'(test: Test) { + const stack = new Stack(); const p = new PolicyStatement(); p.addFederatedPrincipal("com.amazon.cognito", { StringEquals: { key: 'value' }}); - test.deepEqual(resolve(p), { + test.deepEqual(stack.node.resolve(p), { Effect: "Allow", Principal: { Federated: "com.amazon.cognito" @@ -145,10 +155,12 @@ export = { }, 'addAwsAccountPrincipal can be used multiple times'(test: Test) { + const stack = new Stack(); + const p = new PolicyStatement(); p.addAwsAccountPrincipal('1234'); p.addAwsAccountPrincipal('5678'); - test.deepEqual(resolve(p), { + test.deepEqual(stack.node.resolve(p), { Effect: 'Allow', Principal: { AWS: [ @@ -208,13 +220,14 @@ export = { }, 'the { AWS: "*" } principal is represented as `Anyone` or `AnyPrincipal`'(test: Test) { + const stack = new Stack(); const p = new PolicyDocument(); p.addStatement(new PolicyStatement().addPrincipal(new Anyone())); p.addStatement(new PolicyStatement().addPrincipal(new AnyPrincipal())); p.addStatement(new PolicyStatement().addAnyPrincipal()); - test.deepEqual(resolve(p), { + test.deepEqual(stack.node.resolve(p), { Statement: [ { Effect: 'Allow', Principal: '*' }, { Effect: 'Allow', Principal: '*' }, @@ -226,13 +239,14 @@ export = { }, 'addAwsPrincipal/addArnPrincipal are the aliases'(test: Test) { + const stack = new Stack(); const p = new PolicyDocument(); p.addStatement(new PolicyStatement().addAwsPrincipal('111222-A')); p.addStatement(new PolicyStatement().addArnPrincipal('111222-B')); p.addStatement(new PolicyStatement().addPrincipal(new ArnPrincipal('111222-C'))); - test.deepEqual(resolve(p), { + test.deepEqual(stack.node.resolve(p), { Statement: [ { Effect: 'Allow', Principal: { AWS: '111222-A' } }, { Effect: 'Allow', Principal: { AWS: '111222-B' } }, @@ -245,12 +259,13 @@ export = { }, 'addCanonicalUserPrincipal can be used to add cannonical user principals'(test: Test) { + const stack = new Stack(); const p = new PolicyDocument(); p.addStatement(new PolicyStatement().addCanonicalUserPrincipal('cannonical-user-1')); p.addStatement(new PolicyStatement().addPrincipal(new CanonicalUserPrincipal('cannonical-user-2'))); - test.deepEqual(resolve(p), { + test.deepEqual(stack.node.resolve(p), { Statement: [ { Effect: 'Allow', Principal: { CanonicalUser: 'cannonical-user-1' } }, { Effect: 'Allow', Principal: { CanonicalUser: 'cannonical-user-2' } } @@ -262,13 +277,14 @@ export = { }, 'addPrincipal correctly merges array in'(test: Test) { + const stack = new Stack(); const arrayPrincipal: PolicyPrincipal = { assumeRoleAction: 'sts:AssumeRole', policyFragment: () => new PrincipalPolicyFragment({ AWS: ['foo', 'bar'] }), }; const s = new PolicyStatement().addAccountRootPrincipal() .addPrincipal(arrayPrincipal); - test.deepEqual(resolve(s), { + test.deepEqual(stack.node.resolve(s), { Effect: 'Allow', Principal: { AWS: [ @@ -282,13 +298,14 @@ export = { // https://github.com/awslabs/aws-cdk/issues/1201 'policy statements with multiple principal types can be created using multiple addPrincipal calls'(test: Test) { + const stack = new Stack(); const s = new PolicyStatement() .addAwsPrincipal('349494949494') .addServicePrincipal('ec2.amazonaws.com') .addResource('resource') .addAction('action'); - test.deepEqual(resolve(s), { + test.deepEqual(stack.node.resolve(s), { Action: 'action', Effect: 'Allow', Principal: { AWS: '349494949494', Service: 'ec2.amazonaws.com' }, @@ -301,9 +318,10 @@ export = { 'CompositePrincipal can be used to represent a principal that has multiple types': { 'with a single principal'(test: Test) { + const stack = new Stack(); const p = new CompositePrincipal(new ArnPrincipal('i:am:an:arn')); const statement = new PolicyStatement().addPrincipal(p); - test.deepEqual(resolve(statement), { Effect: 'Allow', Principal: { AWS: 'i:am:an:arn' } }); + test.deepEqual(stack.node.resolve(statement), { Effect: 'Allow', Principal: { AWS: 'i:am:an:arn' } }); test.done(); }, @@ -316,6 +334,7 @@ export = { }, 'principals and conditions are a big nice merge'(test: Test) { + const stack = new Stack(); // add via ctor const p = new CompositePrincipal( new ArnPrincipal('i:am:an:arn'), @@ -333,7 +352,7 @@ export = { statement.addAwsPrincipal('aws-principal-3'); statement.addCondition('cond2', { boom: 123 }); - test.deepEqual(resolve(statement), { + test.deepEqual(stack.node.resolve(statement), { Condition: { cond2: { boom: 123 } }, diff --git a/packages/@aws-cdk/aws-iam/test/test.role.ts b/packages/@aws-cdk/aws-iam/test/test.role.ts index b78deac2bb514..54ea02a6a3121 100644 --- a/packages/@aws-cdk/aws-iam/test/test.role.ts +++ b/packages/@aws-cdk/aws-iam/test/test.role.ts @@ -1,5 +1,5 @@ import { expect, haveResource } from '@aws-cdk/assert'; -import { resolve, Resource, Stack } from '@aws-cdk/cdk'; +import { Resource, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { ArnPrincipal, CompositePrincipal, FederatedPrincipal, PolicyStatement, Role, ServicePrincipal } from '../lib'; @@ -249,13 +249,13 @@ export = { const importedRole = Role.import(stack, 'ImportedRole', exportedRole); // THEN - test.deepEqual(resolve(exportedRole), { + test.deepEqual(stack.node.resolve(exportedRole), { roleArn: { 'Fn::ImportValue': 'MyRoleRoleArn3388B7E2' }, roleId: { 'Fn::ImportValue': 'MyRoleRoleIdF7B258D8' } }); - test.deepEqual(resolve(importedRole.roleArn), { 'Fn::ImportValue': 'MyRoleRoleArn3388B7E2' }); - test.deepEqual(resolve(importedRole.roleId), { 'Fn::ImportValue': 'MyRoleRoleIdF7B258D8' }); + test.deepEqual(stack.node.resolve(importedRole.roleArn), { 'Fn::ImportValue': 'MyRoleRoleArn3388B7E2' }); + test.deepEqual(stack.node.resolve(importedRole.roleId), { 'Fn::ImportValue': 'MyRoleRoleIdF7B258D8' }); test.done(); } }; diff --git a/packages/@aws-cdk/aws-inspector/package.json b/packages/@aws-cdk/aws-inspector/package.json index 503088c8c3234..ad3fea43ef757 100644 --- a/packages/@aws-cdk/aws-inspector/package.json +++ b/packages/@aws-cdk/aws-inspector/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-inspector", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::Inspector", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Inspector" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot/package.json b/packages/@aws-cdk/aws-iot/package.json index 991d3b4ba04c6..60d089fd859a7 100644 --- a/packages/@aws-cdk/aws-iot/package.json +++ b/packages/@aws-cdk/aws-iot/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-iot", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::IoT", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::IoT" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot1click/package.json b/packages/@aws-cdk/aws-iot1click/package.json index 6a15d49470199..686a044e63f09 100644 --- a/packages/@aws-cdk/aws-iot1click/package.json +++ b/packages/@aws-cdk/aws-iot1click/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-iot1click", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::IoT1Click", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -36,7 +36,8 @@ "pkglint": "pkglint -f", "test": "cdk-test", "watch": "cdk-watch", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::IoT1Click" @@ -54,18 +55,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotanalytics/package.json b/packages/@aws-cdk/aws-iotanalytics/package.json index 1386cdd8c906d..5316f012c5862 100644 --- a/packages/@aws-cdk/aws-iotanalytics/package.json +++ b/packages/@aws-cdk/aws-iotanalytics/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-iotanalytics", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::IoTAnalytics", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -36,7 +36,8 @@ "pkglint": "pkglint -f", "test": "cdk-test", "watch": "cdk-watch", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::IoTAnalytics" @@ -55,18 +56,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesis/lib/stream.ts b/packages/@aws-cdk/aws-kinesis/lib/stream.ts index 4cdda2e39a634..0b569ccf07227 100644 --- a/packages/@aws-cdk/aws-kinesis/lib/stream.ts +++ b/packages/@aws-cdk/aws-kinesis/lib/stream.ts @@ -197,9 +197,10 @@ export abstract class StreamBase extends cdk.Construct implements IStream { public logSubscriptionDestination(sourceLogGroup: logs.ILogGroup): logs.LogSubscriptionDestination { // Following example from https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/SubscriptionFilters.html#DestinationKinesisExample if (!this.cloudWatchLogsRole) { + const stack = cdk.Stack.find(this); // Create a role to be assumed by CWL that can write to this stream and pass itself. this.cloudWatchLogsRole = new iam.Role(this, 'CloudWatchLogsCanPutRecords', { - assumedBy: new iam.ServicePrincipal(`logs.${new cdk.AwsRegion()}.amazonaws.com`) + assumedBy: new iam.ServicePrincipal(`logs.${stack.region}.amazonaws.com`) }); this.cloudWatchLogsRole.addToPolicy(new iam.PolicyStatement().addAction('kinesis:PutRecord').addResource(this.streamArn)); this.cloudWatchLogsRole.addToPolicy(new iam.PolicyStatement().addAction('iam:PassRole').addResource(this.cloudWatchLogsRole.roleArn)); @@ -428,7 +429,7 @@ class ImportedStream extends StreamBase { this.streamArn = props.streamArn; // Get the name from the ARN - this.streamName = cdk.ArnUtils.parse(props.streamArn).resourceName!; + this.streamName = cdk.Stack.find(this).parseArn(props.streamArn).resourceName!; if (props.encryptionKey) { // TODO: import "scope" should be changed to "this" diff --git a/packages/@aws-cdk/aws-kinesis/package.json b/packages/@aws-cdk/aws-kinesis/package.json index 1737d2f2a329e..afffb056f8b7b 100644 --- a/packages/@aws-cdk/aws-kinesis/package.json +++ b/packages/@aws-cdk/aws-kinesis/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-kinesis", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS Kinesis", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -34,7 +34,8 @@ "test": "cdk-test", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Kinesis" @@ -52,25 +53,25 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-kms": "^0.21.0", - "@aws-cdk/aws-logs": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-kms": "^0.22.0", + "@aws-cdk/aws-logs": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-kms": "^0.21.0", - "@aws-cdk/aws-logs": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-kms": "^0.22.0", + "@aws-cdk/aws-logs": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesisanalytics/package.json b/packages/@aws-cdk/aws-kinesisanalytics/package.json index b4b6596a43fc7..08f6e759bed2a 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics/package.json +++ b/packages/@aws-cdk/aws-kinesisanalytics/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-kinesisanalytics", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::KinesisAnalytics", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::KinesisAnalytics" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesisfirehose/package.json b/packages/@aws-cdk/aws-kinesisfirehose/package.json index 42f9780408cf9..1431c853cf04b 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/package.json +++ b/packages/@aws-cdk/aws-kinesisfirehose/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-kinesisfirehose", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::KinesisFirehose", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::KinesisFirehose" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kms/lib/key.ts b/packages/@aws-cdk/aws-kms/lib/key.ts index e55deee0ccb85..7b259bfb8fc25 100644 --- a/packages/@aws-cdk/aws-kms/lib/key.ts +++ b/packages/@aws-cdk/aws-kms/lib/key.ts @@ -1,5 +1,5 @@ import { PolicyDocument, PolicyStatement } from '@aws-cdk/aws-iam'; -import { Construct, DeletionPolicy, IConstruct, Output, resolve } from '@aws-cdk/cdk'; +import { Construct, DeletionPolicy, IConstruct, Output, TagManager, Tags } from '@aws-cdk/cdk'; import { EncryptionKeyAlias } from './alias'; import { CfnKey } from './kms.generated'; @@ -68,7 +68,7 @@ export abstract class EncryptionKeyBase extends Construct { public addToResourcePolicy(statement: PolicyStatement, allowNoOp = true) { if (!this.policy) { if (allowNoOp) { return; } - throw new Error(`Unable to add statement to IAM resource policy for KMS key: ${JSON.stringify(resolve(this.keyArn))}`); + throw new Error(`Unable to add statement to IAM resource policy for KMS key: ${JSON.stringify(this.node.resolve(this.keyArn))}`); } this.policy.addStatement(statement); @@ -106,6 +106,11 @@ export interface EncryptionKeyProps { * administer the key will be created. */ policy?: PolicyDocument; + + /** + * The AWS resource tags to associate with the KMS key. + */ + tags?: Tags; } /** @@ -134,6 +139,11 @@ export class EncryptionKey extends EncryptionKeyBase { return new ImportedEncryptionKey(scope, id, props); } + /** + * Manage tags for this construct and children + */ + public readonly tags: TagManager; + public readonly keyArn: string; protected readonly policy?: PolicyDocument; @@ -147,11 +157,14 @@ export class EncryptionKey extends EncryptionKeyBase { this.allowAccountToAdmin(); } + this.tags = new TagManager(this, { initialTags: props.tags }); + const resource = new CfnKey(this, 'Resource', { description: props.description, enableKeyRotation: props.enableKeyRotation, enabled: props.enabled, - keyPolicy: this.policy + keyPolicy: this.policy, + tags: this.tags }); this.keyArn = resource.keyArn; diff --git a/packages/@aws-cdk/aws-kms/package.json b/packages/@aws-cdk/aws-kms/package.json index 8acfbfc402353..51e5aa7c6296b 100644 --- a/packages/@aws-cdk/aws-kms/package.json +++ b/packages/@aws-cdk/aws-kms/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-kms", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS KMS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "pkglint": "pkglint -f", "integ": "cdk-integ", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::KMS" @@ -53,22 +54,22 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kms/test/integ.key.ts b/packages/@aws-cdk/aws-kms/test/integ.key.ts index f682b1d19fe6e..370a696fbaab0 100644 --- a/packages/@aws-cdk/aws-kms/test/integ.key.ts +++ b/packages/@aws-cdk/aws-kms/test/integ.key.ts @@ -1,5 +1,5 @@ import { PolicyStatement } from '@aws-cdk/aws-iam'; -import { App, AwsAccountId, Stack } from '@aws-cdk/cdk'; +import { App, Stack } from '@aws-cdk/cdk'; import { EncryptionKey } from '../lib'; const app = new App(); @@ -11,7 +11,7 @@ const key = new EncryptionKey(stack, 'MyKey'); key.addToResourcePolicy(new PolicyStatement() .addAllResources() .addAction('kms:encrypt') - .addAwsPrincipal(new AwsAccountId().toString())); + .addAwsPrincipal(stack.accountId)); key.addAlias('alias/bar'); diff --git a/packages/@aws-cdk/aws-kms/test/test.key.ts b/packages/@aws-cdk/aws-kms/test/test.key.ts index 6ffa56c2806a6..155347aa7a225 100644 --- a/packages/@aws-cdk/aws-kms/test/test.key.ts +++ b/packages/@aws-cdk/aws-kms/test/test.key.ts @@ -143,7 +143,12 @@ export = { const key = new EncryptionKey(stack, 'MyKey', { enableKeyRotation: true, - enabled: false + enabled: false, + tags: { + tag1: 'value1', + tag2: 'value2', + tag3: '' + } }); const p = new PolicyStatement().addAllResources().addAction('kms:encrypt'); p.addAwsPrincipal('arn'); @@ -204,7 +209,21 @@ export = { } ], Version: "2012-10-17" - } + }, + Tags: [ + { + Key: "tag1", + Value: "value1" + }, + { + Key: "tag2", + Value: "value2" + }, + { + Key: "tag3", + Value: "" + } + ] }, DeletionPolicy: "Retain" } diff --git a/packages/@aws-cdk/aws-lambda-event-sources/package.json b/packages/@aws-cdk/aws-lambda-event-sources/package.json index ad9603dd81275..ac0ccf6635220 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/package.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-lambda-event-sources", - "version": "0.21.0", + "version": "0.22.0", "description": "Event sources for AWS Lambda", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -50,27 +50,27 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-lambda": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/aws-sns": "^0.21.0", - "@aws-cdk/aws-sqs": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-lambda": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/aws-sns": "^0.22.0", + "@aws-cdk/aws-sqs": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-lambda": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/aws-sns": "^0.21.0", - "@aws-cdk/aws-sqs": "^0.21.0" + "@aws-cdk/aws-lambda": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/aws-sns": "^0.22.0", + "@aws-cdk/aws-sqs": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/lib/alias.ts b/packages/@aws-cdk/aws-lambda/lib/alias.ts index afe95955754cd..0f54a1feada37 100644 --- a/packages/@aws-cdk/aws-lambda/lib/alias.ts +++ b/packages/@aws-cdk/aws-lambda/lib/alias.ts @@ -71,7 +71,7 @@ export class Alias extends FunctionBase { /** * Role associated with this alias */ - public readonly role?: iam.Role | undefined; + public readonly role?: iam.IRole | undefined; protected readonly canCreatePermissions: boolean = true; // Not used anyway diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-ref.ts b/packages/@aws-cdk/aws-lambda/lib/lambda-ref.ts index ae954a9db205c..8c241d5428af6 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-ref.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda-ref.ts @@ -33,7 +33,7 @@ export interface IFunction extends cdk.IConstruct, events.IEventRuleTarget, logs /** * The IAM role associated with this function. */ - readonly role?: iam.Role; + readonly role?: iam.IRole; /** * Whether or not this Lambda function was bound to a VPC @@ -123,7 +123,7 @@ export interface FunctionImportProps { * * If the role is not specified, any role-related operations will no-op. */ - role?: iam.Role; + role?: iam.IRole; /** * Id of the securityGroup for this Lambda, if in a VPC. @@ -149,7 +149,7 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { /** * The IAM role associated with this function. */ - public abstract readonly role?: iam.Role; + public abstract readonly role?: iam.IRole; /** * Whether the addPermission() call adds any permissions @@ -327,12 +327,13 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { const arn = sourceLogGroup.logGroupArn; if (this.logSubscriptionDestinationPolicyAddedFor.indexOf(arn) === -1) { + const stack = cdk.Stack.find(this); // NOTE: the use of {AWS::Region} limits this to the same region, which shouldn't really be an issue, // since the Lambda must be in the same region as the SubscriptionFilter anyway. // // (Wildcards in principals are unfortunately not supported. this.addPermission('InvokedByCloudWatchLogs', { - principal: new iam.ServicePrincipal(`logs.${new cdk.AwsRegion()}.amazonaws.com`), + principal: new iam.ServicePrincipal(`logs.${stack.region}.amazonaws.com`), sourceArn: arn }); this.logSubscriptionDestinationPolicyAddedFor.push(arn); @@ -351,9 +352,10 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { */ public asBucketNotificationDestination(bucketArn: string, bucketId: string): s3n.BucketNotificationDestinationProps { const permissionId = `AllowBucketNotificationsFrom${bucketId}`; + const stack = cdk.Stack.find(this); if (!this.node.tryFindChild(permissionId)) { this.addPermission(permissionId, { - sourceAccount: new cdk.AwsAccountId().toString(), + sourceAccount: stack.accountId, principal: new iam.ServicePrincipal('s3.amazonaws.com'), sourceArn: bucketArn, }); @@ -414,7 +416,7 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { return (principal as iam.ServicePrincipal).service; } - throw new Error(`Invalid principal type for Lambda permission statement: ${JSON.stringify(cdk.resolve(principal))}. ` + + throw new Error(`Invalid principal type for Lambda permission statement: ${JSON.stringify(this.node.resolve(principal))}. ` + 'Supported: AccountPrincipal, ServicePrincipal'); } } diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda.ts b/packages/@aws-cdk/aws-lambda/lib/lambda.ts index 22536616043b9..32042863c4d43 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda.ts @@ -111,7 +111,7 @@ export interface FunctionProps { * @default a unique role will be generated for this lambda function. * Both supplied and generated roles can always be changed by calling `addToRolePolicy`. */ - role?: iam.Role; + role?: iam.IRole; /** * VPC network to place Lambda network interfaces @@ -284,7 +284,7 @@ export class Function extends FunctionBase { /** * Execution role associated with this function */ - public readonly role?: iam.Role; + public readonly role?: iam.IRole; /** * The runtime configured for this lambda. @@ -311,11 +311,11 @@ export class Function extends FunctionBase { const managedPolicyArns = new Array(); // the arn is in the form of - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - managedPolicyArns.push(new iam.AwsManagedPolicy("service-role/AWSLambdaBasicExecutionRole").policyArn); + managedPolicyArns.push(new iam.AwsManagedPolicy("service-role/AWSLambdaBasicExecutionRole", this).policyArn); if (props.vpc) { // Policy that will have ENI creation permissions - managedPolicyArns.push(new iam.AwsManagedPolicy("service-role/AWSLambdaVPCAccessExecutionRole").policyArn); + managedPolicyArns.push(new iam.AwsManagedPolicy("service-role/AWSLambdaVPCAccessExecutionRole", this).policyArn); } this.role = props.role || new iam.Role(this, 'ServiceRole', { @@ -493,7 +493,7 @@ export class Function extends FunctionBase { export class ImportedFunction extends FunctionBase { public readonly functionName: string; public readonly functionArn: string; - public readonly role?: iam.Role; + public readonly role?: iam.IRole; protected readonly canCreatePermissions = false; diff --git a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts index ff65d1f66565c..d5daeecca091b 100644 --- a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts @@ -37,7 +37,7 @@ export interface SingletonFunctionProps extends FunctionProps { export class SingletonFunction extends FunctionBase { public readonly functionName: string; public readonly functionArn: string; - public readonly role?: iam.Role | undefined; + public readonly role?: iam.IRole | undefined; protected readonly canCreatePermissions: boolean; private lambdaFunction: IFunction; diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index 74b6542f1bd15..4057ad8126434 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-lambda", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS Lambda", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Lambda" @@ -56,43 +57,43 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/assets": "^0.21.0", - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-logs": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/aws-s3-notifications": "^0.21.0", - "@aws-cdk/aws-sqs": "^0.21.0", - "@aws-cdk/aws-stepfunctions": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0", - "@aws-cdk/cx-api": "^0.21.0" + "@aws-cdk/assets": "^0.22.0", + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-logs": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/aws-s3-notifications": "^0.22.0", + "@aws-cdk/aws-sqs": "^0.22.0", + "@aws-cdk/aws-stepfunctions": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0", + "@aws-cdk/cx-api": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/assets": "^0.21.0", - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-logs": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/aws-s3-notifications": "^0.21.0", - "@aws-cdk/aws-sqs": "^0.21.0", - "@aws-cdk/aws-stepfunctions": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/assets": "^0.22.0", + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-logs": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/aws-s3-notifications": "^0.22.0", + "@aws-cdk/aws-sqs": "^0.22.0", + "@aws-cdk/aws-stepfunctions": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/test.alias.ts b/packages/@aws-cdk/aws-lambda/test/test.alias.ts index 3a0316c6a8450..cd2b30baeeaa9 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.alias.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.alias.ts @@ -1,6 +1,6 @@ import { beASupersetOfTemplate, expect, haveResource } from '@aws-cdk/assert'; import { AccountPrincipal } from '@aws-cdk/aws-iam'; -import { resolve, Stack } from '@aws-cdk/cdk'; +import { Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import lambda = require('../lib'); @@ -31,7 +31,7 @@ export = { Type: "AWS::Lambda::Alias", Properties: { FunctionName: { Ref: "MyLambdaCCE802FB" }, - FunctionVersion: resolve(version.functionVersion), + FunctionVersion: stack.node.resolve(version.functionVersion), Name: "prod" } } @@ -59,11 +59,11 @@ export = { }); expect(stack).to(haveResource('AWS::Lambda::Alias', { - FunctionVersion: resolve(version1.functionVersion), + FunctionVersion: stack.node.resolve(version1.functionVersion), RoutingConfig: { AdditionalVersionWeights: [ { - FunctionVersion: resolve(version2.functionVersion), + FunctionVersion: stack.node.resolve(version2.functionVersion), FunctionWeight: 0.1 } ] @@ -123,7 +123,7 @@ export = { // THEN expect(stack).to(haveResource('AWS::Lambda::Permission', { - FunctionName: resolve(fn.functionName), + FunctionName: stack.node.resolve(fn.functionName), Principal: "123456" })); diff --git a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts index e1f8e4526013b..c74dde7129541 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts @@ -1,4 +1,4 @@ -import { countResources, expect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import { countResources, expect, haveResource, MatchStyle, ResourcePart } from '@aws-cdk/assert'; import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import sqs = require('@aws-cdk/aws-sqs'); @@ -118,7 +118,7 @@ export = { fn.addPermission('S3Permission', { action: 'lambda:*', principal: new iam.ServicePrincipal('s3.amazonaws.com'), - sourceAccount: new cdk.AwsAccountId().toString(), + sourceAccount: stack.accountId, sourceArn: 'arn:aws:s3:::my_bucket' }); @@ -621,9 +621,7 @@ export = { 'default function with SQS DLQ when client provides Queue to be used as DLQ'(test: Test) { const stack = new cdk.Stack(); - const dlqStack = new cdk.Stack(); - - const dlQueue = new sqs.Queue(dlqStack, 'DeadLetterQueue', { + const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { queueName: 'MyLambda_DLQ', retentionPeriodSec: 1209600 }); @@ -725,16 +723,14 @@ export = { } } } - ); + , MatchStyle.SUPERSET); test.done(); }, 'default function with SQS DLQ when client provides Queue to be used as DLQ and deadLetterQueueEnabled set to true'(test: Test) { const stack = new cdk.Stack(); - const dlqStack = new cdk.Stack(); - - const dlQueue = new sqs.Queue(dlqStack, 'DeadLetterQueue', { + const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { queueName: 'MyLambda_DLQ', retentionPeriodSec: 1209600 }); @@ -837,16 +833,14 @@ export = { } } } - ); + , MatchStyle.SUPERSET); test.done(); }, 'error when default function with SQS DLQ when client provides Queue to be used as DLQ and deadLetterQueueEnabled set to false'(test: Test) { const stack = new cdk.Stack(); - const dlqStack = new cdk.Stack(); - - const dlQueue = new sqs.Queue(dlqStack, 'DeadLetterQueue', { + const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { queueName: 'MyLambda_DLQ', retentionPeriodSec: 1209600 }); diff --git a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts index 881f6e0769da0..dccec000fa360 100644 --- a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts +++ b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts @@ -60,12 +60,12 @@ export class CrossAccountDestination extends cdk.Construct implements ILogSubscr super(scope, id); // In the underlying model, the name is not optional, but we make it so anyway. - const destinationName = props.destinationName || new cdk.Token(() => this.generateUniqueName()); + const destinationName = props.destinationName || new cdk.Token(() => this.generateUniqueName()).toString(); this.resource = new CfnDestination(this, 'Resource', { destinationName, // Must be stringified policy - destinationPolicy: new cdk.Token(() => this.stringifiedPolicyDocument()), + destinationPolicy: this.lazyStringifiedPolicyDocument(), roleArn: props.role.roleArn, targetArn: props.targetArn }); @@ -94,7 +94,7 @@ export class CrossAccountDestination extends cdk.Construct implements ILogSubscr /** * Return a stringified JSON version of the PolicyDocument */ - private stringifiedPolicyDocument() { - return this.policyDocument.isEmpty ? '' : cdk.CloudFormationJSON.stringify(cdk.resolve(this.policyDocument)); + private lazyStringifiedPolicyDocument(): string { + return new cdk.Token(() => this.policyDocument.isEmpty ? '' : this.node.stringifyJson(this.policyDocument)).toString(); } } diff --git a/packages/@aws-cdk/aws-logs/lib/log-group.ts b/packages/@aws-cdk/aws-logs/lib/log-group.ts index dec077a84d1b3..4f548fd295bdc 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-group.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-group.ts @@ -295,7 +295,7 @@ class ImportedLogGroup extends LogGroupBase { super(scope, id); this.logGroupArn = props.logGroupArn; - this.logGroupName = cdk.ArnUtils.resourceNameComponent(props.logGroupArn, ':'); + this.logGroupName = cdk.Stack.find(this).parseArn(props.logGroupArn, ':').resourceName!; } /** diff --git a/packages/@aws-cdk/aws-logs/package.json b/packages/@aws-cdk/aws-logs/package.json index 12527c729505d..a89af79aad145 100644 --- a/packages/@aws-cdk/aws-logs/package.json +++ b/packages/@aws-cdk/aws-logs/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-logs", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::Logs", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Logs" @@ -53,24 +54,24 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-neptune/package.json b/packages/@aws-cdk/aws-neptune/package.json index e1e13650e9dd6..44b5f245f09a2 100644 --- a/packages/@aws-cdk/aws-neptune/package.json +++ b/packages/@aws-cdk/aws-neptune/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-neptune", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::Neptune", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -36,7 +36,8 @@ "pkglint": "pkglint -f", "test": "cdk-test", "watch": "cdk-watch", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Neptune" @@ -54,18 +55,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-opsworks/package.json b/packages/@aws-cdk/aws-opsworks/package.json index 5d363912958ab..affaf221a2088 100644 --- a/packages/@aws-cdk/aws-opsworks/package.json +++ b/packages/@aws-cdk/aws-opsworks/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-opsworks", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::OpsWorks", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::OpsWorks" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-quickstarts/package.json b/packages/@aws-cdk/aws-quickstarts/package.json index 3f6a55a076514..30aeee13a2db4 100644 --- a/packages/@aws-cdk/aws-quickstarts/package.json +++ b/packages/@aws-cdk/aws-quickstarts/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-quickstarts", - "version": "0.21.0", + "version": "0.22.0", "description": "AWS Quickstarts for the CDK", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -49,22 +49,22 @@ }, "license": "Apache-2.0", "devDependencies": { - "cdk-build-tools": "^0.21.0", - "pkglint": "^0.21.0" + "cdk-build-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-cloudformation": "^0.21.0", - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-rds": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-cloudformation": "^0.22.0", + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-rds": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts b/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts index 59d0c1afb2441..10a8e89c06524 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts @@ -105,7 +105,7 @@ export class ClusterParameterGroup extends cdk.Construct implements IClusterPara /** * Validate this construct */ - public validate(): string[] { + protected validate(): string[] { if (Object.keys(this.parameters).length === 0) { return ['At least one parameter required, call setParameter().']; } diff --git a/packages/@aws-cdk/aws-rds/package.json b/packages/@aws-cdk/aws-rds/package.json index 049f3c1953472..936f35715d7e5 100644 --- a/packages/@aws-cdk/aws-rds/package.json +++ b/packages/@aws-cdk/aws-rds/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-rds", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS RDS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::RDS" @@ -53,24 +54,24 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-kms": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-kms": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster.ts b/packages/@aws-cdk/aws-rds/test/test.cluster.ts index 841a6b2776d04..9ec072c6aa298 100644 --- a/packages/@aws-cdk/aws-rds/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.cluster.ts @@ -136,8 +136,8 @@ export = { const imported = ClusterParameterGroup.import(stack, 'ImportParams', exported); // THEN - test.deepEqual(cdk.resolve(exported), { parameterGroupName: { 'Fn::ImportValue': 'ParamsParameterGroupNameA6B808D7' } }); - test.deepEqual(cdk.resolve(imported.parameterGroupName), { 'Fn::ImportValue': 'ParamsParameterGroupNameA6B808D7' }); + test.deepEqual(stack.node.resolve(exported), { parameterGroupName: { 'Fn::ImportValue': 'ParamsParameterGroupNameA6B808D7' } }); + test.deepEqual(stack.node.resolve(imported.parameterGroupName), { 'Fn::ImportValue': 'ParamsParameterGroupNameA6B808D7' }); test.done(); } }; diff --git a/packages/@aws-cdk/aws-redshift/package.json b/packages/@aws-cdk/aws-redshift/package.json index 6920fbd937764..cdbe8a3a60b6e 100644 --- a/packages/@aws-cdk/aws-redshift/package.json +++ b/packages/@aws-cdk/aws-redshift/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-redshift", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::Redshift", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Redshift" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53/README.md b/packages/@aws-cdk/aws-route53/README.md index 6476432160805..51262f30e865f 100644 --- a/packages/@aws-cdk/aws-route53/README.md +++ b/packages/@aws-cdk/aws-route53/README.md @@ -1,4 +1,4 @@ -## AWS Route53 Constuct Library +## AWS Route53 Construct Library To add a public hosted zone: @@ -34,7 +34,7 @@ To add a TXT record to your zone: ```ts import route53 = require('@aws-cdk/aws-route53'); -new route53.TXTRecord(zone, 'TXTRecord', { +new route53.TxtRecord(zone, 'TXTRecord', { recordName: '_foo', // If the name ends with a ".", it will be used as-is; // if it ends with a "." followed by the zone name, a trailing "." will be added automatically; // otherwise, a ".", the zone name, and a trailing "." will be added automatically. diff --git a/packages/@aws-cdk/aws-route53/lib/hosted-zone-ref.ts b/packages/@aws-cdk/aws-route53/lib/hosted-zone-ref.ts index a007f97db19d2..33a7b0e295fb9 100644 --- a/packages/@aws-cdk/aws-route53/lib/hosted-zone-ref.ts +++ b/packages/@aws-cdk/aws-route53/lib/hosted-zone-ref.ts @@ -5,7 +5,7 @@ import cdk = require('@aws-cdk/cdk'); */ export interface IHostedZone extends cdk.IConstruct { /** - * ID of this hosted zone + * ID of this hosted zone, such as "Z23ABC4XYZL05B" */ readonly hostedZoneId: string; @@ -14,6 +14,14 @@ export interface IHostedZone extends cdk.IConstruct { */ readonly zoneName: string; + /** + * Returns the set of name servers for the specific hosted zone. For example: + * ns1.example.com. + * + * This attribute will be undefined for private hosted zones or hosted zones imported from another stack. + */ + readonly hostedZoneNameServers?: string[]; + /** * Export the hosted zone */ diff --git a/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts b/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts index a4b7c1e865ef2..4b6c621845fa5 100644 --- a/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts +++ b/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts @@ -1,89 +1,125 @@ import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); import { HostedZoneImportProps, IHostedZone } from './hosted-zone-ref'; -import { CfnHostedZone, HostedZoneNameServers } from './route53.generated'; +import { CfnHostedZone } from './route53.generated'; import { validateZoneName } from './util'; -/** - * Properties of a new hosted zone - */ -export interface PublicHostedZoneProps { +export interface CommonHostedZoneProps { /** - * The fully qualified domain name for the hosted zone + * The name of the domain. For resource record types that include a domain + * name, specify a fully qualified domain name. */ zoneName: string; /** * Any comments that you want to include about the hosted zone. * - * @default no comment + * @default none */ comment?: string; /** * The Amazon Resource Name (ARN) for the log group that you want Amazon Route 53 to send query logs to. * - * @default no DNS query logging + * @default disabled */ queryLogsLogGroupArn?: string; } -export abstract class HostedZone extends cdk.Construct implements IHostedZone { - public static import(scope: cdk.Construct, id: string, props: HostedZoneImportProps): IHostedZone { - return new ImportedHostedZone(scope, id, props); - } - - public abstract readonly hostedZoneId: string; - public abstract readonly zoneName: string; - - public export(): HostedZoneImportProps { - return { - hostedZoneId: new cdk.Output(this, 'HostedZoneId', { value: this.hostedZoneId }).makeImportValue().toString(), - zoneName: this.zoneName, - }; - } -} - /** - * Create a Route53 public hosted zone. + * Properties of a new hosted zone */ -export class PublicHostedZone extends HostedZone { +export interface HostedZoneProps extends CommonHostedZoneProps { /** - * Identifier of this hosted zone + * A VPC that you want to associate with this hosted zone. When you specify + * this property, a private hosted zone will be created. + * + * You can associate additional VPCs to this private zone using `addVpc(vpc)`. + * + * @default public (no VPCs associated) */ - public readonly hostedZoneId: string; + vpcs?: ec2.IVpcNetwork[]; +} +export class HostedZone extends cdk.Construct implements IHostedZone { /** - * Fully qualified domain name for the hosted zone + * Imports a hosted zone from another stack. */ + public static import(scope: cdk.Construct, id: string, props: HostedZoneImportProps): IHostedZone { + return new ImportedHostedZone(scope, id, props); + } + + public readonly hostedZoneId: string; public readonly zoneName: string; + public readonly hostedZoneNameServers?: string[]; /** - * Nameservers for this public hosted zone + * VPCs to which this hosted zone will be added */ - public readonly nameServers: HostedZoneNameServers; + protected readonly vpcs = new Array(); - constructor(scope: cdk.Construct, id: string, props: PublicHostedZoneProps) { + constructor(scope: cdk.Construct, id: string, props: HostedZoneProps) { super(scope, id); validateZoneName(props.zoneName); const hostedZone = new CfnHostedZone(this, 'Resource', { - ...determineHostedZoneProps(props) + name: props.zoneName + '.', + hostedZoneConfig: props.comment ? { comment: props.comment } : undefined, + queryLoggingConfig: props.queryLogsLogGroupArn ? { cloudWatchLogsLogGroupArn: props.queryLogsLogGroupArn } : undefined, + vpcs: new cdk.Token(() => this.vpcs.length === 0 ? undefined : this.vpcs) }); this.hostedZoneId = hostedZone.ref; - this.nameServers = hostedZone.hostedZoneNameServers; + this.hostedZoneNameServers = hostedZone.hostedZoneNameServers; this.zoneName = props.zoneName; + + for (const vpc of props.vpcs || []) { + this.addVpc(vpc); + } + } + + public export(): HostedZoneImportProps { + return { + hostedZoneId: new cdk.Output(this, 'HostedZoneId', { value: this.hostedZoneId }).makeImportValue(), + zoneName: this.zoneName, + }; + } + + /** + * Add another VPC to this private hosted zone. + * + * @param vpc the other VPC to add. + */ + public addVpc(vpc: ec2.IVpcNetwork) { + this.vpcs.push({ vpcId: vpc.vpcId, vpcRegion: vpc.vpcRegion }); } } +// tslint:disable-next-line:no-empty-interface +export interface PublicHostedZoneProps extends CommonHostedZoneProps { + +} + /** - * Properties for a private hosted zone. + * Create a Route53 public hosted zone. */ -export interface PrivateHostedZoneProps extends PublicHostedZoneProps { +export class PublicHostedZone extends HostedZone { + constructor(scope: cdk.Construct, id: string, props: PublicHostedZoneProps) { + super(scope, id, props); + } + + public addVpc(_vpc: ec2.IVpcNetwork) { + throw new Error('Cannot associate public hosted zones with a VPC'); + } +} + +export interface PrivateHostedZoneProps extends CommonHostedZoneProps { /** - * One VPC that you want to associate with this hosted zone. + * A VPC that you want to associate with this hosted zone. + * + * Private hosted zones must be associated with at least one VPC. You can + * associated additional VPCs using `addVpc(vpc)`. */ vpc: ec2.IVpcNetwork; } @@ -95,57 +131,11 @@ export interface PrivateHostedZoneProps extends PublicHostedZoneProps { * for the VPC you're configuring for private hosted zones. */ export class PrivateHostedZone extends HostedZone { - /** - * Identifier of this hosted zone - */ - public readonly hostedZoneId: string; - - /** - * Fully qualified domain name for the hosted zone - */ - public readonly zoneName: string; - - /** - * VPCs to which this hosted zone will be added - */ - private readonly vpcs: CfnHostedZone.VPCProperty[] = []; - constructor(scope: cdk.Construct, id: string, props: PrivateHostedZoneProps) { - super(scope, id); - - validateZoneName(props.zoneName); - - const hostedZone = new CfnHostedZone(this, 'Resource', { - vpcs: new cdk.Token(() => this.vpcs ? this.vpcs : undefined), - ...determineHostedZoneProps(props) - }); - - this.hostedZoneId = hostedZone.ref; - this.zoneName = props.zoneName; + super(scope, id, props); this.addVpc(props.vpc); } - - /** - * Add another VPC to this private hosted zone. - * - * @param vpc the other VPC to add. - */ - public addVpc(vpc: ec2.IVpcNetwork) { - this.vpcs.push(toVpcProperty(vpc)); - } -} - -function toVpcProperty(vpc: ec2.IVpcNetwork): CfnHostedZone.VPCProperty { - return { vpcId: vpc.vpcId, vpcRegion: new cdk.AwsRegion() }; -} - -function determineHostedZoneProps(props: PublicHostedZoneProps) { - const name = props.zoneName + '.'; - const hostedZoneConfig = props.comment ? { comment: props.comment } : undefined; - const queryLoggingConfig = props.queryLogsLogGroupArn ? { cloudWatchLogsLogGroupArn: props.queryLogsLogGroupArn } : undefined; - - return { name, hostedZoneConfig, queryLoggingConfig }; } /** @@ -153,7 +143,6 @@ function determineHostedZoneProps(props: PublicHostedZoneProps) { */ class ImportedHostedZone extends cdk.Construct implements IHostedZone { public readonly hostedZoneId: string; - public readonly zoneName: string; constructor(scope: cdk.Construct, name: string, private readonly props: HostedZoneImportProps) { diff --git a/packages/@aws-cdk/aws-route53/lib/records/cname.ts b/packages/@aws-cdk/aws-route53/lib/records/cname.ts new file mode 100644 index 0000000000000..e41bda2f515d8 --- /dev/null +++ b/packages/@aws-cdk/aws-route53/lib/records/cname.ts @@ -0,0 +1,47 @@ +import { Construct } from '@aws-cdk/cdk'; +import { IHostedZone } from '../hosted-zone-ref'; +import { CfnRecordSet } from '../route53.generated'; +import { determineFullyQualifiedDomainName } from './_util'; + +export interface CnameRecordProps { + /** + * The hosted zone in which to define the new TXT record. + */ + zone: IHostedZone; + + /** + * The domain name for this record set. + */ + recordName: string; + + /** + * The value for this record set. + */ + recordValue: string; + + /** + * The resource record cache time to live (TTL) in seconds. + * + * @default 1800 seconds + */ + ttl?: number; +} + +/** + * A DNS CNAME record + */ +export class CnameRecord extends Construct { + constructor(scope: Construct, id: string, props: CnameRecordProps) { + super(scope, id); + + const ttl = props.ttl === undefined ? 1800 : props.ttl; + + new CfnRecordSet(this, 'Resource', { + hostedZoneId: props.zone.hostedZoneId, + name: determineFullyQualifiedDomainName(props.recordName, props.zone), + type: 'CNAME', + resourceRecords: [ props.recordValue ], + ttl: ttl.toString(), + }); + } +} diff --git a/packages/@aws-cdk/aws-route53/lib/records/index.ts b/packages/@aws-cdk/aws-route53/lib/records/index.ts index 655c84bf4ca70..1643af3bdaadb 100644 --- a/packages/@aws-cdk/aws-route53/lib/records/index.ts +++ b/packages/@aws-cdk/aws-route53/lib/records/index.ts @@ -1,3 +1,4 @@ export * from './alias'; export * from './txt'; +export * from './cname'; export * from './zone-delegation'; diff --git a/packages/@aws-cdk/aws-route53/lib/records/txt.ts b/packages/@aws-cdk/aws-route53/lib/records/txt.ts index b227e277e3dde..a4cafcbcea50b 100644 --- a/packages/@aws-cdk/aws-route53/lib/records/txt.ts +++ b/packages/@aws-cdk/aws-route53/lib/records/txt.ts @@ -3,19 +3,35 @@ import { IHostedZone } from '../hosted-zone-ref'; import { CfnRecordSet } from '../route53.generated'; import { determineFullyQualifiedDomainName } from './_util'; -export interface TXTRecordProps { +export interface TxtRecordProps { + /** + * The hosted zone in which to define the new TXT record. + */ zone: IHostedZone; + + /** + * The domain name for this record set. + */ recordName: string; + + /** + * The value for this record set. + */ recordValue: string; - /** @default 1800 seconds */ + + /** + * The resource record cache time to live (TTL) in seconds. + * + * @default 1800 seconds + */ ttl?: number; } /** * A DNS TXT record */ -export class TXTRecord extends Construct { - constructor(scope: Construct, id: string, props: TXTRecordProps) { +export class TxtRecord extends Construct { + constructor(scope: Construct, id: string, props: TxtRecordProps) { super(scope, id); // JSON.stringify conveniently wraps strings in " and escapes ". diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 34482a57ed11a..939a34ce98dba 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-route53", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS Route53", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Route53" @@ -53,31 +54,25 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", + "@aws-cdk/assert": "^0.22.0", "aws-sdk": "^2.259.1", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/aws-logs": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0", - "@aws-cdk/cx-api": "^0.21.0" + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/aws-logs": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0", + "@aws-cdk/cx-api": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" - }, - "awslint": { - "exclude": [ - "resource-attribute:@aws-cdk/aws-route53.IHostedZone.hostedZoneNameServers", - "resource-props:@aws-cdk/aws-route53.HostedZoneProps" - ] } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53/test/integ.route53.expected.json b/packages/@aws-cdk/aws-route53/test/integ.route53.expected.json index a35b1b55fcfd2..ee65a6bd2ae21 100644 --- a/packages/@aws-cdk/aws-route53/test/integ.route53.expected.json +++ b/packages/@aws-cdk/aws-route53/test/integ.route53.expected.json @@ -67,9 +67,6 @@ }, "VPCPublicSubnet1DefaultRoute91CEF279": { "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], "Properties": { "RouteTableId": { "Ref": "VPCPublicSubnet1RouteTableFEE4B781" @@ -78,7 +75,10 @@ "GatewayId": { "Ref": "VPCIGWB7E252D3" } - } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] }, "VPCPublicSubnet1EIP6AD938E8": { "Type": "AWS::EC2::EIP", @@ -158,9 +158,6 @@ }, "VPCPublicSubnet2DefaultRouteB7481BBA": { "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], "Properties": { "RouteTableId": { "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" @@ -169,7 +166,10 @@ "GatewayId": { "Ref": "VPCIGWB7E252D3" } - } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] }, "VPCPublicSubnet2EIP4947BC00": { "Type": "AWS::EC2::EIP", @@ -249,9 +249,6 @@ }, "VPCPublicSubnet3DefaultRouteA0D29D46": { "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], "Properties": { "RouteTableId": { "Ref": "VPCPublicSubnet3RouteTable98AE0E14" @@ -260,7 +257,10 @@ "GatewayId": { "Ref": "VPCIGWB7E252D3" } - } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] }, "VPCPublicSubnet3EIPAD4BC883": { "Type": "AWS::EC2::EIP", @@ -531,6 +531,20 @@ "Properties": { "Name": "cdk.test." } + }, + "CNAMEC70A2D52": { + "Type": "AWS::Route53::RecordSet", + "Properties": { + "Name": "www.cdk.local.", + "Type": "CNAME", + "HostedZoneId": { + "Ref": "PrivateZone27242E85" + }, + "ResourceRecords": [ + "server" + ], + "TTL": "1800" + } } }, "Outputs": { @@ -551,4 +565,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53/test/integ.route53.ts b/packages/@aws-cdk/aws-route53/test/integ.route53.ts index f276d80e26aaa..99b204971f5bf 100644 --- a/packages/@aws-cdk/aws-route53/test/integ.route53.ts +++ b/packages/@aws-cdk/aws-route53/test/integ.route53.ts @@ -1,6 +1,6 @@ import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); -import { PrivateHostedZone, PublicHostedZone, TXTRecord } from '../lib'; +import { CnameRecord, PrivateHostedZone, PublicHostedZone, TxtRecord } from '../lib'; const app = new cdk.App(); @@ -16,13 +16,19 @@ const publicZone = new PublicHostedZone(stack, 'PublicZone', { zoneName: 'cdk.test' }); -new TXTRecord(privateZone, 'TXT', { +new TxtRecord(privateZone, 'TXT', { zone: privateZone, recordName: '_foo', recordValue: 'Bar!', ttl: 60 }); +new CnameRecord(stack, 'CNAME', { + zone: privateZone, + recordName: 'www', + recordValue: 'server' +}); + new cdk.Output(stack, 'PrivateZoneId', { value: privateZone.hostedZoneId }); new cdk.Output(stack, 'PublicZoneId', { value: publicZone.hostedZoneId }); diff --git a/packages/@aws-cdk/aws-route53/test/test.cname-record.ts b/packages/@aws-cdk/aws-route53/test/test.cname-record.ts new file mode 100644 index 0000000000000..92d1b84b478fe --- /dev/null +++ b/packages/@aws-cdk/aws-route53/test/test.cname-record.ts @@ -0,0 +1,67 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import { Stack } from '@aws-cdk/cdk'; +import { Test } from 'nodeunit'; +import route53 = require('../lib'); + +export = { + 'with default ttl'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const zone = new route53.HostedZone(stack, 'HostedZone', { + zoneName: 'myzone' + }); + + new route53.CnameRecord(stack, 'MyCname', { + zone, + recordName: 'www', + recordValue: 'zzz', + }); + + // THEN + expect(stack).to(haveResource('AWS::Route53::RecordSet', { + Name: "www.myzone.", + Type: "CNAME", + HostedZoneId: { + Ref: "HostedZoneDB99F866" + }, + ResourceRecords: [ + "zzz" + ], + TTL: "1800" + })); + test.done(); + }, + + 'with custom ttl'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const zone = new route53.HostedZone(stack, 'HostedZone', { + zoneName: 'myzone' + }); + + new route53.CnameRecord(stack, 'MyCname', { + zone, + recordName: 'aa', + recordValue: 'bbb', + ttl: 6077, + }); + + // THEN + expect(stack).to(haveResource('AWS::Route53::RecordSet', { + Name: "aa.myzone.", + Type: "CNAME", + HostedZoneId: { + Ref: "HostedZoneDB99F866" + }, + ResourceRecords: [ + "bbb" + ], + TTL: "6077" + })); + test.done(); + } +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts b/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts index c22c49cbedd79..e7dd3d2d09b06 100644 --- a/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts +++ b/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts @@ -33,7 +33,7 @@ export = { // WHEN const provider = new HostedZoneProvider(stack, filter); - const zoneProps = cdk.resolve(provider.findHostedZone()); + const zoneProps = stack.node.resolve(provider.findHostedZone()); const zoneRef = provider.findAndImport(stack, 'MyZoneProvider'); // THEN diff --git a/packages/@aws-cdk/aws-route53/test/test.route53.ts b/packages/@aws-cdk/aws-route53/test/test.route53.ts index 3a18465c1ff03..27a26cc19ce6e 100644 --- a/packages/@aws-cdk/aws-route53/test/test.route53.ts +++ b/packages/@aws-cdk/aws-route53/test/test.route53.ts @@ -2,7 +2,7 @@ import { beASupersetOfTemplate, exactlyMatchTemplate, expect, haveResource } fro import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; -import { HostedZone, PrivateHostedZone, PublicHostedZone, TXTRecord } from '../lib'; +import { HostedZone, PrivateHostedZone, PublicHostedZone, TxtRecord } from '../lib'; export = { 'default properties': { @@ -33,7 +33,7 @@ export = { Name: "test.private.", VPCs: [{ VPCId: { Ref: 'VPCB9E5F0B4' }, - VPCRegion: { Ref: 'AWS::Region' } + VPCRegion: 'bermuda-triangle' }] } } @@ -55,11 +55,11 @@ export = { Name: "test.private.", VPCs: [{ VPCId: { Ref: 'VPC17DE2CF87' }, - VPCRegion: { Ref: 'AWS::Region' } + VPCRegion: 'bermuda-triangle' }, { VPCId: { Ref: 'VPC2C1F0E711' }, - VPCRegion: { Ref: 'AWS::Region' } + VPCRegion: 'bermuda-triangle' }] } } @@ -79,7 +79,7 @@ export = { const zoneRef = zone.export(); const importedZone = HostedZone.import(stack2, 'Imported', zoneRef); - new TXTRecord(importedZone as any, 'Record', { + new TxtRecord(importedZone as any, 'Record', { zone: importedZone, recordName: 'lookHere', recordValue: 'SeeThere' @@ -109,6 +109,88 @@ export = { Type: "TXT" })); + test.done(); + }, + + 'adds period to name if not provided'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new HostedZone(stack, 'MyHostedZone', { + zoneName: 'zonename' + }); + + // THEN + expect(stack).to(haveResource('AWS::Route53::HostedZone', { + Name: 'zonename.' + })); + test.done(); + }, + + 'fails if zone name ends with a trailing dot'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // THEN + test.throws(() => new HostedZone(stack, 'MyHostedZone', { zoneName: 'zonename.' }), /zone name must not end with a trailing dot/); + test.done(); + }, + + 'a hosted zone can be assiciated with a VPC either upon creation or using "addVpc"'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc1 = new ec2.VpcNetwork(stack, 'VPC1'); + const vpc2 = new ec2.VpcNetwork(stack, 'VPC2'); + const vpc3 = new ec2.VpcNetwork(stack, 'VPC3'); + + // WHEN + const zone = new HostedZone(stack, 'MyHostedZone', { + zoneName: 'zonename', + vpcs: [ vpc1, vpc2 ] + }); + zone.addVpc(vpc3); + + // THEN + expect(stack).to(haveResource('AWS::Route53::HostedZone', { + VPCs: [ + { + VPCId: { + Ref: "VPC17DE2CF87" + }, + VPCRegion: { + Ref: "AWS::Region" + } + }, + { + VPCId: { + Ref: "VPC2C1F0E711" + }, + VPCRegion: { + Ref: "AWS::Region" + } + }, + { + VPCId: { + Ref: "VPC3CB5FCDA8" + }, + VPCRegion: { + Ref: "AWS::Region" + } + } + ] + })); + test.done(); + }, + + 'public zone cannot be associated with a vpc (runtime error)'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const zone = new PublicHostedZone(stack, 'MyHostedZone', { zoneName: 'zonename' }); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + + // THEN + test.throws(() => zone.addVpc(vpc), /Cannot associate public hosted zones with a VPC/); test.done(); } }; diff --git a/packages/@aws-cdk/aws-route53/test/test.txt-record.ts b/packages/@aws-cdk/aws-route53/test/test.txt-record.ts index 47222bb6406b7..c2f797a037d4b 100644 --- a/packages/@aws-cdk/aws-route53/test/test.txt-record.ts +++ b/packages/@aws-cdk/aws-route53/test/test.txt-record.ts @@ -1,14 +1,14 @@ import { exactlyMatchTemplate, expect } from '@aws-cdk/assert'; import { App, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import { PublicHostedZone, TXTRecord } from '../lib'; +import { PublicHostedZone, TxtRecord } from '../lib'; export = { 'TXT records': { TXT(test: Test) { const app = new TestApp(); const zone = new PublicHostedZone(app.stack, 'HostedZone', { zoneName: 'test.public' }); - new TXTRecord(zone, 'TXT', { zone, recordName: '_foo', recordValue: 'Bar!' }); + new TxtRecord(zone, 'TXT', { zone, recordName: '_foo', recordValue: 'Bar!' }); expect(app.synthesizeTemplate()).to(exactlyMatchTemplate({ Resources: { HostedZoneDB99F866: { diff --git a/packages/@aws-cdk/aws-route53resolver/package.json b/packages/@aws-cdk/aws-route53resolver/package.json index 5875e15290d93..a4c4993f30874 100644 --- a/packages/@aws-cdk/aws-route53resolver/package.json +++ b/packages/@aws-cdk/aws-route53resolver/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-route53resolver", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::Route53Resolver", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -36,7 +36,8 @@ "pkglint": "pkglint -f", "test": "cdk-test", "watch": "cdk-watch", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Route53Resolver" @@ -54,18 +55,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-deployment/package.json b/packages/@aws-cdk/aws-s3-deployment/package.json index 522737d367c28..da52a49ce0da4 100644 --- a/packages/@aws-cdk/aws-s3-deployment/package.json +++ b/packages/@aws-cdk/aws-s3-deployment/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-s3-deployment", - "version": "0.21.0", + "version": "0.22.0", "description": "Constructs for deploying contents to S3 buckets", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -68,25 +68,25 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/assets": "^0.21.0", - "@aws-cdk/aws-cloudformation": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-lambda": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/assets": "^0.22.0", + "@aws-cdk/aws-cloudformation": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-lambda": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-s3": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-s3": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-notifications/package.json b/packages/@aws-cdk/aws-s3-notifications/package.json index 694c48a1824c8..a7490d636eb65 100644 --- a/packages/@aws-cdk/aws-s3-notifications/package.json +++ b/packages/@aws-cdk/aws-s3-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-s3-notifications", - "version": "0.21.0", + "version": "0.22.0", "description": "Bucket Notifications API for AWS S3", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -50,17 +50,17 @@ }, "license": "Apache-2.0", "devDependencies": { - "cdk-build-tools": "^0.21.0", - "pkglint": "^0.21.0" + "cdk-build-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ 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 ec53b1ee88ea9..4e4f2e09c2bdb 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -180,7 +180,7 @@ export interface IBucket extends cdk.IConstruct { */ export interface BucketImportProps { /** - * The ARN fo the bucket. At least one of bucketArn or bucketName must be + * The ARN of the bucket. At least one of bucketArn or bucketName must be * defined in order to initialize a bucket ref. */ bucketArn?: string; @@ -199,6 +199,13 @@ export interface BucketImportProps { * @default Inferred from bucket name */ bucketDomainName?: string; + + /** + * The website URL of the bucket (if static web hosting is enabled). + * + * @default Inferred from bucket name + */ + bucketWebsiteUrl?: string; } /** @@ -310,7 +317,8 @@ export abstract class BucketBase extends cdk.Construct implements IBucket { * @returns an ObjectS3Url token */ public urlForObject(key?: string): string { - const components = [ `https://s3.${new cdk.AwsRegion()}.${new cdk.AwsURLSuffix()}/${this.bucketName}` ]; + const stack = cdk.Stack.find(this); + const components = [ `https://s3.${stack.region}.${stack.urlSuffix}/${this.bucketName}` ]; if (key) { // trim prepending '/' if (typeof key === 'string' && key.startsWith('/')) { @@ -570,6 +578,7 @@ export class Bucket extends BucketBase { public readonly bucketArn: string; public readonly bucketName: string; public readonly domainName: string; + public readonly bucketWebsiteUrl: string; public readonly dualstackDomainName: string; public readonly encryptionKey?: kms.IEncryptionKey; public policy?: BucketPolicy; @@ -598,6 +607,7 @@ export class Bucket extends BucketBase { this.bucketArn = resource.bucketArn; this.bucketName = resource.bucketName; this.domainName = resource.bucketDomainName; + this.bucketWebsiteUrl = resource.bucketWebsiteUrl; this.dualstackDomainName = resource.bucketDualStackDomainName; // Add all lifecycle rules @@ -620,6 +630,7 @@ export class Bucket extends BucketBase { bucketArn: new cdk.Output(this, 'BucketArn', { value: this.bucketArn }).makeImportValue().toString(), bucketName: new cdk.Output(this, 'BucketName', { value: this.bucketName }).makeImportValue().toString(), bucketDomainName: new cdk.Output(this, 'DomainName', { value: this.domainName }).makeImportValue().toString(), + bucketWebsiteUrl: new cdk.Output(this, 'WebsiteURL', { value: this.bucketWebsiteUrl }).makeImportValue().toString() }; } @@ -955,6 +966,7 @@ class ImportedBucket extends BucketBase { public readonly bucketArn: string; public readonly bucketName: string; public readonly domainName: string; + public readonly bucketWebsiteUrl: string; public readonly encryptionKey?: kms.EncryptionKey; public policy?: BucketPolicy; @@ -963,14 +975,15 @@ class ImportedBucket extends BucketBase { constructor(scope: cdk.Construct, id: string, private readonly props: BucketImportProps) { super(scope, id); - const bucketName = parseBucketName(props); + const bucketName = parseBucketName(this, props); if (!bucketName) { throw new Error('Bucket name is required'); } - this.bucketArn = parseBucketArn(props); + this.bucketArn = parseBucketArn(this, props); this.bucketName = bucketName; this.domainName = props.bucketDomainName || this.generateDomainName(); + this.bucketWebsiteUrl = props.bucketWebsiteUrl || this.generateBucketWebsiteUrl(); this.autoCreatePolicy = false; this.policy = undefined; } @@ -985,4 +998,8 @@ class ImportedBucket extends BucketBase { private generateDomainName() { return `${this.bucketName}.s3.amazonaws.com`; } + + private generateBucketWebsiteUrl() { + return `${this.bucketName}.s3-website-${new cdk.AwsRegion()}.amazonaws.com`; + } } 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 a3c04c426b26a..29ac33224c938 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 @@ -50,7 +50,7 @@ export class NotificationsResourceHandler extends cdk.Construct { const role = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), managedPolicyArns: [ - cdk.ArnUtils.fromComponents({ + cdk.Stack.find(this).formatArn({ service: 'iam', region: '', // no region for managed policy account: 'aws', // the account for a managed policy is 'aws' diff --git a/packages/@aws-cdk/aws-s3/lib/util.ts b/packages/@aws-cdk/aws-s3/lib/util.ts index a6c9861829a15..e241db77cd5a0 100644 --- a/packages/@aws-cdk/aws-s3/lib/util.ts +++ b/packages/@aws-cdk/aws-s3/lib/util.ts @@ -1,7 +1,7 @@ import cdk = require('@aws-cdk/cdk'); import { BucketImportProps } from './bucket'; -export function parseBucketArn(props: BucketImportProps): string { +export function parseBucketArn(construct: cdk.IConstruct, props: BucketImportProps): string { // if we have an explicit bucket ARN, use it. if (props.bucketArn) { @@ -9,7 +9,7 @@ export function parseBucketArn(props: BucketImportProps): string { } if (props.bucketName) { - return cdk.ArnUtils.fromComponents({ + return cdk.Stack.find(construct).formatArn({ // S3 Bucket names are globally unique in a partition, // and so their ARNs have empty region and account components region: '', @@ -22,7 +22,7 @@ export function parseBucketArn(props: BucketImportProps): string { throw new Error('Cannot determine bucket ARN. At least `bucketArn` or `bucketName` is needed'); } -export function parseBucketName(props: BucketImportProps): string | undefined { +export function parseBucketName(construct: cdk.IConstruct, props: BucketImportProps): string | undefined { // if we have an explicit bucket name, use it. if (props.bucketName) { @@ -32,9 +32,9 @@ export function parseBucketName(props: BucketImportProps): string | undefined { // if we have a string arn, we can extract the bucket name from it. if (props.bucketArn) { - const resolved = cdk.resolve(props.bucketArn); + const resolved = construct.node.resolve(props.bucketArn); if (typeof(resolved) === 'string') { - const components = cdk.ArnUtils.parse(resolved); + const components = cdk.Stack.find(construct).parseArn(resolved); if (components.service !== 's3') { throw new Error('Invalid ARN. Expecting "s3" service:' + resolved); } diff --git a/packages/@aws-cdk/aws-s3/package.json b/packages/@aws-cdk/aws-s3/package.json index 523508b3e7009..743b9d1686656 100644 --- a/packages/@aws-cdk/aws-s3/package.json +++ b/packages/@aws-cdk/aws-s3/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-s3", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS S3", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::S3" @@ -53,26 +54,26 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-kms": "^0.21.0", - "@aws-cdk/aws-s3-notifications": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-kms": "^0.22.0", + "@aws-cdk/aws-s3-notifications": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-kms": "^0.21.0", - "@aws-cdk/aws-s3-notifications": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-codepipeline-api": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-kms": "^0.22.0", + "@aws-cdk/aws-s3-notifications": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" @@ -86,4 +87,4 @@ "resource-interface:@aws-cdk/aws-s3.IBucketPolicy" ] } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3/test/test.bucket.ts b/packages/@aws-cdk/aws-s3/test/test.bucket.ts index 1c1acab595baf..5bd285d5e0055 100644 --- a/packages/@aws-cdk/aws-s3/test/test.bucket.ts +++ b/packages/@aws-cdk/aws-s3/test/test.bucket.ts @@ -246,7 +246,7 @@ export = { const x = new iam.PolicyStatement().addResource(bucket.bucketArn).addAction('s3:ListBucket'); - test.deepEqual(cdk.resolve(x), { + test.deepEqual(bucket.node.resolve(x), { Action: 's3:ListBucket', Effect: 'Allow', Resource: { 'Fn::GetAtt': [ 'MyBucketF68F3FF0', 'Arn' ] } @@ -262,7 +262,7 @@ export = { const p = new iam.PolicyStatement().addResource(bucket.arnForObjects('hello/world')).addAction('s3:GetObject'); - test.deepEqual(cdk.resolve(p), { + test.deepEqual(bucket.node.resolve(p), { Action: 's3:GetObject', Effect: 'Allow', Resource: { @@ -288,7 +288,7 @@ export = { const resource = bucket.arnForObjects('home/', team.groupName, '/', user.userName, '/*'); const p = new iam.PolicyStatement().addResource(resource).addAction('s3:GetObject'); - test.deepEqual(cdk.resolve(p), { + test.deepEqual(bucket.node.resolve(p), { Action: 's3:GetObject', Effect: 'Allow', Resource: { @@ -331,10 +331,11 @@ export = { const stack = new cdk.Stack(undefined, 'MyStack'); const bucket = new s3.Bucket(stack, 'MyBucket'); const bucketRef = bucket.export(); - test.deepEqual(cdk.resolve(bucketRef), { + test.deepEqual(bucket.node.resolve(bucketRef), { bucketArn: { 'Fn::ImportValue': 'MyStack:MyBucketBucketArnE260558C' }, bucketName: { 'Fn::ImportValue': 'MyStack:MyBucketBucketName8A027014' }, - bucketDomainName: { 'Fn::ImportValue': 'MyStack:MyBucketDomainNameF76B9A7A' } + bucketDomainName: { 'Fn::ImportValue': 'MyStack:MyBucketDomainNameF76B9A7A' }, + bucketWebsiteUrl: { 'Fn::ImportValue': 'MyStack:MyBucketWebsiteURL9C222788' } }); test.done(); }, @@ -343,10 +344,11 @@ export = { const stack = new cdk.Stack(undefined, 'MyStack'); const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.Kms }); const bucketRef = bucket.export(); - test.deepEqual(cdk.resolve(bucketRef), { + test.deepEqual(bucket.node.resolve(bucketRef), { bucketArn: { 'Fn::ImportValue': 'MyStack:MyBucketBucketArnE260558C' }, bucketName: { 'Fn::ImportValue': 'MyStack:MyBucketBucketName8A027014' }, - bucketDomainName: { 'Fn::ImportValue': 'MyStack:MyBucketDomainNameF76B9A7A' } + bucketDomainName: { 'Fn::ImportValue': 'MyStack:MyBucketDomainNameF76B9A7A' }, + bucketWebsiteUrl: { 'Fn::ImportValue': 'MyStack:MyBucketWebsiteURL9C222788' } }); test.done(); }, @@ -363,14 +365,14 @@ export = { const p = new iam.PolicyStatement().addResource(bucket.bucketArn).addAction('s3:ListBucket'); // it is possible to obtain a permission statement for a ref - test.deepEqual(cdk.resolve(p), { + test.deepEqual(bucket.node.resolve(p), { Action: 's3:ListBucket', Effect: 'Allow', Resource: 'arn:aws:s3:::my-bucket' }); test.deepEqual(bucket.bucketArn, bucketArn); - test.deepEqual(cdk.resolve(bucket.bucketName), 'my-bucket'); + test.deepEqual(bucket.node.resolve(bucket.bucketName), 'my-bucket'); test.deepEqual(stack.toCloudFormation(), {}, 'the ref is not a real resource'); test.done(); @@ -465,6 +467,17 @@ export = { "Export": { "Name": "S1:MyBucketDomainNameF76B9A7A" } + }, + "MyBucketWebsiteURL9C222788": { + "Value": { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "WebsiteURL" + ] + }, + "Export": { + "Name": "S1:MyBucketWebsiteURL9C222788" + } } } }); @@ -898,6 +911,17 @@ export = { "Export": { "Name": "MyBucketDomainNameF76B9A7A" } + }, + "MyBucketWebsiteURL9C222788": { + "Value": { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "WebsiteURL" + ] + }, + "Export": { + "Name": "MyBucketWebsiteURL9C222788" + } } } }); @@ -1190,6 +1214,14 @@ export = { } })); test.done(); + }, + 'exports the WebsiteURL'(test: Test) { + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Website', { + websiteIndexDocument: 'index.html' + }); + test.deepEqual(cdk.resolve(bucket.bucketWebsiteUrl), { 'Fn::GetAtt': [ 'Website32962D0B', 'WebsiteURL' ] }); + test.done(); } } }; diff --git a/packages/@aws-cdk/aws-s3/test/test.util.ts b/packages/@aws-cdk/aws-s3/test/test.util.ts index 2f33e12c81a02..11b87854e3551 100644 --- a/packages/@aws-cdk/aws-s3/test/test.util.ts +++ b/packages/@aws-cdk/aws-s3/test/test.util.ts @@ -1,19 +1,20 @@ import cdk = require('@aws-cdk/cdk'); -import { CloudFormationToken } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { parseBucketArn, parseBucketName } from '../lib/util'; export = { parseBucketArn: { 'explicit arn'(test: Test) { + const stack = new cdk.Stack(); const bucketArn = 'my:bucket:arn'; - test.deepEqual(parseBucketArn({ bucketArn }), bucketArn); + test.deepEqual(parseBucketArn(stack, { bucketArn }), bucketArn); test.done(); }, 'produce arn from bucket name'(test: Test) { + const stack = new cdk.Stack(); const bucketName = 'hello'; - test.deepEqual(cdk.resolve(parseBucketArn({ bucketName })), { 'Fn::Join': + test.deepEqual(stack.node.resolve(parseBucketArn(stack, { bucketName })), { 'Fn::Join': [ '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -22,7 +23,8 @@ export = { }, 'fails if neither arn nor name are provided'(test: Test) { - test.throws(() => parseBucketArn({}), /Cannot determine bucket ARN. At least `bucketArn` or `bucketName` is needed/); + const stack = new cdk.Stack(); + test.throws(() => parseBucketArn(stack, {}), /Cannot determine bucket ARN. At least `bucketArn` or `bucketName` is needed/); test.done(); } }, @@ -30,38 +32,44 @@ export = { parseBucketName: { 'explicit name'(test: Test) { + const stack = new cdk.Stack(); const bucketName = 'foo'; - test.deepEqual(cdk.resolve(parseBucketName({ bucketName })), 'foo'); + test.deepEqual(stack.node.resolve(parseBucketName(stack, { bucketName })), 'foo'); test.done(); }, 'extract bucket name from string arn'(test: Test) { + const stack = new cdk.Stack(); const bucketArn = 'arn:aws:s3:::my-bucket'; - test.deepEqual(cdk.resolve(parseBucketName({ bucketArn })), 'my-bucket'); + test.deepEqual(stack.node.resolve(parseBucketName(stack, { bucketArn })), 'my-bucket'); test.done(); }, 'undefined if cannot extract name from a non-string arn'(test: Test) { - const bucketArn = `arn:aws:s3:::${new CloudFormationToken({ Ref: 'my-bucket' })}`; - test.deepEqual(cdk.resolve(parseBucketName({ bucketArn })), undefined); + const stack = new cdk.Stack(); + const bucketArn = `arn:aws:s3:::${new cdk.Token({ Ref: 'my-bucket' })}`; + test.deepEqual(stack.node.resolve(parseBucketName(stack, { bucketArn })), undefined); test.done(); }, 'fails if arn uses a non "s3" service'(test: Test) { + const stack = new cdk.Stack(); const bucketArn = 'arn:aws:xx:::my-bucket'; - test.throws(() => parseBucketName({ bucketArn }), /Invalid ARN/); + test.throws(() => parseBucketName(stack, { bucketArn }), /Invalid ARN/); test.done(); }, 'fails if ARN has invalid format'(test: Test) { + const stack = new cdk.Stack(); const bucketArn = 'invalid-arn'; - test.throws(() => parseBucketName({ bucketArn }), /ARNs must have at least 6 components/); + test.throws(() => parseBucketName(stack, { bucketArn }), /ARNs must have at least 6 components/); test.done(); }, 'fails if ARN has path'(test: Test) { + const stack = new cdk.Stack(); const bucketArn = 'arn:aws:s3:::my-bucket/path'; - test.throws(() => parseBucketName({ bucketArn }), /Bucket ARN must not contain a path/); + test.throws(() => parseBucketName(stack, { bucketArn }), /Bucket ARN must not contain a path/); test.done(); } }, diff --git a/packages/@aws-cdk/aws-sagemaker/package.json b/packages/@aws-cdk/aws-sagemaker/package.json index 32e6ad4bb3fbf..4ecfc6d0b4b56 100644 --- a/packages/@aws-cdk/aws-sagemaker/package.json +++ b/packages/@aws-cdk/aws-sagemaker/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-sagemaker", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::SageMaker", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -36,7 +36,8 @@ "pkglint": "pkglint -f", "test": "cdk-test", "watch": "cdk-watch", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SageMaker" @@ -54,18 +55,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sdb/package.json b/packages/@aws-cdk/aws-sdb/package.json index a7d5f9e1567a9..197c5c93edaf4 100644 --- a/packages/@aws-cdk/aws-sdb/package.json +++ b/packages/@aws-cdk/aws-sdb/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-sdb", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::SDB", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SDB" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-secretsmanager/package.json b/packages/@aws-cdk/aws-secretsmanager/package.json index f682086ddbe90..a00358bd38357 100644 --- a/packages/@aws-cdk/aws-secretsmanager/package.json +++ b/packages/@aws-cdk/aws-secretsmanager/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-secretsmanager", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::SecretsManager", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -36,7 +36,8 @@ "pkglint": "pkglint -f", "test": "cdk-test", "watch": "cdk-watch", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SecretsManager" @@ -54,18 +55,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.secret-string.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.secret-string.ts index 56883bd0f7837..2a0eb8f0afcfd 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/test.secret-string.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/test.secret-string.ts @@ -13,7 +13,7 @@ export = { }); // THEN - test.equal(cdk.resolve(ref.value), '{{resolve:secretsmanager:SomeSecret:SecretString:::}}'); + test.equal(ref.node.resolve(ref.value), '{{resolve:secretsmanager:SomeSecret:SecretString:::}}'); test.done(); }, @@ -28,7 +28,7 @@ export = { }); // THEN - test.equal(cdk.resolve(ref.jsonFieldValue('subkey')), '{{resolve:secretsmanager:SomeSecret:SecretString:subkey::}}'); + test.equal(ref.node.resolve(ref.jsonFieldValue('subkey')), '{{resolve:secretsmanager:SomeSecret:SecretString:subkey::}}'); test.done(); }, diff --git a/packages/@aws-cdk/aws-serverless/package.json b/packages/@aws-cdk/aws-serverless/package.json index 018cb4139c9da..44d158034b23d 100644 --- a/packages/@aws-cdk/aws-serverless/package.json +++ b/packages/@aws-cdk/aws-serverless/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-serverless", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::Serverless", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -36,7 +36,8 @@ "pkglint": "pkglint -f", "test": "cdk-test", "watch": "cdk-watch", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Serverless" @@ -54,18 +55,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/package.json b/packages/@aws-cdk/aws-servicecatalog/package.json index f86e47e1d438f..0fe0f52022b63 100644 --- a/packages/@aws-cdk/aws-servicecatalog/package.json +++ b/packages/@aws-cdk/aws-servicecatalog/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-servicecatalog", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::ServiceCatalog", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ServiceCatalog" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicediscovery/package.json b/packages/@aws-cdk/aws-servicediscovery/package.json index cadbbf6f2d99e..e933931828a54 100644 --- a/packages/@aws-cdk/aws-servicediscovery/package.json +++ b/packages/@aws-cdk/aws-servicediscovery/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-servicediscovery", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::ServiceDiscovery", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ServiceDiscovery" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ses/package.json b/packages/@aws-cdk/aws-ses/package.json index d01b5b192087a..c7922887770fd 100644 --- a/packages/@aws-cdk/aws-ses/package.json +++ b/packages/@aws-cdk/aws-ses/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-ses", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::SES", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SES" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns/package.json b/packages/@aws-cdk/aws-sns/package.json index d557def1ace01..08db238b2e80c 100644 --- a/packages/@aws-cdk/aws-sns/package.json +++ b/packages/@aws-cdk/aws-sns/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-sns", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS SNS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -22,7 +22,9 @@ }, "sphinx": {} }, - "excludeTypescript": ["examples"] + "excludeTypescript": [ + "examples" + ] }, "repository": { "type": "git", @@ -36,7 +38,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SNS" @@ -54,35 +57,35 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-autoscaling-api": "^0.21.0", - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-lambda": "^0.21.0", - "@aws-cdk/aws-s3-notifications": "^0.21.0", - "@aws-cdk/aws-sqs": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-autoscaling-api": "^0.22.0", + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-lambda": "^0.22.0", + "@aws-cdk/aws-s3-notifications": "^0.22.0", + "@aws-cdk/aws-sqs": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-autoscaling-api": "^0.21.0", - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-lambda": "^0.21.0", - "@aws-cdk/aws-s3-notifications": "^0.21.0", - "@aws-cdk/aws-sqs": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-autoscaling-api": "^0.22.0", + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-lambda": "^0.22.0", + "@aws-cdk/aws-s3-notifications": "^0.22.0", + "@aws-cdk/aws-sqs": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ 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 ad7f6bdbc5a27..7d9b21872bd2a 100644 --- a/packages/@aws-cdk/aws-sns/test/test.sns.ts +++ b/packages/@aws-cdk/aws-sns/test/test.sns.ts @@ -5,7 +5,6 @@ import lambda = require('@aws-cdk/aws-lambda'); import s3n = require('@aws-cdk/aws-s3-notifications'); import sqs = require('@aws-cdk/aws-sqs'); import cdk = require('@aws-cdk/cdk'); -import { resolve } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import sns = require('../lib'); @@ -552,7 +551,7 @@ export = { { "Action": "sns:Publish", "Effect": "Allow", - "Resource": cdk.resolve(topic.topicArn) + "Resource": stack.node.resolve(topic.topicArn) } ], } @@ -692,11 +691,11 @@ export = { "Action": "sqs:SendMessage", "Condition": { "ArnEquals": { - "aws:SourceArn": resolve(imported.topicArn) + "aws:SourceArn": stack2.node.resolve(imported.topicArn) } }, "Principal": { "Service": "sns.amazonaws.com" }, - "Resource": resolve(queue.queueArn), + "Resource": stack2.node.resolve(queue.queueArn), "Effect": "Allow", } ], @@ -715,7 +714,7 @@ export = { const bucketId = 'bucketId'; const dest1 = topic.asBucketNotificationDestination(bucketArn, bucketId); - test.deepEqual(resolve(dest1.arn), resolve(topic.topicArn)); + test.deepEqual(stack.node.resolve(dest1.arn), stack.node.resolve(topic.topicArn)); test.deepEqual(dest1.type, s3n.BucketNotificationDestinationType.Topic); const dep: cdk.Construct = dest1.dependencies![0] as any; @@ -723,12 +722,12 @@ export = { // calling again on the same bucket yields is idempotent const dest2 = topic.asBucketNotificationDestination(bucketArn, bucketId); - test.deepEqual(resolve(dest2.arn), resolve(topic.topicArn)); + test.deepEqual(stack.node.resolve(dest2.arn), stack.node.resolve(topic.topicArn)); test.deepEqual(dest2.type, s3n.BucketNotificationDestinationType.Topic); // another bucket will be added to the topic policy const dest3 = topic.asBucketNotificationDestination('bucket2', 'bucket2'); - test.deepEqual(resolve(dest3.arn), resolve(topic.topicArn)); + test.deepEqual(stack.node.resolve(dest3.arn), stack.node.resolve(topic.topicArn)); test.deepEqual(dest3.type, s3n.BucketNotificationDestinationType.Topic); expect(stack).toMatch({ diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index 4cd5ed45af113..811e66a343287 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-sqs", - "version": "0.21.0", + "version": "0.22.0", "description": "CDK Constructs for AWS SQS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SQS" @@ -53,28 +54,28 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "@aws-cdk/aws-s3": "^0.21.0", + "@aws-cdk/assert": "^0.22.0", + "@aws-cdk/aws-s3": "^0.22.0", "aws-sdk": "^2.259.1", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-autoscaling-api": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-kms": "^0.21.0", - "@aws-cdk/aws-s3-notifications": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-autoscaling-api": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-kms": "^0.22.0", + "@aws-cdk/aws-s3-notifications": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-autoscaling-api": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-kms": "^0.21.0", - "@aws-cdk/aws-s3-notifications": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-autoscaling-api": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-kms": "^0.22.0", + "@aws-cdk/aws-s3-notifications": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" @@ -84,4 +85,4 @@ "resource-attribute:@aws-cdk/aws-sqs.IQueue.queueName" ] } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sqs/test/test.sqs.ts b/packages/@aws-cdk/aws-sqs/test/test.sqs.ts index 37e9faff6a5ff..fb2bfcc28883a 100644 --- a/packages/@aws-cdk/aws-sqs/test/test.sqs.ts +++ b/packages/@aws-cdk/aws-sqs/test/test.sqs.ts @@ -2,7 +2,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); import s3 = require('@aws-cdk/aws-s3'); -import { resolve, Stack } from '@aws-cdk/cdk'; +import { Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import sqs = require('../lib'); import { Queue } from '../lib'; @@ -103,8 +103,8 @@ export = { // THEN // "import" returns an IQueue bound to `Fn::ImportValue`s. - test.deepEqual(resolve(imports.queueArn), { 'Fn::ImportValue': 'QueueQueueArn8CF496D5' }); - test.deepEqual(resolve(imports.queueUrl), { 'Fn::ImportValue': 'QueueQueueUrlC30FF916' }); + test.deepEqual(stack.node.resolve(imports.queueArn), { 'Fn::ImportValue': 'QueueQueueArn8CF496D5' }); + test.deepEqual(stack.node.resolve(imports.queueUrl), { 'Fn::ImportValue': 'QueueQueueUrlC30FF916' }); // the exporting stack has Outputs for QueueARN and QueueURL const outputs = stack.toCloudFormation().Outputs; @@ -246,7 +246,7 @@ export = { const exportCustom = customKey.export(); - test.deepEqual(resolve(exportCustom), { + test.deepEqual(stack.node.resolve(exportCustom), { queueArn: { 'Fn::ImportValue': 'QueueWithCustomKeyQueueArnD326BB9B' }, queueUrl: { 'Fn::ImportValue': 'QueueWithCustomKeyQueueUrlF07DDC70' }, keyArn: { 'Fn::ImportValue': 'QueueWithCustomKeyKeyArn537F6E42' } @@ -294,7 +294,7 @@ export = { const exportManaged = managedKey.export(); - test.deepEqual(resolve(exportManaged), { + test.deepEqual(stack.node.resolve(exportManaged), { queueArn: { 'Fn::ImportValue': 'QueueWithManagedKeyQueueArn8798A14E' }, queueUrl: { 'Fn::ImportValue': 'QueueWithManagedKeyQueueUrlD735C981' }, keyArn: { 'Fn::ImportValue': 'QueueWithManagedKeyKeyArn9C42A85D' } diff --git a/packages/@aws-cdk/aws-ssm/package.json b/packages/@aws-cdk/aws-ssm/package.json index bba7ae006a162..bba3ee4940a2c 100644 --- a/packages/@aws-cdk/aws-ssm/package.json +++ b/packages/@aws-cdk/aws-ssm/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-ssm", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::SSM", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SSM" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ssm/test/test.parameter-store-string.ts b/packages/@aws-cdk/aws-ssm/test/test.parameter-store-string.ts index 897f8cfc96cfe..5eaa03e8974ce 100644 --- a/packages/@aws-cdk/aws-ssm/test/test.parameter-store-string.ts +++ b/packages/@aws-cdk/aws-ssm/test/test.parameter-store-string.ts @@ -14,7 +14,7 @@ export = { }); // THEN - test.equal(cdk.resolve(ref.value), '{{resolve:ssm:/some/key:123}}'); + test.equal(ref.node.resolve(ref.value), '{{resolve:ssm:/some/key:123}}'); test.done(); }, @@ -30,7 +30,7 @@ export = { }); // THEN - test.equal(cdk.resolve(ref.value), '{{resolve:ssm-secure:/some/key:123}}'); + test.equal(ref.node.resolve(ref.value), '{{resolve:ssm-secure:/some/key:123}}'); test.done(); }, diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts index 696f5a04c5ddb..cba9fdbd0fb35 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts @@ -71,8 +71,9 @@ export class StateMachine extends cdk.Construct implements IStateMachine { constructor(scope: cdk.Construct, id: string, props: StateMachineProps) { super(scope, id); + const stack = cdk.Stack.find(this); this.role = props.role || new iam.Role(this, 'Role', { - assumedBy: new iam.ServicePrincipal(`states.${new cdk.AwsRegion()}.amazonaws.com`), + assumedBy: new iam.ServicePrincipal(`states.${stack.region}.amazonaws.com`), }); const graph = new StateGraph(props.definition.startState, `State Machine ${id} definition`); @@ -81,7 +82,7 @@ export class StateMachine extends cdk.Construct implements IStateMachine { const resource = new CfnStateMachine(this, 'Resource', { stateMachineName: props.stateMachineName, roleArn: this.role.roleArn, - definitionString: cdk.CloudFormationJSON.stringify(graph.toGraphJson()), + definitionString: this.node.stringifyJson(graph.toGraphJson()), }); for (const statement of graph.policyStatements) { diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts index 1833dbf5988b5..22bd2fe03eca4 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts @@ -104,16 +104,6 @@ export class Parallel extends State implements INextable { return this; } - /** - * Validate this state - */ - public validate(): string[] { - if (this.branches.length === 0) { - return ['Parallel must have at least one branch']; - } - return []; - } - /** * Return the Amazon States Language object for this state */ @@ -128,4 +118,14 @@ export class Parallel extends State implements INextable { ...this.renderBranches(), }; } + + /** + * Validate this state + */ + protected validate(): string[] { + if (this.branches.length === 0) { + return ['Parallel must have at least one branch']; + } + return []; + } } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts index dfc9f8f75c246..e99559edaab43 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts @@ -24,6 +24,16 @@ export interface StateProps { */ inputPath?: string; + /** + * Parameters pass a collection of key-value pairs, either static values or JSONPath expressions that select from the input. + * + * @see + * https://docs.aws.amazon.com/step-functions/latest/dg/input-output-inputpath-params.html#input-output-parameters + * + * @default No parameters + */ + parameters?: { [name: string]: any }; + /** * JSONPath expression to select part of the state to be the output to this state. * @@ -112,6 +122,7 @@ export abstract class State extends cdk.Construct implements IChainable { // pragmatic! protected readonly comment?: string; protected readonly inputPath?: string; + protected readonly parameters?: object; protected readonly outputPath?: string; protected readonly resultPath?: string; protected readonly branches: StateGraph[] = []; @@ -144,6 +155,7 @@ export abstract class State extends cdk.Construct implements IChainable { this.comment = props.comment; this.inputPath = props.inputPath; + this.parameters = props.parameters; this.outputPath = props.outputPath; this.resultPath = props.resultPath; } @@ -297,11 +309,12 @@ export abstract class State extends cdk.Construct implements IChainable { } /** - * Render InputPath/OutputPath in ASL JSON format + * Render InputPath/Parameters/OutputPath in ASL JSON format */ protected renderInputOutput(): any { return { InputPath: renderJsonPath(this.inputPath), + Parameters: this.parameters, OutputPath: renderJsonPath(this.outputPath), }; } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts index b540dacf30787..a6ed6025de560 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -34,6 +34,16 @@ export interface TaskProps { */ inputPath?: string; + /** + * Parameters pass a collection of key-value pairs, either static values or JSONPath expressions that select from the input. + * + * @see + * https://docs.aws.amazon.com/step-functions/latest/dg/input-output-inputpath-params.html#input-output-parameters + * + * @default No parameters + */ + parameters?: { [name: string]: any }; + /** * JSONPath expression to select part of the state to be the output to this state. * diff --git a/packages/@aws-cdk/aws-stepfunctions/package.json b/packages/@aws-cdk/aws-stepfunctions/package.json index 1c15ec1089cdc..ada893e0378e4 100644 --- a/packages/@aws-cdk/aws-stepfunctions/package.json +++ b/packages/@aws-cdk/aws-stepfunctions/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-stepfunctions", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::StepFunctions", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::StepFunctions" @@ -53,24 +54,24 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-cloudwatch": "^0.21.0", - "@aws-cdk/aws-events": "^0.21.0", - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-cloudwatch": "^0.22.0", + "@aws-cdk/aws-events": "^0.22.0", + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" @@ -80,4 +81,4 @@ "resource-attribute:@aws-cdk/aws-stepfunctions.IStateMachine.stateMachineName" ] } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts index 96f1921d90d6c..d4d15c8dec872 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts @@ -60,13 +60,13 @@ export = { namespace: 'AWS/States', dimensions: { ActivityArn: { Ref: 'Activity04690B0A' }}, }; - test.deepEqual(cdk.resolve(activity.metricRunTime()), { + test.deepEqual(stack.node.resolve(activity.metricRunTime()), { ...sharedMetric, metricName: 'ActivityRunTime', statistic: 'Average' }); - test.deepEqual(cdk.resolve(activity.metricFailed()), { + test.deepEqual(stack.node.resolve(activity.metricFailed()), { ...sharedMetric, metricName: 'ActivitiesFailed', statistic: 'Sum' diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts index 6484828c19ec9..d717524180690 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts @@ -79,18 +79,60 @@ export = { namespace: 'AWS/States', dimensions: { ResourceArn: 'resource' }, }; - test.deepEqual(cdk.resolve(task.metricRunTime()), { + test.deepEqual(stack.node.resolve(task.metricRunTime()), { ...sharedMetric, metricName: 'FakeResourceRunTime', statistic: 'Average' }); - test.deepEqual(cdk.resolve(task.metricFailed()), { + test.deepEqual(stack.node.resolve(task.metricFailed()), { ...sharedMetric, metricName: 'FakeResourcesFailed', statistic: 'Sum' }); + test.done(); + }, + + 'Task should render InputPath / Parameters / OutputPath correctly'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const task = new stepfunctions.Task(stack, 'Task', { + resource: new FakeResource(), + inputPath: "$", + outputPath: "$.state", + parameters: { + "input.$": "$", + "stringArgument": "inital-task", + "numberArgument": 123, + "booleanArgument": true, + "arrayArgument": ["a", "b", "c"] + } + }); + + // WHEN + const taskState = task.toStateJson(); + + // THEN + test.deepEqual(taskState, { End: true, + Retry: undefined, + Catch: undefined, + InputPath: '$', + Parameters: + { 'input.$': '$', + 'stringArgument': 'inital-task', + 'numberArgument': 123, + 'booleanArgument': true, + 'arrayArgument': [ 'a', 'b', 'c' ] }, + OutputPath: '$.state', + Type: 'Task', + Comment: undefined, + Resource: 'resource', + ResultPath: undefined, + TimeoutSeconds: undefined, + HeartbeatSeconds: undefined + }); + test.done(); } }; diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts index fd30ca17a1d59..5b15006d36395 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts @@ -729,5 +729,5 @@ class FakeResource implements stepfunctions.IStepFunctionsTaskResource { } function render(sm: stepfunctions.IChainable) { - return cdk.resolve(new stepfunctions.StateGraph(sm.startState, 'Test Graph').toGraphJson()); + return new cdk.Stack().node.resolve(new stepfunctions.StateGraph(sm.startState, 'Test Graph').toGraphJson()); } diff --git a/packages/@aws-cdk/aws-waf/package.json b/packages/@aws-cdk/aws-waf/package.json index 40efdd860d07e..a074a50bd8f7d 100644 --- a/packages/@aws-cdk/aws-waf/package.json +++ b/packages/@aws-cdk/aws-waf/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-waf", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::WAF", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::WAF" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-wafregional/package.json b/packages/@aws-cdk/aws-wafregional/package.json index 7b0e85664a697..9fa6618610b53 100644 --- a/packages/@aws-cdk/aws-wafregional/package.json +++ b/packages/@aws-cdk/aws-wafregional/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-wafregional", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::WAFRegional", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::WAFRegional" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-workspaces/package.json b/packages/@aws-cdk/aws-workspaces/package.json index a0c9b3abbe82a..5084880b39674 100644 --- a/packages/@aws-cdk/aws-workspaces/package.json +++ b/packages/@aws-cdk/aws-workspaces/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-workspaces", - "version": "0.21.0", + "version": "0.22.0", "description": "The CDK Construct Library for AWS::WorkSpaces", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,7 +35,8 @@ "integ": "cdk-integ", "pkglint": "pkglint -f", "package": "cdk-package", - "awslint": "cdk-awslint" + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::WorkSpaces" @@ -53,19 +54,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/app.ts b/packages/@aws-cdk/cdk/lib/app.ts index 7cf7fbecc478f..dc38817aafba8 100644 --- a/packages/@aws-cdk/cdk/lib/app.ts +++ b/packages/@aws-cdk/cdk/lib/app.ts @@ -3,7 +3,6 @@ import fs = require('fs'); import path = require('path'); import { Stack } from './cloudformation/stack'; import { IConstruct, MetadataEntry, PATH_SEP, Root } from './core/construct'; -import { resolve } from './core/tokens'; /** * Represents a CDK program. @@ -59,6 +58,8 @@ export class App extends Root { public synthesizeStack(stackName: string): cxapi.SynthesizedStack { const stack = this.getStack(stackName); + this.node.prepareTree(); + // first, validate this stack and stop if there are errors. const errors = stack.node.validateTree(); if (errors.length > 0) { @@ -81,7 +82,8 @@ export class App extends Root { environment, missing, template: stack.toCloudFormation(), - metadata: this.collectMetadata(stack) + metadata: this.collectMetadata(stack), + dependsOn: noEmptyArray(stack.dependencies().map(s => s.node.id)), }; } @@ -114,7 +116,7 @@ export class App extends Root { function visit(node: IConstruct) { if (node.node.metadata.length > 0) { // Make the path absolute - output[PATH_SEP + node.node.path] = node.node.metadata.map(md => resolve(md) as MetadataEntry); + output[PATH_SEP + node.node.path] = node.node.metadata.map(md => node.node.resolve(md) as MetadataEntry); } for (const child of node.node.children) { @@ -226,4 +228,8 @@ function getJsiiAgentVersion() { } return jsiiAgent; +} + +function noEmptyArray(xs: T[]): T[] | undefined { + return xs.length > 0 ? xs : undefined; } \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/arn.ts b/packages/@aws-cdk/cdk/lib/cloudformation/arn.ts index 52e2fb3a563a3..e6b86166c3a01 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/arn.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/arn.ts @@ -1,239 +1,216 @@ -import { AwsAccountId, AwsPartition, AwsRegion } from '..'; import { Fn } from '../cloudformation/fn'; import { unresolved } from '../core/tokens'; +import { Stack } from './stack'; /** - * An Amazon Resource Name (ARN). - * http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html + * Creates an ARN from components. + * + * If `partition`, `region` or `account` are not specified, the stack's + * partition, region and account will be used. + * + * If any component is the empty string, an empty string will be inserted + * into the generated ARN at the location that component corresponds to. + * + * The ARN will be formatted as follows: + * + * arn:{partition}:{service}:{region}:{account}:{resource}{sep}}{resource-name} + * + * The required ARN pieces that are omitted will be taken from the stack that + * the 'scope' is attached to. If all ARN pieces are supplied, the supplied scope + * can be 'undefined'. */ -export class ArnUtils { - /** - * Creates an ARN from components. - * - * If `partition`, `region` or `account` are not specified, the stack's - * partition, region and account will be used. - * - * If any component is the empty string, an empty string will be inserted - * into the generated ARN at the location that component corresponds to. - * - * The ARN will be formatted as follows: - * - * arn:{partition}:{service}:{region}:{account}:{resource}{sep}}{resource-name} - * - */ - public static fromComponents(components: ArnComponents): string { - const partition = components.partition == null - ? new AwsPartition() - : components.partition; - const region = components.region == null - ? new AwsRegion() - : components.region; - const account = components.account == null - ? new AwsAccountId() - : components.account; - - const values = [ 'arn', ':', partition, ':', components.service, ':', region, ':', account, ':', components.resource ]; - - const sep = components.sep || '/'; - if (sep !== '/' && sep !== ':') { - throw new Error('resourcePathSep may only be ":" or "/"'); - } +export function arnFromComponents(components: ArnComponents, stack: Stack): string { + const partition = components.partition !== undefined ? components.partition : stack.partition; + const region = components.region !== undefined ? components.region : stack.region; + const account = components.account !== undefined ? components.account : stack.accountId; - if (components.resourceName != null) { - values.push(sep); - values.push(components.resourceName); - } + const values = [ 'arn', ':', partition, ':', components.service, ':', region, ':', account, ':', components.resource ]; - return values.join(''); + const sep = components.sep || '/'; + if (sep !== '/' && sep !== ':') { + throw new Error('resourcePathSep may only be ":" or "/"'); } - /** - * Given an ARN, parses it and returns components. - * - * If the ARN is a concrete string, it will be parsed and validated. The - * separator (`sep`) will be set to '/' if the 6th component includes a '/', - * in which case, `resource` will be set to the value before the '/' and - * `resourceName` will be the rest. In case there is no '/', `resource` will - * be set to the 6th components and `resourceName` will be set to the rest - * of the string. - * - * If the ARN includes tokens (or is a token), the ARN cannot be validated, - * since we don't have the actual value yet at the time of this function - * call. You will have to know the separator and the type of ARN. The - * resulting `ArnComponents` object will contain tokens for the - * subexpressions of the ARN, not string literals. In this case this - * function cannot properly parse the complete final resourceName (path) out - * of ARNs that use '/' to both separate the 'resource' from the - * 'resourceName' AND to subdivide the resourceName further. For example, in - * S3 ARNs: - * - * arn:aws:s3:::my_corporate_bucket/path/to/exampleobject.png - * - * After parsing the resourceName will not contain - * 'path/to/exampleobject.png' but simply 'path'. This is a limitation - * because there is no slicing functionality in CloudFormation templates. - * - * @param sep The separator used to separate resource from resourceName - * @param hasName Whether there is a name component in the ARN at all. For - * example, SNS Topics ARNs have the 'resource' component contain the topic - * name, and no 'resourceName' component. - * - * @returns an ArnComponents object which allows access to the various - * components of the ARN. - * - * @returns an ArnComponents object which allows access to the various - * components of the ARN. - */ - public static parse(arn: string, sepIfToken: string = '/', hasName: boolean = true): ArnComponents { - if (unresolved(arn)) { - return ArnUtils.parseToken(arn, sepIfToken, hasName); - } + if (components.resourceName != null) { + values.push(sep); + values.push(components.resourceName); + } - const components = arn.split(':') as Array; + return values.join(''); +} - if (components.length < 6) { - throw new Error('ARNs must have at least 6 components: ' + arn); - } +/** + * Given an ARN, parses it and returns components. + * + * If the ARN is a concrete string, it will be parsed and validated. The + * separator (`sep`) will be set to '/' if the 6th component includes a '/', + * in which case, `resource` will be set to the value before the '/' and + * `resourceName` will be the rest. In case there is no '/', `resource` will + * be set to the 6th components and `resourceName` will be set to the rest + * of the string. + * + * If the ARN includes tokens (or is a token), the ARN cannot be validated, + * since we don't have the actual value yet at the time of this function + * call. You will have to know the separator and the type of ARN. The + * resulting `ArnComponents` object will contain tokens for the + * subexpressions of the ARN, not string literals. In this case this + * function cannot properly parse the complete final resourceName (path) out + * of ARNs that use '/' to both separate the 'resource' from the + * 'resourceName' AND to subdivide the resourceName further. For example, in + * S3 ARNs: + * + * arn:aws:s3:::my_corporate_bucket/path/to/exampleobject.png + * + * After parsing the resourceName will not contain + * 'path/to/exampleobject.png' but simply 'path'. This is a limitation + * because there is no slicing functionality in CloudFormation templates. + * + * @param sep The separator used to separate resource from resourceName + * @param hasName Whether there is a name component in the ARN at all. For + * example, SNS Topics ARNs have the 'resource' component contain the topic + * name, and no 'resourceName' component. + * + * @returns an ArnComponents object which allows access to the various + * components of the ARN. + * + * @returns an ArnComponents object which allows access to the various + * components of the ARN. + */ +export function parseArn(arn: string, sepIfToken: string = '/', hasName: boolean = true): ArnComponents { + if (unresolved(arn)) { + return parseToken(arn, sepIfToken, hasName); + } - const [ arnPrefix, partition, service, region, account, sixth, ...rest ] = components; + const components = arn.split(':') as Array; - if (arnPrefix !== 'arn') { - throw new Error('ARNs must start with "arn:": ' + arn); - } + if (components.length < 6) { + throw new Error('ARNs must have at least 6 components: ' + arn); + } - if (!service) { - throw new Error('The `service` component (3rd component) is required: ' + arn); - } + const [ arnPrefix, partition, service, region, account, sixth, ...rest ] = components; - if (!sixth) { - throw new Error('The `resource` component (6th component) is required: ' + arn); - } + if (arnPrefix !== 'arn') { + throw new Error('ARNs must start with "arn:": ' + arn); + } - let resource: string; - let resourceName: string | undefined; - let sep: string | undefined; + if (!service) { + throw new Error('The `service` component (3rd component) is required: ' + arn); + } - let sepIndex = sixth.indexOf('/'); - if (sepIndex !== -1) { - sep = '/'; - } else if (rest.length > 0) { - sep = ':'; - sepIndex = -1; - } + if (!sixth) { + throw new Error('The `resource` component (6th component) is required: ' + arn); + } - if (sepIndex !== -1) { - resource = sixth.substr(0, sepIndex); - resourceName = sixth.substr(sepIndex + 1); - } else { - resource = sixth; - } + let resource: string; + let resourceName: string | undefined; + let sep: string | undefined; - if (rest.length > 0) { - if (!resourceName) { - resourceName = ''; - } else { - resourceName += ':'; - } + let sepIndex = sixth.indexOf('/'); + if (sepIndex !== -1) { + sep = '/'; + } else if (rest.length > 0) { + sep = ':'; + sepIndex = -1; + } - resourceName += rest.join(':'); - } + if (sepIndex !== -1) { + resource = sixth.substr(0, sepIndex); + resourceName = sixth.substr(sepIndex + 1); + } else { + resource = sixth; + } - const result: ArnComponents = { service, resource }; - if (partition) { - result.partition = partition; + if (rest.length > 0) { + if (!resourceName) { + resourceName = ''; + } else { + resourceName += ':'; } - if (region) { - result.region = region; - } + resourceName += rest.join(':'); + } - if (account) { - result.account = account; - } + const result: ArnComponents = { service, resource }; + if (partition) { + result.partition = partition; + } - if (resourceName) { - result.resourceName = resourceName; - } + if (region) { + result.region = region; + } - if (sep) { - result.sep = sep; - } + if (account) { + result.account = account; + } - return result; + if (resourceName) { + result.resourceName = resourceName; } - /** - * Given a Token evaluating to ARN, parses it and returns components. - * - * The ARN cannot be validated, since we don't have the actual value yet - * at the time of this function call. You will have to know the separator - * and the type of ARN. - * - * The resulting `ArnComponents` object will contain tokens for the - * subexpressions of the ARN, not string literals. - * - * WARNING: this function cannot properly parse the complete final - * resourceName (path) out of ARNs that use '/' to both separate the - * 'resource' from the 'resourceName' AND to subdivide the resourceName - * further. For example, in S3 ARNs: - * - * arn:aws:s3:::my_corporate_bucket/path/to/exampleobject.png - * - * After parsing the resourceName will not contain 'path/to/exampleobject.png' - * but simply 'path'. This is a limitation because there is no slicing - * functionality in CloudFormation templates. - * - * @param arnToken The input token that contains an ARN - * @param sep The separator used to separate resource from resourceName - * @param hasName Whether there is a name component in the ARN at all. - * For example, SNS Topics ARNs have the 'resource' component contain the - * topic name, and no 'resourceName' component. - * @returns an ArnComponents object which allows access to the various - * components of the ARN. - */ - public static parseToken(arnToken: string, sep: string = '/', hasName: boolean = true): ArnComponents { - // Arn ARN looks like: - // arn:partition:service:region:account-id:resource - // arn:partition:service:region:account-id:resourcetype/resource - // arn:partition:service:region:account-id:resourcetype:resource + if (sep) { + result.sep = sep; + } - // We need the 'hasName' argument because {Fn::Select}ing a nonexistent field - // throws an error. + return result; +} - const components = Fn.split(':', arnToken); +/** + * Given a Token evaluating to ARN, parses it and returns components. + * + * The ARN cannot be validated, since we don't have the actual value yet + * at the time of this function call. You will have to know the separator + * and the type of ARN. + * + * The resulting `ArnComponents` object will contain tokens for the + * subexpressions of the ARN, not string literals. + * + * WARNING: this function cannot properly parse the complete final + * resourceName (path) out of ARNs that use '/' to both separate the + * 'resource' from the 'resourceName' AND to subdivide the resourceName + * further. For example, in S3 ARNs: + * + * arn:aws:s3:::my_corporate_bucket/path/to/exampleobject.png + * + * After parsing the resourceName will not contain 'path/to/exampleobject.png' + * but simply 'path'. This is a limitation because there is no slicing + * functionality in CloudFormation templates. + * + * @param arnToken The input token that contains an ARN + * @param sep The separator used to separate resource from resourceName + * @param hasName Whether there is a name component in the ARN at all. + * For example, SNS Topics ARNs have the 'resource' component contain the + * topic name, and no 'resourceName' component. + * @returns an ArnComponents object which allows access to the various + * components of the ARN. + */ +function parseToken(arnToken: string, sep: string = '/', hasName: boolean = true): ArnComponents { + // Arn ARN looks like: + // arn:partition:service:region:account-id:resource + // arn:partition:service:region:account-id:resourcetype/resource + // arn:partition:service:region:account-id:resourcetype:resource - const partition = Fn.select(1, components).toString(); - const service = Fn.select(2, components).toString(); - const region = Fn.select(3, components).toString(); - const account = Fn.select(4, components).toString(); + // We need the 'hasName' argument because {Fn::Select}ing a nonexistent field + // throws an error. - if (sep === ':') { - const resource = Fn.select(5, components).toString(); - const resourceName = hasName ? Fn.select(6, components).toString() : undefined; + const components = Fn.split(':', arnToken); - return { partition, service, region, account, resource, resourceName, sep }; - } else { - const lastComponents = Fn.split(sep, Fn.select(5, components)); + const partition = Fn.select(1, components).toString(); + const service = Fn.select(2, components).toString(); + const region = Fn.select(3, components).toString(); + const account = Fn.select(4, components).toString(); - const resource = Fn.select(0, lastComponents).toString(); - const resourceName = hasName ? Fn.select(1, lastComponents).toString() : undefined; + if (sep === ':') { + const resource = Fn.select(5, components).toString(); + const resourceName = hasName ? Fn.select(6, components).toString() : undefined; - return { partition, service, region, account, resource, resourceName, sep }; - } - } + return { partition, service, region, account, resource, resourceName, sep }; + } else { + const lastComponents = Fn.split(sep, Fn.select(5, components)); - /** - * Return a Token that represents the resource component of the ARN - */ - public static resourceComponent(arn: string, sep: string = '/'): string { - return ArnUtils.parseToken(arn, sep).resource; - } + const resource = Fn.select(0, lastComponents).toString(); + const resourceName = hasName ? Fn.select(1, lastComponents).toString() : undefined; - /** - * Return a Token that represents the resource Name component of the ARN - */ - public static resourceNameComponent(arn: string, sep: string = '/'): string { - return ArnUtils.parseToken(arn, sep, true).resourceName!; + return { partition, service, region, account, resource, resourceName, sep }; } } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/cfn-tokens.ts b/packages/@aws-cdk/cdk/lib/cloudformation/cfn-tokens.ts new file mode 100644 index 0000000000000..9d17016003f6f --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/cloudformation/cfn-tokens.ts @@ -0,0 +1,112 @@ +import { ResolveContext, Token } from "../core/tokens"; + +/** + * A Token that represents a CloudFormation reference to another resource + * + * If these references are used in a different stack from where they are + * defined, appropriate CloudFormation `Export`s and `Fn::ImportValue`s will be + * synthesized automatically instead of the regular CloudFormation references. + * + * Additionally, the dependency between the stacks will be recorded, and the toolkit + * will make sure to deploy producing stack before the consuming stack. + * + * This magic happens in the prepare() phase, where consuming stacks will call + * `consumeFromStack` on these Tokens and if they happen to be exported by a different + * Stack, we'll register the dependency. + */ +export class CfnReference extends Token { + /** + * Check whether this is actually a CfnReference + */ + public static isCfnReference(x: Token): x is CfnReference { + return (x as any).consumeFromStack !== undefined; + } + + public readonly isReference?: boolean; + + /** + * What stack this Token is pointing to + */ + private readonly producingStack?: Stack; + + /** + * The Tokens that should be returned for each consuming stack (as decided by the producing Stack) + */ + private readonly replacementTokens: Map; + + constructor(value: any, displayName?: string, scope?: Construct) { + if (typeof(value) === 'function') { + throw new Error('CfnReference can only hold CloudFormation intrinsics (not a function)'); + } + super(value, displayName); + this.replacementTokens = new Map(); + this.isReference = true; + + if (scope !== undefined) { + this.producingStack = Stack.find(scope); + } + } + + public resolve(context: ResolveContext): any { + // If we have a special token for this consuming stack, resolve that. Otherwise resolve as if + // we are in the same stack. + const token = this.replacementTokens.get(Stack.find(context.scope)); + if (token) { + return token.resolve(context); + } else { + return super.resolve(context); + } + } + + /** + * Register a stack this references is being consumed from. + */ + public consumeFromStack(consumingStack: Stack) { + if (this.producingStack && this.producingStack !== consumingStack && !this.replacementTokens.has(consumingStack)) { + // We're trying to resolve a cross-stack reference + consumingStack.addDependency(this.producingStack); + this.replacementTokens.set(consumingStack, this.exportValue(this, consumingStack)); + } + } + + /** + * Export a Token value for use in another stack + * + * Works by mutating the producing stack in-place. + */ + private exportValue(tokenValue: Token, consumingStack: Stack): Token { + const producingStack = this.producingStack!; + + if (producingStack.env.account !== consumingStack.env.account || producingStack.env.region !== consumingStack.env.region) { + throw new Error('Can only reference cross stacks in the same region and account.'); + } + + // Ensure a singleton "Exports" scoping Construct + // This mostly exists to trigger LogicalID munging, which would be + // disabled if we parented constructs directly under Stack. + // Also it nicely prevents likely construct name clashes + + const exportsName = 'Exports'; + let stackExports = producingStack.node.tryFindChild(exportsName) as Construct; + if (stackExports === undefined) { + stackExports = new Construct(producingStack, exportsName); + } + + // Ensure a singleton Output for this value + const resolved = producingStack.node.resolve(tokenValue); + const id = 'Output' + JSON.stringify(resolved); + let output = stackExports.node.tryFindChild(id) as Output; + if (!output) { + output = new Output(stackExports, id, { value: tokenValue }); + } + + // We want to return an actual FnImportValue Token here, but Fn.importValue() returns a 'string', + // so construct one in-place. + return new Token({ 'Fn::ImportValue': output.export }); + } + +} + +import { Construct } from "../core/construct"; +import { Output } from "./output"; +import { Stack } from "./stack"; diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-json.ts b/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-json.ts index 6075f5650352a..62d5333397f49 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-json.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-json.ts @@ -1,5 +1,7 @@ -import { resolve, Token } from "../core/tokens"; -import { CloudFormationToken, isIntrinsic } from "./cloudformation-token"; +import { IConstruct } from "../core/construct"; +import { Token } from "../core/tokens"; +import { resolve } from "../core/tokens/resolve"; +import { isIntrinsic } from "./instrinsics"; /** * Class for JSON routines that are framework-aware @@ -14,8 +16,11 @@ export class CloudFormationJSON { * * All Tokens substituted in this way must return strings, or the evaluation * in CloudFormation will fail. + * + * @param obj The object to stringify + * @param context The Construct from which to resolve any Tokens found in the object */ - public static stringify(obj: any): Token { + public static stringify(obj: any, context: IConstruct): string { return new Token(() => { // Resolve inner value first so that if they evaluate to literals, we // maintain the type (and discard 'undefined's). @@ -26,12 +31,15 @@ export class CloudFormationJSON { // deep-escapes any strings inside the intrinsic, so that if literal // strings are used in {Fn::Join} or something, they will end up // escaped in the final JSON output. - const resolved = resolve(obj); + const resolved = resolve(obj, { + scope: context, + prefix: [] + }); // We can just directly return this value, since resolve() will be called // on our return value anyway. return JSON.stringify(deepReplaceIntrinsics(resolved)); - }); + }).toString(); /** * Recurse into a structure, replace all intrinsics with IntrinsicTokens. @@ -65,7 +73,7 @@ export class CloudFormationJSON { /** * Token that also stringifies in the toJSON() operation. */ -class IntrinsicToken extends CloudFormationToken { +class IntrinsicToken extends Token { /** * Special handler that gets called when JSON.stringify() is used. */ diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts b/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts deleted file mode 100644 index 467de44968370..0000000000000 --- a/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { resolve, Token, unresolved } from "../core/tokens"; - -/** - * Base class for CloudFormation built-ins - */ -export class CloudFormationToken extends Token { - public concat(left: any | undefined, right: any | undefined): Token { - const parts = new Array(); - if (left !== undefined) { parts.push(left); } - parts.push(resolve(this)); - if (right !== undefined) { parts.push(right); } - return new FnJoin('', parts); - } -} - -/** - * Return whether the given value represents a CloudFormation intrinsic - */ -export function isIntrinsic(x: any) { - if (Array.isArray(x) || x === null || typeof x !== 'object') { return false; } - - const keys = Object.keys(x); - if (keys.length !== 1) { return false; } - - return keys[0] === 'Ref' || keys[0].startsWith('Fn::'); -} - -/** - * The intrinsic function ``Fn::Join`` appends a set of values into a single value, separated by - * the specified delimiter. If a delimiter is the empty string, the set of values are concatenated - * with no delimiter. - */ -export class FnJoin extends CloudFormationToken { - private readonly delimiter: string; - private readonly listOfValues: any[]; - // Cache for the result of resolveValues() - since it otherwise would be computed several times - private _resolvedValues?: any[]; - private canOptimize: boolean; - - /** - * Creates an ``Fn::Join`` function. - * @param delimiter The value you want to occur between fragments. The delimiter will occur between fragments only. - * It will not terminate the final value. - * @param listOfValues The list of values you want combined. - */ - constructor(delimiter: string, listOfValues: any[]) { - if (listOfValues.length === 0) { - throw new Error(`FnJoin requires at least one value to be provided`); - } - // Passing the values as a token, optimization requires resolving stringified tokens, we should be deferred until - // this token is itself being resolved. - super({ 'Fn::Join': [ delimiter, new Token(() => this.resolveValues()) ] }); - this.delimiter = delimiter; - this.listOfValues = listOfValues; - this.canOptimize = true; - } - - public resolve(): any { - const resolved = this.resolveValues(); - if (this.canOptimize && resolved.length === 1) { - return resolved[0]; - } - return super.resolve(); - } - - /** - * Optimization: if an Fn::Join is nested in another one and they share the same delimiter, then flatten it up. Also, - * if two concatenated elements are literal strings (not tokens), then pre-concatenate them with the delimiter, to - * generate shorter output. - */ - private resolveValues() { - if (this._resolvedValues) { return this._resolvedValues; } - - if (unresolved(this.listOfValues)) { - // This is a list token, don't resolve and also don't optimize. - this.canOptimize = false; - return this._resolvedValues = this.listOfValues; - } - - const resolvedValues = [...this.listOfValues.map(e => resolve(e))]; - let i = 0; - while (i < resolvedValues.length) { - const el = resolvedValues[i]; - if (isFnJoinIntrinsicWithSameDelimiter.call(this, el)) { - resolvedValues.splice(i, 1, ...el['Fn::Join'][1]); - } else if (i > 0 && isPlainString(resolvedValues[i - 1]) && isPlainString(resolvedValues[i])) { - resolvedValues[i - 1] += this.delimiter + resolvedValues[i]; - resolvedValues.splice(i, 1); - } else { - i += 1; - } - } - - return this._resolvedValues = resolvedValues; - - function isFnJoinIntrinsicWithSameDelimiter(this: FnJoin, obj: any): boolean { - return isIntrinsic(obj) - && Object.keys(obj)[0] === 'Fn::Join' - && obj['Fn::Join'][0] === this.delimiter; - } - - function isPlainString(obj: any): boolean { - return typeof obj === 'string' && !unresolved(obj); - } - } -} diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/condition.ts b/packages/@aws-cdk/cdk/lib/cloudformation/condition.ts index 706b352c90d86..95f4aab398b1d 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/condition.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/condition.ts @@ -1,20 +1,20 @@ import { Construct } from '../core/construct'; -import { CloudFormationToken } from './cloudformation-token'; -import { Referenceable } from './stack'; +import { ResolveContext } from '../core/tokens'; +import { Referenceable } from './stack-element'; export interface ConditionProps { - expression?: FnCondition; + expression?: IConditionExpression; } /** * Represents a CloudFormation condition, for resources which must be conditionally created and * the determination must be made at deploy time. */ -export class Condition extends Referenceable { +export class Condition extends Referenceable implements IConditionExpression { /** * The condition statement. */ - public expression?: FnCondition; + public expression?: IConditionExpression; /** * Build a new condition. The condition must be constructed with a condition token, @@ -26,35 +26,52 @@ export class Condition extends Referenceable { } public toCloudFormation(): object { + if (!this.expression) { + return { }; + } + return { Conditions: { [this.logicalId]: this.expression } }; } + + /** + * Synthesizes the condition. + */ + public resolve(_context: ResolveContext): any { + return { Condition: this.logicalId }; + } } /** - * You can use intrinsic functions, such as ``Fn::If``, ``Fn::Equals``, and ``Fn::Not``, to conditionally - * create stack resources. These conditions are evaluated based on input parameters that you - * declare when you create or update a stack. After you define all your conditions, you can - * associate them with resources or resource properties in the Resources and Outputs sections - * of a template. + * Represents a CloudFormation element that can be used within a Condition. * - * You define all conditions in the Conditions section of a template except for ``Fn::If`` conditions. - * You can use the ``Fn::If`` condition in the metadata attribute, update policy attribute, and property - * values in the Resources section and Outputs sections of a template. + * You can use intrinsic functions, such as ``Fn.conditionIf``, + * ``Fn.conditionEquals``, and ``Fn.conditionNot``, to conditionally create + * stack resources. These conditions are evaluated based on input parameters + * that you declare when you create or update a stack. After you define all your + * conditions, you can associate them with resources or resource properties in + * the Resources and Outputs sections of a template. * - * You might use conditions when you want to reuse a template that can create resources in different - * contexts, such as a test environment versus a production environment. In your template, you can - * add an EnvironmentType input parameter, which accepts either prod or test as inputs. For the - * production environment, you might include Amazon EC2 instances with certain capabilities; - * however, for the test environment, you want to use less capabilities to save costs. With - * conditions, you can define which resources are created and how they're configured for each - * environment type. + * You define all conditions in the Conditions section of a template except for + * ``Fn.conditionIf`` conditions. You can use the ``Fn.conditionIf`` condition + * in the metadata attribute, update policy attribute, and property values in + * the Resources section and Outputs sections of a template. + * + * You might use conditions when you want to reuse a template that can create + * resources in different contexts, such as a test environment versus a + * production environment. In your template, you can add an EnvironmentType + * input parameter, which accepts either prod or test as inputs. For the + * production environment, you might include Amazon EC2 instances with certain + * capabilities; however, for the test environment, you want to use less + * capabilities to save costs. With conditions, you can define which resources + * are created and how they're configured for each environment type. */ -export class FnCondition extends CloudFormationToken { - constructor(type: string, value: any) { - super({ [type]: value }); - } +export interface IConditionExpression { + /** + * Returns a JSON node that represents this condition expression + */ + resolve(context: ResolveContext): any; } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts b/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts index 44b0f440da057..0a28c09873ee8 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts @@ -1,5 +1,7 @@ -import { CloudFormationToken, FnJoin } from './cloudformation-token'; -import { FnCondition } from './condition'; +import { ResolveContext, Token, unresolved } from '../core/tokens'; +import { resolve } from '../core/tokens/resolve'; +import { IConditionExpression } from './condition'; +import { minimalCloudFormationJoin } from './instrinsics'; // tslint:disable:max-line-length @@ -19,7 +21,7 @@ export class Fn { * attributes available for that resource type. * @returns a CloudFormationToken object */ - public static getAtt(logicalNameOfResource: string, attributeName: string): CloudFormationToken { + public static getAtt(logicalNameOfResource: string, attributeName: string): Token { return new FnGetAtt(logicalNameOfResource, attributeName); } @@ -148,7 +150,7 @@ export class Fn { * @param conditions conditions to AND * @returns an FnCondition token */ - public static conditionAnd(...conditions: FnCondition[]): FnCondition { + public static conditionAnd(...conditions: IConditionExpression[]): IConditionExpression { return new FnAnd(...conditions); } @@ -159,7 +161,7 @@ export class Fn { * @param rhs A value of any type that you want to compare. * @returns an FnCondition token */ - public static conditionEquals(lhs: any, rhs: any): FnCondition { + public static conditionEquals(lhs: any, rhs: any): IConditionExpression { return new FnEquals(lhs, rhs); } @@ -178,7 +180,7 @@ export class Fn { * evaluates to false. * @returns an FnCondition token */ - public static conditionIf(conditionId: string, valueIfTrue: any, valueIfFalse: any): FnCondition { + public static conditionIf(conditionId: string, valueIfTrue: any, valueIfFalse: any): IConditionExpression { return new FnIf(conditionId, valueIfTrue, valueIfFalse); } @@ -189,7 +191,7 @@ export class Fn { * or false. * @returns an FnCondition token */ - public static conditionNot(condition: FnCondition): FnCondition { + public static conditionNot(condition: IConditionExpression): IConditionExpression { return new FnNot(condition); } @@ -201,7 +203,7 @@ export class Fn { * @param conditions conditions that evaluates to true or false. * @returns an FnCondition token */ - public static conditionOr(...conditions: FnCondition[]): FnCondition { + public static conditionOr(...conditions: IConditionExpression[]): IConditionExpression { return new FnOr(...conditions); } @@ -212,7 +214,7 @@ export class Fn { * @param value A string, such as "A", that you want to compare against a list of strings. * @returns an FnCondition token */ - public static conditionContains(listOfStrings: string[], value: string): FnCondition { + public static conditionContains(listOfStrings: string[], value: string): IConditionExpression { return new FnContains(listOfStrings, value); } @@ -223,7 +225,7 @@ export class Fn { * of strings. * @returns an FnCondition token */ - public conditionEachMemberEquals(listOfStrings: string[], value: string): FnCondition { + public conditionEachMemberEquals(listOfStrings: string[], value: string): IConditionExpression { return new FnEachMemberEquals(listOfStrings, value); } @@ -238,7 +240,7 @@ export class Fn { * strings_to_check parameter. * @returns an FnCondition token */ - public conditionEachMemberIn(stringsToCheck: string[], stringsToMatch: string): FnCondition { + public conditionEachMemberIn(stringsToCheck: string[], stringsToMatch: string): IConditionExpression { return new FnEachMemberIn(stringsToCheck, stringsToMatch); } @@ -285,7 +287,7 @@ export class Fn { /** * Base class for tokens that represent CloudFormation intrinsic functions. */ -class FnBase extends CloudFormationToken { +class FnBase extends Token { constructor(name: string, value: any) { super({ [name]: value }); } @@ -442,13 +444,19 @@ class FnCidr extends FnBase { } } +class FnConditionBase extends Token implements IConditionExpression { + constructor(type: string, value: any) { + super({ [type]: value }); + } +} + /** * Returns true if all the specified conditions evaluate to true, or returns false if any one * of the conditions evaluates to false. ``Fn::And`` acts as an AND operator. The minimum number of * conditions that you can include is 2, and the maximum is 10. */ -class FnAnd extends FnCondition { - constructor(...condition: FnCondition[]) { +class FnAnd extends FnConditionBase { + constructor(...condition: IConditionExpression[]) { super('Fn::And', condition); } } @@ -457,7 +465,7 @@ class FnAnd extends FnCondition { * Compares if two values are equal. Returns true if the two values are equal or false * if they aren't. */ -class FnEquals extends FnCondition { +class FnEquals extends FnConditionBase { /** * Creates an ``Fn::Equals`` condition function. * @param lhs A value of any type that you want to compare. @@ -475,7 +483,7 @@ class FnEquals extends FnCondition { * in the Resources section and Outputs sections of a template. You can use the AWS::NoValue * pseudo parameter as a return value to remove the corresponding property. */ -class FnIf extends FnCondition { +class FnIf extends FnConditionBase { /** * Creates an ``Fn::If`` condition function. * @param condition A reference to a condition in the Conditions section. Use the condition's name to reference it. @@ -491,12 +499,12 @@ class FnIf extends FnCondition { * Returns true for a condition that evaluates to false or returns false for a condition that evaluates to true. * ``Fn::Not`` acts as a NOT operator. */ -class FnNot extends FnCondition { +class FnNot extends FnConditionBase { /** * Creates an ``Fn::Not`` condition function. * @param condition A condition such as ``Fn::Equals`` that evaluates to true or false. */ - constructor(condition: FnCondition) { + constructor(condition: IConditionExpression) { super('Fn::Not', [ condition ]); } } @@ -506,12 +514,12 @@ class FnNot extends FnCondition { * all of the conditions evaluates to false. ``Fn::Or`` acts as an OR operator. The minimum number * of conditions that you can include is 2, and the maximum is 10. */ -class FnOr extends FnCondition { +class FnOr extends FnConditionBase { /** * Creates an ``Fn::Or`` condition function. * @param condition A condition that evaluates to true or false. */ - constructor(...condition: FnCondition[]) { + constructor(...condition: IConditionExpression[]) { super('Fn::Or', condition); } } @@ -519,7 +527,7 @@ class FnOr extends FnCondition { /** * Returns true if a specified string matches at least one value in a list of strings. */ -class FnContains extends FnCondition { +class FnContains extends FnConditionBase { /** * Creates an ``Fn::Contains`` function. * @param listOfStrings A list of strings, such as "A", "B", "C". @@ -533,7 +541,7 @@ class FnContains extends FnCondition { /** * Returns true if a specified string matches all values in a list. */ -class FnEachMemberEquals extends FnCondition { +class FnEachMemberEquals extends FnConditionBase { /** * Creates an ``Fn::EachMemberEquals`` function. * @param listOfStrings A list of strings, such as "A", "B", "C". @@ -548,7 +556,7 @@ class FnEachMemberEquals extends FnCondition { * Returns true if each member in a list of strings matches at least one value in a second * list of strings. */ -class FnEachMemberIn extends FnCondition { +class FnEachMemberIn extends FnConditionBase { /** * Creates an ``Fn::EachMemberIn`` function. * @param stringsToCheck A list of strings, such as "A", "B", "C". AWS CloudFormation checks whether each member in the strings_to_check parameter is in the strings_to_match parameter. @@ -601,3 +609,55 @@ class FnValueOfAll extends FnBase { super('Fn::ValueOfAll', [ parameterType, attribute ]); } } + +/** + * The intrinsic function ``Fn::Join`` appends a set of values into a single value, separated by + * the specified delimiter. If a delimiter is the empty string, the set of values are concatenated + * with no delimiter. + */ +class FnJoin extends Token { + private readonly delimiter: string; + private readonly listOfValues: any[]; + // Cache for the result of resolveValues() - since it otherwise would be computed several times + private _resolvedValues?: any[]; + + /** + * Creates an ``Fn::Join`` function. + * @param delimiter The value you want to occur between fragments. The delimiter will occur between fragments only. + * It will not terminate the final value. + * @param listOfValues The list of values you want combined. + */ + constructor(delimiter: string, listOfValues: any[]) { + if (listOfValues.length === 0) { + throw new Error(`FnJoin requires at least one value to be provided`); + } + super(); + + this.delimiter = delimiter; + this.listOfValues = listOfValues; + } + + public resolve(context: ResolveContext): any { + if (unresolved(this.listOfValues)) { + // This is a list token, don't try to do smart things with it. + return { 'Fn::Join': [ this.delimiter, this.listOfValues ] }; + } + const resolved = this.resolveValues(context); + if (resolved.length === 1) { + return resolved[0]; + } + return { 'Fn::Join': [ this.delimiter, resolved ] }; + } + + /** + * Optimization: if an Fn::Join is nested in another one and they share the same delimiter, then flatten it up. Also, + * if two concatenated elements are literal strings (not tokens), then pre-concatenate them with the delimiter, to + * generate shorter output. + */ + private resolveValues(context: ResolveContext) { + if (this._resolvedValues) { return this._resolvedValues; } + + const resolvedValues = this.listOfValues.map(e => resolve(e, context)); + return this._resolvedValues = minimalCloudFormationJoin(this.delimiter, resolvedValues); + } +} diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/include.ts b/packages/@aws-cdk/cdk/lib/cloudformation/include.ts index ba307cd58b475..a938fe87a58ba 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/include.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/include.ts @@ -1,5 +1,5 @@ import { Construct } from '../core/construct'; -import { StackElement } from './stack'; +import { StackElement } from './stack-element'; export interface IncludeProps { /** diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/instrinsics.ts b/packages/@aws-cdk/cdk/lib/cloudformation/instrinsics.ts new file mode 100644 index 0000000000000..af2094cc5894d --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/cloudformation/instrinsics.ts @@ -0,0 +1,50 @@ +/** + * Do an intelligent CloudFormation join on the given values, producing a minimal expression + */ +export function minimalCloudFormationJoin(delimiter: string, values: any[]): any[] { + let i = 0; + while (i < values.length) { + const el = values[i]; + if (isSplicableFnJoinInstrinsic(el)) { + values.splice(i, 1, ...el['Fn::Join'][1]); + } else if (i > 0 && isPlainString(values[i - 1]) && isPlainString(values[i])) { + values[i - 1] += delimiter + values[i]; + values.splice(i, 1); + } else { + i += 1; + } + } + + return values; + + function isPlainString(obj: any): boolean { + return typeof obj === 'string' && !unresolved(obj); + } + + function isSplicableFnJoinInstrinsic(obj: any): boolean { + return isIntrinsic(obj) + && Object.keys(obj)[0] === 'Fn::Join' + && obj['Fn::Join'][0] === delimiter; + } +} + +/** + * Return whether the given value represents a CloudFormation intrinsic + */ +export function isIntrinsic(x: any) { + if (Array.isArray(x) || x === null || typeof x !== 'object') { return false; } + + const keys = Object.keys(x); + if (keys.length !== 1) { return false; } + + return keys[0] === 'Ref' || keys[0].startsWith('Fn::'); +} + +/** + * Return whether this is an intrinsic that could potentially (or definitely) evaluate to a list + */ +export function canEvaluateToList(x: any) { + return isIntrinsic(x) && ['Ref', 'Fn::GetAtt', 'Fn::GetAZs', 'Fn::Split', 'Fn::FindInMap', 'Fn::ImportValue'].includes(Object.keys(x)[0]); +} + +import { unresolved } from "../core/tokens/unresolved"; diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/logical-id.ts b/packages/@aws-cdk/cdk/lib/cloudformation/logical-id.ts index 54d3465cd2dba..5f858f774efd8 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/logical-id.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/logical-id.ts @@ -1,5 +1,5 @@ import { makeUniqueId } from '../util/uniqueid'; -import { StackElement } from './stack'; +import { StackElement } from './stack-element'; const PATH_SEP = '/'; diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/mapping.ts b/packages/@aws-cdk/cdk/lib/cloudformation/mapping.ts index 65f4025bdd2a3..5446b6977f49b 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/mapping.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/mapping.ts @@ -1,6 +1,6 @@ import { Construct } from '../core/construct'; import { Fn } from './fn'; -import { Referenceable } from './stack'; +import { Referenceable } from './stack-element'; export interface MappingProps { mapping?: { [k1: string]: { [k2: string]: any } }; diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/output.ts b/packages/@aws-cdk/cdk/lib/cloudformation/output.ts index bacb463f17dc1..3ac13d52a34c4 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/output.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/output.ts @@ -1,7 +1,5 @@ import { Construct } from '../core/construct'; -import { Condition } from './condition'; -import { Fn } from './fn'; -import { Stack, StackElement } from './stack'; +import { StackElement } from './stack-element'; export interface OutputProps { /** @@ -51,13 +49,6 @@ export class Output extends StackElement { */ public readonly description?: string; - /** - * The value of the property returned by the aws cloudformation describe-stacks command. - * The value of an output can include literals, parameter references, pseudo-parameters, - * a mapping value, or intrinsic functions. - */ - public readonly value?: any; - /** * The name of the resource output to be exported for a cross-stack reference. * By default, the logical ID of the Output element is used as it's export name. @@ -71,6 +62,8 @@ export class Output extends StackElement { */ public readonly condition?: Condition; + private _value?: any; + /** * Creates an Output value for this stack. * @param parent The parent construct. @@ -80,7 +73,7 @@ export class Output extends StackElement { super(scope, id); this.description = props.description; - this.value = props.value; + this._value = props.value; this.condition = props.condition; if (props.export) { @@ -90,12 +83,21 @@ export class Output extends StackElement { this.export = props.export; } else if (!props.disableExport) { // prefix export name with stack name since exports are global within account + region. - const stackName = Stack.find(this).node.id; + const stackName = require('./stack').Stack.find(this).node.id; this.export = stackName ? stackName + ':' : ''; this.export += this.logicalId; } } + /** + * The value of the property returned by the aws cloudformation describe-stacks command. + * The value of an output can include literals, parameter references, pseudo-parameters, + * a mapping value, or intrinsic functions. + */ + public get value(): any { + return this._value; + } + /** * Returns an FnImportValue bound to this export name. */ @@ -103,7 +105,7 @@ export class Output extends StackElement { if (!this.export) { throw new Error('Cannot create an ImportValue without an export name'); } - return Fn.importValue(this.export); + return fn().importValue(this.export); } public toCloudFormation(): object { @@ -206,7 +208,7 @@ export class StringListOutput extends Construct { condition: props.condition, disableExport: props.disableExport, export: props.export, - value: Fn.join(this.separator, props.values) + value: fn().join(this.separator, props.values) }); } @@ -218,9 +220,16 @@ export class StringListOutput extends Construct { const ret = []; for (let i = 0; i < this.length; i++) { - ret.push(Fn.select(i, Fn.split(this.separator, combined))); + ret.push(fn().select(i, fn().split(this.separator, combined))); } return ret; } } + +function fn() { + // Lazy loading of "Fn" module to break dependency cycles on startup + return require('./fn').Fn; +} + +import { Condition } from './condition'; \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/parameter.ts b/packages/@aws-cdk/cdk/lib/cloudformation/parameter.ts index 0a22b6779cf74..6931baa629760 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/parameter.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/parameter.ts @@ -1,6 +1,6 @@ import { Construct } from '../core/construct'; import { Token } from '../core/tokens'; -import { Ref, Referenceable } from './stack'; +import { Ref, Referenceable } from './stack-element'; export interface ParameterProps { /** diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts index e576320655ec2..e85ac027ad082 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts @@ -1,61 +1,101 @@ -import { CloudFormationToken } from './cloudformation-token'; +import { Construct } from '../core/construct'; +import { Token } from '../core/tokens'; +import { CfnReference } from './cfn-tokens'; -export class PseudoParameter extends CloudFormationToken { - constructor(name: string) { - super({ Ref: name }, name); +/** + * Accessor for pseudo parameters + * + * Since pseudo parameters need to be anchored to a stack somewhere in the + * construct tree, this class takes an scope parameter; the pseudo parameter + * values can be obtained as properties from an scoped object. + */ +export class Aws { + constructor(private readonly scope?: Construct) { } -} -export class AwsAccountId extends PseudoParameter { - constructor() { - super('AWS::AccountId'); + public get accountId(): string { + return new AwsAccountId(this.scope).toString(); + } + + public get urlSuffix(): string { + return new AwsURLSuffix(this.scope).toString(); + } + + public get notificationArns(): string[] { + return new AwsNotificationARNs(this.scope).toList(); + } + + public get partition(): string { + return new AwsPartition(this.scope).toString(); + } + + public get region(): string { + return new AwsRegion(this.scope).toString(); + } + + public get stackId(): string { + return new AwsStackId(this.scope).toString(); + } + + public get stackName(): string { + return new AwsStackName(this.scope).toString(); + } + + public get noValue(): string { + return new AwsNoValue().toString(); } } -export class AwsDomainSuffix extends PseudoParameter { - constructor() { - super('AWS::DomainSuffix'); +class PseudoParameter extends CfnReference { + constructor(name: string, scope: Construct | undefined) { + super({ Ref: name }, name, scope); } } -export class AwsURLSuffix extends PseudoParameter { - constructor() { - super('AWS::URLSuffix'); +class AwsAccountId extends PseudoParameter { + constructor(scope: Construct | undefined) { + super('AWS::AccountId', scope); } } -export class AwsNotificationARNs extends PseudoParameter { - constructor() { - super('AWS::NotificationARNs'); +class AwsURLSuffix extends PseudoParameter { + constructor(scope: Construct | undefined) { + super('AWS::URLSuffix', scope); } } -export class AwsNoValue extends PseudoParameter { - constructor() { - super('AWS::NoValue'); +class AwsNotificationARNs extends PseudoParameter { + constructor(scope: Construct | undefined) { + super('AWS::NotificationARNs', scope); } } -export class AwsPartition extends PseudoParameter { +export class AwsNoValue extends Token { constructor() { - super('AWS::Partition'); + super({ Ref: 'AWS::NoValue' }); } } -export class AwsRegion extends PseudoParameter { - constructor() { - super('AWS::Region'); +class AwsPartition extends PseudoParameter { + constructor(scope: Construct | undefined) { + super('AWS::Partition', scope); } } -export class AwsStackId extends PseudoParameter { - constructor() { - super('AWS::StackId'); +class AwsRegion extends PseudoParameter { + constructor(scope: Construct | undefined) { + super('AWS::Region', scope); } } -export class AwsStackName extends PseudoParameter { - constructor() { - super('AWS::StackName'); +class AwsStackId extends PseudoParameter { + constructor(scope: Construct | undefined) { + super('AWS::StackId', scope); } } + +class AwsStackName extends PseudoParameter { + constructor(scope: Construct | undefined) { + super('AWS::StackName', scope); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts index 92cdd25eeb99e..a2a4243dde2ac 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts @@ -1,10 +1,10 @@ import cxapi = require('@aws-cdk/cx-api'); import { Construct } from '../core/construct'; import { capitalizePropertyNames, ignoreEmpty } from '../core/util'; -import { CloudFormationToken } from './cloudformation-token'; +import { CfnReference } from './cfn-tokens'; import { Condition } from './condition'; import { CreationPolicy, DeletionPolicy, UpdatePolicy } from './resource-policy'; -import { IDependable, Referenceable, StackElement } from './stack'; +import { IDependable, Referenceable, StackElement } from './stack-element'; export interface ResourceProps { /** @@ -105,7 +105,7 @@ export class Resource extends Referenceable { * @param attributeName The name of the attribute. */ public getAtt(attributeName: string) { - return new CloudFormationToken({ 'Fn::GetAtt': [this.logicalId, attributeName] }, `${this.logicalId}.${attributeName}`); + return new CfnReference({ 'Fn::GetAtt': [this.logicalId, attributeName] }, `${this.logicalId}.${attributeName}`, this); } /** @@ -187,12 +187,12 @@ export class Resource extends Referenceable { Resources: { [this.logicalId]: deepMerge({ Type: this.resourceType, - Properties: ignoreEmpty(properties), - DependsOn: ignoreEmpty(this.renderDependsOn()), - CreationPolicy: capitalizePropertyNames(this.options.creationPolicy), - UpdatePolicy: capitalizePropertyNames(this.options.updatePolicy), - DeletionPolicy: capitalizePropertyNames(this.options.deletionPolicy), - Metadata: ignoreEmpty(this.options.metadata), + Properties: ignoreEmpty(this, properties), + DependsOn: ignoreEmpty(this, this.renderDependsOn()), + CreationPolicy: capitalizePropertyNames(this, this.options.creationPolicy), + UpdatePolicy: capitalizePropertyNames(this, this.options.updatePolicy), + DeletionPolicy: capitalizePropertyNames(this, this.options.deletionPolicy), + Metadata: ignoreEmpty(this, this.options.metadata), Condition: this.options.condition && this.options.condition.logicalId }, this.rawOverrides) } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/rule.ts b/packages/@aws-cdk/cdk/lib/cloudformation/rule.ts index 85ff566366897..1b7b0fe5caac9 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/rule.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/rule.ts @@ -1,7 +1,7 @@ import { Construct } from '../core/construct'; import { capitalizePropertyNames } from '../core/util'; -import { FnCondition } from './condition'; -import { Referenceable } from './stack'; +import { IConditionExpression } from './condition'; +import { Referenceable } from './stack-element'; /** * A rule can include a RuleCondition property and must include an Assertions property. @@ -29,7 +29,7 @@ export interface RuleProps { * If the rule condition evaluates to false, the rule doesn't take effect. * If the function in the rule condition evaluates to true, expressions in each assert are evaluated and applied. */ - ruleCondition?: FnCondition; + ruleCondition?: IConditionExpression; /** * Assertions which define the rule. @@ -57,7 +57,7 @@ export class Rule extends Referenceable { * If the rule condition evaluates to false, the rule doesn't take effect. * If the function in the rule condition evaluates to true, expressions in each assert are evaluated and applied. */ - public ruleCondition?: FnCondition; + public ruleCondition?: IConditionExpression; /** * Assertions which define the rule. @@ -81,7 +81,7 @@ export class Rule extends Referenceable { * @param condition The expression to evaluation. * @param description The description of the assertion. */ - public addAssertion(condition: FnCondition, description: string) { + public addAssertion(condition: IConditionExpression, description: string) { if (!this.assertions) { this.assertions = []; } @@ -97,7 +97,7 @@ export class Rule extends Referenceable { Rules: { [this.logicalId]: { RuleCondition: this.ruleCondition, - Assertions: capitalizePropertyNames(this.assertions) + Assertions: capitalizePropertyNames(this, this.assertions) } } }; @@ -111,7 +111,7 @@ export interface RuleAssertion { /** * The assertion. */ - assert: FnCondition; + assert: IConditionExpression; /** * The assertion description. diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/stack-element.ts b/packages/@aws-cdk/cdk/lib/cloudformation/stack-element.ts new file mode 100644 index 0000000000000..175c564aa8bfe --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/cloudformation/stack-element.ts @@ -0,0 +1,169 @@ +import { Construct, IConstruct, PATH_SEP } from "../core/construct"; + +const LOGICAL_ID_MD = 'aws:cdk:logicalId'; + +/** + * Represents a construct that can be "depended on" via `addDependency`. + */ +export interface IDependable { + /** + * Returns the set of all stack elements (resources, parameters, conditions) + * that should be added when a resource "depends on" this construct. + */ + readonly dependencyElements: IDependable[]; +} + +/** + * An element of a CloudFormation stack. + */ +export abstract class StackElement extends Construct implements IDependable { + /** + * Returns `true` if a construct is a stack element (i.e. part of the + * synthesized cloudformation template). + * + * Uses duck-typing instead of `instanceof` to allow stack elements from different + * versions of this library to be included in the same stack. + * + * @returns The construct as a stack element or undefined if it is not a stack element. + */ + public static _asStackElement(construct: IConstruct): StackElement | undefined { + if ('logicalId' in construct && 'toCloudFormation' in construct) { + return construct as StackElement; + } else { + return undefined; + } + } + + /** + * The logical ID for this CloudFormation stack element + */ + public readonly logicalId: string; + + /** + * The stack this Construct has been made a part of + */ + protected stack: Stack; + + /** + * Creates an entity and binds it to a tree. + * Note that the root of the tree must be a Stack object (not just any Root). + * + * @param parent The parent construct + * @param props Construct properties + */ + constructor(scope: Construct, id: string) { + super(scope, id); + const s = Stack.find(this); + if (!s) { + throw new Error('The tree root must be derived from "Stack"'); + } + this.stack = s; + + this.node.addMetadata(LOGICAL_ID_MD, new (require("../core/tokens/token").Token)(() => this.logicalId), this.constructor); + + this.logicalId = this.stack.logicalIds.getLogicalId(this); + } + + /** + * @returns the stack trace of the point where this Resource was created from, sourced + * from the +metadata+ entry typed +aws:cdk:logicalId+, and with the bottom-most + * node +internal+ entries filtered. + */ + public get creationStackTrace(): string[] { + return filterStackTrace(this.node.metadata.find(md => md.type === LOGICAL_ID_MD)!.trace); + + function filterStackTrace(stack: string[]): string[] { + const result = Array.of(...stack); + while (result.length > 0 && shouldFilter(result[result.length - 1])) { + result.pop(); + } + // It's weird if we filtered everything, so return the whole stack... + return result.length === 0 ? stack : result; + } + + function shouldFilter(str: string): boolean { + return str.match(/[^(]+\(internal\/.*/) !== null; + } + } + + /** + * Return the path with respect to the stack + */ + public get stackPath(): string { + return this.node.ancestors(this.stack).map(c => c.node.id).join(PATH_SEP); + } + + public get dependencyElements(): IDependable[] { + return [ this ]; + } + + /** + * Returns the CloudFormation 'snippet' for this entity. The snippet will only be merged + * at the root level to ensure there are no identity conflicts. + * + * For example, a Resource class will return something like: + * { + * Resources: { + * [this.logicalId]: { + * Type: this.resourceType, + * Properties: this.props, + * Condition: this.condition + * } + * } + * } + */ + public abstract toCloudFormation(): object; + + /** + * Automatically detect references in this StackElement + */ + protected prepare() { + try { + // Note: it might be that the properties of the CFN object aren't valid. + // This will usually be preventatively caught in a construct's validate() + // and turned into a nicely descriptive error, but we're running prepare() + // before validate(). Swallow errors that occur because the CFN layer + // doesn't validate completely. + // + // This does make the assumption that the error will not be rectified, + // but the error will be thrown later on anyway. If the error doesn't + // get thrown down the line, we may miss references. + this.node.recordReference(...findTokens(this, () => this.toCloudFormation())); + } catch (e) { + if (e.type !== 'CfnSynthesisError') { throw e; } + } + } +} + +import { CfnReference } from "./cfn-tokens"; + +/** + * A generic, untyped reference to a Stack Element + */ +export class Ref extends CfnReference { + constructor(element: StackElement) { + super({ Ref: element.logicalId }, `${element.logicalId}.Ref`, element); + } +} + +import { findTokens } from "../core/tokens/resolve"; +import { Stack } from "./stack"; + +/** + * Base class for referenceable CloudFormation constructs which are not Resources + * + * These constructs are things like Conditions and Parameters, can be + * referenced by taking the `.ref` attribute. + * + * Resource constructs do not inherit from Referenceable because they have their + * own, more specific types returned from the .ref attribute. Also, some + * resources aren't referenceable at all (such as BucketPolicies or GatewayAttachments). + */ +export abstract class Referenceable extends StackElement { + /** + * Returns a token to a CloudFormation { Ref } that references this entity based on it's logical ID. + */ + public get ref(): string { + return new Ref(this).toString(); + } +} diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts index f7a3af4b62b05..b93d32b7e52a9 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts @@ -1,9 +1,8 @@ import cxapi = require('@aws-cdk/cx-api'); import { App } from '../app'; -import { Construct, IConstruct, PATH_SEP } from '../core/construct'; -import { resolve, Token } from '../core/tokens'; +import { Construct, IConstruct } from '../core/construct'; import { Environment } from '../environment'; -import { CloudFormationToken } from './cloudformation-token'; +import { CfnReference } from './cfn-tokens'; import { HashedAddressingScheme, IAddressingScheme, LogicalIDs } from './logical-id'; import { Resource } from './resource'; @@ -30,17 +29,17 @@ export interface StackProps { export class Stack extends Construct { /** * Traverses the tree and looks up for the Stack root. - * @param node A construct in the tree + * @param scope A construct in the tree * @returns The Stack object (throws if the node is not part of a Stack-rooted tree) */ - public static find(node: Construct): Stack { - let curr: IConstruct | undefined = node; + public static find(scope: IConstruct): Stack { + let curr: IConstruct | undefined = scope; while (curr != null && !Stack.isStack(curr)) { curr = curr.node.scope; } if (curr == null) { - throw new Error(`Cannot find a Stack parent for '${node.toString()}'`); + throw new Error(`Cannot find a Stack parent for '${scope.toString()}'`); } return curr; } @@ -96,11 +95,16 @@ export class Stack extends Construct { */ public readonly name: string; - /** + /* * Used to determine if this construct is a stack. */ protected readonly _isStack = true; + /** + * Other stacks this stack depends on + */ + private readonly stackDependencies = new Set(); + /** * Creates a new stack. * @@ -108,7 +112,7 @@ export class Stack extends Construct { * @param name The name of the CloudFormation stack. Defaults to "Stack". * @param props Stack properties. */ - public constructor(scope?: App, name?: string, props?: StackProps) { + public constructor(scope?: App, name?: string, private readonly props?: StackProps) { // For unit test convenience parents are optional, so bypass the type check when calling the parent. super(scope!, name!); @@ -164,7 +168,7 @@ export class Stack extends Construct { } // resolve all tokens and remove all empties - const ret = resolve(template) || { }; + const ret = this.node.resolve(template) || {}; this.logicalIds.assertAllRenamesApplied(); @@ -235,159 +239,227 @@ export class Stack extends Construct { } /** - * Applied defaults to environment attributes. + * Add a dependency between this stack and another stack */ - private parseEnvironment(props?: StackProps) { - // start with `env`. - const env: Environment = (props && props.env) || { }; + public addDependency(stack: Stack) { + if (stack.dependsOnStack(this)) { + // tslint:disable-next-line:max-line-length + throw new Error(`Stack '${this.name}' already depends on stack '${stack.name}'. Adding this dependency would create a cyclic reference.`); + } + this.stackDependencies.add(stack); + } - // if account is not specified, attempt to read from context. - if (!env.account) { - env.account = this.node.getContext(cxapi.DEFAULT_ACCOUNT_CONTEXT_KEY); + /** + * Return the stacks this stack depends on + */ + public dependencies(): Stack[] { + return Array.from(this.stackDependencies.values()); + } + + /** + * The account in which this stack is defined + * + * Either returns the literal account for this stack if it was specified + * literally upon Stack construction, or a symbolic value that will evaluate + * to the correct account at deployment time. + */ + public get accountId(): string { + if (this.props && this.props.env && this.props.env.account) { + return this.props.env.account; } + return new Aws(this).accountId; + } - // if region is not specified, attempt to read from context. - if (!env.region) { - env.region = this.node.getContext(cxapi.DEFAULT_REGION_CONTEXT_KEY); + /** + * The region in which this stack is defined + * + * Either returns the literal region for this stack if it was specified + * literally upon Stack construction, or a symbolic value that will evaluate + * to the correct region at deployment time. + */ + public get region(): string { + if (this.props && this.props.env && this.props.env.region) { + return this.props.env.region; } + return new Aws(this).region; + } - return env; + /** + * The partition in which this stack is defined + */ + public get partition(): string { + return new Aws(this).partition; } -} -function merge(template: any, part: any) { - for (const section of Object.keys(part)) { - const src = part[section]; + /** + * The Amazon domain suffix for the region in which this stack is defined + */ + public get urlSuffix(): string { + return new Aws(this).urlSuffix; + } - // create top-level section if it doesn't exist - let dest = template[section]; - if (!dest) { - template[section] = dest = src; - } else { - // add all entities from source section to destination section - for (const id of Object.keys(src)) { - if (id in dest) { - throw new Error(`section '${section}' already contains '${id}'`); - } - dest[id] = src[id]; - } - } + /** + * The ID of the stack + * + * @example After resolving, looks like arn:aws:cloudformation:us-west-2:123456789012:stack/teststack/51af3dc0-da77-11e4-872e-1234567db123 + */ + public get stackId(): string { + return new Aws(this).stackId; } -} -const LOGICAL_ID_MD = 'aws:cdk:logicalId'; + /** + * The name of the stack currently being deployed + * + * Only available at deployment time. + */ + public get stackName(): string { + return new Aws(this).stackName; + } -/** - * Represents a construct that can be "depended on" via `addDependency`. - */ -export interface IDependable { /** - * Returns the set of all stack elements (resources, parameters, conditions) - * that should be added when a resource "depends on" this construct. + * Returns the list of notification Amazon Resource Names (ARNs) for the current stack. */ - readonly dependencyElements: IDependable[]; -} + public get notificationArns(): string[] { + return new Aws(this).notificationArns; + } -/** - * An element of a CloudFormation stack. - */ -export abstract class StackElement extends Construct implements IDependable { /** - * Returns `true` if a construct is a stack element (i.e. part of the - * synthesized cloudformation template). + * Creates an ARN from components. + * + * If `partition`, `region` or `account` are not specified, the stack's + * partition, region and account will be used. + * + * If any component is the empty string, an empty string will be inserted + * into the generated ARN at the location that component corresponds to. + * + * The ARN will be formatted as follows: * - * Uses duck-typing instead of `instanceof` to allow stack elements from different - * versions of this library to be included in the same stack. + * arn:{partition}:{service}:{region}:{account}:{resource}{sep}}{resource-name} * - * @returns The construct as a stack element or undefined if it is not a stack element. + * The required ARN pieces that are omitted will be taken from the stack that + * the 'scope' is attached to. If all ARN pieces are supplied, the supplied scope + * can be 'undefined'. */ - public static _asStackElement(construct: IConstruct): StackElement | undefined { - if ('logicalId' in construct && 'toCloudFormation' in construct) { - return construct as StackElement; - } else { - return undefined; - } + public formatArn(components: ArnComponents): string { + return arnFromComponents(components, this); } /** - * The logical ID for this CloudFormation stack element + * Given an ARN, parses it and returns components. + * + * If the ARN is a concrete string, it will be parsed and validated. The + * separator (`sep`) will be set to '/' if the 6th component includes a '/', + * in which case, `resource` will be set to the value before the '/' and + * `resourceName` will be the rest. In case there is no '/', `resource` will + * be set to the 6th components and `resourceName` will be set to the rest + * of the string. + * + * If the ARN includes tokens (or is a token), the ARN cannot be validated, + * since we don't have the actual value yet at the time of this function + * call. You will have to know the separator and the type of ARN. The + * resulting `ArnComponents` object will contain tokens for the + * subexpressions of the ARN, not string literals. In this case this + * function cannot properly parse the complete final resourceName (path) out + * of ARNs that use '/' to both separate the 'resource' from the + * 'resourceName' AND to subdivide the resourceName further. For example, in + * S3 ARNs: + * + * arn:aws:s3:::my_corporate_bucket/path/to/exampleobject.png + * + * After parsing the resourceName will not contain + * 'path/to/exampleobject.png' but simply 'path'. This is a limitation + * because there is no slicing functionality in CloudFormation templates. + * + * @param sep The separator used to separate resource from resourceName + * @param hasName Whether there is a name component in the ARN at all. For + * example, SNS Topics ARNs have the 'resource' component contain the topic + * name, and no 'resourceName' component. + * + * @returns an ArnComponents object which allows access to the various + * components of the ARN. + * + * @returns an ArnComponents object which allows access to the various + * components of the ARN. */ - public readonly logicalId: string; + public parseArn(arn: string, sepIfToken: string = '/', hasName: boolean = true): ArnComponents { + return parseArn(arn, sepIfToken, hasName); + } /** - * The stack this Construct has been made a part of + * Validate stack name + * + * CloudFormation stack names can include dashes in addition to the regular identifier + * character classes, and we don't allow one of the magic markers. */ - protected stack: Stack; + protected _validateId(name: string) { + if (name && !Stack.VALID_STACK_NAME_REGEX.test(name)) { + throw new Error(`Stack name must match the regular expression: ${Stack.VALID_STACK_NAME_REGEX.toString()}, got '${name}'`); + } + } /** - * Creates an entity and binds it to a tree. - * Note that the root of the tree must be a Stack object (not just any Root). + * Prepare stack * - * @param parent The parent construct - * @param props Construct properties - */ - constructor(scope: Construct, id: string) { - super(scope, id); - const s = Stack.find(this); - if (!s) { - throw new Error('The tree root must be derived from "Stack"'); + * Find all CloudFormation references and tell them we're consuming them. + */ + protected prepare() { + for (const ref of this.node.findReferences()) { + if (CfnReference.isCfnReference(ref)) { + ref.consumeFromStack(this); + } } - this.stack = s; - - this.node.addMetadata(LOGICAL_ID_MD, new Token(() => this.logicalId), this.constructor); - - this.logicalId = this.stack.logicalIds.getLogicalId(this); } /** - * @returns the stack trace of the point where this Resource was created from, sourced - * from the +metadata+ entry typed +aws:cdk:logicalId+, and with the bottom-most - * node +internal+ entries filtered. + * Applied defaults to environment attributes. */ - public get creationStackTrace(): string[] { - return filterStackTrace(this.node.metadata.find(md => md.type === LOGICAL_ID_MD)!.trace); + private parseEnvironment(props?: StackProps) { + // start with `env`. + const env: Environment = (props && props.env) || { }; - function filterStackTrace(stack: string[]): string[] { - const result = Array.of(...stack); - while (result.length > 0 && shouldFilter(result[result.length - 1])) { - result.pop(); - } - // It's weird if we filtered everything, so return the whole stack... - return result.length === 0 ? stack : result; + // if account is not specified, attempt to read from context. + if (!env.account) { + env.account = this.node.getContext(cxapi.DEFAULT_ACCOUNT_CONTEXT_KEY); } - function shouldFilter(str: string): boolean { - return str.match(/[^(]+\(internal\/.*/) !== null; + // if region is not specified, attempt to read from context. + if (!env.region) { + env.region = this.node.getContext(cxapi.DEFAULT_REGION_CONTEXT_KEY); } + + return env; } /** - * Return the path with respect to the stack + * Check whether this stack has a (transitive) dependency on another stack */ - public get stackPath(): string { - return this.node.ancestors(this.stack).map(c => c.node.id).join(PATH_SEP); + private dependsOnStack(other: Stack) { + if (this === other) { return true; } + for (const dep of this.stackDependencies) { + if (dep.dependsOnStack(other)) { return true; } + } + return false; } +} - public get dependencyElements(): IDependable[] { - return [ this ]; - } +function merge(template: any, part: any) { + for (const section of Object.keys(part)) { + const src = part[section]; - /** - * Returns the CloudFormation 'snippet' for this entity. The snippet will only be merged - * at the root level to ensure there are no identity conflicts. - * - * For example, a Resource class will return something like: - * { - * Resources: { - * [this.logicalId]: { - * Type: this.resourceType, - * Properties: this.props, - * Condition: this.condition - * } - * } - * } - */ - public abstract toCloudFormation(): object; + // create top-level section if it doesn't exist + let dest = template[section]; + if (!dest) { + template[section] = dest = src; + } else { + // add all entities from source section to destination section + for (const id of Object.keys(src)) { + if (id in dest) { + throw new Error(`section '${section}' already contains '${id}'`); + } + dest[id] = src[id]; + } + } + } } /** @@ -416,25 +488,6 @@ export interface TemplateOptions { metadata?: { [key: string]: any }; } -/** - * Base class for referenceable CloudFormation constructs which are not Resources - * - * These constructs are things like Conditions and Parameters, can be - * referenced by taking the `.ref` attribute. - * - * Resource constructs do not inherit from Referenceable because they have their - * own, more specific types returned from the .ref attribute. Also, some - * resources aren't referenceable at all (such as BucketPolicies or GatewayAttachments). - */ -export abstract class Referenceable extends StackElement { - /** - * Returns a token to a CloudFormation { Ref } that references this entity based on it's logical ID. - */ - public get ref(): string { - return new Ref(this).toString(); - } -} - /** * Collect all StackElements from a construct * @@ -455,11 +508,7 @@ function stackElements(node: IConstruct, into: StackElement[] = []): StackElemen return into; } -/** - * A generic, untyped reference to a Stack Element - */ -export class Ref extends CloudFormationToken { - constructor(element: StackElement) { - super({ Ref: element.logicalId }, `${element.logicalId}.Ref`); - } -} +// These imports have to be at the end to prevent circular imports +import { ArnComponents, arnFromComponents, parseArn } from './arn'; +import { Aws } from './pseudo'; +import { StackElement } from './stack-element'; \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/construct.ts b/packages/@aws-cdk/cdk/lib/core/construct.ts index 96b6453b35602..e6d2c76e67ba9 100644 --- a/packages/@aws-cdk/cdk/lib/core/construct.ts +++ b/packages/@aws-cdk/cdk/lib/core/construct.ts @@ -1,6 +1,8 @@ import cxapi = require('@aws-cdk/cx-api'); +import { CloudFormationJSON } from '../cloudformation/cloudformation-json'; import { makeUniqueId } from '../util/uniqueid'; -import { unresolved } from './tokens'; +import { Token, unresolved } from './tokens'; +import { resolve } from './tokens/resolve'; export const PATH_SEP = '/'; /** @@ -35,6 +37,7 @@ export class ConstructNode { private readonly _children: { [name: string]: IConstruct } = { }; private readonly context: { [key: string]: any } = { }; private readonly _metadata = new Array(); + private readonly references = new Set(); /** * If this is set to 'true'. addChild() calls for this construct and any child @@ -150,7 +153,23 @@ export class ConstructNode { * All direct children of this construct. */ public get children() { - return Object.keys(this._children).map(k => this._children[k]); + return Object.values(this._children); + } + + /** + * Return this construct and all of its children in the given order + */ + public findAll(order: ConstructOrder = ConstructOrder.DepthFirst): IConstruct[] { + const ret = new Array(); + const queue: IConstruct[] = [this.host]; + + while (queue.length > 0) { + const next = order === ConstructOrder.BreadthFirst ? queue.splice(0, 1)[0] : queue.pop()!; + ret.push(next); + queue.push(...next.node.children); + } + + return ret; } /** @@ -263,10 +282,23 @@ export class ConstructNode { errors = errors.concat(child.node.validateTree()); } - const localErrors: string[] = this.host.validate(); + const localErrors: string[] = (this.host as any).validate(); return errors.concat(localErrors.map(msg => new ValidationError(this.host, msg))); } + /** + * Run 'prepare()' on all constructs in the tree + */ + public prepareTree() { + const constructs = this.host.node.findAll(ConstructOrder.BreadthFirst); + // Use .reverse() to achieve post-order traversal + for (const construct of constructs.reverse()) { + if (Construct.isConstruct(construct)) { + (construct as any).prepare(); + } + } + } + /** * Return the ancestors (including self) of this Construct up until and excluding the indicated component * @@ -350,15 +382,6 @@ export class ConstructNode { this._locked = false; } - /** - * Return the path of components up to but excluding the root - */ - private rootPath(): IConstruct[] { - const ancestors = this.ancestors(); - ancestors.shift(); - return ancestors; - } - /** * Returns true if this construct or the scopes in which it is defined are * locked. @@ -375,6 +398,64 @@ export class ConstructNode { return false; } + /** + * Resolve a tokenized value in the context of the current Construct + */ + public resolve(obj: any): any { + return resolve(obj, { + scope: this.host, + prefix: [] + }); + } + + /** + * Convert an object, potentially containing tokens, to a JSON string + */ + public stringifyJson(obj: any): string { + return CloudFormationJSON.stringify(obj, this.host).toString(); + } + + /** + * Record a reference originating from this construct node + */ + public recordReference(...refs: Token[]) { + for (const ref of refs) { + if (ref.isReference) { + this.references.add(ref); + } + } + } + + /** + * Return all references of the given type originating from this node or any of its children + */ + public findReferences(): Token[] { + const ret = new Set(); + + function recurse(node: ConstructNode) { + for (const ref of node.references) { + ret.add(ref); + } + + for (const child of node.children) { + recurse(child.node); + } + } + + recurse(this); + + return Array.from(ret); + } + + /** + * Return the path of components up to but excluding the root + */ + private rootPath(): IConstruct[] { + const ancestors = this.ancestors(); + ancestors.shift(); + return ancestors; + } + /** * If the construct ID contains a path separator, it is replaced by double dash (`--`). */ @@ -391,6 +472,13 @@ export class ConstructNode { * another construct. */ export class Construct implements IConstruct { + /** + * Return whether the given object is a Construct + */ + public static isConstruct(x: IConstruct): x is Construct { + return (x as any).prepare !== undefined && (x as any).validate !== undefined; + } + /** * Construct node. */ @@ -417,14 +505,30 @@ export class Construct implements IConstruct { } /** + * Validate the current construct. + * * This method can be implemented by derived constructs in order to perform * validation logic. It is called on all constructs before synthesis. * * @returns An array of validation error messages, or an empty array if there the construct is valid. */ - public validate(): string[] { + protected validate(): string[] { return []; } + + /** + * Perform final modifications before synthesis + * + * This method can be implemented by derived constructs in order to perform + * final changes before synthesis. prepare() will be called after child + * constructs have been prepared. + * + * This is an advanced framework feature. Only use this if you + * understand the implications. + */ + protected prepare(): void { + // Intentionally left blank + } } /** @@ -479,3 +583,18 @@ function createStackTrace(below: Function): string[] { } return object.stack.split('\n').slice(1).map(s => s.replace(/^\s*at\s+/, '')); } + +/** + * In what order to return constructs + */ +export enum ConstructOrder { + /** + * Breadth first + */ + BreadthFirst, + + /** + * Depth first + */ + DepthFirst +} diff --git a/packages/@aws-cdk/cdk/lib/core/tag-manager.ts b/packages/@aws-cdk/cdk/lib/core/tag-manager.ts index 8ee0ab75b07e3..7f5f6c57d3814 100644 --- a/packages/@aws-cdk/cdk/lib/core/tag-manager.ts +++ b/packages/@aws-cdk/cdk/lib/core/tag-manager.ts @@ -1,5 +1,5 @@ import { Construct, IConstruct } from './construct'; -import { Token } from './tokens'; +import { ResolveContext, Token } from './tokens'; /** * ITaggable indicates a entity manages tags via the `tags` property @@ -171,7 +171,7 @@ export class TagManager extends Token { /** * Converts the `tags` to a Token for use in lazy evaluation */ - public resolve(): any { + public resolve(_context: ResolveContext): any { // need this for scoping const blockedTags = this.blockedTags; function filterTags(_tags: FullTags, filter: TagProps = {}): Tags { diff --git a/packages/@aws-cdk/cdk/lib/core/tokens.ts b/packages/@aws-cdk/cdk/lib/core/tokens.ts deleted file mode 100644 index 8b5b4b99a3dde..0000000000000 --- a/packages/@aws-cdk/cdk/lib/core/tokens.ts +++ /dev/null @@ -1,520 +0,0 @@ -import { Construct } from "./construct"; - -/** - * If objects has a function property by this name, they will be considered tokens, and this - * function will be called to resolve the value for this object. - */ -export const RESOLVE_METHOD = 'resolve'; - -/** - * Represents a special or lazily-evaluated value. - * - * Can be used to delay evaluation of a certain value in case, for example, - * that it requires some context or late-bound data. Can also be used to - * mark values that need special processing at document rendering time. - * - * Tokens can be embedded into strings while retaining their original - * semantics. - */ -export class Token { - private tokenStringification?: string; - private tokenListification?: string[]; - - /** - * Creates a token that resolves to `value`. - * - * If value is a function, the function is evaluated upon resolution and - * the value it returns will be used as the token's value. - * - * displayName is used to represent the Token when it's embedded into a string; it - * will look something like this: - * - * "embedded in a larger string is ${Token[DISPLAY_NAME.123]}" - * - * This value is used as a hint to humans what the meaning of the Token is, - * and does not have any effect on the evaluation. - * - * Must contain only alphanumeric and simple separator characters (_.:-). - * - * @param valueOrFunction What this token will evaluate to, literal or function. - * @param displayName A human-readable display hint for this Token - */ - constructor(private readonly valueOrFunction?: any, private readonly displayName?: string) { - } - - /** - * @returns The resolved value for this token. - */ - public resolve(): any { - let value = this.valueOrFunction; - if (typeof(value) === 'function') { - value = value(); - } - - return value; - } - - /** - * Return a reversible string representation of this token - * - * If the Token is initialized with a literal, the stringified value of the - * literal is returned. Otherwise, a special quoted string representation - * of the Token is returned that can be embedded into other strings. - * - * Strings with quoted Tokens in them can be restored back into - * complex values with the Tokens restored by calling `resolve()` - * on the string. - */ - public toString(): string { - const valueType = typeof this.valueOrFunction; - // Optimization: if we can immediately resolve this, don't bother - // registering a Token. - if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') { - return this.valueOrFunction.toString(); - } - - if (this.tokenStringification === undefined) { - this.tokenStringification = TOKEN_MAP.registerString(this, this.displayName); - } - return this.tokenStringification; - } - - /** - * Turn this Token into JSON - * - * This gets called by JSON.stringify(). We want to prohibit this, because - * it's not possible to do this properly, so we just throw an error here. - */ - public toJSON(): any { - // tslint:disable-next-line:max-line-length - throw new Error('JSON.stringify() cannot be applied to structure with a Token in it. Use a document-specific stringification method instead.'); - } - - /** - * Return a string list representation of this token - * - * Call this if the Token intrinsically evaluates to a list of strings. - * If so, you can represent the Token in a similar way in the type - * system. - * - * Note that even though the Token is represented as a list of strings, you - * still cannot do any operations on it such as concatenation, indexing, - * or taking its length. The only useful operations you can do to these lists - * is constructing a `FnJoin` or a `FnSelect` on it. - */ - public toList(): string[] { - const valueType = typeof this.valueOrFunction; - if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') { - throw new Error('Got a literal Token value; cannot be encoded as a list.'); - } - - if (this.tokenListification === undefined) { - this.tokenListification = TOKEN_MAP.registerList(this, this.displayName); - } - return this.tokenListification; - } - - /** - * Return a concated version of this Token in a string context - * - * The default implementation of this combines strings, but specialized - * implements of Token can return a more appropriate value. - */ - public concat(left: any | undefined, right: any | undefined): Token { - const parts = [left, resolve(this), right].filter(x => x !== undefined); - return new Token(parts.map(x => `${x}`).join('')); - } -} - -/** - * Returns true if obj is a token (i.e. has the resolve() method or is a string - * that includes token markers), or it's a listifictaion of a Token string. - * - * @param obj The object to test. - */ -export function unresolved(obj: any): boolean { - if (typeof(obj) === 'string') { - return TOKEN_MAP.createStringTokenString(obj).test(); - } else if (Array.isArray(obj) && obj.length === 1) { - return isListToken(obj[0]); - } else { - return typeof(obj[RESOLVE_METHOD]) === 'function'; - } -} - -/** - * Resolves an object by evaluating all tokens and removing any undefined or empty objects or arrays. - * Values can only be primitives, arrays or tokens. Other objects (i.e. with methods) will be rejected. - * - * @param obj The object to resolve. - * @param prefix Prefix key path components for diagnostics. - */ -export function resolve(obj: any, prefix?: string[]): any { - const path = prefix || [ ]; - const pathName = '/' + path.join('/'); - - // protect against cyclic references by limiting depth. - if (path.length > 200) { - throw new Error('Unable to resolve object tree with circular reference. Path: ' + pathName); - } - - // - // undefined - // - - if (typeof(obj) === 'undefined') { - return undefined; - } - - // - // null - // - - if (obj === null) { - return null; - } - - // - // functions - not supported (only tokens are supported) - // - - if (typeof(obj) === 'function') { - throw new Error(`Trying to resolve a non-data object. Only token are supported for lazy evaluation. Path: ${pathName}. Object: ${obj}`); - } - - // - // string - potentially replace all stringified Tokens - // - if (typeof(obj) === 'string') { - return TOKEN_MAP.resolveStringTokens(obj as string); - } - - // - // primitives - as-is - // - - if (typeof(obj) !== 'object' || obj instanceof Date) { - return obj; - } - - // - // arrays - resolve all values, remove undefined and remove empty arrays - // - - if (Array.isArray(obj)) { - if (containsListToken(obj)) { - return TOKEN_MAP.resolveListTokens(obj); - } - - const arr = obj - .map((x, i) => resolve(x, path.concat(i.toString()))) - .filter(x => typeof(x) !== 'undefined'); - - return arr; - } - - // - // tokens - invoke 'resolve' and continue to resolve recursively - // - - if (unresolved(obj)) { - const value = obj[RESOLVE_METHOD](); - return resolve(value, path); - } - - // - // objects - deep-resolve all values - // - - // Must not be a Construct at this point, otherwise you probably made a type - // mistake somewhere and resolve will get into an infinite loop recursing into - // child.parent <---> parent.children - if (obj instanceof Construct) { - throw new Error('Trying to resolve() a Construct at ' + pathName); - } - - const result: any = { }; - for (const key of Object.keys(obj)) { - const resolvedKey = resolve(key); - if (typeof(resolvedKey) !== 'string') { - throw new Error(`The key "${key}" has been resolved to ${JSON.stringify(resolvedKey)} but must be resolvable to a string`); - } - - const value = resolve(obj[key], path.concat(key)); - - // skip undefined - if (typeof(value) === 'undefined') { - continue; - } - - result[resolvedKey] = value; - } - - return result; -} - -function isListToken(x: any) { - return typeof(x) === 'string' && TOKEN_MAP.createListTokenString(x).test(); -} - -function containsListToken(xs: any[]) { - return xs.some(isListToken); -} - -/** - * Central place where we keep a mapping from Tokens to their String representation - * - * The string representation is used to embed token into strings, - * and stored to be able to - * - * All instances of TokenStringMap share the same storage, so that this process - * works even when different copies of the library are loaded. - */ -class TokenMap { - private readonly tokenMap: {[key: string]: Token}; - - constructor() { - const glob = global as any; - this.tokenMap = glob.__cdkTokenMap = glob.__cdkTokenMap || {}; - } - - /** - * Generate a unique string for this Token, returning a key - * - * Every call for the same Token will produce a new unique string, no - * attempt is made to deduplicate. Token objects should cache the - * value themselves, if required. - * - * The token can choose (part of) its own representation string with a - * hint. This may be used to produce aesthetically pleasing and - * recognizable token representations for humans. - */ - public registerString(token: Token, representationHint?: string): string { - const key = this.register(token, representationHint); - return `${BEGIN_STRING_TOKEN_MARKER}${key}${END_TOKEN_MARKER}`; - } - - /** - * Generate a unique string for this Token, returning a key - */ - public registerList(token: Token, representationHint?: string): string[] { - const key = this.register(token, representationHint); - return [`${BEGIN_LIST_TOKEN_MARKER}${key}${END_TOKEN_MARKER}`]; - } - - /** - * Returns a `TokenString` for this string. - */ - public createStringTokenString(s: string) { - return new TokenString(s, BEGIN_STRING_TOKEN_MARKER, `[${VALID_KEY_CHARS}]+`, END_TOKEN_MARKER); - } - - /** - * Returns a `TokenString` for this string. - */ - public createListTokenString(s: string) { - return new TokenString(s, BEGIN_LIST_TOKEN_MARKER, `[${VALID_KEY_CHARS}]+`, END_TOKEN_MARKER); - } - - /** - * Replace any Token markers in this string with their resolved values - */ - public resolveStringTokens(s: string): any { - const str = this.createStringTokenString(s); - const fragments = str.split(this.lookupToken.bind(this)); - return fragments.join(); - } - - public resolveListTokens(xs: string[]): any { - // Must be a singleton list token, because concatenation is not allowed. - if (xs.length !== 1) { - throw new Error(`Cannot add elements to list token, got: ${xs}`); - } - - const str = this.createListTokenString(xs[0]); - const fragments = str.split(this.lookupToken.bind(this)); - if (fragments.length !== 1) { - throw new Error(`Cannot concatenate strings in a tokenized string array, got: ${xs[0]}`); - } - return fragments.values()[0]; - } - - /** - * Find a Token by key - */ - public lookupToken(key: string): Token { - if (!(key in this.tokenMap)) { - throw new Error(`Unrecognized token key: ${key}`); - } - - return this.tokenMap[key]; - } - - private register(token: Token, representationHint?: string): string { - const counter = Object.keys(this.tokenMap).length; - const representation = representationHint || `TOKEN`; - - const key = `${representation}.${counter}`; - if (new RegExp(`[^${VALID_KEY_CHARS}]`).exec(key)) { - throw new Error(`Invalid characters in token representation: ${key}`); - } - - this.tokenMap[key] = token; - return key; - } -} - -const BEGIN_STRING_TOKEN_MARKER = '${Token['; -const BEGIN_LIST_TOKEN_MARKER = '#{Token['; -const END_TOKEN_MARKER = ']}'; -const VALID_KEY_CHARS = 'a-zA-Z0-9:._-'; - -/** - * Singleton instance of the token string map - */ -const TOKEN_MAP = new TokenMap(); - -/** - * Interface that Token joiners implement - */ -export interface ITokenJoiner { - /** - * The name of the joiner. - * - * Must be unique per joiner: this value will be used to assert that there - * is exactly only type of joiner in a join operation. - */ - id: string; - - /** - * Return the language intrinsic that will combine the strings in the given engine - */ - join(fragments: any[]): any; -} - -/** - * A string with markers in it that can be resolved to external values - */ -class TokenString { - private pattern: string; - - constructor( - private readonly str: string, - private readonly beginMarker: string, - private readonly idPattern: string, - private readonly endMarker: string) { - this.pattern = `${regexQuote(this.beginMarker)}(${this.idPattern})${regexQuote(this.endMarker)}`; - } - - /** - * Split string on markers, substituting markers with Tokens - */ - public split(lookup: (id: string) => Token): TokenStringFragments { - const re = new RegExp(this.pattern, 'g'); - const ret = new TokenStringFragments(); - - let rest = 0; - let m = re.exec(this.str); - while (m) { - if (m.index > rest) { - ret.addString(this.str.substring(rest, m.index)); - } - - ret.addToken(lookup(m[1])); - - rest = re.lastIndex; - m = re.exec(this.str); - } - - if (rest < this.str.length) { - ret.addString(this.str.substring(rest)); - } - - return ret; - } - - /** - * Indicates if this string includes tokens. - */ - public test(): boolean { - const re = new RegExp(this.pattern, 'g'); - return re.test(this.str); - } -} - -/** - * Result of the split of a string with Tokens - * - * Either a literal part of the string, or an unresolved Token. - */ -type StringFragment = { type: 'string'; str: string }; -type TokenFragment = { type: 'token'; token: Token }; -type Fragment = StringFragment | TokenFragment; - -/** - * Fragments of a string with markers - */ -class TokenStringFragments { - private readonly fragments = new Array(); - - public get length() { - return this.fragments.length; - } - - public values(): any[] { - return this.fragments.map(f => f.type === 'token' ? resolve(f.token) : f.str); - } - - public addString(str: string) { - this.fragments.push({ type: 'string', str }); - } - - public addToken(token: Token) { - this.fragments.push({ type: 'token', token }); - } - - /** - * Combine the resolved string fragments using the Tokens to join. - * - * Resolves the result. - */ - public join(): any { - if (this.fragments.length === 0) { return ''; } - if (this.fragments.length === 1) { return resolveFragment(this.fragments[0]); } - - const first = this.fragments[0]; - - let i; - let token: Token; - - if (first.type === 'token') { - token = first.token; - i = 1; - } else { - // We never have two strings in a row - token = (this.fragments[1] as TokenFragment).token.concat(first.str, undefined); - i = 2; - } - - while (i < this.fragments.length) { - token = token.concat(undefined, resolveFragment(this.fragments[i])); - i++; - } - - return resolve(token); - } -} - -/** - * Resolve the value from a single fragment - */ -function resolveFragment(fragment: Fragment): any { - return fragment.type === 'string' ? fragment.str : resolve(fragment.token); -} - -/** - * Quote a string for use in a regex - */ -function regexQuote(s: string) { - return s.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&"); -} diff --git a/packages/@aws-cdk/cdk/lib/core/tokens/cfn-concat.ts b/packages/@aws-cdk/cdk/lib/core/tokens/cfn-concat.ts new file mode 100644 index 0000000000000..f454521bce255 --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/core/tokens/cfn-concat.ts @@ -0,0 +1,22 @@ +/** + * Produce a CloudFormation expression to concat two arbitrary expressions when resolving + */ +export function cloudFormationConcat(left: any | undefined, right: any | undefined): any { + if (left === undefined && right === undefined) { return ''; } + + const parts = new Array(); + if (left !== undefined) { parts.push(left); } + if (right !== undefined) { parts.push(right); } + + // Some case analysis to produce minimal expressions + if (parts.length === 1) { return parts[0]; } + if (parts.length === 2 && typeof parts[0] === 'string' && typeof parts[1] === 'string') { + return parts[0] + parts[1]; + } + + // Otherwise return a Join intrinsic (already in the target document language to avoid taking + // circular dependencies on FnJoin & friends) + return { 'Fn::Join': ['', minimalCloudFormationJoin('', parts)] }; +} + +import { minimalCloudFormationJoin } from "../../cloudformation/instrinsics"; \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/tokens/encoding.ts b/packages/@aws-cdk/cdk/lib/core/tokens/encoding.ts new file mode 100644 index 0000000000000..301698214dbb9 --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/core/tokens/encoding.ts @@ -0,0 +1,288 @@ +import { resolve } from "./resolve"; +import { ResolveContext, Token } from "./token"; +import { unresolved } from "./unresolved"; + +// Encoding Tokens into native types; should not be exported + +/** + * Central place where we keep a mapping from Tokens to their String representation + * + * The string representation is used to embed token into strings, + * and stored to be able to + * + * All instances of TokenStringMap share the same storage, so that this process + * works even when different copies of the library are loaded. + */ +export class TokenMap { + private readonly tokenMap: {[key: string]: Token} = {}; + + /** + * Generate a unique string for this Token, returning a key + * + * Every call for the same Token will produce a new unique string, no + * attempt is made to deduplicate. Token objects should cache the + * value themselves, if required. + * + * The token can choose (part of) its own representation string with a + * hint. This may be used to produce aesthetically pleasing and + * recognizable token representations for humans. + */ + public registerString(token: Token, representationHint?: string): string { + const key = this.register(token, representationHint); + return `${BEGIN_STRING_TOKEN_MARKER}${key}${END_TOKEN_MARKER}`; + } + + /** + * Generate a unique string for this Token, returning a key + */ + public registerList(token: Token, representationHint?: string): string[] { + const key = this.register(token, representationHint); + return [`${BEGIN_LIST_TOKEN_MARKER}${key}${END_TOKEN_MARKER}`]; + } + + /** + * Returns a `TokenString` for this string. + */ + public createStringTokenString(s: string) { + return new TokenString(s, BEGIN_STRING_TOKEN_MARKER, `[${VALID_KEY_CHARS}]+`, END_TOKEN_MARKER); + } + + /** + * Returns a `TokenString` for this string. + */ + public createListTokenString(s: string) { + return new TokenString(s, BEGIN_LIST_TOKEN_MARKER, `[${VALID_KEY_CHARS}]+`, END_TOKEN_MARKER); + } + + /** + * Replace any Token markers in this string with their resolved values + */ + public resolveStringTokens(s: string, context: ResolveContext): any { + const str = this.createStringTokenString(s); + const fragments = str.split(this.lookupToken.bind(this)); + // require() here to break cyclic dependencies + const ret = fragments.mapUnresolved(x => resolve(x, context)).join(require('./cfn-concat').cloudFormationConcat); + if (unresolved(ret)) { + return resolve(ret, context); + } + return ret; + } + + public resolveListTokens(xs: string[], context: ResolveContext): any { + // Must be a singleton list token, because concatenation is not allowed. + if (xs.length !== 1) { + throw new Error(`Cannot add elements to list token, got: ${xs}`); + } + + const str = this.createListTokenString(xs[0]); + const fragments = str.split(this.lookupToken.bind(this)); + if (fragments.length !== 1) { + throw new Error(`Cannot concatenate strings in a tokenized string array, got: ${xs[0]}`); + } + return fragments.mapUnresolved(x => resolve(x, context)).values[0]; + } + + /** + * Find a Token by key + */ + public lookupToken(key: string): Token { + if (!(key in this.tokenMap)) { + throw new Error(`Unrecognized token key: ${key}`); + } + + return this.tokenMap[key]; + } + + private register(token: Token, representationHint?: string): string { + const counter = Object.keys(this.tokenMap).length; + const representation = representationHint || `TOKEN`; + + const key = `${representation}.${counter}`; + if (new RegExp(`[^${VALID_KEY_CHARS}]`).exec(key)) { + throw new Error(`Invalid characters in token representation: ${key}`); + } + + this.tokenMap[key] = token; + return key; + } +} + +const BEGIN_STRING_TOKEN_MARKER = '${Token['; +const BEGIN_LIST_TOKEN_MARKER = '#{Token['; +const END_TOKEN_MARKER = ']}'; +const VALID_KEY_CHARS = 'a-zA-Z0-9:._-'; + +/** + * Interface that Token joiners implement + */ +export interface ITokenJoiner { + /** + * The name of the joiner. + * + * Must be unique per joiner: this value will be used to assert that there + * is exactly only type of joiner in a join operation. + */ + id: string; + + /** + * Return the language intrinsic that will combine the strings in the given engine + */ + join(fragments: any[]): any; +} + +/** + * A string with markers in it that can be resolved to external values + */ +class TokenString { + private pattern: string; + + constructor( + private readonly str: string, + private readonly beginMarker: string, + private readonly idPattern: string, + private readonly endMarker: string) { + this.pattern = `${regexQuote(this.beginMarker)}(${this.idPattern})${regexQuote(this.endMarker)}`; + } + + /** + * Split string on markers, substituting markers with Tokens + */ + public split(lookup: (id: string) => Token): TokenizedStringFragments { + const re = new RegExp(this.pattern, 'g'); + const ret = new TokenizedStringFragments(); + + let rest = 0; + let m = re.exec(this.str); + while (m) { + if (m.index > rest) { + ret.addLiteral(this.str.substring(rest, m.index)); + } + + ret.addUnresolved(lookup(m[1])); + + rest = re.lastIndex; + m = re.exec(this.str); + } + + if (rest < this.str.length) { + ret.addLiteral(this.str.substring(rest)); + } + + return ret; + } + + /** + * Indicates if this string includes tokens. + */ + public test(): boolean { + const re = new RegExp(this.pattern, 'g'); + return re.test(this.str); + } +} + +/** + * Result of the split of a string with Tokens + * + * Either a literal part of the string, or an unresolved Token. + */ +type LiteralFragment = { type: 'literal'; lit: any; }; +type UnresolvedFragment = { type: 'unresolved'; token: any; }; +type Fragment = LiteralFragment | UnresolvedFragment; + +/** + * Fragments of a string with markers + */ +class TokenizedStringFragments { + private readonly fragments = new Array(); + + public get length() { + return this.fragments.length; + } + + public get values(): any[] { + return this.fragments.map(f => f.type === 'unresolved' ? f.token : f.lit); + } + + public addLiteral(lit: any) { + this.fragments.push({ type: 'literal', lit }); + } + + public addUnresolved(token: Token) { + this.fragments.push({ type: 'unresolved', token }); + } + + public mapUnresolved(fn: (t: any) => any): TokenizedStringFragments { + const ret = new TokenizedStringFragments(); + + for (const f of this.fragments) { + switch (f.type) { + case 'literal': + ret.addLiteral(f.lit); + break; + case 'unresolved': + const mappedToken = fn(f.token); + + if (unresolved(mappedToken)) { + ret.addUnresolved(mappedToken); + } else { + ret.addLiteral(mappedToken); + } + break; + } + } + + return ret; + } + + /** + * Combine the resolved string fragments using the Tokens to join. + * + * Resolves the result. + */ + public join(concat: ConcatFunc): any { + if (this.fragments.length === 0) { return concat(undefined, undefined); } + + const values = this.fragments.map(fragmentValue); + + while (values.length > 1) { + const prefix = values.splice(0, 2); + values.splice(0, 0, concat(prefix[0], prefix[1])); + } + + return values[0]; + } +} + +/** + * Resolve the value from a single fragment + */ +function fragmentValue(fragment: Fragment): any { + return fragment.type === 'literal' ? fragment.lit : fragment.token; +} + +/** + * Quote a string for use in a regex + */ +function regexQuote(s: string) { + return s.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&"); +} + +/** + * Function used to concatenate symbols in the target document language + */ +export type ConcatFunc = (left: any | undefined, right: any | undefined) => any; + +const glob = global as any; + +/** + * Singleton instance of the token string map + */ +export const TOKEN_MAP: TokenMap = glob.__cdkTokenMap = glob.__cdkTokenMap || new TokenMap(); + +export function isListToken(x: any) { + return typeof(x) === 'string' && TOKEN_MAP.createListTokenString(x).test(); +} + +export function containsListToken(xs: any[]) { + return xs.some(isListToken); +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/tokens/index.ts b/packages/@aws-cdk/cdk/lib/core/tokens/index.ts new file mode 100644 index 0000000000000..af82e0f1c238c --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/core/tokens/index.ts @@ -0,0 +1,4 @@ +// This exports the modules that should be publicly available (not all of them) + +export * from './token'; +export * from './unresolved'; \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/tokens/options.ts b/packages/@aws-cdk/cdk/lib/core/tokens/options.ts new file mode 100644 index 0000000000000..8fb5bc90eee16 --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/core/tokens/options.ts @@ -0,0 +1,56 @@ +import { Token } from "./token"; + +/** + * Function used to preprocess Tokens before resolving + */ +export type CollectFunc = (token: Token) => void; + +/** + * Global options for resolve() + * + * Because there are many independent calls to resolve(), some losing context, + * we cannot simply pass through options at each individual call. Instead, + * we configure global context at the stack synthesis level. + */ +export class ResolveConfiguration { + private readonly options = new Array(); + + public push(options: ResolveOptions): IOptionsContext { + this.options.push(options); + + return { + pop: () => { + if (this.options.length === 0 || this.options[this.options.length - 1] !== options) { + throw new Error('ResolveConfiguration push/pop mismatch'); + } + this.options.pop(); + } + }; + } + + public get collect(): CollectFunc | undefined { + for (let i = this.options.length - 1; i >= 0; i--) { + const ret = this.options[i].collect; + if (ret !== undefined) { return ret; } + } + return undefined; + } +} + +interface IOptionsContext { + pop(): void; +} + +interface ResolveOptions { + /** + * What function to use to preprocess Tokens before resolving them + */ + collect?: CollectFunc; +} + +const glob = global as any; + +/** + * Singleton instance of resolver options + */ +export const RESOLVE_OPTIONS: ResolveConfiguration = glob.__cdkResolveOptions = glob.__cdkResolveOptions || new ResolveConfiguration(); diff --git a/packages/@aws-cdk/cdk/lib/core/tokens/resolve.ts b/packages/@aws-cdk/cdk/lib/core/tokens/resolve.ts new file mode 100644 index 0000000000000..3d3e91e3e627d --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/core/tokens/resolve.ts @@ -0,0 +1,148 @@ +import { IConstruct } from '../construct'; +import { containsListToken, TOKEN_MAP } from "./encoding"; +import { RESOLVE_OPTIONS } from "./options"; +import { RESOLVE_METHOD, ResolveContext, Token } from "./token"; +import { unresolved } from "./unresolved"; + +// This file should not be exported to consumers, resolving should happen through Construct.resolve() + +/** + * Resolves an object by evaluating all tokens and removing any undefined or empty objects or arrays. + * Values can only be primitives, arrays or tokens. Other objects (i.e. with methods) will be rejected. + * + * @param obj The object to resolve. + * @param prefix Prefix key path components for diagnostics. + */ +export function resolve(obj: any, context: ResolveContext): any { + const pathName = '/' + context.prefix.join('/'); + + // protect against cyclic references by limiting depth. + if (context.prefix.length > 200) { + throw new Error('Unable to resolve object tree with circular reference. Path: ' + pathName); + } + + // + // undefined + // + + if (typeof(obj) === 'undefined') { + return undefined; + } + + // + // null + // + + if (obj === null) { + return null; + } + + // + // functions - not supported (only tokens are supported) + // + + if (typeof(obj) === 'function') { + throw new Error(`Trying to resolve a non-data object. Only token are supported for lazy evaluation. Path: ${pathName}. Object: ${obj}`); + } + + // + // string - potentially replace all stringified Tokens + // + if (typeof(obj) === 'string') { + return TOKEN_MAP.resolveStringTokens(obj, context); + } + + // + // primitives - as-is + // + + if (typeof(obj) !== 'object' || obj instanceof Date) { + return obj; + } + + // + // arrays - resolve all values, remove undefined and remove empty arrays + // + + if (Array.isArray(obj)) { + if (containsListToken(obj)) { + return TOKEN_MAP.resolveListTokens(obj, context); + } + + const arr = obj + .map((x, i) => resolve(x, { ...context, prefix: context.prefix.concat(i.toString()) })) + .filter(x => typeof(x) !== 'undefined'); + + return arr; + } + + // + // tokens - invoke 'resolve' and continue to resolve recursively + // + + if (unresolved(obj)) { + const collect = RESOLVE_OPTIONS.collect; + if (collect) { collect(obj); } + const value = obj[RESOLVE_METHOD](context); + return resolve(value, context); + } + + // + // objects - deep-resolve all values + // + + // Must not be a Construct at this point, otherwise you probably made a typo + // mistake somewhere and resolve will get into an infinite loop recursing into + // child.parent <---> parent.children + if (isConstruct(obj)) { + throw new Error('Trying to resolve() a Construct at ' + pathName); + } + + const result: any = { }; + for (const key of Object.keys(obj)) { + const resolvedKey = resolve(key, context); + if (typeof(resolvedKey) !== 'string') { + throw new Error(`The key "${key}" has been resolved to ${JSON.stringify(resolvedKey)} but must be resolvable to a string`); + } + + const value = resolve(obj[key], {...context, prefix: context.prefix.concat(key) }); + + // skip undefined + if (typeof(value) === 'undefined') { + continue; + } + + result[resolvedKey] = value; + } + + return result; +} + +/** + * Find all Tokens that are used in the given structure + */ +export function findTokens(scope: IConstruct, fn: () => any): Token[] { + const ret = new Array(); + + const options = RESOLVE_OPTIONS.push({ collect: ret.push.bind(ret) }); + try { + resolve(fn(), { + scope, + prefix: [] + }); + } finally { + options.pop(); + } + + return ret; +} + +/** + * Determine whether an object is a Construct + * + * Not in 'construct.ts' because that would lead to a dependency cycle via 'uniqueid.ts', + * and this is a best-effort protection against a common programming mistake anyway. + */ +function isConstruct(x: any): boolean { + return x._children !== undefined && x._metadata !== undefined; +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/tokens/token.ts b/packages/@aws-cdk/cdk/lib/core/tokens/token.ts new file mode 100644 index 0000000000000..35924a457f717 --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/core/tokens/token.ts @@ -0,0 +1,133 @@ +import { IConstruct } from "../construct"; +import { TOKEN_MAP } from "./encoding"; + +/** + * If objects has a function property by this name, they will be considered tokens, and this + * function will be called to resolve the value for this object. + */ +export const RESOLVE_METHOD = 'resolve'; + +/** + * Represents a special or lazily-evaluated value. + * + * Can be used to delay evaluation of a certain value in case, for example, + * that it requires some context or late-bound data. Can also be used to + * mark values that need special processing at document rendering time. + * + * Tokens can be embedded into strings while retaining their original + * semantics. + */ +export class Token { + /** + * Indicate whether this Token represent a "reference" + * + * The Construct tree can be queried for the Reference Tokens that + * are used in it. + */ + public readonly isReference?: boolean; + + private tokenStringification?: string; + private tokenListification?: string[]; + + /** + * Creates a token that resolves to `value`. + * + * If value is a function, the function is evaluated upon resolution and + * the value it returns will be used as the token's value. + * + * displayName is used to represent the Token when it's embedded into a string; it + * will look something like this: + * + * "embedded in a larger string is ${Token[DISPLAY_NAME.123]}" + * + * This value is used as a hint to humans what the meaning of the Token is, + * and does not have any effect on the evaluation. + * + * Must contain only alphanumeric and simple separator characters (_.:-). + * + * @param valueOrFunction What this token will evaluate to, literal or function. + * @param displayName A human-readable display hint for this Token + */ + constructor(private readonly valueOrFunction?: any, private readonly displayName?: string) { + } + + /** + * @returns The resolved value for this token. + */ + public resolve(_context: ResolveContext): any { + let value = this.valueOrFunction; + if (typeof(value) === 'function') { + value = value(); + } + + return value; + } + + /** + * Return a reversible string representation of this token + * + * If the Token is initialized with a literal, the stringified value of the + * literal is returned. Otherwise, a special quoted string representation + * of the Token is returned that can be embedded into other strings. + * + * Strings with quoted Tokens in them can be restored back into + * complex values with the Tokens restored by calling `resolve()` + * on the string. + */ + public toString(): string { + const valueType = typeof this.valueOrFunction; + // Optimization: if we can immediately resolve this, don't bother + // registering a Token. + if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') { + return this.valueOrFunction.toString(); + } + + if (this.tokenStringification === undefined) { + this.tokenStringification = TOKEN_MAP.registerString(this, this.displayName); + } + return this.tokenStringification; + } + + /** + * Turn this Token into JSON + * + * This gets called by JSON.stringify(). We want to prohibit this, because + * it's not possible to do this properly, so we just throw an error here. + */ + public toJSON(): any { + // tslint:disable-next-line:max-line-length + throw new Error('JSON.stringify() cannot be applied to structure with a Token in it. Use this.node.stringifyJson() instead.'); + } + + /** + * Return a string list representation of this token + * + * Call this if the Token intrinsically evaluates to a list of strings. + * If so, you can represent the Token in a similar way in the type + * system. + * + * Note that even though the Token is represented as a list of strings, you + * still cannot do any operations on it such as concatenation, indexing, + * or taking its length. The only useful operations you can do to these lists + * is constructing a `FnJoin` or a `FnSelect` on it. + */ + public toList(): string[] { + const valueType = typeof this.valueOrFunction; + if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') { + throw new Error('Got a literal Token value; only intrinsics can ever evaluate to lists.'); + } + + if (this.tokenListification === undefined) { + this.tokenListification = TOKEN_MAP.registerList(this, this.displayName); + } + return this.tokenListification; + } +} + +/** + * Current resolution context for tokens + */ +export interface ResolveContext { + scope: IConstruct; + prefix: string[]; +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/tokens/unresolved.ts b/packages/@aws-cdk/cdk/lib/core/tokens/unresolved.ts new file mode 100644 index 0000000000000..1262f6f86c0fd --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/core/tokens/unresolved.ts @@ -0,0 +1,18 @@ +import { isListToken, TOKEN_MAP } from "./encoding"; +import { RESOLVE_METHOD } from "./token"; + +/** + * Returns true if obj is a token (i.e. has the resolve() method or is a string + * that includes token markers), or it's a listifictaion of a Token string. + * + * @param obj The object to test. + */ +export function unresolved(obj: any): boolean { + if (typeof(obj) === 'string') { + return TOKEN_MAP.createStringTokenString(obj).test(); + } else if (Array.isArray(obj) && obj.length === 1) { + return isListToken(obj[0]); + } else { + return obj && typeof(obj[RESOLVE_METHOD]) === 'function'; + } +} diff --git a/packages/@aws-cdk/cdk/lib/core/util.ts b/packages/@aws-cdk/cdk/lib/core/util.ts index d1be7700e7c26..e083ddc5fc271 100644 --- a/packages/@aws-cdk/cdk/lib/core/util.ts +++ b/packages/@aws-cdk/cdk/lib/core/util.ts @@ -1,18 +1,18 @@ -import { resolve } from './tokens'; +import { IConstruct } from "./construct"; /** * Given an object, converts all keys to PascalCase given they are currently in camel case. * @param obj The object. */ -export function capitalizePropertyNames(obj: any): any { - obj = resolve(obj); +export function capitalizePropertyNames(construct: IConstruct, obj: any): any { + obj = construct.node.resolve(obj); if (typeof(obj) !== 'object') { return obj; } if (Array.isArray(obj)) { - return obj.map(x => capitalizePropertyNames(x)); + return obj.map(x => capitalizePropertyNames(construct, x)); } const newObj: any = { }; @@ -21,7 +21,7 @@ export function capitalizePropertyNames(obj: any): any { const first = key.charAt(0).toUpperCase(); const newKey = first + key.slice(1); - newObj[newKey] = capitalizePropertyNames(value); + newObj[newKey] = capitalizePropertyNames(construct, value); } return newObj; @@ -30,8 +30,8 @@ export function capitalizePropertyNames(obj: any): any { /** * Turns empty arrays/objects to undefined (after evaluating tokens). */ -export function ignoreEmpty(o: any): any { - o = resolve(o); // first resolve tokens, in case they evaluate to 'undefined'. +export function ignoreEmpty(construct: IConstruct, o: any): any { + o = construct.node.resolve(o); // first resolve tokens, in case they evaluate to 'undefined'. // undefined/null if (o == null) { diff --git a/packages/@aws-cdk/cdk/lib/index.ts b/packages/@aws-cdk/cdk/lib/index.ts index e04b06a8812d8..72e4e00592b2d 100644 --- a/packages/@aws-cdk/cdk/lib/index.ts +++ b/packages/@aws-cdk/cdk/lib/index.ts @@ -3,7 +3,7 @@ export * from './core/tokens'; export * from './core/tag-manager'; export * from './cloudformation/cloudformation-json'; -export * from './cloudformation/cloudformation-token'; +export * from './cloudformation/cfn-tokens'; export * from './cloudformation/condition'; export * from './cloudformation/fn'; export * from './cloudformation/include'; @@ -16,6 +16,7 @@ export * from './cloudformation/resource'; export * from './cloudformation/resource-policy'; export * from './cloudformation/rule'; export * from './cloudformation/stack'; +export * from './cloudformation/stack-element'; export * from './cloudformation/dynamic-reference'; export * from './cloudformation/tag'; export * from './cloudformation/removal-policy'; diff --git a/packages/@aws-cdk/cdk/lib/runtime.ts b/packages/@aws-cdk/cdk/lib/runtime.ts index 7087a5cee19ad..e8fb8ba893064 100644 --- a/packages/@aws-cdk/cdk/lib/runtime.ts +++ b/packages/@aws-cdk/cdk/lib/runtime.ts @@ -138,7 +138,7 @@ export class ValidationResult { let message = this.errorTree(); // The first letter will be lowercase, so uppercase it for a nicer error message message = message.substr(0, 1).toUpperCase() + message.substr(1); - throw new TypeError(message); + throw new CfnSynthesisError(message); } } @@ -384,3 +384,8 @@ function isCloudFormationIntrinsic(x: any) { return keys[0] === 'Ref' || keys[0].substr(0, 4) === 'Fn::'; } + +// Cannot be public because JSII gets confused about es5.d.ts +class CfnSynthesisError extends Error { + public readonly type = 'CfnSynthesisError'; +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/util/uniqueid.ts b/packages/@aws-cdk/cdk/lib/util/uniqueid.ts index b9a9e34eb97fe..6f58d44bd7471 100644 --- a/packages/@aws-cdk/cdk/lib/util/uniqueid.ts +++ b/packages/@aws-cdk/cdk/lib/util/uniqueid.ts @@ -1,6 +1,5 @@ // tslint:disable-next-line:no-var-requires import crypto = require('crypto'); -import { unresolved } from '../core/tokens'; /** * Resources with this ID are hidden from humans @@ -36,7 +35,8 @@ export function makeUniqueId(components: string[]) { throw new Error('Unable to calculate a unique id for an empty set of components'); } - const unresolvedTokens = components.filter(c => unresolved(c)); + // Lazy require in order to break a module dependency cycle + const unresolvedTokens = components.filter(c => require('../core/tokens').unresolved(c)); if (unresolvedTokens.length > 0) { throw new Error(`ID components may not include unresolved tokens: ${unresolvedTokens.join(',')}`); } diff --git a/packages/@aws-cdk/cdk/package.json b/packages/@aws-cdk/cdk/package.json index 1461494506fc5..6376c03863e3e 100644 --- a/packages/@aws-cdk/cdk/package.json +++ b/packages/@aws-cdk/cdk/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cdk", - "version": "0.21.0", + "version": "0.22.0", "description": "AWS Cloud Development Kit Core Library", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -62,14 +62,14 @@ "devDependencies": { "@types/js-base64": "^2.3.1", "@types/lodash": "^4.14.118", - "cdk-build-tools": "^0.21.0", - "cfn2ts": "^0.21.0", + "cdk-build-tools": "^0.22.0", + "cfn2ts": "^0.22.0", "fast-check": "^1.7.0", "lodash": "^4.17.11", - "pkglint": "^0.21.0" + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cx-api": "^0.21.0", + "@aws-cdk/cx-api": "^0.22.0", "js-base64": "^2.4.5", "json-diff": "^0.3.1" }, @@ -79,9 +79,9 @@ ], "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cx-api": "^0.21.0" + "@aws-cdk/cx-api": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/test/cloudformation/test.arn.ts b/packages/@aws-cdk/cdk/test/cloudformation/test.arn.ts index 18b90b460db69..cbb3c3caa96de 100644 --- a/packages/@aws-cdk/cdk/test/cloudformation/test.arn.ts +++ b/packages/@aws-cdk/cdk/test/cloudformation/test.arn.ts @@ -1,20 +1,26 @@ import { Test } from 'nodeunit'; -import { ArnComponents, ArnUtils, AwsAccountId, AwsPartition, AwsRegion, resolve, Token } from '../../lib'; +import { ArnComponents, Aws, Stack, Token } from '../../lib'; export = { 'create from components with defaults'(test: Test) { - const arn = ArnUtils.fromComponents({ + const stack = new Stack(); + + const arn = stack.formatArn({ service: 'sqs', resource: 'myqueuename' }); - test.deepEqual(resolve(arn), - resolve(`arn:${new AwsPartition()}:sqs:${new AwsRegion()}:${new AwsAccountId()}:myqueuename`)); + const pseudo = new Aws(stack); + + test.deepEqual(stack.node.resolve(arn), + stack.node.resolve(`arn:${pseudo.partition}:sqs:${pseudo.region}:${pseudo.accountId}:myqueuename`)); test.done(); }, 'create from components with specific values for the various components'(test: Test) { - const arn = ArnUtils.fromComponents({ + const stack = new Stack(); + + const arn = stack.formatArn({ service: 'dynamodb', resource: 'table', account: '123456789012', @@ -23,13 +29,15 @@ export = { resourceName: 'mytable/stream/label' }); - test.deepEqual(resolve(arn), + test.deepEqual(stack.node.resolve(arn), 'arn:aws-cn:dynamodb:us-east-1:123456789012:table/mytable/stream/label'); test.done(); }, 'allow empty string in components'(test: Test) { - const arn = ArnUtils.fromComponents({ + const stack = new Stack(); + + const arn = stack.formatArn({ service: 's3', resource: 'my-bucket', account: '', @@ -37,27 +45,33 @@ export = { partition: 'aws-cn', }); - test.deepEqual(resolve(arn), + test.deepEqual(stack.node.resolve(arn), 'arn:aws-cn:s3:::my-bucket'); test.done(); }, 'resourcePathSep can be set to ":" instead of the default "/"'(test: Test) { - const arn = ArnUtils.fromComponents({ + const stack = new Stack(); + + const arn = stack.formatArn({ service: 'codedeploy', resource: 'application', sep: ':', resourceName: 'WordPress_App' }); - test.deepEqual(resolve(arn), - resolve(`arn:${new AwsPartition()}:codedeploy:${new AwsRegion()}:${new AwsAccountId()}:application:WordPress_App`)); + const pseudo = new Aws(stack); + + test.deepEqual(stack.node.resolve(arn), + stack.node.resolve(`arn:${pseudo.partition}:codedeploy:${pseudo.region}:${pseudo.accountId}:application:WordPress_App`)); test.done(); }, 'fails if resourcePathSep is neither ":" nor "/"'(test: Test) { - test.throws(() => ArnUtils.fromComponents({ + const stack = new Stack(); + + test.throws(() => stack.formatArn({ service: 'foo', resource: 'bar', sep: 'x' })); @@ -68,27 +82,32 @@ export = { 'fails': { 'if doesn\'t start with "arn:"'(test: Test) { - test.throws(() => ArnUtils.parse("barn:foo:x:a:1:2"), /ARNs must start with "arn:": barn:foo/); + const stack = new Stack(); + test.throws(() => stack.parseArn("barn:foo:x:a:1:2"), /ARNs must start with "arn:": barn:foo/); test.done(); }, 'if the ARN doesnt have enough components'(test: Test) { - test.throws(() => ArnUtils.parse('arn:is:too:short'), /ARNs must have at least 6 components: arn:is:too:short/); + const stack = new Stack(); + test.throws(() => stack.parseArn('arn:is:too:short'), /ARNs must have at least 6 components: arn:is:too:short/); test.done(); }, 'if "service" is not specified'(test: Test) { - test.throws(() => ArnUtils.parse('arn:aws::4:5:6'), /The `service` component \(3rd component\) is required/); + const stack = new Stack(); + test.throws(() => stack.parseArn('arn:aws::4:5:6'), /The `service` component \(3rd component\) is required/); test.done(); }, 'if "resource" is not specified'(test: Test) { - test.throws(() => ArnUtils.parse('arn:aws:service:::'), /The `resource` component \(6th component\) is required/); + const stack = new Stack(); + test.throws(() => stack.parseArn('arn:aws:service:::'), /The `resource` component \(6th component\) is required/); test.done(); } }, 'various successful parses'(test: Test) { + const stack = new Stack(); const tests: { [arn: string]: ArnComponents } = { 'arn:aws:a4b:region:accountid:resourcetype/resource': { partition: 'aws', @@ -130,37 +149,39 @@ export = { Object.keys(tests).forEach(arn => { const expected = tests[arn]; - test.deepEqual(ArnUtils.parse(arn), expected, arn); + test.deepEqual(stack.parseArn(arn), expected, arn); }); test.done(); }, 'a Token with : separator'(test: Test) { + const stack = new Stack(); const theToken = { Ref: 'SomeParameter' }; - const parsed = ArnUtils.parseToken(new Token(() => theToken).toString(), ':'); - - test.deepEqual(resolve(parsed.partition), { 'Fn::Select': [ 1, { 'Fn::Split': [ ':', theToken ]} ]}); - test.deepEqual(resolve(parsed.service), { 'Fn::Select': [ 2, { 'Fn::Split': [ ':', theToken ]} ]}); - test.deepEqual(resolve(parsed.region), { 'Fn::Select': [ 3, { 'Fn::Split': [ ':', theToken ]} ]}); - test.deepEqual(resolve(parsed.account), { 'Fn::Select': [ 4, { 'Fn::Split': [ ':', theToken ]} ]}); - test.deepEqual(resolve(parsed.resource), { 'Fn::Select': [ 5, { 'Fn::Split': [ ':', theToken ]} ]}); - test.deepEqual(resolve(parsed.resourceName), { 'Fn::Select': [ 6, { 'Fn::Split': [ ':', theToken ]} ]}); + const parsed = stack.parseArn(new Token(() => theToken).toString(), ':'); + + test.deepEqual(stack.node.resolve(parsed.partition), { 'Fn::Select': [ 1, { 'Fn::Split': [ ':', theToken ]} ]}); + test.deepEqual(stack.node.resolve(parsed.service), { 'Fn::Select': [ 2, { 'Fn::Split': [ ':', theToken ]} ]}); + test.deepEqual(stack.node.resolve(parsed.region), { 'Fn::Select': [ 3, { 'Fn::Split': [ ':', theToken ]} ]}); + test.deepEqual(stack.node.resolve(parsed.account), { 'Fn::Select': [ 4, { 'Fn::Split': [ ':', theToken ]} ]}); + test.deepEqual(stack.node.resolve(parsed.resource), { 'Fn::Select': [ 5, { 'Fn::Split': [ ':', theToken ]} ]}); + test.deepEqual(stack.node.resolve(parsed.resourceName), { 'Fn::Select': [ 6, { 'Fn::Split': [ ':', theToken ]} ]}); test.equal(parsed.sep, ':'); test.done(); }, 'a Token with / separator'(test: Test) { + const stack = new Stack(); const theToken = { Ref: 'SomeParameter' }; - const parsed = ArnUtils.parseToken(new Token(() => theToken).toString()); + const parsed = stack.parseArn(new Token(() => theToken).toString()); test.equal(parsed.sep, '/'); // tslint:disable-next-line:max-line-length - test.deepEqual(resolve(parsed.resource), { 'Fn::Select': [ 0, { 'Fn::Split': [ '/', { 'Fn::Select': [ 5, { 'Fn::Split': [ ':', theToken ]} ]} ]} ]}); + test.deepEqual(stack.node.resolve(parsed.resource), { 'Fn::Select': [ 0, { 'Fn::Split': [ '/', { 'Fn::Select': [ 5, { 'Fn::Split': [ ':', theToken ]} ]} ]} ]}); // tslint:disable-next-line:max-line-length - test.deepEqual(resolve(parsed.resourceName), { 'Fn::Select': [ 1, { 'Fn::Split': [ '/', { 'Fn::Select': [ 5, { 'Fn::Split': [ ':', theToken ]} ]} ]} ]}); + test.deepEqual(stack.node.resolve(parsed.resourceName), { 'Fn::Select': [ 1, { 'Fn::Split': [ '/', { 'Fn::Select': [ 5, { 'Fn::Split': [ ':', theToken ]} ]} ]} ]}); test.done(); } diff --git a/packages/@aws-cdk/cdk/test/cloudformation/test.cloudformation-json.ts b/packages/@aws-cdk/cdk/test/cloudformation/test.cloudformation-json.ts index ecc52f95aea52..440e782af6ac6 100644 --- a/packages/@aws-cdk/cdk/test/cloudformation/test.cloudformation-json.ts +++ b/packages/@aws-cdk/cdk/test/cloudformation/test.cloudformation-json.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { CloudFormationJSON, CloudFormationToken, Fn, resolve, Token } from '../../lib'; +import { CloudFormationJSON, Fn, Stack, Token } from '../../lib'; import { evaluateCFN } from './evaluate-cfn'; export = { @@ -16,12 +16,14 @@ export = { }, 'string tokens can be JSONified and JSONification can be reversed'(test: Test) { + const stack = new Stack(); + for (const token of tokensThatResolveTo('woof woof')) { // GIVEN const fido = { name: 'Fido', speaks: token }; // WHEN - const resolved = resolve(CloudFormationJSON.stringify(fido)); + const resolved = stack.node.resolve(CloudFormationJSON.stringify(fido, stack)); // THEN test.deepEqual(evaluateCFN(resolved), '{"name":"Fido","speaks":"woof woof"}'); @@ -31,12 +33,14 @@ export = { }, 'string tokens can be embedded while being JSONified'(test: Test) { + const stack = new Stack(); + for (const token of tokensThatResolveTo('woof woof')) { // GIVEN const fido = { name: 'Fido', speaks: `deep ${token}` }; // WHEN - const resolved = resolve(CloudFormationJSON.stringify(fido)); + const resolved = stack.node.resolve(CloudFormationJSON.stringify(fido, stack)); // THEN test.deepEqual(evaluateCFN(resolved), '{"name":"Fido","speaks":"deep woof woof"}'); @@ -47,25 +51,27 @@ export = { 'integer Tokens behave correctly in stringification and JSONification'(test: Test) { // GIVEN + const stack = new Stack(); const num = new Token(() => 1); const embedded = `the number is ${num}`; // WHEN - test.equal(evaluateCFN(resolve(embedded)), "the number is 1"); - test.equal(evaluateCFN(resolve(CloudFormationJSON.stringify({ embedded }))), "{\"embedded\":\"the number is 1\"}"); - test.equal(evaluateCFN(resolve(CloudFormationJSON.stringify({ num }))), "{\"num\":1}"); + test.equal(evaluateCFN(stack.node.resolve(embedded)), "the number is 1"); + test.equal(evaluateCFN(stack.node.resolve(CloudFormationJSON.stringify({ embedded }, stack))), "{\"embedded\":\"the number is 1\"}"); + test.equal(evaluateCFN(stack.node.resolve(CloudFormationJSON.stringify({ num }, stack))), "{\"num\":1}"); test.done(); }, 'tokens in strings survive additional TokenJSON.stringification()'(test: Test) { // GIVEN + const stack = new Stack(); for (const token of tokensThatResolveTo('pong!')) { // WHEN - const stringified = CloudFormationJSON.stringify(`ping? ${token}`); + const stringified = CloudFormationJSON.stringify(`ping? ${token}`, stack); // THEN - test.equal(evaluateCFN(resolve(stringified)), '"ping? pong!"'); + test.equal(evaluateCFN(stack.node.resolve(stringified)), '"ping? pong!"'); } test.done(); @@ -73,10 +79,11 @@ export = { 'intrinsic Tokens embed correctly in JSONification'(test: Test) { // GIVEN - const bucketName = new CloudFormationToken({ Ref: 'MyBucket' }); + const stack = new Stack(); + const bucketName = new Token({ Ref: 'MyBucket' }); // WHEN - const resolved = resolve(CloudFormationJSON.stringify({ theBucket: bucketName })); + const resolved = stack.node.resolve(CloudFormationJSON.stringify({ theBucket: bucketName }, stack)); // THEN const context = {MyBucket: 'TheName'}; @@ -86,14 +93,15 @@ export = { }, 'embedded string literals in intrinsics are escaped when calling TokenJSON.stringify()'(test: Test) { - // WHEN + // GIVEN + const stack = new Stack(); const token = Fn.join('', [ 'Hello', 'This\nIs', 'Very "cool"' ]); // WHEN - const resolved = resolve(CloudFormationJSON.stringify({ + const resolved = stack.node.resolve(CloudFormationJSON.stringify({ literal: 'I can also "contain" quotes', token - })); + }, stack)); // THEN const expected = '{"literal":"I can also \\"contain\\" quotes","token":"HelloThis\\nIsVery \\"cool\\""}'; @@ -104,11 +112,12 @@ export = { 'Tokens in Tokens are handled correctly'(test: Test) { // GIVEN - const bucketName = new CloudFormationToken({ Ref: 'MyBucket' }); + const stack = new Stack(); + const bucketName = new Token({ Ref: 'MyBucket' }); const combinedName = Fn.join('', [ 'The bucket name is ', bucketName.toString() ]); // WHEN - const resolved = resolve(CloudFormationJSON.stringify({ theBucket: combinedName })); + const resolved = stack.node.resolve(CloudFormationJSON.stringify({ theBucket: combinedName }, stack)); // THEN const context = {MyBucket: 'TheName'}; @@ -119,12 +128,13 @@ export = { 'Doubly nested strings evaluate correctly in JSON context'(test: Test) { // WHEN + const stack = new Stack(); const fidoSays = new Token(() => 'woof'); // WHEN - const resolved = resolve(CloudFormationJSON.stringify({ + const resolved = stack.node.resolve(CloudFormationJSON.stringify({ information: `Did you know that Fido says: ${fidoSays}` - })); + }, stack)); // THEN test.deepEqual(evaluateCFN(resolved), '{"information":"Did you know that Fido says: woof"}'); @@ -133,13 +143,14 @@ export = { }, 'Doubly nested intrinsics evaluate correctly in JSON context'(test: Test) { - // WHEN - const fidoSays = new CloudFormationToken(() => ({ Ref: 'Something' })); + // GIVEN + const stack = new Stack(); + const fidoSays = new Token(() => ({ Ref: 'Something' })); // WHEN - const resolved = resolve(CloudFormationJSON.stringify({ + const resolved = stack.node.resolve(CloudFormationJSON.stringify({ information: `Did you know that Fido says: ${fidoSays}` - })); + }, stack)); // THEN const context = {Something: 'woof woof'}; @@ -149,13 +160,14 @@ export = { }, 'Quoted strings in embedded JSON context are escaped'(test: Test) { - // WHEN + // GIVEN + const stack = new Stack(); const fidoSays = new Token(() => '"woof"'); // WHEN - const resolved = resolve(CloudFormationJSON.stringify({ + const resolved = stack.node.resolve(CloudFormationJSON.stringify({ information: `Did you know that Fido says: ${fidoSays}` - })); + }, stack)); // THEN test.deepEqual(evaluateCFN(resolved), '{"information":"Did you know that Fido says: \\"woof\\""}'); diff --git a/packages/@aws-cdk/cdk/test/cloudformation/test.condition.ts b/packages/@aws-cdk/cdk/test/cloudformation/test.condition.ts new file mode 100644 index 0000000000000..4ca8300bf25e0 --- /dev/null +++ b/packages/@aws-cdk/cdk/test/cloudformation/test.condition.ts @@ -0,0 +1,32 @@ +import { Test } from 'nodeunit'; +import cdk = require('../../lib'); + +export = { + 'chain conditions'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const param = new cdk.Parameter(stack, 'Param1', { type: 'String' }); + const cond1 = new cdk.Condition(stack, 'Condition1', { expression: cdk.Fn.conditionEquals("a", "b") }); + const cond2 = new cdk.Condition(stack, 'Condition2', { expression: cdk.Fn.conditionContains([ "a", "b", "c" ], "c") }); + const cond3 = new cdk.Condition(stack, 'Condition3', { expression: cdk.Fn.conditionEquals(param, "hello") }); + + // WHEN + new cdk.Condition(stack, 'Condition4', { + expression: cdk.Fn.conditionOr(cond1, cond2, cdk.Fn.conditionNot(cond3)) + }); + + // THEN + test.deepEqual(stack.toCloudFormation(), { + Parameters: { Param1: { Type: 'String' } }, + Conditions: { + Condition1: { 'Fn::Equals': [ 'a', 'b' ] }, + Condition2: { 'Fn::Contains': [ [ 'a', 'b', 'c' ], 'c' ] }, + Condition3: { 'Fn::Equals': [ { Ref: 'Param1' }, 'hello' ] }, + Condition4: { 'Fn::Or': [ + { Condition: 'Condition1' }, + { Condition: 'Condition2' }, + { 'Fn::Not': [ { Condition: 'Condition3' } ] } ] } } }); + + test.done(); + } +}; diff --git a/packages/@aws-cdk/cdk/test/cloudformation/test.dynamic-reference.ts b/packages/@aws-cdk/cdk/test/cloudformation/test.dynamic-reference.ts index 4dc2f2767f40d..37b3751b3c167 100644 --- a/packages/@aws-cdk/cdk/test/cloudformation/test.dynamic-reference.ts +++ b/packages/@aws-cdk/cdk/test/cloudformation/test.dynamic-reference.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { DynamicReference, DynamicReferenceService, resolve, Stack } from '../../lib'; +import { DynamicReference, DynamicReferenceService, Stack } from '../../lib'; export = { 'can create dynamic references with service and key with colons'(test: Test) { @@ -13,7 +13,7 @@ export = { }); // THEN - test.equal(resolve(ref.value), '{{resolve:ssm:a:b:c}}'); + test.equal(stack.node.resolve(ref.value), '{{resolve:ssm:a:b:c}}'); test.done(); }, diff --git a/packages/@aws-cdk/cdk/test/cloudformation/test.fn.ts b/packages/@aws-cdk/cdk/test/cloudformation/test.fn.ts index afb247fbebd57..adb82677c5248 100644 --- a/packages/@aws-cdk/cdk/test/cloudformation/test.fn.ts +++ b/packages/@aws-cdk/cdk/test/cloudformation/test.fn.ts @@ -1,9 +1,7 @@ import fc = require('fast-check'); import _ = require('lodash'); import nodeunit = require('nodeunit'); -import { CloudFormationToken } from '../../lib'; -import { Fn } from '../../lib/cloudformation/fn'; -import { resolve } from '../../lib/core/tokens'; +import { Fn, Stack, Token } from '../../lib'; function asyncTest(cb: (test: nodeunit.Test) => Promise): (test: nodeunit.Test) => void { return async (test: nodeunit.Test) => { @@ -31,36 +29,59 @@ export = nodeunit.testCase({ test.throws(() => Fn.join('.', [])); test.done(); }, + 'collapse nested FnJoins even if they contain tokens'(test: nodeunit.Test) { + const stack = new Stack(); + + const obj = Fn.join('', [ + 'a', + Fn.join('', [Fn.getAtt('a', 'bc').toString(), 'c']), + 'd' + ]); + + test.deepEqual(stack.node.resolve(obj), { 'Fn::Join': [ "", + [ + "a", + { 'Fn::GetAtt': ['a', 'bc'] }, + 'cd', + ] + ]}); + + test.done(); + }, 'resolves to the value if only one value is joined': asyncTest(async () => { + const stack = new Stack(); await fc.assert( fc.property( fc.string(), anyValue, - (delimiter, value) => _.isEqual(resolve(Fn.join(delimiter, [value])), value) + (delimiter, value) => _.isEqual(stack.node.resolve(Fn.join(delimiter, [value])), value) ), { verbose: true } ); }), 'pre-concatenates string literals': asyncTest(async () => { + const stack = new Stack(); await fc.assert( fc.property( fc.string(), fc.array(nonEmptyString, 1, 15), - (delimiter, values) => resolve(Fn.join(delimiter, values)) === values.join(delimiter) + (delimiter, values) => stack.node.resolve(Fn.join(delimiter, values)) === values.join(delimiter) ), { verbose: true } ); }), 'pre-concatenates around tokens': asyncTest(async () => { + const stack = new Stack(); await fc.assert( fc.property( fc.string(), fc.array(nonEmptyString, 1, 3), tokenish, fc.array(nonEmptyString, 1, 3), (delimiter, prefix, obj, suffix) => - _.isEqual(resolve(Fn.join(delimiter, [...prefix, stringToken(obj), ...suffix])), + _.isEqual(stack.node.resolve(Fn.join(delimiter, [...prefix, stringToken(obj), ...suffix])), { 'Fn::Join': [delimiter, [prefix.join(delimiter), obj, suffix.join(delimiter)]] }) ), { verbose: true, seed: 1539874645005, path: "0:0:0:0:0:0:0:0:0" } ); }), 'flattens joins nested under joins with same delimiter': asyncTest(async () => { + const stack = new Stack(); await fc.assert( fc.property( fc.string(), fc.array(anyValue), @@ -68,13 +89,14 @@ export = nodeunit.testCase({ fc.array(anyValue), (delimiter, prefix, nested, suffix) => // Gonna test - _.isEqual(resolve(Fn.join(delimiter, [...prefix, Fn.join(delimiter, nested), ...suffix])), - resolve(Fn.join(delimiter, [...prefix, ...nested, ...suffix]))) + _.isEqual(stack.node.resolve(Fn.join(delimiter, [...prefix, Fn.join(delimiter, nested), ...suffix])), + stack.node.resolve(Fn.join(delimiter, [...prefix, ...nested, ...suffix]))) ), { verbose: true } ); }), 'does not flatten joins nested under joins with different delimiter': asyncTest(async () => { + const stack = new Stack(); await fc.assert( fc.property( fc.string(), fc.string(), @@ -84,7 +106,7 @@ export = nodeunit.testCase({ (delimiter1, delimiter2, prefix, nested, suffix) => { fc.pre(delimiter1 !== delimiter2); const join = Fn.join(delimiter1, [...prefix, Fn.join(delimiter2, stringListToken(nested)), ...suffix]); - const resolved = resolve(join); + const resolved = stack.node.resolve(join); return resolved['Fn::Join'][1].find((e: any) => typeof e === 'object' && ('Fn::Join' in e) && e['Fn::Join'][0] === delimiter2) != null; @@ -97,8 +119,8 @@ export = nodeunit.testCase({ }); function stringListToken(o: any): string[] { - return new CloudFormationToken(o).toList(); + return new Token(o).toList(); } function stringToken(o: any): string { - return new CloudFormationToken(o).toString(); -} \ No newline at end of file + return new Token(o).toString(); +} diff --git a/packages/@aws-cdk/cdk/test/cloudformation/test.output.ts b/packages/@aws-cdk/cdk/test/cloudformation/test.output.ts index 3e9de87245942..b6ee26217d2d7 100644 --- a/packages/@aws-cdk/cdk/test/cloudformation/test.output.ts +++ b/packages/@aws-cdk/cdk/test/cloudformation/test.output.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { Construct, Output, Ref, resolve, Resource, Stack } from '../../lib'; +import { Construct, Output, Ref, Resource, Stack } from '../../lib'; export = { 'outputs can be added to the stack'(test: Test) { @@ -63,7 +63,7 @@ export = { 'makeImportValue can be used to create an Fn::ImportValue from an output'(test: Test) { const stack = new Stack(undefined, 'MyStack'); const output = new Output(stack, 'MyOutput'); - test.deepEqual(resolve(output.makeImportValue()), { 'Fn::ImportValue': 'MyStack:MyOutput' }); + test.deepEqual(stack.node.resolve(output.makeImportValue()), { 'Fn::ImportValue': 'MyStack:MyOutput' }); test.done(); } -}; +}; \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/test/cloudformation/test.parameter.ts b/packages/@aws-cdk/cdk/test/cloudformation/test.parameter.ts index d866af8099a8a..2a5526c4255f2 100644 --- a/packages/@aws-cdk/cdk/test/cloudformation/test.parameter.ts +++ b/packages/@aws-cdk/cdk/test/cloudformation/test.parameter.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { Construct, Parameter, resolve, Resource, Stack } from '../../lib'; +import { Construct, Parameter, Resource, Stack } from '../../lib'; export = { 'parameters can be used and referenced using param.ref'(test: Test) { @@ -32,7 +32,7 @@ export = { const stack = new Stack(); const param = new Parameter(stack, 'MyParam', { type: 'String' }); - test.deepEqual(resolve(param), { Ref: 'MyParam' }); + test.deepEqual(stack.node.resolve(param), { Ref: 'MyParam' }); test.done(); } -}; +}; \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/test/cloudformation/test.resource.ts b/packages/@aws-cdk/cdk/test/cloudformation/test.resource.ts index 9debe742f70c5..3b0ee595aa35a 100644 --- a/packages/@aws-cdk/cdk/test/cloudformation/test.resource.ts +++ b/packages/@aws-cdk/cdk/test/cloudformation/test.resource.ts @@ -2,7 +2,7 @@ import cxapi = require('@aws-cdk/cx-api'); import { Test } from 'nodeunit'; import { applyRemovalPolicy, Condition, Construct, DeletionPolicy, Fn, HashedAddressingScheme, IDependable, - RemovalPolicy, resolve, Resource, Root, Stack } from '../../lib'; + RemovalPolicy, Resource, Root, Stack } from '../../lib'; export = { 'all resources derive from Resource, which derives from Entity'(test: Test) { @@ -359,7 +359,7 @@ export = { const stack = new Stack(); const r = new Resource(stack, 'MyResource', { type: 'R' }); - test.deepEqual(resolve(r.ref), { Ref: 'MyResource' }); + test.deepEqual(stack.node.resolve(r.ref), { Ref: 'MyResource' }); test.done(); }, diff --git a/packages/@aws-cdk/cdk/test/cloudformation/test.secret.ts b/packages/@aws-cdk/cdk/test/cloudformation/test.secret.ts index 33422251adaa9..37b4dbd2bc805 100644 --- a/packages/@aws-cdk/cdk/test/cloudformation/test.secret.ts +++ b/packages/@aws-cdk/cdk/test/cloudformation/test.secret.ts @@ -1,13 +1,14 @@ import { Test } from 'nodeunit'; -import { resolve, Secret, SecretParameter, Stack } from '../../lib'; +import { Secret, SecretParameter, Stack } from '../../lib'; export = { 'Secret is merely a token'(test: Test) { + const stack = new Stack(); const foo = new Secret('Foo'); const bar = new Secret(() => 'Bar'); - test.deepEqual(resolve(foo), 'Foo'); - test.deepEqual(resolve(bar), 'Bar'); + test.deepEqual(stack.node.resolve(foo), 'Foo'); + test.deepEqual(stack.node.resolve(bar), 'Bar'); test.done(); }, @@ -43,7 +44,7 @@ export = { NoEcho: true } } }); // value resolves to a "Ref" - test.deepEqual(resolve(mySecret.value), { Ref: 'MySecretParameterBB81DE58' }); + test.deepEqual(stack.node.resolve(mySecret.value), { Ref: 'MySecretParameterBB81DE58' }); test.done(); } diff --git a/packages/@aws-cdk/cdk/test/cloudformation/test.stack.ts b/packages/@aws-cdk/cdk/test/cloudformation/test.stack.ts index 1561dfea92e0a..880068e92d589 100644 --- a/packages/@aws-cdk/cdk/test/cloudformation/test.stack.ts +++ b/packages/@aws-cdk/cdk/test/cloudformation/test.stack.ts @@ -1,5 +1,6 @@ +import cxapi = require('@aws-cdk/cx-api'); import { Test } from 'nodeunit'; -import { App, Condition, Construct, Include, Output, Parameter, Resource, Root, Stack, Token } from '../../lib'; +import { App, Aws, Condition, Construct, Include, Output, Parameter, Resource, Root, Stack, Token } from '../../lib'; export = { 'a stack can be serialized into a CloudFormation template, initially it\'s empty'(test: Test) { @@ -172,20 +173,175 @@ export = { test.done(); }, - 'Can\'t add children during synthesis'(test: Test) { - const stack = new Stack(); + 'Pseudo values attached to one stack can be referenced in another stack'(test: Test) { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1'); + const account1 = new Aws(stack1).accountId; + const stack2 = new Stack(app, 'Stack2'); + + // WHEN - used in another stack + new Parameter(stack2, 'SomeParameter', { type: 'String', default: account1 }); + + // THEN + // Need to do this manually now, since we're in testing mode. In a normal CDK app, + // this happens as part of app.run(). + app.node.prepareTree(); + + test.deepEqual(stack1.toCloudFormation(), { + Outputs: { + ExportsOutputRefAWSAccountIdAD568057: { + Value: { Ref: 'AWS::AccountId' }, + Export: { Name: 'Stack1:ExportsOutputRefAWSAccountIdAD568057' } + } + } + }); + + test.deepEqual(stack2.toCloudFormation(), { + Parameters: { + SomeParameter: { + Type: 'String', + Default: { 'Fn::ImportValue': 'Stack1:ExportsOutputRefAWSAccountIdAD568057' } + } + } + }); + + test.done(); + }, + + 'cross-stack references in lazy tokens work'(test: Test) { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1'); + const account1 = new Aws(stack1).accountId; + const stack2 = new Stack(app, 'Stack2'); + + // WHEN - used in another stack + new Parameter(stack2, 'SomeParameter', { type: 'String', default: new Token(() => account1) }); + + app.node.prepareTree(); + + // THEN + test.deepEqual(stack1.toCloudFormation(), { + Outputs: { + ExportsOutputRefAWSAccountIdAD568057: { + Value: { Ref: 'AWS::AccountId' }, + Export: { Name: 'Stack1:ExportsOutputRefAWSAccountIdAD568057' } + } + } + }); + + test.deepEqual(stack2.toCloudFormation(), { + Parameters: { + SomeParameter: { + Type: 'String', + Default: { 'Fn::ImportValue': 'Stack1:ExportsOutputRefAWSAccountIdAD568057' } + } + } + }); + + test.done(); + }, + + 'cross-stack references in strings work'(test: Test) { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1'); + const account1 = new Aws(stack1).accountId; + const stack2 = new Stack(app, 'Stack2'); + + // WHEN - used in another stack + new Parameter(stack2, 'SomeParameter', { type: 'String', default: `TheAccountIs${account1}` }); + + app.node.prepareTree(); + + // THEN + test.deepEqual(stack2.toCloudFormation(), { + Parameters: { + SomeParameter: { + Type: 'String', + Default: { 'Fn::Join': [ '', [ 'TheAccountIs', { 'Fn::ImportValue': 'Stack1:ExportsOutputRefAWSAccountIdAD568057' } ]] } + } + } + }); + + test.done(); + }, + + 'cannot create cyclic reference between stacks'(test: Test) { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1'); + const account1 = new Aws(stack1).accountId; + const stack2 = new Stack(app, 'Stack2'); + const account2 = new Aws(stack2).accountId; + + // WHEN + new Parameter(stack2, 'SomeParameter', { type: 'String', default: account1 }); + new Parameter(stack1, 'SomeParameter', { type: 'String', default: account2 }); + + test.throws(() => { + app.node.prepareTree(); + }, /Adding this dependency would create a cyclic reference/); + + test.done(); + }, + + 'stacks know about their dependencies'(test: Test) { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1'); + const account1 = new Aws(stack1).accountId; + const stack2 = new Stack(app, 'Stack2'); + + // WHEN + new Parameter(stack2, 'SomeParameter', { type: 'String', default: account1 }); + + app.node.prepareTree(); + + // THEN + test.deepEqual(stack2.dependencies().map(s => s.node.id), ['Stack1']); + + test.done(); + }, + + 'cannot create references to stacks in other regions/accounts'(test: Test) { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1', { env: { account: '123456789012', region: 'es-norst-1' }}); + const account1 = new Aws(stack1).accountId; + const stack2 = new Stack(app, 'Stack2', { env: { account: '123456789012', region: 'es-norst-2' }}); + + // WHEN + new Parameter(stack2, 'SomeParameter', { type: 'String', default: account1 }); + + test.throws(() => { + app.node.prepareTree(); + }, /Can only reference cross stacks in the same region and account/); + + test.done(); + }, + + 'stack with region supplied via props returns literal value'(test: Test) { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'Stack1', { env: { account: '123456789012', region: 'es-norst-1' }}); + + // THEN + test.equal(stack.node.resolve(stack.region), 'es-norst-1'); + + test.done(); + }, - // add a construct with a token that when resolved adds a child. this - // means that this child is going to be added during synthesis and this - // is a no-no. - new Resource(stack, 'Resource', { type: 'T', properties: { - foo: new Token(() => new Construct(stack, 'Foo')) - }}); + 'stack with region supplied via context returns symbolic value'(test: Test) { + // GIVEN + const app = new App(); - test.throws(() => stack.toCloudFormation(), /Cannot add children during synthesis/); + app.node.setContext(cxapi.DEFAULT_REGION_CONTEXT_KEY, 'es-norst-1'); + const stack = new Stack(app, 'Stack1'); - // okay to add after synthesis - new Construct(stack, 'C1'); + // THEN + test.deepEqual(stack.node.resolve(stack.region), { Ref: 'AWS::Region' }); test.done(); }, diff --git a/packages/@aws-cdk/cdk/test/core/test.construct.ts b/packages/@aws-cdk/cdk/test/core/test.construct.ts index aa70c643d8e51..dc786b63b1288 100644 --- a/packages/@aws-cdk/cdk/test/core/test.construct.ts +++ b/packages/@aws-cdk/cdk/test/core/test.construct.ts @@ -307,13 +307,13 @@ export = { 'construct.validate() can be implemented to perform validation, construct.validateTree() will return all errors from the subtree (DFS)'(test: Test) { class MyConstruct extends Construct { - public validate() { + protected validate() { return [ 'my-error1', 'my-error2' ]; } } class YourConstruct extends Construct { - public validate() { + protected validate() { return [ 'your-error1' ]; } } @@ -325,7 +325,7 @@ export = { new YourConstruct(this, 'YourConstruct'); } - public validate() { + protected validate() { return [ 'their-error' ]; } } @@ -338,7 +338,7 @@ export = { new TheirConstruct(this, 'TheirConstruct'); } - public validate() { + protected validate() { return [ 'stack-error' ]; } } diff --git a/packages/@aws-cdk/cdk/test/core/test.tag-manager.ts b/packages/@aws-cdk/cdk/test/core/test.tag-manager.ts index f78d98f233786..71af1326f6b7b 100644 --- a/packages/@aws-cdk/cdk/test/core/test.tag-manager.ts +++ b/packages/@aws-cdk/cdk/test/core/test.tag-manager.ts @@ -1,6 +1,5 @@ import { Test } from 'nodeunit'; -import { Construct, Root } from '../../lib/core/construct'; -import { ITaggable, TagManager } from '../../lib/core/tag-manager'; +import { Construct, ITaggable, Root, TagManager } from '../../lib'; class ChildTagger extends Construct implements ITaggable { public readonly tags: TagManager; @@ -32,10 +31,10 @@ export = { const tagArray = [tag]; for (const construct of [ctagger, ctagger1]) { - test.deepEqual(construct.tags.resolve(), tagArray); + test.deepEqual(root.node.resolve(construct.tags), tagArray); } - test.deepEqual(ctagger2.tags.resolve(), undefined); + test.deepEqual(root.node.resolve(ctagger2.tags), undefined); test.done(); }, 'setTag with propagate false tags do not propagate'(test: Test) { @@ -51,10 +50,10 @@ export = { ctagger.tags.setTag(tag.key, tag.value, {propagate: false}); for (const construct of [ctagger1, ctagger2]) { - test.deepEqual(construct.tags.resolve(), undefined); + test.deepEqual(root.node.resolve(construct.tags), undefined); } - test.deepEqual(ctagger.tags.resolve()[0].key, 'Name'); - test.deepEqual(ctagger.tags.resolve()[0].value, 'TheCakeIsALie'); + test.deepEqual(root.node.resolve(ctagger.tags)[0].key, 'Name'); + test.deepEqual(root.node.resolve(ctagger.tags)[0].value, 'TheCakeIsALie'); test.done(); }, 'setTag with overwrite false does not overwrite a tag'(test: Test) { @@ -62,7 +61,7 @@ export = { const ctagger = new ChildTagger(root, 'one'); ctagger.tags.setTag('Env', 'Dev'); ctagger.tags.setTag('Env', 'Prod', {overwrite: false}); - const result = ctagger.tags.resolve(); + const result = root.node.resolve(ctagger.tags); test.deepEqual(result, [{key: 'Env', value: 'Dev'}]); test.done(); }, @@ -72,8 +71,8 @@ export = { const ctagger1 = new ChildTagger(ctagger, 'two'); ctagger.tags.setTag('Parent', 'Is always right'); ctagger1.tags.setTag('Parent', 'Is wrong', {sticky: false}); - const parent = ctagger.tags.resolve(); - const child = ctagger1.tags.resolve(); + const parent = root.node.resolve(ctagger.tags); + const child = root.node.resolve(ctagger1.tags); test.deepEqual(parent, child); test.done(); @@ -86,7 +85,7 @@ export = { const ctagger2 = new ChildTagger(cNoTag, 'four'); const tag = {key: 'Name', value: 'TheCakeIsALie'}; ctagger.tags.setTag(tag.key, tag.value, {propagate: true}); - test.deepEqual(ctagger2.tags.resolve(), [tag]); + test.deepEqual(root.node.resolve(ctagger2.tags), [tag]); test.done(); }, 'a tag can be removed and added back'(test: Test) { @@ -94,11 +93,11 @@ export = { const ctagger = new ChildTagger(root, 'one'); const tag = {key: 'Name', value: 'TheCakeIsALie'}; ctagger.tags.setTag(tag.key, tag.value, {propagate: true}); - test.deepEqual(ctagger.tags.resolve(), [tag]); + test.deepEqual(root.node.resolve(ctagger.tags), [tag]); ctagger.tags.removeTag(tag.key); - test.deepEqual(ctagger.tags.resolve(), undefined); + test.deepEqual(root.node.resolve(ctagger.tags), undefined); ctagger.tags.setTag(tag.key, tag.value, {propagate: true}); - test.deepEqual(ctagger.tags.resolve(), [tag]); + test.deepEqual(root.node.resolve(ctagger.tags), [tag]); test.done(); }, 'removeTag removes a tag by key'(test: Test) { @@ -115,7 +114,7 @@ export = { ctagger.tags.removeTag('Name'); for (const construct of [ctagger, ctagger1, ctagger2]) { - test.deepEqual(construct.tags.resolve(), undefined); + test.deepEqual(root.node.resolve(construct.tags), undefined); } test.done(); }, @@ -125,9 +124,9 @@ export = { const ctagger1 = new ChildTagger(ctagger, 'two'); ctagger.tags.setTag('Env', 'Dev'); ctagger1.tags.removeTag('Env', {blockPropagate: true}); - const result = ctagger.tags.resolve(); + const result = root.node.resolve(ctagger.tags); test.deepEqual(result, [{key: 'Env', value: 'Dev'}]); - test.deepEqual(ctagger1.tags.resolve(), undefined); + test.deepEqual(root.node.resolve(ctagger1.tags), undefined); test.done(); }, 'children can override parent propagated tags'(test: Test) { @@ -139,8 +138,8 @@ export = { ctagger.tags.setTag(tag2.key, tag2.value); ctagger.tags.setTag(tag.key, tag.value); ctagChild.tags.setTag(tag2.key, tag2.value); - const parentTags = ctagger.tags.resolve(); - const childTags = ctagChild.tags.resolve(); + const parentTags = root.node.resolve(ctagger.tags); + const childTags = root.node.resolve(ctagChild.tags); test.deepEqual(parentTags, [tag]); test.deepEqual(childTags, [tag2]); test.done(); @@ -167,11 +166,11 @@ export = { const cAll = ctagger.tags; const cProp = ctagChild.tags; - for (const tag of cAll.resolve()) { + for (const tag of root.node.resolve(cAll)) { const expectedTag = allTags.filter( (t) => (t.key === tag.key)); test.deepEqual(expectedTag[0].value, tag.value); } - for (const tag of cProp.resolve()) { + for (const tag of root.node.resolve(cProp)) { const expectedTag = tagsProp.filter( (t) => (t.key === tag.key)); test.deepEqual(expectedTag[0].value, tag.value); } diff --git a/packages/@aws-cdk/cdk/test/core/test.tokens.ts b/packages/@aws-cdk/cdk/test/core/test.tokens.ts index 341730aa91bce..dfe4a54498b0b 100644 --- a/packages/@aws-cdk/cdk/test/core/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/core/test.tokens.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { CloudFormationToken, Fn, resolve, Token, unresolved } from '../../lib'; +import { Fn, Root, Token, unresolved } from '../../lib'; import { evaluateCFN } from '../cloudformation/evaluate-cfn'; export = { @@ -159,7 +159,7 @@ export = { 'Tokens stringification and reversing of CloudFormation Tokens is implemented using Fn::Join'(test: Test) { // GIVEN - const token = new CloudFormationToken(() => ({ woof: 'woof' })); + const token = new Token(() => ({ woof: 'woof' })); // WHEN const stringified = `The dog says: ${token}`; @@ -259,7 +259,7 @@ export = { 'fails if token in a hash key resolves to a non-string'(test: Test) { // GIVEN - const token = new CloudFormationToken({ Ref: 'Other' }); + const token = new Token({ Ref: 'Other' }); // WHEN const s = { @@ -274,7 +274,7 @@ export = { 'list encoding': { 'can encode Token to string and resolve the encoding'(test: Test) { // GIVEN - const token = new CloudFormationToken({ Ref: 'Other' }); + const token = new Token({ Ref: 'Other' }); // WHEN const struct = { @@ -291,7 +291,7 @@ export = { 'cannot add to encoded list'(test: Test) { // GIVEN - const token = new CloudFormationToken({ Ref: 'Other' }); + const token = new Token({ Ref: 'Other' }); // WHEN const encoded: string[] = token.toList(); @@ -307,7 +307,7 @@ export = { 'cannot add to strings in encoded list'(test: Test) { // GIVEN - const token = new CloudFormationToken({ Ref: 'Other' }); + const token = new Token({ Ref: 'Other' }); // WHEN const encoded: string[] = token.toList(); @@ -323,7 +323,7 @@ export = { 'can pass encoded lists to FnSelect'(test: Test) { // GIVEN - const encoded: string[] = new CloudFormationToken({ Ref: 'Other' }).toList(); + const encoded: string[] = new Token({ Ref: 'Other' }).toList(); // WHEN const struct = Fn.select(1, encoded); @@ -338,7 +338,7 @@ export = { 'can pass encoded lists to FnJoin'(test: Test) { // GIVEN - const encoded: string[] = new CloudFormationToken({ Ref: 'Other' }).toList(); + const encoded: string[] = new Token({ Ref: 'Other' }).toList(); // WHEN const struct = Fn.join('/', encoded); @@ -348,6 +348,21 @@ export = { 'Fn::Join': ['/', { Ref: 'Other'}] }); + test.done(); + }, + + 'can pass encoded lists to FnJoin, even if join is stringified'(test: Test) { + // GIVEN + const encoded: string[] = new Token({ Ref: 'Other' }).toList(); + + // WHEN + const struct = Fn.join('/', encoded).toString(); + + // THEN + test.deepEqual(resolve(struct), { + 'Fn::Join': ['/', { Ref: 'Other'}] + }); + test.done(); } } @@ -401,8 +416,8 @@ function literalTokensThatResolveTo(value: any): Token[] { */ function cloudFormationTokensThatResolveTo(value: any): Token[] { return [ - new CloudFormationToken(value), - new CloudFormationToken(() => value) + new Token(value), + new Token(() => value) ]; } @@ -412,3 +427,12 @@ function cloudFormationTokensThatResolveTo(value: any): Token[] { function tokensThatResolveTo(value: string): Token[] { return literalTokensThatResolveTo(value).concat(cloudFormationTokensThatResolveTo(value)); } + +/** + * Wrapper for resolve that creates an throwaway Construct to call it on + * + * So I don't have to change all call sites in this file. + */ +function resolve(x: any) { + return new Root().node.resolve(x); +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/test/core/test.util.ts b/packages/@aws-cdk/cdk/test/core/test.util.ts index 2a7cb94ec87c5..694e3ae86f456 100644 --- a/packages/@aws-cdk/cdk/test/core/test.util.ts +++ b/packages/@aws-cdk/cdk/test/core/test.util.ts @@ -1,25 +1,27 @@ import { Test } from 'nodeunit'; +import { Root } from '../../lib'; import { capitalizePropertyNames, ignoreEmpty } from '../../lib/core/util'; export = { 'capitalizeResourceProperties capitalizes all keys of an object (recursively) from camelCase to PascalCase'(test: Test) { + const c = new Root(); - test.equal(capitalizePropertyNames(undefined), undefined); - test.equal(capitalizePropertyNames(12), 12); - test.equal(capitalizePropertyNames('hello'), 'hello'); - test.deepEqual(capitalizePropertyNames([ 'hello', 88 ]), [ 'hello', 88 ]); - test.deepEqual(capitalizePropertyNames( + test.equal(capitalizePropertyNames(c, undefined), undefined); + test.equal(capitalizePropertyNames(c, 12), 12); + test.equal(capitalizePropertyNames(c, 'hello'), 'hello'); + test.deepEqual(capitalizePropertyNames(c, [ 'hello', 88 ]), [ 'hello', 88 ]); + test.deepEqual(capitalizePropertyNames(c, { Hello: 'world', hey: 'dude' }), { Hello: 'world', Hey: 'dude' }); - test.deepEqual(capitalizePropertyNames( + test.deepEqual(capitalizePropertyNames(c, [ 1, 2, { three: 3 }]), [ 1, 2, { Three: 3 }]); - test.deepEqual(capitalizePropertyNames( + test.deepEqual(capitalizePropertyNames(c, { Hello: 'world', recursive: { foo: 123, there: { another: [ 'hello', { world: 123 } ]} } }), { Hello: 'world', Recursive: { Foo: 123, There: { Another: [ 'hello', { World: 123 } ]} } }); // make sure tokens are resolved and result is also capitalized - test.deepEqual(capitalizePropertyNames( + test.deepEqual(capitalizePropertyNames(c, { hello: { resolve: () => ({ foo: 'bar' }) }, world: new SomeToken() }), { Hello: { Foo: 'bar' }, World: 100 }); @@ -29,38 +31,44 @@ export = { 'ignoreEmpty': { '[]'(test: Test) { - test.strictEqual(ignoreEmpty([]), undefined); + const c = new Root(); + test.strictEqual(ignoreEmpty(c, []), undefined); test.done(); }, '{}'(test: Test) { - test.strictEqual(ignoreEmpty({}), undefined); + const c = new Root(); + test.strictEqual(ignoreEmpty(c, {}), undefined); test.done(); }, 'undefined/null'(test: Test) { - test.strictEqual(ignoreEmpty(undefined), undefined); - test.strictEqual(ignoreEmpty(null), null); + const c = new Root(); + test.strictEqual(ignoreEmpty(c, undefined), undefined); + test.strictEqual(ignoreEmpty(c, null), null); test.done(); }, 'primitives'(test: Test) { - test.strictEqual(ignoreEmpty(12), 12); - test.strictEqual(ignoreEmpty("12"), "12"); + const c = new Root(); + test.strictEqual(ignoreEmpty(c, 12), 12); + test.strictEqual(ignoreEmpty(c, "12"), "12"); test.done(); }, 'non-empty arrays/objects'(test: Test) { - test.deepEqual(ignoreEmpty([ 1, 2, 3, undefined ]), [ 1, 2, 3 ]); // undefined array values is cleaned up by "resolve" - test.deepEqual(ignoreEmpty({ o: 1, b: 2, j: 3 }), { o: 1, b: 2, j: 3 }); + const c = new Root(); + test.deepEqual(ignoreEmpty(c, [ 1, 2, 3, undefined ]), [ 1, 2, 3 ]); // undefined array values is cleaned up by "resolve" + test.deepEqual(ignoreEmpty(c, { o: 1, b: 2, j: 3 }), { o: 1, b: 2, j: 3 }); test.done(); }, 'resolve first'(test: Test) { - test.deepEqual(ignoreEmpty({ xoo: { resolve: () => 123 }}), { xoo: 123 }); - test.strictEqual(ignoreEmpty({ xoo: { resolve: () => undefined }}), undefined); - test.deepEqual(ignoreEmpty({ xoo: { resolve: () => [ ] }}), { xoo: [] }); - test.deepEqual(ignoreEmpty({ xoo: { resolve: () => [ undefined, undefined ] }}), { xoo: [] }); + const c = new Root(); + test.deepEqual(ignoreEmpty(c, { xoo: { resolve: () => 123 }}), { xoo: 123 }); + test.strictEqual(ignoreEmpty(c, { xoo: { resolve: () => undefined }}), undefined); + test.deepEqual(ignoreEmpty(c, { xoo: { resolve: () => [ ] }}), { xoo: [] }); + test.deepEqual(ignoreEmpty(c, { xoo: { resolve: () => [ undefined, undefined ] }}), { xoo: [] }); test.done(); } } diff --git a/packages/@aws-cdk/cdk/test/test.app.ts b/packages/@aws-cdk/cdk/test/test.app.ts index 8c55bc8cc4d45..b0e5edff50db5 100644 --- a/packages/@aws-cdk/cdk/test/test.app.ts +++ b/packages/@aws-cdk/cdk/test/test.app.ts @@ -195,7 +195,7 @@ export = { 'app.synthesizeStack(stack) performs validation first (app.validateAll()) and if there are errors, it returns the errors'(test: Test) { class Child extends Construct { - public validate() { + protected validate() { return [ `Error from ${this.node.id}` ]; } } diff --git a/packages/@aws-cdk/cdk/test/test.context.ts b/packages/@aws-cdk/cdk/test/test.context.ts index 373c5e0dfcea4..0752b7fa1c2f1 100644 --- a/packages/@aws-cdk/cdk/test/test.context.ts +++ b/packages/@aws-cdk/cdk/test/test.context.ts @@ -1,7 +1,7 @@ import cxapi = require('@aws-cdk/cx-api'); import { Test } from 'nodeunit'; import { App, AvailabilityZoneProvider, Construct, ContextProvider, - MetadataEntry, resolve, SSMParameterProvider, Stack } from '../lib'; + MetadataEntry, SSMParameterProvider, Stack } from '../lib'; export = { 'AvailabilityZoneProvider returns a list with dummy values if the context is not available'(test: Test) { @@ -86,7 +86,7 @@ export = { stack.node.setContext(key, 'abc'); const ssmp = new SSMParameterProvider(stack, {parameterName: 'test'}); - const azs = resolve(ssmp.parameterValue()); + const azs = stack.node.resolve(ssmp.parameterValue()); test.deepEqual(azs, 'abc'); test.done(); 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 5d33a4a404369..b71a21fe00ae6 100644 --- a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts +++ b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts @@ -107,7 +107,8 @@ async function main() { package: "cdk-package", pkglint: "pkglint -f", test: "cdk-test", - watch: "cdk-watch" + watch: "cdk-watch", + cfn2ts: "cfn2ts" }, 'cdk-build': { cloudformation: namespace diff --git a/packages/@aws-cdk/cfnspec/package.json b/packages/@aws-cdk/cfnspec/package.json index 7f6ed90bc27f3..4dcd7e0432204 100644 --- a/packages/@aws-cdk/cfnspec/package.json +++ b/packages/@aws-cdk/cfnspec/package.json @@ -1,7 +1,7 @@ { "name": "@aws-cdk/cfnspec", "description": "The CloudFormation resource specification used by @aws-cdk packages", - "version": "0.21.0", + "version": "0.22.0", "scripts": { "update": "cdk-build && /bin/bash build-tools/update.sh", "build": "cdk-build && node build-tools/build", @@ -21,11 +21,11 @@ "devDependencies": { "@types/fs-extra": "^5.0.4", "@types/md5": "^2.1.32", - "cdk-build-tools": "^0.21.0", + "cdk-build-tools": "^0.22.0", "fast-json-patch": "^2.0.6", "fs-extra": "^7.0.0", "json-diff": "^0.3.1", - "pkglint": "^0.21.0", + "pkglint": "^0.22.0", "sort-json": "^2.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/cloudformation-diff/lib/diff-template.ts b/packages/@aws-cdk/cloudformation-diff/lib/diff-template.ts index 9a0d6ae88039b..b9d47e7080456 100644 --- a/packages/@aws-cdk/cloudformation-diff/lib/diff-template.ts +++ b/packages/@aws-cdk/cloudformation-diff/lib/diff-template.ts @@ -32,7 +32,7 @@ const DIFF_HANDLERS: HandlerRegistry = { * Compare two CloudFormation templates and return semantic differences between them. * * @param currentTemplate the current state of the stack. - * @param newTemplate the target state of the stack. + * @param newTemplate the target state of the stack. * * @returns a +types.TemplateDiff+ object that represents the changes that will happen if * a stack which current state is described by +currentTemplate+ is updated with @@ -144,4 +144,4 @@ function deepCopy(x: any): any { } return x; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/cloudformation-diff/lib/format.ts b/packages/@aws-cdk/cloudformation-diff/lib/format.ts index df88d075cc2f6..c808dbc822497 100644 --- a/packages/@aws-cdk/cloudformation-diff/lib/format.ts +++ b/packages/@aws-cdk/cloudformation-diff/lib/format.ts @@ -8,15 +8,23 @@ import { deepEqual } from './diff/util'; import { IamChanges } from './iam/iam-changes'; import { SecurityGroupChanges } from './network/security-group-changes'; +// tslint:disable-next-line:no-var-requires +const { structuredPatch } = require('diff'); + /** * Renders template differences to the process' console. * - * @param templateDiff TemplateDiff to be rendered to the console. + * @param stream The IO stream where to output the rendered diff. + * @param templateDiff TemplateDiff to be rendered to the console. * @param logicalToPathMap A map from logical ID to construct path. Useful in * case there is no aws:cdk:path metadata in the template. + * @param context the number of context lines to use in arbitrary JSON diff (defaults to 3). */ -export function formatDifferences(stream: NodeJS.WriteStream, templateDiff: TemplateDiff, logicalToPathMap: { [logicalId: string]: string } = { }) { - const formatter = new Formatter(stream, logicalToPathMap, templateDiff); +export function formatDifferences(stream: NodeJS.WriteStream, + templateDiff: TemplateDiff, + logicalToPathMap: { [logicalId: string]: string } = { }, + context: number = 3) { + const formatter = new Formatter(stream, logicalToPathMap, templateDiff, context); if (templateDiff.awsTemplateFormatVersion || templateDiff.transform || templateDiff.description) { formatter.printSectionHeader('Template'); @@ -40,8 +48,11 @@ export function formatDifferences(stream: NodeJS.WriteStream, templateDiff: Temp /** * Renders a diff of security changes to the given stream */ -export function formatSecurityChanges(stream: NodeJS.WriteStream, templateDiff: TemplateDiff, logicalToPathMap: {[logicalId: string]: string} = {}) { - const formatter = new Formatter(stream, logicalToPathMap, templateDiff); +export function formatSecurityChanges(stream: NodeJS.WriteStream, + templateDiff: TemplateDiff, + logicalToPathMap: {[logicalId: string]: string} = {}, + context?: number) { + const formatter = new Formatter(stream, logicalToPathMap, templateDiff, context); formatSecurityChangesWithBanner(formatter, templateDiff); } @@ -56,11 +67,15 @@ function formatSecurityChangesWithBanner(formatter: Formatter, templateDiff: Tem } const ADDITION = colors.green('[+]'); +const CONTEXT = colors.grey('[ ]'); const UPDATE = colors.yellow('[~]'); const REMOVAL = colors.red('[-]'); class Formatter { - constructor(private readonly stream: NodeJS.WriteStream, private readonly logicalToPathMap: { [logicalId: string]: string }, diff?: TemplateDiff) { + constructor(private readonly stream: NodeJS.WriteStream, + private readonly logicalToPathMap: { [logicalId: string]: string }, + diff?: TemplateDiff, + private readonly context: number = 3) { // Read additional construct paths from the diff if it is supplied if (diff) { this.readConstructPathsFrom(diff); @@ -126,7 +141,7 @@ class Formatter { * Print a resource difference for a given logical ID. * * @param logicalId the logical ID of the resource that changed. - * @param diff the change to be rendered. + * @param diff the change to be rendered. */ public formatResourceDifference(_type: string, logicalId: string, diff: ResourceDifference) { const resourceType = diff.isRemoval ? diff.oldResourceType : diff.newResourceType; @@ -184,9 +199,9 @@ class Formatter { /** * Renders a tree of differences under a particular name. - * @param name the name of the root of the tree. - * @param diff the difference on the tree. - * @param last whether this is the last node of a parent tree. + * @param name the name of the root of the tree. + * @param diff the difference on the tree. + * @param last whether this is the last node of a parent tree. */ public formatTreeDiff(name: string, diff: Difference, last: boolean) { let additionalInfo = ''; @@ -210,10 +225,19 @@ class Formatter { * @param linePrefix a prefix (indent-like) to be used on every line. */ public formatObjectDiff(oldObject: any, newObject: any, linePrefix: string) { - if ((typeof oldObject !== typeof newObject) || Array.isArray(oldObject) || typeof oldObject === 'string' || typeof oldObject === 'number') { + if ((typeof oldObject !== typeof newObject) || Array.isArray(oldObject) || typeof oldObject === 'string' || typeof oldObject === 'number') { if (oldObject !== undefined && newObject !== undefined) { - this.print('%s ├─ %s %s', linePrefix, REMOVAL, this.formatValue(oldObject, colors.red)); - this.print('%s └─ %s %s', linePrefix, ADDITION, this.formatValue(newObject, colors.green)); + if (typeof oldObject === 'object' || typeof newObject === 'object') { + const oldStr = JSON.stringify(oldObject, null, 2); + const newStr = JSON.stringify(newObject, null, 2); + const diff = _diffStrings(oldStr, newStr, this.context); + for (let i = 0 ; i < diff.length ; i++) { + this.print('%s %s %s', linePrefix, i === 0 ? '└─' : ' ', diff[i]); + } + } else { + this.print('%s ├─ %s %s', linePrefix, REMOVAL, this.formatValue(oldObject, colors.red)); + this.print('%s └─ %s %s', linePrefix, ADDITION, this.formatValue(newObject, colors.green)); + } } else if (oldObject !== undefined /* && newObject === undefined */) { this.print('%s └─ %s', linePrefix, this.formatValue(oldObject, colors.red)); } else /* if (oldObject === undefined && newObject !== undefined) */ { @@ -398,3 +422,75 @@ function stripHorizontalLines(tableRendering: string) { return cols[1]; } } + +/** + * A patch as returned by ``diff.structuredPatch``. + */ +interface Patch { + /** + * Hunks in the patch. + */ + hunks: ReadonlyArray; +} + +/** + * A hunk in a patch produced by ``diff.structuredPatch``. + */ +interface PatchHunk { + oldStart: number; + oldLines: number; + newStart: number; + newLines: number; + lines: string[]; +} + +/** + * Creates a unified diff of two strings. + * + * @param oldStr the "old" version of the string. + * @param newStr the "new" version of the string. + * @param context the number of context lines to use in arbitrary JSON diff. + * + * @returns an array of diff lines. + */ +function _diffStrings(oldStr: string, newStr: string, context: number): string[] { + const patch: Patch = structuredPatch(null, null, oldStr, newStr, null, null, { context }); + const result = new Array(); + for (const hunk of patch.hunks) { + result.push(colors.magenta(`@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`)); + const baseIndent = _findIndent(hunk.lines); + for (const line of hunk.lines) { + // Don't care about termination newline. + if (line === '\\ No newline at end of file') { continue; } + const marker = line.charAt(0); + const text = line.slice(1 + baseIndent); + switch (marker) { + case ' ': + result.push(`${CONTEXT} ${text}`); + break; + case '+': + result.push(colors.bold(`${ADDITION} ${colors.green(text)}`)); + break; + case '-': + result.push(colors.bold(`${REMOVAL} ${colors.red(text)}`)); + break; + default: + throw new Error(`Unexpected diff marker: ${marker} (full line: ${line})`); + } + } + } + return result; + + function _findIndent(lines: string[]): number { + let indent = Number.MAX_SAFE_INTEGER; + for (const line of lines) { + for (let i = 1 ; i < line.length ; i++) { + if (line.charAt(i) !== ' ') { + indent = indent > i - 1 ? i - 1 : indent; + break; + } + } + } + return indent; + } +} diff --git a/packages/@aws-cdk/cloudformation-diff/package-lock.json b/packages/@aws-cdk/cloudformation-diff/package-lock.json index 7ac316ae5efc2..b8036bf0336eb 100644 --- a/packages/@aws-cdk/cloudformation-diff/package-lock.json +++ b/packages/@aws-cdk/cloudformation-diff/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cloudformation-diff", - "version": "0.18.1", + "version": "0.21.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -35,6 +35,11 @@ "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.2.tgz", "integrity": "sha512-rhP0JSBGYvpcNQj4s5AdShMeE5ahMop96cTeDl/v9qQQm2fYClE2QXZRi8wLzc+GmXSxdIqqbOIAhyObEXDbfQ==" }, + "diff": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==" + }, "fast-check": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-1.8.0.tgz", diff --git a/packages/@aws-cdk/cloudformation-diff/package.json b/packages/@aws-cdk/cloudformation-diff/package.json index 19f92a3197b1a..5c1c1454d720b 100644 --- a/packages/@aws-cdk/cloudformation-diff/package.json +++ b/packages/@aws-cdk/cloudformation-diff/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cloudformation-diff", - "version": "0.21.0", + "version": "0.22.0", "description": "Utilities to diff CDK stacks against CloudFormation templates", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -23,18 +23,19 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-cdk/cfnspec": "^0.21.0", - "@aws-cdk/cx-api": "^0.21.0", + "@aws-cdk/cfnspec": "^0.22.0", + "@aws-cdk/cx-api": "^0.22.0", "cli-table": "^0.3.1", "colors": "^1.2.1", + "diff": "^4.0.1", "fast-deep-equal": "^2.0.1", "source-map-support": "^0.5.6" }, "devDependencies": { "@types/cli-table": "^0.3.0", - "cdk-build-tools": "^0.21.0", + "cdk-build-tools": "^0.22.0", "fast-check": "^1.8.0", - "pkglint": "^0.21.0" + "pkglint": "^0.22.0" }, "repository": { "url": "https://github.com/awslabs/aws-cdk.git", diff --git a/packages/@aws-cdk/cx-api/lib/cxapi.ts b/packages/@aws-cdk/cx-api/lib/cxapi.ts index 83c0fa6f4828a..867fd4a1af323 100644 --- a/packages/@aws-cdk/cx-api/lib/cxapi.ts +++ b/packages/@aws-cdk/cx-api/lib/cxapi.ts @@ -56,6 +56,11 @@ export interface SynthesizedStack { missing?: { [key: string]: MissingContext }; metadata: StackMetadata; template: any; + + /** + * Other stacks this stack depends on + */ + dependsOn?: string[]; } /** diff --git a/packages/@aws-cdk/cx-api/package.json b/packages/@aws-cdk/cx-api/package.json index 1f29869c88486..b2df53d303564 100644 --- a/packages/@aws-cdk/cx-api/package.json +++ b/packages/@aws-cdk/cx-api/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cx-api", - "version": "0.21.0", + "version": "0.22.0", "description": "Cloud executable protocol", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -39,8 +39,8 @@ }, "license": "Apache-2.0", "devDependencies": { - "cdk-build-tools": "^0.21.0", - "pkglint": "^0.21.0" + "cdk-build-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "repository": { "url": "https://github.com/awslabs/aws-cdk.git", diff --git a/packages/@aws-cdk/runtime-values/lib/rtv.ts b/packages/@aws-cdk/runtime-values/lib/rtv.ts index 53be77ca90d30..3cf7222e78c0b 100644 --- a/packages/@aws-cdk/runtime-values/lib/rtv.ts +++ b/packages/@aws-cdk/runtime-values/lib/rtv.ts @@ -27,11 +27,6 @@ export class RuntimeValue extends cdk.Construct { */ public static readonly ENV_NAME = 'RTV_STACK_NAME'; - /** - * The value to assign to the `RTV_STACK_NAME` environment variable. - */ - public static readonly ENV_VALUE = new cdk.AwsStackName(); - /** * IAM actions needed to read a value from an SSM parameter. */ @@ -41,6 +36,11 @@ export class RuntimeValue extends cdk.Construct { 'ssm:GetParameter' ]; + /** + * The value to assign to the `RTV_STACK_NAME` environment variable. + */ + public readonly envValue: string; + /** * The name of the runtime parameter. */ @@ -54,7 +54,10 @@ export class RuntimeValue extends cdk.Construct { constructor(scope: cdk.Construct, id: string, props: RuntimeValueProps) { super(scope, id); - this.parameterName = `/rtv/${new cdk.AwsStackName()}/${props.package}/${id}`; + const stack = cdk.Stack.find(this); + + this.parameterName = `/rtv/${stack.stackName}/${props.package}/${id}`; + this.envValue = stack.stackName; new ssm.CfnParameter(this, 'Parameter', { name: this.parameterName, @@ -62,7 +65,7 @@ export class RuntimeValue extends cdk.Construct { value: props.value, }); - this.parameterArn = cdk.ArnUtils.fromComponents({ + this.parameterArn = cdk.Stack.find(this).formatArn({ service: 'ssm', resource: 'parameter', resourceName: this.parameterName diff --git a/packages/@aws-cdk/runtime-values/package.json b/packages/@aws-cdk/runtime-values/package.json index 69c4cc442c7f7..e50ad6a7a87a4 100644 --- a/packages/@aws-cdk/runtime-values/package.json +++ b/packages/@aws-cdk/runtime-values/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/runtime-values", - "version": "0.21.0", + "version": "0.22.0", "description": "Runtime values support for the AWS CDK", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -49,25 +49,25 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.21.0", - "@aws-cdk/aws-ec2": "^0.21.0", - "@aws-cdk/aws-lambda": "^0.21.0", - "@aws-cdk/aws-sqs": "^0.21.0", - "cdk-build-tools": "^0.21.0", - "cdk-integ-tools": "^0.21.0", - "pkglint": "^0.21.0" + "@aws-cdk/assert": "^0.22.0", + "@aws-cdk/aws-ec2": "^0.22.0", + "@aws-cdk/aws-lambda": "^0.22.0", + "@aws-cdk/aws-sqs": "^0.22.0", + "cdk-build-tools": "^0.22.0", + "cdk-integ-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/aws-ssm": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/aws-ssm": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.21.0", - "@aws-cdk/cdk": "^0.21.0" + "@aws-cdk/aws-iam": "^0.22.0", + "@aws-cdk/cdk": "^0.22.0" }, "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts b/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts index ab9432cb88353..81e8b7d4d48f6 100644 --- a/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts +++ b/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts @@ -31,7 +31,7 @@ class TestStack extends cdk.Stack { // adds the `RTV_STACK_NAME` to the environment of the lambda function // and the fleet (via user-data) - fn.addEnvironment(RuntimeValue.ENV_NAME, RuntimeValue.ENV_VALUE); + fn.addEnvironment(RuntimeValue.ENV_NAME, queueUrlRtv.envValue); } } diff --git a/packages/@aws-cdk/runtime-values/test/test.rtv.ts b/packages/@aws-cdk/runtime-values/test/test.rtv.ts index 4cd4f6c6a95ab..5bb331617dd87 100644 --- a/packages/@aws-cdk/runtime-values/test/test.rtv.ts +++ b/packages/@aws-cdk/runtime-values/test/test.rtv.ts @@ -23,6 +23,8 @@ class RuntimeValueTest extends cdk.Construct { constructor(scope: cdk.Construct, id: string) { super(scope, id); + const stack = cdk.Stack.find(this); + const queue = new sqs.CfnQueue(this, 'Queue', {}); const role = new iam.Role(this, 'Role', { @@ -42,7 +44,7 @@ class RuntimeValueTest extends cdk.Construct { role: role.roleArn, environment: { variables: { - [RuntimeValue.ENV_NAME]: RuntimeValue.ENV_VALUE + [RuntimeValue.ENV_NAME]: stack.stackName, } } }); diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index e505d6c6ef9d5..e869181ad8f51 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -9,7 +9,8 @@ import yargs = require('yargs'); import { bootstrapEnvironment, deployStack, destroyStack, loadToolkitInfo, Mode, SDK } from '../lib'; import { environmentsFromDescriptors, globEnvironmentsFromStacks } from '../lib/api/cxapp/environments'; -import { AppStacks, listStackNames } from '../lib/api/cxapp/stacks'; +import { AppStacks, ExtendedStackSelection, listStackNames } from '../lib/api/cxapp/stacks'; +import { leftPad } from '../lib/api/util/string-manipulation'; import { printSecurityDiff, printStackDiff, RequireApproval } from '../lib/diff'; import { availableInitLanguages, cliInit, printAvailableTemplates } from '../lib/init'; import { interactive } from '../lib/interactive'; @@ -52,14 +53,19 @@ async function parseCommandLineArguments() { .command([ 'list', 'ls' ], 'Lists all stacks in the app', yargs => yargs .option('long', { type: 'boolean', default: false, alias: 'l', desc: 'display environment information for each stack' })) .command([ 'synthesize [STACKS..]', 'synth [STACKS..]' ], 'Synthesizes and prints the CloudFormation template for this stack', yargs => yargs + .option('exclusively', { type: 'boolean', alias: 'e', desc: 'only deploy requested stacks, don\'t include dependencies' }) .option('interactive', { type: 'boolean', alias: 'i', desc: 'interactively watch and show template updates' }) - .option('output', { type: 'string', alias: 'o', desc: 'write CloudFormation template for requested stacks to the given directory' })) + .option('output', { type: 'string', alias: 'o', desc: 'write CloudFormation template for requested stacks to the given directory' }) + .option('numbered', { type: 'boolean', alias: 'n', desc: 'Prefix filenames with numbers to indicate deployment ordering' })) .command('bootstrap [ENVIRONMENTS..]', 'Deploys the CDK toolkit stack into an AWS environment') .command('deploy [STACKS..]', 'Deploys the stack(s) named STACKS into your AWS account', yargs => yargs + .option('exclusively', { type: 'boolean', alias: 'e', desc: 'only deploy requested stacks, don\'t include dependencies' }) .option('require-approval', { type: 'string', choices: [RequireApproval.Never, RequireApproval.AnyChange, RequireApproval.Broadening], desc: 'what security-sensitive changes need manual approval' })) .command('destroy [STACKS..]', 'Destroy the stack(s) named STACKS', yargs => yargs + .option('exclusively', { type: 'boolean', alias: 'x', desc: 'only deploy requested stacks, don\'t include dependees' }) .option('force', { type: 'boolean', alias: 'f', desc: 'Do not ask for confirmation before destroying the stacks' })) - .command('diff [STACK]', 'Compares the specified stack with the deployed stack or a local template file', yargs => yargs + .command('diff [STACK]', 'Compares the specified stack with the deployed stack or a local template file, and returns with status 1 if any difference is found', yargs => yargs + .option('context-lines', { type: 'number', desc: 'number of context lines to include in arbitrary JSON diff rendering', default: 3 }) .option('template', { type: 'string', desc: 'the path to the CloudFormation template to compare with' }) .option('strict', { type: 'boolean', desc: 'do not filter out AWS::CDK::Metadata resources', default: false })) .command('metadata [STACK]', 'Returns all metadata associated with this stack') @@ -142,7 +148,7 @@ async function initCommandLine() { return returnValue; } - async function main(command: string, args: any): Promise { + async function main(command: string, args: any): Promise { const toolkitStackName: string = configuration.combined.get(['toolkitStackName']) || DEFAULT_TOOLKIT_STACK_NAME; if (toolkitStackName !== DEFAULT_TOOLKIT_STACK_NAME) { @@ -158,20 +164,20 @@ async function initCommandLine() { return await cliList({ long: args.long }); case 'diff': - return await diffStack(await findStack(args.STACK), args.template, args.strict); + return await diffStack(await findStack(args.STACK), args.template, args.strict, args.contextLines); case 'bootstrap': return await cliBootstrap(args.ENVIRONMENTS, toolkitStackName, args.roleArn); case 'deploy': - return await cliDeploy(args.STACKS, toolkitStackName, args.roleArn, configuration.combined.get(['requireApproval'])); + return await cliDeploy(args.STACKS, args.exclusively, toolkitStackName, args.roleArn, configuration.combined.get(['requireApproval'])); case 'destroy': - return await cliDestroy(args.STACKS, args.force, args.roleArn); + return await cliDestroy(args.STACKS, args.exclusively, args.force, args.roleArn); case 'synthesize': case 'synth': - return await cliSynthesize(args.STACKS, args.interactive, args.output, args.json); + return await cliSynthesize(args.STACKS, args.exclusively, args.interactive, args.output, args.json, args.numbered); case 'metadata': return await cliMetadata(await findStack(args.STACK)); @@ -236,10 +242,12 @@ async function initCommandLine() { * should be supplied, where the templates will be written. */ async function cliSynthesize(stackNames: string[], + exclusively: boolean, doInteractive: boolean, outputDir: string|undefined, - json: boolean): Promise { - const stacks = await appStacks.selectStacks(...stackNames); + json: boolean, + numbered: boolean): Promise { + const stacks = await appStacks.selectStacks(stackNames, exclusively ? ExtendedStackSelection.None : ExtendedStackSelection.Upstream); renames.validateSelectedStacks(stacks); if (doInteractive) { @@ -260,11 +268,14 @@ async function initCommandLine() { fs.mkdirpSync(outputDir); + let i = 0; for (const stack of stacks) { const finalName = renames.finalName(stack.name); - const fileName = `${outputDir}/${finalName}.template.${json ? 'json' : 'yaml'}`; + const prefix = numbered ? leftPad(`${i}`, 3, '0') + '.' : ''; + const fileName = `${outputDir}/${prefix}${finalName}.template.${json ? 'json' : 'yaml'}`; highlight(fileName); await fs.writeFile(fileName, toJsonOrYaml(stack.template)); + i++; } return undefined; // Nothing to print @@ -293,10 +304,14 @@ async function initCommandLine() { return 0; // exit-code } - async function cliDeploy(stackNames: string[], toolkitStackName: string, roleArn: string | undefined, requireApproval: RequireApproval) { + async function cliDeploy(stackNames: string[], + exclusively: boolean, + toolkitStackName: string, + roleArn: string | undefined, + requireApproval: RequireApproval) { if (requireApproval === undefined) { requireApproval = RequireApproval.Broadening; } - const stacks = await appStacks.selectStacks(...stackNames); + const stacks = await appStacks.selectStacks(stackNames, exclusively ? ExtendedStackSelection.None : ExtendedStackSelection.Upstream); renames.validateSelectedStacks(stacks); for (const stack of stacks) { @@ -357,8 +372,12 @@ async function initCommandLine() { } } - async function cliDestroy(stackNames: string[], force: boolean, roleArn: string | undefined) { - const stacks = await appStacks.selectStacks(...stackNames); + async function cliDestroy(stackNames: string[], exclusively: boolean, force: boolean, roleArn: string | undefined) { + const stacks = await appStacks.selectStacks(stackNames, exclusively ? ExtendedStackSelection.None : ExtendedStackSelection.Downstream); + + // The stacks will have been ordered for deployment, so reverse them for deletion. + stacks.reverse(); + renames.validateSelectedStacks(stacks); if (!force) { @@ -383,10 +402,10 @@ async function initCommandLine() { } } - async function diffStack(stackName: string, templatePath: string | undefined, strict: boolean): Promise { + async function diffStack(stackName: string, templatePath: string | undefined, strict: boolean, context: number): Promise { const stack = await appStacks.synthesizeStack(stackName); const currentTemplate = await readCurrentTemplate(stack, templatePath); - if (printStackDiff(currentTemplate, stack, strict) === 0) { + if (printStackDiff(currentTemplate, stack, strict, context) === 0) { return 0; } else { return 1; @@ -427,7 +446,7 @@ async function initCommandLine() { * Match a single stack from the list of available stacks */ async function findStack(name: string): Promise { - const stacks = await appStacks.selectStacks(name); + const stacks = await appStacks.selectStacks([name], ExtendedStackSelection.None); // Could have been a glob so check that we evaluated to exactly one if (stacks.length > 1) { diff --git a/packages/aws-cdk/integ-tests/app/app.js b/packages/aws-cdk/integ-tests/app/app.js index 8da2b8aa6507b..28c231e442326 100644 --- a/packages/aws-cdk/integ-tests/app/app.js +++ b/packages/aws-cdk/integ-tests/app/app.js @@ -30,6 +30,24 @@ class IamStack extends cdk.Stack { } } +class ProvidingStack extends cdk.Stack { + constructor(parent, id) { + super(parent, id); + + new sns.Topic(this, 'BogusTopic'); // Some filler + } +} + +class ConsumingStack extends cdk.Stack { + constructor(parent, id, providingStack) { + super(parent, id); + + + new sns.Topic(this, 'BogusTopic'); // Some filler + new cdk.Output(this, 'IConsumedSomething', { value: providingStack.stackName }); + } +} + const app = new cdk.App(); // Deploy all does a wildcard cdk-toolkit-integration-test-* @@ -37,5 +55,7 @@ new MyStack(app, 'cdk-toolkit-integration-test-1'); new YourStack(app, 'cdk-toolkit-integration-test-2'); // Not included in wildcard new IamStack(app, 'cdk-toolkit-integration-iam-test'); +const providing = new ProvidingStack(app, 'cdk-toolkit-integration-order-providing'); +new ConsumingStack(app, 'cdk-toolkit-integration-order-consuming', providing); app.run(); diff --git a/packages/aws-cdk/integ-tests/common.bash b/packages/aws-cdk/integ-tests/common.bash index 97733316dd841..6af9d8bec095c 100644 --- a/packages/aws-cdk/integ-tests/common.bash +++ b/packages/aws-cdk/integ-tests/common.bash @@ -77,7 +77,7 @@ function assert() { echo "| running ${command}" - $command > ${actual} || { + eval "$command" > ${actual} || { fail "command ${command} non-zero exit code" } diff --git a/packages/aws-cdk/integ-tests/test-cdk-order.sh b/packages/aws-cdk/integ-tests/test-cdk-order.sh new file mode 100755 index 0000000000000..3825579eac07e --- /dev/null +++ b/packages/aws-cdk/integ-tests/test-cdk-order.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -euo pipefail +scriptdir=$(cd $(dirname $0) && pwd) +source ${scriptdir}/common.bash +# ---------------------------------------------------------- + +setup + +# Deploy the consuming stack which will include the producing stack +cdk deploy cdk-toolkit-integration-order-consuming + +# Destroy the providing stack which will include the consuming stack +cdk destroy -f cdk-toolkit-integration-order-providing + +echo "✅ success" diff --git a/packages/aws-cdk/lib/api/cxapp/environments.ts b/packages/aws-cdk/lib/api/cxapp/environments.ts index a744622f29cde..ff63349b85e15 100644 --- a/packages/aws-cdk/lib/api/cxapp/environments.ts +++ b/packages/aws-cdk/lib/api/cxapp/environments.ts @@ -1,12 +1,12 @@ import cxapi = require('@aws-cdk/cx-api'); import minimatch = require('minimatch'); -import { AppStacks } from './stacks'; +import { AppStacks, ExtendedStackSelection } from './stacks'; export async function globEnvironmentsFromStacks(appStacks: AppStacks, environmentGlobs: string[]): Promise { if (environmentGlobs.length === 0) { environmentGlobs = [ '**' ]; // default to ALL } - const stacks = await appStacks.selectStacks(); + const stacks = await appStacks.selectStacks([], ExtendedStackSelection.None); const availableEnvironments = distinct(stacks.map(stack => stack.environment) .filter(env => env !== undefined) as cxapi.Environment[]); diff --git a/packages/aws-cdk/lib/api/cxapp/stacks.ts b/packages/aws-cdk/lib/api/cxapp/stacks.ts index 2ba52322e148f..b6def12806b04 100644 --- a/packages/aws-cdk/lib/api/cxapp/stacks.ts +++ b/packages/aws-cdk/lib/api/cxapp/stacks.ts @@ -1,4 +1,5 @@ import cxapi = require('@aws-cdk/cx-api'); +import colors = require('colors/safe'); import minimatch = require('minimatch'); import yargs = require('yargs'); import contextproviders = require('../../context-providers'); @@ -6,6 +7,7 @@ import { debug, error, print, warning } from '../../logging'; import { Configuration } from '../../settings'; import cdkUtil = require('../../util'); import { SDK } from '../util/sdk'; +import { topologicalSort } from '../util/toposort'; import { execProgram } from './exec'; /** @@ -29,7 +31,7 @@ export class AppStacks { * It's an error if there are no stacks to select, or if one of the requested parameters * refers to a nonexistant stack. */ - public async selectStacks(...selectors: string[]): Promise { + public async selectStacks(selectors: string[], extendedSelection: ExtendedStackSelection): Promise { selectors = selectors.filter(s => s != null); // filter null/undefined const stacks: cxapi.SynthesizedStack[] = await this.listStacks(); @@ -42,14 +44,19 @@ export class AppStacks { return stacks; } + const allStacks = new Map(); + for (const stack of stacks) { + allStacks.set(stack.name, stack); + } + // For every selector argument, pick stacks from the list. - const matched = new Set(); + const selectedStacks = new Map(); for (const pattern of selectors) { let found = false; for (const stack of stacks) { - if (minimatch(stack.name, pattern)) { - matched.add(stack.name); + if (minimatch(stack.name, pattern) && !selectedStacks.has(stack.name)) { + selectedStacks.set(stack.name, stack); found = true; } } @@ -59,12 +66,30 @@ export class AppStacks { } } - return stacks.filter(s => matched.has(s.name)); + switch (extendedSelection) { + case ExtendedStackSelection.Downstream: + includeDownstreamStacks(selectedStacks, allStacks); + break; + case ExtendedStackSelection.Upstream: + includeUpstreamStacks(selectedStacks, allStacks); + break; + } + + // Filter original array because it is in the right order + return stacks.filter(s => selectedStacks.has(s.name)); } + /** + * Return all stacks in the CX + * + * If the stacks have dependencies between them, they will be returned in + * topologically sorted order. If there are dependencies that are not in the + * set, they will be ignored; it is the user's responsibility that the + * non-selected stacks have already been deployed previously. + */ public async listStacks(): Promise { const response = await this.synthesizeStacks(); - return response.stacks; + return topologicalSort(response.stacks, s => s.name, s => s.dependsOn || []); } /** @@ -197,3 +222,78 @@ export class AppStacks { export function listStackNames(stacks: cxapi.SynthesizedStack[]): string { return stacks.map(s => s.name).join(', '); } + +/** + * When selecting stacks, what other stacks to include because of dependencies + */ +export enum ExtendedStackSelection { + /** + * Don't select any extra stacks + */ + None, + + /** + * Include stacks that this stack depends on + */ + Upstream, + + /** + * Include stacks that depend on this stack + */ + Downstream +} + +/** + * Include stacks that depend on the stacks already in the set + * + * Modifies `selectedStacks` in-place. + */ +function includeDownstreamStacks(selectedStacks: Map, allStacks: Map) { + const added = new Array(); + + let madeProgress = true; + while (madeProgress) { + madeProgress = false; + + for (const [name, stack] of allStacks) { + // Select this stack if it's not selected yet AND it depends on a stack that's in the selected set + if (!selectedStacks.has(name) && (stack.dependsOn || []).some(dependencyName => selectedStacks.has(dependencyName))) { + selectedStacks.set(name, stack); + added.push(name); + madeProgress = true; + } + } + } + + if (added.length > 0) { + print('Including depending stacks: %s', colors.bold(added.join(', '))); + } +} + +/** + * Include stacks that that stacks in the set depend on + * + * Modifies `selectedStacks` in-place. + */ +function includeUpstreamStacks(selectedStacks: Map, allStacks: Map) { + const added = new Array(); + let madeProgress = true; + while (madeProgress) { + madeProgress = false; + + for (const stack of selectedStacks.values()) { + // Select an additional stack if it's not selected yet and a dependency of a selected stack (and exists, obviously) + for (const dependencyName of (stack.dependsOn || [])) { + if (!selectedStacks.has(dependencyName) && allStacks.has(dependencyName)) { + added.push(dependencyName); + selectedStacks.set(dependencyName, allStacks.get(dependencyName)!); + madeProgress = true; + } + } + } + } + + if (added.length > 0) { + print('Including dependency stacks: %s', colors.bold(added.join(', '))); + } +} \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/util/string-manipulation.ts b/packages/aws-cdk/lib/api/util/string-manipulation.ts new file mode 100644 index 0000000000000..aa51679967ba3 --- /dev/null +++ b/packages/aws-cdk/lib/api/util/string-manipulation.ts @@ -0,0 +1,7 @@ +/** + * Pad 's' on the left with 'char' until it is n characters wide + */ +export function leftPad(s: string, n: number, char: string) { + const padding = Math.max(0, n - s.length); + return char.repeat(padding) + s; +} \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/util/toposort.ts b/packages/aws-cdk/lib/api/util/toposort.ts new file mode 100644 index 0000000000000..97dd35ea8bb46 --- /dev/null +++ b/packages/aws-cdk/lib/api/util/toposort.ts @@ -0,0 +1,44 @@ +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. + */ +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); + + for (const selected of selectable) { + ret.push(selected.element); + remaining.delete(selected.key); + } + + // 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(', ')}`); + } + } + + return ret; +} + +interface TopoElement { + key: string; + dependencies: string[]; + element: T; +} \ No newline at end of file diff --git a/packages/aws-cdk/lib/diff.ts b/packages/aws-cdk/lib/diff.ts index cc83d9fc88178..5a56a3fc6b1a0 100644 --- a/packages/aws-cdk/lib/diff.ts +++ b/packages/aws-cdk/lib/diff.ts @@ -8,10 +8,12 @@ import { print, warning } from './logging'; * * @param oldTemplate the old/current state of the stack. * @param newTemplate the new/target state of the stack. + * @param strict do not filter out AWS::CDK::Metadata + * @param context lines of context to use in arbitrary JSON diff * * @returns the count of differences that were rendered. */ -export function printStackDiff(oldTemplate: any, newTemplate: cxapi.SynthesizedStack, strict: boolean): number { +export function printStackDiff(oldTemplate: any, newTemplate: cxapi.SynthesizedStack, strict: boolean, context: number): number { if (_hasAssets(newTemplate)) { const issue = 'https://github.com/awslabs/aws-cdk/issues/395'; warning(`The ${newTemplate.name} stack uses assets, which are currently not accounted for in the diff output! See ${issue}`); @@ -30,7 +32,7 @@ export function printStackDiff(oldTemplate: any, newTemplate: cxapi.SynthesizedS } if (!diff.isEmpty) { - cfnDiff.formatDifferences(process.stderr, diff, buildLogicalToPathMap(newTemplate)); + cfnDiff.formatDifferences(process.stderr, diff, buildLogicalToPathMap(newTemplate), context); } else { print(colors.green('There were no differences')); } diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index baf1efbe8f939..d88f4e79ca779 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -1,7 +1,7 @@ { "name": "aws-cdk", "description": "CDK Toolkit, the command line tool for CDK apps", - "version": "0.21.0", + "version": "0.22.0", "main": "lib/index.js", "types": "lib/index.d.ts", "bin": { @@ -41,14 +41,14 @@ "@types/uuid": "^3.4.3", "@types/yaml": "^1.0.0", "@types/yargs": "^8.0.3", - "cdk-build-tools": "^0.21.0", + "cdk-build-tools": "^0.22.0", "mockery": "^2.1.0", - "pkglint": "^0.21.0" + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/applet-js": "^0.21.0", - "@aws-cdk/cloudformation-diff": "^0.21.0", - "@aws-cdk/cx-api": "^0.21.0", + "@aws-cdk/applet-js": "^0.22.0", + "@aws-cdk/cloudformation-diff": "^0.22.0", + "@aws-cdk/cx-api": "^0.22.0", "archiver": "^2.1.1", "aws-sdk": "^2.259.1", "camelcase": "^5.0.0", diff --git a/packages/simple-resource-bundler/package.json b/packages/simple-resource-bundler/package.json index 45559d1f8233d..bfed9fcde7865 100644 --- a/packages/simple-resource-bundler/package.json +++ b/packages/simple-resource-bundler/package.json @@ -1,6 +1,6 @@ { "name": "simple-resource-bundler", - "version": "0.21.0", + "version": "0.22.0", "description": "Command-line tool to embed resources into JS libraries", "main": "bundler.js", "types": "bundler.d.ts", @@ -24,8 +24,8 @@ "devDependencies": { "@types/fs-extra": "^5.0.4", "@types/yargs": "^8.0.3", - "cdk-build-tools": "^0.21.0", - "pkglint": "^0.21.0" + "cdk-build-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { "fs-extra": "^7.0.0", diff --git a/scripts/generate-aggregate-tsconfig.sh b/scripts/generate-aggregate-tsconfig.sh index 9b6b4649aae86..724186a28663a 100755 --- a/scripts/generate-aggregate-tsconfig.sh +++ b/scripts/generate-aggregate-tsconfig.sh @@ -8,10 +8,12 @@ echo ' "__comment__": "This file is necessary to make transitive Project Refe echo ' "files": [],' echo ' "references": [' comma=' ' -for package in $(node_modules/.bin/lerna ls -p); do - relpath=${package#"$prefix"} - echo ' '"$comma"'{ "path": "'"$relpath"'" }' - comma=', ' +for package in $(node_modules/.bin/lerna ls -ap); do + if [[ -f ${package}/tsconfig.json ]]; then + relpath=${package#"$prefix"} + echo ' '"$comma"'{ "path": "'"$relpath"'" }' + comma=', ' + fi done echo ' ]' echo '}' diff --git a/scripts/jetbrains-remove-node-modules.js b/scripts/jetbrains-remove-node-modules.js new file mode 100644 index 0000000000000..875ebfbc3b231 --- /dev/null +++ b/scripts/jetbrains-remove-node-modules.js @@ -0,0 +1,60 @@ +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +const getAllChildDirectories = (dir) => fs.readdirSync(dir).map(name => path.join(dir, name.toString())).filter(name => fs.lstatSync(name).isDirectory()); +const isNodeModulesDirectory = (name) => name.toString().endsWith('node_modules'); + +function getAllNodeModulesPaths(dir) { + let nodeModulesPaths = []; + getAllChildDirectories(dir).forEach(name => { + if (isNodeModulesDirectory(name)) { + console.log('Excluding ' + name); + nodeModulesPaths.push(name); + } else { + const subNodeModulesPaths = getAllNodeModulesPaths(name); + nodeModulesPaths = nodeModulesPaths.concat(subNodeModulesPaths); + } + }); + return nodeModulesPaths; +} + +// Should be run at the root directory +if (!fs.existsSync('lerna.json')) { + throw new Error('This script should be run from the root of the repo.'); +} + +const nodeModulesPaths = getAllNodeModulesPaths('.'); + +// Hardcoded exclusions for this project (in addition to node_modules) +const exclusions = nodeModulesPaths.map(path => ``); +exclusions.push(''); +exclusions.push(''); +exclusions.push(''); +exclusions.push(''); + +exclusionsString = exclusions.join(os.EOL); + +// Let filename be passed in as an override +let fileName = process.argv[2] || process.cwd().split('/').slice(-1).pop() + '.iml'; + +// Jetbrains IDEs store iml in .idea except for IntelliJ, which uses root. +if (fs.existsSync('.idea/' + fileName)) { + fileName = '.idea/' + fileName; +} else if (!fs.existsSync(fileName)) { + throw new Error('iml file not found in .idea or at root. Please pass in a path explicitly as the first argument.'); +} + +// Keep the contents. We are only updating exclusions. +const exclusionInfo = fs.readFileSync(fileName); + +const toWrite = exclusionInfo.toString().replace(/(?:\s.+)+\/content>/m, `${os.EOL}${exclusionsString}${os.EOL}`); + +console.log(os.EOL + 'Writing to file...'); + +// "Delete" the file first to avoid strange concurrent use errors. +fs.unlinkSync(fileName); + +fs.writeFileSync(fileName, toWrite); + +console.log('Done!'); diff --git a/tools/awslint/package.json b/tools/awslint/package.json index b86084317530f..4a80eeda18775 100644 --- a/tools/awslint/package.json +++ b/tools/awslint/package.json @@ -1,7 +1,7 @@ { "name": "awslint", "private": true, - "version": "0.21.0", + "version": "0.22.0", "description": "Enforces the AWS Construct Library guidelines", "main": "index.js", "scripts": { @@ -20,12 +20,12 @@ "yargs": "^12.0.5" }, "devDependencies": { - "tslint": "^5.12.0", - "typescript": "^3.2.2", + "@types/colors": "^1.2.1", "@types/fs-extra": "^5.0.4", "@types/jest": "^23.3.10", - "@types/colors": "^1.2.1", - "@types/yargs": "^12.0.4" + "@types/yargs": "^12.0.4", + "tslint": "^5.12.0", + "typescript": "^3.2.2" }, "repository": { "type": "git", diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index 83231fd466935..a9994143ce8e6 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -1,7 +1,7 @@ { "name": "cdk-build-tools", "private": true, - "version": "0.21.0", + "version": "0.22.0", "description": "Tools package with shared build scripts for CDK packages", "main": "lib/index.js", "repository": { @@ -29,16 +29,16 @@ "devDependencies": { "@types/fs-extra": "^5.0.4", "@types/yargs": "^8.0.3", - "pkglint": "^0.21.0" + "pkglint": "^0.22.0" }, "dependencies": { + "awslint": "^0.22.0", "fs-extra": "^7.0.0", "jsii": "^0.7.13", "jsii-pacmak": "^0.7.13", "nodeunit": "^0.11.3", "nyc": "^13.0.1", "typescript": "^3.1.2", - "awslint": "^0.21.0", "yargs": "^9.0.1" }, "keywords": [ diff --git a/tools/cdk-integ-tools/bin/cdk-integ.ts b/tools/cdk-integ-tools/bin/cdk-integ.ts index 898f86996423f..96c39794d5c0f 100644 --- a/tools/cdk-integ-tools/bin/cdk-integ.ts +++ b/tools/cdk-integ-tools/bin/cdk-integ.ts @@ -35,7 +35,8 @@ async function main() { } try { - await test.invoke([ ...args, 'deploy', '--prompt', 'never' ], { verbose: argv.verbose }); // Note: no context, so use default user settings! + // tslint:disable-next-line:max-line-length + await test.invoke([ ...args, 'deploy', '--require-approval', 'never' ], { verbose: argv.verbose }); // Note: no context, so use default user settings! console.error(`Success! Writing out reference synth.`); diff --git a/tools/cdk-integ-tools/package.json b/tools/cdk-integ-tools/package.json index d034893a8a4c6..500fea706e0bd 100644 --- a/tools/cdk-integ-tools/package.json +++ b/tools/cdk-integ-tools/package.json @@ -1,7 +1,7 @@ { "name": "cdk-integ-tools", "private": true, - "version": "0.21.0", + "version": "0.22.0", "description": "Package with integration test scripts for CDK packages", "main": "index.js", "repository": { @@ -27,13 +27,13 @@ "license": "Apache-2.0", "devDependencies": { "@types/yargs": "^8.0.3", - "cdk-build-tools": "^0.21.0", - "pkglint": "^0.21.0" + "cdk-build-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { - "@aws-cdk/cloudformation-diff": "^0.21.0", - "@aws-cdk/cx-api": "^0.21.0", - "aws-cdk": "^0.21.0", + "@aws-cdk/cloudformation-diff": "^0.22.0", + "@aws-cdk/cx-api": "^0.22.0", + "aws-cdk": "^0.22.0", "yargs": "^9.0.1" }, "keywords": [ diff --git a/tools/cfn2ts/bin/cfn2ts.ts b/tools/cfn2ts/bin/cfn2ts.ts index 36502e2a72549..c38e67b3aa75d 100755 --- a/tools/cfn2ts/bin/cfn2ts.ts +++ b/tools/cfn2ts/bin/cfn2ts.ts @@ -1,20 +1,43 @@ #!/usr/bin/env node -import 'source-map-support/register'; - +import fs = require('fs-extra'); import yargs = require('yargs'); import generate from '../lib'; -// tslint:disable-next-line:no-unused-expression -const argv = - yargs.usage('Usage: cfn2ts') - .option('scope', { type: 'string', desc: 'Scope to generate TypeScript for (e.g: AWS::IAM)', required: true }) +// tslint:disable:no-console +// tslint:disable:max-line-length + +async function main() { + const argv = yargs.usage('Usage: cfn2ts') + .option('scope', { type: 'string', desc: 'Scope to generate TypeScript for (e.g: AWS::IAM)' }) .option('out', { type: 'string', desc: 'Path to the directory where the TypeScript files should be written', default: 'lib' }) - .option('force', { type: 'boolean', desc: 'Generate the spec even if it appears up-to-date', default: false }) + .epilog('if --scope is not defined, cfn2ts will try to obtain the scope from the local package.json under the "cdk-build.cloudformation" key.') .argv; -generate(argv.scope, argv.out, argv.force).catch(err => { - // tslint:disable:no-console + if (!argv.scope) { + argv.scope = await tryAutoDetectScope(); + } + + if (!argv.scope) { + throw new Error(`--scope is not provided and cannot be auto-detected from package.json (under "cdk-build.cloudformation")`); + } + + await generate(argv.scope, argv.out); +} + +main().catch(err => { console.error(err); - // tslint:enable:no-console process.exit(1); }); + +async function tryAutoDetectScope() { + if (!await fs.pathExists('./package.json')) { + return undefined; + } + + const pkg = await fs.readJSON('./package.json'); + if (!pkg['cdk-build']) { + return undefined; + } + + return pkg['cdk-build'].cloudformation; +} diff --git a/tools/cfn2ts/lib/codegen.ts b/tools/cfn2ts/lib/codegen.ts index 63a65507d666c..17be51bc8ddce 100644 --- a/tools/cfn2ts/lib/codegen.ts +++ b/tools/cfn2ts/lib/codegen.ts @@ -71,23 +71,10 @@ export default class CodeGenerator { const cfnName = SpecName.parse(name); const resourceName = genspec.CodeName.forCfnResource(cfnName); - const legacyResourceName = genspec.CodeName.forLegacyResource(cfnName); this.code.line(); - const attributeTypes = this.emitResourceType(resourceName, resourceType); + this.emitResourceType(resourceName, resourceType); this.emitPropertyTypes(name, resourceName); - - // emit the "cloudformation.XxxResource" classes for backwards compatibility - // those will also include a deprecation warning. - this.code.line('// legacy "cloudformation" namespace (will be deprecated soon)'); - this.code.openBlock('export namespace cloudformation'); - this.emitResourceType(legacyResourceName, resourceType, resourceName); - this.emitPropertyTypes(name, legacyResourceName); - this.code.closeBlock(); - - for (const attributeType of attributeTypes) { - this.emitAttributeType(attributeType); - } } } @@ -177,7 +164,7 @@ export default class CodeGenerator { } } - private emitResourceType(resourceName: genspec.CodeName, spec: schema.ResourceType, deprecated?: genspec.CodeName) { + private emitResourceType(resourceName: genspec.CodeName, spec: schema.ResourceType, deprecated?: genspec.CodeName): void { this.beginNamespace(resourceName); // @@ -215,7 +202,6 @@ export default class CodeGenerator { // Attributes // - const attributeTypes = new Array(); const attributes = new Array(); if (spec.Attributes) { @@ -227,10 +213,9 @@ export default class CodeGenerator { this.docLink(undefined, `@cloudformation_attribute ${attributeName}`); const attr = genspec.attributeDefinition(resourceName, attributeName, attributeSpec); - this.code.line(`public readonly ${attr.propertyName}: ${attr.attributeType.typeName.className};`); + this.code.line(`public readonly ${attr.propertyName}: ${attr.attributeType};`); attributes.push(attr); - attributeTypes.push(attr.attributeType); } } @@ -242,9 +227,8 @@ export default class CodeGenerator { // If there's already an attribute with the same name, ref is not needed if (!attributes.some(a => a.propertyName === refAttribute.propertyName)) { - this.code.line(`public readonly ${refAttribute.propertyName}: ${refAttribute.attributeType.typeName.className};`); + this.code.line(`public readonly ${refAttribute.propertyName}: ${refAttribute.attributeType};`); attributes.push(refAttribute); - attributeTypes.push(refAttribute.attributeType); } } @@ -286,14 +270,12 @@ export default class CodeGenerator { // initialize all attribute properties for (const at of attributes) { - if (at.attributeType.isPrimitive) { - if (at.attributeType.typeName.className === 'string') { - this.code.line(`this.${at.propertyName} = ${at.constructorArguments}.toString();`); - } else { - throw new Error(`Unsupported primitive attribute type ${at.attributeType.typeName.className}`); - } - } else { - this.code.line(`this.${at.propertyName} = new ${at.attributeType.typeName.className}(${at.constructorArguments});`); + if (at.attributeType === 'string') { + this.code.line(`this.${at.propertyName} = ${at.constructorArguments}.toString();`); + } else if (at.attributeType === 'string[]') { + this.code.line(`this.${at.propertyName} = ${at.constructorArguments}.toList();`); + } else if (at.attributeType === genspec.TOKEN_NAME.fqn) { + this.code.line(`this.${at.propertyName} = ${at.constructorArguments};`); } } @@ -315,8 +297,6 @@ export default class CodeGenerator { this.closeClass(resourceName); this.endNamespace(resourceName); - - return attributeTypes; } /** @@ -330,7 +310,7 @@ export default class CodeGenerator { this.code.closeBlock(); this.code.openBlock('protected renderProperties(properties: any): { [key: string]: any } '); - this.code.line(`return ${genspec.cfnMapperName(propsType).fqn}(${CORE}.resolve(properties));`); + this.code.line(`return ${genspec.cfnMapperName(propsType).fqn}(this.node.resolve(properties));`); this.code.closeBlock(); } @@ -486,26 +466,6 @@ export default class CodeGenerator { this.code.closeBlock(); } - /** - * Attribute types are classes that represent resource attributes (e.g. QueueArnAttribute). - */ - private emitAttributeType(attr: genspec.AttributeTypeDeclaration) { - if (!attr.baseClassName) { - return; // primitive, no attribute type generated - } - - this.code.line(); - this.openClass(attr.typeName, attr.docLink, attr.baseClassName.fqn); - // Add a private member that will make the class structurally - // different in TypeScript, which prevents assigning returning - // incorrectly-typed Tokens. Those will cause ClassCastExceptions - // in strictly-typed languages. - this.code.line('// @ts-ignore: private but unused on purpose.'); - this.code.line(`private readonly thisIsA${attr.typeName.className} = true;`); - - this.closeClass(attr.typeName); - } - private emitProperty(context: genspec.CodeName, propName: string, spec: schema.Property, additionalDocs: string): string { const question = spec.Required ? '' : '?'; const javascriptPropertyName = genspec.cloudFormationToScriptName(propName); @@ -596,8 +556,14 @@ export default class CodeGenerator { alternatives.push(this.renderTypeUnion(resourceContext, types)); } - // Always - alternatives.push(genspec.TOKEN_NAME.fqn); + // Only if this property is not of a "tokenizable type" (string, string[], + // number in the future) we add a type union for `cdk.Token`. We rather + // everything to be tokenizable because there are languages that do not + // support union types (i.e. Java, .NET), so we lose type safety if we have + // a union. + if (!tokenizableType(alternatives)) { + alternatives.push(genspec.TOKEN_NAME.fqn); + } return alternatives.join(' | '); } @@ -649,3 +615,22 @@ function mapperNames(types: genspec.CodeName[]): string { function quoteCode(code: string): string { return '``' + code + '``'; } + +function tokenizableType(alternatives: string[]) { + if (alternatives.length > 1) { + return false; + } + + const type = alternatives[0]; + if (type === 'string') { + return true; + } + + if (type === 'string[]') { + return true; + } + + // TODO: number + + return false; +} \ No newline at end of file diff --git a/tools/cfn2ts/lib/genspec.ts b/tools/cfn2ts/lib/genspec.ts index e63444f3b7db3..1d65543141817 100644 --- a/tools/cfn2ts/lib/genspec.ts +++ b/tools/cfn2ts/lib/genspec.ts @@ -8,7 +8,6 @@ import { itemTypeNames, PropertyAttributeName, scalarTypeNames, SpecName } from import util = require('./util'); const RESOURCE_CLASS_PREFIX = 'Cfn'; -const LEGACY_RESOURCE_CLASS_POSTFIX = 'Resource'; export const CORE_NAMESPACE = 'cdk'; @@ -25,20 +24,6 @@ export class CodeName { return new CodeName(packageName(specName), '', className, specName); } - public static forLegacyResource(specName: SpecName): CodeName { - let className = specName.resourceName; - - // add a "Resource" postfix to the class name (unless there is already a resource postfix). - if (!className.endsWith(LEGACY_RESOURCE_CLASS_POSTFIX)) { - className += LEGACY_RESOURCE_CLASS_POSTFIX; - } else { - // tslint:disable-next-line:no-console - console.error('INFO: Resource class %s already had a %s postfix, so we didn\'t add one', className, LEGACY_RESOURCE_CLASS_POSTFIX); - } - - return new CodeName(packageName(specName), '', className, specName); - } - public static forResourceProperties(resourceName: CodeName): CodeName { return new CodeName(resourceName.packageName, resourceName.namespace, `${resourceName.className}Props`, resourceName.specName); } @@ -102,22 +87,6 @@ export class CodeName { } } -/** - * Class declaration - */ -export class AttributeTypeDeclaration { - constructor( - readonly typeName: CodeName, - readonly baseClassName?: CodeName, - readonly docLink?: string - ) { - } - - public get isPrimitive() { - return !this.baseClassName; - } -} - export const TAG_NAME = new CodeName('', CORE_NAMESPACE, 'Tag'); export const TOKEN_NAME = new CodeName('', CORE_NAMESPACE, 'Token'); @@ -127,7 +96,7 @@ export const TOKEN_NAME = new CodeName('', CORE_NAMESPACE, 'Token'); export class Attribute { constructor( readonly propertyName: string, - readonly attributeType: AttributeTypeDeclaration, + readonly attributeType: string, readonly constructorArguments: string) { } } @@ -192,13 +161,15 @@ export function attributeDefinition(resourceName: CodeName, attributeName: strin const descriptiveName = descriptiveAttributeName(resourceName, attributeName); // "BucketArn" const propertyName = cloudFormationToScriptName(descriptiveName); // "bucketArn" - let attrType; + let attrType: string; if ('PrimitiveType' in spec && spec.PrimitiveType === 'String') { - attrType = new AttributeTypeDeclaration(CodeName.forPrimitive('string')); + attrType = 'string'; + } else if ('Type' in spec && 'PrimitiveItemType' in spec && spec.Type === 'List' && spec.PrimitiveItemType === 'String') { + attrType = 'string[]'; } else { - // Not in a namespace, base the name on the descriptive name - const typeName = new CodeName(resourceName.packageName, '', descriptiveName); // "BucketArn" - attrType = new AttributeTypeDeclaration(typeName, TOKEN_NAME, undefined); + // tslint:disable-next-line:no-console + console.error(`WARNING: Unable to represent attribute type ${JSON.stringify(spec)} as a native type`); + attrType = TOKEN_NAME.fqn; } const constructorArguments = `this.getAtt('${attributeName}')`; @@ -213,8 +184,7 @@ export function refAttributeDefinition(resourceName: CodeName, refKind: string): const constructorArguments = 'this.ref'; - const refType = new AttributeTypeDeclaration(CodeName.forPrimitive('string')); - return new Attribute(propertyName, refType, constructorArguments); + return new Attribute(propertyName, 'string', constructorArguments); } /** diff --git a/tools/cfn2ts/lib/index.ts b/tools/cfn2ts/lib/index.ts index d06c845b6ec6f..3d38b29efc662 100644 --- a/tools/cfn2ts/lib/index.ts +++ b/tools/cfn2ts/lib/index.ts @@ -1,11 +1,9 @@ import cfnSpec = require('@aws-cdk/cfnspec'); -import colors = require('colors/safe'); import fs = require('fs-extra'); -import path = require('path'); import CodeGenerator from './codegen'; import { packageName } from './genspec'; -export default async function(scope: string, outPath: string, force: boolean) { +export default async function(scope: string, outPath: string) { if (outPath !== '.') { await fs.mkdirp(outPath); } const spec = cfnSpec.filteredSpecification(s => s.startsWith(`${scope}::`)); @@ -15,15 +13,7 @@ export default async function(scope: string, outPath: string, force: boolean) { const name = packageName(scope); const generator = new CodeGenerator(name, spec); - - if (!force && await generator.upToDate(outPath)) { - // tslint:disable-next-line:no-console - console.log('Generated code already up-to-date: %s', colors.green(path.join(outPath, generator.outputFile))); - return; - } generator.emitCode(); - // tslint:disable-next-line:no-console - console.log('Generated code: %s', colors.green(path.join(outPath, generator.outputFile))); await generator.save(outPath); } diff --git a/tools/cfn2ts/package.json b/tools/cfn2ts/package.json index 57958466d3a7a..2ceb2437a2fe0 100644 --- a/tools/cfn2ts/package.json +++ b/tools/cfn2ts/package.json @@ -1,7 +1,7 @@ { "name": "cfn2ts", "private": true, - "version": "0.21.0", + "version": "0.22.0", "description": "Generates typescript types from CloudFormation spec, with support for enrichments", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -30,19 +30,17 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-cdk/cfnspec": "^0.21.0", + "@aws-cdk/cfnspec": "^0.22.0", "codemaker": "^0.6.4", - "colors": "^1.2.1", "fast-json-patch": "^2.0.6", "fs-extra": "^7.0.0", - "source-map-support": "^0.5.6", "yargs": "^9.0.1" }, "devDependencies": { "@types/fs-extra": "^5.0.4", "@types/yargs": "^8.0.3", - "cdk-build-tools": "^0.21.0", - "pkglint": "^0.21.0" + "cdk-build-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "keywords": [ "aws", diff --git a/tools/pkglint/lib/rules.ts b/tools/pkglint/lib/rules.ts index 4e6a6649fe5de..c0cc3775933e9 100644 --- a/tools/pkglint/lib/rules.ts +++ b/tools/pkglint/lib/rules.ts @@ -680,10 +680,30 @@ export class AwsLint extends ValidationRule { return; } + if (!isAWS(pkg)) { + return; + } + expectJSON(this.name, pkg, 'scripts.awslint', 'cdk-awslint'); } } +export class Cfn2Ts extends ValidationRule { + public name = 'cfn2ts'; + + public validate(pkg: PackageJson) { + if (!isJSII(pkg)) { + return; + } + + if (!isAWS(pkg)) { + return; + } + + expectJSON(this.name, pkg, 'scripts.cfn2ts', 'cfn2ts'); + } +} + /** * Determine whether this is a JSII package * @@ -693,6 +713,14 @@ function isJSII(pkg: PackageJson): boolean { return pkg.json.jsii; } +/** + * Indicates that this is an "AWS" package (i.e. that it it has a cloudformation source) + * @param pkg + */ +function isAWS(pkg: PackageJson): boolean { + return pkg.json['cdk-build'] && pkg.json['cdk-build'].cloudformation; +} + /** * Determine whether the package has tests * diff --git a/tools/pkglint/package.json b/tools/pkglint/package.json index 215b4e71faa3c..e76b7e733235b 100644 --- a/tools/pkglint/package.json +++ b/tools/pkglint/package.json @@ -1,6 +1,6 @@ { "name": "pkglint", - "version": "0.21.0", + "version": "0.22.0", "private": true, "description": "Validate and fix package.json files", "main": "lib/index.js", diff --git a/tools/pkgtools/package.json b/tools/pkgtools/package.json index 643ecdb9956b3..cb58e9147593a 100644 --- a/tools/pkgtools/package.json +++ b/tools/pkgtools/package.json @@ -1,7 +1,7 @@ { "name": "pkgtools", "private": true, - "version": "0.21.0", + "version": "0.22.0", "description": "Tools for generating cross-package artifacts", "main": "index.js", "repository": { @@ -28,8 +28,8 @@ "devDependencies": { "@types/fs-extra": "^5.0.4", "@types/yargs": "^8.0.3", - "cdk-build-tools": "^0.21.0", - "pkglint": "^0.21.0" + "cdk-build-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "dependencies": { "fs-extra": "^7.0.0", diff --git a/tools/y-npm/package.json b/tools/y-npm/package.json index 555dcd87dcaa2..29d4e517ae5d7 100644 --- a/tools/y-npm/package.json +++ b/tools/y-npm/package.json @@ -1,6 +1,6 @@ { "name": "y-npm", - "version": "0.21.0", + "version": "0.22.0", "description": "Run npm commands using a local registry overlay", "private": true, "author": { @@ -35,8 +35,8 @@ "@types/colors": "^1.2.1", "@types/fs-extra": "^5.0.4", "@types/semver": "^5.5.0", - "cdk-build-tools": "^0.21.0", - "pkglint": "^0.21.0" + "cdk-build-tools": "^0.22.0", + "pkglint": "^0.22.0" }, "keywords": [ "aws",