Skip to content

Commit 55f4a3e

Browse files
authored
Merge branch 'main' into cloudwatch_logs_transformer_support
2 parents 067537a + 4c7d9f4 commit 55f4a3e

File tree

6 files changed

+216
-3
lines changed

6 files changed

+216
-3
lines changed

packages/aws-cdk-lib/core/lib/stack.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,15 @@ export interface StackProps {
121121
readonly stackName?: string;
122122

123123
/**
124-
* Stack tags that will be applied to all the taggable resources and the stack itself.
124+
* Tags that will be applied to the Stack
125+
*
126+
* These tags are applied to the CloudFormation Stack itself. They will not
127+
* appear in the CloudFormation template.
128+
*
129+
* However, at deployment time, CloudFormation will apply these tags to all
130+
* resources in the stack that support tagging. You will not be able to exempt
131+
* resources from tagging (using the `excludeResourceTypes` property of
132+
* `Tags.of(...).add()`) for tags applied in this way.
125133
*
126134
* @default {}
127135
*/
@@ -1609,6 +1617,24 @@ export class Stack extends Construct implements ITaggable {
16091617
pattern,
16101618
));
16111619
}
1620+
1621+
/**
1622+
* Configure a stack tag
1623+
*
1624+
* At deploy time, CloudFormation will automatically apply all stack tags to all resources in the stack.
1625+
*/
1626+
public addStackTag(tagName: string, tagValue: string) {
1627+
this.tags.setTag(tagName, tagValue);
1628+
}
1629+
1630+
/**
1631+
* Remove a stack tag
1632+
*
1633+
* At deploy time, CloudFormation will automatically apply all stack tags to all resources in the stack.
1634+
*/
1635+
public removeStackTag(tagName: string) {
1636+
this.tags.removeTag(tagName, 0);
1637+
}
16121638
}
16131639

16141640
function merge(template: any, fragment: any): void {

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

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Construct, IConstruct } from 'constructs';
22
import { Annotations } from './annotations';
33
import { IAspect, Aspects, AspectOptions } from './aspect';
44
import { UnscopedValidationError } from './errors';
5+
import { FeatureFlags } from './feature-flags';
6+
import * as cxapi from '../../cx-api';
57
import { mutatingAspectPrio32333 } from './private/aspect-prio';
68
import { ITaggable, ITaggableV2, TagManager } from './tag-manager';
79

