Skip to content

Commit

Permalink
refactor(core): Misc cleanups to App-related APIs
Browse files Browse the repository at this point in the history
Ensure documentation coverage and clean up of the various APIs.

Fixes #1891


BREAKING CHANGE:
* **core:** `StackProps.autoDeploy` has been removed and replaced by `StackProps.hide` (with negated logic).
* **core:** `ISynthesizable.synthesize` now accepts an `ISynthesisSession` which contains the `CloudAssemblyBuilder` object.
* **cx-api:** Multiple changes to the cloud assembly APIs to reduce surface area and clean up.
  • Loading branch information
Elad Ben-Israel committed Jun 3, 2019
1 parent 4e12fe6 commit b81502e
Show file tree
Hide file tree
Showing 21 changed files with 388 additions and 194 deletions.
5 changes: 3 additions & 2 deletions packages/@aws-cdk/assert/lib/inspector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ export class StackPathInspector extends Inspector {
// The names of paths in metadata in tests are very ill-defined. Try with the full path first,
// then try with the stack name preprended for backwards compat with most tests that happen to give
// their stack an ID that's the same as the stack name.
const md = this.stack.metadata[this.path] || this.stack.metadata[`/${this.stack.name}${this.path}`];
const metadata = this.stack.manifest.metadata || {};
const md = metadata[this.path] || metadata[`/${this.stack.name}${this.path}`];
if (md === undefined) { return undefined; }
const resourceMd = md.find(entry => entry.type === 'aws:cdk:logicalId');
const resourceMd = md.find(entry => entry.type === api.LOGICAL_ID_METADATA_KEY);
if (resourceMd === undefined) { return undefined; }
const logicalId = resourceMd.data;
return this.stack.template.Resources[logicalId];
Expand Down
9 changes: 5 additions & 4 deletions packages/@aws-cdk/assets/lib/staging.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Construct } from '@aws-cdk/cdk';
import { Construct, ISynthesisSession, ISynthesizable } from '@aws-cdk/cdk';
import cxapi = require('@aws-cdk/cx-api');
import fs = require('fs');
import path = require('path');
Expand Down Expand Up @@ -26,7 +26,7 @@ export interface StagingProps extends CopyOptions {
* The file/directory are staged based on their content hash (fingerprint). This
* means that only if content was changed, copy will happen.
*/
export class Staging extends Construct {
export class Staging extends Construct implements ISynthesizable {

/**
* The path to the asset (stringinfied token).
Expand Down Expand Up @@ -66,12 +66,13 @@ export class Staging extends Construct {
}
}

protected synthesize(session: cxapi.CloudAssemblyBuilder) {
public synthesize(session: ISynthesisSession) {
const assembly = session.assembly;
if (!this.relativePath) {
return;
}

const targetPath = path.join(session.outdir, this.relativePath);
const targetPath = path.join(assembly.outdir, this.relativePath);

// asset already staged
if (fs.existsSync(targetPath)) {
Expand Down
5 changes: 3 additions & 2 deletions packages/@aws-cdk/assets/test/test.asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ export = {
});

const synth = app.run().getStack(stack.name);
test.deepEqual(synth.metadata['/my-stack/MyAsset'][0].data, {
const meta = synth.manifest.metadata || {};
test.deepEqual(meta['/my-stack/MyAsset'][0].data, {
path: 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2',
id: "mystackMyAssetD6B1B593",
packaging: "zip",
Expand Down Expand Up @@ -343,7 +344,7 @@ export = {
const session = app.run();
const artifact = session.getStack(stack.name);

const md = Object.values(artifact.metadata)[0][0].data;
const md = Object.values(artifact.manifest.metadata || {})[0][0].data;
test.deepEqual(md.path, 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2');
test.done();
}
Expand Down
48 changes: 33 additions & 15 deletions packages/@aws-cdk/cdk/lib/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import { Synthesizer } from './synthesis';
const APP_SYMBOL = Symbol.for('@aws-cdk/cdk.App');

/**
* Custom construction properties for a CDK program
* Initialization props for apps.
*/
export interface AppProps {
/**
* 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 (CDK_OUTDIR is set), false otherwise
* @default true if running via CDK toolkit (`CDK_OUTDIR` is set), false otherwise
*/
readonly autoRun?: boolean;

Expand All @@ -29,29 +29,48 @@ export interface AppProps {

/**
* Include stack traces in construct metadata entries.
* @default true stack traces are included
* @default true stack traces are included unless `aws:cdk:disable-stack-trace` is set in the context.
*/
readonly stackTraces?: boolean;

/**
* Include runtime versioning information in cloud assembly manifest
* @default true runtime info is included
* @default true runtime info is included unless `aws:cdk:disable-runtime-info` is set in the context.
*/
readonly runtimeInfo?: boolean;

/**
* Additional context values for the application
* Additional context values for the application.
*
* @default No additional context
* Context can be read from any construct using `node.getContext(key)`.
*
* @default - no additional context
*/
readonly context?: { [key: string]: string };
}

/**
* Represents a CDK program.
* A construct which represents an entire CDK app. This construct is normally
* the root of the construct tree.
*
* You would normally define an `App` instance in your program's entrypoint,
* then define constructs where the app is used as the parent scope.
*
* After all the child constructs are defined within the app, you should call
* `app.synth()` which will emit a "cloud assembly" from this app into the
* directory specified by `outdir`. Cloud assemblies includes artifacts such as
* CloudFormation templates and assets that are needed to deploy this app into
* the AWS cloud.
*
* @see https://docs.aws.amazon.com/cdk/latest/guide/apps_and_stacks.html
*/
export class App extends Construct {

/**
* Checks if an object is an instance of the `App` class.
* @returns `true` if `obj` is an `App`.
* @param obj The object to evaluate
*/
public static isApp(obj: any): obj is App {
return APP_SYMBOL in obj;
}
Expand All @@ -62,7 +81,7 @@ export class App extends Construct {

/**
* Initializes a CDK application.
* @param request Optional toolkit request (e.g. for tests)
* @param props initialization properties
*/
constructor(props: AppProps = {}) {
super(undefined as any, '');
Expand All @@ -84,20 +103,19 @@ export class App extends Construct {
this.outdir = props.outdir || process.env[cxapi.OUTDIR_ENV];

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.
// 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());
}
}

/**
* Runs the program. Output is written to output directory as specified in the
* request.
* Synthesizes a cloud assembly for this app. Emits it to the directory
* specified by `outdir`.
*
* @returns a `CloudAssembly` which includes all the synthesized artifacts
* such as CloudFormation templates and assets.
* @returns a `CloudAssembly` which can be used to inspect synthesized
* artifacts such as CloudFormation templates and assets.
*/
public run(): CloudAssembly {
// this app has already been executed, no-op for you
Expand Down
7 changes: 3 additions & 4 deletions packages/@aws-cdk/cdk/lib/cfn-element.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import cxapi = require('@aws-cdk/cx-api');
import { Construct, IConstruct, PATH_SEP } from "./construct";
import { Token } from './token';

const LOGICAL_ID_MD = 'aws:cdk:logicalId';

/**
* An element of a CloudFormation stack.
*/
Expand Down Expand Up @@ -43,7 +42,7 @@ export abstract class CfnElement extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);

this.node.addMetadata(LOGICAL_ID_MD, new (require("./token").Token)(() => this.logicalId), this.constructor);
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.logicalId = new Token(() => this._logicalId, `${notTooLong(this.node.path)}.LogicalID`).toString();
Expand All @@ -63,7 +62,7 @@ export abstract class CfnElement extends Construct {
* node +internal+ entries filtered.
*/
public get creationStackTrace(): string[] | undefined {
const trace = this.node.metadata.find(md => md.type === LOGICAL_ID_MD)!.trace;
const trace = this.node.metadata.find(md => md.type === cxapi.LOGICAL_ID_METADATA_KEY)!.trace;
if (!trace) {
return undefined;
}
Expand Down
41 changes: 23 additions & 18 deletions packages/@aws-cdk/cdk/lib/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { CfnParameter } from './cfn-parameter';
import { Construct, IConstruct, PATH_SEP } from './construct';
import { Environment } from './environment';
import { HashedAddressingScheme, IAddressingScheme, LogicalIDs } from './logical-id';
import { ISynthesisSession } from './synthesis';
import { makeUniqueId } from './uniqueid';

export interface StackProps {
/**
* The AWS environment (account/region) where this stack will be deployed.
Expand All @@ -31,15 +33,16 @@ export interface StackProps {
readonly namingScheme?: IAddressingScheme;

/**
* Should the Stack be deployed when running `cdk deploy` without arguments
* (and listed when running `cdk synth` without arguments).
* Setting this to `false` is useful when you have a Stack in your CDK app
* that you don't want to deploy using the CDK toolkit -
* for example, because you're planning on deploying it through CodePipeline.
* Indicates if this stack is hidden, which means that it won't be
* automatically deployed when running `cdk deploy` without arguments.
*
* Setting this to `true` is useful when you have a stack in your CDK app
* which you don't want to deploy using the CDK toolkit. For example, because
* you're planning on deploying it through a deployment pipeline.
*
* @default true
* @default false
*/
readonly autoDeploy?: boolean;
readonly hidden?: boolean;

/**
* Stack tags that will be applied to all the taggable resources and the stack itself.
Expand Down Expand Up @@ -116,15 +119,16 @@ export class Stack extends Construct implements ITaggable {
public readonly name: string;

/**
* Should the Stack be deployed when running `cdk deploy` without arguments
* (and listed when running `cdk synth` without arguments).
* Setting this to `false` is useful when you have a Stack in your CDK app
* that you don't want to deploy using the CDK toolkit -
* for example, because you're planning on deploying it through CodePipeline.
* Indicates if this stack is hidden, which means that it won't be
* automatically deployed when running `cdk deploy` without arguments.
*
* Setting this to `true` is useful when you have a stack in your CDK app
* which you don't want to deploy using the CDK toolkit. For example, because
* you're planning on deploying it through a deployment pipeline.
*
* By default, this is `true`.
* By default, all stacks are visible (this will be false).
*/
public readonly autoDeploy: boolean;
public readonly hidden: boolean;

/**
* Other stacks this stack depends on
Expand Down Expand Up @@ -159,9 +163,9 @@ export class Stack extends Construct implements ITaggable {
this.configuredEnv = props.env || {};
this.env = this.parseEnvironment(props.env);

this.logicalIds = new LogicalIDs(props && props.namingScheme ? props.namingScheme : new HashedAddressingScheme());
this.logicalIds = new LogicalIDs(props.namingScheme ? props.namingScheme : new HashedAddressingScheme());
this.name = props.stackName !== undefined ? props.stackName : this.calculateStackName();
this.autoDeploy = props && props.autoDeploy === false ? false : true;
this.hidden = props.hidden === undefined ? false : true;
this.tags = new TagManager(TagType.KeyValue, "aws:cdk:stack", props.tags);

if (!Stack.VALID_STACK_NAME_REGEX.test(this.name)) {
Expand Down Expand Up @@ -509,7 +513,8 @@ export class Stack extends Construct implements ITaggable {
}
}

protected synthesize(builder: cxapi.CloudAssemblyBuilder): void {
protected synthesize(session: ISynthesisSession): void {
const builder = session.assembly;
const template = `${this.name}.template.json`;

// write the CloudFormation template as a JSON file
Expand All @@ -529,7 +534,7 @@ export class Stack extends Construct implements ITaggable {
type: cxapi.ArtifactType.AwsCloudFormationStack,
environment: this.environment,
properties,
autoDeploy: this.autoDeploy ? undefined : false,
hidden: this.hidden ? true : undefined, // omit if stack is visible
dependencies: deps.length > 0 ? deps : undefined,
metadata: Object.keys(meta).length > 0 ? meta : undefined,
missing: this.missingContext && Object.keys(this.missingContext).length > 0 ? this.missingContext : undefined
Expand Down
15 changes: 11 additions & 4 deletions packages/@aws-cdk/cdk/lib/synthesis.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { BuildOptions, CloudAssembly, CloudAssemblyBuilder } from '@aws-cdk/cx-api';
import { ConstructOrder, IConstruct } from './construct';

export interface ISynthesisSession {
/**
* The cloud assembly being synthesized.
*/
assembly: CloudAssemblyBuilder;
}

export interface ISynthesizable {
synthesize(session: CloudAssemblyBuilder): void;
synthesize(session: ISynthesisSession): void;
}

export interface SynthesisOptions extends BuildOptions {
Expand All @@ -21,7 +28,7 @@ export interface SynthesisOptions extends BuildOptions {

export class Synthesizer {
public synthesize(root: IConstruct, options: SynthesisOptions = { }): CloudAssembly {
const session = new CloudAssemblyBuilder(options.outdir);
const builder = new CloudAssemblyBuilder(options.outdir);

// the three holy phases of synthesis: prepare, validate and synthesize

Expand All @@ -41,12 +48,12 @@ export class Synthesizer {
// synthesize (leaves first)
for (const c of root.node.findAll(ConstructOrder.PostOrder)) {
if (isSynthesizable(c)) {
c.synthesize(session);
c.synthesize({ assembly: builder });
}
}

// write session manifest and lock store
return session.build(options);
return builder.build(options);
}
}

Expand Down
6 changes: 3 additions & 3 deletions packages/@aws-cdk/cdk/test/test.app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export = {
test.deepEqual(stack1.template, { Resources:
{ s1c1: { Type: 'DummyResource', Properties: { Prop1: 'Prop1' } },
s1c2: { Type: 'DummyResource', Properties: { Foo: 123 } } } });
test.deepEqual(stack1.metadata, {
test.deepEqual(stack1.manifest.metadata, {
'/stack1': [{ type: 'meta', data: 111 }],
'/stack1/s1c1': [{ type: 'aws:cdk:logicalId', data: 's1c1' }],
'/stack1/s1c2':
Expand All @@ -77,7 +77,7 @@ export = {
{ s2c1: { Type: 'DummyResource', Properties: { Prog2: 'Prog2' } },
s1c2r1D1791C01: { Type: 'ResourceType1' },
s1c2r25F685FFF: { Type: 'ResourceType2' } } });
test.deepEqual(stack2.metadata, {
test.deepEqual(stack2.manifest.metadata, {
'/stack2/s2c1': [{ type: 'aws:cdk:logicalId', data: 's2c1' }],
'/stack2/s1c2': [{ type: 'meta', data: { key: 'value' } }],
'/stack2/s1c2/r1':
Expand Down Expand Up @@ -193,7 +193,7 @@ export = {
new MyStack(app, 'MyStack');
});

test.deepEqual(response.stacks[0].missing, {
test.deepEqual(response.missing, {
"missing-context-key": {
provider: 'fake',
props: {
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/cdk/test/test.context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export = {

const assembly = app.run();
const output = assembly.getStack('test-stack');
const metadata = output.metadata;
const metadata = output.manifest.metadata || {};
const azError: cxapi.MetadataEntry | undefined = metadata['/test-stack'].find(x => x.type === cxapi.ERROR_METADATA_KEY);
const ssmError: cxapi.MetadataEntry | undefined = metadata['/test-stack/ChildConstruct'].find(x => x.type === cxapi.ERROR_METADATA_KEY);

Expand Down
Loading

0 comments on commit b81502e

Please sign in to comment.