Skip to content

Commit 47afdc2

Browse files
author
Elad Ben-Israel
authored
refactor(core): revisit the Stack API (#2818)
Fixes #2728 BREAKING CHANGE: multiple changes to the `Stack` API * **core:** `stack.name` renamed to `stack.stackName` * **core:** `stack.stackName` will return the concrete stack name. Use `Aws.stackName` to indicate { Ref: "AWS::StackName" }. * **core:** `stack.account` and `stack.region` will return the concrete account/region only if they are explicitly specified when the stack is defined (under the `env` prop). Otherwise, they will return a token that resolves to the AWS::AccountId and AWS::Region intrinsic references. Use `Context.getDefaultAccount()` and `Context.getDefaultRegion()` to obtain the defaults passed through the toolkit in case those are needed. Use `Token.isUnresolved(v)` to check if you have a concrete or intrinsic. * **core:** `stack.logicalId` has been removed. Use `stack.getLogicalId()` * **core:** `stack.env` has been removed, use `stack.account`, `stack.region` and `stack.environment` instead * **core:** `stack.accountId` renamed to `stack.account` (to allow treating account more abstractly) * **core:** `AvailabilityZoneProvider` can now be accessed through `Context.getAvailabilityZones()` * **core:** `SSMParameterProvider` can now be accessed through `Context.getSsmParameter()` * **core:** `parseArn` is now `Arn.parse` * **core:** `arnFromComponents` is now `arn.format` * **core:** `node.lock` and `node.unlock` are now private * **core:** `stack.requireRegion` and `requireAccountId` have been removed. Use `Token.unresolved(stack.region)` instead * **core:** `stack.parentApp` have been removed. Use `App.isApp(stack.node.root)` instead. * **core:** `stack.missingContext` is now private * **core:** `stack.renameLogical` have been renamed to `stack.renameLogicalId` * **core:** `IAddressingScheme`, `HashedAddressingScheme` and `LogicalIDs` are now internal. Override `Stack.allocateLogicalId` to customize how logical IDs are allocated to resources.
1 parent 0f30e39 commit 47afdc2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+771
-798
lines changed

packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export class PipelineDeployStackAction extends cdk.Construct {
105105
constructor(scope: cdk.Construct, id: string, props: PipelineDeployStackActionProps) {
106106
super(scope, id);
107107

108-
if (!cdk.environmentEquals(props.stack.env, cdk.Stack.of(this).env)) {
108+
if (props.stack.environment !== cdk.Stack.of(this).environment) {
109109
// FIXME: Add the necessary to extend to stacks in a different account
110110
throw new Error(`Cross-environment deployment is not supported`);
111111
}
@@ -125,8 +125,8 @@ export class PipelineDeployStackAction extends cdk.Construct {
125125
actionName: 'ChangeSet',
126126
changeSetName,
127127
runOrder: createChangeSetRunOrder,
128-
stackName: props.stack.name,
129-
templatePath: props.input.atPath(`${props.stack.name}.template.yaml`),
128+
stackName: props.stack.stackName,
129+
templatePath: props.input.atPath(`${props.stack.stackName}.template.yaml`),
130130
adminPermissions: props.adminPermissions,
131131
deploymentRole: props.role,
132132
capabilities,
@@ -136,7 +136,7 @@ export class PipelineDeployStackAction extends cdk.Construct {
136136
actionName: 'Execute',
137137
changeSetName,
138138
runOrder: executeChangeSetRunOrder,
139-
stackName: props.stack.name,
139+
stackName: props.stack.stackName,
140140
}));
141141

142142
this.deploymentRole = changeSetAction.deploymentRole;
@@ -160,7 +160,7 @@ export class PipelineDeployStackAction extends cdk.Construct {
160160
const assets = this.stack.node.metadata.filter(md => md.type === cxapi.ASSET_METADATA);
161161
if (assets.length > 0) {
162162
// FIXME: Implement the necessary actions to publish assets
163-
result.push(`Cannot deploy the stack ${this.stack.name} because it references ${assets.length} asset(s)`);
163+
result.push(`Cannot deploy the stack ${this.stack.stackName} because it references ${assets.length} asset(s)`);
164164
}
165165
return result;
166166
}

packages/@aws-cdk/assert/lib/synth-utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export class SynthUtils {
99
// always synthesize against the root (be it an App or whatever) so all artifacts will be included
1010
const root = stack.node.root;
1111
const assembly = ConstructNode.synth(root.node, options);
12-
return assembly.getStack(stack.name);
12+
return assembly.getStack(stack.stackName);
1313
}
1414

1515
/**

packages/@aws-cdk/assert/test/test.assertions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ function synthesizedStack(fn: (stack: cdk.Stack) => void): cx.CloudFormationStac
210210
fn(stack);
211211

212212
const assembly = app.synth();
213-
return assembly.getStack(stack.name);
213+
return assembly.getStack(stack.stackName);
214214
}
215215

216216
interface TestResourceProps extends cdk.CfnResourceProps {

packages/@aws-cdk/assets/test/test.asset.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export = {
5757
path: dirPath
5858
});
5959

60-
const synth = app.synth().getStack(stack.name);
60+
const synth = app.synth().getStack(stack.stackName);
6161
const meta = synth.manifest.metadata || {};
6262
test.ok(meta['/my-stack/MyAsset']);
6363
test.ok(meta['/my-stack/MyAsset'][0]);
@@ -344,7 +344,7 @@ export = {
344344

345345
// WHEN
346346
const session = app.synth();
347-
const artifact = session.getStack(stack.name);
347+
const artifact = session.getStack(stack.stackName);
348348
const metadata = artifact.manifest.metadata || {};
349349
const md = Object.values(metadata)[0]![0]!.data;
350350
test.deepEqual(md.path, 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2');

packages/@aws-cdk/aws-apigateway/lib/deployment.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class LatestDeploymentResource extends CfnDeployment {
9999
constructor(scope: Construct, id: string, props: CfnDeploymentProps) {
100100
super(scope, id, props);
101101

102-
this.originalLogicalId = Stack.of(this).logicalIds.getLogicalId(this);
102+
this.originalLogicalId = Stack.of(this).getLogicalId(this);
103103
}
104104

105105
/**

packages/@aws-cdk/aws-cloudtrail/lib/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export class Trail extends Resource {
136136
.addServicePrincipal(cloudTrailPrincipal));
137137

138138
s3bucket.addToResourcePolicy(new iam.PolicyStatement()
139-
.addResource(s3bucket.arnForObjects(`AWSLogs/${Stack.of(this).accountId}/*`))
139+
.addResource(s3bucket.arnForObjects(`AWSLogs/${Stack.of(this).account}/*`))
140140
.addActions("s3:PutObject")
141141
.addServicePrincipal(cloudTrailPrincipal)
142142
.setCondition("StringEquals", {'s3:x-amz-acl': "bucket-owner-full-control"}));

packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import iam = require('@aws-cdk/aws-iam');
77
import lambda = require('@aws-cdk/aws-lambda');
88
import s3 = require('@aws-cdk/aws-s3');
99
import sns = require('@aws-cdk/aws-sns');
10-
import { App, CfnParameter, ConstructNode, PhysicalName, SecretValue, Stack } from '@aws-cdk/cdk';
10+
import { App, Aws, CfnParameter, ConstructNode, PhysicalName, SecretValue, Stack } from '@aws-cdk/cdk';
1111
import { Test } from 'nodeunit';
1212
import cpactions = require('../lib');
1313

@@ -56,7 +56,7 @@ export = {
5656
const stack = new Stack(undefined, 'StackName');
5757

5858
new codepipeline.Pipeline(stack, 'Pipeline', {
59-
pipelineName: stack.stackName,
59+
pipelineName: Aws.stackName,
6060
});
6161

6262
expect(stack, true).to(haveResourceLike('AWS::CodePipeline::Pipeline', {
@@ -695,8 +695,8 @@ export = {
695695

696696
const usEast1ScaffoldStack = pipeline.crossRegionScaffolding['us-east-1'];
697697
test.notEqual(usEast1ScaffoldStack, undefined);
698-
test.equal(usEast1ScaffoldStack.env.region, 'us-east-1');
699-
test.equal(usEast1ScaffoldStack.env.account, pipelineAccount);
698+
test.equal(usEast1ScaffoldStack.region, 'us-east-1');
699+
test.equal(usEast1ScaffoldStack.account, pipelineAccount);
700700
test.ok(usEast1ScaffoldStack.node.id.indexOf('us-east-1') !== -1,
701701
`expected '${usEast1ScaffoldStack.node.id}' to contain 'us-east-1'`);
702702

packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts

+22-12
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import events = require('@aws-cdk/aws-events');
22
import iam = require('@aws-cdk/aws-iam');
33
import kms = require('@aws-cdk/aws-kms');
44
import s3 = require('@aws-cdk/aws-s3');
5-
import { Construct, Lazy, PhysicalName, RemovalPolicy, Resource, Stack } from '@aws-cdk/cdk';
5+
import { App, Construct, Lazy, PhysicalName, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/cdk';
66
import { Action, IPipeline, IStage } from "./action";
77
import { CfnPipeline } from './codepipeline.generated';
88
import { Stage } from './stage';
@@ -364,14 +364,21 @@ export class Pipeline extends PipelineBase {
364364
});
365365
}
366366

367+
private requireRegion() {
368+
const region = Stack.of(this).region;
369+
if (Token.isUnresolved(region)) {
370+
throw new Error(`You need to specify an explicit region when using CodePipeline's cross-region support`);
371+
}
372+
return region;
373+
}
374+
367375
private ensureReplicationBucketExistsFor(region?: string) {
368376
if (!region) {
369377
return;
370378
}
371379

372380
// get the region the Pipeline itself is in
373-
const pipelineRegion = Stack.of(this).requireRegion(
374-
"You need to specify an explicit region when using CodePipeline's cross-region support");
381+
const pipelineRegion = this.requireRegion();
375382

376383
// if we already have an ArtifactStore generated for this region, or it's the Pipeline's region, nothing to do
377384
if (this.artifactStores[region] || region === pipelineRegion) {
@@ -380,11 +387,14 @@ export class Pipeline extends PipelineBase {
380387

381388
let replicationBucketName = this.crossRegionReplicationBuckets[region];
382389
if (!replicationBucketName) {
383-
const pipelineAccount = Stack.of(this).requireAccountId(
384-
"You need to specify an explicit account when using CodePipeline's cross-region support");
385-
const app = Stack.of(this).parentApp();
386-
if (!app) {
387-
throw new Error(`Pipeline stack which uses cross region actions must be part of an application`);
390+
const pipelineAccount = Stack.of(this).account;
391+
if (Token.isUnresolved(pipelineAccount)) {
392+
throw new Error("You need to specify an explicit account when using CodePipeline's cross-region support");
393+
}
394+
395+
const app = this.node.root;
396+
if (!app || !App.isApp(app)) {
397+
throw new Error(`Pipeline stack which uses cross region actions must be part of a CDK app`);
388398
}
389399
const crossRegionScaffoldStack = new CrossRegionScaffoldStack(this, `cross-region-stack-${pipelineAccount}:${region}`, {
390400
region,
@@ -421,8 +431,7 @@ export class Pipeline extends PipelineBase {
421431
const pipelineStack = Stack.of(this);
422432
const resourceStack = Stack.of(action.resource);
423433
// check if resource is from a different account
424-
if (pipelineStack.env.account && resourceStack.env.account &&
425-
pipelineStack.env.account !== resourceStack.env.account) {
434+
if (pipelineStack.environment !== resourceStack.environment) {
426435
// if it is, the pipeline's bucket must have a KMS key
427436
if (!this.artifactBucket.encryptionKey) {
428437
throw new Error('The Pipeline is being used in a cross-account manner, ' +
@@ -434,7 +443,7 @@ export class Pipeline extends PipelineBase {
434443
// generate a role in the other stack, that the Pipeline will assume for executing this action
435444
actionRole = new iam.Role(resourceStack,
436445
`${this.node.uniqueId}-${stage.stageName}-${action.actionName}-ActionRole`, {
437-
assumedBy: new iam.AccountPrincipal(pipelineStack.env.account),
446+
assumedBy: new iam.AccountPrincipal(pipelineStack.account),
438447
roleName: PhysicalName.auto({ crossEnvironment: true }),
439448
});
440449

@@ -555,7 +564,8 @@ export class Pipeline extends PipelineBase {
555564

556565
// add the Pipeline's artifact store
557566
const primaryStore = this.renderPrimaryArtifactStore();
558-
this.artifactStores[Stack.of(this).requireRegion()] = {
567+
const primaryRegion = this.requireRegion();
568+
this.artifactStores[primaryRegion] = {
559569
location: primaryStore.location,
560570
type: primaryStore.type,
561571
encryptionKey: primaryStore.encryptionKey,

packages/@aws-cdk/aws-ec2/lib/machine-image.ts

+8-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Construct, SSMParameterProvider, Stack } from '@aws-cdk/cdk';
1+
import { Construct, Context, Stack, Token } from '@aws-cdk/cdk';
22

33
/**
44
* Interface for classes that can select an appropriate machine image to use
@@ -25,11 +25,7 @@ export class WindowsImage implements IMachineImageSource {
2525
* Return the image to use in the given context
2626
*/
2727
public getImage(scope: Construct): MachineImage {
28-
const ssmProvider = new SSMParameterProvider(scope, {
29-
parameterName: this.imageParameterName(this.version),
30-
});
31-
32-
const ami = ssmProvider.parameterValue();
28+
const ami = Context.getSsmParameter(scope, this.imageParameterName(this.version));
3329
return new MachineImage(ami, new WindowsOS());
3430
}
3531

@@ -106,11 +102,7 @@ export class AmazonLinuxImage implements IMachineImageSource {
106102
].filter(x => x !== undefined); // Get rid of undefineds
107103

108104
const parameterName = '/aws/service/ami-amazon-linux-latest/' + parts.join('-');
109-
110-
const ssmProvider = new SSMParameterProvider(scope, {
111-
parameterName,
112-
});
113-
const ami = ssmProvider.parameterValue();
105+
const ami = Context.getSsmParameter(scope, parameterName);
114106
return new MachineImage(ami, new LinuxOS());
115107
}
116108
}
@@ -188,7 +180,11 @@ export class GenericLinuxImage implements IMachineImageSource {
188180
}
189181

190182
public getImage(scope: Construct): MachineImage {
191-
const region = Stack.of(scope).requireRegion('AMI cannot be determined');
183+
let region = Stack.of(scope).region;
184+
if (Token.isUnresolved(region)) {
185+
region = Context.getDefaultRegion(scope);
186+
}
187+
192188
const ami = region !== 'test-region' ? this.amiMap[region] : 'ami-12345';
193189
if (!ami) {
194190
throw new Error(`Unable to find AMI in AMI map: no AMI specified for region '${region}'`);

packages/@aws-cdk/aws-ec2/lib/vpc.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -789,8 +789,7 @@ export class Vpc extends VpcBase {
789789

790790
this.node.applyAspect(new cdk.Tag(NAME_TAG, this.node.path));
791791

792-
this.availabilityZones = new cdk.AvailabilityZoneProvider(this).availabilityZones;
793-
this.availabilityZones.sort();
792+
this.availabilityZones = cdk.Context.getAvailabilityZones(this);
794793

795794
const maxAZs = props.maxAZs !== undefined ? props.maxAZs : 3;
796795
this.availabilityZones = this.availabilityZones.slice(0, maxAZs);
@@ -946,10 +945,6 @@ export class Vpc extends VpcBase {
946945
private createSubnets() {
947946
const remainingSpaceSubnets: SubnetConfiguration[] = [];
948947

949-
// Calculate number of public/private subnets based on number of AZs
950-
const zones = new cdk.AvailabilityZoneProvider(this).availabilityZones;
951-
zones.sort();
952-
953948
for (const subnet of this.subnetConfiguration) {
954949
if (subnet.cidrMask === undefined) {
955950
remainingSpaceSubnets.push(subnet);

packages/@aws-cdk/aws-ec2/test/test.vpc.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { countResources, expect, haveResource, haveResourceLike, isSuperObject } from '@aws-cdk/assert';
2-
import { AvailabilityZoneProvider, Construct, Stack, Tag } from '@aws-cdk/cdk';
2+
import { Construct, Context, Stack, Tag } from '@aws-cdk/cdk';
33
import { Test } from 'nodeunit';
44
import { CfnVPC, DefaultInstanceTenancy, IVpc, SubnetType, Vpc } from '../lib';
55
import { exportVpc } from './export-helper';
@@ -63,7 +63,7 @@ export = {
6363
"contains the correct number of subnets"(test: Test) {
6464
const stack = getTestStack();
6565
const vpc = new Vpc(stack, 'TheVPC');
66-
const zones = new AvailabilityZoneProvider(stack).availabilityZones.length;
66+
const zones = Context.getAvailabilityZones(stack).length;
6767
test.equal(vpc.publicSubnets.length, zones);
6868
test.equal(vpc.privateSubnets.length, zones);
6969
test.deepEqual(stack.resolve(vpc.vpcId), { Ref: 'TheVPC92636AB0' });
@@ -109,7 +109,7 @@ export = {
109109

110110
"with no subnets defined, the VPC should have an IGW, and a NAT Gateway per AZ"(test: Test) {
111111
const stack = getTestStack();
112-
const zones = new AvailabilityZoneProvider(stack).availabilityZones.length;
112+
const zones = Context.getAvailabilityZones(stack).length;
113113
new Vpc(stack, 'TheVPC', { });
114114
expect(stack).to(countResources("AWS::EC2::InternetGateway", 1));
115115
expect(stack).to(countResources("AWS::EC2::NatGateway", zones));
@@ -186,7 +186,7 @@ export = {
186186
},
187187
"with custom subnets, the VPC should have the right number of subnets, an IGW, and a NAT Gateway per AZ"(test: Test) {
188188
const stack = getTestStack();
189-
const zones = new AvailabilityZoneProvider(stack).availabilityZones.length;
189+
const zones = Context.getAvailabilityZones(stack).length;
190190
new Vpc(stack, 'TheVPC', {
191191
cidr: '10.0.0.0/21',
192192
subnetConfiguration: [

packages/@aws-cdk/aws-ecs/lib/cluster.ts

+2-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import cloudwatch = require ('@aws-cdk/aws-cloudwatch');
33
import ec2 = require('@aws-cdk/aws-ec2');
44
import iam = require('@aws-cdk/aws-iam');
55
import cloudmap = require('@aws-cdk/aws-servicediscovery');
6-
import { Construct, IResource, Resource, SSMParameterProvider, Stack } from '@aws-cdk/cdk';
6+
import { Construct, Context, IResource, Resource, Stack } from '@aws-cdk/cdk';
77
import { InstanceDrainHook } from './drain-hook/instance-drain-hook';
88
import { CfnCluster } from './ecs.generated';
99

@@ -268,13 +268,8 @@ export class EcsOptimizedAmi implements ec2.IMachineImageSource {
268268
* Return the correct image
269269
*/
270270
public getImage(scope: Construct): ec2.MachineImage {
271-
const ssmProvider = new SSMParameterProvider(scope, {
272-
parameterName: this.amiParameterName
273-
});
274-
275-
const json = ssmProvider.parameterValue("{\"image_id\": \"\"}");
271+
const json = Context.getSsmParameter(scope, this.amiParameterName, { defaultValue: "{\"image_id\": \"\"}" });
276272
const ami = JSON.parse(json).image_id;
277-
278273
return new ec2.MachineImage(ami, new ec2.LinuxOS());
279274
}
280275
}

packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch');
22
import ec2 = require('@aws-cdk/aws-ec2');
33
import iam = require('@aws-cdk/aws-iam');
44
import s3 = require('@aws-cdk/aws-s3');
5-
import { Construct, Lazy, Resource, Stack } from '@aws-cdk/cdk';
5+
import { Construct, Lazy, Resource, Stack, Token } from '@aws-cdk/cdk';
66
import { BaseLoadBalancer, BaseLoadBalancerProps, ILoadBalancerV2 } from '../shared/base-load-balancer';
77
import { IpAddressType } from '../shared/enums';
88
import { ApplicationListener, BaseApplicationListenerProps } from './application-listener';
@@ -86,7 +86,11 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic
8686
this.setAttribute('access_logs.s3.bucket', bucket.bucketName.toString());
8787
this.setAttribute('access_logs.s3.prefix', prefix);
8888

89-
const region = Stack.of(this).requireRegion('Enable ELBv2 access logging');
89+
const region = Stack.of(this).region;
90+
if (Token.isUnresolved(region)) {
91+
throw new Error(`Region is required to enable ELBv2 access logging`);
92+
}
93+
9094
const account = ELBV2_ACCOUNTS[region];
9195
if (!account) {
9296
throw new Error(`Cannot enable access logging; don't know ELBv2 account for region ${region}`);

packages/@aws-cdk/aws-glue/lib/database.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class Database extends Resource implements IDatabase {
5454
public databaseArn = databaseArn;
5555
public databaseName = stack.parseArn(databaseArn).resourceName!;
5656
public catalogArn = stack.formatArn({ service: 'glue', resource: 'catalog' });
57-
public catalogId = stack.accountId;
57+
public catalogId = stack.account;
5858
}
5959

6060
return new Import(scope, id);
@@ -95,7 +95,7 @@ export class Database extends Resource implements IDatabase {
9595
this.locationUri = `s3://${bucket.bucketName}/${props.databaseName}`;
9696
}
9797

98-
this.catalogId = Stack.of(this).accountId;
98+
this.catalogId = Stack.of(this).account;
9999
const resource = new CfnDatabase(this, 'Resource', {
100100
catalogId: this.catalogId,
101101
databaseInput: {

packages/@aws-cdk/aws-iam/lib/principals.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ export class FederatedPrincipal extends PrincipalBase {
232232

233233
export class AccountRootPrincipal extends AccountPrincipal {
234234
constructor() {
235-
super(new StackDependentToken(stack => stack.accountId).toString());
235+
super(new StackDependentToken(stack => stack.account).toString());
236236
}
237237

238238
public toString() {

packages/@aws-cdk/aws-kms/test/integ.key.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const key = new Key(stack, 'MyKey', { retain: false });
1111
key.addToResourcePolicy(new PolicyStatement()
1212
.addAllResources()
1313
.addAction('kms:encrypt')
14-
.addAwsPrincipal(stack.accountId));
14+
.addAwsPrincipal(stack.account));
1515

1616
key.addAlias('alias/bar');
1717

0 commit comments

Comments
 (0)