Skip to content

Commit dd912da

Browse files
authored
feat(core): acknowledge warnings (#26144)
This PR adds the ability to acknowledge annotation warning messages. The main motivation behind this is to allow people to set the `--strict` mode to fail synthesis on warnings. Currently it is all or nothing, you have to get rid of _all_ warnings to use `--strict`. With this feature users will be able to `acknowledge` warnings saying that they are aware, but it does not apply to them. Since we want all warnings to now have an id this will deprecate the `addWarning` method and adds a new `addWarningV2` method. Since the acknowledgements and warnings are written as metadata, it is possible to enhance this in the future to report on warnings and acknowledgements. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent dc8f313 commit dd912da

File tree

89 files changed

+734
-208
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+734
-208
lines changed

packages/@aws-cdk/aws-gamelift-alpha/lib/fleet-base.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -639,10 +639,10 @@ export abstract class FleetBase extends cdk.Resource implements IFleet {
639639
}
640640

641641
protected warnVpcPeeringAuthorizations(scope: Construct): void {
642-
cdk.Annotations.of(scope).addWarning([
642+
cdk.Annotations.of(scope).addWarningV2('@aws-cdk/aws-gamelift:fleetAutorizeVpcPeering', [
643643
'To authorize the VPC peering, call the GameLift service API CreateVpcPeeringAuthorization() or use the AWS CLI command create-vpc-peering-authorization.',
644644
'Make this call using the account that manages your non-GameLift resources.',
645645
'See: https://docs.aws.amazon.com/gamelift/latest/developerguide/vpc-peering.html',
646646
].join('\n'));
647647
}
648-
}
648+
}

packages/@aws-cdk/aws-servicecatalogappregistry-alpha/lib/aspects/stack-associator.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ abstract class StackAssociatorBase implements IAspect {
3838
if (Stage.isStage(childNode)) {
3939
var stageAssociated = this.applicationAssociator?.isStageAssociated(childNode);
4040
if (stageAssociated === false) {
41-
this.warning(childNode, 'Associate Stage: ' + childNode.stageName + ' to ensure all stacks in your cdk app are associated with AppRegistry. '
41+
this.warning('StackNotAssociated', childNode, 'Associate Stage: ' + childNode.stageName + ' to ensure all stacks in your cdk app are associated with AppRegistry. '
4242
+ 'You can use ApplicationAssociator.associateStage to associate any stage.');
4343
}
4444
}
@@ -73,8 +73,8 @@ abstract class StackAssociatorBase implements IAspect {
7373
* @param node The scope to add the warning to.
7474
* @param message The error message.
7575
*/
76-
private warning(node: IConstruct, message: string): void {
77-
Annotations.of(node).addWarning(message);
76+
private warning(id: string, node: IConstruct, message: string): void {
77+
Annotations.of(node).addWarningV2(`@aws-cdk/servicecatalogappregistry:${id}`, message);
7878
}
7979

8080
/**
@@ -87,12 +87,12 @@ abstract class StackAssociatorBase implements IAspect {
8787
*/
8888
private handleCrossRegionStack(node: Stack): void {
8989
if (isRegionUnresolved(this.application.env.region, node.region)) {
90-
this.warning(node, 'Environment agnostic stack determined, AppRegistry association might not work as expected in case you deploy cross-region or cross-account stack.');
90+
this.warning('EnvironmentAgnosticStack', node, 'Environment agnostic stack determined, AppRegistry association might not work as expected in case you deploy cross-region or cross-account stack.');
9191
return;
9292
}
9393

9494
if (node.region != this.application.env.region) {
95-
this.warning(node, 'AppRegistry does not support cross region associations, deployment might fail if there is cross region stacks in the app. Application region '
95+
this.warning('CrossRegionAssociation', node, 'AppRegistry does not support cross region associations, deployment might fail if there is cross region stacks in the app. Application region '
9696
+ this.application.env.region + ', stack region ' + node.region);
9797
}
9898
}
@@ -106,7 +106,7 @@ abstract class StackAssociatorBase implements IAspect {
106106
*/
107107
private handleCrossAccountStack(node: Stack): void {
108108
if (isAccountUnresolved(this.application.env.account!, node.account)) {
109-
this.warning(node, 'Environment agnostic stack determined, AppRegistry association might not work as expected in case you deploy cross-region or cross-account stack.');
109+
this.warning('EnvironmentAgnosticStack', node, 'Environment agnostic stack determined, AppRegistry association might not work as expected in case you deploy cross-region or cross-account stack.');
110110
return;
111111
}
112112

@@ -121,7 +121,7 @@ abstract class StackAssociatorBase implements IAspect {
121121

122122
this.sharedAccounts.add(node.account);
123123
} else {
124-
this.warning(node, 'Cross-account stack detected but application sharing and association will be skipped because cross-account option is not enabled.');
124+
this.warning('AssociationSkipped', node, 'Cross-account stack detected but application sharing and association will be skipped because cross-account option is not enabled.');
125125
return;
126126
}
127127
}

packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application-associator.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ describe('Scope based Associations with Application with Cross Region/Account',
191191
const crossAccountStack = new cdk.Stack(app, 'crossRegionStack', {
192192
env: { account: 'account', region: 'region' },
193193
});
194-
Annotations.fromStack(crossAccountStack).hasWarning('*', 'Cross-account stack detected but application sharing and association will be skipped because cross-account option is not enabled.');
194+
Annotations.fromStack(crossAccountStack).hasWarning('*', 'Cross-account stack detected but application sharing and association will be skipped because cross-account option is not enabled. [ack: @aws-cdk/servicecatalogappregistry:AssociationSkipped]');
195195
});
196196

197197
test('ApplicationAssociator with cross account stacks inside cdkApp does not give warning if associateCrossAccountStacks is set to true', () => {
@@ -223,7 +223,7 @@ describe('Scope based Associations with Application with Cross Region/Account',
223223
env: { account: 'account', region: 'region' },
224224
});
225225
Annotations.fromStack(crossRegionStack).hasWarning('*', 'AppRegistry does not support cross region associations, deployment might fail if there is cross region stacks in the app.'
226-
+ ' Application region region2, stack region region');
226+
+ ' Application region region2, stack region region [ack: @aws-cdk/servicecatalogappregistry:CrossRegionAssociation]');
227227
});
228228

229229
test('Environment Agnostic ApplicationAssociator with cross region stacks inside cdkApp gives warning', () => {
@@ -237,7 +237,7 @@ describe('Scope based Associations with Application with Cross Region/Account',
237237
const crossRegionStack = new cdk.Stack(app, 'crossRegionStack', {
238238
env: { account: 'account', region: 'region' },
239239
});
240-
Annotations.fromStack(crossRegionStack).hasWarning('*', 'Environment agnostic stack determined, AppRegistry association might not work as expected in case you deploy cross-region or cross-account stack.');
240+
Annotations.fromStack(crossRegionStack).hasWarning('*', 'Environment agnostic stack determined, AppRegistry association might not work as expected in case you deploy cross-region or cross-account stack. [ack: @aws-cdk/servicecatalogappregistry:EnvironmentAgnosticStack]');
241241
});
242242

243243
test('Cdk App Containing Pipeline with stage but stage not associated throws error', () => {
@@ -253,7 +253,7 @@ describe('Scope based Associations with Application with Cross Region/Account',
253253
});
254254
app.synth();
255255
Annotations.fromStack(pipelineStack).hasWarning('*',
256-
'Associate Stage: SampleStage to ensure all stacks in your cdk app are associated with AppRegistry. You can use ApplicationAssociator.associateStage to associate any stage.');
256+
'Associate Stage: SampleStage to ensure all stacks in your cdk app are associated with AppRegistry. You can use ApplicationAssociator.associateStage to associate any stage. [ack: @aws-cdk/servicecatalogappregistry:StackNotAssociated]');
257257
});
258258

259259
test('Cdk App Containing Pipeline with stage and stage associated successfully gets synthesized', () => {

packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ describe('Scope based Associations with Application with Cross Region/Account',
512512
application.associateAllStacksInScope(stage);
513513
Annotations.fromStack(stageStack).hasWarning('*',
514514
'AppRegistry does not support cross region associations, deployment might fail if there is cross region stacks in the app.'
515-
+ ' Application region region, stack region region1');
515+
+ ' Application region region, stack region region1 [ack: @aws-cdk/servicecatalogappregistry:CrossRegionAssociation]');
516516
});
517517
});
518518

packages/aws-cdk-lib/assertions/test/annotations.test.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ describe('Messages', () => {
7575

7676
describe('hasWarning', () => {
7777
test('match', () => {
78-
annotations.hasWarning('/Default/Fred', 'this is a warning');
78+
annotations.hasWarning('/Default/Fred', 'this is a warning [ack: Fred]');
7979
});
8080

8181
test('no match', () => {
@@ -89,7 +89,7 @@ describe('Messages', () => {
8989
});
9090

9191
test('no match', () => {
92-
expect(() => annotations.hasNoWarning('/Default/Fred', 'this is a warning'))
92+
expect(() => annotations.hasNoWarning('/Default/Fred', 'this is a warning [ack: Fred]'))
9393
.toThrowError(/Expected no matches, but stack has 1 messages as follows:/);
9494
});
9595
});
@@ -183,16 +183,16 @@ describe('Multiple Messages on the Resource', () => {
183183
test('succeeds on hasXxx APIs', () => {
184184
annotations.hasError('/Default/Foo', 'error: this is an error');
185185
annotations.hasError('/Default/Foo', 'error: unsupported type Foo::Bar');
186-
annotations.hasWarning('/Default/Foo', 'warning: Foo::Bar is deprecated');
186+
annotations.hasWarning('/Default/Foo', 'warning: Foo::Bar is deprecated [ack: Foo]');
187187
});
188188

189189
test('succeeds on findXxx APIs', () => {
190190
const result1 = annotations.findError('*', Match.stringLikeRegexp('error:.*'));
191191
expect(result1.length).toEqual(4);
192192
const result2 = annotations.findError('/Default/Bar', Match.stringLikeRegexp('error:.*'));
193193
expect(result2.length).toEqual(2);
194-
const result3 = annotations.findWarning('/Default/Bar', 'warning: Foo::Bar is deprecated');
195-
expect(result3[0].entry.data).toEqual('warning: Foo::Bar is deprecated');
194+
const result3 = annotations.findWarning('/Default/Bar', 'warning: Foo::Bar is deprecated [ack: Bar]');
195+
expect(result3[0].entry.data).toEqual('warning: Foo::Bar is deprecated [ack: Bar]');
196196
});
197197
});
198198
class MyAspect implements IAspect {
@@ -209,7 +209,8 @@ class MyAspect implements IAspect {
209209
};
210210

211211
protected warn(node: IConstruct, message: string): void {
212-
Annotations.of(node).addWarning(message);
212+
// Use construct ID as suppression string, just to make it unique easily
213+
Annotations.of(node).addWarningV2(node.node.id, message);
213214
}
214215

215216
protected error(node: IConstruct, message: string): void {
@@ -231,10 +232,10 @@ class MultipleAspectsPerNode implements IAspect {
231232
}
232233

233234
protected warn(node: IConstruct, message: string): void {
234-
Annotations.of(node).addWarning(message);
235+
Annotations.of(node).addWarningV2(node.node.id, message);
235236
}
236237

237238
protected error(node: IConstruct, message: string): void {
238239
Annotations.of(node).addError(message);
239240
}
240-
}
241+
}

packages/aws-cdk-lib/aws-apigateway/lib/method.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ export class Method extends Resource {
288288
public addMethodResponse(methodResponse: MethodResponse): void {
289289
const mr = this.methodResponses.find((x) => x.statusCode === methodResponse.statusCode);
290290
if (mr) {
291-
Annotations.of(this).addWarning(`addMethodResponse called multiple times with statusCode=${methodResponse.statusCode}, deployment will be nondeterministic. Use a single addMethodResponse call to configure the entire response.`);
291+
Annotations.of(this).addWarningV2('@aws-cdk/aws-apigateway:duplicateStatusCodes', `addMethodResponse called multiple times with statusCode=${methodResponse.statusCode}, deployment will be nondeterministic. Use a single addMethodResponse call to configure the entire response.`);
292292
}
293293
this.methodResponses.push(methodResponse);
294294
}
@@ -512,4 +512,4 @@ export enum AuthorizationType {
512512

513513
function pathForArn(path: string): string {
514514
return path.replace(/\{[^\}]*\}/g, '*'); // replace path parameters (like '{bookId}') with asterisk
515-
}
515+
}

packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export abstract class Schedule {
6363
public readonly expressionString: string = `cron(${minute} ${hour} ${day} ${month} ${weekDay} ${year})`;
6464
public _bind(scope: Construct) {
6565
if (!options.minute) {
66-
Annotations.of(scope).addWarning('cron: If you don\'t pass \'minute\', by default the event runs every minute. Pass \'minute: \'*\'\' if that\'s what you intend, or \'minute: 0\' to run once per hour instead.');
66+
Annotations.of(scope).addWarningV2('@aws-cdk/aws-applicationautoscaling:defaultRunEveryMinute', 'cron: If you don\'t pass \'minute\', by default the event runs every minute. Pass \'minute: \'*\'\' if that\'s what you intend, or \'minute: 0\' to run once per hour instead.');
6767
}
6868
return new LiteralSchedule(this.expressionString);
6969
}

packages/aws-cdk-lib/aws-autoscaling/lib/aspects/require-imdsv2-aspect.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ export class AutoScalingGroupRequireImdsv2Aspect implements cdk.IAspect {
3434
* @param message The warning message.
3535
*/
3636
protected warn(node: IConstruct, message: string) {
37-
cdk.Annotations.of(node).addWarning(`${AutoScalingGroupRequireImdsv2Aspect.name} failed on node ${node.node.id}: ${message}`);
37+
cdk.Annotations.of(node).addWarningV2(`@aws-cdk/aws-autoscaling:imdsv2${AutoScalingGroupRequireImdsv2Aspect.name}`, `${AutoScalingGroupRequireImdsv2Aspect.name} failed on node ${node.node.id}: ${message}`);
3838
}
3939
}

packages/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1380,7 +1380,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements
13801380
});
13811381

13821382
if (desiredCapacity !== undefined) {
1383-
Annotations.of(this).addWarning('desiredCapacity has been configured. Be aware this will reset the size of your AutoScalingGroup on every deployment. See https://github.com/aws/aws-cdk/issues/5215');
1383+
Annotations.of(this).addWarningV2('@aws-cdk/aws-autoscaling:desiredCapacitySet', 'desiredCapacity has been configured. Be aware this will reset the size of your AutoScalingGroup on every deployment. See https://github.com/aws/aws-cdk/issues/5215');
13841384
}
13851385

13861386
this.maxInstanceLifetime = props.maxInstanceLifetime;
@@ -2296,7 +2296,7 @@ function synthesizeBlockDeviceMappings(construct: Construct, blockDevices: Block
22962296
throw new Error('iops property is required with volumeType: EbsDeviceVolumeType.IO1');
22972297
}
22982298
} else if (volumeType !== EbsDeviceVolumeType.IO1) {
2299-
Annotations.of(construct).addWarning('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1');
2299+
Annotations.of(construct).addWarningV2('@aws-cdk/aws-autoscaling:iopsIgnored', 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1');
23002300
}
23012301
}
23022302

packages/aws-cdk-lib/aws-autoscaling/lib/schedule.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export abstract class Schedule {
3333
public readonly expressionString: string = `${minute} ${hour} ${day} ${month} ${weekDay}`;
3434
public _bind(scope: Construct) {
3535
if (!options.minute) {
36-
Annotations.of(scope).addWarning('cron: If you don\'t pass \'minute\', by default the event runs every minute. Pass \'minute: \'*\'\' if that\'s what you intend, or \'minute: 0\' to run once per hour instead.');
36+
Annotations.of(scope).addWarningV2('@aws-cdk/aws-autoscaling:scheduleDefaultRunsEveryMinute', 'cron: If you don\'t pass \'minute\', by default the event runs every minute. Pass \'minute: \'*\'\' if that\'s what you intend, or \'minute: 0\' to run once per hour instead.');
3737
}
3838
return new LiteralSchedule(this.expressionString);
3939
}

packages/aws-cdk-lib/aws-autoscaling/test/auto-scaling-group.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1235,7 +1235,7 @@ describe('auto scaling group', () => {
12351235
});
12361236

12371237
// THEN
1238-
Annotations.fromStack(stack).hasWarning('/Default/MyStack', 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1');
1238+
Annotations.fromStack(stack).hasWarning('/Default/MyStack', 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1 [ack: @aws-cdk/aws-autoscaling:iopsIgnored]');
12391239
});
12401240

12411241
test('warning if iops and volumeType !== IO1', () => {
@@ -1259,7 +1259,7 @@ describe('auto scaling group', () => {
12591259
});
12601260

12611261
// THEN
1262-
Annotations.fromStack(stack).hasWarning('/Default/MyStack', 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1');
1262+
Annotations.fromStack(stack).hasWarning('/Default/MyStack', 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1 [ack: @aws-cdk/aws-autoscaling:iopsIgnored]');
12631263
});
12641264

12651265
test('step scaling on metric', () => {

packages/aws-cdk-lib/aws-autoscaling/test/scheduled-action.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ describeDeprecated('scheduled action', () => {
133133
});
134134

135135
// THEN
136-
Annotations.fromStack(stack).hasWarning('/Default/ASG/ScheduledActionScaleOutInTheMorning', "cron: If you don't pass 'minute', by default the event runs every minute. Pass 'minute: '*'' if that's what you intend, or 'minute: 0' to run once per hour instead.");
136+
Annotations.fromStack(stack).hasWarning('/Default/ASG/ScheduledActionScaleOutInTheMorning', "cron: If you don't pass 'minute', by default the event runs every minute. Pass 'minute: '*'' if that's what you intend, or 'minute: 0' to run once per hour instead. [ack: @aws-cdk/aws-autoscaling:scheduleDefaultRunsEveryMinute]");
137137
});
138138

139139
test('scheduled scaling shows no warning when minute is * in cron', () => {

packages/aws-cdk-lib/aws-cloudwatch/lib/alarm.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,8 @@ export class Alarm extends AlarmBase {
222222
value: props.threshold,
223223
};
224224

225-
for (const w of this.metric.warnings ?? []) {
226-
Annotations.of(this).addWarning(w);
225+
for (const [i, message] of Object.entries(this.metric.warningsV2 ?? {})) {
226+
Annotations.of(this).addWarningV2(i, message);
227227
}
228228
}
229229

packages/aws-cdk-lib/aws-cloudwatch/lib/dashboard.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,14 @@ export class Dashboard extends Resource {
179179
return;
180180
}
181181

182-
const warnings = allWidgetsDeep(widgets).flatMap(w => w.warnings ?? []);
183-
for (const w of warnings) {
184-
Annotations.of(this).addWarning(w);
182+
const warnings = allWidgetsDeep(widgets).reduce((prev, curr) => {
183+
return {
184+
...prev,
185+
...curr.warningsV2,
186+
};
187+
}, {} as { [id: string]: string });
188+
for (const [id, message] of Object.entries(warnings ?? {})) {
189+
Annotations.of(this).addWarningV2(id, message);
185190
}
186191

187192
const w = widgets.length > 1 ? new Row(...widgets) : widgets[0];

packages/aws-cdk-lib/aws-cloudwatch/lib/metric-types.ts

+10
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,19 @@ export interface IMetric {
1010
* Should be attached to the consuming construct.
1111
*
1212
* @default - None
13+
* @deprecated - use warningsV2
1314
*/
1415
readonly warnings?: string[];
1516

17+
/**
18+
* Any warnings related to this metric
19+
*
20+
* Should be attached to the consuming construct.
21+
*
22+
* @default - None
23+
*/
24+
readonly warningsV2?: { [id: string]: string };
25+
1626
/**
1727
* Inspect the details of the metric object
1828
*/

0 commit comments

Comments
 (0)