Skip to content

Commit 398f872

Browse files
authored
feat: "stack relative exports" flag (#9604)
Previously, CloudFormation `Exports` generated by cross-stack references were named after a static part of the construct tree (everything after the 2nd component). This must have been left over from the days when the construct tree structure was strictly ``` App -> Stack -> Other constructs... ``` And so it would skip the `App` and `Stack` levels, leaving a construct path that was relative to the containing stack. For a while, `Stacks` have been able to occur at multiple levels, but this code was never updated. The result is that this now produces considerable breakage if stacks are moved around (motivating example: moving `Stack`s into `Stages` for the benefit of deploying them using CDK Pipelines). This behavior cannot be fixed without breaking most CDK applications that currently exist, so it is being introduced as a feature flag, which should be enabled on new applications and made default in V2, but for now is opt-in and to be enabled by people migrating their existing apps onto CDK Pipelines. The new behavior is to explicitly build the export name relative to the containing Stack, as opposed to relative to construct tree level 2. > Note that giving the new containing Stage the construct ID `"Default"` > is not good enough, as the new construct tree leveling looks like: > > ``` > App -> Stage -> Stack -> [Construct IDs] > "Default" / "Stack" / "IDs/I/care/about" > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ current algo will use these > ^^^^^^^^^^^^^^^^^ I actually want these > ``` > i.e., `"Default"` does not occur at a level where it will be properly ignored. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent a772fe8 commit 398f872

File tree

3 files changed

+58
-1
lines changed

3 files changed

+58
-1
lines changed

packages/@aws-cdk/core/lib/private/refs.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// ----------------------------------------------------
22
// CROSS REFERENCES
33
// ----------------------------------------------------
4+
import * as cxapi from '@aws-cdk/cx-api';
5+
46
import { CfnElement } from '../cfn-element';
57
import { CfnOutput } from '../cfn-output';
68
import { CfnParameter } from '../cfn-parameter';
@@ -199,8 +201,15 @@ function getCreateExportsScope(stack: Stack) {
199201
}
200202

201203
function generateExportName(stackExports: Construct, id: string) {
204+
const stackRelativeExports = stackExports.construct.tryGetContext(cxapi.STACK_RELATIVE_EXPORTS_CONTEXT);
202205
const stack = Stack.of(stackExports);
203-
const components = [...stackExports.construct.scopes.slice(2).map(c => c.construct.id), id];
206+
207+
const components = [
208+
...stackExports.construct.scopes
209+
.slice(stackRelativeExports ? stack.construct.scopes.length : 2)
210+
.map(c => c.construct.id),
211+
id,
212+
];
204213
const prefix = stack.stackName ? stack.stackName + ':' : '';
205214
const exportName = prefix + makeUniqueId(components);
206215
return exportName;

packages/@aws-cdk/core/test/test.stack.ts

+37
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,43 @@ export = {
249249
test.done();
250250
},
251251

252+
'Cross-stack reference export names are relative to the stack (when the flag is set)'(test: Test) {
253+
// GIVEN
254+
const app = new App({
255+
context: {
256+
'@aws-cdk/core:stackRelativeExports': 'true',
257+
},
258+
});
259+
const indifferentScope = new Construct(app, 'ExtraScope');
260+
261+
const stack1 = new Stack(indifferentScope, 'Stack1', {
262+
stackName: 'Stack1',
263+
});
264+
const resource1 = new CfnResource(stack1, 'Resource', { type: 'BLA' });
265+
const stack2 = new Stack(indifferentScope, 'Stack2');
266+
267+
// WHEN - used in another resource
268+
new CfnResource(stack2, 'SomeResource', { type: 'AWS::Some::Resource', properties: {
269+
someProperty: new Intrinsic(resource1.ref),
270+
}});
271+
272+
// THEN
273+
const assembly = app.synth();
274+
const template2 = assembly.getStackByName(stack2.stackName).template;
275+
276+
test.deepEqual(template2, {
277+
Resources: {
278+
SomeResource: {
279+
Type: 'AWS::Some::Resource',
280+
Properties: {
281+
someProperty: { 'Fn::ImportValue': 'Stack1:ExportsOutputRefResource1D5D905A' },
282+
},
283+
},
284+
},
285+
});
286+
test.done();
287+
},
288+
252289
'cross-stack references in lazy tokens work'(test: Test) {
253290
// GIVEN
254291
const app = new App();

packages/@aws-cdk/cx-api/lib/features.ts

+11
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ export const ENABLE_DIFF_NO_FAIL = ENABLE_DIFF_NO_FAIL_CONTEXT;
3434
*/
3535
export const NEW_STYLE_STACK_SYNTHESIS_CONTEXT = '@aws-cdk/core:newStyleStackSynthesis';
3636

37+
/**
38+
* Name exports based on the construct paths relative to the stack, rather than the global construct path
39+
*
40+
* Combined with the stack name this relative construct path is good enough to
41+
* ensure uniqueness, and makes the export names robust against refactoring
42+
* the location of the stack in the construct tree (specifically, moving the Stack
43+
* into a Stage).
44+
*/
45+
export const STACK_RELATIVE_EXPORTS_CONTEXT = '@aws-cdk/core:stackRelativeExports';
46+
3747
/**
3848
* This map includes context keys and values for feature flags that enable
3949
* capabilities "from the future", which we could not introduce as the default
@@ -50,6 +60,7 @@ export const NEW_STYLE_STACK_SYNTHESIS_CONTEXT = '@aws-cdk/core:newStyleStackSyn
5060
export const FUTURE_FLAGS = {
5161
[ENABLE_STACK_NAME_DUPLICATES_CONTEXT]: 'true',
5262
[ENABLE_DIFF_NO_FAIL_CONTEXT]: 'true',
63+
[STACK_RELATIVE_EXPORTS_CONTEXT]: 'true',
5364

5465
// We will advertise this flag when the feature is complete
5566
// [NEW_STYLE_STACK_SYNTHESIS]: 'true',

0 commit comments

Comments
 (0)