Skip to content

Commit

Permalink
Merge branch 'master' into bump/12.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Apr 16, 2020
2 parents c66c530 + 000f0c2 commit d847083
Show file tree
Hide file tree
Showing 42 changed files with 1,957 additions and 1,479 deletions.
95 changes: 42 additions & 53 deletions packages/@aws-cdk/aws-cloudformation/lib/nested-stack.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as sns from '@aws-cdk/aws-sns';
import { Aws, CfnOutput, CfnParameter, CfnResource, Construct, Duration, Fn, IResolvable, IResolveContext, Lazy, Reference, Stack, Token } from '@aws-cdk/core';
import { Aws, CfnResource, Construct, Duration, FileAssetPackaging, Fn, IResolveContext, Stack, Token } from '@aws-cdk/core';
import { Lazy } from 'constructs';
import * as crypto from 'crypto';
import { CfnStack } from './cloudformation.generated';

const NESTED_STACK_SYMBOL = Symbol.for('@aws-cdk/aws-cloudformation.NestedStack');
Expand All @@ -10,7 +12,6 @@ const NESTED_STACK_SYMBOL = Symbol.for('@aws-cdk/aws-cloudformation.NestedStack'
* @experimental
*/
export interface NestedStackProps {

/**
* The set value pairs that represent the parameters passed to CloudFormation
* when this nested stack is created. Each parameter has a name corresponding
Expand Down Expand Up @@ -82,12 +83,16 @@ export class NestedStack extends Stack {
private readonly resource: CfnStack;
private readonly _contextualStackId: string;
private readonly _contextualStackName: string;
private _templateUrl?: string;
private _parentStack: Stack;

constructor(scope: Construct, id: string, props: NestedStackProps = { }) {
const parentStack = findParentStack(scope);

super(scope, id, { env: { account: parentStack.account, region: parentStack.region } });

this._parentStack = parentStack;

// @deprecate: remove this in v2.0 (redundent)
const parentScope = new Construct(scope, id + '.NestedStack');

Expand All @@ -99,7 +104,7 @@ export class NestedStack extends Stack {
this.parameters = props.parameters || {};

this.resource = new CfnStack(parentScope, `${id}.NestedStackResource`, {
templateUrl: this.templateUrl,
templateUrl: Lazy.stringValue({ produce: () => this._templateUrl || '<unresolved>' }),
parameters: Lazy.anyValue({ produce: () => Object.keys(this.parameters).length > 0 ? this.parameters : undefined }),
notificationArns: props.notifications ? props.notifications.map(n => n.topicArn) : undefined,
timeoutInMinutes: props.timeout ? props.timeout.toMinutes() : undefined,
Expand Down Expand Up @@ -144,62 +149,46 @@ export class NestedStack extends Stack {
}

/**
* Called by the base "prepare" method when a reference is found.
* Assign a value to one of the nested stack parameters.
* @param name The parameter name (ID)
* @param value The value to assign
*/
protected prepareCrossReference(sourceStack: Stack, reference: Reference): IResolvable {
const targetStack = Stack.of(reference.target);

// the nested stack references a resource from the parent stack:
// we pass it through a as a cloudformation parameter
if (targetStack === sourceStack.nestedStackParent) {
// we call "this.resolve" to ensure that tokens do not creep in (for example, if the reference display name includes tokens)
const paramId = this.resolve(`reference-to-${reference.target.node.uniqueId}.${reference.displayName}`);
let param = this.node.tryFindChild(paramId) as CfnParameter;
if (!param) {
param = new CfnParameter(this, paramId, { type: 'String' });
this.parameters[param.logicalId] = Token.asString(reference);
}

return param.value;
}

// parent stack references a resource from the nested stack:
// we output it from the nested stack and use "Fn::GetAtt" as the reference value
if (targetStack === this && targetStack.nestedStackParent === sourceStack) {
return this.getCreateOutputForReference(reference);
}

// sibling nested stacks (same parent):
// output from one and pass as parameter to the other
if (targetStack.nestedStackParent && targetStack.nestedStackParent === sourceStack.nestedStackParent) {
const outputValue = this.getCreateOutputForReference(reference);
return (sourceStack as NestedStack).prepareCrossReference(sourceStack, outputValue);
}

// nested stack references a value from some other non-nested stack:
// normal export/import, with dependency between the parents
if (sourceStack.nestedStackParent && sourceStack.nestedStackParent !== targetStack) {
return super.prepareCrossReference(sourceStack, reference);
}
public setParameter(name: string, value: string) {
this.parameters[name] = value;
}

// some non-nested stack (that is not the parent) references a resource inside the nested stack:
// we output the value and let our parent export it
if (!sourceStack.nestedStackParent && targetStack.nestedStackParent && targetStack.nestedStackParent !== sourceStack) {
const outputValue = this.getCreateOutputForReference(reference);
return (targetStack.nestedStackParent as NestedStack).prepareCrossReference(sourceStack, outputValue);
/**
* Defines an asset at the parent stack which represents the template of this
* nested stack.
*
* This private API is used by `App.prepare()` within a loop that rectifies
* references every time an asset is added. This is because (at the moment)
* assets are addressed using CloudFormation parameters.
*
* @returns `true` if a new asset was added or `false` if an asset was
* previously added. When this returns `true`, App will do another reference
* rectification cycle.
*
* @internal
*/
public _prepareTemplateAsset() {
if (this._templateUrl) {
return false;
}

throw new Error('unexpected nested stack cross reference');
}
const cfn = JSON.stringify((this as any)._toCloudFormation());
const templateHash = crypto.createHash('sha256').update(cfn).digest('hex');

private getCreateOutputForReference(reference: Reference) {
const outputId = `${reference.target.node.uniqueId}${reference.displayName}`;
let output = this.node.tryFindChild(outputId) as CfnOutput;
if (!output) {
output = new CfnOutput(this, outputId, { value: Token.asString(reference) });
}
const templateLocation = this._parentStack.addFileAsset({
packaging: FileAssetPackaging.FILE,
sourceHash: templateHash,
fileName: this.templateFile
});

return this.resource.getAtt(`Outputs.${output.logicalId}`);
// if bucketName/objectKey are cfn parameters from a stack other than the parent stack, they will
// be resolved as cross-stack references like any other (see "multi" tests).
this._templateUrl = `https://s3.${this._parentStack.region}.${this._parentStack.urlSuffix}/${templateLocation.bucketName}/${templateLocation.objectKey}`;
return true;
}

private contextualAttribute(innerValue: string, outerValue: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@
},
"/",
{
"Ref": "AssetParametersdddca70fcceefd0a4532c8eb5ad3d5da6f51d64fda9343f0b57dd664736dccafS3BucketE3660F43"
"Ref": "AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aS3Bucket1DDC9C52"
},
"/",
{
Expand All @@ -168,7 +168,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParametersdddca70fcceefd0a4532c8eb5ad3d5da6f51d64fda9343f0b57dd664736dccafS3VersionKeyFD0B0470"
"Ref": "AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aS3VersionKey2B4F31C1"
}
]
}
Expand All @@ -181,7 +181,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParametersdddca70fcceefd0a4532c8eb5ad3d5da6f51d64fda9343f0b57dd664736dccafS3VersionKeyFD0B0470"
"Ref": "AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aS3VersionKey2B4F31C1"
}
]
}
Expand Down Expand Up @@ -254,29 +254,29 @@
}
},
"Parameters": {
"AssetParameters0d0404717d8867c09534f2cf382e8e24531ff64a968afa2efd7f071ad65a22dfS3BucketB322F951": {
"AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aS3Bucket1DDC9C52": {
"Type": "String",
"Description": "S3 bucket for asset \"0d0404717d8867c09534f2cf382e8e24531ff64a968afa2efd7f071ad65a22df\""
"Description": "S3 bucket for asset \"4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1a\""
},
"AssetParameters0d0404717d8867c09534f2cf382e8e24531ff64a968afa2efd7f071ad65a22dfS3VersionKeyAA9C5AF4": {
"AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aS3VersionKey2B4F31C1": {
"Type": "String",
"Description": "S3 key for asset version \"0d0404717d8867c09534f2cf382e8e24531ff64a968afa2efd7f071ad65a22df\""
"Description": "S3 key for asset version \"4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1a\""
},
"AssetParameters0d0404717d8867c09534f2cf382e8e24531ff64a968afa2efd7f071ad65a22dfArtifactHash5D335705": {
"AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aArtifactHash3AA59378": {
"Type": "String",
"Description": "Artifact hash for asset \"0d0404717d8867c09534f2cf382e8e24531ff64a968afa2efd7f071ad65a22df\""
"Description": "Artifact hash for asset \"4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1a\""
},
"AssetParametersdddca70fcceefd0a4532c8eb5ad3d5da6f51d64fda9343f0b57dd664736dccafS3BucketE3660F43": {
"AssetParameters0d0404717d8867c09534f2cf382e8e24531ff64a968afa2efd7f071ad65a22dfS3BucketB322F951": {
"Type": "String",
"Description": "S3 bucket for asset \"dddca70fcceefd0a4532c8eb5ad3d5da6f51d64fda9343f0b57dd664736dccaf\""
"Description": "S3 bucket for asset \"0d0404717d8867c09534f2cf382e8e24531ff64a968afa2efd7f071ad65a22df\""
},
"AssetParametersdddca70fcceefd0a4532c8eb5ad3d5da6f51d64fda9343f0b57dd664736dccafS3VersionKeyFD0B0470": {
"AssetParameters0d0404717d8867c09534f2cf382e8e24531ff64a968afa2efd7f071ad65a22dfS3VersionKeyAA9C5AF4": {
"Type": "String",
"Description": "S3 key for asset version \"dddca70fcceefd0a4532c8eb5ad3d5da6f51d64fda9343f0b57dd664736dccaf\""
"Description": "S3 key for asset version \"0d0404717d8867c09534f2cf382e8e24531ff64a968afa2efd7f071ad65a22df\""
},
"AssetParametersdddca70fcceefd0a4532c8eb5ad3d5da6f51d64fda9343f0b57dd664736dccafArtifactHashEECD8E35": {
"AssetParameters0d0404717d8867c09534f2cf382e8e24531ff64a968afa2efd7f071ad65a22dfArtifactHash5D335705": {
"Type": "String",
"Description": "Artifact hash for asset \"dddca70fcceefd0a4532c8eb5ad3d5da6f51d64fda9343f0b57dd664736dccaf\""
"Description": "Artifact hash for asset \"0d0404717d8867c09534f2cf382e8e24531ff64a968afa2efd7f071ad65a22df\""
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
{
"Resources": {
"Level1ABBD39B3": {
"Type": "AWS::SNS::Topic"
},
"Nested1NestedStackNested1NestedStackResourceCD0AD36B": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": {
"Fn::Join": [
"",
[
"https://s3.",
{
"Ref": "AWS::Region"
},
".",
{
"Ref": "AWS::URLSuffix"
},
"/",
{
"Ref": "AssetParametersad23da1cfc8b3fd7916c6ffc7debacadf084765e62fab8acf0b8b0a9b0289f95S3BucketDB605F9E"
},
"/",
{
"Fn::Select": [
0,
{
"Fn::Split": [
"||",
{
"Ref": "AssetParametersad23da1cfc8b3fd7916c6ffc7debacadf084765e62fab8acf0b8b0a9b0289f95S3VersionKey26685906"
}
]
}
]
},
{
"Fn::Select": [
1,
{
"Fn::Split": [
"||",
{
"Ref": "AssetParametersad23da1cfc8b3fd7916c6ffc7debacadf084765e62fab8acf0b8b0a9b0289f95S3VersionKey26685906"
}
]
}
]
}
]
]
},
"Parameters": {
"referencetonestedstacksmultirefsLevel19FB2466DTopicName": {
"Fn::GetAtt": [
"Level1ABBD39B3",
"TopicName"
]
},
"referencetonestedstacksmultirefsAssetParameters495a6bc36c13a0adeb3778c921d18ac4a8205f5471108fcc199a291d14855c3aS3Bucket03F0C3B1Ref": {
"Ref": "AssetParameters495a6bc36c13a0adeb3778c921d18ac4a8205f5471108fcc199a291d14855c3aS3Bucket58724FCA"
},
"referencetonestedstacksmultirefsAssetParameters495a6bc36c13a0adeb3778c921d18ac4a8205f5471108fcc199a291d14855c3aS3VersionKey5F9CF809Ref": {
"Ref": "AssetParameters495a6bc36c13a0adeb3778c921d18ac4a8205f5471108fcc199a291d14855c3aS3VersionKey2CCE0573"
},
"referencetonestedstacksmultirefsAssetParameterscc623add53df153cf6a7df1cea4dc90740d7be087472579110754a633ec90847S3Bucket8F1E17B9Ref": {
"Ref": "AssetParameterscc623add53df153cf6a7df1cea4dc90740d7be087472579110754a633ec90847S3Bucket9A14AA6D"
},
"referencetonestedstacksmultirefsAssetParameterscc623add53df153cf6a7df1cea4dc90740d7be087472579110754a633ec90847S3VersionKey9EEEF950Ref": {
"Ref": "AssetParameterscc623add53df153cf6a7df1cea4dc90740d7be087472579110754a633ec90847S3VersionKeyF124C0D9"
}
}
}
}
},
"Parameters": {
"AssetParameters495a6bc36c13a0adeb3778c921d18ac4a8205f5471108fcc199a291d14855c3aS3Bucket58724FCA": {
"Type": "String",
"Description": "S3 bucket for asset \"495a6bc36c13a0adeb3778c921d18ac4a8205f5471108fcc199a291d14855c3a\""
},
"AssetParameters495a6bc36c13a0adeb3778c921d18ac4a8205f5471108fcc199a291d14855c3aS3VersionKey2CCE0573": {
"Type": "String",
"Description": "S3 key for asset version \"495a6bc36c13a0adeb3778c921d18ac4a8205f5471108fcc199a291d14855c3a\""
},
"AssetParameters495a6bc36c13a0adeb3778c921d18ac4a8205f5471108fcc199a291d14855c3aArtifactHashAE1436B7": {
"Type": "String",
"Description": "Artifact hash for asset \"495a6bc36c13a0adeb3778c921d18ac4a8205f5471108fcc199a291d14855c3a\""
},
"AssetParameterscc623add53df153cf6a7df1cea4dc90740d7be087472579110754a633ec90847S3Bucket9A14AA6D": {
"Type": "String",
"Description": "S3 bucket for asset \"cc623add53df153cf6a7df1cea4dc90740d7be087472579110754a633ec90847\""
},
"AssetParameterscc623add53df153cf6a7df1cea4dc90740d7be087472579110754a633ec90847S3VersionKeyF124C0D9": {
"Type": "String",
"Description": "S3 key for asset version \"cc623add53df153cf6a7df1cea4dc90740d7be087472579110754a633ec90847\""
},
"AssetParameterscc623add53df153cf6a7df1cea4dc90740d7be087472579110754a633ec90847ArtifactHashAF64C405": {
"Type": "String",
"Description": "Artifact hash for asset \"cc623add53df153cf6a7df1cea4dc90740d7be087472579110754a633ec90847\""
},
"AssetParametersad23da1cfc8b3fd7916c6ffc7debacadf084765e62fab8acf0b8b0a9b0289f95S3BucketDB605F9E": {
"Type": "String",
"Description": "S3 bucket for asset \"ad23da1cfc8b3fd7916c6ffc7debacadf084765e62fab8acf0b8b0a9b0289f95\""
},
"AssetParametersad23da1cfc8b3fd7916c6ffc7debacadf084765e62fab8acf0b8b0a9b0289f95S3VersionKey26685906": {
"Type": "String",
"Description": "S3 key for asset version \"ad23da1cfc8b3fd7916c6ffc7debacadf084765e62fab8acf0b8b0a9b0289f95\""
},
"AssetParametersad23da1cfc8b3fd7916c6ffc7debacadf084765e62fab8acf0b8b0a9b0289f95ArtifactHashAF8D54FC": {
"Type": "String",
"Description": "Artifact hash for asset \"ad23da1cfc8b3fd7916c6ffc7debacadf084765e62fab8acf0b8b0a9b0289f95\""
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as sns from '@aws-cdk/aws-sns';
import { App, Fn, Stack } from '@aws-cdk/core';
import { NestedStack } from '../lib';

const app = new App();
const top = new Stack(app, 'nested-stacks-multi-refs');
const level1 = new sns.Topic(top, 'Level1');
const nested1 = new NestedStack(top, 'Nested1');
const nested2 = new NestedStack(nested1, 'Nested2');
const nested3 = new NestedStack(nested2, 'Nested3');

// WHEN
const level2 = new sns.Topic(nested2, 'Level2ReferencesLevel1', {
displayName: shortName(level1.topicName)
});

new sns.Topic(nested3, 'Level3ReferencesLevel1', {
displayName: shortName(level1.topicName)
});

new sns.Topic(nested3, 'Level3ReferencesLevel2', {
displayName: shortName(level2.topicName)
});

app.synth();

// topicName is too long for displayName, so just take the second part:
// Stack1-NestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305B-EM64TEGA04J9-TopicInNestedUnderStack115E329C4-HEO7NLYC1AFL
function shortName(topicName: string) {
return Fn.select(1, Fn.split('-', topicName));
}
Loading

0 comments on commit d847083

Please sign in to comment.