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 1c01c1761c691..6b36c01db3f8a 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 @@ -105,7 +105,7 @@ export class PipelineDeployStackAction extends cdk.Construct { constructor(scope: cdk.Construct, id: string, props: PipelineDeployStackActionProps) { super(scope, id); - if (!cdk.environmentEquals(props.stack.env, this.node.stack.env)) { + if (!cdk.environmentEquals(props.stack.env, cdk.Stack.of(this).env)) { // FIXME: Add the necessary to extend to stacks in a different account throw new Error(`Cross-environment deployment is not supported`); } 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 42b313a0da85d..3dfaaa12e6391 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 @@ -6,6 +6,7 @@ import cpactions = require('@aws-cdk/aws-codepipeline-actions'); import iam = require('@aws-cdk/aws-iam'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); +import { ConstructNode } from '@aws-cdk/cdk'; import cxapi = require('@aws-cdk/cx-api'); import fc = require('fast-check'); import nodeunit = require('nodeunit'); @@ -289,7 +290,7 @@ export = nodeunit.testCase({ for (let i = 0 ; i < assetCount ; i++) { deployedStack.node.addMetadata(cxapi.ASSET_METADATA, {}); } - test.deepEqual(action.node.validateTree().map(x => x.message), + test.deepEqual(ConstructNode.validate(action.node).map(x => x.message), [`Cannot deploy the stack DeployedStack because it references ${assetCount} asset(s)`]); } ) diff --git a/packages/@aws-cdk/assets/lib/asset.ts b/packages/@aws-cdk/assets/lib/asset.ts index 8653bbe04de39..24d2d012012c1 100644 --- a/packages/@aws-cdk/assets/lib/asset.ts +++ b/packages/@aws-cdk/assets/lib/asset.ts @@ -198,7 +198,7 @@ export class Asset extends cdk.Construct implements IAsset { * (e.g. "Code" for AWS::Lambda::Function) */ public addResourceMetadata(resource: cdk.CfnResource, resourceProperty: string) { - if (!this.node.getContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT)) { + if (!this.node.tryGetContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT)) { return; // not enabled } diff --git a/packages/@aws-cdk/assets/lib/staging.ts b/packages/@aws-cdk/assets/lib/staging.ts index 030ed3f6195a9..59340eac4edbd 100644 --- a/packages/@aws-cdk/assets/lib/staging.ts +++ b/packages/@aws-cdk/assets/lib/staging.ts @@ -57,7 +57,7 @@ export class Staging extends Construct { this.copyOptions = props; this.sourceHash = fingerprint(this.sourcePath, props); - const stagingDisabled = this.node.getContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT); + const stagingDisabled = this.node.tryGetContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT); if (stagingDisabled) { this.stagedPath = this.sourcePath; } else { diff --git a/packages/@aws-cdk/assets/test/test.asset.ts b/packages/@aws-cdk/assets/test/test.asset.ts index 2712e7e4b4421..5814620eb7c30 100644 --- a/packages/@aws-cdk/assets/test/test.asset.ts +++ b/packages/@aws-cdk/assets/test/test.asset.ts @@ -31,7 +31,7 @@ export = { // verify that now the template contains parameters for this asset const session = app.synth(); - test.deepEqual(stack.node.resolve(entry!.data), { + test.deepEqual(stack.resolve(entry!.data), { path: SAMPLE_ASSET_DIR, id: 'MyStackMyAssetBDDF29E3', packaging: 'zip', @@ -84,7 +84,7 @@ export = { // synthesize first so "prepare" is called const template = SynthUtils.synthesize(stack).template; - test.deepEqual(stack.node.resolve(entry!.data), { + test.deepEqual(stack.resolve(entry!.data), { path: 'asset.78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197.txt', packaging: 'file', id: 'MyAsset', diff --git a/packages/@aws-cdk/aws-apigateway/lib/deployment.ts b/packages/@aws-cdk/aws-apigateway/lib/deployment.ts index 6cafaeca0d833..3c2a0861fea16 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/deployment.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/deployment.ts @@ -1,4 +1,4 @@ -import { Construct, DeletionPolicy, Resource, Token } from '@aws-cdk/cdk'; +import { Construct, DeletionPolicy, Resource, Stack, Token } from '@aws-cdk/cdk'; import crypto = require('crypto'); import { CfnDeployment, CfnDeploymentProps } from './apigateway.generated'; import { IRestApi } from './restapi'; @@ -99,7 +99,7 @@ class LatestDeploymentResource extends CfnDeployment { constructor(scope: Construct, id: string, props: CfnDeploymentProps) { super(scope, id, props); - this.originalLogicalId = this.node.stack.logicalIds.getLogicalId(this); + this.originalLogicalId = Stack.of(this).logicalIds.getLogicalId(this); } /** @@ -121,12 +121,14 @@ class LatestDeploymentResource extends CfnDeployment { * add via `addToLogicalId`. */ protected prepare() { + const stack = Stack.of(this); + // 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) { const md5 = crypto.createHash('md5'); this.hashComponents - .map(c => this.node.resolve(c)) + .map(c => stack.resolve(c)) .forEach(c => md5.update(JSON.stringify(c))); this.overrideLogicalId(this.originalLogicalId + md5.digest("hex")); diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts index 8ccb4d11cc654..2d42cf08f6b50 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts @@ -1,4 +1,5 @@ import cdk = require('@aws-cdk/cdk'); +import { Stack } from '@aws-cdk/cdk'; import { Integration, IntegrationOptions, IntegrationType } from '../integration'; import { Method } from '../method'; import { parseAwsApiCall } from '../util'; @@ -80,7 +81,7 @@ export class AwsIntegration extends Integration { integrationHttpMethod: props.integrationHttpMethod || 'POST', uri: new cdk.Token(() => { if (!this.scope) { throw new Error('AwsIntegration must be used in API'); } - return this.scope.node.stack.formatArn({ + return Stack.of(this.scope).formatArn({ service: 'apigateway', account: backend, resource: apiType, diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index bd8a1a41c011f..8da76ef3bf597 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -1,4 +1,4 @@ -import { Construct, Resource } from '@aws-cdk/cdk'; +import { Construct, Resource, Stack } from '@aws-cdk/cdk'; import { CfnMethod, CfnMethodProps } from './apigateway.generated'; import { ConnectionType, Integration } from './integration'; import { MockIntegration } from './integrations/mock'; @@ -197,7 +197,7 @@ export class Method extends Resource { } else if (options.credentialsPassthrough) { // arn:aws:iam::*:user/* // tslint:disable-next-line:max-line-length - credentials = this.node.stack.formatArn({ service: 'iam', region: '', account: '*', resource: 'user', sep: '/', resourceName: '*' }); + credentials = Stack.of(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 28c0483f81a06..665d169f1e8ff 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -1,5 +1,5 @@ import iam = require('@aws-cdk/aws-iam'); -import { CfnOutput, Construct, IResource as IResourceBase, Resource } from '@aws-cdk/cdk'; +import { CfnOutput, Construct, IResource as IResourceBase, Resource, Stack } from '@aws-cdk/cdk'; import { ApiKey, IApiKey } from './api-key'; import { CfnAccount, CfnRestApi } from './apigateway.generated'; import { Deployment } from './deployment'; @@ -284,7 +284,7 @@ export class RestApi extends Resource implements IRestApi { method = '*'; } - return this.node.stack.formatArn({ + return Stack.of(this).formatArn({ service: 'execute-api', resource: this.restApiId, sep: '/', @@ -343,7 +343,7 @@ export class RestApi extends Resource implements IRestApi { private configureCloudWatchRole(apiResource: CfnRestApi) { const role = new iam.Role(this, 'CloudWatchRole', { assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), - managedPolicyArns: [ this.node.stack.formatArn({ + managedPolicyArns: [ Stack.of(this).formatArn({ service: 'iam', region: '', account: 'aws', diff --git a/packages/@aws-cdk/aws-apigateway/lib/stage.ts b/packages/@aws-cdk/aws-apigateway/lib/stage.ts index 2fe3e24e33b43..f4c9ba393d683 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/stage.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/stage.ts @@ -1,4 +1,4 @@ -import { Construct, Resource } from '@aws-cdk/cdk'; +import { Construct, Resource, Stack } from '@aws-cdk/cdk'; import { CfnStage } from './apigateway.generated'; import { Deployment } from './deployment'; import { IRestApi } from './restapi'; @@ -209,7 +209,7 @@ export class Stage extends Resource { if (!path.startsWith('/')) { throw new Error(`Path must begin with "/": ${path}`); } - return `https://${this.restApi.restApiId}.execute-api.${this.node.stack.region}.${this.node.stack.urlSuffix}/${this.stageName}${path}`; + return `https://${this.restApi.restApiId}.execute-api.${Stack.of(this).region}.${Stack.of(this).urlSuffix}/${this.stageName}${path}`; } private renderMethodSettings(props: StageProps): CfnStage.MethodSettingProperty[] | undefined { diff --git a/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts b/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts index 3f138b47c2369..886da94fd1ff6 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts @@ -152,7 +152,6 @@ export = { test.done(); function synthesize() { - stack.node.prepareTree(); return SynthUtils.synthesize(stack).template; } }, diff --git a/packages/@aws-cdk/aws-apigateway/test/test.method.ts b/packages/@aws-cdk/aws-apigateway/test/test.method.ts index 70b1125b02bd4..8897601bcdbe0 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.method.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.method.ts @@ -148,7 +148,7 @@ export = { }); // THEN - test.deepEqual(method.node.resolve(method.methodArn), { + test.deepEqual(stack.resolve(method.methodArn), { "Fn::Join": [ "", [ @@ -182,7 +182,7 @@ export = { }); // THEN - test.deepEqual(method.node.resolve(method.testMethodArn), { + test.deepEqual(stack.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 ef30cbbdc93c0..7b3b7f462cacc 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts @@ -336,7 +336,7 @@ export = { const imported = apigateway.RestApi.fromRestApiId(stack, 'imported-api', 'api-rxt4498f'); // THEN - test.deepEqual(imported.node.resolve(imported.restApiId), 'api-rxt4498f'); + test.deepEqual(stack.resolve(imported.restApiId), 'api-rxt4498f'); test.done(); }, @@ -347,7 +347,7 @@ export = { api.root.addMethod('GET'); // THEN - test.deepEqual(api.node.resolve(api.url), { 'Fn::Join': + test.deepEqual(stack.resolve(api.url), { 'Fn::Join': [ '', [ 'https://', { Ref: 'apiC8550315' }, @@ -358,7 +358,7 @@ export = { "/", { Ref: 'apiDeploymentStageprod896C8101' }, '/' ] ] }); - test.deepEqual(api.node.resolve(api.urlForPath('/foo/bar')), { 'Fn::Join': + test.deepEqual(stack.resolve(api.urlForPath('/foo/bar')), { 'Fn::Join': [ '', [ 'https://', { Ref: 'apiC8550315' }, @@ -405,7 +405,7 @@ export = { const arn = api.executeApiArn('method', '/path', 'stage'); // THEN - test.deepEqual(api.node.resolve(arn), { 'Fn::Join': + test.deepEqual(stack.resolve(arn), { 'Fn::Join': [ '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -438,7 +438,7 @@ export = { const method = api.root.addMethod('ANY'); // THEN - test.deepEqual(api.node.resolve(method.methodArn), { 'Fn::Join': + test.deepEqual(stack.resolve(method.methodArn), { 'Fn::Join': [ '', [ 'arn:', { Ref: 'AWS::Partition' }, 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 a112f9efe78d8..671ed8d8a6208 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -363,7 +363,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements }); this.connections = new ec2.Connections({ securityGroups: [this.securityGroup] }); this.securityGroups.push(this.securityGroup); - this.node.apply(new Tag(NAME_TAG, this.node.path)); + this.node.applyAspect(new Tag(NAME_TAG, this.node.path)); this.role = props.role || new iam.Role(this, 'InstanceRole', { assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com') diff --git a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts index 65981a3df8880..72fe66b8dc838 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts @@ -382,8 +382,8 @@ export = { pauseTimeSec: 345 }, }); - asg.node.apply(new cdk.Tag('superfood', 'acai')); - asg.node.apply(new cdk.Tag('notsuper', 'caramel', { applyToLaunchedInstances: false })); + asg.node.applyAspect(new cdk.Tag('superfood', 'acai')); + asg.node.applyAspect(new cdk.Tag('notsuper', 'caramel', { applyToLaunchedInstances: false })); // THEN expect(stack).to(haveResource("AWS::AutoScaling::AutoScalingGroup", { diff --git a/packages/@aws-cdk/aws-cloudtrail/lib/index.ts b/packages/@aws-cdk/aws-cloudtrail/lib/index.ts index 6c8001a8712fa..61349309500e0 100644 --- a/packages/@aws-cdk/aws-cloudtrail/lib/index.ts +++ b/packages/@aws-cdk/aws-cloudtrail/lib/index.ts @@ -3,7 +3,7 @@ import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); import logs = require('@aws-cdk/aws-logs'); import s3 = require('@aws-cdk/aws-s3'); -import { Construct, Resource } from '@aws-cdk/cdk'; +import { Construct, Resource, Stack } from '@aws-cdk/cdk'; import { CfnTrail } from './cloudtrail.generated'; // AWS::CloudTrail CloudFormation Resources: @@ -136,7 +136,7 @@ export class Trail extends Resource { .addServicePrincipal(cloudTrailPrincipal)); s3bucket.addToResourcePolicy(new iam.PolicyStatement() - .addResource(s3bucket.arnForObjects(`AWSLogs/${this.node.stack.accountId}/*`)) + .addResource(s3bucket.arnForObjects(`AWSLogs/${Stack.of(this).accountId}/*`)) .addActions("s3:PutObject") .addServicePrincipal(cloudTrailPrincipal) .setCondition("StringEquals", {'s3:x-amz-acl': "bucket-owner-full-control"})); diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts index c8a98091da763..7d762ca10c3dc 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts @@ -1,4 +1,4 @@ -import { Construct, IResource, Resource, Token } from '@aws-cdk/cdk'; +import { Construct, IResource, Resource, Stack, Token } from '@aws-cdk/cdk'; import { IAlarmAction } from './alarm-action'; import { CfnAlarm } from './cloudwatch.generated'; import { HorizontalAnnotation } from './graph'; @@ -80,7 +80,7 @@ export class Alarm extends Resource implements IAlarm { public static fromAlarmArn(scope: Construct, id: string, alarmArn: string): IAlarm { class Import extends Resource implements IAlarm { public readonly alarmArn = alarmArn; - public readonly alarmName = scope.node.stack.parseArn(alarmArn, ':').resourceName!; + public readonly alarmName = Stack.of(scope).parseArn(alarmArn, ':').resourceName!; } return new Import(scope, id); } diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts index 300cd69d49729..3091806aea51c 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts @@ -1,4 +1,4 @@ -import { Construct, Resource, Token } from "@aws-cdk/cdk"; +import { Construct, Resource, Stack, Token } from "@aws-cdk/cdk"; import { CfnDashboard } from './cloudwatch.generated'; import { Column, Row } from "./layout"; import { IWidget } from "./widget"; @@ -61,7 +61,7 @@ export class Dashboard extends Resource { dashboardBody: new Token(() => { const column = new Column(...this.rows); column.position(0, 0); - return this.node.stringifyJson({ + return Stack.of(this).toJsonString({ start: props ? props.start : undefined, end: props ? props.end : undefined, periodOverride: props ? props.periodOverride : undefined, diff --git a/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts b/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts index 45534b3bc06a6..72641b3e4f6f9 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts @@ -17,7 +17,7 @@ export = { }); // THEN - test.deepEqual(stack.node.resolve(widget.toJson()), [{ + test.deepEqual(stack.resolve(widget.toJson()), [{ type: 'metric', width: 6, height: 6, @@ -45,7 +45,7 @@ export = { }); // THEN - test.deepEqual(stack.node.resolve(widget.toJson()), [{ + test.deepEqual(stack.resolve(widget.toJson()), [{ type: 'metric', width: 6, height: 6, @@ -74,7 +74,7 @@ export = { }); // THEN - test.deepEqual(stack.node.resolve(widget.toJson()), [{ + test.deepEqual(stack.resolve(widget.toJson()), [{ type: 'metric', width: 6, height: 3, @@ -105,7 +105,7 @@ export = { }); // THEN - test.deepEqual(stack.node.resolve(widget.toJson()), [{ + test.deepEqual(stack.resolve(widget.toJson()), [{ type: 'metric', width: 6, height: 6, @@ -139,7 +139,7 @@ export = { }); // THEN - test.deepEqual(stack.node.resolve(widget.toJson()), [{ + test.deepEqual(stack.resolve(widget.toJson()), [{ type: 'metric', width: 6, height: 6, @@ -182,7 +182,7 @@ export = { }); // THEN - test.deepEqual(stack.node.resolve(widget.toJson()), [{ + test.deepEqual(stack.resolve(widget.toJson()), [{ type: 'metric', width: 6, height: 6, @@ -229,7 +229,7 @@ export = { }); // THEN - test.deepEqual(stack.node.resolve(widget.toJson()), [{ + test.deepEqual(stack.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 7355ce328becc..d8c35077938c0 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -6,7 +6,7 @@ import ecr = require('@aws-cdk/aws-ecr'); import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); -import { Aws, Construct, IResource, Resource, Token } from '@aws-cdk/cdk'; +import { Aws, Construct, IResource, Resource, Stack, Token } from '@aws-cdk/cdk'; import { BuildArtifacts, CodePipelineBuildArtifacts, NoBuildArtifacts } from './artifacts'; import { Cache } from './cache'; import { CfnProject } from './codebuild.generated'; @@ -541,7 +541,7 @@ export class Project extends ProjectBase { class Import extends ProjectBase { public readonly grantPrincipal: iam.IPrincipal; public readonly projectArn = projectArn; - public readonly projectName = scope.node.stack.parseArn(projectArn).resourceName!; + public readonly projectName = Stack.of(scope).parseArn(projectArn).resourceName!; public readonly role?: iam.Role = undefined; constructor(s: Construct, i: string) { @@ -578,7 +578,7 @@ export class Project extends ProjectBase { constructor(s: Construct, i: string) { super(s, i); - this.projectArn = this.node.stack.formatArn({ + this.projectArn = Stack.of(this).formatArn({ service: 'codebuild', resource: 'project', resourceName: projectName, @@ -793,7 +793,7 @@ export class Project extends ProjectBase { } private createLoggingPermission() { - const logGroupArn = this.node.stack.formatArn({ + const logGroupArn = Stack.of(this).formatArn({ service: 'logs', resource: 'log-group', sep: ':', diff --git a/packages/@aws-cdk/aws-codecommit/lib/repository.ts b/packages/@aws-cdk/aws-codecommit/lib/repository.ts index 03e2372b5e7ba..1e3d2b7c7ada7 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/repository.ts +++ b/packages/@aws-cdk/aws-codecommit/lib/repository.ts @@ -233,7 +233,7 @@ export class Repository extends RepositoryBase { * @param repositoryArn (e.g. `arn:aws:codecommit:us-east-1:123456789012:MyDemoRepo`) */ public static fromRepositoryArn(scope: Construct, id: string, repositoryArn: string): IRepository { - const stack = scope.node.stack; + const stack = Stack.of(scope); const repositoryName = stack.parseArn(repositoryArn).resource; class Import extends RepositoryBase { @@ -247,7 +247,7 @@ export class Repository extends RepositoryBase { } public static fromRepositoryName(scope: Construct, id: string, repositoryName: string): IRepository { - const stack = scope.node.stack; + const stack = Stack.of(scope); class Import extends RepositoryBase { public repositoryName = repositoryName; @@ -264,7 +264,7 @@ export class Repository extends RepositoryBase { } private static arnForLocalRepository(repositoryName: string, scope: IConstruct): string { - return scope.node.stack.formatArn({ + return Stack.of(scope).formatArn({ service: 'codecommit', resource: repositoryName, }); diff --git a/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts b/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts index 711043a8e2ab5..abd3fa373c48d 100644 --- a/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts +++ b/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts @@ -59,8 +59,8 @@ export = { const repo = Repository.fromRepositoryArn(stack, 'ImportedRepo', repositoryArn); // THEN - test.deepEqual(repo.node.resolve(repo.repositoryArn), repositoryArn); - test.deepEqual(repo.node.resolve(repo.repositoryName), 'my-repo'); + test.deepEqual(stack.resolve(repo.repositoryArn), repositoryArn); + test.deepEqual(stack.resolve(repo.repositoryName), 'my-repo'); test.done(); }, @@ -73,7 +73,7 @@ export = { const repo = Repository.fromRepositoryName(stack, 'ImportedRepo', 'my-repo'); // THEN - test.deepEqual(repo.node.resolve(repo.repositoryArn), { + test.deepEqual(stack.resolve(repo.repositoryArn), { 'Fn::Join': ['', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -84,7 +84,7 @@ export = { ':my-repo' ]], }); - test.deepEqual(repo.node.resolve(repo.repositoryName), 'my-repo'); + test.deepEqual(stack.resolve(repo.repositoryName), 'my-repo'); test.done(); }, diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts index 7d426fa27dbe1..93c61be2cf0ab 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts @@ -4,6 +4,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import iam = require('@aws-cdk/aws-iam'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); +import { Stack } from '@aws-cdk/cdk'; import { CfnDeploymentGroup } from '../codedeploy.generated'; import { AutoRollbackConfig } from '../rollback-config'; import { arnForDeploymentGroup, renderAlarmConfiguration, renderAutoRollbackConfiguration } from '../utils'; @@ -276,7 +277,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { this._autoScalingGroups = props.autoScalingGroups || []; this.installAgent = props.installAgent === undefined ? true : props.installAgent; - this.codeDeployBucket = s3.Bucket.fromBucketName(this, 'Bucket', `aws-codedeploy-${this.node.stack.region}`); + this.codeDeployBucket = s3.Bucket.fromBucketName(this, 'Bucket', `aws-codedeploy-${Stack.of(this).region}`); for (const asg of this._autoScalingGroups) { this.addCodeDeployAgentInstallUserData(asg); } @@ -358,7 +359,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { '$PKG_CMD install -y awscli', 'TMP_DIR=`mktemp -d`', 'cd $TMP_DIR', - `aws s3 cp s3://aws-codedeploy-${this.node.stack.region}/latest/install . --region ${this.node.stack.region}`, + `aws s3 cp s3://aws-codedeploy-${Stack.of(this).region}/latest/install . --region ${Stack.of(this).region}`, 'chmod +x ./install', './install auto', 'rm -fr $TMP_DIR', @@ -367,7 +368,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-${this.node.stack.region}/latest/codedeploy-agent.msi $TEMPDIR\\codedeploy-agent.msi`, + `aws s3 cp s3://aws-codedeploy-${Stack.of(this).region}/latest/codedeploy-agent.msi $TEMPDIR\\codedeploy-agent.msi`, '$TEMPDIR\\codedeploy-agent.msi /quiet /l c:\\temp\\host-agent-install-log.txt', ); break; diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts index ac712fa8ccd18..58a3f0f4c4443 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts @@ -2,6 +2,7 @@ import cloudformation = require('@aws-cdk/aws-cloudformation'); import codepipeline = require('@aws-cdk/aws-codepipeline'); import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); +import { Stack } from '@aws-cdk/cdk'; /** * Properties common to all CloudFormation actions @@ -227,7 +228,7 @@ export abstract class CloudFormationDeployAction extends CloudFormationAction { // None evaluates to empty string which is falsey and results in undefined Capabilities: (capabilities && capabilities.toString()) || undefined, RoleArn: new cdk.Token(() => this.deploymentRole.roleArn), - ParameterOverrides: new cdk.Token(() => this.scope.node.stringifyJson(props.parameterOverrides)), + ParameterOverrides: new cdk.Token(() => Stack.of(this.scope).toJsonString(props.parameterOverrides)), TemplateConfiguration: props.templateConfiguration ? props.templateConfiguration.location : undefined, StackName: props.stackName, }); @@ -524,7 +525,7 @@ class SingletonPolicy extends cdk.Construct { } private stackArnFromProps(props: { stackName: string, region?: string }): string { - return this.node.stack.formatArn({ + return Stack.of(this).formatArn({ region: props.region, service: 'cloudformation', resource: 'stack', diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts index 2063ca58c8d32..ca982fcb7c386 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts @@ -1,6 +1,7 @@ import codepipeline = require('@aws-cdk/aws-codepipeline'); import iam = require('@aws-cdk/aws-iam'); import lambda = require('@aws-cdk/aws-lambda'); +import { Stack } from '@aws-cdk/cdk'; /** * Construction properties of the {@link LambdaInvokeAction Lambda invoke CodePipeline Action}. @@ -68,7 +69,7 @@ export class LambdaInvokeAction extends codepipeline.Action { }, configuration: { FunctionName: props.lambda.functionName, - UserParameters: props.lambda.node.stringifyJson(props.userParameters), + UserParameters: Stack.of(props.lambda).toJsonString(props.userParameters), }, }); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts index 649a5d7635773..6fc336e6af37f 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts @@ -2,6 +2,7 @@ import codepipeline = require('@aws-cdk/aws-codepipeline'); import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); +import { Stack } from '@aws-cdk/cdk'; import _ = require('lodash'); import nodeunit = require('nodeunit'); import cpactions = require('../../lib'); @@ -71,7 +72,7 @@ export = nodeunit.testCase({ }); test.deepEqual( - stack.node.resolve(pipelineRole.statements), + stack.resolve(pipelineRole.statements), [ { Action: 'iam:PassRole', @@ -152,7 +153,7 @@ export = nodeunit.testCase({ }); test.deepEqual( - stack.node.resolve(pipelineRole.statements), + stack.resolve(pipelineRole.statements), [ { Action: 'cloudformation:ExecuteChangeSet', @@ -294,7 +295,7 @@ function _isOrContains(entity: string | string[], value: string): boolean { } function _stackArn(stackName: string, scope: cdk.IConstruct): string { - return scope.node.stack.formatArn({ + return Stack.of(scope).formatArn({ service: 'cloudformation', resource: 'stack', resourceName: `${stackName}/*`, @@ -309,7 +310,7 @@ class PipelineDouble extends cdk.Construct implements codepipeline.IPipeline { constructor(scope: cdk.Construct, id: string, { pipelineName, role }: { pipelineName?: string, role: iam.Role }) { super(scope, id); this.pipelineName = pipelineName || 'TestPipeline'; - this.pipelineArn = this.node.stack.formatArn({ service: 'codepipeline', resource: 'pipeline', resourceName: this.pipelineName }); + this.pipelineArn = Stack.of(this).formatArn({ service: 'codepipeline', resource: 'pipeline', resourceName: this.pipelineName }); this.role = role; } @@ -384,5 +385,5 @@ class RoleDouble extends iam.Role { } function resolve(x: any): any { - return new cdk.Stack().node.resolve(x); + return new cdk.Stack().resolve(x); } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts index 442dd9c082adc..0e67c60edb447 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts @@ -6,7 +6,7 @@ import targets = require('@aws-cdk/aws-events-targets'); import lambda = require('@aws-cdk/aws-lambda'); import s3 = require('@aws-cdk/aws-s3'); import sns = require('@aws-cdk/aws-sns'); -import { App, CfnParameter, SecretValue, Stack } from '@aws-cdk/cdk'; +import { App, CfnParameter, ConstructNode, SecretValue, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import cpactions = require('../lib'); @@ -47,7 +47,7 @@ export = { }); test.notDeepEqual(SynthUtils.toCloudFormation(stack), {}); - test.deepEqual([], pipeline.node.validateTree()); + test.deepEqual([], ConstructNode.validate(pipeline.node)); test.done(); }, @@ -280,7 +280,7 @@ export = { ] })); - test.deepEqual([], p.node.validateTree()); + test.deepEqual([], ConstructNode.validate(p.node)); test.done(); }, @@ -371,7 +371,7 @@ export = { ] })); - test.deepEqual([], pipeline.node.validateTree()); + test.deepEqual([], ConstructNode.validate(pipeline.node)); test.done(); }, diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index f5f5d6e59fb88..24c8d963035c9 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -170,7 +170,7 @@ export class Pipeline extends PipelineBase { public static fromPipelineArn(scope: Construct, id: string, pipelineArn: string): IPipeline { class Import extends PipelineBase { - public pipelineName = scope.node.stack.parseArn(pipelineArn).resource; + public pipelineName = Stack.of(scope).parseArn(pipelineArn).resource; public pipelineArn = pipelineArn; public grantBucketRead(identity: iam.IGrantable): iam.Grant { @@ -261,7 +261,7 @@ export class Pipeline extends PipelineBase { this.artifactStores = {}; // Does not expose a Fn::GetAtt for the ARN so we'll have to make it ourselves - this.pipelineArn = this.node.stack.formatArn({ + this.pipelineArn = Stack.of(this).formatArn({ service: 'codepipeline', resource: this.pipelineName }); @@ -362,7 +362,7 @@ export class Pipeline extends PipelineBase { private ensureReplicationBucketExistsFor(region: string) { // get the region the Pipeline itself is in - const pipelineRegion = this.node.stack.requireRegion( + const pipelineRegion = Stack.of(this).requireRegion( "You need to specify an explicit region when using CodePipeline's cross-region support"); // if we already have an ArtifactStore generated for this region, or it's the Pipeline's region, nothing to do @@ -372,9 +372,9 @@ export class Pipeline extends PipelineBase { let replicationBucketName = this.crossRegionReplicationBuckets[region]; if (!replicationBucketName) { - const pipelineAccount = this.node.stack.requireAccountId( + const pipelineAccount = Stack.of(this).requireAccountId( "You need to specify an explicit account when using CodePipeline's cross-region support"); - const app = this.node.stack.parentApp(); + const app = Stack.of(this).parentApp(); if (!app) { throw new Error(`Pipeline stack which uses cross region actions must be part of an application`); } @@ -499,7 +499,7 @@ export class Pipeline extends PipelineBase { // add the Pipeline's artifact store const primaryStore = this.renderPrimaryArtifactStore(); - this.artifactStores[this.node.stack.requireRegion()] = { + this.artifactStores[Stack.of(this).requireRegion()] = { location: primaryStore.location, type: primaryStore.type, encryptionKey: primaryStore.encryptionKey, diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.artifacts.ts b/packages/@aws-cdk/aws-codepipeline/test/test.artifacts.ts index bb7e144aa5c4e..efb78a348b2f8 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.artifacts.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.artifacts.ts @@ -1,5 +1,6 @@ import { expect, haveResourceLike } from '@aws-cdk/assert'; import cdk = require('@aws-cdk/cdk'); +import { ConstructNode } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import codepipeline = require('../lib'); import { FakeBuildAction } from './fake-build-action'; @@ -176,6 +177,6 @@ export = { }; function validate(construct: cdk.IConstruct): cdk.ValidationError[] { - construct.node.prepareTree(); - return construct.node.validateTree(); + ConstructNode.prepare(construct.node); + return ConstructNode.validate(construct.node); } 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 901177465596b..fe01b6a432a49 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts @@ -1,4 +1,5 @@ import cdk = require('@aws-cdk/cdk'); +import { ConstructNode } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { IStage } from '../lib/action'; import { Artifact } from '../lib/artifact'; @@ -49,7 +50,7 @@ export = { const stack = new cdk.Stack(); const pipeline = new Pipeline(stack, 'Pipeline'); - test.deepEqual(pipeline.node.validateTree().length, 1); + test.deepEqual(ConstructNode.validate(pipeline.node).length, 1); test.done(); }, @@ -68,7 +69,7 @@ export = { ], }); - test.deepEqual(pipeline.node.validateTree().length, 1); + test.deepEqual(ConstructNode.validate(pipeline.node).length, 1); test.done(); } diff --git a/packages/@aws-cdk/aws-cognito/test/test.user-pool-client.ts b/packages/@aws-cdk/aws-cognito/test/test.user-pool-client.ts index fe7c6e8008aa3..6f7fbb5c809ce 100644 --- a/packages/@aws-cdk/aws-cognito/test/test.user-pool-client.ts +++ b/packages/@aws-cdk/aws-cognito/test/test.user-pool-client.ts @@ -16,7 +16,7 @@ export = { // THEN expect(stack).to(haveResourceLike('AWS::Cognito::UserPoolClient', { - UserPoolId: pool.node.resolve(pool.userPoolId) + UserPoolId: stack.resolve(pool.userPoolId) })); test.done(); diff --git a/packages/@aws-cdk/aws-cognito/test/test.user-pool.ts b/packages/@aws-cdk/aws-cognito/test/test.user-pool.ts index fb8c330b7e1a8..598de74f06467 100644 --- a/packages/@aws-cdk/aws-cognito/test/test.user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/test/test.user-pool.ts @@ -42,8 +42,8 @@ export = { // THEN expect(stack).to(haveResourceLike('AWS::Cognito::UserPool', { LambdaConfig: { - PreSignUp: fn.node.resolve(fn.functionArn), - CustomMessage: fn.node.resolve(fn.functionArn) + PreSignUp: stack.resolve(fn.functionArn), + CustomMessage: stack.resolve(fn.functionArn) } })); @@ -73,14 +73,14 @@ export = { // THEN expect(stack).to(haveResourceLike('AWS::Cognito::UserPool', { LambdaConfig: { - CreateAuthChallenge: fn.node.resolve(fn.functionArn), - CustomMessage: fn.node.resolve(fn.functionArn), - DefineAuthChallenge: fn.node.resolve(fn.functionArn), - PostAuthentication: fn.node.resolve(fn.functionArn), - PostConfirmation: fn.node.resolve(fn.functionArn), - PreAuthentication: fn.node.resolve(fn.functionArn), - PreSignUp: fn.node.resolve(fn.functionArn), - VerifyAuthChallengeResponse: fn.node.resolve(fn.functionArn) + CreateAuthChallenge: stack.resolve(fn.functionArn), + CustomMessage: stack.resolve(fn.functionArn), + DefineAuthChallenge: stack.resolve(fn.functionArn), + PostAuthentication: stack.resolve(fn.functionArn), + PostConfirmation: stack.resolve(fn.functionArn), + PreAuthentication: stack.resolve(fn.functionArn), + PreSignUp: stack.resolve(fn.functionArn), + VerifyAuthChallengeResponse: stack.resolve(fn.functionArn) } })); @@ -105,7 +105,7 @@ export = { // THEN expect(stack).to(haveResourceLike('AWS::Lambda::Permission', { - FunctionName: fn.node.resolve(fn.functionArn), + FunctionName: stack.resolve(fn.functionArn), Principal: 'cognito-idp.amazonaws.com' })); diff --git a/packages/@aws-cdk/aws-config/lib/managed-rules.ts b/packages/@aws-cdk/aws-config/lib/managed-rules.ts index 8a203c5f37afa..5c42afced58f1 100644 --- a/packages/@aws-cdk/aws-config/lib/managed-rules.ts +++ b/packages/@aws-cdk/aws-config/lib/managed-rules.ts @@ -1,6 +1,6 @@ import iam = require('@aws-cdk/aws-iam'); import sns = require('@aws-cdk/aws-sns'); -import { Construct, Token } from '@aws-cdk/cdk'; +import { Construct, Stack, Token } from '@aws-cdk/cdk'; import { ManagedRule, RuleProps } from './rule'; /** @@ -81,7 +81,7 @@ export class CloudFormationStackDriftDetectionCheck extends ManagedRule { } }); - this.scopeToResource('AWS::CloudFormation::Stack', props.ownStackOnly ? this.node.stack.stackId : undefined); + this.scopeToResource('AWS::CloudFormation::Stack', props.ownStackOnly ? Stack.of(this).stackId : undefined); this.role = props.role || new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('config.amazonaws.com'), diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index 686f6c14e0ae5..e9cb106f2c61d 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -1,6 +1,6 @@ import appscaling = require('@aws-cdk/aws-applicationautoscaling'); import iam = require('@aws-cdk/aws-iam'); -import { Aws, Construct, Resource, Token } from '@aws-cdk/cdk'; +import { Aws, Construct, Resource, Stack, Token } from '@aws-cdk/cdk'; import { CfnTable } from './dynamodb.generated'; import { EnableScalingProps, IScalableTableAttribute } from './scalable-attribute-api'; import { ScalableTableAttribute } from './scalable-table-attribute'; @@ -635,7 +635,7 @@ export class Table extends Resource { private makeScalingRole(): iam.IRole { // Use a Service Linked Role. // https://docs.aws.amazon.com/autoscaling/application/userguide/application-auto-scaling-service-linked-roles.html - return iam.Role.fromRoleArn(this, 'ScalingRole', this.node.stack.formatArn({ + return iam.Role.fromRoleArn(this, 'ScalingRole', Stack.of(this).formatArn({ service: 'iam', resource: 'role/aws-service-role/dynamodb.application-autoscaling.amazonaws.com', resourceName: 'AWSServiceRoleForApplicationAutoScaling_DynamoDBTable' diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts index 9341d2a7968ce..a2e2aec0f876e 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts @@ -56,7 +56,7 @@ const tableWithGlobalAndLocalSecondaryIndex = new Table(stack, TABLE_WITH_GLOBAL sortKey: TABLE_SORT_KEY }); -tableWithGlobalAndLocalSecondaryIndex.node.apply(new Tag('Environment', 'Production')); +tableWithGlobalAndLocalSecondaryIndex.node.applyAspect(new Tag('Environment', 'Production')); tableWithGlobalAndLocalSecondaryIndex.addGlobalSecondaryIndex({ indexName: GSI_TEST_CASE_1, diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts index 3e6cadf15ea92..646332c4748e6 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts @@ -54,7 +54,7 @@ const tableWithGlobalAndLocalSecondaryIndex = new Table(stack, TABLE_WITH_GLOBAL sortKey: TABLE_SORT_KEY }); -tableWithGlobalAndLocalSecondaryIndex.node.apply(new Tag('Environment', 'Production')); +tableWithGlobalAndLocalSecondaryIndex.node.applyAspect(new Tag('Environment', 'Production')); tableWithGlobalAndLocalSecondaryIndex.addGlobalSecondaryIndex({ indexName: GSI_TEST_CASE_1, partitionKey: GSI_PARTITION_KEY, diff --git a/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts b/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts index 2fc3b186b34f0..b92d3057c17de 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts @@ -1,6 +1,6 @@ import { expect, haveResource } from '@aws-cdk/assert'; import iam = require('@aws-cdk/aws-iam'); -import { Stack, Tag } from '@aws-cdk/cdk'; +import { ConstructNode, Stack, Tag } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { Attribute, @@ -319,7 +319,7 @@ export = { partitionKey: TABLE_PARTITION_KEY, sortKey: TABLE_SORT_KEY, }); - table.node.apply(new Tag('Environment', 'Production')); + table.node.applyAspect(new Tag('Environment', 'Production')); expect(stack).to(haveResource('AWS::DynamoDB::Table', { @@ -967,7 +967,7 @@ export = { sortKey: LSI_SORT_KEY }); - const errors = table.node.validateTree(); + const errors = ConstructNode.validate(table.node); test.strictEqual(1, errors.length); test.strictEqual('a sort key of the table must be specified to add local secondary indexes', errors[0].message); diff --git a/packages/@aws-cdk/aws-ec2/lib/machine-image.ts b/packages/@aws-cdk/aws-ec2/lib/machine-image.ts index ab916b06d4915..e975fcb941245 100644 --- a/packages/@aws-cdk/aws-ec2/lib/machine-image.ts +++ b/packages/@aws-cdk/aws-ec2/lib/machine-image.ts @@ -1,4 +1,4 @@ -import { Construct, SSMParameterProvider } from '@aws-cdk/cdk'; +import { Construct, SSMParameterProvider, Stack } from '@aws-cdk/cdk'; /** * Interface for classes that can select an appropriate machine image to use @@ -188,7 +188,7 @@ export class GenericLinuxImage implements IMachineImageSource { } public getImage(scope: Construct): MachineImage { - const region = scope.node.stack.requireRegion('AMI cannot be determined'); + const region = Stack.of(scope).requireRegion('AMI cannot be determined'); const ami = region !== 'test-region' ? this.amiMap[region] : 'ami-12345'; if (!ami) { throw new Error(`Unable to find AMI in AMI map: no AMI specified for region '${region}'`); diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index d9023fb37b889..994f3130cfe10 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -1,4 +1,4 @@ -import { Construct, IResource, Resource, Token } from '@aws-cdk/cdk'; +import { Construct, IResource, Resource, Stack, Token } from '@aws-cdk/cdk'; import { Connections, IConnectable } from './connections'; import { CfnSecurityGroup, CfnSecurityGroupEgress, CfnSecurityGroupIngress } from './ec2.generated'; import { IPortRange, ISecurityGroupRule } from './security-group-rule'; @@ -178,7 +178,7 @@ function determineRuleScope( } function differentStacks(group1: SecurityGroupBase, group2: SecurityGroupBase) { - return group1.node.stack !== group2.node.stack; + return Stack.of(group1) !== Stack.of(group2); } export interface SecurityGroupProps { diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index f1b6af01bcac8..c94618974d2d6 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -1,5 +1,5 @@ import cdk = require('@aws-cdk/cdk'); -import { ConcreteDependable, Construct, IConstruct, IDependable, IResource, Resource } from '@aws-cdk/cdk'; +import { ConcreteDependable, Construct, IConstruct, IDependable, IResource, Resource, Stack } from '@aws-cdk/cdk'; import { CfnEIP, CfnInternetGateway, CfnNatGateway, CfnRoute, CfnVPNGateway, CfnVPNGatewayRoutePropagation } from './ec2.generated'; import { CfnRouteTable, CfnSubnet, CfnSubnetRouteTableAssociation, CfnVPC, CfnVPCGatewayAttachment } from './ec2.generated'; import { NetworkBuilder } from './network-util'; @@ -301,7 +301,7 @@ abstract class VpcBase extends Resource implements IVpc { * The region where this VPC is defined */ public get region(): string { - return this.node.stack.region; + return Stack.of(this).region; } /** @@ -787,7 +787,7 @@ export class Vpc extends VpcBase { this.vpcDefaultSecurityGroup = this.resource.vpcDefaultSecurityGroup; this.vpcIpv6CidrBlocks = this.resource.vpcIpv6CidrBlocks; - this.node.apply(new cdk.Tag(NAME_TAG, this.node.path)); + this.node.applyAspect(new cdk.Tag(NAME_TAG, this.node.path)); this.availabilityZones = new cdk.AvailabilityZoneProvider(this).availabilityZones; this.availabilityZones.sort(); @@ -1004,8 +1004,8 @@ export class Vpc extends VpcBase { // These values will be used to recover the config upon provider import const includeResourceTypes = [CfnSubnet.resourceTypeName]; - subnet.node.apply(new cdk.Tag(SUBNETNAME_TAG, subnetConfig.name, {includeResourceTypes})); - subnet.node.apply(new cdk.Tag(SUBNETTYPE_TAG, subnetTypeTagValue(subnetConfig.subnetType), {includeResourceTypes})); + subnet.node.applyAspect(new cdk.Tag(SUBNETNAME_TAG, subnetConfig.name, {includeResourceTypes})); + subnet.node.applyAspect(new cdk.Tag(SUBNETTYPE_TAG, subnetTypeTagValue(subnetConfig.subnetType), {includeResourceTypes})); }); } } @@ -1111,7 +1111,7 @@ export class Subnet extends cdk.Resource implements ISubnet { Object.defineProperty(this, VPC_SUBNET_SYMBOL, { value: true }); - this.node.apply(new cdk.Tag(NAME_TAG, this.node.path)); + this.node.applyAspect(new cdk.Tag(NAME_TAG, this.node.path)); this.availabilityZone = props.availabilityZone; const subnet = new CfnSubnet(this, 'Subnet', { diff --git a/packages/@aws-cdk/aws-ec2/test/test.connections.ts b/packages/@aws-cdk/aws-ec2/test/test.connections.ts index f93ac6dea6eb6..91811719f0ed0 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.connections.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.connections.ts @@ -1,5 +1,5 @@ import { expect, haveResource } from '@aws-cdk/assert'; -import { App, Stack } from '@aws-cdk/cdk'; +import { App, ConstructNode, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { @@ -179,7 +179,7 @@ export = { sg2.connections.allowFrom(sg1, new TcpPort(100)); // THEN -- both rules are in Stack2 - app.node.prepareTree(); + ConstructNode.prepare(app.node); expect(stack2).to(haveResource('AWS::EC2::SecurityGroupIngress', { GroupId: { "Fn::GetAtt": [ "SecurityGroupDD263621", "GroupId" ] }, @@ -210,7 +210,7 @@ export = { sg2.connections.allowTo(sg1, new TcpPort(100)); // THEN -- both rules are in Stack2 - app.node.prepareTree(); + ConstructNode.prepare(app.node); expect(stack2).to(haveResource('AWS::EC2::SecurityGroupIngress', { GroupId: { "Fn::ImportValue": "Stack1:ExportsOutputFnGetAttSecurityGroupDD263621GroupIdDF6F8B09" }, @@ -243,7 +243,7 @@ export = { sg2.connections.allowFrom(sg1b, new TcpPort(100)); // THEN -- both egress rules are in Stack2 - app.node.prepareTree(); + ConstructNode.prepare(app.node); expect(stack2).to(haveResource('AWS::EC2::SecurityGroupEgress', { GroupId: { "Fn::ImportValue": "Stack1:ExportsOutputFnGetAttSecurityGroupAED40ADC5GroupId1D10C76A" }, diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index 40532e0167ada..f42e2d3bfb82b 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -11,7 +11,7 @@ export = { "vpc.vpcId returns a token to the VPC ID"(test: Test) { const stack = getTestStack(); const vpc = new Vpc(stack, 'TheVPC'); - test.deepEqual(stack.node.resolve(vpc.vpcId), {Ref: 'TheVPC92636AB0' } ); + test.deepEqual(stack.resolve(vpc.vpcId), {Ref: 'TheVPC92636AB0' } ); test.done(); }, @@ -66,7 +66,7 @@ export = { const zones = new AvailabilityZoneProvider(stack).availabilityZones.length; test.equal(vpc.publicSubnets.length, zones); test.equal(vpc.privateSubnets.length, zones); - test.deepEqual(stack.node.resolve(vpc.vpcId), { Ref: 'TheVPC92636AB0' }); + test.deepEqual(stack.resolve(vpc.vpcId), { Ref: 'TheVPC92636AB0' }); test.done(); }, @@ -565,8 +565,8 @@ export = { const vpc = new Vpc(stack, 'TheVPC'); // overwrite to set propagate - vpc.node.apply(new Tag('BusinessUnit', 'Marketing', {includeResourceTypes: [CfnVPC.resourceTypeName]})); - vpc.node.apply(new Tag('VpcType', 'Good')); + vpc.node.applyAspect(new Tag('BusinessUnit', 'Marketing', {includeResourceTypes: [CfnVPC.resourceTypeName]})); + vpc.node.applyAspect(new Tag('VpcType', 'Good')); expect(stack).to(haveResource("AWS::EC2::VPC", hasTags(toCfnTags(allTags)))); const taggables = ['Subnet', 'InternetGateway', 'NatGateway', 'RouteTable']; const propTags = toCfnTags(tags); @@ -597,7 +597,7 @@ export = { const vpc = new Vpc(stack, 'TheVPC'); const tag = {Key: 'Late', Value: 'Adder'}; expect(stack).notTo(haveResource('AWS::EC2::VPC', hasTags([tag]))); - vpc.node.apply(new Tag(tag.Key, tag.Value)); + vpc.node.applyAspect(new Tag(tag.Key, tag.Value)); expect(stack).to(haveResource('AWS::EC2::VPC', hasTags([tag]))); test.done(); }, @@ -713,7 +713,7 @@ export = { }); // THEN - test.deepEqual(vpc2.node.resolve(vpc2.vpcId), { + test.deepEqual(Stack.of(vpc2).resolve(vpc2.vpcId), { 'Fn::ImportValue': 'TestStack:TheVPCVpcIdD346CDBA' }); @@ -732,7 +732,7 @@ export = { }); // THEN - test.deepEqual(imported.node.resolve(imported.vpcId), { + test.deepEqual(Stack.of(imported).resolve(imported.vpcId), { 'Fn::ImportValue': 'TestStack:TheVPCVpcIdD346CDBA' }); diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpn.ts b/packages/@aws-cdk/aws-ec2/test/test.vpn.ts index 3f2ba05a24ee9..97e6c321ec56d 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpn.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpn.ts @@ -274,7 +274,7 @@ export = { }); // THEN - test.deepEqual(stack.node.resolve(vpn.metricTunnelState()), { + test.deepEqual(stack.resolve(vpn.metricTunnelState()), { dimensions: { VpnId: { Ref: 'VpcNetworkVpnA476C58D' } }, namespace: 'AWS/VPN', metricName: 'TunnelState', @@ -290,7 +290,7 @@ export = { const stack = new Stack(); // THEN - test.deepEqual(stack.node.resolve(VpnConnection.metricAllTunnelDataOut()), { + test.deepEqual(stack.resolve(VpnConnection.metricAllTunnelDataOut()), { namespace: 'AWS/VPN', metricName: 'TunnelDataOut', periodSec: 300, diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index 1c8afe1a47171..7a8ae20363706 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -1,6 +1,6 @@ import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); -import { Construct, DeletionPolicy, IConstruct, IResource, Resource, Token } from '@aws-cdk/cdk'; +import { Construct, DeletionPolicy, IConstruct, IResource, Resource, Stack, Token } from '@aws-cdk/cdk'; import { CfnRepository } from './ecr.generated'; import { CountType, LifecycleRule, TagStatus } from './lifecycle'; @@ -120,7 +120,7 @@ export abstract class RepositoryBase extends Resource implements IRepository { */ public repositoryUriForTag(tag?: string): string { const tagSuffix = tag ? `:${tag}` : ''; - const parts = this.node.stack.parseArn(this.repositoryArn); + const parts = Stack.of(this).parseArn(this.repositoryArn); return `${parts.account}.dkr.ecr.${parts.region}.amazonaws.com/${this.repositoryName}${tagSuffix}`; } @@ -324,7 +324,7 @@ export class Repository extends RepositoryBase { * as the current stack. */ public static arnForLocalRepository(repositoryName: string, scope: IConstruct): string { - return scope.node.stack.formatArn({ + return Stack.of(scope).formatArn({ service: 'ecr', resource: 'repository', resourceName: repositoryName @@ -400,12 +400,13 @@ export class Repository extends RepositoryBase { * Render the life cycle policy object */ private renderLifecyclePolicy(): CfnRepository.LifecyclePolicyProperty | undefined { + const stack = Stack.of(this); let lifecyclePolicyText: any; if (this.lifecycleRules.length === 0 && !this.registryId) { return undefined; } if (this.lifecycleRules.length > 0) { - lifecyclePolicyText = JSON.stringify(this.node.resolve({ + lifecyclePolicyText = JSON.stringify(stack.resolve({ rules: this.orderedLifecycleRules().map(renderLifecycleRule), })); } diff --git a/packages/@aws-cdk/aws-ecr/test/test.repository.ts b/packages/@aws-cdk/aws-ecr/test/test.repository.ts index 76b9865efae30..ebbaf4aa44504 100644 --- a/packages/@aws-cdk/aws-ecr/test/test.repository.ts +++ b/packages/@aws-cdk/aws-ecr/test/test.repository.ts @@ -158,7 +158,7 @@ export = { // THEN const arnSplit = { 'Fn::Split': [ ':', { 'Fn::GetAtt': [ 'Repo02AC86CF', 'Arn' ] } ] }; - test.deepEqual(repo.node.resolve(uri), { 'Fn::Join': [ '', [ + test.deepEqual(stack.resolve(uri), { 'Fn::Join': [ '', [ { 'Fn::Select': [ 4, arnSplit ] }, '.dkr.ecr.', { 'Fn::Select': [ 3, arnSplit ] }, @@ -177,8 +177,8 @@ export = { const repo2 = ecr.Repository.fromRepositoryArn(stack, 'repo', 'arn:aws:ecr:us-east-1:585695036304:repository/foo/bar/foo/fooo'); // THEN - 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(stack.resolve(repo2.repositoryArn), 'arn:aws:ecr:us-east-1:585695036304:repository/foo/bar/foo/fooo'); + test.deepEqual(stack.resolve(repo2.repositoryName), 'foo/bar/foo/fooo'); test.done(); }, @@ -205,8 +205,8 @@ export = { }); // THEN - test.deepEqual(repo.node.resolve(repo.repositoryArn), { 'Fn::GetAtt': [ 'Boom', 'Arn' ] }); - test.deepEqual(repo.node.resolve(repo.repositoryName), { 'Fn::GetAtt': [ 'Boom', 'Name' ] }); + test.deepEqual(stack.resolve(repo.repositoryArn), { 'Fn::GetAtt': [ 'Boom', 'Arn' ] }); + test.deepEqual(stack.resolve(repo.repositoryName), { 'Fn::GetAtt': [ 'Boom', 'Name' ] }); test.done(); }, @@ -218,7 +218,7 @@ export = { const repo = ecr.Repository.fromRepositoryName(stack, 'just-name', 'my-repo'); // THEN - test.deepEqual(repo.node.resolve(repo.repositoryArn), { + test.deepEqual(stack.resolve(repo.repositoryArn), { 'Fn::Join': [ '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -229,7 +229,7 @@ export = { ':repository/my-repo' ] ] }); - test.deepEqual(repo.node.resolve(repo.repositoryName), 'my-repo'); + test.deepEqual(stack.resolve(repo.repositoryName), 'my-repo'); test.done(); }, @@ -245,8 +245,8 @@ export = { }); // THEN - test.deepEqual(repo.node.resolve(repo.repositoryName), { 'Fn::GetAtt': [ 'Boom', 'Name' ] }); - test.deepEqual(repo.node.resolve(repo.repositoryArn), { + test.deepEqual(stack.resolve(repo.repositoryName), { 'Fn::GetAtt': [ 'Boom', 'Name' ] }); + test.deepEqual(stack.resolve(repo.repositoryArn), { 'Fn::Join': [ '', [ 'arn:', { Ref: 'AWS::Partition' }, 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 892d9e6913e09..847c8310fbe5f 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -4,7 +4,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); import iam = require('@aws-cdk/aws-iam'); import cloudmap = require('@aws-cdk/aws-servicediscovery'); -import { IResource, Resource } from '@aws-cdk/cdk'; +import { IResource, Resource, Stack } from '@aws-cdk/cdk'; import cdk = require('@aws-cdk/cdk'); import { NetworkMode, TaskDefinition } from '../base/task-definition'; import { ICluster } from '../cluster'; @@ -289,7 +289,7 @@ export abstract class BaseService extends Resource */ private makeAutoScalingRole(): iam.IRole { // Use a Service Linked Role. - return iam.Role.fromRoleArn(this, 'ScalingRole', this.node.stack.formatArn({ + return iam.Role.fromRoleArn(this, 'ScalingRole', Stack.of(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/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index ca8b8a78e2bd4..db9389fbdbd7c 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -3,7 +3,7 @@ import cloudwatch = require ('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); import iam = require('@aws-cdk/aws-iam'); import cloudmap = require('@aws-cdk/aws-servicediscovery'); -import { Construct, IResource, Resource, SSMParameterProvider } from '@aws-cdk/cdk'; +import { Construct, IResource, Resource, SSMParameterProvider, Stack } from '@aws-cdk/cdk'; import { InstanceDrainHook } from './drain-hook/instance-drain-hook'; import { CfnCluster } from './ecs.generated'; @@ -398,13 +398,13 @@ class ImportedCluster extends Construct implements ICluster { this.hasEc2Capacity = props.hasEc2Capacity !== false; this._defaultNamespace = props.defaultNamespace; - this.clusterArn = props.clusterArn !== undefined ? props.clusterArn : this.node.stack.formatArn({ + this.clusterArn = props.clusterArn !== undefined ? props.clusterArn : Stack.of(this).formatArn({ service: 'ecs', resource: 'cluster', resourceName: props.clusterName, }); - this.clusterArn = props.clusterArn !== undefined ? props.clusterArn : this.node.stack.formatArn({ + this.clusterArn = props.clusterArn !== undefined ? props.clusterArn : Stack.of(this).formatArn({ service: 'ecs', resource: 'cluster', resourceName: props.clusterName, 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 b5753f2c9183d..b21e77d74ccc0 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 @@ -1,5 +1,6 @@ import logs = require('@aws-cdk/aws-logs'); import cdk = require('@aws-cdk/cdk'); +import { Stack } from '@aws-cdk/cdk'; import { ContainerDefinition } from '../container-definition'; import { CfnTaskDefinition } from '../ecs.generated'; import { LogDriver } from "./log-driver"; @@ -97,7 +98,7 @@ export class AwsLogDriver extends LogDriver { options: removeEmpty({ 'awslogs-group': this.logGroup.logGroupName, 'awslogs-stream-prefix': this.props.streamPrefix, - 'awslogs-region': this.node.stack.region, + 'awslogs-region': Stack.of(this).region, 'awslogs-datetime-format': this.props.datetimeFormat, 'awslogs-multiline-pattern': this.props.multilinePattern, }), 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 d16d915e7adb3..51669be9b32a9 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 @@ -222,7 +222,7 @@ export = { // THEN expect(stack).to(haveResourceLike("AWS::ECS::TaskDefinition", { - TaskRoleArn: stack.node.resolve(taskDefinition.taskRole.roleArn) + TaskRoleArn: stack.resolve(taskDefinition.taskRole.roleArn) })); test.done(); diff --git a/packages/@aws-cdk/aws-ecs/test/test.aws-log-driver.ts b/packages/@aws-cdk/aws-ecs/test/test.aws-log-driver.ts index 92dba654ad329..159205c50d31b 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.aws-log-driver.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.aws-log-driver.ts @@ -23,7 +23,7 @@ export = { })); test.deepEqual( - stack.node.resolve(driver.renderLogDriver()), + stack.resolve(driver.renderLogDriver()), { logDriver: 'awslogs', options: { @@ -52,7 +52,7 @@ export = { // THEN test.deepEqual( - stack.node.resolve(driver.renderLogDriver()), + stack.resolve(driver.renderLogDriver()), { logDriver: 'awslogs', options: { diff --git a/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts b/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts index bd43e43e12ce0..aadabf7c3f485 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts @@ -381,7 +381,7 @@ export = { // THEN test.equal(cluster2.defaultNamespace!.type, cloudmap.NamespaceType.DnsPrivate); - test.deepEqual(stack2.node.resolve(cluster2.defaultNamespace!.namespaceId), 'import-namespace-id'); + test.deepEqual(stack2.resolve(cluster2.defaultNamespace!.namespaceId), 'import-namespace-id'); // Can retrieve subnets from VPC - will throw 'There are no 'Private' subnets in this VPC. Use a different VPC subnet selection.' if broken. cluster2.vpc.selectSubnets(); diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index e751732070855..c63c3eab36d1d 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -313,7 +313,7 @@ export class Cluster extends Resource implements ICluster { autoScalingGroup.role.attachManagedPolicy(new iam.AwsManagedPolicy('AmazonEC2ContainerRegistryReadOnly', this).policyArn); // EKS Required Tags - autoScalingGroup.node.apply(new Tag(`kubernetes.io/cluster/${this.clusterName}`, 'owned', { applyToLaunchedInstances: true })); + autoScalingGroup.node.applyAspect(new Tag(`kubernetes.io/cluster/${this.clusterName}`, 'owned', { applyToLaunchedInstances: true })); // Create an CfnOutput for the Instance Role ARN (need to paste it into aws-auth-cm.yaml) new CfnOutput(autoScalingGroup, 'InstanceRoleARN', { @@ -337,7 +337,7 @@ export class Cluster extends Resource implements ICluster { return; } - subnet.node.apply(new Tag("kubernetes.io/role/internal-elb", "1")); + subnet.node.applyAspect(new Tag("kubernetes.io/role/internal-elb", "1")); } } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts index 7a7edf243f163..30510c227ee33 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts @@ -2,7 +2,7 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); import iam = require('@aws-cdk/aws-iam'); import s3 = require('@aws-cdk/aws-s3'); -import { Construct, Token } from '@aws-cdk/cdk'; +import { Construct, Stack, Token } from '@aws-cdk/cdk'; import { BaseLoadBalancer, BaseLoadBalancerProps, ILoadBalancerV2 } from '../shared/base-load-balancer'; import { IpAddressType } from '../shared/enums'; import { ApplicationListener, BaseApplicationListenerProps } from './application-listener'; @@ -86,7 +86,7 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic this.setAttribute('access_logs.s3.bucket', bucket.bucketName.toString()); this.setAttribute('access_logs.s3.prefix', prefix); - const region = this.node.stack.requireRegion('Enable ELBv2 access logging'); + const region = Stack.of(this).requireRegion('Enable ELBv2 access logging'); const account = ELBV2_ACCOUNTS[region]; if (!account) { throw new Error(`Cannot enable access logging; don't know ELBv2 account for region ${region}`); 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 2aa275e993bed..5e77a74014285 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts @@ -1,6 +1,7 @@ import { expect, haveResource, MatchStyle } from '@aws-cdk/assert'; import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); +import { ConstructNode } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import elbv2 = require('../../lib'); import { FakeSelfRegisteringTarget } from '../helpers'; @@ -88,7 +89,7 @@ export = { }); // THEN - const errors = stack.node.validateTree(); + const errors = ConstructNode.validate(stack.node); test.deepEqual(errors.map(e => e.message), ['HTTPS Listener needs at least one certificate (call addCertificateArns)']); test.done(); @@ -430,7 +431,7 @@ export = { test.equal('AWS/ApplicationELB', metric.namespace); const loadBalancerArn = { Ref: "LBSomeListenerCA01F1A0" }; - test.deepEqual(lb.node.resolve(metric.dimensions), { + test.deepEqual(stack.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 7a372c2fdc806..0b607002a2562 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 @@ -240,7 +240,7 @@ export = { for (const metric of metrics) { test.equal('AWS/ApplicationELB', metric.namespace); - test.deepEqual(stack.node.resolve(metric.dimensions), { + test.deepEqual(stack.resolve(metric.dimensions), { LoadBalancer: { 'Fn::GetAtt': ['LB8A12904C', 'LoadBalancerFullName'] } }); } diff --git a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts index bc47ff7dbad54..b2e8205725698 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts @@ -3,6 +3,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import ecs = require('@aws-cdk/aws-ecs'); import events = require ('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); +import { Stack } from '@aws-cdk/cdk'; import { ContainerOverride } from './ecs-task-properties'; import { singletonEventRole } from './util'; @@ -121,7 +122,7 @@ export class EcsTask implements events.IRuleTarget { apiVersion: '2015-10-07', action: 'putTargets', parameters: { - Rule: this.taskDefinition.node.stack.parseArn(rule.ruleArn).resourceName, + Rule: Stack.of(this.taskDefinition).parseArn(rule.ruleArn).resourceName, Targets: [ { Arn: arn, diff --git a/packages/@aws-cdk/aws-events/lib/input.ts b/packages/@aws-cdk/aws-events/lib/input.ts index 077b9892bde15..1fd9b67c5ce43 100644 --- a/packages/@aws-cdk/aws-events/lib/input.ts +++ b/packages/@aws-cdk/aws-events/lib/input.ts @@ -1,4 +1,4 @@ -import { CloudFormationLang, DefaultTokenResolver, IResolveContext, resolve, StringConcat, Token } from '@aws-cdk/cdk'; +import { DefaultTokenResolver, IResolveContext, resolve, Stack, StringConcat, Token } from '@aws-cdk/cdk'; import { IRule } from './rule-ref'; /** @@ -159,6 +159,8 @@ class FieldAwareEventInput extends RuleTargetInput { } } + const stack = Stack.of(rule); + let resolved: string; if (this.inputType === InputType.Multiline) { // JSONify individual lines @@ -166,9 +168,9 @@ class FieldAwareEventInput extends RuleTargetInput { scope: rule, resolver: new EventFieldReplacer() }); - resolved = resolved.split('\n').map(CloudFormationLang.toJSON).join('\n'); + resolved = resolved.split('\n').map(stack.toJsonString).join('\n'); } else { - resolved = CloudFormationLang.toJSON(resolve(this.input, { + resolved = stack.toJsonString(resolve(this.input, { scope: rule, resolver: new EventFieldReplacer() })); diff --git a/packages/@aws-cdk/aws-events/test/test.rule.ts b/packages/@aws-cdk/aws-events/test/test.rule.ts index 8c4a4351b9bc8..d9c0ed4230521 100644 --- a/packages/@aws-cdk/aws-events/test/test.rule.ts +++ b/packages/@aws-cdk/aws-events/test/test.rule.ts @@ -349,7 +349,7 @@ export = { const rule = new Rule(stack, 'EventRule'); rule.addTarget(t1); - test.deepEqual(stack.node.resolve(receivedRuleArn), stack.node.resolve(rule.ruleArn)); + test.deepEqual(stack.resolve(receivedRuleArn), stack.resolve(rule.ruleArn)); test.deepEqual(receivedRuleId, rule.node.uniqueId); test.done(); }, diff --git a/packages/@aws-cdk/aws-glue/lib/database.ts b/packages/@aws-cdk/aws-glue/lib/database.ts index aec37901ec5f0..adcaf1186c100 100644 --- a/packages/@aws-cdk/aws-glue/lib/database.ts +++ b/packages/@aws-cdk/aws-glue/lib/database.ts @@ -1,5 +1,5 @@ import s3 = require('@aws-cdk/aws-s3'); -import { Construct, IResource, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, Resource, Stack } from '@aws-cdk/cdk'; import { CfnDatabase } from './glue.generated'; export interface IDatabase extends IResource { @@ -48,11 +48,13 @@ export interface DatabaseProps { export class Database extends Resource implements IDatabase { public static fromDatabaseArn(scope: Construct, id: string, databaseArn: string): IDatabase { + const stack = Stack.of(scope); + class Import extends Construct implements IDatabase { public databaseArn = databaseArn; - public databaseName = scope.node.stack.parseArn(databaseArn).resourceName!; - public catalogArn = scope.node.stack.formatArn({ service: 'glue', resource: 'catalog' }); - public catalogId = scope.node.stack.accountId; + public databaseName = stack.parseArn(databaseArn).resourceName!; + public catalogArn = stack.formatArn({ service: 'glue', resource: 'catalog' }); + public catalogId = stack.accountId; } return new Import(scope, id); @@ -93,7 +95,7 @@ export class Database extends Resource implements IDatabase { this.locationUri = `s3://${bucket.bucketName}/${props.databaseName}`; } - this.catalogId = this.node.stack.accountId; + this.catalogId = Stack.of(this).accountId; const resource = new CfnDatabase(this, 'Resource', { catalogId: this.catalogId, databaseInput: { @@ -104,13 +106,13 @@ export class Database extends Resource implements IDatabase { // see https://docs.aws.amazon.com/glue/latest/dg/glue-specifying-resource-arns.html#data-catalog-resource-arns this.databaseName = resource.databaseName; - this.databaseArn = this.node.stack.formatArn({ + this.databaseArn = Stack.of(this).formatArn({ service: 'glue', resource: 'database', resourceName: this.databaseName }); // catalogId is implicitly the accountId, which is why we don't pass the catalogId here - this.catalogArn = this.node.stack.formatArn({ + this.catalogArn = Stack.of(this).formatArn({ service: 'glue', resource: 'catalog' }); diff --git a/packages/@aws-cdk/aws-glue/lib/table.ts b/packages/@aws-cdk/aws-glue/lib/table.ts index 9fca439772d35..7442143c64763 100644 --- a/packages/@aws-cdk/aws-glue/lib/table.ts +++ b/packages/@aws-cdk/aws-glue/lib/table.ts @@ -1,7 +1,7 @@ import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); import s3 = require('@aws-cdk/aws-s3'); -import { Construct, Fn, IResource, Resource } from '@aws-cdk/cdk'; +import { Construct, Fn, IResource, Resource, Stack } from '@aws-cdk/cdk'; import { DataFormat } from './data-format'; import { IDatabase } from './database'; import { CfnTable } from './glue.generated'; @@ -150,7 +150,7 @@ export interface TableProps { export class Table extends Resource implements ITable { public static fromTableArn(scope: Construct, id: string, tableArn: string): ITable { - const tableName = Fn.select(1, Fn.split('/', scope.node.stack.parseArn(tableArn).resourceName!)); + const tableName = Fn.select(1, Fn.split('/', Stack.of(scope).parseArn(tableArn).resourceName!)); return Table.fromTableAttributes(scope, id, { tableArn, @@ -277,7 +277,7 @@ export class Table extends Resource implements ITable { }); this.tableName = tableResource.tableName; - this.tableArn = this.node.stack.formatArn({ + this.tableArn = Stack.of(this).formatArn({ service: 'glue', resource: 'table', resourceName: `${this.database.databaseName}/${this.tableName}` diff --git a/packages/@aws-cdk/aws-glue/test/test.database.ts b/packages/@aws-cdk/aws-glue/test/test.database.ts index 47df25856ce67..71501270ea99b 100644 --- a/packages/@aws-cdk/aws-glue/test/test.database.ts +++ b/packages/@aws-cdk/aws-glue/test/test.database.ts @@ -84,9 +84,9 @@ export = { // THEN test.deepEqual(database.databaseArn, 'arn:aws:glue:us-east-1:123456789012:database/db1'); test.deepEqual(database.databaseName, 'db1'); - test.deepEqual(stack.node.resolve(database.catalogArn), { 'Fn::Join': [ '', + test.deepEqual(stack.resolve(database.catalogArn), { 'Fn::Join': [ '', [ 'arn:', { Ref: 'AWS::Partition' }, ':glue:', { Ref: 'AWS::Region' }, ':', { Ref: 'AWS::AccountId' }, ':catalog' ] ] }); - test.deepEqual(stack.node.resolve(database.catalogId), { Ref: 'AWS::AccountId' }); + test.deepEqual(stack.resolve(database.catalogId), { Ref: 'AWS::AccountId' }); test.done(); } }; diff --git a/packages/@aws-cdk/aws-iam/lib/group.ts b/packages/@aws-cdk/aws-iam/lib/group.ts index 5a92f046c9632..c5de7b2352384 100644 --- a/packages/@aws-cdk/aws-iam/lib/group.ts +++ b/packages/@aws-cdk/aws-iam/lib/group.ts @@ -1,4 +1,4 @@ -import { Construct, Resource } from '@aws-cdk/cdk'; +import { Construct, Resource, Stack } from '@aws-cdk/cdk'; import { CfnGroup } from './iam.generated'; import { IIdentity } from './identity-base'; import { Policy } from './policy'; @@ -106,7 +106,7 @@ export class Group extends GroupBase { * @param groupArn (e.g. `arn:aws:iam::account-id:group/group-name`) */ public static fromGroupArn(scope: Construct, id: string, groupArn: string): IGroup { - const groupName = scope.node.stack.parseArn(groupArn).resourceName!; + const groupName = Stack.of(scope).parseArn(groupArn).resourceName!; class Import extends GroupBase { public groupName = groupName; public groupArn = groupArn; diff --git a/packages/@aws-cdk/aws-iam/lib/imported-resource-principal.ts b/packages/@aws-cdk/aws-iam/lib/imported-resource-principal.ts index 67b9e1d675579..87530e9d3006b 100644 --- a/packages/@aws-cdk/aws-iam/lib/imported-resource-principal.ts +++ b/packages/@aws-cdk/aws-iam/lib/imported-resource-principal.ts @@ -1,4 +1,4 @@ -import cdk = require('@aws-cdk/cdk'); +import { IConstruct, Stack } from '@aws-cdk/cdk'; import { PolicyStatement } from './policy-document'; import { IPrincipal, PrincipalPolicyFragment } from './principals'; @@ -9,7 +9,7 @@ export interface ImportedResourcePrincipalProps { /** * The resource the role proxy is for */ - readonly resource: cdk.IConstruct; + readonly resource: IConstruct; } /** @@ -26,7 +26,7 @@ export interface ImportedResourcePrincipalProps { export class ImportedResourcePrincipal implements IPrincipal { public readonly assumeRoleAction: string = 'sts:AssumeRole'; public readonly grantPrincipal: IPrincipal; - private readonly resource: cdk.IConstruct; + private readonly resource: IConstruct; constructor(props: ImportedResourcePrincipalProps) { this.resource = props.resource; @@ -38,7 +38,8 @@ export class ImportedResourcePrincipal implements IPrincipal { } public addToPolicy(statement: PolicyStatement): boolean { - const repr = JSON.stringify(this.resource.node.resolve(statement)); + const stack = Stack.of(this.resource); + const repr = JSON.stringify(stack.resolve(statement)); this.resource.node.addWarning(`Add statement to this resource's role: ${repr}`); return true; // Pretend we did the work. The human will do it for us, eventually. } diff --git a/packages/@aws-cdk/aws-iam/lib/managed-policy.ts b/packages/@aws-cdk/aws-iam/lib/managed-policy.ts index 7699f6a8b222e..726e51624c1c1 100644 --- a/packages/@aws-cdk/aws-iam/lib/managed-policy.ts +++ b/packages/@aws-cdk/aws-iam/lib/managed-policy.ts @@ -1,4 +1,5 @@ import cdk = require('@aws-cdk/cdk'); +import { Stack } from '@aws-cdk/cdk'; /** * A policy managed by AWS @@ -18,7 +19,7 @@ export class AwsManagedPolicy { */ public get policyArn(): string { // the arn is in the form of - arn:aws:iam::aws:policy/ - return this.scope.node.stack.formatArn({ + return Stack.of(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/principals.ts b/packages/@aws-cdk/aws-iam/lib/principals.ts index e655ef12ba011..81df7ba7d6628 100644 --- a/packages/@aws-cdk/aws-iam/lib/principals.ts +++ b/packages/@aws-cdk/aws-iam/lib/principals.ts @@ -1,4 +1,5 @@ import cdk = require('@aws-cdk/cdk'); +import { Stack } from '@aws-cdk/cdk'; import { Default, RegionInfo } from '@aws-cdk/region-info'; import { PolicyStatement } from './policy-document'; import { mergePrincipal } from './util'; @@ -316,7 +317,7 @@ class StackDependentToken extends cdk.Token { } public resolve(context: cdk.IResolveContext) { - return this.fn(context.scope.node.stack); + return this.fn(Stack.of(context.scope)); } } @@ -327,8 +328,8 @@ class ServicePrincipalToken extends cdk.Token { } public resolve(ctx: cdk.IResolveContext) { - const region = this.opts.region || ctx.scope.node.stack.region; + const region = this.opts.region || Stack.of(ctx.scope).region; const fact = RegionInfo.get(region).servicePrincipal(this.service); - return fact || Default.servicePrincipal(this.service, region, ctx.scope.node.stack.urlSuffix); + return fact || Default.servicePrincipal(this.service, region, Stack.of(ctx.scope).urlSuffix); } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/lib/role.ts b/packages/@aws-cdk/aws-iam/lib/role.ts index b92dae7f6cba5..56bcd5de1d2bc 100644 --- a/packages/@aws-cdk/aws-iam/lib/role.ts +++ b/packages/@aws-cdk/aws-iam/lib/role.ts @@ -1,4 +1,4 @@ -import { Construct, Resource } from '@aws-cdk/cdk'; +import { Construct, Resource, Stack } from '@aws-cdk/cdk'; import { Grant } from './grant'; import { CfnRole } from './iam.generated'; import { IIdentity } from './identity-base'; @@ -114,7 +114,7 @@ export class Role extends Resource implements IRole { public readonly assumeRoleAction: string = 'sts:AssumeRole'; public readonly policyFragment = new ArnPrincipal(roleArn).policyFragment; public readonly roleArn = roleArn; - public readonly roleName = scope.node.stack.parseArn(roleArn).resourceName!; + public readonly roleName = Stack.of(scope).parseArn(roleArn).resourceName!; public addToPolicy(_statement: PolicyStatement): boolean { // Statement will be added to resource instead diff --git a/packages/@aws-cdk/aws-iam/test/test.lazy-role.ts b/packages/@aws-cdk/aws-iam/test/test.lazy-role.ts index dd3ab5ec63f2b..ef298e2131abe 100644 --- a/packages/@aws-cdk/aws-iam/test/test.lazy-role.ts +++ b/packages/@aws-cdk/aws-iam/test/test.lazy-role.ts @@ -52,7 +52,7 @@ export = nodeunit.testCase({ }); // THEN - test.deepEqual(stack.node.resolve(role.roleName), + test.deepEqual(stack.resolve(role.roleName), { Ref: 'Lazy399F7F48'}); test.done(); } 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 142a239febaee..ffb6f503ddc19 100644 --- a/packages/@aws-cdk/aws-iam/test/test.managed-policy.ts +++ b/packages/@aws-cdk/aws-iam/test/test.managed-policy.ts @@ -7,7 +7,7 @@ export = { const stack = new cdk.Stack(); const mp = new AwsManagedPolicy("service-role/SomePolicy", stack); - test.deepEqual(stack.node.resolve(mp.policyArn), { + test.deepEqual(stack.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 37882965149dd..d7818c1e878a1 100644 --- a/packages/@aws-cdk/aws-iam/test/test.policy-document.ts +++ b/packages/@aws-cdk/aws-iam/test/test.policy-document.ts @@ -17,7 +17,7 @@ export = { p.addAwsAccountPrincipal(`my${new Token({ account: 'account' })}name`); p.limitToAccount('12221121221'); - test.deepEqual(stack.node.resolve(p), { Action: + test.deepEqual(stack.resolve(p), { Action: [ 'sqs:SendMessage', 'dynamodb:CreateTable', 'dynamodb:DeleteTable' ], @@ -51,7 +51,7 @@ export = { doc.addStatement(p1); doc.addStatement(p2); - test.deepEqual(stack.node.resolve(doc), { + test.deepEqual(stack.resolve(doc), { Version: '2012-10-17', Statement: [ { Effect: 'Allow', Action: 'sqs:SendMessage', Resource: '*' }, @@ -73,7 +73,7 @@ export = { const doc = new PolicyDocument(base); doc.addStatement(new PolicyStatement().addResource('resource').addAction('action')); - test.deepEqual(stack.node.resolve(doc), { Version: 'Foo', + test.deepEqual(stack.resolve(doc), { Version: 'Foo', Something: 123, Statement: [ { Statement1: 1 }, @@ -85,7 +85,7 @@ 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(stack.node.resolve(perm), { + test.deepEqual(stack.resolve(perm), { Effect: 'Allow', Action: [ 'Action1', 'Action2', 'Action3' ], Resource: 'MyResource' }); @@ -95,7 +95,7 @@ export = { 'PolicyDoc resolves to undefined if there are no permissions'(test: Test) { const stack = new Stack(); const p = new PolicyDocument(); - test.deepEqual(stack.node.resolve(p), undefined); + test.deepEqual(stack.resolve(p), undefined); test.done(); }, @@ -104,7 +104,7 @@ export = { const p = new PolicyStatement(); const canoncialUser = "averysuperduperlongstringfor"; p.addPrincipal(new CanonicalUserPrincipal(canoncialUser)); - test.deepEqual(stack.node.resolve(p), { + test.deepEqual(stack.resolve(p), { Effect: "Allow", Principal: { CanonicalUser: canoncialUser @@ -118,7 +118,7 @@ export = { const p = new PolicyStatement(); p.addAccountRootPrincipal(); - test.deepEqual(stack.node.resolve(p), { + test.deepEqual(stack.resolve(p), { Effect: "Allow", Principal: { AWS: { @@ -142,7 +142,7 @@ export = { const stack = new Stack(); const p = new PolicyStatement(); p.addFederatedPrincipal("com.amazon.cognito", { StringEquals: { key: 'value' }}); - test.deepEqual(stack.node.resolve(p), { + test.deepEqual(stack.resolve(p), { Effect: "Allow", Principal: { Federated: "com.amazon.cognito" @@ -160,7 +160,7 @@ export = { const p = new PolicyStatement(); p.addAwsAccountPrincipal('1234'); p.addAwsAccountPrincipal('5678'); - test.deepEqual(stack.node.resolve(p), { + test.deepEqual(stack.resolve(p), { Effect: 'Allow', Principal: { AWS: [ @@ -226,7 +226,7 @@ export = { p.addStatement(new PolicyStatement().addPrincipal(new Anyone())); - test.deepEqual(stack.node.resolve(p), { + test.deepEqual(stack.resolve(p), { Statement: [ { Effect: 'Allow', Principal: '*' } ], @@ -241,7 +241,7 @@ export = { p.addStatement(new PolicyStatement().addPrincipal(new AnyPrincipal())); - test.deepEqual(stack.node.resolve(p), { + test.deepEqual(stack.resolve(p), { Statement: [ { Effect: 'Allow', Principal: '*' } ], @@ -256,7 +256,7 @@ export = { p.addStatement(new PolicyStatement().addAnyPrincipal()); - test.deepEqual(stack.node.resolve(p), { + test.deepEqual(stack.resolve(p), { Statement: [ { Effect: 'Allow', Principal: '*' } ], @@ -274,7 +274,7 @@ export = { p.addStatement(new PolicyStatement().addArnPrincipal('111222-B')); p.addStatement(new PolicyStatement().addPrincipal(new ArnPrincipal('111222-C'))); - test.deepEqual(stack.node.resolve(p), { + test.deepEqual(stack.resolve(p), { Statement: [ { Effect: 'Allow', Principal: { AWS: '111222-A' } }, { Effect: 'Allow', Principal: { AWS: '111222-B' } }, @@ -293,7 +293,7 @@ export = { .addActions(...new Token(() => ['a', 'b', 'c']).toList()) .addResources(...new Token(() => ['x', 'y', 'z']).toList()); - test.deepEqual(stack.node.resolve(statement), { + test.deepEqual(stack.resolve(statement), { Effect: 'Allow', Action: ['a', 'b', 'c'], Resource: ['x', 'y', 'z'], @@ -309,7 +309,7 @@ export = { p.addStatement(new PolicyStatement().addCanonicalUserPrincipal('cannonical-user-1')); p.addStatement(new PolicyStatement().addPrincipal(new CanonicalUserPrincipal('cannonical-user-2'))); - test.deepEqual(stack.node.resolve(p), { + test.deepEqual(stack.resolve(p), { Statement: [ { Effect: 'Allow', Principal: { CanonicalUser: 'cannonical-user-1' } }, { Effect: 'Allow', Principal: { CanonicalUser: 'cannonical-user-2' } } @@ -330,7 +330,7 @@ export = { }; const s = new PolicyStatement().addAccountRootPrincipal() .addPrincipal(arrayPrincipal); - test.deepEqual(stack.node.resolve(s), { + test.deepEqual(stack.resolve(s), { Effect: 'Allow', Principal: { AWS: [ @@ -351,7 +351,7 @@ export = { .addResource('resource') .addAction('action'); - test.deepEqual(stack.node.resolve(s), { + test.deepEqual(stack.resolve(s), { Action: 'action', Effect: 'Allow', Principal: { AWS: '349494949494', Service: 'test.service' }, @@ -368,7 +368,7 @@ export = { .addAction('test:Action') .addServicePrincipal('codedeploy.amazonaws.com'); - test.deepEqual(stack.node.resolve(s), { + test.deepEqual(stack.resolve(s), { Effect: 'Allow', Action: 'test:Action', Principal: { Service: 'codedeploy.cn-north-1.amazonaws.com.cn' } @@ -383,7 +383,7 @@ export = { .addAction('test:Action') .addServicePrincipal('codedeploy.amazonaws.com', { region: 'cn-north-1' }); - test.deepEqual(stack.node.resolve(s), { + test.deepEqual(stack.resolve(s), { Effect: 'Allow', Action: 'test:Action', Principal: { Service: 'codedeploy.cn-north-1.amazonaws.com.cn' } @@ -398,7 +398,7 @@ export = { .addAction('test:Action') .addServicePrincipal('test.service-principal.dev'); - test.deepEqual(stack.node.resolve(s), { + test.deepEqual(stack.resolve(s), { Effect: 'Allow', Action: 'test:Action', Principal: { Service: 'test.service-principal.dev' } @@ -414,7 +414,7 @@ export = { const stack = new Stack(); const p = new CompositePrincipal(new ArnPrincipal('i:am:an:arn')); const statement = new PolicyStatement().addPrincipal(p); - test.deepEqual(stack.node.resolve(statement), { Effect: 'Allow', Principal: { AWS: 'i:am:an:arn' } }); + test.deepEqual(stack.resolve(statement), { Effect: 'Allow', Principal: { AWS: 'i:am:an:arn' } }); test.done(); }, @@ -445,7 +445,7 @@ export = { statement.addAwsPrincipal('aws-principal-3'); statement.addCondition('cond2', { boom: 123 }); - test.deepEqual(stack.node.resolve(statement), { + test.deepEqual(stack.resolve(statement), { Condition: { cond2: { boom: 123 } }, @@ -496,7 +496,7 @@ export = { p.addStatement(statement); // THEN - test.equal(stack.node.resolve(p).Statement.length, 1); + test.equal(stack.resolve(p).Statement.length, 1); test.done(); }, @@ -517,7 +517,7 @@ export = { p.addStatement(statement2); // THEN - test.equal(stack.node.resolve(p).Statement.length, 1); + test.equal(stack.resolve(p).Statement.length, 1); test.done(); }, @@ -546,7 +546,7 @@ export = { .addResource('resource')); // THEN - test.equal(stack.node.resolve(p).Statement.length, 1); + test.equal(stack.resolve(p).Statement.length, 1); test.done(); } }, @@ -565,7 +565,7 @@ export = { // THEN const stack = new Stack(); - test.deepEqual(stack.node.resolve(doc), { + test.deepEqual(stack.resolve(doc), { Version: '2012-10-17', Statement: [ { Action: 'action1', Effect: 'Allow', Resource: 'resource1', Sid: '0' }, diff --git a/packages/@aws-cdk/aws-kinesis/lib/stream.ts b/packages/@aws-cdk/aws-kinesis/lib/stream.ts index e8ecfd6a820a0..7de1d50ff6793 100644 --- a/packages/@aws-cdk/aws-kinesis/lib/stream.ts +++ b/packages/@aws-cdk/aws-kinesis/lib/stream.ts @@ -1,6 +1,6 @@ import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); -import { Construct, IResource, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, Resource, Stack } from '@aws-cdk/cdk'; import { CfnStream } from './kinesis.generated'; export interface IStream extends IResource { @@ -227,7 +227,7 @@ export class Stream extends StreamBase { public static fromStreamAttributes(scope: Construct, id: string, attrs: StreamAttributes): IStream { class Import extends StreamBase { public readonly streamArn = attrs.streamArn; - public readonly streamName = scope.node.stack.parseArn(attrs.streamArn).resourceName!; + public readonly streamName = Stack.of(scope).parseArn(attrs.streamArn).resourceName!; public readonly encryptionKey = attrs.encryptionKey; } diff --git a/packages/@aws-cdk/aws-kms/lib/key.ts b/packages/@aws-cdk/aws-kms/lib/key.ts index 3efc794dc63b7..9950da6b13d05 100644 --- a/packages/@aws-cdk/aws-kms/lib/key.ts +++ b/packages/@aws-cdk/aws-kms/lib/key.ts @@ -1,6 +1,6 @@ import iam = require('@aws-cdk/aws-iam'); import { PolicyDocument, PolicyStatement } from '@aws-cdk/aws-iam'; -import { Construct, DeletionPolicy, IResource, Resource } from '@aws-cdk/cdk'; +import { Construct, DeletionPolicy, IResource, Resource, Stack } from '@aws-cdk/cdk'; import { Alias } from './alias'; import { CfnKey } from './kms.generated'; @@ -79,9 +79,11 @@ abstract class KeyBase extends Resource implements IKey { * no-op. */ public addToResourcePolicy(statement: PolicyStatement, allowNoOp = true) { + const stack = Stack.of(this); + if (!this.policy) { if (allowNoOp) { return; } - throw new Error(`Unable to add statement to IAM resource policy for KMS key: ${JSON.stringify(this.node.resolve(this.keyArn))}`); + throw new Error(`Unable to add statement to IAM resource policy for KMS key: ${JSON.stringify(stack.resolve(this.keyArn))}`); } this.policy.addStatement(statement); diff --git a/packages/@aws-cdk/aws-kms/test/test.key.ts b/packages/@aws-cdk/aws-kms/test/test.key.ts index ac1e68047a2ae..4923791b90093 100644 --- a/packages/@aws-cdk/aws-kms/test/test.key.ts +++ b/packages/@aws-cdk/aws-kms/test/test.key.ts @@ -157,9 +157,9 @@ export = { p.addAwsPrincipal('arn'); key.addToResourcePolicy(p); - key.node.apply(new Tag('tag1', 'value1')); - key.node.apply(new Tag('tag2', 'value2')); - key.node.apply(new Tag('tag3', '')); + key.node.applyAspect(new Tag('tag1', 'value1')); + key.node.applyAspect(new Tag('tag2', 'value2')); + key.node.applyAspect(new Tag('tag3', '')); expect(stack).to(exactlyMatchTemplate({ Resources: { diff --git a/packages/@aws-cdk/aws-kms/test/test.via-service-principal.ts b/packages/@aws-cdk/aws-kms/test/test.via-service-principal.ts index 308bdd996b829..85bdd0a6110a9 100644 --- a/packages/@aws-cdk/aws-kms/test/test.via-service-principal.ts +++ b/packages/@aws-cdk/aws-kms/test/test.via-service-principal.ts @@ -15,7 +15,7 @@ export = { .addResource('*'); // THEN - test.deepEqual(stack.node.resolve(statement), { + test.deepEqual(stack.resolve(statement), { Action: 'abc:call', Condition: { StringEquals: { 'kms:ViaService': 'bla.amazonaws.com' } }, Effect: 'Allow', @@ -37,7 +37,7 @@ export = { .addResource('*'); // THEN - test.deepEqual(stack.node.resolve(statement), { + test.deepEqual(stack.resolve(statement), { Action: 'abc:call', Condition: { StringEquals: { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts index c1945bb0220c9..d6f618cb5b4b4 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts @@ -1,5 +1,6 @@ import apigw = require('@aws-cdk/aws-apigateway'); import lambda = require('@aws-cdk/aws-lambda'); +import { Stack } from '@aws-cdk/cdk'; export class ApiEventSource implements lambda.IEventSource { constructor(private readonly method: string, private readonly path: string, private readonly options?: apigw.MethodOptions) { @@ -10,7 +11,7 @@ export class ApiEventSource implements lambda.IEventSource { public bind(target: lambda.IFunction): void { const id = `${target.node.uniqueId}:ApiEventSourceA7A86A4F`; - const stack = target.node.stack; + const stack = Stack.of(target); let api = stack.node.tryFindChild(id) as apigw.RestApi; if (!api) { api = new apigw.RestApi(stack, id, { diff --git a/packages/@aws-cdk/aws-lambda/lib/alias.ts b/packages/@aws-cdk/aws-lambda/lib/alias.ts index aed8d2c009d7a..b46c31dac4277 100644 --- a/packages/@aws-cdk/aws-lambda/lib/alias.ts +++ b/packages/@aws-cdk/aws-lambda/lib/alias.ts @@ -1,5 +1,5 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); -import { Construct } from '@aws-cdk/cdk'; +import { Construct, Stack } from '@aws-cdk/cdk'; import { FunctionBase, IFunction } from './function-base'; import { IVersion } from './lambda-version'; import { CfnAlias } from './lambda.generated'; @@ -97,7 +97,7 @@ export class Alias extends FunctionBase { // ARN parsing splits on `:`, so we can only get the function's name from the ARN as resourceName... // And we're parsing it out (instead of using the underlying function directly) in order to have use of it incur // an implicit dependency on the resource. - this.functionName = `${this.node.stack.parseArn(alias.aliasArn, ":").resourceName!}:${props.aliasName}`; + this.functionName = `${Stack.of(this).parseArn(alias.aliasArn, ":").resourceName!}:${props.aliasName}`; this.functionArn = alias.aliasArn; } diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index fdd779bd310f6..d569ff8cc9d26 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -273,7 +273,7 @@ export abstract class FunctionBase extends Resource implements IFunction { return (principal as iam.ServicePrincipal).service; } - throw new Error(`Invalid principal type for Lambda permission statement: ${this.node.resolve(principal.toString())}. ` + + throw new Error(`Invalid principal type for Lambda permission statement: ${principal.constructor.name}. ` + 'Supported: AccountPrincipal, ServicePrincipal'); } } diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index ee499a4b56dd0..e7aa0cee86089 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -3,7 +3,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import iam = require('@aws-cdk/aws-iam'); import logs = require('@aws-cdk/aws-logs'); import sqs = require('@aws-cdk/aws-sqs'); -import { Construct, Fn, Token } from '@aws-cdk/cdk'; +import { Construct, Fn, Stack, Token } from '@aws-cdk/cdk'; import { Code } from './code'; import { IEventSource } from './event-source'; import { FunctionAttributes, FunctionBase, IFunction } from './function-base'; @@ -414,10 +414,11 @@ export class Function extends FunctionBase { this.role.addToPolicy(statement); } - const isChina = this.node.stack.env.region && this.node.stack.env.region.startsWith('cn-'); + const region = Stack.of(this).env.region; + const isChina = region && region.startsWith('cn-'); if (isChina && props.environment && Object.keys(props.environment).length > 0) { // tslint:disable-next-line:max-line-length - throw new Error(`Environment variables are not supported in this region (${this.node.stack.env.region}); consider using tags or SSM parameters instead`); + throw new Error(`Environment variables are not supported in this region (${region}); consider using tags or SSM parameters instead`); } const resource = new CfnFunction(this, 'Resource', { diff --git a/packages/@aws-cdk/aws-lambda/lib/layers.ts b/packages/@aws-cdk/aws-lambda/lib/layers.ts index 71f4af437aca4..92a7ecd1b598c 100644 --- a/packages/@aws-cdk/aws-lambda/lib/layers.ts +++ b/packages/@aws-cdk/aws-lambda/lib/layers.ts @@ -1,4 +1,4 @@ -import { Construct, IResource, Resource, Token } from '@aws-cdk/cdk'; +import { Construct, IResource, Resource, Stack, Token } from '@aws-cdk/cdk'; import { Code } from './code'; import { CfnLayerVersion, CfnLayerVersionPermission } from './lambda.generated'; import { Runtime } from './runtime'; @@ -223,10 +223,10 @@ export class SingletonLayerVersion extends Construct implements ILayerVersion { private ensureLayerVersion(props: SingletonLayerVersionProps): ILayerVersion { const singletonId = `SingletonLayer-${props.uuid}`; - const existing = this.node.stack.node.tryFindChild(singletonId); + const existing = Stack.of(this).node.tryFindChild(singletonId); if (existing) { return existing as unknown as ILayerVersion; } - return new LayerVersion(this.node.stack, singletonId, props); + return new LayerVersion(Stack.of(this), singletonId, props); } } diff --git a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts index 50708d1360867..08025d5dc3636 100644 --- a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts @@ -1,5 +1,6 @@ import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); +import { Stack } from '@aws-cdk/cdk'; import { Function as LambdaFunction, FunctionProps } from './function'; import { FunctionBase, IFunction } from './function-base'; import { Permission } from './permission'; @@ -63,13 +64,13 @@ export class SingletonFunction extends FunctionBase { private ensureLambda(props: SingletonFunctionProps): IFunction { const constructName = (props.lambdaPurpose || 'SingletonLambda') + slugify(props.uuid); - const existing = this.node.stack.node.tryFindChild(constructName); + const existing = Stack.of(this).node.tryFindChild(constructName); if (existing) { // Just assume this is true return existing as FunctionBase; } - return new LambdaFunction(this.node.stack, constructName, props); + return new LambdaFunction(Stack.of(this), constructName, props); } } diff --git a/packages/@aws-cdk/aws-lambda/test/test.alias.ts b/packages/@aws-cdk/aws-lambda/test/test.alias.ts index d5ee321959f9a..f3208e37f0d39 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.alias.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.alias.ts @@ -31,7 +31,7 @@ export = { Type: "AWS::Lambda::Alias", Properties: { FunctionName: { Ref: "MyLambdaCCE802FB" }, - FunctionVersion: stack.node.resolve(version.version), + FunctionVersion: stack.resolve(version.version), Name: "prod" } } @@ -86,11 +86,11 @@ export = { }); expect(stack).to(haveResource('AWS::Lambda::Alias', { - FunctionVersion: stack.node.resolve(version1.version), + FunctionVersion: stack.resolve(version1.version), RoutingConfig: { AdditionalVersionWeights: [ { - FunctionVersion: stack.node.resolve(version2.version), + FunctionVersion: stack.resolve(version2.version), FunctionWeight: 0.1 } ] @@ -213,7 +213,7 @@ export = { const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', version }); // WHEN - test.deepEqual(stack.node.resolve(alias.functionName), { + test.deepEqual(stack.resolve(alias.functionName), { "Fn::Join": [ "", [ diff --git a/packages/@aws-cdk/aws-lambda/test/test.code.ts b/packages/@aws-cdk/aws-lambda/test/test.code.ts index 29b21b0a66cf6..0b5e76ba239ac 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.code.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.code.ts @@ -122,8 +122,8 @@ export = { }, })); - test.equal(stack.node.resolve(code.bucketNameParam), 'FunctionLambdaSourceBucketNameParameter9E9E108F'); - test.equal(stack.node.resolve(code.objectKeyParam), 'FunctionLambdaSourceObjectKeyParameter1C7AED11'); + test.equal(stack.resolve(code.bucketNameParam), 'FunctionLambdaSourceBucketNameParameter9E9E108F'); + test.equal(stack.resolve(code.objectKeyParam), 'FunctionLambdaSourceObjectKeyParameter1C7AED11'); test.done(); }, @@ -156,8 +156,8 @@ export = { objectKeyParam: bucketKeyParam, }); - test.equal(stack.node.resolve(code.bucketNameParam), 'BucketNameParam'); - test.equal(stack.node.resolve(code.objectKeyParam), 'ObjectKeyParam'); + test.equal(stack.resolve(code.bucketNameParam), 'BucketNameParam'); + test.equal(stack.resolve(code.objectKeyParam), 'ObjectKeyParam'); new lambda.Function(stack, 'Function', { code, @@ -192,7 +192,7 @@ export = { }); // when - const overrides = stack.node.resolve(code.assign({ + const overrides = stack.resolve(code.assign({ bucketName: 'SomeBucketName', objectKey: 'SomeObjectKey', })); diff --git a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts index ccf1403684999..f491ff4eb101c 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts @@ -1084,7 +1084,7 @@ export = { }); // THEN - test.deepEqual(stack.node.resolve(fn.metricErrors()), { + test.deepEqual(stack.resolve(fn.metricErrors()), { dimensions: { FunctionName: { Ref: 'Function76856677' }}, namespace: 'AWS/Lambda', metricName: 'Errors', diff --git a/packages/@aws-cdk/aws-lambda/test/test.layers.ts b/packages/@aws-cdk/aws-lambda/test/test.layers.ts index 244c58095ace8..31b0cdf258c6d 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.layers.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.layers.ts @@ -20,7 +20,7 @@ export = testCase({ // THEN expect(stack).to(haveResource('AWS::Lambda::LayerVersion', { Content: { - S3Bucket: stack.node.resolve(bucket.bucketName), + S3Bucket: stack.resolve(bucket.bucketName), S3Key: 'ObjectKey', }, CompatibleRuntimes: ['nodejs8.10'] @@ -46,12 +46,12 @@ export = testCase({ // THEN expect(stack).to(haveResource('AWS::Lambda::LayerVersionPermission', { Action: 'lambda:GetLayerVersion', - LayerVersionArn: stack.node.resolve(layer.layerVersionArn), + LayerVersionArn: stack.resolve(layer.layerVersionArn), Principal: '123456789012', })); expect(stack).to(haveResource('AWS::Lambda::LayerVersionPermission', { Action: 'lambda:GetLayerVersion', - LayerVersionArn: stack.node.resolve(layer.layerVersionArn), + LayerVersionArn: stack.resolve(layer.layerVersionArn), Principal: '*', OrganizationId: 'o-123456' })); 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 1230a6822e87c..7bda668a1739b 100644 --- a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts +++ b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts @@ -1,6 +1,6 @@ import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); -import { Construct } from '@aws-cdk/cdk'; +import { Construct, Stack } from '@aws-cdk/cdk'; import { ILogGroup } from './log-group'; import { CfnDestination } from './logs.generated'; import { ILogSubscriptionDestination, LogSubscriptionDestinationConfig } from './subscription-filter'; @@ -92,13 +92,13 @@ export class CrossAccountDestination extends cdk.Construct implements ILogSubscr */ private generateUniqueName(): string { // Combination of stack name and LogicalID, which are guaranteed to be unique. - return this.node.stack.name + '-' + this.resource.logicalId; + return Stack.of(this).name + '-' + this.resource.logicalId; } /** * Return a stringified JSON version of the PolicyDocument */ private lazyStringifiedPolicyDocument(): string { - return new cdk.Token(() => this.policyDocument.isEmpty ? '' : this.node.stringifyJson(this.policyDocument)).toString(); + return new cdk.Token(() => this.policyDocument.isEmpty ? '' : Stack.of(this).toJsonString(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 9aebfa8feaf3a..6881d573ecc0f 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-group.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-group.ts @@ -1,6 +1,6 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import iam = require('@aws-cdk/aws-iam'); -import { applyRemovalPolicy, Construct, IResource, RemovalPolicy, Resource } from '@aws-cdk/cdk'; +import { applyRemovalPolicy, Construct, IResource, RemovalPolicy, Resource, Stack } from '@aws-cdk/cdk'; import { LogStream } from './log-stream'; import { CfnLogGroup } from './logs.generated'; import { MetricFilter } from './metric-filter'; @@ -311,7 +311,7 @@ export class LogGroup extends LogGroupBase { public static fromLogGroupArn(scope: Construct, id: string, logGroupArn: string): ILogGroup { class Import extends LogGroupBase { public readonly logGroupArn = logGroupArn; - public readonly logGroupName = scope.node.stack.parseArn(logGroupArn, ':').resourceName!; + public readonly logGroupName = Stack.of(scope).parseArn(logGroupArn, ':').resourceName!; } return new Import(scope, id); diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index acfb4e095801e..c367d9a7bd449 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -5,7 +5,7 @@ import kms = require('@aws-cdk/aws-kms'); import lambda = require('@aws-cdk/aws-lambda'); import logs = require('@aws-cdk/aws-logs'); import secretsmanager = require('@aws-cdk/aws-secretsmanager'); -import { Construct, DeletionPolicy, IResource, Resource, SecretValue, Token } from '@aws-cdk/cdk'; +import { Construct, DeletionPolicy, IResource, Resource, SecretValue, Stack, Token } from '@aws-cdk/cdk'; import { DatabaseSecret } from './database-secret'; import { Endpoint } from './endpoint'; import { IOptionGroup} from './option-group'; @@ -130,7 +130,7 @@ export abstract class DatabaseInstanceBase extends Resource implements IDatabase * The instance arn. */ public get instanceArn(): string { - return this.node.stack.formatArn({ + return Stack.of(this).formatArn({ service: 'rds', resource: 'db', sep: ':', @@ -487,7 +487,7 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData if (props.monitoringInterval) { monitoringRole = new iam.Role(this, 'MonitoringRole', { assumedBy: new iam.ServicePrincipal('monitoring.rds.amazonaws.com'), - managedPolicyArns: [this.node.stack.formatArn({ + managedPolicyArns: [Stack.of(this).formatArn({ service: 'iam', region: '', account: 'aws', diff --git a/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts b/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts index 2131992d69382..b17ce3903cbb6 100644 --- a/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts +++ b/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts @@ -2,7 +2,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import lambda = require('@aws-cdk/aws-lambda'); import serverless = require('@aws-cdk/aws-sam'); import secretsmanager = require('@aws-cdk/aws-secretsmanager'); -import { Construct } from '@aws-cdk/cdk'; +import { Construct, Stack } from '@aws-cdk/cdk'; /** * A secret rotation serverless application. @@ -113,7 +113,7 @@ export class SecretRotation extends Construct { const application = new serverless.CfnApplication(this, 'Resource', { location: props.application, parameters: { - endpoint: `https://secretsmanager.${this.node.stack.region}.${this.node.stack.urlSuffix}`, + endpoint: `https://secretsmanager.${Stack.of(this).region}.${Stack.of(this).urlSuffix}`, functionName: rotationFunctionName, vpcSecurityGroupIds: securityGroup.securityGroupId, vpcSubnetIds: subnetIds.join(',') @@ -121,7 +121,7 @@ export class SecretRotation extends Construct { }); // Dummy import to reference this function in the rotation schedule - const rotationLambda = lambda.Function.fromFunctionArn(this, 'RotationLambda', this.node.stack.formatArn({ + const rotationLambda = lambda.Function.fromFunctionArn(this, 'RotationLambda', Stack.of(this).formatArn({ service: 'lambda', resource: 'function', sep: ':', @@ -133,7 +133,7 @@ export class SecretRotation extends Construct { const permission = new lambda.CfnPermission(this, 'Permission', { action: 'lambda:InvokeFunction', functionName: rotationFunctionName, - principal: `secretsmanager.${this.node.stack.urlSuffix}` + principal: `secretsmanager.${Stack.of(this).urlSuffix}` }); permission.node.addDependency(application); // Add permission after application is deployed diff --git a/packages/@aws-cdk/aws-rds/test/test.instance.ts b/packages/@aws-cdk/aws-rds/test/test.instance.ts index cfd1287ba49d2..441fa691e6151 100644 --- a/packages/@aws-cdk/aws-rds/test/test.instance.ts +++ b/packages/@aws-cdk/aws-rds/test/test.instance.ts @@ -367,7 +367,7 @@ export = { }); // THEN - test.deepEqual(stack.node.resolve(instance.metricCPUUtilization()), { + test.deepEqual(stack.resolve(instance.metricCPUUtilization()), { dimensions: { DBInstanceIdentifier: { Ref: 'InstanceC1063A87' } }, namespace: 'AWS/RDS', metricName: 'CPUUtilization', 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 31c0eeb8f6185..c9cb57a4e4aee 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 @@ -36,7 +36,7 @@ export = { // WHEN const provider = new HostedZoneProvider(stack, filter); - const zoneProps = stack.node.resolve(provider.findHostedZone()); + const zoneProps = stack.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 f2efdd0b53ad9..6e1dd3286abe3 100644 --- a/packages/@aws-cdk/aws-route53/test/test.route53.ts +++ b/packages/@aws-cdk/aws-route53/test/test.route53.ts @@ -188,8 +188,8 @@ export = { expect(stack).to(haveResource('AWS::Route53::RecordSet', { Type: 'NS', Name: 'sub.top.test.', - HostedZoneId: zone.node.resolve(zone.hostedZoneId), - ResourceRecords: zone.node.resolve(delegate.hostedZoneNameServers), + HostedZoneId: stack.resolve(zone.hostedZoneId), + ResourceRecords: stack.resolve(delegate.hostedZoneNameServers), TTL: '1337', })); test.done(); diff --git a/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts b/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts index 38e8e4e581fd5..e3206b82b10dd 100644 --- a/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts +++ b/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts @@ -1,7 +1,7 @@ import iam = require('@aws-cdk/aws-iam'); import lambda = require('@aws-cdk/aws-lambda'); import s3 = require('@aws-cdk/aws-s3'); -import { CfnResource, Construct } from '@aws-cdk/cdk'; +import { CfnResource, Construct, Stack } from '@aws-cdk/cdk'; /** * Use a Lambda function as a bucket notification destination @@ -15,7 +15,7 @@ export class LambdaDestination implements s3.IBucketNotificationDestination { if (this.fn.node.tryFindChild(permissionId) === undefined) { this.fn.addPermission(permissionId, { - sourceAccount: bucket.node.stack.accountId, + sourceAccount: Stack.of(bucket).accountId, principal: new iam.ServicePrincipal('s3.amazonaws.com'), sourceArn: bucket.bucketArn }); diff --git a/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts b/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts index a04ed46b3e405..9dd6b4c793305 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts @@ -3,7 +3,7 @@ import '@aws-cdk/assert/jest'; import s3 = require('@aws-cdk/aws-s3'); import sns = require('@aws-cdk/aws-sns'); import cdk = require('@aws-cdk/cdk'); -import { Stack } from '@aws-cdk/cdk'; +import { ConstructNode, Stack } from '@aws-cdk/cdk'; import s3n = require('../lib'); // tslint:disable:object-literal-key-quotes @@ -39,7 +39,7 @@ test('when notification are added, a custom resource is provisioned + a lambda h test('when notification are added, you can tag the lambda', () => { const stack = new cdk.Stack(); - stack.node.apply(new cdk.Tag('Lambda', 'AreTagged')); + stack.node.applyAspect(new cdk.Tag('Lambda', 'AreTagged')); const bucket = new s3.Bucket(stack, 'MyBucket'); @@ -290,7 +290,8 @@ test('a notification destination can specify a set of dependencies that must be bucket.addObjectCreatedNotification(dest); - stack.node.prepareTree(); + ConstructNode.prepare(stack.node); + expect(SynthUtils.synthesize(stack).template.Resources.BucketNotifications8F2E257D).toEqual({ Type: 'Custom::S3BucketNotifications', Properties: { diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 496bfa1ddadb3..a0cca4a678f3d 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -1,7 +1,7 @@ import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); -import { applyRemovalPolicy, Construct, IResource, RemovalPolicy, Resource, Token } from '@aws-cdk/cdk'; +import { applyRemovalPolicy, Construct, IResource, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/cdk'; import { EOL } from 'os'; import { BucketPolicy } from './bucket-policy'; import { IBucketNotificationDestination } from './destination'; @@ -361,7 +361,8 @@ abstract class BucketBase extends Resource implements IBucket { * @returns an ObjectS3Url token */ public urlForObject(key?: string): string { - const components = [ `https://s3.${this.node.stack.region}.${this.node.stack.urlSuffix}/${this.bucketName}` ]; + const stack = Stack.of(this); + const components = [ `https://s3.${stack.region}.${stack.urlSuffix}/${this.bucketName}` ]; if (key) { // trim prepending '/' if (typeof key === 'string' && key.startsWith('/')) { @@ -717,8 +718,9 @@ export class Bucket extends BucketBase { * `bucket.export()` or manually created. */ public static fromBucketAttributes(scope: Construct, id: string, attrs: BucketAttributes): IBucket { - const region = scope.node.stack.region; - const urlSuffix = scope.node.stack.urlSuffix; + const stack = Stack.of(scope); + const region = stack.region; + const urlSuffix = stack.urlSuffix; const bucketName = parseBucketName(scope, attrs); if (!bucketName) { 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 9415d6e136e0b..45cb2b839a661 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 @@ -1,5 +1,6 @@ import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); +import { Stack } from '@aws-cdk/cdk'; /** * A Lambda-based custom resource handler that provisions S3 bucket @@ -26,7 +27,7 @@ export class NotificationsResourceHandler extends cdk.Construct { * @returns The ARN of the custom resource lambda function. */ public static singleton(context: cdk.Construct) { - const root = context.node.stack; + const root = Stack.of(context); // well-known logical id to ensure stack singletonity const logicalId = 'BucketNotificationsHandler050a0587b7544547bf325f094a3db834'; @@ -50,7 +51,7 @@ export class NotificationsResourceHandler extends cdk.Construct { const role = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), managedPolicyArns: [ - this.node.stack.formatArn({ + Stack.of(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 5b0674b3a8387..50582e96c67de 100644 --- a/packages/@aws-cdk/aws-s3/lib/util.ts +++ b/packages/@aws-cdk/aws-s3/lib/util.ts @@ -1,4 +1,5 @@ import cdk = require('@aws-cdk/cdk'); +import { Stack } from '@aws-cdk/cdk'; import { BucketAttributes } from './bucket'; export function parseBucketArn(construct: cdk.IConstruct, props: BucketAttributes): string { @@ -9,7 +10,7 @@ export function parseBucketArn(construct: cdk.IConstruct, props: BucketAttribute } if (props.bucketName) { - return construct.node.stack.formatArn({ + return Stack.of(construct).formatArn({ // S3 Bucket names are globally unique in a partition, // and so their ARNs have empty region and account components region: '', @@ -29,20 +30,9 @@ export function parseBucketName(construct: cdk.IConstruct, props: BucketAttribut return props.bucketName; } - // if we have a string arn, we can extract the bucket name from it. + // extract bucket name from bucket arn if (props.bucketArn) { - - const resolved = construct.node.resolve(props.bucketArn); - if (typeof(resolved) === 'string') { - const components = construct.node.stack.parseArn(resolved); - if (components.service !== 's3') { - throw new Error('Invalid ARN. Expecting "s3" service:' + resolved); - } - if (components.resourceName) { - throw new Error(`Bucket ARN must not contain a path`); - } - return components.resource; - } + return Stack.of(construct).parseArn(props.bucketArn).resource; } // no bucket name is okay since it's optional. diff --git a/packages/@aws-cdk/aws-s3/test/test.bucket.ts b/packages/@aws-cdk/aws-s3/test/test.bucket.ts index 8bdae25647490..b7ca523d4b3e2 100644 --- a/packages/@aws-cdk/aws-s3/test/test.bucket.ts +++ b/packages/@aws-cdk/aws-s3/test/test.bucket.ts @@ -468,7 +468,7 @@ export = { const x = new iam.PolicyStatement().addResource(bucket.bucketArn).addAction('s3:ListBucket'); - test.deepEqual(bucket.node.resolve(x), { + test.deepEqual(stack.resolve(x), { Action: 's3:ListBucket', Effect: 'Allow', Resource: { 'Fn::GetAtt': ['MyBucketF68F3FF0', 'Arn'] } @@ -484,7 +484,7 @@ export = { const p = new iam.PolicyStatement().addResource(bucket.arnForObjects('hello/world')).addAction('s3:GetObject'); - test.deepEqual(bucket.node.resolve(p), { + test.deepEqual(stack.resolve(p), { Action: 's3:GetObject', Effect: 'Allow', Resource: { @@ -510,7 +510,7 @@ export = { const resource = bucket.arnForObjects(`home/${team.groupName}/${user.userName}/*`); const p = new iam.PolicyStatement().addResource(resource).addAction('s3:GetObject'); - test.deepEqual(bucket.node.resolve(p), { + test.deepEqual(stack.resolve(p), { Action: 's3:GetObject', Effect: 'Allow', Resource: { @@ -562,14 +562,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(bucket.node.resolve(p), { + test.deepEqual(stack.resolve(p), { Action: 's3:ListBucket', Effect: 'Allow', Resource: 'arn:aws:s3:::my-bucket' }); test.deepEqual(bucket.bucketArn, bucketArn); - test.deepEqual(bucket.node.resolve(bucket.bucketName), 'my-bucket'); + test.deepEqual(stack.resolve(bucket.bucketName), 'my-bucket'); test.deepEqual(SynthUtils.synthesize(stack).template, {}, 'the ref is not a real resource'); test.done(); @@ -1338,7 +1338,7 @@ export = { const bucket = new s3.Bucket(stack, 'Website', { websiteIndexDocument: 'index.html' }); - test.deepEqual(bucket.node.resolve(bucket.bucketWebsiteUrl), { 'Fn::GetAtt': ['Website32962D0B', 'WebsiteURL'] }); + test.deepEqual(stack.resolve(bucket.bucketWebsiteUrl), { 'Fn::GetAtt': ['Website32962D0B', 'WebsiteURL'] }); test.done(); } }, @@ -1365,7 +1365,7 @@ export = { // THEN test.deepEqual(bucket.bucketName, 'my-bucket-name'); - test.deepEqual(stack.node.resolve(bucket.bucketArn), { + test.deepEqual(stack.resolve(bucket.bucketArn), { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':s3:::my-bucket-name']] }); 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 11b87854e3551..6ece7e06b0058 100644 --- a/packages/@aws-cdk/aws-s3/test/test.util.ts +++ b/packages/@aws-cdk/aws-s3/test/test.util.ts @@ -14,11 +14,13 @@ export = { 'produce arn from bucket name'(test: Test) { const stack = new cdk.Stack(); const bucketName = 'hello'; - test.deepEqual(stack.node.resolve(parseBucketArn(stack, { bucketName })), { 'Fn::Join': - [ '', - [ 'arn:', - { Ref: 'AWS::Partition' }, - ':s3:::hello' ] ] }); + test.deepEqual(stack.resolve(parseBucketArn(stack, { bucketName })), { + 'Fn::Join': + ['', + ['arn:', + { Ref: 'AWS::Partition' }, + ':s3:::hello']] + }); test.done(); }, @@ -34,28 +36,52 @@ export = { 'explicit name'(test: Test) { const stack = new cdk.Stack(); const bucketName = 'foo'; - test.deepEqual(stack.node.resolve(parseBucketName(stack, { bucketName })), 'foo'); + test.deepEqual(stack.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(stack.node.resolve(parseBucketName(stack, { bucketArn })), 'my-bucket'); + test.deepEqual(stack.resolve(parseBucketName(stack, { bucketArn })), 'my-bucket'); test.done(); }, - 'undefined if cannot extract name from a non-string arn'(test: Test) { + 'can parse bucket name even if it contains a token'(test: Test) { 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(stack, { bucketArn }), /Invalid ARN/); + test.deepEqual(stack.resolve(parseBucketName(stack, { bucketArn })), { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "/", + { + "Fn::Select": [ + 5, + { + "Fn::Split": [ + ":", + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + Ref: "my-bucket" + } + ] + ] + } + ] + } + ] + } + ] + } + ] + }); test.done(); }, @@ -65,12 +91,5 @@ export = { 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(stack, { bucketArn }), /Bucket ARN must not contain a path/); - test.done(); - } }, }; diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index 09ac282e02807..f4cb1ad90aa3b 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -1,6 +1,6 @@ import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); -import { Construct, IResource, Resource, SecretValue } from '@aws-cdk/cdk'; +import { Construct, IResource, Resource, SecretValue, Stack } from '@aws-cdk/cdk'; import { RotationSchedule, RotationScheduleOptions } from './rotation-schedule'; import secretsmanager = require('./secretsmanager.generated'); @@ -121,7 +121,7 @@ abstract class SecretBase extends Resource implements ISecret { if (this.encryptionKey) { // @see https://docs.aws.amazon.com/fr_fr/kms/latest/developerguide/services-secrets-manager.html this.encryptionKey.grantDecrypt( - new kms.ViaServicePrincipal(`secretsmanager.${this.node.stack.region}.amazonaws.com`, grantee.grantPrincipal) + new kms.ViaServicePrincipal(`secretsmanager.${Stack.of(this).region}.amazonaws.com`, grantee.grantPrincipal) ); } diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts index 94121ebb2591e..ee22c2b55cbc9 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts @@ -303,8 +303,8 @@ export = { // THEN test.equals(secret.secretArn, secretArn); test.same(secret.encryptionKey, encryptionKey); - test.deepEqual(stack.node.resolve(secret.secretValue), '{{resolve:secretsmanager:arn::of::a::secret:SecretString:::}}'); - test.deepEqual(stack.node.resolve(secret.secretJsonValue('password')), '{{resolve:secretsmanager:arn::of::a::secret:SecretString:password::}}'); + test.deepEqual(stack.resolve(secret.secretValue), '{{resolve:secretsmanager:arn::of::a::secret:SecretString:::}}'); + test.deepEqual(stack.resolve(secret.secretJsonValue('password')), '{{resolve:secretsmanager:arn::of::a::secret:SecretString:password::}}'); test.done(); }, @@ -403,7 +403,7 @@ export = { const value = SecretValue.secretsManager('my-secret-arn', { jsonField: 'password' }); // THEN - test.deepEqual(stack.node.resolve(imported), stack.node.resolve(value)); + test.deepEqual(stack.resolve(imported), stack.resolve(value)); test.done(); } }; diff --git a/packages/@aws-cdk/aws-sns/lib/topic.ts b/packages/@aws-cdk/aws-sns/lib/topic.ts index 6ee2d498daa80..3184167cc0cc3 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic.ts @@ -1,4 +1,4 @@ -import { Construct } from '@aws-cdk/cdk'; +import { Construct, Stack } from '@aws-cdk/cdk'; import { CfnTopic } from './sns.generated'; import { ITopic, TopicBase } from './topic-base'; @@ -33,7 +33,7 @@ export class Topic extends TopicBase { public static fromTopicArn(scope: Construct, id: string, topicArn: string): ITopic { class Import extends TopicBase { public readonly topicArn = topicArn; - public readonly topicName = scope.node.stack.parseArn(topicArn).resource; + public readonly topicName = Stack.of(scope).parseArn(topicArn).resource; protected autoCreatePolicy: boolean = false; } diff --git a/packages/@aws-cdk/aws-sns/test/test.sns.ts b/packages/@aws-cdk/aws-sns/test/test.sns.ts index 9eb263ade1d52..bf7a5363fdf60 100644 --- a/packages/@aws-cdk/aws-sns/test/test.sns.ts +++ b/packages/@aws-cdk/aws-sns/test/test.sns.ts @@ -629,7 +629,7 @@ export = { { "Action": "sns:Publish", "Effect": "Allow", - "Resource": stack.node.resolve(topic.topicArn) + "Resource": stack.resolve(topic.topicArn) } ], } @@ -703,7 +703,7 @@ export = { const topic = new sns.Topic(stack, 'Topic'); // THEN - test.deepEqual(stack.node.resolve(topic.metricNumberOfMessagesPublished()), { + test.deepEqual(stack.resolve(topic.metricNumberOfMessagesPublished()), { dimensions: {TopicName: { 'Fn::GetAtt': [ 'TopicBFC7AF6E', 'TopicName' ] }}, namespace: 'AWS/SNS', metricName: 'NumberOfMessagesPublished', @@ -711,7 +711,7 @@ export = { statistic: 'Sum' }); - test.deepEqual(stack.node.resolve(topic.metricPublishSize()), { + test.deepEqual(stack.resolve(topic.metricPublishSize()), { dimensions: {TopicName: { 'Fn::GetAtt': [ 'TopicBFC7AF6E', 'TopicName' ] }}, namespace: 'AWS/SNS', metricName: 'PublishSize', diff --git a/packages/@aws-cdk/aws-sqs/lib/queue.ts b/packages/@aws-cdk/aws-sqs/lib/queue.ts index 983cecec85e21..f34d62d002a16 100644 --- a/packages/@aws-cdk/aws-sqs/lib/queue.ts +++ b/packages/@aws-cdk/aws-sqs/lib/queue.ts @@ -1,5 +1,5 @@ import kms = require('@aws-cdk/aws-kms'); -import { Construct } from '@aws-cdk/cdk'; +import { Construct, Stack } from '@aws-cdk/cdk'; import { IQueue, QueueAttributes, QueueBase } from './queue-base'; import { CfnQueue } from './sqs.generated'; import { validateProps } from './validate-props'; @@ -189,7 +189,7 @@ export class Queue extends QueueBase { * Import an existing queue */ public static fromQueueAttributes(scope: Construct, id: string, attrs: QueueAttributes): IQueue { - const stack = scope.node.stack; + const stack = Stack.of(scope); const queueName = attrs.queueName || stack.parseArn(attrs.queueArn).resource; const queueUrl = attrs.queueUrl || `https://sqs.${stack.region}.${stack.urlSuffix}/${stack.accountId}/${queueName}`; diff --git a/packages/@aws-cdk/aws-sqs/test/test.sqs.ts b/packages/@aws-cdk/aws-sqs/test/test.sqs.ts index ed73b22e769e3..5d25587b5385e 100644 --- a/packages/@aws-cdk/aws-sqs/test/test.sqs.ts +++ b/packages/@aws-cdk/aws-sqs/test/test.sqs.ts @@ -100,10 +100,10 @@ export = { // THEN // "import" returns an IQueue bound to `Fn::ImportValue`s. - test.deepEqual(stack.node.resolve(imports.queueArn), 'arn:aws:sqs:us-east-1:123456789012:queue1'); - test.deepEqual(stack.node.resolve(imports.queueUrl), { 'Fn::Join': + test.deepEqual(stack.resolve(imports.queueArn), 'arn:aws:sqs:us-east-1:123456789012:queue1'); + test.deepEqual(stack.resolve(imports.queueUrl), { 'Fn::Join': [ '', [ 'https://sqs.', { Ref: 'AWS::Region' }, '.', { Ref: 'AWS::URLSuffix' }, '/', { Ref: 'AWS::AccountId' }, '/queue1' ] ] }); - test.deepEqual(stack.node.resolve(imports.queueName), 'queue1'); + test.deepEqual(stack.resolve(imports.queueName), 'queue1'); test.done(); }, @@ -281,7 +281,7 @@ export = { const topic = new Queue(stack, 'Queue'); // THEN - test.deepEqual(stack.node.resolve(topic.metricNumberOfMessagesSent()), { + test.deepEqual(stack.resolve(topic.metricNumberOfMessagesSent()), { dimensions: {QueueName: { 'Fn::GetAtt': [ 'Queue4A7E3555', 'QueueName' ] }}, namespace: 'AWS/SQS', metricName: 'NumberOfMessagesSent', @@ -289,7 +289,7 @@ export = { statistic: 'Sum' }); - test.deepEqual(stack.node.resolve(topic.metricSentMessageSize()), { + test.deepEqual(stack.resolve(topic.metricSentMessageSize()), { dimensions: {QueueName: { 'Fn::GetAtt': [ 'Queue4A7E3555', 'QueueName' ] }}, namespace: 'AWS/SQS', metricName: 'SentMessageSize', diff --git a/packages/@aws-cdk/aws-ssm/lib/parameter.ts b/packages/@aws-cdk/aws-ssm/lib/parameter.ts index bfb38b7a118fc..27b4d44aaffa6 100644 --- a/packages/@aws-cdk/aws-ssm/lib/parameter.ts +++ b/packages/@aws-cdk/aws-ssm/lib/parameter.ts @@ -1,5 +1,5 @@ import iam = require('@aws-cdk/aws-iam'); -import { CfnDynamicReference, CfnDynamicReferenceService, Construct, Fn, IResource, Resource, Token } from '@aws-cdk/cdk'; +import { CfnDynamicReference, CfnDynamicReferenceService, Construct, Fn, IResource, Resource, Stack, Token } from '@aws-cdk/cdk'; import ssm = require('./ssm.generated'); /** @@ -119,7 +119,7 @@ abstract class ParameterBase extends Resource implements IParameter { public abstract readonly parameterType: string; public get parameterArn(): string { - return this.node.stack.formatArn({ + return Stack.of(this).formatArn({ service: 'ssm', resource: 'parameter', sep: '', // Sep is empty because this.parameterName starts with a / already! 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 1ee43b2df1f0b..05b1fb9759c35 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 @@ -15,7 +15,7 @@ export = { }); // THEN - test.equal(ref.node.resolve(ref.stringValue), '{{resolve:ssm:/some/key:123}}'); + test.equal(stack.resolve(ref.stringValue), '{{resolve:ssm:/some/key:123}}'); test.done(); }, @@ -39,7 +39,7 @@ export = { } }); - test.deepEqual(ref.node.resolve(ref.stringValue), { Ref: 'RefParameter407AF5C8' }); + test.deepEqual(stack.resolve(ref.stringValue), { Ref: 'RefParameter407AF5C8' }); test.done(); }, @@ -55,7 +55,7 @@ export = { }); // THEN - test.equal(stack.node.resolve(ref), '{{resolve:ssm-secure:/some/key:123}}'); + test.equal(stack.resolve(ref), '{{resolve:ssm-secure:/some/key:123}}'); test.done(); }, diff --git a/packages/@aws-cdk/aws-ssm/test/test.parameter.ts b/packages/@aws-cdk/aws-ssm/test/test.parameter.ts index 51c1962a5162e..31c48ca238877 100644 --- a/packages/@aws-cdk/aws-ssm/test/test.parameter.ts +++ b/packages/@aws-cdk/aws-ssm/test/test.parameter.ts @@ -110,7 +110,7 @@ export = { const param = new ssm.StringParameter(stack, 'Parameter', { stringValue: 'Foo' }); // THEN - test.deepEqual(param.node.resolve(param.parameterArn), { + test.deepEqual(stack.resolve(param.parameterArn), { 'Fn::Join': ['', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -133,7 +133,7 @@ export = { const param = ssm.StringParameter.fromStringParameterName(stack, 'MyParamName', 'MyParamName'); // THEN - test.deepEqual(stack.node.resolve(param.parameterArn), { + test.deepEqual(stack.resolve(param.parameterArn), { 'Fn::Join': [ '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -143,9 +143,9 @@ export = { { Ref: 'AWS::AccountId' }, ':parameterMyParamName' ] ] }); - test.deepEqual(stack.node.resolve(param.parameterName), 'MyParamName'); - test.deepEqual(stack.node.resolve(param.parameterType), 'String'); - test.deepEqual(stack.node.resolve(param.stringValue), '{{resolve:ssm:MyParamName}}'); + test.deepEqual(stack.resolve(param.parameterName), 'MyParamName'); + test.deepEqual(stack.resolve(param.parameterType), 'String'); + test.deepEqual(stack.resolve(param.stringValue), '{{resolve:ssm:MyParamName}}'); test.done(); }, @@ -157,7 +157,7 @@ export = { const param = ssm.StringListParameter.fromStringListParameterName(stack, 'MyParamName', 'MyParamName'); // THEN - test.deepEqual(stack.node.resolve(param.parameterArn), { + test.deepEqual(stack.resolve(param.parameterArn), { 'Fn::Join': [ '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -167,9 +167,9 @@ export = { { Ref: 'AWS::AccountId' }, ':parameterMyParamName' ] ] }); - test.deepEqual(stack.node.resolve(param.parameterName), 'MyParamName'); - test.deepEqual(stack.node.resolve(param.parameterType), 'StringList'); - test.deepEqual(stack.node.resolve(param.stringListValue), { 'Fn::Split': [ ',', '{{resolve:ssm:MyParamName}}' ] }); + test.deepEqual(stack.resolve(param.parameterName), 'MyParamName'); + test.deepEqual(stack.resolve(param.parameterType), 'StringList'); + test.deepEqual(stack.resolve(param.stringListValue), { 'Fn::Split': [ ',', '{{resolve:ssm:MyParamName}}' ] }); test.done(); } }; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-task-base.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-task-base.ts index ef09344d63214..9702081f36e0b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-task-base.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-task-base.ts @@ -3,6 +3,7 @@ import ecs = require('@aws-cdk/aws-ecs'); import iam = require('@aws-cdk/aws-iam'); import sfn = require('@aws-cdk/aws-stepfunctions'); import cdk = require('@aws-cdk/cdk'); +import { Stack } from '@aws-cdk/cdk'; import { ContainerOverride } from './run-ecs-task-base-types'; /** @@ -117,7 +118,7 @@ export class EcsRunTaskBase implements ec2.IConnectable, sfn.IStepFunctionsTask } private makePolicyStatements(task: sfn.Task): iam.PolicyStatement[] { - const stack = task.node.stack; + const stack = Stack.of(task); // https://docs.aws.amazon.com/step-functions/latest/dg/ecs-iam.html const policyStatements = [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts index b1edbbd9a1de7..ef5f6c817ec15 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts @@ -74,7 +74,7 @@ test('Running a Fargate Task', () => { }); // THEN - expect(stack.node.resolve(runTask.toStateJson())).toEqual({ + expect(stack.resolve(runTask.toStateJson())).toEqual({ End: true, Parameters: { Cluster: {"Fn::GetAtt": ["ClusterEB0386A7", "Arn"]}, @@ -172,7 +172,7 @@ test('Running an EC2 Task with bridge network', () => { }); // THEN - expect(stack.node.resolve(runTask.toStateJson())).toEqual({ + expect(stack.resolve(runTask.toStateJson())).toEqual({ End: true, Parameters: { Cluster: {"Fn::GetAtt": ["ClusterEB0386A7", "Arn"]}, @@ -260,7 +260,7 @@ test('Running an EC2 Task with placement strategies', () => { }); // THEN - expect(stack.node.resolve(runTask.toStateJson())).toEqual({ + expect(stack.resolve(runTask.toStateJson())).toEqual({ End: true, Parameters: { Cluster: {"Fn::GetAtt": ["ClusterEB0386A7", "Arn"]}, @@ -306,7 +306,7 @@ test('Running an EC2 Task with overridden number values', () => { const runTask = new sfn.Task(stack, 'Run', { task: ec2Task }); // THEN - expect(stack.node.resolve(runTask.toStateJson())).toEqual({ + expect(stack.resolve(runTask.toStateJson())).toEqual({ End: true, Parameters: { Cluster: {"Fn::GetAtt": ["ClusterEB0386A7", "Arn"]}, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts index ad846441a73d6..5dbfcd9cda4ae 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts @@ -14,7 +14,7 @@ test('publish to SNS', () => { }) }); // THEN - expect(stack.node.resolve(pub.toStateJson())).toEqual({ + expect(stack.resolve(pub.toStateJson())).toEqual({ Type: 'Task', Resource: 'arn:aws:states:::sns:publish', End: true, @@ -38,7 +38,7 @@ test('publish JSON to SNS', () => { }) }); // THEN - expect(stack.node.resolve(pub.toStateJson())).toEqual({ + expect(stack.resolve(pub.toStateJson())).toEqual({ Type: 'Task', Resource: 'arn:aws:states:::sns:publish', End: true, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts index 6bd53350058fa..2407e3c6a2d41 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts @@ -20,7 +20,7 @@ test('publish to queue', () => { }) }); // THEN - expect(stack.node.resolve(pub.toStateJson())).toEqual({ + expect(stack.resolve(pub.toStateJson())).toEqual({ Type: 'Task', Resource: 'arn:aws:states:::sqs:sendMessage', End: true, @@ -41,7 +41,7 @@ test('message body can come from state', () => { }); // THEN - expect(stack.node.resolve(pub.toStateJson())).toEqual({ + expect(stack.resolve(pub.toStateJson())).toEqual({ Type: 'Task', Resource: 'arn:aws:states:::sqs:sendMessage', End: true, @@ -64,7 +64,7 @@ test('message body can be an object', () => { }); // THEN - expect(stack.node.resolve(pub.toStateJson())).toEqual({ + expect(stack.resolve(pub.toStateJson())).toEqual({ Type: 'Task', Resource: 'arn:aws:states:::sqs:sendMessage', End: true, @@ -89,7 +89,7 @@ test('message body object can contain references', () => { }); // THEN - expect(stack.node.resolve(pub.toStateJson())).toEqual({ + expect(stack.resolve(pub.toStateJson())).toEqual({ Type: 'Task', Resource: 'arn:aws:states:::sqs:sendMessage', End: true, diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts index 8ab47f29e9223..aefb94b000325 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts @@ -1,5 +1,5 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); -import { Construct, IResource, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, Resource, Stack } from '@aws-cdk/cdk'; import { CfnActivity } from './stepfunctions.generated'; export interface ActivityProps { @@ -22,7 +22,7 @@ export class Activity extends Resource implements IActivity { class Imported extends Construct implements IActivity { public get activityArn() { return activityArn; } public get activityName() { - return this.node.stack.parseArn(activityArn, ':').resourceName || ''; + return Stack.of(this).parseArn(activityArn, ':').resourceName || ''; } } @@ -33,7 +33,7 @@ export class Activity extends Resource implements IActivity { * Construct an Activity from an existing Activity Name */ public static fromActivityName(scope: Construct, id: string, activityName: string): IActivity { - return Activity.fromActivityArn(scope, id, scope.node.stack.formatArn({ + return Activity.fromActivityArn(scope, id, Stack.of(scope).formatArn({ service: 'states', resource: 'activity', resourceName: activityName, diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts index 30dee9d4a9e0b..8bf24d2ea79ed 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts @@ -1,6 +1,6 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import iam = require('@aws-cdk/aws-iam'); -import { Construct, IResource, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, Resource, Stack } from '@aws-cdk/cdk'; import { StateGraph } from './state-graph'; import { CfnStateMachine } from './stepfunctions.generated'; import { IChainable } from './types'; @@ -71,7 +71,7 @@ export class StateMachine extends Resource implements IStateMachine { super(scope, id); this.role = props.role || new iam.Role(this, 'Role', { - assumedBy: new iam.ServicePrincipal(`states.${this.node.stack.region}.amazonaws.com`), + assumedBy: new iam.ServicePrincipal(`states.${Stack.of(this).region}.amazonaws.com`), }); const graph = new StateGraph(props.definition.startState, `State Machine ${id} definition`); @@ -80,7 +80,7 @@ export class StateMachine extends Resource implements IStateMachine { const resource = new CfnStateMachine(this, 'Resource', { stateMachineName: props.stateMachineName, roleArn: this.role.roleArn, - definitionString: this.node.stringifyJson(graph.toGraphJson()), + definitionString: Stack.of(this).toJsonString(graph.toGraphJson()), }); for (const statement of graph.policyStatements) { diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts index 02cd600f42d21..5b45bdee74467 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts @@ -32,13 +32,13 @@ export = { namespace: 'AWS/States', dimensions: { ActivityArn: { Ref: 'Activity04690B0A' }}, }; - test.deepEqual(stack.node.resolve(activity.metricRunTime()), { + test.deepEqual(stack.resolve(activity.metricRunTime()), { ...sharedMetric, metricName: 'ActivityRunTime', statistic: 'Average' }); - test.deepEqual(stack.node.resolve(activity.metricFailed()), { + test.deepEqual(stack.resolve(activity.metricFailed()), { ...sharedMetric, metricName: 'ActivitiesFailed', statistic: 'Sum' 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 4cbd6dbd3e472..edc794c6f126b 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts @@ -722,7 +722,7 @@ class SimpleChain extends stepfunctions.StateMachineFragment { } function render(sm: stepfunctions.IChainable) { - return new cdk.Stack().node.resolve(new stepfunctions.StateGraph(sm.startState, 'Test Graph').toGraphJson()); + return new cdk.Stack().resolve(new stepfunctions.StateGraph(sm.startState, 'Test Graph').toGraphJson()); } class FakeTask implements IStepFunctionsTask { diff --git a/packages/@aws-cdk/cdk/lib/app.ts b/packages/@aws-cdk/cdk/lib/app.ts index feb26482cffc9..8c52b1b84539f 100644 --- a/packages/@aws-cdk/cdk/lib/app.ts +++ b/packages/@aws-cdk/cdk/lib/app.ts @@ -27,7 +27,7 @@ export interface AppProps { readonly outdir?: string; /** - * Include stack traces in construct metadata entries. + * Include construct creation stack trace in the `aws:cdk:trace` metadata key of all constructs. * @default true stack traces are included unless `aws:cdk:disable-stack-trace` is set in the context. */ readonly stackTraces?: boolean; @@ -98,7 +98,7 @@ export class App extends Construct { } // both are reverse logic - this.runtimeInfo = this.node.getContext(cxapi.DISABLE_VERSION_REPORTING) ? false : true; + this.runtimeInfo = this.node.tryGetContext(cxapi.DISABLE_VERSION_REPORTING) ? false : true; this.outdir = props.outdir || process.env[cxapi.OUTDIR_ENV]; const autoRun = props.autoRun !== undefined ? props.autoRun : cxapi.OUTDIR_ENV in process.env; diff --git a/packages/@aws-cdk/cdk/lib/cfn-element.ts b/packages/@aws-cdk/cdk/lib/cfn-element.ts index 2bc8e35c85289..e4943070963a2 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-element.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-element.ts @@ -1,6 +1,5 @@ import cxapi = require('@aws-cdk/cx-api'); -import { Construct, ConstructNode } from "./construct"; -import { Token } from './token'; +import { Construct } from "./construct"; const CFN_ELEMENT_SYMBOL = Symbol.for('@aws-cdk/cdk.CfnElement'); @@ -32,6 +31,11 @@ export abstract class CfnElement extends Construct { */ public readonly logicalId: string; + /** + * The stack in which this element is defined. CfnElements must be defined within a stack scope (directly or indirectly). + */ + public readonly stack: Stack; + private _logicalId: string; /** @@ -48,7 +52,8 @@ export abstract class CfnElement extends Construct { this.node.addMetadata(cxapi.LOGICAL_ID_METADATA_KEY, new (require("./token").Token)(() => this.logicalId), this.constructor); - this._logicalId = this.node.stack.logicalIds.getLogicalId(this); + this.stack = Stack.of(this); + this._logicalId = this.stack.logicalIds.getLogicalId(this); this.logicalId = new Token(() => this._logicalId, `${notTooLong(this.node.path)}.LogicalID`).toString(); } @@ -87,13 +92,6 @@ export abstract class CfnElement extends Construct { } } - /** - * Return the path with respect to the stack - */ - public get stackPath(): string { - return this.node.ancestors(this.node.stack).map(c => c.node.id).join(ConstructNode.PATH_SEP); - } - /** * Returns the CloudFormation 'snippet' for this entity. The snippet will only be merged * at the root level to ensure there are no identity conflicts. @@ -127,7 +125,7 @@ export abstract class CfnElement extends Construct { // 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())); + this.node.addReference(...findTokens(this, () => this._toCloudFormation())); } catch (e) { if (e.type !== 'CfnSynthesisError') { throw e; } } @@ -167,3 +165,5 @@ function notTooLong(x: string) { import { CfnReference } from "./cfn-reference"; import { findTokens } from "./resolve"; +import { Stack } from './stack'; +import { Token } from './token'; diff --git a/packages/@aws-cdk/cdk/lib/cfn-output.ts b/packages/@aws-cdk/cdk/lib/cfn-output.ts index 46b462a625772..66f538e73a8ef 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-output.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-output.ts @@ -161,7 +161,7 @@ export class CfnOutput extends CfnElement { */ private uniqueOutputName() { // prefix export name with stack name since exports are global within account + region. - const stackName = this.node.stack.name; + const stackName = this.stack.name; return (stackName ? stackName + ':' : '') + this.logicalId; } } diff --git a/packages/@aws-cdk/cdk/lib/cfn-reference.ts b/packages/@aws-cdk/cdk/lib/cfn-reference.ts index 9b155bd659a17..18394e3a4f705 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-reference.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-reference.ts @@ -95,14 +95,14 @@ export class CfnReference extends Reference { this.originalDisplayName = displayName; this.replacementTokens = new Map(); - this.producingStack = target.node.stack; + this.producingStack = Stack.of(target); Object.defineProperty(this, CFN_REFERENCE_SYMBOL, { value: true }); } public resolve(context: IResolveContext): 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(context.scope.node.stack); + const token = this.replacementTokens.get(Stack.of(context.scope)); if (token) { return token.resolve(context); } else { @@ -152,7 +152,7 @@ export class CfnReference extends Reference { } // Ensure a singleton CfnOutput for this value - const resolved = producingStack.node.resolve(tokenValue); + const resolved = producingStack.resolve(tokenValue); const id = 'Output' + JSON.stringify(resolved); let output = stackExports.node.tryFindChild(id) as CfnOutput; if (!output) { diff --git a/packages/@aws-cdk/cdk/lib/cfn-resource.ts b/packages/@aws-cdk/cdk/lib/cfn-resource.ts index efdebd222796a..c8a8fb292dafd 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-resource.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-resource.ts @@ -108,7 +108,7 @@ export class CfnResource extends CfnRefElement { // if aws:cdk:enable-path-metadata is set, embed the current construct's // path in the CloudFormation template, so it will be possible to trace // back to the actual construct path. - if (this.node.getContext(cxapi.PATH_METADATA_ENABLE_CONTEXT)) { + if (this.node.tryGetContext(cxapi.PATH_METADATA_ENABLE_CONTEXT)) { this.options.metadata = { [cxapi.PATH_METADATA_KEY]: this.node.path }; @@ -192,6 +192,13 @@ export class CfnResource extends CfnRefElement { this.dependsOn.add(resource); } + /** + * @returns a string representation of this resource + */ + public toString() { + return `${super.toString()} [${this.resourceType}]`; + } + /** * Emits CloudFormation for this resource. * @internal diff --git a/packages/@aws-cdk/cdk/lib/construct.ts b/packages/@aws-cdk/cdk/lib/construct.ts index 412c67c986cb9..c0bbda8f82340 100644 --- a/packages/@aws-cdk/cdk/lib/construct.ts +++ b/packages/@aws-cdk/cdk/lib/construct.ts @@ -1,8 +1,6 @@ import cxapi = require('@aws-cdk/cx-api'); import { IAspect } from './aspect'; -import { CLOUDFORMATION_TOKEN_RESOLVER, CloudFormationLang } from './cloudformation-lang'; import { DependableTrait, IDependable } from './dependency'; -import { resolve } from './resolve'; import { createStackTrace } from './stack-trace'; import { Token } from './token'; import { makeUniqueId } from './uniqueid'; @@ -39,12 +37,12 @@ export class ConstructNode { // the three holy phases of synthesis: prepare, validate and synthesize // prepare - root.prepareTree(); + this.prepare(root); // validate const validate = options.skipValidation === undefined ? true : !options.skipValidation; if (validate) { - const errors = root.validateTree(); + const errors = this.validate(root); if (errors.length > 0) { const errorList = errors.map(e => `[${e.source.node.path}] ${e.message}`).join('\n '); throw new Error(`Validation failed with the following errors:\n ${errorList}`); @@ -61,41 +59,64 @@ export class ConstructNode { } /** - * Returns the scope in which this construct is defined. + * Invokes "prepare" on all constructs (depth-first, post-order) in the tree under `node`. + * @param node The root node */ - public readonly scope?: IConstruct; + public static prepare(node: ConstructNode) { + const constructs = node.findAll(ConstructOrder.PreOrder); - /** - * The scoped construct ID - * This ID is unique amongst all constructs defined in the same scope. - * To obtain a global unique id for this construct, use `uniqueId`. - */ - public readonly id: string; + // Aspects are applied root to leaf + for (const construct of constructs) { + construct.node.invokeAspects(); + } + + // Use .reverse() to achieve post-order traversal + for (const construct of constructs.reverse()) { + if (Construct.isConstruct(construct)) { + (construct as any).prepare(); // "as any" is needed because we want to keep "prepare" protected + } + } + } /** - * An array of aspects applied to this node + * Invokes "validate" on all constructs in the tree (depth-first, pre-order) and returns + * the list of all errors. An empty list indicates that there are no errors. + * + * @param node The root node */ - private readonly aspects: IAspect[] = []; + public static validate(node: ConstructNode) { + let errors = new Array(); + + for (const child of node.children) { + errors = errors.concat(this.validate(child.node)); + } + + const localErrors: string[] = (node.host as any).validate(); // "as any" is needed because we want to keep "validate" protected + return errors.concat(localErrors.map(msg => ({ source: node.host, message: msg }))); + } /** - * List of children and their names + * Returns the scope in which this construct is defined. + * + * The value is `undefined` at the root of the construct scope tree. */ - private readonly _children: { [name: string]: IConstruct } = { }; - private readonly context: { [key: string]: any } = { }; - private readonly _metadata = new Array(); - private readonly references = new Set(); - private readonly dependencies = new Set(); - - /** Will be used to cache the value of ``this.stack``. */ - private _stack?: import('./stack').Stack; + public readonly scope?: IConstruct; /** - * If this is set to 'true'. addChild() calls for this construct and any child - * will fail. This is used to prevent tree mutations during synthesis. + * The id of this construct within the current scope. + * + * This is a a scope-unique id. To obtain an app-unique id for this construct, use `uniqueId`. */ - private _locked = false; + public readonly id: string; - private invokedAspects: IAspect[] = []; + private _locked = false; // if this is "true", addChild will fail + private readonly _aspects: IAspect[] = []; + private readonly _children: { [id: string]: IConstruct } = { }; + private readonly _context: { [key: string]: any } = { }; + private readonly _metadata = new Array(); + private readonly _references = new Set(); + private readonly _dependencies = new Set(); + private readonly invokedAspects: IAspect[] = []; constructor(private readonly host: Construct, scope: IConstruct, id: string) { id = id || ''; // if undefined, convert to empty string @@ -125,32 +146,13 @@ export class ConstructNode { } } - /** - * The stack the construct is a part of. - */ - public get stack(): import('./stack').Stack { - // Lazy import to break cyclic import - const stack: typeof import('./stack') = require('./stack'); - return this._stack || (this._stack = _lookStackUp(this)); - - function _lookStackUp(_this: ConstructNode): import('./stack').Stack { - if (stack.Stack.isStack(_this.host)) { - return _this.host; - } - if (!_this.scope) { - throw new Error(`No stack could be identified for the construct at path ${_this.path}`); - } - return _this.scope.node.stack; - } - } - /** * The full, absolute path of this construct in the tree. * * Components are separated by '/'. */ public get path(): string { - const components = this.ancestors().slice(1).map(c => c.node.id); + const components = this.scopes.slice(1).map(c => c.node.id); return components.join(ConstructNode.PATH_SEP); } @@ -159,26 +161,10 @@ export class ConstructNode { * Includes all components of the tree. */ public get uniqueId(): string { - const components = this.ancestors().slice(1).map(c => c.node.id); + const components = this.scopes.slice(1).map(c => c.node.id); return components.length > 0 ? makeUniqueId(components) : ''; } - /** - * Returns a string with a tree representation of this construct and it's children. - */ - public toTreeString(depth = 0) { - let out = ''; - for (let i = 0; i < depth; ++i) { - out += ' '; - } - const name = this.id || ''; - out += `${this.typename}${name.length > 0 ? ' [' + name + ']' : ''}\n`; - for (const child of this.children) { - out += child.node.toTreeString(depth + 1); - } - return out; - } - /** * Return a descendant by path, or undefined * @@ -277,7 +263,7 @@ export class ConstructNode { const names = this.children.map(c => c.node.id); throw new Error('Cannot set context after children have been added: ' + names.join(',')); } - this.context[key] = value; + this._context[key] = value; } /** @@ -286,36 +272,21 @@ export class ConstructNode { * Context is usually initialized at the root, but can be overridden at any point in the tree. * * @param key The context key - * @returns The context value or undefined + * @returns The context value or `undefined` if there is no context value for thie key. */ - public getContext(key: string): any { - const value = this.context[key]; + public tryGetContext(key: string): any { + const value = this._context[key]; if (value !== undefined) { return value; } - return this.scope && this.scope.node.getContext(key); + return this.scope && this.scope.node.tryGetContext(key); } /** - * Retrieve a value from tree-global context - * - * It is an error if the context object is not available. - */ - public requireContext(key: string): any { - const value = this.getContext(key); - - if (value == null) { - throw new Error(`You must supply a context value named '${key}'`); - } - - return value; - } - - /** - * An array of metadata objects associated with this construct. + * An immutable array of metadata objects associated with this construct. * This can be used, for example, to implement support for deprecation notices, source mapping, etc. */ public get metadata() { - return this._metadata; + return [ ...this._metadata ]; } /** @@ -333,7 +304,7 @@ export class ConstructNode { return; } - const trace = this.getContext(cxapi.DISABLE_METADATA_STACK_TRACE) ? undefined : createStackTrace(from || this.addMetadata); + const trace = this.tryGetContext(cxapi.DISABLE_METADATA_STACK_TRACE) ? undefined : createStackTrace(from || this.addMetadata); this._metadata.push({ type, data, trace }); } @@ -365,63 +336,26 @@ export class ConstructNode { this.addMetadata(cxapi.ERROR_METADATA_KEY, message); } - /** - * Invokes 'validate' on all child constructs and then on this construct (depth-first). - * @returns A list of validation errors. If the list is empty, all constructs are valid. - */ - public validateTree(): ValidationError[] { - let errors = new Array(); - - for (const child of this.children) { - errors = errors.concat(child.node.validateTree()); - } - - const localErrors: string[] = (this.host as any).validate(); // "as any" is needed because we want to keep "validate" protected - return errors.concat(localErrors.map(msg => ({ source: this.host, message: msg }))); - } - - /** - * Run 'prepare()' on all constructs in the tree - */ - public prepareTree() { - const constructs = this.host.node.findAll(ConstructOrder.PreOrder); - // Aspects are applied root to leaf - for (const construct of constructs) { - construct.node.invokeAspects(); - } - // Use .reverse() to achieve post-order traversal - for (const construct of constructs.reverse()) { - if (Construct.isConstruct(construct)) { - (construct as any).prepare(); // "as any" is needed because we want to keep "prepare" protected - } - } - } - /** * Applies the aspect to this Constructs node */ - public apply(aspect: IAspect): void { - this.aspects.push(aspect); + public applyAspect(aspect: IAspect): void { + this._aspects.push(aspect); return; } /** - * Return the ancestors (including self) of this Construct up until and - * excluding the indicated component - * - * @param upTo The construct to return the path components relative to, or the - * entire list of ancestors (including root) if omitted. This construct will - * not be included in the returned list. + * All parent scopes of this construct. * * @returns a list of parent scopes. The last element in the list will always - * be `this` and the first element is the oldest scope (if `upTo` is not set, - * it will be the root of the construct tree). + * be the current construct and the first element will be the root of the + * tree. */ - public ancestors(upTo?: Construct): IConstruct[] { + public get scopes(): IConstruct[] { const ret = new Array(); let curr: IConstruct | undefined = this.host; - while (curr && curr !== upTo) { + while (curr) { ret.unshift(curr); curr = curr.node && curr.node.scope; } @@ -433,58 +367,7 @@ export class ConstructNode { * @returns The root of the construct tree. */ public get root() { - return this.ancestors()[0]; - } - - /** - * Throws if the `props` bag doesn't include the property `name`. - * In the future we can add some type-checking here, maybe even auto-generate during compilation. - * @param props The props bag. - * @param name The name of the required property. - * - * @deprecated use ``requireProperty`` from ``@aws-cdk/runtime`` instead. - */ - public required(props: any, name: string): any { - if (!(name in props)) { - throw new Error(`Construct of type ${this.typename} is missing required property: ${name}`); - } - - const value = props[name]; - return value; - } - - /** - * @returns The type name of this node. - */ - public get typename(): string { - const ctor: any = this.host.constructor; - return ctor.name || 'Construct'; - } - - /** - * Adds a child construct to this node. - * - * @param child The child construct - * @param childName The type name of the child construct. - * @returns The resolved path part name of the child - */ - public addChild(child: Construct, childName: string) { - if (this.locked) { - - // special error if root is locked - if (!this.path) { - throw new Error('Cannot add children during synthesis'); - } - - throw new Error(`Cannot add children to "${this.path}" during synthesis`); - } - - if (childName in this._children) { - const name = this.id || ''; - throw new Error(`There is already a Construct with name '${childName}' in ${this.typename}${name.length > 0 ? ' [' + name + ']' : ''}`); - } - - this._children[childName] = child; + return this.scopes[0]; } /** @@ -518,43 +401,25 @@ 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: [], - resolver: CLOUDFORMATION_TOKEN_RESOLVER, - }); - } - - /** - * Convert an object, potentially containing tokens, to a JSON string - */ - public stringifyJson(obj: any): string { - return CloudFormationLang.toJSON(obj).toString(); - } - /** * Record a reference originating from this construct node */ - public recordReference(...refs: Token[]) { + public addReference(...refs: Token[]) { for (const ref of refs) { if (Reference.isReference(ref)) { - this.references.add(ref); + this._references.add(ref); } } } /** - * Return all references of the given type originating from this node or any of its children + * Return all references originating from this node or any of its children */ - public findReferences(): OutgoingReference[] { + public get references(): OutgoingReference[] { const ret = new Set(); function recurse(node: ConstructNode) { - for (const reference of node.references) { + for (const reference of node._references) { ret.add({ source: node.host, reference }); } @@ -576,19 +441,19 @@ export class ConstructNode { */ public addDependency(...dependencies: IDependable[]) { for (const dependency of dependencies) { - this.dependencies.add(dependency); + this._dependencies.add(dependency); } } /** * Return all dependencies registered on this node or any of its children */ - public findDependencies(): Dependency[] { + public get dependencies(): Dependency[] { const found = new Map>(); // Deduplication map const ret = new Array(); for (const source of this.findAll()) { - for (const dependable of source.node.dependencies) { + for (const dependable of source.node._dependencies) { for (const target of DependableTrait.get(dependable).dependencyRoots) { let foundTargets = found.get(source); if (!foundTargets) { found.set(source, foundTargets = new Set()); } @@ -604,12 +469,39 @@ export class ConstructNode { return ret; } + /** + * Adds a child construct to this node. + * + * @param child The child construct + * @param childName The type name of the child construct. + * @returns The resolved path part name of the child + */ + private addChild(child: Construct, childName: string) { + if (this.locked) { + + // special error if root is locked + if (!this.path) { + throw new Error('Cannot add children during synthesis'); + } + + throw new Error(`Cannot add children to "${this.path}" during synthesis`); + } + + if (childName in this._children) { + const name = this.id || ''; + const typeName = this.host.constructor.name; + throw new Error(`There is already a Construct with name '${childName}' in ${typeName}${name.length > 0 ? ' [' + name + ']' : ''}`); + } + + this._children[childName] = child; + } + /** * Triggers each aspect to invoke visit */ private invokeAspects(): void { const descendants = this.findAll(); - for (const aspect of this.aspects) { + for (const aspect of this._aspects) { if (this.invokedAspects.includes(aspect)) { continue; } @@ -669,8 +561,7 @@ export class Construct implements IConstruct { * Returns a string representation of this construct. */ public toString() { - const path = this.node.path; - return this.node.typename + (path.length > 0 ? ` [${path}]` : ''); + return this.node.path || ''; } /** @@ -758,22 +649,17 @@ export interface Dependency { } /** - * A single dependency + * Represents a reference that originates from a specific construct. */ -export interface Dependency { +export interface OutgoingReference { /** - * Source the dependency + * The originating construct. */ readonly source: IConstruct; /** - * Target of the dependency + * The reference. */ - readonly target: IConstruct; -} - -export interface OutgoingReference { - readonly source: IConstruct; readonly reference: Reference; } diff --git a/packages/@aws-cdk/cdk/lib/context.ts b/packages/@aws-cdk/cdk/lib/context.ts index d95f5d3843c2c..980bbfe43328c 100644 --- a/packages/@aws-cdk/cdk/lib/context.ts +++ b/packages/@aws-cdk/cdk/lib/context.ts @@ -1,7 +1,9 @@ import cxapi = require('@aws-cdk/cx-api'); import { Construct } from './construct'; +import { Stack } from './stack'; type ContextProviderProps = {[key: string]: any}; + /** * Base class for the model side of context providers * @@ -12,15 +14,17 @@ type ContextProviderProps = {[key: string]: any}; * ContextProvider needs access to a Construct to hook into the context mechanism. */ export class ContextProvider { - private readonly props: ContextProviderProps; constructor(private readonly context: Construct, private readonly provider: string, props: ContextProviderProps = {}) { + + const stack = Stack.of(context); + this.props = { - account: context.node.stack.env.account, - region: context.node.stack.env.region, + account: stack.env.account, + region: stack.env.region, ...props, }; } @@ -41,13 +45,13 @@ export class ContextProvider { return defaultValue; } - const value = this.context.node.getContext(this.key); + const value = this.context.node.tryGetContext(this.key); if (value != null) { return value; } - this.context.node.stack.reportMissingContext({ + this.reportMissingContext({ key: this.key, provider: this.provider, props: this.props, @@ -67,7 +71,7 @@ export class ContextProvider { return defaultValue; } - const value = this.context.node.getContext(this.key); + const value = this.context.node.tryGetContext(this.key); if (value != null) { if (typeof value !== 'string') { @@ -76,7 +80,7 @@ export class ContextProvider { return value; } - this.context.node.stack.reportMissingContext({ + this.reportMissingContext({ key: this.key, provider: this.provider, props: this.props, @@ -89,33 +93,36 @@ export class ContextProvider { * Read a provider value, verifying it's a list * @param defaultValue The value to return if there is no value defined for this context key */ - public getStringListValue( - defaultValue: string[]): string[] { - // if scope is undefined, this is probably a test mode, so we just - // return the default value and report an error so this in not accidentally used - // in the toolkit - if (!this.props.account || !this.props.region) { - this.context.node.addError(formatMissingScopeError(this.provider, this.props)); - return defaultValue; - } + public getStringListValue(defaultValue: string[]): string[] { + // if scope is undefined, this is probably a test mode, so we just + // return the default value and report an error so this in not accidentally used + // in the toolkit + if (!this.props.account || !this.props.region) { + this.context.node.addError(formatMissingScopeError(this.provider, this.props)); + return defaultValue; + } - const value = this.context.node.getContext(this.key); + const value = this.context.node.tryGetContext(this.key); - if (value != null) { - if (!value.map) { - throw new Error(`Context value '${this.key}' is supposed to be a list, got '${JSON.stringify(value)}'`); - } - return value; + if (value != null) { + if (!value.map) { + throw new Error(`Context value '${this.key}' is supposed to be a list, got '${JSON.stringify(value)}'`); } + return value; + } - this.context.node.stack.reportMissingContext({ - key: this.key, - provider: this.provider, - props: this.props, - }); + this.reportMissingContext({ + key: this.key, + provider: this.provider, + props: this.props, + }); - return defaultValue; - } + return defaultValue; + } + + protected reportMissingContext(report: cxapi.MissingContext) { + Stack.of(this.context).reportMissingContext(report); + } } /** diff --git a/packages/@aws-cdk/cdk/lib/index.ts b/packages/@aws-cdk/cdk/lib/index.ts index 05027847f76d1..5cb2fc282258c 100644 --- a/packages/@aws-cdk/cdk/lib/index.ts +++ b/packages/@aws-cdk/cdk/lib/index.ts @@ -9,7 +9,6 @@ export * from './dependency'; export * from './resolve'; export * from './string-fragments'; -export * from './cloudformation-lang'; export * from './reference'; export * from './cfn-condition'; export * from './fn'; diff --git a/packages/@aws-cdk/cdk/lib/logical-id.ts b/packages/@aws-cdk/cdk/lib/logical-id.ts index c8e8405a166cb..56d929ede614e 100644 --- a/packages/@aws-cdk/cdk/lib/logical-id.ts +++ b/packages/@aws-cdk/cdk/lib/logical-id.ts @@ -1,8 +1,6 @@ import { CfnElement } from './cfn-element'; import { makeUniqueId } from './uniqueid'; -const PATH_SEP = '/'; - /** * Interface for classes that implementation logical ID assignment strategies */ @@ -93,8 +91,9 @@ export class LogicalIDs { * Return the logical ID for the given stack element */ public getLogicalId(cfnElement: CfnElement): string { - const path = cfnElement.stackPath.split(PATH_SEP); - + const scopes = cfnElement.node.scopes; + const stackIndex = scopes.indexOf(cfnElement.stack); + const path = scopes.slice(stackIndex + 1).map(x => x.node.id); const generatedId = this.namingScheme.allocateAddress(path); const finalId = this.applyRename(generatedId); validateLogicalId(finalId); diff --git a/packages/@aws-cdk/cdk/lib/stack.ts b/packages/@aws-cdk/cdk/lib/stack.ts index bfabcaeac51ee..d5d85facba520 100644 --- a/packages/@aws-cdk/cdk/lib/stack.ts +++ b/packages/@aws-cdk/cdk/lib/stack.ts @@ -3,12 +3,15 @@ import fs = require('fs'); import path = require('path'); import { App } from './app'; import { CfnParameter } from './cfn-parameter'; +import { CLOUDFORMATION_TOKEN_RESOLVER, CloudFormationLang } from './cloudformation-lang'; import { Construct, ConstructNode, IConstruct, ISynthesisSession } from './construct'; import { Environment } from './environment'; import { HashedAddressingScheme, IAddressingScheme, LogicalIDs } from './logical-id'; +import { resolve } from './resolve'; import { makeUniqueId } from './uniqueid'; const STACK_SYMBOL = Symbol.for('@aws-cdk/cdk.Stack'); +const VALID_STACK_NAME_REGEX = /^[A-Za-z][A-Za-z0-9-]*$/; export interface StackProps { /** @@ -54,7 +57,25 @@ export class Stack extends Construct implements ITaggable { return STACK_SYMBOL in x; } - private static readonly VALID_STACK_NAME_REGEX = /^[A-Za-z][A-Za-z0-9-]*$/; + /** + * Looks up the first stack scope in which `construct` is defined. Fails if there is no stack up the tree. + * @param construct The construct to start the search from. + */ + public static of(construct: IConstruct): Stack { + return _lookup(construct); + + function _lookup(c: IConstruct): Stack { + if (Stack.isStack(c)) { + return c; + } + + if (!c.node.scope) { + throw new Error(`No stack could be identified for the construct at path ${construct.node.path}`); + } + + return _lookup(c.node.scope); + } + } /** * Tags to be applied to the stack. @@ -128,8 +149,8 @@ export class Stack extends Construct implements ITaggable { this.name = props.stackName !== undefined ? props.stackName : this.calculateStackName(); this.tags = new TagManager(TagType.KeyValue, 'aws:cdk:stack', props.tags); - if (!Stack.VALID_STACK_NAME_REGEX.test(this.name)) { - throw new Error(`Stack name must match the regular expression: ${Stack.VALID_STACK_NAME_REGEX.toString()}, got '${name}'`); + if (!VALID_STACK_NAME_REGEX.test(this.name)) { + throw new Error(`Stack name must match the regular expression: ${VALID_STACK_NAME_REGEX.toString()}, got '${name}'`); } } @@ -160,41 +181,21 @@ export class Stack extends Construct implements ITaggable { } /** - * Returns the CloudFormation template for this stack by traversing - * the tree and invoking _toCloudFormation() on all Entity objects. - * - * @internal + * Resolve a tokenized value in the context of the current stack. */ - public _toCloudFormation() { - // before we begin synthesis, we shall lock this stack, so children cannot be added - this.node.lock(); - - try { - const template: any = { - Description: this.templateOptions.description, - Transform: this.templateOptions.transform, - AWSTemplateFormatVersion: this.templateOptions.templateFormatVersion, - Metadata: this.templateOptions.metadata - }; - - const elements = cfnElements(this); - const fragments = elements.map(e => this.node.resolve(e._toCloudFormation())); - - // merge in all CloudFormation fragments collected from the tree - for (const fragment of fragments) { - merge(template, fragment); - } - - // resolve all tokens and remove all empties - const ret = this.node.resolve(template) || {}; - - this.logicalIds.assertAllRenamesApplied(); + public resolve(obj: any): any { + return resolve(obj, { + scope: this, + prefix: [], + resolver: CLOUDFORMATION_TOKEN_RESOLVER, + }); + } - return ret; - } finally { - // allow mutations after synthesis is finished. - this.node.unlock(); - } + /** + * Convert an object, potentially containing tokens, to a JSON string + */ + public toJsonString(obj: any): string { + return CloudFormationLang.toJSON(obj).toString(); } /** @@ -274,7 +275,7 @@ export class Stack extends Construct implements ITaggable { /** * Return the stacks this stack depends on */ - public dependencies(): Stack[] { + public get dependencies(): Stack[] { return Array.from(this.stackDependencies.values()).map(d => d.stack); } @@ -435,8 +436,8 @@ export class Stack extends Construct implements ITaggable { * @internal */ 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}'`); + if (name && !VALID_STACK_NAME_REGEX.test(name)) { + throw new Error(`Stack name must match the regular expression: ${VALID_STACK_NAME_REGEX.toString()}, got '${name}'`); } } @@ -449,15 +450,15 @@ export class Stack extends Construct implements ITaggable { */ protected prepare() { // References - for (const ref of this.node.findReferences()) { + for (const ref of this.node.references) { if (CfnReference.isCfnReference(ref.reference)) { ref.reference.consumeFromStack(this, ref.source); } } // Resource dependencies - for (const dependency of this.node.findDependencies()) { - const theirStack = dependency.target.node.stack; + for (const dependency of this.node.dependencies) { + const theirStack = Stack.of(dependency.target); if (theirStack !== undefined && theirStack !== this) { this.addDependency(theirStack); } else { @@ -482,12 +483,12 @@ export class Stack extends Construct implements ITaggable { const outPath = path.join(builder.outdir, template); fs.writeFileSync(outPath, JSON.stringify(this._toCloudFormation(), undefined, 2)); - const deps = this.dependencies().map(s => s.name); + const deps = this.dependencies.map(s => s.name); const meta = this.collectMetadata(); const properties: cxapi.AwsCloudFormationStackProperties = { templateFile: template, - parameters: Object.keys(this.parameterValues).length > 0 ? this.node.resolve(this.parameterValues) : undefined + parameters: Object.keys(this.parameterValues).length > 0 ? this.resolve(this.parameterValues) : undefined }; // add an artifact that represents this stack @@ -504,13 +505,51 @@ export class Stack extends Construct implements ITaggable { } } + /** + * Returns the CloudFormation template for this stack by traversing + * the tree and invoking _toCloudFormation() on all Entity objects. + * + * @internal + */ + protected _toCloudFormation() { + // before we begin synthesis, we shall lock this stack, so children cannot be added + this.node.lock(); + + try { + const template: any = { + Description: this.templateOptions.description, + Transform: this.templateOptions.transform, + AWSTemplateFormatVersion: this.templateOptions.templateFormatVersion, + Metadata: this.templateOptions.metadata + }; + + const elements = cfnElements(this); + const fragments = elements.map(e => this.resolve(e._toCloudFormation())); + + // merge in all CloudFormation fragments collected from the tree + for (const fragment of fragments) { + merge(template, fragment); + } + + // resolve all tokens and remove all empties + const ret = this.resolve(template) || {}; + + this.logicalIds.assertAllRenamesApplied(); + + return ret; + } finally { + // allow mutations after synthesis is finished. + this.node.unlock(); + } + } + /** * Applied defaults to environment attributes. */ private parseEnvironment(env: Environment = {}) { return { - account: env.account ? env.account : this.node.getContext(cxapi.DEFAULT_ACCOUNT_CONTEXT_KEY), - region: env.region ? env.region : this.node.getContext(cxapi.DEFAULT_REGION_CONTEXT_KEY) + account: env.account ? env.account : this.node.tryGetContext(cxapi.DEFAULT_ACCOUNT_CONTEXT_KEY), + region: env.region ? env.region : this.node.tryGetContext(cxapi.DEFAULT_REGION_CONTEXT_KEY) }; } @@ -533,6 +572,7 @@ export class Stack extends Construct implements ITaggable { private collectMetadata() { const output: { [id: string]: cxapi.MetadataEntry[] } = { }; + const stack = this; visit(this); @@ -548,7 +588,7 @@ export class Stack extends Construct implements ITaggable { if (node.node.metadata.length > 0) { // Make the path absolute - output[ConstructNode.PATH_SEP + node.node.path] = node.node.metadata.map(md => node.node.resolve(md) as cxapi.MetadataEntry); + output[ConstructNode.PATH_SEP + node.node.path] = node.node.metadata.map(md => stack.resolve(md) as cxapi.MetadataEntry); } for (const child of node.node.children) { @@ -563,7 +603,7 @@ export class Stack extends Construct implements ITaggable { private calculateStackName() { // In tests, it's possible for this stack to be the root object, in which case // we need to use it as part of the root path. - const rootPath = this.node.scope !== undefined ? this.node.ancestors().slice(1) : [this]; + const rootPath = this.node.scope !== undefined ? this.node.scopes.slice(1) : [this]; const ids = rootPath.map(c => c.node.id); // Special case, if rootPath is length 1 then just use ID (backwards compatibility) diff --git a/packages/@aws-cdk/cdk/lib/util.ts b/packages/@aws-cdk/cdk/lib/util.ts index 477f61e9015bd..a0cbef73dd68f 100644 --- a/packages/@aws-cdk/cdk/lib/util.ts +++ b/packages/@aws-cdk/cdk/lib/util.ts @@ -1,4 +1,5 @@ import { IConstruct } from "./construct"; +import { Stack } from './stack'; import { IResolveContext, IResolvedValuePostProcessor, Token } from "./token"; /** @@ -6,7 +7,8 @@ import { IResolveContext, IResolvedValuePostProcessor, Token } from "./token"; * @param obj The object. */ export function capitalizePropertyNames(construct: IConstruct, obj: any): any { - obj = construct.node.resolve(obj); + const stack = Stack.of(construct); + obj = stack.resolve(obj); if (typeof(obj) !== 'object') { return obj; diff --git a/packages/@aws-cdk/cdk/test/test.app.ts b/packages/@aws-cdk/cdk/test/test.app.ts index 3dfa7fcbb53b3..a4d3862350a1e 100644 --- a/packages/@aws-cdk/cdk/test/test.app.ts +++ b/packages/@aws-cdk/cdk/test/test.app.ts @@ -96,8 +96,8 @@ export = { key2: 'val2' }); const prog = new App(); - test.deepEqual(prog.node.getContext('key1'), 'val1'); - test.deepEqual(prog.node.getContext('key2'), 'val2'); + test.deepEqual(prog.node.tryGetContext('key1'), 'val1'); + test.deepEqual(prog.node.tryGetContext('key2'), 'val2'); test.done(); }, @@ -129,7 +129,7 @@ export = { 'setContext(k,v) can be used to set context programmatically'(test: Test) { const prog = new App(); prog.node.setContext('foo', 'bar'); - test.deepEqual(prog.node.getContext('foo'), 'bar'); + test.deepEqual(prog.node.tryGetContext('foo'), 'bar'); test.done(); }, @@ -310,6 +310,6 @@ class MyConstruct extends Construct { super(scope, id); new CfnResource(this, 'r1', { type: 'ResourceType1' }); - new CfnResource(this, 'r2', { type: 'ResourceType2', properties: { FromContext: this.node.getContext('ctx1') } }); + new CfnResource(this, 'r2', { type: 'ResourceType2', properties: { FromContext: this.node.tryGetContext('ctx1') } }); } } diff --git a/packages/@aws-cdk/cdk/test/test.arn.ts b/packages/@aws-cdk/cdk/test/test.arn.ts index ef1041a2b0997..f7fd18a749ec5 100644 --- a/packages/@aws-cdk/cdk/test/test.arn.ts +++ b/packages/@aws-cdk/cdk/test/test.arn.ts @@ -1,5 +1,6 @@ import { Test } from 'nodeunit'; import { ArnComponents, CfnOutput, ScopedAws, Stack, Token } from '../lib'; +import { toCloudFormation } from './util'; export = { 'create from components with defaults'(test: Test) { @@ -12,8 +13,8 @@ export = { const pseudo = new ScopedAws(stack); - test.deepEqual(stack.node.resolve(arn), - stack.node.resolve(`arn:${pseudo.partition}:sqs:${pseudo.region}:${pseudo.accountId}:myqueuename`)); + test.deepEqual(stack.resolve(arn), + stack.resolve(`arn:${pseudo.partition}:sqs:${pseudo.region}:${pseudo.accountId}:myqueuename`)); test.done(); }, @@ -29,7 +30,7 @@ export = { resourceName: 'mytable/stream/label' }); - test.deepEqual(stack.node.resolve(arn), + test.deepEqual(stack.resolve(arn), 'arn:aws-cn:dynamodb:us-east-1:123456789012:table/mytable/stream/label'); test.done(); }, @@ -45,7 +46,7 @@ export = { partition: 'aws-cn', }); - test.deepEqual(stack.node.resolve(arn), + test.deepEqual(stack.resolve(arn), 'arn:aws-cn:s3:::my-bucket'); test.done(); @@ -63,8 +64,8 @@ export = { const pseudo = new ScopedAws(stack); - test.deepEqual(stack.node.resolve(arn), - stack.node.resolve(`arn:${pseudo.partition}:codedeploy:${pseudo.region}:${pseudo.accountId}:application:WordPress_App`)); + test.deepEqual(stack.resolve(arn), + stack.resolve(`arn:${pseudo.partition}:codedeploy:${pseudo.region}:${pseudo.accountId}:application:WordPress_App`)); test.done(); }, @@ -80,8 +81,8 @@ export = { const pseudo = new ScopedAws(stack); - test.deepEqual(stack.node.resolve(arn), - stack.node.resolve(`arn:${pseudo.partition}:ssm:${pseudo.region}:${pseudo.accountId}:parameter/parameter-name`)); + test.deepEqual(stack.resolve(arn), + stack.resolve(`arn:${pseudo.partition}:ssm:${pseudo.region}:${pseudo.accountId}:parameter/parameter-name`)); test.done(); }, @@ -177,12 +178,12 @@ export = { const theToken = { Ref: 'SomeParameter' }; 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.deepEqual(stack.resolve(parsed.partition), { 'Fn::Select': [ 1, { 'Fn::Split': [ ':', theToken ]} ]}); + test.deepEqual(stack.resolve(parsed.service), { 'Fn::Select': [ 2, { 'Fn::Split': [ ':', theToken ]} ]}); + test.deepEqual(stack.resolve(parsed.region), { 'Fn::Select': [ 3, { 'Fn::Split': [ ':', theToken ]} ]}); + test.deepEqual(stack.resolve(parsed.account), { 'Fn::Select': [ 4, { 'Fn::Split': [ ':', theToken ]} ]}); + test.deepEqual(stack.resolve(parsed.resource), { 'Fn::Select': [ 5, { 'Fn::Split': [ ':', theToken ]} ]}); + test.deepEqual(stack.resolve(parsed.resourceName), { 'Fn::Select': [ 6, { 'Fn::Split': [ ':', theToken ]} ]}); test.equal(parsed.sep, ':'); test.done(); @@ -196,9 +197,9 @@ export = { test.equal(parsed.sep, '/'); // tslint:disable-next-line:max-line-length - test.deepEqual(stack.node.resolve(parsed.resource), { 'Fn::Select': [ 0, { 'Fn::Split': [ '/', { 'Fn::Select': [ 5, { 'Fn::Split': [ ':', theToken ]} ]} ]} ]}); + test.deepEqual(stack.resolve(parsed.resource), { 'Fn::Select': [ 0, { 'Fn::Split': [ '/', { 'Fn::Select': [ 5, { 'Fn::Split': [ ':', theToken ]} ]} ]} ]}); // tslint:disable-next-line:max-line-length - test.deepEqual(stack.node.resolve(parsed.resourceName), { 'Fn::Select': [ 1, { 'Fn::Split': [ '/', { 'Fn::Select': [ 5, { 'Fn::Split': [ ':', theToken ]} ]} ]} ]}); + test.deepEqual(stack.resolve(parsed.resourceName), { 'Fn::Select': [ 1, { 'Fn::Split': [ '/', { 'Fn::Select': [ 5, { 'Fn::Split': [ ':', theToken ]} ]} ]} ]}); test.done(); } @@ -219,7 +220,7 @@ export = { new CfnOutput(stack2, 'SomeValue', { value: arn }); // THEN - test.deepEqual(stack2.node.resolve(stack2._toCloudFormation()), { + test.deepEqual(toCloudFormation(stack2), { Outputs: { SomeValue: { Value: { diff --git a/packages/@aws-cdk/cdk/test/test.aspect.ts b/packages/@aws-cdk/cdk/test/test.aspect.ts index 718a8b122f0d1..76bd9546ce80c 100644 --- a/packages/@aws-cdk/cdk/test/test.aspect.ts +++ b/packages/@aws-cdk/cdk/test/test.aspect.ts @@ -1,7 +1,7 @@ import { Test } from 'nodeunit'; import { App } from '../lib'; import { IAspect } from '../lib/aspect'; -import { Construct, IConstruct } from '../lib/construct'; +import { Construct, ConstructNode, IConstruct } from '../lib/construct'; class MyConstruct extends Construct { public static IsMyConstruct(x: any): x is MyConstruct { @@ -21,10 +21,10 @@ export = { 'Aspects are invoked only once'(test: Test) { const app = new App(); const root = new MyConstruct(app, 'MyConstruct'); - root.node.apply(new VisitOnce()); - root.node.prepareTree(); + root.node.applyAspect(new VisitOnce()); + ConstructNode.prepare(root.node); test.deepEqual(root.visitCounter, 1); - root.node.prepareTree(); + ConstructNode.prepare(root.node); test.deepEqual(root.visitCounter, 1); test.done(); }, diff --git a/packages/@aws-cdk/cdk/test/test.cloudformation-json.ts b/packages/@aws-cdk/cdk/test/test.cloudformation-json.ts index 64e5239d25142..60c10c69a2b27 100644 --- a/packages/@aws-cdk/cdk/test/test.cloudformation-json.ts +++ b/packages/@aws-cdk/cdk/test/test.cloudformation-json.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { CloudFormationLang, Fn, Stack, Token } from '../lib'; +import { Fn, Stack, Token } from '../lib'; import { evaluateCFN } from './evaluate-cfn'; export = { @@ -11,7 +11,7 @@ export = { const fido = { name: 'Fido', speaks: token }; // WHEN - const resolved = stack.node.resolve(CloudFormationLang.toJSON(fido)); + const resolved = stack.resolve(stack.toJsonString(fido)); // THEN test.deepEqual(evaluateCFN(resolved), '{"name":"Fido","speaks":"woof woof"}'); @@ -28,7 +28,7 @@ export = { const fido = { name: 'Fido', speaks: `deep ${token}` }; // WHEN - const resolved = stack.node.resolve(CloudFormationLang.toJSON(fido)); + const resolved = stack.resolve(stack.toJsonString(fido)); // THEN test.deepEqual(evaluateCFN(resolved), '{"name":"Fido","speaks":"deep woof woof"}'); @@ -43,7 +43,7 @@ export = { const inputString = 'Hello, "world"'; // WHEN - const resolved = stack.node.resolve(CloudFormationLang.toJSON(inputString)); + const resolved = stack.resolve(stack.toJsonString(inputString)); // THEN test.deepEqual(evaluateCFN(resolved), JSON.stringify(inputString)); @@ -58,9 +58,9 @@ export = { const embedded = `the number is ${num}`; // WHEN - test.equal(evaluateCFN(stack.node.resolve(embedded)), "the number is 1"); - test.equal(evaluateCFN(stack.node.resolve(CloudFormationLang.toJSON({ embedded }))), "{\"embedded\":\"the number is 1\"}"); - test.equal(evaluateCFN(stack.node.resolve(CloudFormationLang.toJSON({ num }))), "{\"num\":1}"); + test.equal(evaluateCFN(stack.resolve(embedded)), "the number is 1"); + test.equal(evaluateCFN(stack.resolve(stack.toJsonString({ embedded }))), "{\"embedded\":\"the number is 1\"}"); + test.equal(evaluateCFN(stack.resolve(stack.toJsonString({ num }))), "{\"num\":1}"); test.done(); }, @@ -70,10 +70,10 @@ export = { const stack = new Stack(); for (const token of tokensThatResolveTo('pong!')) { // WHEN - const stringified = CloudFormationLang.toJSON(`ping? ${token}`); + const stringified = stack.toJsonString(`ping? ${token}`); // THEN - test.equal(evaluateCFN(stack.node.resolve(stringified)), '"ping? pong!"'); + test.equal(evaluateCFN(stack.resolve(stringified)), '"ping? pong!"'); } test.done(); @@ -85,7 +85,7 @@ export = { const bucketName = new Token({ Ref: 'MyBucket' }); // WHEN - const resolved = stack.node.resolve(CloudFormationLang.toJSON({ theBucket: bucketName })); + const resolved = stack.resolve(stack.toJsonString({ theBucket: bucketName })); // THEN const context = {MyBucket: 'TheName'}; @@ -110,8 +110,8 @@ export = { }, })); - const stringified = CloudFormationLang.toJSON(fakeIntrinsics); - test.equal(evaluateCFN(stack.node.resolve(stringified)), + const stringified = stack.toJsonString(fakeIntrinsics); + test.equal(evaluateCFN(stack.resolve(stringified)), '{"a":{"Fn::GetArtifactAtt":{"key":"val"}},"b":{"Fn::GetParam":["val1","val2"]}}'); test.done(); @@ -123,7 +123,7 @@ export = { const token = Fn.join('', [ 'Hello', 'This\nIs', 'Very "cool"' ]); // WHEN - const resolved = stack.node.resolve(CloudFormationLang.toJSON({ + const resolved = stack.resolve(stack.toJsonString({ literal: 'I can also "contain" quotes', token })); @@ -142,7 +142,7 @@ export = { const combinedName = Fn.join('', [ 'The bucket name is ', bucketName.toString() ]); // WHEN - const resolved = stack.node.resolve(CloudFormationLang.toJSON({ theBucket: combinedName })); + const resolved = stack.resolve(stack.toJsonString({ theBucket: combinedName })); // THEN const context = {MyBucket: 'TheName'}; @@ -157,7 +157,7 @@ export = { const fidoSays = new Token(() => 'woof'); // WHEN - const resolved = stack.node.resolve(CloudFormationLang.toJSON({ + const resolved = stack.resolve(stack.toJsonString({ information: `Did you know that Fido says: ${fidoSays}` })); @@ -173,7 +173,7 @@ export = { const fidoSays = new Token(() => ({ Ref: 'Something' })); // WHEN - const resolved = stack.node.resolve(CloudFormationLang.toJSON({ + const resolved = stack.resolve(stack.toJsonString({ information: `Did you know that Fido says: ${fidoSays}` })); @@ -190,7 +190,7 @@ export = { const fidoSays = new Token(() => '"woof"'); // WHEN - const resolved = stack.node.resolve(CloudFormationLang.toJSON({ + const resolved = stack.resolve(stack.toJsonString({ information: `Did you know that Fido says: ${fidoSays}` })); diff --git a/packages/@aws-cdk/cdk/test/test.condition.ts b/packages/@aws-cdk/cdk/test/test.condition.ts index 18733eaa05574..e5fa20dcdc882 100644 --- a/packages/@aws-cdk/cdk/test/test.condition.ts +++ b/packages/@aws-cdk/cdk/test/test.condition.ts @@ -1,5 +1,6 @@ import { Test } from 'nodeunit'; import cdk = require('../lib'); +import { toCloudFormation } from './util'; export = { 'chain conditions'(test: Test) { @@ -16,7 +17,7 @@ export = { }); // THEN - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Parameters: { Param1: { Type: 'String' } }, Conditions: { Condition1: { 'Fn::Equals': [ 'a', 'b' ] }, @@ -45,7 +46,7 @@ export = { // THEN test.ok(cdk.Token.isToken(propValue)); - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: 'AWS::Foo::Bar', diff --git a/packages/@aws-cdk/cdk/test/test.construct.ts b/packages/@aws-cdk/cdk/test/test.construct.ts index 8ae01847064e3..455dc251585ff 100644 --- a/packages/@aws-cdk/cdk/test/test.construct.ts +++ b/packages/@aws-cdk/cdk/test/test.construct.ts @@ -1,6 +1,6 @@ import cxapi = require('@aws-cdk/cx-api'); import { Test } from 'nodeunit'; -import { App as Root, ArnComponents, Construct, ConstructOrder, Stack, Token } from '../lib'; +import { App as Root, Construct, ConstructNode, ConstructOrder, IConstruct, Token, ValidationError } from '../lib'; // tslint:disable:variable-name // tslint:disable:max-line-length @@ -95,50 +95,6 @@ export = { test.done(); }, - 'construct.node.stack returns the correct stack'(test: Test) { - const stack = new Stack(); - test.same(stack.node.stack, stack); - const parent = new Construct(stack, 'Parent'); - const construct = new Construct(parent, 'Construct'); - test.same(construct.node.stack, stack); - test.done(); - }, - - 'construct.node.stack throws when there is no parent Stack'(test: Test) { - const root = new Root(); - const construct = new Construct(root, 'Construct'); - test.throws(() => construct.node.stack, /No stack could be identified for the construct at path/); - test.done(); - }, - - 'construct.node.stack.formatArn forwards to the Stack'(test: Test) { - const stack = new Stack(); - const components: ArnComponents = { service: 'test', resource: 'test' }; - const dummyArn = 'arn:::dummy'; - stack.formatArn = (args) => { - test.same(args, components); - return dummyArn; - }; - - const construct = new Construct(stack, 'Construct'); - test.same(construct.node.stack.formatArn(components), dummyArn); - test.done(); - }, - - 'construct.node.stack.parseArn forwards to the Stack'(test: Test) { - const stack = new Stack(); - const components: ArnComponents = { service: 'test', resource: 'test' }; - const dummyArn = 'arn:::dummy'; - stack.parseArn = (arn) => { - test.same(arn, dummyArn); - return components; - }; - - const construct = new Construct(stack, 'Construct'); - test.same(construct.node.stack.parseArn(dummyArn), components); - test.done(); - }, - 'construct.getChildren() returns an array of all children'(test: Test) { const root = new Root(); const child = new Construct(root, 'Child1'); @@ -169,10 +125,10 @@ export = { 'construct.toString() and construct.toTreeString() can be used for diagnostics'(test: Test) { const t = createTree(); - test.equal(t.root.toString(), 'App'); - test.equal(t.child1_1_1.toString(), 'Construct [Child1/Child11/Child111]'); - test.equal(t.child2.toString(), 'Construct [Child2]'); - test.equal(t.root.node.toTreeString(), 'App\n Construct [Child1]\n Construct [Child11]\n Construct [Child111]\n Construct [Child12]\n Construct [Child2]\n Construct [Child21]\n'); + test.equal(t.root.toString(), ''); + test.equal(t.child1_1_1.toString(), 'Child1/Child11/Child111'); + test.equal(t.child2.toString(), 'Child2'); + test.equal(toTreeString(t.root), 'App\n Construct [Child1]\n Construct [Child11]\n Construct [Child111]\n Construct [Child12]\n Construct [Child2]\n Construct [Child21]\n'); test.done(); }, @@ -183,8 +139,8 @@ export = { }; const t = createTree(context); - test.equal(t.root.node.getContext('ctx1'), 12); - test.equal(t.child1_1_1.node.getContext('ctx2'), 'hello'); + test.equal(t.root.node.tryGetContext('ctx1'), 12); + test.equal(t.child1_1_1.node.tryGetContext('ctx2'), 'hello'); test.done(); }, @@ -202,22 +158,22 @@ export = { child3.node.setContext('c1', 'child3'); child3.node.setContext('c4', 'child3'); - test.equal(root.node.getContext('c1'), 'root'); - test.equal(root.node.getContext('c2'), 'root'); - test.equal(root.node.getContext('c3'), undefined); + test.equal(root.node.tryGetContext('c1'), 'root'); + test.equal(root.node.tryGetContext('c2'), 'root'); + test.equal(root.node.tryGetContext('c3'), undefined); - test.equal(child1.node.getContext('c1'), 'root'); - test.equal(child1.node.getContext('c2'), 'child1'); - test.equal(child1.node.getContext('c3'), 'child1'); + test.equal(child1.node.tryGetContext('c1'), 'root'); + test.equal(child1.node.tryGetContext('c2'), 'child1'); + test.equal(child1.node.tryGetContext('c3'), 'child1'); - test.equal(child2.node.getContext('c1'), 'root'); - test.equal(child2.node.getContext('c2'), 'root'); - test.equal(child2.node.getContext('c3'), undefined); + test.equal(child2.node.tryGetContext('c1'), 'root'); + test.equal(child2.node.tryGetContext('c2'), 'root'); + test.equal(child2.node.tryGetContext('c3'), undefined); - test.equal(child3.node.getContext('c1'), 'child3'); - test.equal(child3.node.getContext('c2'), 'child1'); - test.equal(child3.node.getContext('c3'), 'child1'); - test.equal(child3.node.getContext('c4'), 'child3'); + test.equal(child3.node.tryGetContext('c1'), 'child3'); + test.equal(child3.node.tryGetContext('c2'), 'child1'); + test.equal(child3.node.tryGetContext('c3'), 'child1'); + test.equal(child3.node.tryGetContext('c4'), 'child3'); test.done(); }, @@ -343,21 +299,8 @@ export = { test.done(); }, - 'construct.required(props, name) can be used to validate that required properties are defined'(test: Test) { - const root = new Root(); - - // should be ok - const c = new ConstructWithRequired(root, 'Construct', { requiredProp: 123, anotherRequiredProp: true }); - test.equal(c.requiredProp, 123); - test.equal(c.anotherRequiredProp, true); - - // should throw - test.throws(() => new ConstructWithRequired(root, 'C', { optionalProp: 'hello' } as any)); - test.done(); - }, - // tslint:disable-next-line:max-line-length - 'construct.validate() can be implemented to perform validation, construct.validateTree() will return all errors from the subtree (DFS)'(test: Test) { + 'construct.validate() can be implemented to perform validation, ConstructNode.validate(construct.node) will return all errors from the subtree (DFS)'(test: Test) { class MyConstruct extends Construct { protected validate() { @@ -398,7 +341,7 @@ export = { const stack = new TestStack(); - const errors = (stack.node.validateTree()).map(v => ({ path: v.source.node.path, message: v.message })); + const errors = ConstructNode.validate(stack.node).map((v: ValidationError) => ({ path: v.source.node.path, message: v.message })); // validate DFS test.deepEqual(errors, [ @@ -465,11 +408,8 @@ export = { }, 'ancestors returns a list of parents up to root'(test: Test) { - const { child1, child1_1_1 } = createTree(); - - test.deepEqual(child1_1_1.node.ancestors().map(x => x.node.id), [ '', 'Child1', 'Child11', 'Child111' ]); - test.deepEqual(child1_1_1.node.ancestors(child1).map(x => x.node.id), [ 'Child11', 'Child111' ]); - test.deepEqual(child1_1_1.node.ancestors(child1_1_1), [ ]); + const { child1_1_1 } = createTree(); + test.deepEqual(child1_1_1.node.scopes.map(x => x.node.id), [ '', 'Child1', 'Child11', 'Child111' ]); test.done(); }, @@ -547,20 +487,18 @@ class MyBeautifulConstruct extends Construct { } } -interface ConstructWithRequiredProps { - optionalProp?: string; - requiredProp: number; - anotherRequiredProp: boolean; -} - -class ConstructWithRequired extends Construct { - public readonly requiredProp: string; - public readonly anotherRequiredProp: boolean; - - constructor(scope: Construct, id: string, props: ConstructWithRequiredProps) { - super(scope, id); - - this.requiredProp = this.node.required(props, 'requiredProp'); - this.anotherRequiredProp = this.node.required(props, 'anotherRequiredProp'); +/** + * Returns a string with a tree representation of this construct and it's children. + */ +function toTreeString(node: IConstruct, depth = 0) { + let out = ''; + for (let i = 0; i < depth; ++i) { + out += ' '; + } + const name = node.node.id || ''; + out += `${node.constructor.name}${name.length > 0 ? ' [' + name + ']' : ''}\n`; + for (const child of node.node.children) { + out += toTreeString(child, depth + 1); } + return out; } diff --git a/packages/@aws-cdk/cdk/test/test.context.ts b/packages/@aws-cdk/cdk/test/test.context.ts index 9cb168dfd6099..5ffc6e43e704d 100644 --- a/packages/@aws-cdk/cdk/test/test.context.ts +++ b/packages/@aws-cdk/cdk/test/test.context.ts @@ -85,7 +85,7 @@ export = { stack.node.setContext(key, 'abc'); const ssmp = new SSMParameterProvider(stack, {parameterName: 'test'}); - const azs = stack.node.resolve(ssmp.parameterValue()); + const azs = stack.resolve(ssmp.parameterValue()); test.deepEqual(azs, 'abc'); test.done(); diff --git a/packages/@aws-cdk/cdk/test/test.dynamic-reference.ts b/packages/@aws-cdk/cdk/test/test.dynamic-reference.ts index b16b999f9527e..e233fb60b3991 100644 --- a/packages/@aws-cdk/cdk/test/test.dynamic-reference.ts +++ b/packages/@aws-cdk/cdk/test/test.dynamic-reference.ts @@ -10,7 +10,7 @@ export = { const ref = new CfnDynamicReference(CfnDynamicReferenceService.Ssm, 'a:b:c'); // THEN - test.equal(stack.node.resolve(ref), '{{resolve:ssm:a:b:c}}'); + test.equal(stack.resolve(ref), '{{resolve:ssm:a:b:c}}'); test.done(); }, diff --git a/packages/@aws-cdk/cdk/test/test.fn.ts b/packages/@aws-cdk/cdk/test/test.fn.ts index da7399d171ecf..f88db2116f7a6 100644 --- a/packages/@aws-cdk/cdk/test/test.fn.ts +++ b/packages/@aws-cdk/cdk/test/test.fn.ts @@ -48,7 +48,7 @@ export = nodeunit.testCase({ 'd' ]); - test.deepEqual(stack.node.resolve(obj), { 'Fn::Join': [ "", + test.deepEqual(stack.resolve(obj), { 'Fn::Join': [ "", [ "a", { 'Fn::GetAtt': ['a', 'bc'] }, @@ -63,7 +63,7 @@ export = nodeunit.testCase({ await fc.assert( fc.property( fc.string(), anyValue, - (delimiter, value) => _.isEqual(stack.node.resolve(Fn.join(delimiter, [value])), value) + (delimiter, value) => _.isEqual(stack.resolve(Fn.join(delimiter, [value])), value) ), { verbose: true } ); @@ -73,7 +73,7 @@ export = nodeunit.testCase({ await fc.assert( fc.property( fc.string(), fc.array(nonEmptyString, 1, 15), - (delimiter, values) => stack.node.resolve(Fn.join(delimiter, values)) === values.join(delimiter) + (delimiter, values) => stack.resolve(Fn.join(delimiter, values)) === values.join(delimiter) ), { verbose: true } ); @@ -84,7 +84,7 @@ export = nodeunit.testCase({ fc.property( fc.string(), fc.array(nonEmptyString, 1, 3), tokenish, fc.array(nonEmptyString, 1, 3), (delimiter, prefix, obj, suffix) => - _.isEqual(stack.node.resolve(Fn.join(delimiter, [...prefix, stringToken(obj), ...suffix])), + _.isEqual(stack.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" } @@ -99,8 +99,8 @@ export = nodeunit.testCase({ fc.array(anyValue), (delimiter, prefix, nested, suffix) => // Gonna test - _.isEqual(stack.node.resolve(Fn.join(delimiter, [...prefix, Fn.join(delimiter, nested), ...suffix])), - stack.node.resolve(Fn.join(delimiter, [...prefix, ...nested, ...suffix]))) + _.isEqual(stack.resolve(Fn.join(delimiter, [...prefix, Fn.join(delimiter, nested), ...suffix])), + stack.resolve(Fn.join(delimiter, [...prefix, ...nested, ...suffix]))) ), { verbose: true } ); @@ -116,7 +116,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 = stack.node.resolve(join); + const resolved = stack.resolve(join); return resolved['Fn::Join'][1].find((e: any) => typeof e === 'object' && ('Fn::Join' in e) && e['Fn::Join'][0] === delimiter2) != null; diff --git a/packages/@aws-cdk/cdk/test/test.include.ts b/packages/@aws-cdk/cdk/test/test.include.ts index 0f4b400f21dc9..f1637c9a2a34d 100644 --- a/packages/@aws-cdk/cdk/test/test.include.ts +++ b/packages/@aws-cdk/cdk/test/test.include.ts @@ -1,5 +1,6 @@ import { Test } from 'nodeunit'; import { CfnOutput, CfnParameter, CfnResource, Include, Stack } from '../lib'; +import { toCloudFormation } from './util'; export = { 'the Include construct can be used to embed an existing template as-is into a stack'(test: Test) { @@ -7,7 +8,7 @@ export = { new Include(stack, 'T1', { template: clone(template) }); - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Parameters: { MyParam: { Type: 'String', Default: 'Hello' } }, Resources: { MyResource1: { Type: 'ResourceType1', Properties: { P1: 1, P2: 2 } }, @@ -24,7 +25,7 @@ export = { new CfnOutput(stack, 'MyOutput', { description: 'Out!', disableExport: true, value: 'hey' }); new CfnParameter(stack, 'MyParam2', { type: 'Integer' }); - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Parameters: { MyParam: { Type: 'String', Default: 'Hello' }, MyParam2: { Type: 'Integer' } }, @@ -46,7 +47,7 @@ export = { new CfnOutput(stack, 'MyOutput', { description: 'Out!', value: 'in' }); new CfnParameter(stack, 'MyParam', { type: 'Integer' }); // duplicate! - test.throws(() => stack._toCloudFormation()); + test.throws(() => toCloudFormation(stack)); test.done(); }, }; diff --git a/packages/@aws-cdk/cdk/test/test.logical-id.ts b/packages/@aws-cdk/cdk/test/test.logical-id.ts index 5b7466cd2cc77..f8122064b238b 100644 --- a/packages/@aws-cdk/cdk/test/test.logical-id.ts +++ b/packages/@aws-cdk/cdk/test/test.logical-id.ts @@ -1,5 +1,6 @@ import { Test } from 'nodeunit'; -import { CfnResource, Construct, HashedAddressingScheme, IAddressingScheme, Stack } from '../lib'; +import { CfnResource, Construct, ConstructNode, HashedAddressingScheme, IAddressingScheme, Stack } from '../lib'; +import { toCloudFormation } from './util'; /** * These tests are executed once (for specific ID schemes) @@ -29,7 +30,7 @@ const uniqueTests = { const r = new CfnResource(stack, 'MyAwesomeness', { type: 'Resource' }); // THEN - test.equal(stack.node.resolve(r.logicalId), 'MyAwesomeness'); + test.equal(stack.resolve(r.logicalId), 'MyAwesomeness'); test.done(); }, @@ -44,7 +45,7 @@ const uniqueTests = { new CfnResource(parent, 'ThingResource', { type: 'AWS::TAAS::Thing' }); // THEN - const template = stack._toCloudFormation(); + const template = toCloudFormation(stack); test.ok('Renamed' in template.Resources); test.done(); @@ -59,9 +60,7 @@ const uniqueTests = { new Construct(stack, 'Parent'); // THEN - test.throws(() => { - stack._toCloudFormation(); - }); + test.throws(() => toCloudFormation(stack)); test.done(); }, @@ -95,7 +94,7 @@ const uniqueTests = { new CfnResource(child2, 'HeyThere', { type: 'AWS::TAAS::Thing' }); // THEN - const template = stack._toCloudFormation(); + const template = toCloudFormation(stack); test.deepEqual(template, { Resources: { ParentChildHeyThere35220347: { @@ -112,7 +111,7 @@ const uniqueTests = { const stack1 = new Stack(); const parent1 = new Construct(stack1, 'Parent'); new CfnResource(parent1, 'HeyThere', { type: 'AWS::TAAS::Thing' }); - const template1 = stack1._toCloudFormation(); + const template1 = toCloudFormation(stack1); // AND const theId1 = Object.keys(template1.Resources)[0]; @@ -123,7 +122,7 @@ const uniqueTests = { const parent2 = new Construct(stack2, 'Parent'); const invisibleWrapper = new Construct(parent2, 'Default'); new CfnResource(invisibleWrapper, 'HeyThere', { type: 'AWS::TAAS::Thing' }); - const template2 = stack1._toCloudFormation(); + const template2 = toCloudFormation(stack1); const theId2 = Object.keys(template2.Resources)[0]; test.equal('AWS::TAAS::Thing', template2.Resources[theId2].Type); @@ -210,8 +209,8 @@ const allSchemesTests: {[name: string]: (scheme: IAddressingScheme, test: Test) c2.node.addDependency(c1); // THEN - stack.node.prepareTree(); - test.deepEqual(stack._toCloudFormation(), { + ConstructNode.prepare(stack.node); + test.deepEqual(toCloudFormation(stack), { Resources: { NewName: { Type: 'R1' }, diff --git a/packages/@aws-cdk/cdk/test/test.mappings.ts b/packages/@aws-cdk/cdk/test/test.mappings.ts index 4f524ae1f1e07..4aadcd874655e 100644 --- a/packages/@aws-cdk/cdk/test/test.mappings.ts +++ b/packages/@aws-cdk/cdk/test/test.mappings.ts @@ -1,5 +1,6 @@ import { Test } from 'nodeunit'; import { Aws, CfnMapping, CfnResource, Fn, Stack } from '../lib'; +import { toCloudFormation } from './util'; export = { 'mappings can be added as another type of entity, and mapping.findInMap can be used to get a token'(test: Test) { @@ -28,7 +29,7 @@ export = { mapping.setValue('TopLevelKey2', 'SecondLevelKey2', 'Hi'); mapping.setValue('TopLevelKey1', 'SecondLevelKey1', [ 1, 2, 3, 4 ]); - test.deepEqual(stack._toCloudFormation(), { Mappings: + test.deepEqual(toCloudFormation(stack), { Mappings: { MyMapping: { TopLevelKey1: { SecondLevelKey1: [ 1, 2, 3, 4 ], @@ -59,8 +60,8 @@ export = { const v2 = Fn.findInMap(mapping.logicalId, 'instanceCount', Aws.region); const expected = { 'Fn::FindInMap': [ 'mapping', 'instanceCount', { Ref: 'AWS::Region' } ] }; - test.deepEqual(stack.node.resolve(v1), expected); - test.deepEqual(stack.node.resolve(v2), expected); + test.deepEqual(stack.resolve(v1), expected); + test.deepEqual(stack.resolve(v2), expected); test.done(); }, @@ -79,7 +80,7 @@ export = { const v = mapping.findInMap(Aws.region, 'size'); // THEN - test.deepEqual(stack.node.resolve(v), { + test.deepEqual(stack.resolve(v), { "Fn::FindInMap": [ 'mapping', { Ref: "AWS::Region" }, "size" ] }); test.done(); @@ -101,7 +102,7 @@ export = { // THEN test.throws(() => mapping.findInMap('not-found', Aws.region), /Mapping doesn't contain top-level key 'not-found'/); - test.deepEqual(stack.node.resolve(v), { 'Fn::FindInMap': [ 'mapping', 'size', { Ref: 'AWS::Region' } ] }); + test.deepEqual(stack.resolve(v), { 'Fn::FindInMap': [ 'mapping', 'size', { Ref: 'AWS::Region' } ] }); test.done(); }, }; diff --git a/packages/@aws-cdk/cdk/test/test.output.ts b/packages/@aws-cdk/cdk/test/test.output.ts index 43b239c33583d..857ac9d55df01 100644 --- a/packages/@aws-cdk/cdk/test/test.output.ts +++ b/packages/@aws-cdk/cdk/test/test.output.ts @@ -1,5 +1,6 @@ import { Test } from 'nodeunit'; import { CfnOutput, CfnResource, Stack } from '../lib'; +import { toCloudFormation } from './util'; export = { 'outputs can be added to the stack'(test: Test) { @@ -12,7 +13,7 @@ export = { value: ref, description: 'CfnOutput properties' }); - test.deepEqual(stack._toCloudFormation(), { Resources: { MyResource: { Type: 'R' } }, + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: 'R' } }, Outputs: { MyOutput: { Description: 'CfnOutput properties', @@ -47,16 +48,16 @@ export = { 'if stack name is undefined, we will only use the logical ID for the export name'(test: Test) { const stack = new Stack(); const output = new CfnOutput(stack, 'MyOutput', { value: 'boom' }); - test.deepEqual(stack.node.resolve(output.makeImportValue()), { 'Fn::ImportValue': 'Stack:MyOutput' }); + test.deepEqual(stack.resolve(output.makeImportValue()), { 'Fn::ImportValue': 'Stack:MyOutput' }); test.done(); }, 'makeImportValue can be used to create an Fn::ImportValue from an output'(test: Test) { const stack = new Stack(undefined, 'MyStack'); const output = new CfnOutput(stack, 'MyOutput', { value: 'boom' }); - test.deepEqual(stack.node.resolve(output.makeImportValue()), { 'Fn::ImportValue': 'MyStack:MyOutput' }); + test.deepEqual(stack.resolve(output.makeImportValue()), { 'Fn::ImportValue': 'MyStack:MyOutput' }); - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Outputs: { MyOutput: { Value: 'boom', @@ -75,7 +76,7 @@ export = { new CfnOutput(stack, 'SomeOutput', { value: 'x' }); // THEN - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Outputs: { SomeOutput: { Value: 'x' diff --git a/packages/@aws-cdk/cdk/test/test.parameter.ts b/packages/@aws-cdk/cdk/test/test.parameter.ts index c8e783a59e8dd..5fd688ee6f5c5 100644 --- a/packages/@aws-cdk/cdk/test/test.parameter.ts +++ b/packages/@aws-cdk/cdk/test/test.parameter.ts @@ -1,5 +1,6 @@ import { Test } from 'nodeunit'; import { CfnParameter, CfnResource, Construct, Stack } from '../lib'; +import { toCloudFormation } from './util'; export = { 'parameters can be used and referenced using param.ref'(test: Test) { @@ -14,7 +15,7 @@ export = { new CfnResource(stack, 'Resource', { type: 'Type', properties: { ReferenceToParam: param.ref } }); - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Parameters: { ChildMyParam3161BF5D: { Default: 10, @@ -32,7 +33,7 @@ export = { const stack = new Stack(); const param = new CfnParameter(stack, 'MyParam', { type: 'String' }); - test.deepEqual(stack.node.resolve(param), { Ref: 'MyParam' }); + test.deepEqual(stack.resolve(param), { Ref: 'MyParam' }); test.done(); } }; diff --git a/packages/@aws-cdk/cdk/test/test.resource.ts b/packages/@aws-cdk/cdk/test/test.resource.ts index 976186301d319..d66c13f871c63 100644 --- a/packages/@aws-cdk/cdk/test/test.resource.ts +++ b/packages/@aws-cdk/cdk/test/test.resource.ts @@ -1,8 +1,9 @@ import cxapi = require('@aws-cdk/cx-api'); import { Test } from 'nodeunit'; import { App, App as Root, applyRemovalPolicy, CfnCondition, - CfnResource, Construct, DeletionPolicy, Fn, - HashedAddressingScheme, RemovalPolicy, Stack } from '../lib'; + CfnResource, Construct, ConstructNode, DeletionPolicy, + Fn, HashedAddressingScheme, RemovalPolicy, Stack } from '../lib'; +import { toCloudFormation } from './util'; export = { 'all resources derive from Resource, which derives from Entity'(test: Test) { @@ -15,7 +16,7 @@ export = { } }); - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: "MyResourceType", @@ -44,8 +45,8 @@ export = { const res1 = new CfnResource(level1, 'childoflevel1', { type: 'MyResourceType1' }); const res2 = new CfnResource(level3, 'childoflevel3', { type: 'MyResourceType2' }); - test.equal(withoutHash(stack.node.resolve(res1.logicalId)), 'level1childoflevel1'); - test.equal(withoutHash(stack.node.resolve(res2.logicalId)), 'level1level2level3childoflevel3'); + test.equal(withoutHash(stack.resolve(res1.logicalId)), 'level1childoflevel1'); + test.equal(withoutHash(stack.resolve(res2.logicalId)), 'level1level2level3childoflevel3'); test.done(); }, @@ -56,7 +57,7 @@ export = { res.increment(); res.increment(2); - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: 'My::Counter', Properties: { Count: 13 } } } @@ -78,7 +79,7 @@ export = { } }); - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: 'My::Counter', Properties: { Count: 10 } }, YourResource: { @@ -105,7 +106,7 @@ export = { } }); - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: "My::Counter", Properties: { Count: 1 } }, MyResource2: { @@ -130,8 +131,8 @@ export = { r2.node.addDependency(r1); r2.node.addDependency(r3); - stack.node.prepareTree(); - test.deepEqual(stack._toCloudFormation(), { + ConstructNode.prepare(stack.node); + test.deepEqual(toCloudFormation(stack), { Resources: { Counter1: { Type: "My::Counter", @@ -166,7 +167,7 @@ export = { dependent.addDependsOn(r1); // THEN - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Resources: { Counter1: { Type: "My::Counter", @@ -191,7 +192,7 @@ export = { const cond = new CfnCondition(stack, 'MyCondition', { expression: Fn.conditionNot(Fn.conditionEquals('a', 'b')) }); r1.options.condition = cond; - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Resources: { Resource: { Type: 'Type', Condition: 'MyCondition' } }, Conditions: { MyCondition: { 'Fn::Not': [ { 'Fn::Equals': [ 'a', 'b' ] } ] } } }); @@ -217,7 +218,7 @@ export = { r1.options.deletionPolicy = DeletionPolicy.Retain; r1.options.updateReplacePolicy = DeletionPolicy.Snapshot; - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Resources: { Resource: { Type: 'Type', @@ -246,7 +247,7 @@ export = { r1.options.updatePolicy = { useOnlineResharding: true }; - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Resources: { Resource: { Type: 'Type', @@ -269,7 +270,7 @@ export = { MyValue: 99 }; - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Resources: { Resource: { Type: "Type", @@ -301,7 +302,7 @@ export = { applyRemovalPolicy(forbid, RemovalPolicy.Forbid); applyRemovalPolicy(destroy, RemovalPolicy.Destroy); - test.deepEqual(stack._toCloudFormation(), { Resources: + test.deepEqual(toCloudFormation(stack), { Resources: { Orphan: { Type: 'T1', DeletionPolicy: 'Retain' }, Forbid: { Type: 'T2', DeletionPolicy: 'Retain' }, Destroy: { Type: 'T3' } } }); @@ -351,8 +352,8 @@ export = { dependingResource.node.addDependency(c1, c2); dependingResource.node.addDependency(c3); - stack.node.prepareTree(); - test.deepEqual(stack._toCloudFormation(), { Resources: + ConstructNode.prepare(stack.node); + test.deepEqual(toCloudFormation(stack), { Resources: { MyC1R1FB2A562F: { Type: 'T1' }, MyC1R2AE2B5066: { Type: 'T2' }, MyC2R3809EEAD6: { Type: 'T3' }, @@ -371,7 +372,7 @@ export = { const stack = new Stack(); const r = new CfnResource(stack, 'MyResource', { type: 'R' }); - test.deepEqual(stack.node.resolve(r.ref), { Ref: 'MyResource' }); + test.deepEqual(stack.resolve(r.ref), { Ref: 'MyResource' }); test.done(); }, @@ -387,7 +388,7 @@ export = { r.addOverride('Use.Dot.Notation', 'To create subtrees'); // THEN - test.deepEqual(stack._toCloudFormation(), { Resources: + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: 'YouCanEvenOverrideTheType', Use: { Dot: { Notation: 'To create subtrees' } }, @@ -416,7 +417,7 @@ export = { r.addOverride('Properties.Hello.World.Value2', null); // THEN - test.deepEqual(stack._toCloudFormation(), { Resources: + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: 'AWS::Resource::Type', Properties: { Hello: { World: { Value1: 'Hello', Value2: null } } } } } }); @@ -444,7 +445,7 @@ export = { r.addOverride('Properties.Hello.World.Value2', undefined); // THEN - test.deepEqual(stack._toCloudFormation(), { Resources: + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: 'AWS::Resource::Type', Properties: { Hello: { World: { Value1: 'Hello' } } } } } }); @@ -463,7 +464,7 @@ export = { r.addPropertyOverride('Tree.Does.Not.Exist', undefined); // THEN - test.deepEqual(stack._toCloudFormation(), { Resources: + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: 'AWS::Resource::Type', Properties: { Tree: { Exists: 42 } } } } }); @@ -493,7 +494,7 @@ export = { r.addPropertyDeletionOverride('Hello.World.Value3'); // THEN - test.deepEqual(stack._toCloudFormation(), { Resources: + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: 'AWS::Resource::Type', Properties: { Hello: { World: { Value1: 'Hello' } } } } } }); @@ -519,7 +520,7 @@ export = { r.addOverride('Properties.Hello.World.Foo.Bar', 42); // THEN - test.deepEqual(stack._toCloudFormation(), { Resources: + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: 'AWS::Resource::Type', Properties: @@ -542,7 +543,7 @@ export = { r.addPropertyOverride('Hello.World', { Hey: 'Jude' }); // THEN - test.deepEqual(stack._toCloudFormation(), { Resources: + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: 'AWS::Resource::Type', Properties: { Hello: { World: { Hey: 'Jude' } } } } } }); @@ -564,7 +565,7 @@ export = { cfn.addOverride('Properties.Foo.Bar', 'Bar'); // THEN - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Resources: { rr: { Type: 'AWS::Resource::Type', @@ -592,7 +593,7 @@ export = { r.setProperty('prop2', 'bar'); - test.deepEqual(stack._toCloudFormation(), { Resources: + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: 'MyResourceType', Properties: { PROP1: 'foo', PROP2: 'bar' } } } }); @@ -606,7 +607,7 @@ export = { r.setProperty('prop3', 'zoo'); - test.deepEqual(stack._toCloudFormation(), { Resources: + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: 'MyResourceType', Properties: { PROP3: 'zoo' } } } }); @@ -621,7 +622,7 @@ export = { r.setProperty('prop3', 'zoo'); r.setProperty('prop2', 'hey'); - test.deepEqual(stack._toCloudFormation(), { Resources: + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: 'MyResourceType', Properties: { PROP2: 'hey', PROP3: 'zoo' } } } }); @@ -640,7 +641,7 @@ export = { type: 'MyResourceType', }); - test.deepEqual(stack._toCloudFormation(), { Resources: + test.deepEqual(toCloudFormation(stack), { Resources: { ParentMyResource4B1FDBCC: { Type: 'MyResourceType', Metadata: { [cxapi.PATH_METADATA_KEY]: 'Parent/MyResource' } } } }); @@ -660,8 +661,10 @@ export = { resB.node.addDependency(resA); // THEN - app.node.prepareTree(); - test.deepEqual(stackB._toCloudFormation(), { + const assembly = app.run(); + const templateB = assembly.getStack(stackB.name).template; + + test.deepEqual(templateB, { Resources: { Resource: { Type: 'R' @@ -669,7 +672,7 @@ export = { } } }); - test.deepEqual(stackB.dependencies().map(s => s.node.id), ['StackA']); + test.deepEqual(stackB.dependencies.map(s => s.node.id), ['StackA']); test.done(); }, diff --git a/packages/@aws-cdk/cdk/test/test.rule.ts b/packages/@aws-cdk/cdk/test/test.rule.ts index f400cad1b9bd0..c72c82b9632f2 100644 --- a/packages/@aws-cdk/cdk/test/test.rule.ts +++ b/packages/@aws-cdk/cdk/test/test.rule.ts @@ -1,5 +1,6 @@ import { Test } from 'nodeunit'; import { CfnRule, Fn, Stack } from '../lib'; +import { toCloudFormation } from './util'; export = { 'Rule can be used to create rules'(test: Test) { @@ -9,7 +10,7 @@ export = { rule.addAssertion(Fn.conditionEquals('lhs', 'rhs'), 'lhs equals rhs'); rule.addAssertion(Fn.conditionNot(Fn.conditionAnd(Fn.conditionContains([ 'hello', 'world' ], "world"))), 'some assertion'); - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Rules: { MyRule: { Assertions: [ diff --git a/packages/@aws-cdk/cdk/test/test.secret-value.ts b/packages/@aws-cdk/cdk/test/test.secret-value.ts index 5d3e54f013f30..a818e5edff366 100644 --- a/packages/@aws-cdk/cdk/test/test.secret-value.ts +++ b/packages/@aws-cdk/cdk/test/test.secret-value.ts @@ -10,7 +10,7 @@ export = { const v = SecretValue.plainText('this just resolves to a string'); // THEN - test.deepEqual(stack.node.resolve(v), 'this just resolves to a string'); + test.deepEqual(stack.resolve(v), 'this just resolves to a string'); test.done(); }, @@ -26,7 +26,7 @@ export = { }); // THEN - test.deepEqual(stack.node.resolve(v), '{{resolve:secretsmanager:secret-id:SecretString:json-key:version-stage:version-id}}'); + test.deepEqual(stack.resolve(v), '{{resolve:secretsmanager:secret-id:SecretString:json-key:version-stage:version-id}}'); test.done(); }, @@ -38,7 +38,7 @@ export = { const v = SecretValue.secretsManager('secret-id'); // THEN - test.deepEqual(stack.node.resolve(v), '{{resolve:secretsmanager:secret-id:SecretString:::}}'); + test.deepEqual(stack.resolve(v), '{{resolve:secretsmanager:secret-id:SecretString:::}}'); test.done(); }, @@ -55,7 +55,7 @@ export = { const v = SecretValue.ssmSecure('param-name', 'param-version'); // THEN - test.deepEqual(stack.node.resolve(v), '{{resolve:ssm-secure:param-name:param-version}}'); + test.deepEqual(stack.resolve(v), '{{resolve:ssm-secure:param-name:param-version}}'); test.done(); }, @@ -67,7 +67,7 @@ export = { const v = SecretValue.cfnDynamicReference(new CfnDynamicReference(CfnDynamicReferenceService.Ssm, 'foo:bar')); // THEN - test.deepEqual(stack.node.resolve(v), '{{resolve:ssm:foo:bar}}'); + test.deepEqual(stack.resolve(v), '{{resolve:ssm:foo:bar}}'); test.done(); }, @@ -80,7 +80,7 @@ export = { const v = SecretValue.cfnParameter(p); // THEN - test.deepEqual(stack.node.resolve(v), { Ref: 'MyParam' }); + test.deepEqual(stack.resolve(v), { Ref: 'MyParam' }); test.done(); }, diff --git a/packages/@aws-cdk/cdk/test/test.stack.ts b/packages/@aws-cdk/cdk/test/test.stack.ts index aca60622c1544..af97bb504f06e 100644 --- a/packages/@aws-cdk/cdk/test/test.stack.ts +++ b/packages/@aws-cdk/cdk/test/test.stack.ts @@ -1,11 +1,12 @@ import cxapi = require('@aws-cdk/cx-api'); import { Test } from 'nodeunit'; -import { App, CfnCondition, CfnOutput, CfnParameter, CfnResource, Construct, Include, ScopedAws, Stack, Token } from '../lib'; +import { App, CfnCondition, CfnOutput, CfnParameter, CfnResource, Construct, ConstructNode, Include, ScopedAws, Stack, Token } from '../lib'; +import { toCloudFormation } from './util'; export = { 'a stack can be serialized into a CloudFormation template, initially it\'s empty'(test: Test) { const stack = new Stack(); - test.deepEqual(stack._toCloudFormation(), { }); + test.deepEqual(toCloudFormation(stack), { }); test.done(); }, @@ -14,7 +15,7 @@ export = { stack.templateOptions.templateFormatVersion = 'MyTemplateVersion'; stack.templateOptions.description = 'This is my description'; stack.templateOptions.transform = 'SAMy'; - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Description: 'This is my description', AWSTemplateFormatVersion: 'MyTemplateVersion', Transform: 'SAMy' @@ -34,7 +35,7 @@ export = { const stack = new Stack(undefined, 'MyStack'); new CfnResource(stack, 'MyResource', { type: 'MyResourceType' }); - test.deepEqual(stack._toCloudFormation(), { Resources: { MyResource: { Type: 'MyResourceType' } } }); + test.deepEqual(toCloudFormation(stack), { Resources: { MyResource: { Type: 'MyResourceType' } } }); test.done(); }, @@ -48,7 +49,7 @@ export = { MetadataKey: 'MetadataValue' }; - test.deepEqual(stack._toCloudFormation(), { + test.deepEqual(toCloudFormation(stack), { Description: 'StackDescription', Transform: 'Transform', AWSTemplateFormatVersion: 'TemplateVersion', @@ -94,12 +95,12 @@ export = { test.ok(!stack.node.tryFindChild('foo'), 'empty stack'); const r1 = new CfnResource(stack, 'Hello', { type: 'MyResource' }); - test.equal(stack.findResource(r1.stackPath), r1, 'look up top-level'); + test.equal(stack.findResource(r1.node.path), r1, 'look up top-level'); const child = new Construct(stack, 'Child'); const r2 = new CfnResource(child, 'Hello', { type: 'MyResource' }); - test.equal(stack.findResource(r2.stackPath), r2, 'look up child'); + test.equal(stack.findResource(r2.node.path), r2, 'look up child'); test.done(); }, @@ -146,7 +147,7 @@ export = { new Include(stack, 'Include', { template }); - const output = stack._toCloudFormation(); + const output = toCloudFormation(stack); test.equal(typeof output.Description, 'string'); test.done(); @@ -163,11 +164,11 @@ export = { new CfnParameter(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.synth(). - app.node.prepareTree(); + const assembly = app.synth(); + const template1 = assembly.getStack(stack1.name).template; + const template2 = assembly.getStack(stack2.name).template; - test.deepEqual(stack1._toCloudFormation(), { + test.deepEqual(template1, { Outputs: { ExportsOutputRefAWSAccountIdAD568057: { Value: { Ref: 'AWS::AccountId' }, @@ -176,7 +177,7 @@ export = { } }); - test.deepEqual(stack2._toCloudFormation(), { + test.deepEqual(template2, { Parameters: { SomeParameter: { Type: 'String', @@ -201,11 +202,10 @@ export = { }}); // THEN - // Need to do this manually now, since we're in testing mode. In a normal CDK app, - // this happens as part of app.synth(). - app.node.prepareTree(); + const assembly = app.synth(); + const template2 = assembly.getStack(stack2.name).template; - test.deepEqual(stack2._toCloudFormation(), { + test.deepEqual(template2, { Resources: { SomeResource: { Type: 'AWS::Some::Resource', @@ -228,10 +228,12 @@ export = { // WHEN - used in another stack new CfnParameter(stack2, 'SomeParameter', { type: 'String', default: new Token(() => account1) }); - app.node.prepareTree(); + const assembly = app.synth(); + const template1 = assembly.getStack(stack1.name).template; + const template2 = assembly.getStack(stack2.name).template; // THEN - test.deepEqual(stack1._toCloudFormation(), { + test.deepEqual(template1, { Outputs: { ExportsOutputRefAWSAccountIdAD568057: { Value: { Ref: 'AWS::AccountId' }, @@ -240,7 +242,7 @@ export = { } }); - test.deepEqual(stack2._toCloudFormation(), { + test.deepEqual(template2, { Parameters: { SomeParameter: { Type: 'String', @@ -262,11 +264,10 @@ export = { new CfnOutput(stack2, 'DemOutput', { value: stack1.region }); // THEN - // Need to do this manually now, since we're in testing mode. In a normal CDK app, - // this happens as part of app.synth(). - app.node.prepareTree(); + const assembly = app.synth(); + const template2 = assembly.getStack(stack2.name).template; - test.deepEqual(stack2._toCloudFormation(), { + test.deepEqual(template2, { Outputs: { DemOutput: { Value: { Ref: 'AWS::Region' }, @@ -287,10 +288,11 @@ export = { // WHEN - used in another stack new CfnParameter(stack2, 'SomeParameter', { type: 'String', default: `TheAccountIs${account1}` }); - app.node.prepareTree(); + const assembly = app.synth(); + const template2 = assembly.getStack(stack2.name).template; // THEN - test.deepEqual(stack2._toCloudFormation(), { + test.deepEqual(template2, { Parameters: { SomeParameter: { Type: 'String', @@ -315,7 +317,7 @@ export = { new CfnParameter(stack1, 'SomeParameter', { type: 'String', default: account2 }); test.throws(() => { - app.node.prepareTree(); + ConstructNode.prepare(app.node); // tslint:disable-next-line:max-line-length }, "'Stack2' depends on 'Stack1' (Stack2/SomeParameter -> Stack1.AWS::AccountId). Adding this dependency (Stack1/SomeParameter -> Stack2.AWS::AccountId) would create a cyclic reference."); @@ -332,10 +334,10 @@ export = { // WHEN new CfnParameter(stack2, 'SomeParameter', { type: 'String', default: account1 }); - app.node.prepareTree(); + ConstructNode.prepare(app.node); // THEN - test.deepEqual(stack2.dependencies().map(s => s.node.id), ['Stack1']); + test.deepEqual(stack2.dependencies.map(s => s.node.id), ['Stack1']); test.done(); }, @@ -351,7 +353,7 @@ export = { new CfnParameter(stack2, 'SomeParameter', { type: 'String', default: account1 }); test.throws(() => { - app.node.prepareTree(); + ConstructNode.prepare(app.node); }, /Can only reference cross stacks in the same region and account/); test.done(); @@ -363,7 +365,7 @@ export = { 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.equal(stack.resolve(stack.region), 'es-norst-1'); test.done(); }, @@ -376,7 +378,7 @@ export = { const stack = new Stack(app, 'Stack1'); // THEN - test.deepEqual(stack.node.resolve(stack.region), { Ref: 'AWS::Region' }); + test.deepEqual(stack.resolve(stack.region), { Ref: 'AWS::Region' }); test.done(); }, @@ -395,7 +397,7 @@ export = { bonjour.overrideLogicalId('BOOM'); // THEN - test.deepEqual(stack._toCloudFormation(), { Resources: + test.deepEqual(toCloudFormation(stack), { Resources: { BOOM: { Type: 'Resource::Type' }, RefToBonjour: { Type: 'Other::Resource', @@ -452,7 +454,23 @@ export = { /Stack name must match the regular expression/); test.done(); - } + }, + + 'Stack.of(stack) returns the correct stack'(test: Test) { + const stack = new Stack(); + test.same(Stack.of(stack), stack); + const parent = new Construct(stack, 'Parent'); + const construct = new Construct(parent, 'Construct'); + test.same(Stack.of(construct), stack); + test.done(); + }, + + 'Stack.of() throws when there is no parent Stack'(test: Test) { + const root = new Construct(undefined as any, 'Root'); + const construct = new Construct(root, 'Construct'); + test.throws(() => Stack.of(construct), /No stack could be identified for the construct at path/); + test.done(); + }, }; class StackWithPostProcessor extends Stack { diff --git a/packages/@aws-cdk/cdk/test/test.tag-aspect.ts b/packages/@aws-cdk/cdk/test/test.tag-aspect.ts index 3bc2ec87bd40a..925ac1bf433bb 100644 --- a/packages/@aws-cdk/cdk/test/test.tag-aspect.ts +++ b/packages/@aws-cdk/cdk/test/test.tag-aspect.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { CfnResource, CfnResourceProps, Construct, RemoveTag, Stack, Tag, TagManager, TagType } from '../lib'; +import { CfnResource, CfnResourceProps, Construct, ConstructNode, RemoveTag, Stack, Tag, TagManager, TagType } from '../lib'; class TaggableResource extends CfnResource { public readonly tags: TagManager; @@ -53,8 +53,10 @@ export = { const map = new MapTaggableResource(res, 'MapFakeResource', { type: 'AWS::Fake::Thing', }); - res.node.apply(new Tag('foo', 'bar')); - root.node.prepareTree(); + res.node.applyAspect(new Tag('foo', 'bar')); + + ConstructNode.synth(root.node); + test.deepEqual(res.tags.renderTags(), [{key: 'foo', value: 'bar'}]); test.deepEqual(res2.tags.renderTags(), [{key: 'foo', value: 'bar'}]); test.deepEqual(map.tags.renderTags(), {foo: 'bar'}); @@ -69,11 +71,11 @@ export = { const res2 = new TaggableResource(res, 'FakeResource', { type: 'AWS::Fake::Thing', }); - res.node.apply(new Tag('foo', 'bar')); - res.node.apply(new Tag('foo', 'foobar')); - res.node.apply(new Tag('foo', 'baz')); - res2.node.apply(new Tag('foo', 'good')); - root.node.prepareTree(); + res.node.applyAspect(new Tag('foo', 'bar')); + res.node.applyAspect(new Tag('foo', 'foobar')); + res.node.applyAspect(new Tag('foo', 'baz')); + res2.node.applyAspect(new Tag('foo', 'good')); + ConstructNode.prepare(root.node); test.deepEqual(res.tags.renderTags(), [{key: 'foo', value: 'baz'}]); test.deepEqual(res2.tags.renderTags(), [{key: 'foo', value: 'good'}]); test.done(); @@ -93,11 +95,11 @@ export = { const map = new MapTaggableResource(res, 'MapFakeResource', { type: 'AWS::Fake::Thing', }); - root.node.apply(new Tag('root', 'was here')); - res.node.apply(new Tag('first', 'there is only 1')); - res.node.apply(new RemoveTag('root')); - res.node.apply(new RemoveTag('doesnotexist')); - root.node.prepareTree(); + root.node.applyAspect(new Tag('root', 'was here')); + res.node.applyAspect(new Tag('first', 'there is only 1')); + res.node.applyAspect(new RemoveTag('root')); + res.node.applyAspect(new RemoveTag('doesnotexist')); + ConstructNode.prepare(root.node); test.deepEqual(res.tags.renderTags(), [{key: 'first', value: 'there is only 1'}]); test.deepEqual(map.tags.renderTags(), {first: 'there is only 1'}); @@ -111,12 +113,12 @@ export = { type: 'AWS::Fake::Thing', }); - res.node.apply(new Tag('foo', 'bar')); - root.node.prepareTree(); + res.node.applyAspect(new Tag('foo', 'bar')); + ConstructNode.prepare(root.node); test.deepEqual(res.tags.renderTags(), [{key: 'foo', value: 'bar'}]); - root.node.prepareTree(); + ConstructNode.prepare(root.node); test.deepEqual(res.tags.renderTags(), [{key: 'foo', value: 'bar'}]); - root.node.prepareTree(); + ConstructNode.prepare(root.node); test.deepEqual(res.tags.renderTags(), [{key: 'foo', value: 'bar'}]); test.done(); }, @@ -128,9 +130,9 @@ export = { const res2 = new TaggableResource(res, 'FakeResource', { type: 'AWS::Fake::Thing', }); - res.node.apply(new RemoveTag('key')); - res2.node.apply(new Tag('key', 'value')); - root.node.prepareTree(); + res.node.applyAspect(new RemoveTag('key')); + res2.node.applyAspect(new Tag('key', 'value')); + ConstructNode.prepare(root.node); test.deepEqual(res.tags.renderTags(), undefined); test.deepEqual(res2.tags.renderTags(), undefined); test.done(); @@ -143,9 +145,9 @@ export = { const res2 = new TaggableResource(res, 'FakeResource', { type: 'AWS::Fake::Thing', }); - res.node.apply(new RemoveTag('key', {priority: 0})); - res2.node.apply(new Tag('key', 'value')); - root.node.prepareTree(); + res.node.applyAspect(new RemoveTag('key', {priority: 0})); + res2.node.applyAspect(new Tag('key', 'value')); + ConstructNode.prepare(root.node); test.deepEqual(res.tags.renderTags(), undefined); test.deepEqual(res2.tags.renderTags(), [{key: 'key', value: 'value'}]); test.done(); @@ -187,8 +189,8 @@ export = { ], }, }); - aspectBranch.node.apply(new Tag('aspects', 'rule')); - root.node.prepareTree(); + aspectBranch.node.applyAspect(new Tag('aspects', 'rule')); + ConstructNode.prepare(root.node); test.deepEqual(aspectBranch.testProperties().tags, [{key: 'aspects', value: 'rule'}, {key: 'cfn', value: 'is cool'}]); test.deepEqual(asgResource.testProperties().tags, [ {key: 'aspects', value: 'rule', propagateAtLaunch: true}, diff --git a/packages/@aws-cdk/cdk/test/test.tokens.ts b/packages/@aws-cdk/cdk/test/test.tokens.ts index 845d9f389806a..ed150eb0c63db 100644 --- a/packages/@aws-cdk/cdk/test/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/test.tokens.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { App as Root, findTokens, Fn, Stack, Token } from '../lib'; +import { findTokens, Fn, Stack, Token } from '../lib'; import { createTokenDouble, extractTokenDouble } from '../lib/encoding'; import { TokenMap } from '../lib/token-map'; import { evaluateCFN } from './evaluate-cfn'; @@ -675,5 +675,5 @@ function tokensThatResolveTo(value: string): Token[] { * So I don't have to change all call sites in this file. */ function resolve(x: any) { - return new Root().node.resolve(x); + return new Stack().resolve(x); } diff --git a/packages/@aws-cdk/cdk/test/test.util.ts b/packages/@aws-cdk/cdk/test/test.util.ts index 87140cd57767d..f21b4d59d13d6 100644 --- a/packages/@aws-cdk/cdk/test/test.util.ts +++ b/packages/@aws-cdk/cdk/test/test.util.ts @@ -1,10 +1,10 @@ import { Test } from 'nodeunit'; -import { App as Root } from '../lib'; +import { Stack } from '../lib'; import { capitalizePropertyNames, ignoreEmpty } from '../lib/util'; export = { 'capitalizeResourceProperties capitalizes all keys of an object (recursively) from camelCase to PascalCase'(test: Test) { - const c = new Root(); + const c = new Stack(); test.equal(capitalizePropertyNames(c, undefined), undefined); test.equal(capitalizePropertyNames(c, 12), 12); @@ -31,44 +31,44 @@ export = { 'ignoreEmpty': { '[]'(test: Test) { - const c = new Root(); - test.strictEqual(c.node.resolve(ignoreEmpty([])), undefined); + const stack = new Stack(); + test.strictEqual(stack.resolve(ignoreEmpty([])), undefined); test.done(); }, '{}'(test: Test) { - const c = new Root(); - test.strictEqual(c.node.resolve(ignoreEmpty({})), undefined); + const stack = new Stack(); + test.strictEqual(stack.resolve(ignoreEmpty({})), undefined); test.done(); }, 'undefined/null'(test: Test) { - const c = new Root(); - test.strictEqual(c.node.resolve(ignoreEmpty(undefined)), undefined); - test.strictEqual(c.node.resolve(ignoreEmpty(null)), null); + const stack = new Stack(); + test.strictEqual(stack.resolve(ignoreEmpty(undefined)), undefined); + test.strictEqual(stack.resolve(ignoreEmpty(null)), null); test.done(); }, 'primitives'(test: Test) { - const c = new Root(); - test.strictEqual(c.node.resolve(ignoreEmpty(12)), 12); - test.strictEqual(c.node.resolve(ignoreEmpty("12")), "12"); + const stack = new Stack(); + test.strictEqual(stack.resolve(ignoreEmpty(12)), 12); + test.strictEqual(stack.resolve(ignoreEmpty("12")), "12"); test.done(); }, 'non-empty arrays/objects'(test: Test) { - const c = new Root(); - test.deepEqual(c.node.resolve(ignoreEmpty([ 1, 2, 3, undefined ])), [ 1, 2, 3 ]); // undefined array values is cleaned up by "resolve" - test.deepEqual(c.node.resolve(ignoreEmpty({ o: 1, b: 2, j: 3 })), { o: 1, b: 2, j: 3 }); + const stack = new Stack(); + test.deepEqual(stack.resolve(ignoreEmpty([ 1, 2, 3, undefined ])), [ 1, 2, 3 ]); // undefined array values is cleaned up by "resolve" + test.deepEqual(stack.resolve(ignoreEmpty({ o: 1, b: 2, j: 3 })), { o: 1, b: 2, j: 3 }); test.done(); }, 'resolve first'(test: Test) { - const c = new Root(); - test.deepEqual(c.node.resolve(ignoreEmpty({ xoo: { resolve: () => 123 }})), { xoo: 123 }); - test.strictEqual(c.node.resolve(ignoreEmpty({ xoo: { resolve: () => undefined }})), undefined); - test.deepEqual(c.node.resolve(ignoreEmpty({ xoo: { resolve: () => [ ] }})), { xoo: [] }); - test.deepEqual(c.node.resolve(ignoreEmpty({ xoo: { resolve: () => [ undefined, undefined ] }})), { xoo: [] }); + const stack = new Stack(); + test.deepEqual(stack.resolve(ignoreEmpty({ xoo: { resolve: () => 123 }})), { xoo: 123 }); + test.strictEqual(stack.resolve(ignoreEmpty({ xoo: { resolve: () => undefined }})), undefined); + test.deepEqual(stack.resolve(ignoreEmpty({ xoo: { resolve: () => [ ] }})), { xoo: [] }); + test.deepEqual(stack.resolve(ignoreEmpty({ xoo: { resolve: () => [ undefined, undefined ] }})), { xoo: [] }); test.done(); } } diff --git a/packages/@aws-cdk/cdk/test/util.ts b/packages/@aws-cdk/cdk/test/util.ts new file mode 100644 index 0000000000000..33a55fc77dc98 --- /dev/null +++ b/packages/@aws-cdk/cdk/test/util.ts @@ -0,0 +1,5 @@ +import { ConstructNode, Stack } from '../lib'; + +export function toCloudFormation(stack: Stack): any { + return ConstructNode.synth(stack.node, { skipValidation: true }).getStack(stack.name).template; +} \ No newline at end of file diff --git a/tools/cfn2ts/lib/codegen.ts b/tools/cfn2ts/lib/codegen.ts index 23203cae0da9c..23d66fab92150 100644 --- a/tools/cfn2ts/lib/codegen.ts +++ b/tools/cfn2ts/lib/codegen.ts @@ -286,12 +286,12 @@ export default class CodeGenerator { if (spec.RequiredTransform) { const transformField = `${resourceName.className}.requiredTransform`; this.code.line('// If a different transform than the required one is in use, this resource cannot be used'); - this.code.openBlock(`if (this.node.stack.templateOptions.transform && this.node.stack.templateOptions.transform !== ${transformField})`); + this.code.openBlock(`if (this.stack.templateOptions.transform && this.stack.templateOptions.transform !== ${transformField})`); // tslint:disable-next-line:max-line-length - this.code.line(`throw new Error(\`The \${JSON.stringify(${transformField})} transform is required when using ${resourceName.className}, but the \${JSON.stringify(this.node.stack.templateOptions.transform)} is used.\`);`); + this.code.line(`throw new Error(\`The \${JSON.stringify(${transformField})} transform is required when using ${resourceName.className}, but the \${JSON.stringify(this.stack.templateOptions.transform)} is used.\`);`); this.code.closeBlock(); this.code.line('// Automatically configure the required transform'); - this.code.line(`this.node.stack.templateOptions.transform = ${resourceName.className}.requiredTransform;`); + this.code.line(`this.stack.templateOptions.transform = ${resourceName.className}.requiredTransform;`); } // initialize all attribute properties