Skip to content

Commit

Permalink
feat(core): implicit app for root stacks (#9342)
Browse files Browse the repository at this point in the history
When a stack is created as the root of the construct tree, we now implicitly create an `App` that serves as its parent scope.

The root stack is created with the ID `Default`, which ensures that `node.uniqueId` of constructs within that stack is preserved.

BREAKING CHANGE: in unit tests, the `node.path` of constructs within stacks created the root of the tree via `new Stack()` will now have a prefix `Default/` which represents an implicit `App` root.

Related: aws/aws-cdk-rfcs#192


----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
Elad Ben-Israel committed Aug 3, 2020
1 parent 5e087e5 commit 1d85a9f
Show file tree
Hide file tree
Showing 39 changed files with 227 additions and 202 deletions.
32 changes: 23 additions & 9 deletions packages/@aws-cdk/assert/lib/synth-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import * as core from '@aws-cdk/core';
import * as cxapi from '@aws-cdk/cx-api';

export class SynthUtils {
/**
* Returns the cloud assembly template artifact for a stack.
*/
public static synthesize(stack: core.Stack, options: core.SynthesisOptions = { }): cxapi.CloudFormationStackArtifact {
// always synthesize against the root (be it an App or whatever) so all artifacts will be included
const root = stack.node.root;

// if the root is an app, invoke "synth" to avoid double synthesis
const assembly = root instanceof core.App ? root.synth() : core.ConstructNode.synth(root.node, options);

const assembly = synthesizeApp(stack, options);
return assembly.getStackArtifact(stack.artifactId);
}

Expand Down Expand Up @@ -51,10 +50,7 @@ export class SynthUtils {
*/
public static _synthesizeWithNested(stack: core.Stack, options: core.SynthesisOptions = { }): cxapi.CloudFormationStackArtifact | object {
// always synthesize against the root (be it an App or whatever) so all artifacts will be included
const root = stack.node.root;

// if the root is an app, invoke "synth" to avoid double synthesis
const assembly = root instanceof core.App ? root.synth() : core.ConstructNode.synth(root.node, options);
const assembly = synthesizeApp(stack, options);

// if this is a nested stack (it has a parent), then just read the template as a string
if (stack.nestedStackParent) {
Expand All @@ -65,6 +61,24 @@ export class SynthUtils {
}
}

/**
* Synthesizes the app in which a stack resides and returns the cloud assembly object.
*/
function synthesizeApp(stack: core.Stack, options: core.SynthesisOptions) {
const root = stack.node.root;
if (!core.Stage.isStage(root)) {
throw new Error('unexpected: all stacks must be part of a Stage or an App');
}

// to support incremental assertions (i.e. "expect(stack).toNotContainSomething(); doSomething(); expect(stack).toContainSomthing()")
const force = true;

return root.synth({
force,
...options,
});
}

export interface SubsetOptions {
/**
* Match all resources of the given type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "AssetParameterse7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0S3Bucket316B0B52"
"Ref": "AssetParameters3dc8c5549b88fef617feef923524902b3650973ae1159c9489ee8405344dd5a0S3BucketD7637C1B"
},
"S3Key": {
"Fn::Join": [
Expand All @@ -49,7 +49,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameterse7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0S3VersionKey4A4C6C19"
"Ref": "AssetParameters3dc8c5549b88fef617feef923524902b3650973ae1159c9489ee8405344dd5a0S3VersionKeyC19FD924"
}
]
}
Expand All @@ -62,7 +62,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameterse7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0S3VersionKey4A4C6C19"
"Ref": "AssetParameters3dc8c5549b88fef617feef923524902b3650973ae1159c9489ee8405344dd5a0S3VersionKeyC19FD924"
}
]
}
Expand Down Expand Up @@ -272,17 +272,17 @@
}
},
"Parameters": {
"AssetParameterse7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0S3Bucket316B0B52": {
"AssetParameters3dc8c5549b88fef617feef923524902b3650973ae1159c9489ee8405344dd5a0S3BucketD7637C1B": {
"Type": "String",
"Description": "S3 bucket for asset \"e7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0\""
"Description": "S3 bucket for asset \"3dc8c5549b88fef617feef923524902b3650973ae1159c9489ee8405344dd5a0\""
},
"AssetParameterse7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0S3VersionKey4A4C6C19": {
"AssetParameters3dc8c5549b88fef617feef923524902b3650973ae1159c9489ee8405344dd5a0S3VersionKeyC19FD924": {
"Type": "String",
"Description": "S3 key for asset version \"e7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0\""
"Description": "S3 key for asset version \"3dc8c5549b88fef617feef923524902b3650973ae1159c9489ee8405344dd5a0\""
},
"AssetParameterse7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0ArtifactHash2FE6C4D8": {
"AssetParameters3dc8c5549b88fef617feef923524902b3650973ae1159c9489ee8405344dd5a0ArtifactHash9DF43F02": {
"Type": "String",
"Description": "Artifact hash for asset \"e7d4044be8659ef3bb40a53a69846d7ca1b2d8e4e4bd36ad8c9d8e69fe3b68a0\""
"Description": "Artifact hash for asset \"3dc8c5549b88fef617feef923524902b3650973ae1159c9489ee8405344dd5a0\""
}
},
"Outputs": {
Expand Down Expand Up @@ -313,4 +313,4 @@
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3Bucket4E51347F"
"Ref": "AssetParametersfec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3S3Bucket2E551A38"
},
"S3Key": {
"Fn::Join": [
Expand All @@ -49,7 +49,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3VersionKey707D1166"
"Ref": "AssetParametersfec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3S3VersionKeyE54FD621"
}
]
}
Expand All @@ -62,7 +62,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3VersionKey707D1166"
"Ref": "AssetParametersfec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3S3VersionKeyE54FD621"
}
]
}
Expand Down Expand Up @@ -281,17 +281,17 @@
}
},
"Parameters": {
"AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3Bucket4E51347F": {
"AssetParametersfec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3S3Bucket2E551A38": {
"Type": "String",
"Description": "S3 bucket for asset \"4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181e\""
"Description": "S3 bucket for asset \"fec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3\""
},
"AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3VersionKey707D1166": {
"AssetParametersfec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3S3VersionKeyE54FD621": {
"Type": "String",
"Description": "S3 key for asset version \"4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181e\""
"Description": "S3 key for asset version \"fec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3\""
},
"AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eArtifactHash267391ED": {
"AssetParametersfec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3ArtifactHashD7A29DA9": {
"Type": "String",
"Description": "Artifact hash for asset \"4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181e\""
"Description": "Artifact hash for asset \"fec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3\""
}
},
"Outputs": {
Expand Down Expand Up @@ -322,4 +322,4 @@
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3Bucket4E51347F"
"Ref": "AssetParametersfec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3S3Bucket2E551A38"
},
"S3Key": {
"Fn::Join": [
Expand All @@ -49,7 +49,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3VersionKey707D1166"
"Ref": "AssetParametersfec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3S3VersionKeyE54FD621"
}
]
}
Expand All @@ -62,7 +62,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3VersionKey707D1166"
"Ref": "AssetParametersfec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3S3VersionKeyE54FD621"
}
]
}
Expand Down Expand Up @@ -272,17 +272,17 @@
}
},
"Parameters": {
"AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3Bucket4E51347F": {
"AssetParametersfec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3S3Bucket2E551A38": {
"Type": "String",
"Description": "S3 bucket for asset \"4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181e\""
"Description": "S3 bucket for asset \"fec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3\""
},
"AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eS3VersionKey707D1166": {
"AssetParametersfec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3S3VersionKeyE54FD621": {
"Type": "String",
"Description": "S3 key for asset version \"4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181e\""
"Description": "S3 key for asset version \"fec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3\""
},
"AssetParameters4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181eArtifactHash267391ED": {
"AssetParametersfec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3ArtifactHashD7A29DA9": {
"Type": "String",
"Description": "Artifact hash for asset \"4e547bc3a1a10467cf6503c7845fe1a857e509746b927699a9e3bea8b802181e\""
"Description": "Artifact hash for asset \"fec8e8354e12687c5a4b843b4e269741f53dec634946869b276f7fd1017845c3\""
}
},
"Outputs": {
Expand Down Expand Up @@ -313,4 +313,4 @@
}
}
}
}
}
18 changes: 9 additions & 9 deletions packages/@aws-cdk/aws-apigateway/test/integ.cors.expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "AssetParameters41bb2d4a81eb782e91694a93a5e13f3e4a4ed3148bdb066856d1e5275b593cd7S3Bucket763974CB"
"Ref": "AssetParametersc7bba0d9d477c86c6dc2adb0eb95842634a1c040dd3a66b42eec2bb604644d4fS3BucketE85F411C"
},
"S3Key": {
"Fn::Join": [
Expand All @@ -541,7 +541,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameters41bb2d4a81eb782e91694a93a5e13f3e4a4ed3148bdb066856d1e5275b593cd7S3VersionKey57F05589"
"Ref": "AssetParametersc7bba0d9d477c86c6dc2adb0eb95842634a1c040dd3a66b42eec2bb604644d4fS3VersionKeyF02404BC"
}
]
}
Expand All @@ -554,7 +554,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameters41bb2d4a81eb782e91694a93a5e13f3e4a4ed3148bdb066856d1e5275b593cd7S3VersionKey57F05589"
"Ref": "AssetParametersc7bba0d9d477c86c6dc2adb0eb95842634a1c040dd3a66b42eec2bb604644d4fS3VersionKeyF02404BC"
}
]
}
Expand Down Expand Up @@ -607,17 +607,17 @@
}
},
"Parameters": {
"AssetParameters41bb2d4a81eb782e91694a93a5e13f3e4a4ed3148bdb066856d1e5275b593cd7S3Bucket763974CB": {
"AssetParametersc7bba0d9d477c86c6dc2adb0eb95842634a1c040dd3a66b42eec2bb604644d4fS3BucketE85F411C": {
"Type": "String",
"Description": "S3 bucket for asset \"41bb2d4a81eb782e91694a93a5e13f3e4a4ed3148bdb066856d1e5275b593cd7\""
"Description": "S3 bucket for asset \"c7bba0d9d477c86c6dc2adb0eb95842634a1c040dd3a66b42eec2bb604644d4f\""
},
"AssetParameters41bb2d4a81eb782e91694a93a5e13f3e4a4ed3148bdb066856d1e5275b593cd7S3VersionKey57F05589": {
"AssetParametersc7bba0d9d477c86c6dc2adb0eb95842634a1c040dd3a66b42eec2bb604644d4fS3VersionKeyF02404BC": {
"Type": "String",
"Description": "S3 key for asset version \"41bb2d4a81eb782e91694a93a5e13f3e4a4ed3148bdb066856d1e5275b593cd7\""
"Description": "S3 key for asset version \"c7bba0d9d477c86c6dc2adb0eb95842634a1c040dd3a66b42eec2bb604644d4f\""
},
"AssetParameters41bb2d4a81eb782e91694a93a5e13f3e4a4ed3148bdb066856d1e5275b593cd7ArtifactHash9CD65872": {
"AssetParametersc7bba0d9d477c86c6dc2adb0eb95842634a1c040dd3a66b42eec2bb604644d4fArtifactHash6742F1C9": {
"Type": "String",
"Description": "Artifact hash for asset \"41bb2d4a81eb782e91694a93a5e13f3e4a4ed3148bdb066856d1e5275b593cd7\""
"Description": "Artifact hash for asset \"c7bba0d9d477c86c6dc2adb0eb95842634a1c040dd3a66b42eec2bb604644d4f\""
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ nodeunitShim({
'MyFleetInstanceSecurityGroup774E8234': {
'Type': 'AWS::EC2::SecurityGroup',
'Properties': {
'GroupDescription': 'MyFleet/InstanceSecurityGroup',
'GroupDescription': 'TestStack/MyFleet/InstanceSecurityGroup',
'SecurityGroupEgress': [
{
'CidrIp': '0.0.0.0/0',
Expand All @@ -43,7 +43,7 @@ nodeunitShim({
'Tags': [
{
'Key': 'Name',
'Value': 'MyFleet',
'Value': 'TestStack/MyFleet',
},
],

Expand All @@ -68,7 +68,7 @@ nodeunitShim({
'Tags': [
{
'Key': 'Name',
'Value': 'MyFleet',
'Value': 'TestStack/MyFleet',
},
],
},
Expand Down Expand Up @@ -122,7 +122,7 @@ nodeunitShim({
{
'Key': 'Name',
'PropagateAtLaunch': true,
'Value': 'MyFleet',
'Value': 'TestStack/MyFleet',
},
],

Expand Down Expand Up @@ -520,7 +520,7 @@ nodeunitShim({
{
Key: 'Name',
PropagateAtLaunch: true,
Value: 'MyFleet',
Value: 'TestStack/MyFleet',
},
{
Key: 'notsuper',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ nodeunitShim({
{
Key: 'Name',
PropagateAtLaunch: true,
Value: 'ASG',
Value: 'Default/ASG',
},
],
VPCZoneIdentifier: [
Expand Down
6 changes: 3 additions & 3 deletions packages/@aws-cdk/aws-cloudformation/test/test.deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,9 @@ export = {
const nested2 = new NestedStack(nested1, 'Nested2');

// THEN
test.throws(() => nested1.addDependency(root), /Nested stack 'Nested1' cannot depend on a parent stack ''/);
test.throws(() => nested2.addDependency(nested1), /Nested stack 'Nested1\/Nested2' cannot depend on a parent stack 'Nested1'/);
test.throws(() => nested2.addDependency(root), /Nested stack 'Nested1\/Nested2' cannot depend on a parent stack ''/);
test.throws(() => nested1.addDependency(root), /Nested stack 'Default\/Nested1' cannot depend on a parent stack 'Default'/);
test.throws(() => nested2.addDependency(nested1), /Nested stack 'Default\/Nested1\/Nested2' cannot depend on a parent stack 'Default\/Nested1'/);
test.throws(() => nested2.addDependency(root), /Nested stack 'Default\/Nested1\/Nested2' cannot depend on a parent stack 'Default'/);
test.done();
},

Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export = {
'all',
],
DestinationArn: 'arn:aws:sns:*:123456789012:my_topic',
Name: 'MyRepository/arn:aws:sns:*:123456789012:my_topic',
Name: 'Default/MyRepository/arn:aws:sns:*:123456789012:my_topic',
},
],
},
Expand Down
6 changes: 3 additions & 3 deletions packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,7 @@ test('if an encryption key is included, encrypt/decrypt permissions are also add
],
'Version': '2012-10-17',
},
'Description': 'Customer-managed key auto-created for encrypting DynamoDB table at Table A',
'Description': 'Customer-managed key auto-created for encrypting DynamoDB table at Default/Table A',
'EnableKeyRotation': true,
},
'UpdateReplacePolicy': 'Retain',
Expand Down Expand Up @@ -1758,7 +1758,7 @@ describe('grants', () => {
const user = new iam.User(stack, 'user');

// WHEN
expect(() => table.grantTableListStreams(user)).toThrow(/DynamoDB Streams must be enabled on the table my-table/);
expect(() => table.grantTableListStreams(user)).toThrow(/DynamoDB Streams must be enabled on the table Default\/my-table/);
});

test('"grantTableListStreams" allows principal to list all streams for this table', () => {
Expand Down Expand Up @@ -1804,7 +1804,7 @@ describe('grants', () => {
const user = new iam.User(stack, 'user');

// WHEN
expect(() => table.grantStreamRead(user)).toThrow(/DynamoDB Streams must be enabled on the table my-table/);
expect(() => table.grantStreamRead(user)).toThrow(/DynamoDB Streams must be enabled on the table Default\/my-table/);
});

test('"grantStreamRead" allows principal to read and describe the table stream"', () => {
Expand Down

0 comments on commit 1d85a9f

Please sign in to comment.