Skip to content

Commit

Permalink
feat(core): overrideLogicalId: override IDs of CFN elements
Browse files Browse the repository at this point in the history
Allow users to explicitly override the logical ID of a CloudFormation
element (such as "Cfn" resources) by invoking `overrideLogicalId`
on the resource object.

For example:

    const bucket = new s3.CfnBucket(this, 'MyBucket');
    bucket.overrideLogicalId('YourBucket');

The resulting template will use `YourBucket` as the logical ID.

NOTE: the `logicalId` property will now return a stringified token
instead of a concrete value.

Fixes #1594
  • Loading branch information
Elad Ben-Israel committed Feb 4, 2019
1 parent 1d705e7 commit fe6b5b2
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 50 deletions.
2 changes: 1 addition & 1 deletion packages/@aws-cdk/cdk/lib/cloudformation/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export class Resource extends Referenceable {
* @param attributeName The name of the attribute.
*/
public getAtt(attributeName: string) {
return new CfnReference({ 'Fn::GetAtt': [this.logicalId, attributeName] }, `${this.logicalId}.${attributeName}`, this);
return new CfnReference({ 'Fn::GetAtt': [this.logicalId, attributeName] }, `${this.node.path.replace('/', '.')}.${attributeName}`, this);
}

/**
Expand Down
41 changes: 28 additions & 13 deletions packages/@aws-cdk/cdk/lib/cloudformation/stack-element.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Construct, IConstruct, PATH_SEP } from "../core/construct";
import { Token } from '../core/tokens';

const LOGICAL_ID_MD = 'aws:cdk:logicalId';

Expand All @@ -15,24 +16,17 @@ export abstract class StackElement extends Construct {
*
* @returns The construct as a stack element or undefined if it is not a stack element.
*/
public static _asStackElement(construct: IConstruct): StackElement | undefined {
if ('logicalId' in construct && 'toCloudFormation' in construct) {
return construct as StackElement;
} else {
return undefined;
}
public static isStackElement(construct: IConstruct): construct is StackElement {
return ('logicalId' in construct && 'toCloudFormation' in construct);
}

/**
* The logical ID for this CloudFormation stack element
*/
public readonly logicalId: string;

/**
* The stack this Construct has been made a part of
*/
protected stack: Stack;

private _logicalId: string;

/**
* Creates an entity and binds it to a tree.
* Note that the root of the tree must be a Stack object (not just any Root).
Expand All @@ -50,7 +44,28 @@ export abstract class StackElement extends Construct {

this.node.addMetadata(LOGICAL_ID_MD, new (require("../core/tokens/token").Token)(() => this.logicalId), this.constructor);

this.logicalId = this.stack.logicalIds.getLogicalId(this);
this._logicalId = this.stack.logicalIds.getLogicalId(this);
}

/**
* The logical ID for this CloudFormation stack element. The logical ID of the element
* is calculated from the path of the resource node in the construct tree.
*
* To override this value, use `overrideLogicalId(newLogicalId)`.
*
* @returns the logical ID as a stringified token. This value will only get
* resolved during synthesis.
*/
public get logicalId(): string {
return new Token(() => this._logicalId).toString();
}

/**
* Overrides the auto-generated logical ID with a specific ID.
* @param newLogicalId The new logical ID to use for this stack element.
*/
public overrideLogicalId(newLogicalId: string) {
this._logicalId = newLogicalId;
}

/**
Expand Down Expand Up @@ -127,7 +142,7 @@ import { CfnReference } from "./cfn-tokens";
*/
export class Ref extends CfnReference {
constructor(element: StackElement) {
super({ Ref: element.logicalId }, `${element.logicalId}.Ref`, element);
super({ Ref: element.logicalId }, `${element.node.path.replace('/', '.')}.Ref`, element);
}
}

Expand Down
39 changes: 20 additions & 19 deletions packages/@aws-cdk/cdk/lib/cloudformation/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export class Stack extends Construct {

// merge in all CloudFormation fragments collected from the tree
for (const fragment of fragments) {
merge(template, fragment);
this.merge(template, fragment);
}

// resolve all tokens and remove all empties
Expand Down Expand Up @@ -466,23 +466,25 @@ export class Stack extends Construct {
}
return false;
}
}

function merge(template: any, part: any) {
for (const section of Object.keys(part)) {
const src = part[section];

// create top-level section if it doesn't exist
let dest = template[section];
if (!dest) {
template[section] = dest = src;
} else {
// add all entities from source section to destination section
for (const id of Object.keys(src)) {
if (id in dest) {
throw new Error(`section '${section}' already contains '${id}'`);
private merge(template: any, part: any) {
part = this.node.resolve(part);
// console.error({ merge: { part: JSON.stringify(part) }});
for (const section of Object.keys(part)) {
const src = part[section];

// create top-level section if it doesn't exist
let dest = template[section];
if (!dest) {
template[section] = dest = src;
} else {
// add all entities from source section to destination section
for (const id of Object.keys(src)) {
if (id in dest) {
throw new Error(`section '${section}' already contains '${id}'`);
}
dest[id] = src[id];
}
dest[id] = src[id];
}
}
}
Expand Down Expand Up @@ -522,9 +524,8 @@ export interface TemplateOptions {
* @returns The same array as is being collected into
*/
function stackElements(node: IConstruct, into: StackElement[] = []): StackElement[] {
const element = StackElement._asStackElement(node);
if (element) {
into.push(element);
if (StackElement.isStackElement(node)) {
into.push(node);
}

for (const child of node.node.children) {
Expand Down
10 changes: 5 additions & 5 deletions packages/@aws-cdk/cdk/test/cloudformation/test.logical-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const uniqueTests = {
const r = new Resource(stack, 'MyAwesomeness', { type: 'Resource' });

// THEN
test.equal(r.logicalId, 'MyAwesomeness');
test.equal(stack.node.resolve(r.logicalId), 'MyAwesomeness');

test.done();
},
Expand Down Expand Up @@ -204,13 +204,13 @@ const allSchemesTests: {[name: string]: (scheme: IAddressingScheme, test: Test)
stack.node.prepareTree();
test.deepEqual(stack.toCloudFormation(), {
Resources: {
[c1.logicalId]: {
NewName: {
Type: 'R1' },
[c2.logicalId]: {
Construct2: {
Type: 'R2',
Properties: {
ReferenceToR1: { Ref: c1.logicalId } },
DependsOn: [ c1.logicalId ] } } });
ReferenceToR1: { Ref: 'NewName' } },
DependsOn: [ 'NewName' ] } } });

test.done();
},
Expand Down
9 changes: 5 additions & 4 deletions packages/@aws-cdk/cdk/test/cloudformation/test.output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ export = {
const output = new Output(stack, 'MyOutput');
const child = new Construct(stack, 'MyConstruct');
const output2 = new Output(child, 'MyOutput2');
test.equal(output.export, 'MyStack:MyOutput');
test.equal(output2.export, 'MyStack:MyConstructMyOutput255322D15');

test.equal(stack.node.resolve(output.export), 'MyStack:MyOutput');
test.equal(stack.node.resolve(output2.export), 'MyStack:MyConstructMyOutput255322D15');
test.done();
},

Expand All @@ -53,10 +54,10 @@ export = {
test.done();
},

'is stack name is undefined, we will only use the logical ID for the export name'(test: Test) {
'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 Output(stack, 'MyOutput');
test.equal(output.export, 'MyOutput');
test.equal(stack.node.resolve(output.export), 'MyOutput');
test.done();
},

Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/cdk/test/cloudformation/test.parameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ export = {

test.deepEqual(stack.toCloudFormation(), {
Parameters: {
[param.logicalId]: {
ChildMyParam3161BF5D: {
Default: 10,
Type: 'Integer',
Description: 'My first parameter' } },
Resources: {
Resource: {
Type: 'Type',
Properties: { ReferenceToParam: { Ref: param.logicalId } } } } });
Properties: { ReferenceToParam: { Ref: 'ChildMyParam3161BF5D' } } } } });

test.done();
},
Expand Down
12 changes: 6 additions & 6 deletions packages/@aws-cdk/cdk/test/cloudformation/test.resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ export = {
const res1 = new Resource(level1, 'childoflevel1', { type: 'MyResourceType1' });
const res2 = new Resource(level3, 'childoflevel3', { type: 'MyResourceType2' });

test.equal(withoutHash(res1.logicalId), 'level1childoflevel1');
test.equal(withoutHash(res2.logicalId), 'level1level2level3childoflevel3');
test.equal(withoutHash(stack.node.resolve(res1.logicalId)), 'level1childoflevel1');
test.equal(withoutHash(stack.node.resolve(res2.logicalId)), 'level1level2level3childoflevel3');

test.done();
},
Expand Down Expand Up @@ -327,10 +327,10 @@ export = {
MyResource:
{ Type: 'R',
DependsOn:
[ 'MyC1R1FB2A562F',
'MyC1R2AE2B5066',
'MyC2R3809EEAD6',
'MyC3C2R38CE6F9F7' ] } } });
[ 'MyC1R2AE2B5066',
'MyC1R1FB2A562F',
'MyC2R3809EEAD6',
'MyC3C2R38CE6F9F7' ] } } });
test.done();
},

Expand Down
24 changes: 24 additions & 0 deletions packages/@aws-cdk/cdk/test/cloudformation/test.stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,30 @@ export = {

test.done();
},

'overrideLogicalId(id) can be used to override the logical ID of a resource'(test: Test) {
// GIVEN
const stack = new Stack();
const bonjour = new Resource(stack, 'BonjourResource', { type: 'Resource::Type' });

// { Ref } and { GetAtt }
new Resource(stack, 'RefToBonjour', { type: 'Other::Resource', properties: {
RefToBonjour: bonjour.ref.toString(),
GetAttBonjour: bonjour.getAtt('TheAtt').toString()
}});

bonjour.overrideLogicalId('BOOM');

// THEN
test.deepEqual(stack.toCloudFormation(), { Resources:
{ BOOM: { Type: 'Resource::Type' },
RefToBonjour:
{ Type: 'Other::Resource',
Properties:
{ RefToBonjour: { Ref: 'BOOM' },
GetAttBonjour: { 'Fn::GetAtt': [ 'BOOM', 'TheAtt' ] } } } } });
test.done();
}
};

class StackWithPostProcessor extends Stack {
Expand Down

0 comments on commit fe6b5b2

Please sign in to comment.