Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): can use Constructs to model collections of Stacks #1940

Merged
merged 12 commits into from
Mar 20, 2019
9,897 changes: 0 additions & 9,897 deletions package-lock.json

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,11 @@
{
"Ref": "AWS::Partition"
},
":cloudformation:us-west-2:12345678:stack/aws-cdk-codepipeline-cross-region-deploy-stack/*"
":cloudformation:us-west-2:",
rix0rrr marked this conversation as resolved.
Show resolved Hide resolved
{
"Ref": "AWS::AccountId"
},
":stack/aws-cdk-codepipeline-cross-region-deploy-stack/*"
]
]
}
Expand Down
33 changes: 31 additions & 2 deletions packages/@aws-cdk/cdk/lib/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@ import cxapi = require('@aws-cdk/cx-api');
import { ConstructOrder, Root } from './construct';
import { FileSystemStore, InMemoryStore, ISynthesisSession, SynthesisSession } from './synthesis';

/**
* Custom construction properties for a CDK program
*/
export interface AppProps {
rix0rrr marked this conversation as resolved.
Show resolved Hide resolved
/**
* Automatically call run before the application exits
*
* If you set this, you don't have to call `run()` anymore.
*
* @default true if running via CDK toolkit, false otherwise
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be more specific about the mechanism: true if running via the toolkit (CDK_OUTDIR is set)

*/
autoRun?: boolean;

/**
* Additional context values for the application
*
* @default No additional context
*/
context?: { [key: string]: string };
}

