Skip to content

Commit fc7663b

Browse files
author
CDK Contributor
committed
fix(core): ensure CloudFormation ChangeSets receive tags with explicitStackTags flag
When the @aws-cdk/core:explicitStackTags feature flag was introduced in v2.205.0, it inadvertently caused CloudFormation ChangeSets to not receive stack tags, breaking deployments with SCP policies requiring tags on ChangeSets. This fix adds a new property 'applyToChangeSets' to TagProps (default: true) that ensures tags are still applied to the stack for ChangeSet purposes, while maintaining the correct behavior of not duplicating tags on resources. Fixes regression introduced in v2.205.0 where ChangeSets lost their tags.
1 parent 88f3e04 commit fc7663b

11 files changed

Lines changed: 373 additions & 0 deletions

packages/aws-cdk-lib/core/lib/tag-aspect.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Annotations } from './annotations';
33
import { IAspect, Aspects, AspectOptions } from './aspect';
44
import { UnscopedValidationError } from './errors';
55
import { FeatureFlags } from './feature-flags';
6+
import { Stack } from './stack';
67
import * as cxapi from '../../cx-api';
78
import { mutatingAspectPrio32333 } from './private/aspect-prio';
89
import { ITaggable, ITaggableV2, TagManager } from './tag-manager';
@@ -38,6 +39,18 @@ export interface TagProps {
3839
*/
3940
readonly includeResourceTypes?: string[];
4041

42+
/**
43+
* Whether to apply tags to CloudFormation ChangeSets
44+
*
45+
* This ensures tags are applied to ChangeSets even when the
46+
* explicitStackTags feature flag excludes stack-level tags.
47+
* This is important for compliance with SCP policies that
48+
* require tags on ChangeSets.
49+
*
50+
* @default true
51+
*/
52+
readonly applyToChangeSets?: boolean;
53+
4154
/**
4255
* Priority of the tag operation
4356
*
@@ -186,7 +199,18 @@ export class Tags {
186199
*/
187200
public add(key: string, value: string, props: TagProps = {}) {
188201
// Implicitly add `aws:cdk:stack` to the `excludeResourceTypes` array in modern behavior
202+
// BUT: If applyToChangeSets is true (default), we need to ensure ChangeSets still get tagged
189203
if (this.explicitStackTags && !props.includeResourceTypes?.includes('aws:cdk:stack')) {
204+
// Check if we should still apply to ChangeSets
205+
const applyToChangeSets = props.applyToChangeSets !== false;
206+
207+
// If applyToChangeSets is true, we need to ensure the stack still gets the tags
208+
// for ChangeSets, even though resources won't get them from the stack
209+
if (applyToChangeSets && Stack.isStack(this.scope)) {
210+
// Apply the tag directly to the stack for ChangeSet purposes
211+
(this.scope as Stack).addStackTag(key, value);
212+
}
213+
190214
props = {
191215
...props,
192216
excludeResourceTypes: [...props.excludeResourceTypes ?? [], 'aws:cdk:stack'],

packages/aws-cdk-lib/core/test/integ.stack-tags-changeset.js.snapshot/cdk.out

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/aws-cdk-lib/core/test/integ.stack-tags-changeset.js.snapshot/integ-stack-tags-changeset.assets.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"Outputs": {
3+
"TestOutput": {
4+
"Description": "Test output for integration test",
5+
"Value": "test-value"
6+
}
7+
}
8+
}

packages/aws-cdk-lib/core/test/integ.stack-tags-changeset.js.snapshot/integ.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/aws-cdk-lib/core/test/integ.stack-tags-changeset.js.snapshot/integtestmodelDefaultTestDeployAssertCF40BD53.assets.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/aws-cdk-lib/core/test/integ.stack-tags-changeset.js.snapshot/integtestmodelDefaultTestDeployAssertCF40BD53.template.json

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/aws-cdk-lib/core/test/integ.stack-tags-changeset.js.snapshot/manifest.json

Lines changed: 83 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/aws-cdk-lib/core/test/integ.stack-tags-changeset.js.snapshot/tree.json

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Integration test for stack tagging behavior with explicitStackTags feature flag.
3+
* This test verifies that ChangeSets receive proper tags even when the explicitStackTags
4+
* feature flag is enabled, ensuring compliance with SCP policies that require tags on ChangeSets.
5+
*/
6+
7+
import { App, Stack, Tags, CfnOutput } from 'aws-cdk-lib';
8+
9+
const app = new App({
10+
context: {
11+
'@aws-cdk/core:explicitStackTags': true,
12+
},
13+
});
14+
15+
// Stack with Tags.of(stack).add() - should apply to ChangeSets
16+
const stack = new Stack(app, 'integ-stack-tags-changeset', {
17+
env: { region: 'us-east-1' },
18+
});
19+
20+
// Add tags using Tags.of(stack).add() - these should be applied to ChangeSets
21+
Tags.of(stack).add('Environment', 'IntegTest');
22+
Tags.of(stack).add('Project', 'CDK-Core');
23+
Tags.of(stack).add('Owner', 'CDK-Team');
24+
Tags.of(stack).add('CostCenter', 'Engineering');
25+
26+
// Test the new applyToChangeSets property
27+
Tags.of(stack).add('ChangeSetTag', 'ShouldAppear');
28+
Tags.of(stack).add('NoChangeSetTag', 'ShouldNotAppear', { applyToChangeSets: false });
29+
30+
// Add direct stack tags as well
31+
stack.addStackTag('DirectTag', 'DirectValue');
32+
33+
// Add a simple resource to make the stack deployable
34+
new CfnOutput(stack, 'TestOutput', {
35+
value: 'test-value',
36+
description: 'Test output for integration test',
37+
});
38+
39+
app.synth();

0 commit comments

Comments
 (0)