@@ -22,6 +24,7 @@ export interface TagProps {
2224
* An empty array will allow this tag to be applied to all resources. A
2325
* non-empty array will apply this tag only if the Resource type is not in
2426
* this array.
27+
*
2528
* @default []
2629
*/
2730
readonly excludeResourceTypes?: string[];
@@ -155,12 +158,41 @@ export class Tags {
155158
return new Tags(scope);
156159
}
157160

158-
private constructor(private readonly scope: IConstruct) { }
161+
private readonly explicitStackTags: boolean;
162+
163+
private constructor(private readonly scope: IConstruct) {
164+
this.explicitStackTags = FeatureFlags.of(scope).isEnabled(cxapi.EXPLICIT_STACK_TAGS) ?? false;
165+
}
159166

160167
/**
161-
* add tags to the node of a construct and all its the taggable children
168+
* Add tags to the node of a construct and all its the taggable children
169+
*
170+
* ## Tagging and CloudFormation Stacks
171+
*
172+
* If the feature flag `@aws-cdk/core:explicitStackTags` is set to `true`
173+
* (recommended modern behavior), Stacks will not automatically be tagged.
174+
* Stack tags should be configured on Stacks directly (preferred), or
175+
* you must explicitly include the resource type `aws:cdk:stack` in the
176+
* `includeResourceTypes` array.
177+
*
178+
* If the feature flag is set to `false` (legacy behavior) then both Stacks
179+
* and resources in the indicated scope will both be tagged by default, which
180+
* leads to tags being applied twice (once in the template, and then once
181+
* again automatically by CloudFormation as part of the stack deployment).
182+
* That behavior leads to loss of control as `excludeResourceTypes` will
183+
* prevent tags from appearing in the template, but they will still be
184+
* applied to the Stack and hence CloudFormation will still apply them
185+
* to the resource.
162186
*/
163187
public add(key: string, value: string, props: TagProps = {}) {
188+
// Implicitly add `aws:cdk:stack` to the `excludeResourceTypes` array in modern behavior
189+
if (this.explicitStackTags && !props.includeResourceTypes?.includes('aws:cdk:stack')) {
190+
props = {
191+
...props,
192+
excludeResourceTypes: [...props.excludeResourceTypes ?? [], 'aws:cdk:stack'],
193+
};
194+
}
195+
164196
const tag = new Tag(key, value, props);
165197
const options: AspectOptions = { priority: mutatingAspectPrio32333(this.scope) };
166198
Aspects.of(this.scope).add(tag, options);

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

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import {
1414
PERMISSIONS_BOUNDARY_CONTEXT_KEY,
1515
Aspects,
1616
Stage,
17+
TagManager,
18+
Resource,
19+
TagType,
20+
ITaggable,
21+
ITaggableV2,
1722
Token,
1823
} from '../lib';
1924
import { Intrinsic } from '../lib/private/intrinsic';
@@ -2162,6 +2167,89 @@ describe('stack', () => {
21622167
expect(asm.getStackArtifact(stack2.artifactId).tags).toEqual(expected);
21632168
});
21642169

2170+
describe.each([false, true])(`with ${cxapi.EXPLICIT_STACK_TAGS} set to %p`, (explicitStackTags) => {
2171+
let app: App;
2172+
2173+
beforeEach(() => {
2174+
app = new App({
2175+
stackTraces: false,
2176+
context: {
2177+
[cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false,
2178+
[cxapi.EXPLICIT_STACK_TAGS]: explicitStackTags,
2179+
},
2180+
});
2181+
});
2182+
2183+
test('stack tags are both in stack metadata and stack artifact properties', () => {
2184+
// GIVEN
2185+
2186+
const stack = new Stack(app, 'stack1', {
2187+
tags: {
2188+
foo: 'bar',
2189+
},
2190+
});
2191+
2192+
// THEN
2193+
const asm = app.synth();
2194+
2195+
const stackArtifact = asm.getStackArtifact(stack.artifactId);
2196+
expect(stackArtifact.manifest.metadata).toEqual({
2197+
'/stack1': [
2198+
{
2199+
type: 'aws:cdk:stack-tags',
2200+
data: [{ key: 'foo', value: 'bar' }],
2201+
},
2202+
],
2203+
});
2204+
expect(stackArtifact.tags).toEqual({ foo: 'bar' });
2205+
});
2206+
2207+
test('stack tags do not appear in template', () => {
2208+
const stack = new Stack(app, 'stack1', {
2209+
tags: {
2210+
foo: 'bar',
2211+
},
2212+
});
2213+
new TaggableResource(stack, 'res');
2214+
2215+
// THEN
2216+
const asm = app.synth();
2217+
const stackArtifact = asm.getStackArtifact(stack.artifactId);
2218+
expect(stackArtifact.template.Resources.res).toEqual({
2219+
Type: 'AWS::Taggable::Resource',
2220+
Properties: {
2221+
R: 1,
2222+
},
2223+
});
2224+
});
2225+
2226+
test('tags added using Tags.of() are or are not applied to Stacks', () => {
2227+
const stack = new Stack(app, 'stack1');
2228+
Tags.of(stack).add('resourceTag', 'resourceValue');
2229+
2230+
// THEN
2231+
const asm = app.synth();
2232+
const stackArtifact = asm.getStackArtifact(stack.artifactId);
2233+
if (explicitStackTags) {
2234+
expect(stackArtifact.tags).toEqual({});
2235+
} else {
2236+
expect(stackArtifact.tags).toEqual({ resourceTag: 'resourceValue' });
2237+
}
2238+
});
2239+
2240+
test('tags added using Tags.of() are applied to Stacks if explicitly included', () => {
2241+
const stack = new Stack(app, 'stack1');
2242+
Tags.of(stack).add('resourceTag', 'resourceValue', {
2243+
includeResourceTypes: ['aws:cdk:stack'],
2244+
});
2245+
2246+
// THEN
2247+
const asm = app.synth();
2248+
const stackArtifact = asm.getStackArtifact(stack.artifactId);
2249+
expect(stackArtifact.tags).toEqual({ resourceTag: 'resourceValue' });
2250+
});
2251+
});
2252+
21652253
test('warning when stack tags contain tokens', () => {
21662254
// GIVEN
21672255
const app = new App({
@@ -2573,3 +2661,19 @@ class StackWithPostProcessor extends Stack {
25732661
return template;
25742662
}
25752663
}
2664+
2665+
class TaggableResource extends CfnResource implements ITaggableV2 {
2666+
public readonly cdkTagManager = new TagManager(TagType.KEY_VALUE, 'TaggableResource', {}, {
2667+
tagPropertyName: 'Tags',
2668+
});
2669+
2670+
constructor(scope: Construct, id: string) {
2671+
super(scope, id, {
2672+
type: 'AWS::Taggable::Resource',
2673+
properties: {
2674+
R: 1,
2675+
Tags: Lazy.any({ produce: () => this.cdkTagManager.renderTags() }),
2676+
},
2677+
});
2678+
}
2679+
}

packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Flags come in three types:
104104
| [@aws-cdk/aws-s3:publicAccessBlockedByDefault](#aws-cdkaws-s3publicaccessblockedbydefault) | When enabled, setting any combination of options for BlockPublicAccess will automatically set true for any options not defined. | 2.196.0 | fix |
105105
| [@aws-cdk/aws-lambda:useCdkManagedLogGroup](#aws-cdkaws-lambdausecdkmanagedloggroup) | When enabled, CDK creates and manages loggroup for the lambda function | 2.200.0 | new default |
106106
| [@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal](#aws-cdkaws-kmsapplyimportedaliaspermissionstoprincipal) | Enable grant methods on Aliases imported by name to use kms:ResourceAliases condition | 2.202.0 | fix |
107+
| [@aws-cdk/core:explicitStackTags](#aws-cdkcoreexplicitstacktags) | When enabled, stack tags need to be assigned explicitly on a Stack. | V2NEXT | new default |
107108

108109
<!-- END table -->
109110

@@ -167,6 +168,7 @@ The following json shows the current recommended set of flags, as `cdk init` wou
167168
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
168169
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
169170
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
171+
"@aws-cdk/core:explicitStackTags": true,
170172
"@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false,
171173
"@aws-cdk/aws-ecs:disableEcsImdsBlocking": true,
172174
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
@@ -2196,4 +2198,31 @@ When disabled, grant calls on imported aliases will be dropped (no-op) to mainta
21962198
**Compatibility with old behavior:** Remove calls to the grant* methods on the aliases referenced by name
21972199

21982200

2201+
### @aws-cdk/core:explicitStackTags
2202+
2203+
*When enabled, stack tags need to be assigned explicitly on a Stack.*
2204+
2205+
Flag type: New default behavior
2206+
2207+
Without this feature flag enabled, if tags are added to a Stack using
2208+
`Tags.of(scope).add(...)`, they will be added to both the stack and all resources
2209+
in the stack template.
2210+
2211+
That leads to the tags being applied twice: once in the template, and once
2212+
again automatically by CloudFormation, which will apply all stack tags to
2213+
all resources in the stack. This leads to loss of control, as the
2214+
`excludeResourceTypes` option of the Tags API will not have any effect.
2215+
2216+
With this flag enabled, tags added to a stack using `Tags.of(...)` are ignored,
2217+
and Stack tags must be configured explicitly on the Stack object.
2218+
2219+
2220+
| Since | Unset behaves like | Recommended value |
2221+
| ----- | ----- | ----- |
2222+
| (not in v1) | | |
2223+
| V2NEXT | `false` | `true` |
2224+
2225+
**Compatibility with old behavior:** Configure stack-level tags using `new Stack(..., { tags: { ... } })`.
2226+
2227+
21992228
<!-- END details -->

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export const ECS_REMOVE_DEFAULT_DEPLOYMENT_ALARM = '@aws-cdk/aws-ecs:removeDefau
112112
export const LOG_API_RESPONSE_DATA_PROPERTY_TRUE_DEFAULT = '@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault';
113113
export const S3_KEEP_NOTIFICATION_IN_IMPORTED_BUCKET = '@aws-cdk/aws-s3:keepNotificationInImportedBucket';
114114
export const USE_NEW_S3URI_PARAMETERS_FOR_BEDROCK_INVOKE_MODEL_TASK = '@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask';
115+
export const EXPLICIT_STACK_TAGS = '@aws-cdk/core:explicitStackTags';
115116
export const REDUCE_EC2_FARGATE_CLOUDWATCH_PERMISSIONS = '@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions';
116117
export const DYNAMODB_TABLEV2_RESOURCE_POLICY_PER_REPLICA = '@aws-cdk/aws-dynamodb:resourcePolicyPerReplica';
117118
export const EC2_SUM_TIMEOUT_ENABLED = '@aws-cdk/aws-ec2:ec2SumTImeoutEnabled';
@@ -1177,6 +1178,26 @@ export const FLAGS: Record<string, FlagInfo> = {
11771178
},
11781179

11791180
//////////////////////////////////////////////////////////////////////
1181+
[EXPLICIT_STACK_TAGS]: {
1182+
type: FlagType.ApiDefault,
1183+
summary: 'When enabled, stack tags need to be assigned explicitly on a Stack.',
1184+
detailsMd: `
1185+
Without this feature flag enabled, if tags are added to a Stack using
1186+
\`Tags.of(scope).add(...)\`, they will be added to both the stack and all resources
1187+
in the stack template.
1188+
1189+
That leads to the tags being applied twice: once in the template, and once
1190+
again automatically by CloudFormation, which will apply all stack tags to
1191+
all resources in the stack. This leads to loss of control, as the
1192+
\`excludeResourceTypes\` option of the Tags API will not have any effect.
1193+
1194+
With this flag enabled, tags added to a stack using \`Tags.of(...)\` are ignored,
1195+
and Stack tags must be configured explicitly on the Stack object.
1196+
`,
1197+
introducedIn: { v2: 'V2NEXT' },
1198+
recommendedValue: true,
1199+
compatibilityWithOldBehaviorMd: 'Configure stack-level tags using `new Stack(..., { tags: { ... } })`.',
1200+
},
11801201
[Enable_IMDS_Blocking_Deprecated_Feature]: {
11811202
type: FlagType.Temporary,
11821203
summary: 'When set to true along with canContainersAccessInstanceRole=false in ECS cluster, new updated ' +

packages/aws-cdk-lib/recommended-feature-flags.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
5252
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
5353
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
54+
"@aws-cdk/core:explicitStackTags": true,
5455
"@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false,
5556
"@aws-cdk/aws-ecs:disableEcsImdsBlocking": true,
5657
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,

0 commit comments

Comments
 (0)