/**
* Represents a CDK program.
*/
Expand All @@ -14,13 +35,21 @@ export class App extends Root {
* Initializes a CDK application.
* @param request Optional toolkit request (e.g. for tests)
*/
constructor(context?: { [key: string]: string }) {
constructor(props: AppProps = {}) {
super();
this.loadContext(context);
this.loadContext(props.context);

// both are reverse logic
this.legacyManifest = this.node.getContext(cxapi.DISABLE_LEGACY_MANIFEST_CONTEXT) ? false : true;
this.runtimeInformation = this.node.getContext(cxapi.DISABLE_VERSION_REPORTING) ? false : true;

const autoRun = props.autoRun !== undefined ? props.autoRun : cxapi.OUTDIR_ENV in process.env;

if (autoRun) {
// run() guarantuees it will only execute once, so a default of 'true' doesn't bite manual calling
// of the function.
process.once('beforeExit', () => this.run());
}
}

/**
Expand Down
16 changes: 4 additions & 12 deletions packages/@aws-cdk/cdk/lib/construct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,12 @@ export class ConstructNode {
}

/**
* The full path of this construct in the tree.
* The full, absolute path of this construct in the tree.
*
* Components are separated by '/'.
*/
public get path(): string {
const components = this.rootPath().map(c => c.node.id);
const components = this.ancestors().slice(1).map(c => c.node.id);
return components.join(PATH_SEP);
}

Expand All @@ -119,7 +120,7 @@ export class ConstructNode {
* Includes all components of the tree.
*/
public get uniqueId(): string {
const components = this.rootPath().map(c => c.node.id);
const components = this.ancestors().slice(1).map(c => c.node.id);
return components.length > 0 ? makeUniqueId(components) : '';
}

Expand Down Expand Up @@ -547,15 +548,6 @@ export class ConstructNode {
}
}

/**
* Return the path of components up to but excluding the root
*/
private rootPath(): IConstruct[] {
const ancestors = this.ancestors();
ancestors.shift();
return ancestors;
}

/**
* If the construct ID contains a path separator, it is replaced by double dash (`--`).
*/
Expand Down
66 changes: 51 additions & 15 deletions packages/@aws-cdk/cdk/lib/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Environment } from './environment';
import { HashedAddressingScheme, IAddressingScheme, LogicalIDs } from './logical-id';
import { Reference } from './reference';
import { ISynthesisSession } from './synthesis';
import { makeUniqueId } from './uniqueid';

export interface StackProps {
/**
Expand All @@ -16,6 +17,13 @@ export interface StackProps {
*/
env?: Environment;

/**
* Name to deploy the stack with
*
* @default Derived from construct path
*/
stackName?: string;

/**
* Strategy for logical ID generation
*
Expand Down Expand Up @@ -94,25 +102,33 @@ export class Stack extends Construct {
*/
private readonly parameterValues: { [logicalId: string]: string } = { };

/**
* Environment as configured via props
*
* (Both on Stack and inherited from App)
*/
private readonly configuredEnv: Environment;

/**
* Creates a new stack.
*
* @param scope Parent of this stack, usually a Program instance.
* @param name The name of the CloudFormation stack. Defaults to "Stack".
* @param props Stack properties.
*/
public constructor(scope?: App, name?: string, private readonly props?: StackProps) {
public constructor(scope?: Construct, name?: string, props: StackProps = {}) {
// For unit test convenience parents are optional, so bypass the type check when calling the parent.
super(scope!, name!);

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}'`);
}

this.env = this.parseEnvironment(props);
this.configuredEnv = props.env || {};
this.env = this.parseEnvironment(props.env);

this.logicalIds = new LogicalIDs(props && props.namingScheme ? props.namingScheme : new HashedAddressingScheme());
this.name = this.node.id;
this.name = props.stackName !== undefined ? props.stackName : this.calculateStackName();
}

/**
Expand Down Expand Up @@ -263,8 +279,8 @@ export class Stack extends Construct {
* to the correct account at deployment time.
*/
public get accountId(): string {
if (this.props && this.props.env && this.props.env.account) {
return this.props.env.account;
if (this.configuredEnv.account) {
return this.configuredEnv.account;
}
// Does not need to be scoped, the only situation in which
// Export/Fn::ImportValue would work if { Ref: "AWS::AccountId" } is the
Expand All @@ -280,8 +296,8 @@ export class Stack extends Construct {
* to the correct region at deployment time.
*/
public get region(): string {
if (this.props && this.props.env && this.props.env.region) {
return this.props.env.region;
if (this.configuredEnv.region) {
return this.configuredEnv.region;
}
// Does not need to be scoped, the only situation in which
// Export/Fn::ImportValue would work if { Ref: "AWS::AccountId" } is the
Expand Down Expand Up @@ -484,21 +500,20 @@ export class Stack extends Construct {
/**
* Applied defaults to environment attributes.
*/
private parseEnvironment(props?: StackProps) {
// start with `env`.
const env: Environment = (props && props.env) || { };
private parseEnvironment(env: Environment = {}) {
const ret: Environment = {...env};

// if account is not specified, attempt to read from context.
if (!env.account) {
env.account = this.node.getContext(cxapi.DEFAULT_ACCOUNT_CONTEXT_KEY);
if (!ret.account) {
ret.account = this.node.getContext(cxapi.DEFAULT_ACCOUNT_CONTEXT_KEY);
}

// if region is not specified, attempt to read from context.
if (!env.region) {
env.region = this.node.getContext(cxapi.DEFAULT_REGION_CONTEXT_KEY);
if (!ret.region) {
ret.region = this.node.getContext(cxapi.DEFAULT_REGION_CONTEXT_KEY);
}

return env;
return ret;
}

/**
Expand Down Expand Up @@ -535,6 +550,27 @@ export class Stack extends Construct {
}
}
}

/**
* Calculcate the stack name based on the construct path
*/
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 ids = rootPath.map(c => c.node.id);

// Special case, if rootPath is length 1 then just use ID (backwards compatibility)
// otherwise use a unique stack name (including hash). This logic is already
// in makeUniqueId, *however* makeUniqueId will also strip dashes from the name,
// which *are* allowed and also used, so we short-circuit it.
if (ids.length === 1) {
// Could be empty in a unit test, so just pretend it's named "Stack" then
return ids[0] || 'Stack';
}

return makeUniqueId(ids);
}
}

function merge(template: any, part: any) {
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/cdk/test/test.app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CfnResource, Construct, Stack, StackProps } from '../lib';
import { App } from '../lib/app';

function withApp(context: { [key: string]: any } | undefined, block: (app: App) => void): cxapi.SynthesizeResponse {
const app = new App(context);
const app = new App({ context, autoRun: false });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make autoRun: false the default when running outside the context of the toolkit (e.g. CDK_OUT is not set)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

autoRun false is not needed now i guess


block(app);

Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/cdk/test/test.output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ 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');
test.deepEqual(stack.node.resolve(output.makeImportValue()), { 'Fn::ImportValue': 'MyOutput' });
test.deepEqual(stack.node.resolve(output.makeImportValue()), { 'Fn::ImportValue': 'Stack:MyOutput' });
test.done();
},

Expand Down
24 changes: 23 additions & 1 deletion packages/@aws-cdk/cdk/test/test.stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,29 @@ export = {
{ RefToBonjour: { Ref: 'BOOM' },
GetAttBonjour: { 'Fn::GetAtt': [ 'BOOM', 'TheAtt' ] } } } } });
test.done();
}
},

'Stack name can be overridden via properties'(test: Test) {
// WHEN
const stack = new Stack(undefined, 'Stack', { stackName: 'otherName' });

// THEN
test.deepEqual(stack.name, 'otherName');

test.done();
},

'Stack name is inherited from App name if available'(test: Test) {
// WHEN
const root = new App();
const app = new Construct(root, 'Prod');
const stack = new Stack(app, 'Stack');

// THEN
test.deepEqual(stack.name, 'ProdStackD5279B22');

test.done();
},
};

class StackWithPostProcessor extends Stack {
Expand Down
6 changes: 4 additions & 2 deletions packages/@aws-cdk/cdk/test/test.synthesis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ const storeTestMatrix: any = {};

function createModernApp() {
return new cdk.App({
[cxapi.DISABLE_LEGACY_MANIFEST_CONTEXT]: 'true',
[cxapi.DISABLE_VERSION_REPORTING]: 'true', // for test reproducibility
context: {
[cxapi.DISABLE_LEGACY_MANIFEST_CONTEXT]: 'true',
[cxapi.DISABLE_VERSION_REPORTING]: 'true', // for test reproducibility
}
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ import { %name.PascalCased%Stack } from '../lib/%name%-stack';

const app = new cdk.App();
new %name.PascalCased%Stack(app, '%name.PascalCased%Stack');
app.run();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about other langs?

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import cdk = require('@aws-cdk/cdk');

export class %name.PascalCased%Stack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

// The code that defines your stack goes here
Expand Down
Loading