diff --git a/packages/@aws-cdk/core/test/test.cross-environment-token.ts b/packages/@aws-cdk/core/test/test.cross-environment-token.ts index 20cdf98dd0829..c18ea934e382f 100644 --- a/packages/@aws-cdk/core/test/test.cross-environment-token.ts +++ b/packages/@aws-cdk/core/test/test.cross-environment-token.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { App, CfnOutput, Construct, PhysicalName, Resource, Stack } from '../lib'; +import { App, CfnOutput, CfnResource, Construct, PhysicalName, Resource, Stack } from '../lib'; import { toCloudFormation } from './util'; // tslint:disable:object-literal-key-quotes @@ -195,6 +195,53 @@ export = { test.done(); }, + + 'cross environment when stack is a substack'(test: Test) { + const app = new App(); + + const parentStack = new Stack(app, 'ParentStack', { + env: { account: '112233', region: 'us-east-1' } + }); + + const childStack = new Stack(parentStack, 'ChildStack', { + env: { account: '998877', region: 'eu-west-2' } + }); + + const childResource = new MyResource(childStack, 'ChildResource', PhysicalName.GENERATE_IF_NEEDED); + + new CfnResource(parentStack, 'ParentResource', { + type: 'Parent::Resource', + properties: { + RefToChildResource: childResource.name + } + }); + + const assembly = app.synth(); + + test.deepEqual(assembly.getStack(parentStack.stackName).template, { + Resources: { + ParentResource: { + Type: 'Parent::Resource', + Properties: { + RefToChildResource: 'parentstackchildstack83c5ackchildresource852877eeb919bda2008e' + } + } + } + }); + + test.deepEqual(assembly.getStack(childStack.stackName).template, { + Resources: { + ChildResource8C37244D: { + Type: 'My::Resource', + Properties: { + resourceName: 'parentstackchildstack83c5ackchildresource852877eeb919bda2008e' + } + } + } + }); + + test.done(); + } }; class MyResource extends Resource { @@ -212,5 +259,12 @@ class MyResource extends Resource { service: 'myservice', }); this.name = this.getResourceNameAttribute('simple-name'); + + new CfnResource(this, 'Resource', { + type: 'My::Resource', + properties: { + resourceName: this.physicalName + } + }); } } diff --git a/packages/@aws-cdk/core/test/test.stack.ts b/packages/@aws-cdk/core/test/test.stack.ts index 725eca0f958d5..7ab0d8778c07e 100644 --- a/packages/@aws-cdk/core/test/test.stack.ts +++ b/packages/@aws-cdk/core/test/test.stack.ts @@ -284,6 +284,103 @@ export = { test.done(); }, + 'Stacks can be children of other stacks (substack) and they will be synthesized separately'(test: Test) { + // GIVEN + const app = new App(); + + // WHEN + const parentStack = new Stack(app, 'parent'); + const childStack = new Stack(parentStack, 'child'); + new CfnResource(parentStack, 'MyParentResource', { type: 'Resource::Parent' }); + new CfnResource(childStack, 'MyChildResource', { type: 'Resource::Child' }); + + // THEN + const assembly = app.synth(); + test.deepEqual(assembly.getStack(parentStack.stackName).template, { Resources: { MyParentResource: { Type: 'Resource::Parent' } } }); + test.deepEqual(assembly.getStack(childStack.stackName).template, { Resources: { MyChildResource: { Type: 'Resource::Child' } } }); + test.done(); + }, + + 'cross-stack reference (substack references parent stack)'(test: Test) { + // GIVEN + const app = new App(); + const parentStack = new Stack(app, 'parent'); + const childStack = new Stack(parentStack, 'child'); + + // WHEN (a resource from the child stack references a resource from the parent stack) + const parentResource = new CfnResource(parentStack, 'MyParentResource', { type: 'Resource::Parent' }); + new CfnResource(childStack, 'MyChildResource', { + type: 'Resource::Child', + properties: { + ChildProp: parentResource.getAtt('AttOfParentResource') + } + }); + + // THEN + const assembly = app.synth(); + test.deepEqual(assembly.getStack(parentStack.stackName).template, { + Resources: { MyParentResource: { Type: 'Resource::Parent' } }, + Outputs: { ExportsOutputFnGetAttMyParentResourceAttOfParentResourceC2D0BB9E: { + Value: { 'Fn::GetAtt': [ 'MyParentResource', 'AttOfParentResource' ] }, + Export: { Name: 'parent:ExportsOutputFnGetAttMyParentResourceAttOfParentResourceC2D0BB9E' } } + } + }); + test.deepEqual(assembly.getStack(childStack.stackName).template, { + Resources: { + MyChildResource: { + Type: 'Resource::Child', + Properties: { + ChildProp: { + 'Fn::ImportValue': 'parent:ExportsOutputFnGetAttMyParentResourceAttOfParentResourceC2D0BB9E' + } + } + } + } + }); + test.done(); + }, + + 'cross-stack reference (parent stack references substack)'(test: Test) { + // GIVEN + const app = new App(); + const parentStack = new Stack(app, 'parent'); + const childStack = new Stack(parentStack, 'child'); + + // WHEN (a resource from the child stack references a resource from the parent stack) + const childResource = new CfnResource(childStack, 'MyChildResource', { type: 'Resource::Child' }); + new CfnResource(parentStack, 'MyParentResource', { + type: 'Resource::Parent', + properties: { + ParentProp: childResource.getAtt('AttributeOfChildResource') + } + }); + + // THEN + const assembly = app.synth(); + test.deepEqual(assembly.getStack(parentStack.stackName).template, { + Resources: { + MyParentResource: { + Type: 'Resource::Parent', + Properties: { + ParentProp: { 'Fn::ImportValue': 'parentchild13F9359B:childExportsOutputFnGetAttMyChildResourceAttributeOfChildResource420052FC' } + } + } + } + }); + + test.deepEqual(assembly.getStack(childStack.stackName).template, { + Resources: { + MyChildResource: { Type: 'Resource::Child' } }, + Outputs: { + ExportsOutputFnGetAttMyChildResourceAttributeOfChildResource52813264: { + Value: { 'Fn::GetAtt': [ 'MyChildResource', 'AttributeOfChildResource' ] }, + Export: { Name: 'parentchild13F9359B:childExportsOutputFnGetAttMyChildResourceAttributeOfChildResource420052FC' } + } + } + }); + test.done(); + }, + 'cannot create cyclic reference between stacks'(test: Test) { // GIVEN const app = new App(); @@ -439,6 +536,26 @@ export = { test.done(); }, + 'Stack.of() works for substacks'(test: Test) { + // GIVEN + const app = new App(); + + // WHEN + const parentStack = new Stack(app, 'ParentStack'); + const parentResource = new CfnResource(parentStack, 'ParentResource', { type: 'parent::resource' }); + + // we will define a substack under the /resource/... just for giggles. + const childStack = new Stack(parentResource, 'ChildStack'); + const childResource = new CfnResource(childStack, 'ChildResource', { type: 'child::resource' }); + + // THEN + test.same(Stack.of(parentStack), parentStack); + test.same(Stack.of(parentResource), parentStack); + test.same(Stack.of(childStack), childStack); + test.same(Stack.of(childResource), childStack); + test.done(); + }, + 'stack.availabilityZones falls back to Fn::GetAZ[0],[2] if region is not specified'(test: Test) { // GIVEN const app = new App();