From 5ce3b709d15d42f302d6f5405daecd5059115b61 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Tue, 27 Jun 2023 17:54:02 +0000 Subject: [PATCH 01/26] feat(core): acknowledge annotation warning messages This PR adds the ability to acknowledge annotation warning messages. I've updated some of the annotations in the `rds.Cluster` construct to show how this will work. After this is merged we can work on adding the ids to all of the warning messages throughout the construct library. 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. --- packages/aws-cdk-lib/aws-rds/lib/cluster.ts | 10 +- .../aws-cdk-lib/aws-rds/test/cluster.test.ts | 91 ++++++++++++++++++- .../lib/cloud-assembly/metadata-schema.ts | 57 +++++++++++- packages/aws-cdk-lib/core/lib/annotations.ts | 44 ++++++++- .../aws-cdk-lib/core/test/annotations.test.ts | 69 ++++++++++++++ packages/aws-cdk-lib/core/test/util.ts | 13 ++- .../aws-cdk-lib/cx-api/lib/cloud-artifact.ts | 34 ++++++- 7 files changed, 301 insertions(+), 17 deletions(-) diff --git a/packages/aws-cdk-lib/aws-rds/lib/cluster.ts b/packages/aws-cdk-lib/aws-rds/lib/cluster.ts index 1f7acddde80ae..8e65228eab9db 100644 --- a/packages/aws-cdk-lib/aws-rds/lib/cluster.ts +++ b/packages/aws-cdk-lib/aws-rds/lib/cluster.ts @@ -706,23 +706,27 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase { const hasOnlyServerlessReaders = hasServerlessReader && !hasProvisionedReader; if (hasOnlyServerlessReaders) { if (noFailoverTierInstances) { - Annotations.of(this).addWarning( + Annotations.of(this).addWarningV2( + 'RDSNoFailoverServerlessReaders', `Cluster ${this.node.id} only has serverless readers and no reader is in promotion tier 0-1.`+ 'Serverless readers in promotion tiers >= 2 will NOT scale with the writer, which can lead to '+ 'availability issues if a failover event occurs. It is recommended that at least one reader '+ 'has `scaleWithWriter` set to true', ); + } } else { if (serverlessInHighestTier && highestTier > 1) { - Annotations.of(this).addWarning( + Annotations.of(this).addWarningV2( + 'RDSServerlessInHighestTier2-15', `There are serverlessV2 readers in tier ${highestTier}. Since there are no instances in a higher tier, `+ 'any instance in this tier is a failover target. Since this tier is > 1 the serverless reader will not scale '+ 'with the writer which could lead to availability issues during failover.', ); } if (someProvisionedReadersDontMatchWriter.length > 0 && writer.type === InstanceType.PROVISIONED) { - Annotations.of(this).addWarning( + Annotations.of(this).addWarningV2( + 'RDSProvisionedReadersDontMatchWriter', `There are provisioned readers in the highest promotion tier ${highestTier} that do not have the same `+ 'InstanceSize as the writer. Any of these instances could be chosen as the new writer in the event '+ 'of a failover.\n'+ diff --git a/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts b/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts index 298dfec3352e0..39d1bf095b1fb 100644 --- a/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts +++ b/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts @@ -5,7 +5,7 @@ import * as kms from '../../aws-kms'; import * as logs from '../../aws-logs'; import * as s3 from '../../aws-s3'; import * as cdk from '../../core'; -import { RemovalPolicy, Stack } from '../../core'; +import { RemovalPolicy, Stack, Annotations as CoreAnnotations } from '../../core'; import { AuroraEngineVersion, AuroraMysqlEngineVersion, AuroraPostgresEngineVersion, CfnDBCluster, Credentials, DatabaseCluster, DatabaseClusterEngine, DatabaseClusterFromSnapshot, ParameterGroup, PerformanceInsightRetention, SubnetGroup, DatabaseSecret, @@ -428,6 +428,89 @@ describe('cluster new api', () => { }); Annotations.fromStack(stack).hasWarning('*', + `RDSNoFailoverServerlessReaders: Cluster ${cluster.node.id} only has serverless readers and no reader is in promotion tier 0-1.`+ + 'Serverless readers in promotion tiers >= 2 will NOT scale with the writer, which can lead to '+ + 'availability issues if a failover event occurs. It is recommended that at least one reader '+ + 'has `scaleWithWriter` set to true', + ); + }); + + test('serverless reader in promotion tier 2 does not throws', () => { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + const cluster = new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA, + vpc, + writer: ClusterInstance.provisioned('writer'), + readers: [ClusterInstance.serverlessV2('reader')], + iamAuthentication: true, + }); + + CoreAnnotations.of(stack).acknowledgeWarning('RDSNoFailoverServerlessReaders'); + // THEN + const template = Template.fromStack(stack); + template.resourceCountIs('AWS::RDS::DBInstance', 2); + template.hasResourceProperties('AWS::RDS::DBInstance', { + DBClusterIdentifier: { Ref: 'DatabaseB269D8BB' }, + DBInstanceClass: 'db.t3.medium', + PromotionTier: 0, + }); + + template.hasResourceProperties('AWS::RDS::DBInstance', { + DBClusterIdentifier: { Ref: 'DatabaseB269D8BB' }, + DBInstanceClass: 'db.serverless', + PromotionTier: 2, + }); + + Annotations.fromStack(stack).hasNoWarning('*', + `Cluster ${cluster.node.id} only has serverless readers and no reader is in promotion tier 0-1.`+ + 'Serverless readers in promotion tiers >= 2 will NOT scale with the writer, which can lead to '+ + 'availability issues if a failover event occurs. It is recommended that at least one reader '+ + 'has `scaleWithWriter` set to true', + ); + }); + + test('serverless reader in promotion tier 2 does not throws with root context', () => { + // GIVEN + const app = new cdk.App({ + context: { + ACKNOWLEDGEMENTS_CONTEXT_KEY: { + RDSNoFailoverServerlessReaders: ['Default/Database'], + + }, + }, + }); + const stack = testStack(app); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + const cluster = new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA, + vpc, + writer: ClusterInstance.provisioned('writer'), + readers: [ClusterInstance.serverlessV2('reader')], + iamAuthentication: true, + }); + + // THEN + const template = Template.fromStack(stack); + template.resourceCountIs('AWS::RDS::DBInstance', 2); + template.hasResourceProperties('AWS::RDS::DBInstance', { + DBClusterIdentifier: { Ref: 'DatabaseB269D8BB' }, + DBInstanceClass: 'db.t3.medium', + PromotionTier: 0, + }); + + template.hasResourceProperties('AWS::RDS::DBInstance', { + DBClusterIdentifier: { Ref: 'DatabaseB269D8BB' }, + DBInstanceClass: 'db.serverless', + PromotionTier: 2, + }); + + Annotations.fromStack(stack).hasNoWarning('*', `Cluster ${cluster.node.id} only has serverless readers and no reader is in promotion tier 0-1.`+ 'Serverless readers in promotion tiers >= 2 will NOT scale with the writer, which can lead to '+ 'availability issues if a failover event occurs. It is recommended that at least one reader '+ @@ -591,7 +674,7 @@ describe('cluster new api', () => { }); Annotations.fromStack(stack).hasWarning('*', - 'There are provisioned readers in the highest promotion tier 2 that do not have the same '+ + 'RDSProvisionedReadersDontMatchWriter: There are provisioned readers in the highest promotion tier 2 that do not have the same '+ 'InstanceSize as the writer. Any of these instances could be chosen as the new writer in the event '+ 'of a failover.\n'+ 'Writer InstanceSize: m5.24xlarge\n'+ @@ -727,13 +810,13 @@ describe('cluster new api', () => { }); Annotations.fromStack(stack).hasWarning('*', - 'There are serverlessV2 readers in tier 2. Since there are no instances in a higher tier, '+ + 'RDSServerlessInHighestTier2-15: There are serverlessV2 readers in tier 2. Since there are no instances in a higher tier, '+ 'any instance in this tier is a failover target. Since this tier is > 1 the serverless reader will not scale '+ 'with the writer which could lead to availability issues during failover.', ); Annotations.fromStack(stack).hasWarning('*', - 'There are provisioned readers in the highest promotion tier 2 that do not have the same '+ + 'RDSProvisionedReadersDontMatchWriter: There are provisioned readers in the highest promotion tier 2 that do not have the same '+ 'InstanceSize as the writer. Any of these instances could be chosen as the new writer in the event '+ 'of a failover.\n'+ 'Writer InstanceSize: m5.24xlarge\n'+ diff --git a/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts b/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts index 569ef31d1e8c9..a16c7425403fb 100644 --- a/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts +++ b/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts @@ -221,6 +221,30 @@ export type AssetMetadataEntry = FileAssetMetadataEntry | ContainerImageAssetMet */ export type LogMessageMetadataEntry = string; +/** + * Newer log message metadata entries contain more info + * + * @see ArtifactMetadataEntryType.INFO + * @see ArtifactMetadataEntryType.WARN + * @see ArtifactMetadataEntryType.ERROR + */ +export type LogMessageObjectMetadataEntry = { + /** + * The message id + */ + id: string; + + /** + * The scope the message applies to + */ + scope: string; + + /** + * The log message + */ + message: string; +}; + /** * @see ArtifactMetadataEntryType.LOGICAL_ID */ @@ -231,10 +255,36 @@ export type LogicalIdMetadataEntry = string; */ export type StackTagsMetadataEntry = Tag[]; +/** + * @see ArtifactMetadataEntryType.ACKNOWLEDGE + */ +export type AcknowledgementMetadataEntry = { + /** + * The message id that is acknowledged + */ + id: string; + + /** + * The list of scopes that this acknowledgement should apply to + */ + scopes: string[]; + + /** + * The acknowledgement message + */ + message?: string; +}; + /** * Union type for all metadata entries that might exist in the manifest. */ -export type MetadataEntryData = AssetMetadataEntry | LogMessageMetadataEntry | LogicalIdMetadataEntry | StackTagsMetadataEntry; +export type MetadataEntryData = + AssetMetadataEntry | + LogMessageMetadataEntry | + LogicalIdMetadataEntry | + StackTagsMetadataEntry | + LogMessageObjectMetadataEntry | + AcknowledgementMetadataEntry; /** * Type of artifact metadata entry. @@ -260,6 +310,11 @@ export enum ArtifactMetadataEntryType { */ ERROR = 'aws:cdk:error', + /** + * Metadata key used to suppress WARNING-level messages + */ + ACKNOWLEDGE = 'aws:cdk:acknowledge', + /** * Represents the CloudFormation logical ID of a resource at a certain path. */ diff --git a/packages/aws-cdk-lib/core/lib/annotations.ts b/packages/aws-cdk-lib/core/lib/annotations.ts index 45ce2f1b8fd06..e55ff314b8907 100644 --- a/packages/aws-cdk-lib/core/lib/annotations.ts +++ b/packages/aws-cdk-lib/core/lib/annotations.ts @@ -24,6 +24,45 @@ export class Annotations { this.stackTraces = !disableTrace; } + /** + * Acknowledge a warning. When a warning is acknowledged for a scope + * all warnings that match the id will be ignored. + * + * The acknowledgement will apply to all child scopes + * + * @example + * declare const stack: Stack; + * Annotations.of(stack).acknowledgeWarning('SomeWarningId'); + * + * @param id - the id of the warning message to acknowledge + */ + public acknowledgeWarning(id: string, message?: string): void { + const scopes = this.scope.node.findAll().map(child => child.node.path); + this.addMessage(cxschema.ArtifactMetadataEntryType.ACKNOWLEDGE, { + id, + message, + scopes, + }); + } + + /** + * Adds a warning metadata entry to this construct. + * + * The CLI will display the warning when an app is synthesized, or fail if run + * in --strict mode. + * + * @param id the unique identifier for the warning. This can be used to acknowledge the warning + * @param message The warning message. + */ + public addWarningV2(id: string, message: string) { + const warning = { + id: id, + scope: this.scope.node.path, + message, + }; + this.addMessage(cxschema.ArtifactMetadataEntryType.WARN, warning); + } + /** * Adds a warning metadata entry to this construct. * @@ -88,7 +127,10 @@ export class Annotations { * @param level The message level * @param message The message itself */ - private addMessage(level: string, message: string) { + private addMessage( + level: string, + message: cxschema.LogMessageMetadataEntry | cxschema.LogMessageObjectMetadataEntry | cxschema.AcknowledgementMetadataEntry, + ) { const isNew = !this.scope.node.metadata.find((x) => x.data === message); if (isNew) { this.scope.node.addMetadata(level, message, { stackTrace: this.stackTraces }); diff --git a/packages/aws-cdk-lib/core/test/annotations.test.ts b/packages/aws-cdk-lib/core/test/annotations.test.ts index fc5c7430d22a8..71ccfa987b30b 100644 --- a/packages/aws-cdk-lib/core/test/annotations.test.ts +++ b/packages/aws-cdk-lib/core/test/annotations.test.ts @@ -93,4 +93,73 @@ describe('annotations', () => { }], ); }); + + test('acknowledgeWarning removes warning', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'S1'); + const c1 = new Construct(stack, 'C1'); + + // WHEN + Annotations.of(c1).addWarningV2('MESSAGE1', 'You should know this!'); + Annotations.of(c1).addWarningV2('MESSAGE2', 'You Should know this too!'); + Annotations.of(c1).acknowledgeWarning('MESSAGE2', 'I Ack this'); + + // THEN + const assembly = app.synth(); + let acknoledgement: any = {}; + for (const s of Object.values(assembly.manifest.artifacts ?? {})) { + for (const [_path, md] of Object.entries(s.metadata ?? {})) { + for (const x of md) { + if (x.type === 'aws:cdk:acknowledge') { + acknoledgement.message = x.data as string; + } + } + } + } + expect(acknoledgement).toEqual({ + message: { + id: 'MESSAGE2', + scopes: ['S1/C1'], + message: 'I Ack this', + }, + }); + expect(getWarnings(assembly)).toEqual([{ + path: '/S1/C1', + message: 'MESSAGE1: You should know this!', + }]); + }); + + test('acknowledgeWarning removes warning on children', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'S1'); + const c1 = new Construct(stack, 'C1'); + const c2 = new Construct(c1, 'C2'); + + // WHEN + Annotations.of(c2).addWarningV2('MESSAGE2', 'You Should know this too!'); + Annotations.of(c1).acknowledgeWarning('MESSAGE2', 'I Ack this'); + + // THEN + const assembly = app.synth(); + let acknoledgement: any = {}; + for (const s of Object.values(assembly.manifest.artifacts ?? {})) { + for (const [_path, md] of Object.entries(s.metadata ?? {})) { + for (const x of md) { + if (x.type === 'aws:cdk:acknowledge') { + acknoledgement.message = x.data as string; + } + } + } + } + expect(acknoledgement).toEqual({ + message: { + id: 'MESSAGE2', + scopes: ['S1/C1', 'S1/C1/C2'], + message: 'I Ack this', + }, + }); + expect(getWarnings(assembly)).toEqual([]); + }); }); diff --git a/packages/aws-cdk-lib/core/test/util.ts b/packages/aws-cdk-lib/core/test/util.ts index 6c5c2eba6dbad..594acc55a5134 100644 --- a/packages/aws-cdk-lib/core/test/util.ts +++ b/packages/aws-cdk-lib/core/test/util.ts @@ -1,4 +1,4 @@ -import { CloudAssembly } from '../../cx-api'; +import { CloudArtifact, CloudAssembly, SynthesisMessageLevel } from '../../cx-api'; import { Stack } from '../lib'; import { CDK_DEBUG } from '../lib/debug'; import { synthesize } from '../lib/private/synthesis'; @@ -35,13 +35,12 @@ export function restoreStackTraceColection(previousValue: string | undefined): v export function getWarnings(casm: CloudAssembly) { const result = new Array<{ path: string, message: string }>(); for (const stack of Object.values(casm.manifest.artifacts ?? {})) { - for (const [path, md] of Object.entries(stack.metadata ?? {})) { - for (const x of md) { - if (x.type === 'aws:cdk:warning') { - result.push({ path, message: x.data as string }); - } + const artifact = CloudArtifact.fromManifest(casm, 'art', stack); + artifact?.messages.forEach(message => { + if (message.level === SynthesisMessageLevel.WARNING) { + result.push({ path: message.id, message: message.entry.data as string }); } - } + }); } return result; } diff --git a/packages/aws-cdk-lib/cx-api/lib/cloud-artifact.ts b/packages/aws-cdk-lib/cx-api/lib/cloud-artifact.ts index 01fefe31934fc..d052be16be58f 100644 --- a/packages/aws-cdk-lib/cx-api/lib/cloud-artifact.ts +++ b/packages/aws-cdk-lib/cx-api/lib/cloud-artifact.ts @@ -1,6 +1,7 @@ import type { CloudAssembly } from './cloud-assembly'; import { MetadataEntryResult, SynthesisMessage, SynthesisMessageLevel } from './metadata'; import * as cxschema from '../../cloud-assembly-schema'; +import type { LogMessageObjectMetadataEntry } from '../../cloud-assembly-schema/lib/cloud-assembly/metadata-schema'; /** * Artifact properties for CloudFormation stacks. @@ -110,12 +111,36 @@ export class CloudArtifact { private renderMessages() { const messages = new Array(); + // messageId: scopes[] + const acks = new Map(); + // collect any acknowledgements + for (const [_id, metadata] of Object.entries(this.manifest.metadata || { })) { + for (const entry of metadata) { + if (entry.type === cxschema.ArtifactMetadataEntryType.ACKNOWLEDGE) { + const data = (entry.data as cxschema.AcknowledgementMetadataEntry); + acks.set(data.id, data.scopes); + } + } + } for (const [id, metadata] of Object.entries(this.manifest.metadata || { })) { for (const entry of metadata) { let level: SynthesisMessageLevel; + let entryData: string | undefined = undefined; switch (entry.type) { case cxschema.ArtifactMetadataEntryType.WARN: + if (typeof entry.data === 'object' && 'id' in entry.data) { + const data = entry.data as LogMessageObjectMetadataEntry; + const ack = acks.get(data.id); + // if the scope has been acknowledged then don't add the warning + if (ack && ack.includes(data.scope)) { + continue; + } else { + entryData = `${data.id}: ${data.message}`; + level = SynthesisMessageLevel.WARNING; + break; + } + } level = SynthesisMessageLevel.WARNING; break; case cxschema.ArtifactMetadataEntryType.ERROR: @@ -128,7 +153,14 @@ export class CloudArtifact { continue; } - messages.push({ level, entry, id }); + messages.push({ + level, + entry: { + ...entry, + data: entryData ?? entry.data, + }, + id, + }); } } From fca3a1ac27da4dfd0d570bd671cd9439e9655d01 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Wed, 28 Jun 2023 11:42:45 +0000 Subject: [PATCH 02/26] adding deprecation --- packages/aws-cdk-lib/core/lib/annotations.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/aws-cdk-lib/core/lib/annotations.ts b/packages/aws-cdk-lib/core/lib/annotations.ts index e55ff314b8907..80bd61f7f8329 100644 --- a/packages/aws-cdk-lib/core/lib/annotations.ts +++ b/packages/aws-cdk-lib/core/lib/annotations.ts @@ -70,6 +70,7 @@ export class Annotations { * in --strict mode. * * @param message The warning message. + * @deprecated - use addWarningV2 instead */ public addWarning(message: string) { this.addMessage(cxschema.ArtifactMetadataEntryType.WARN, message); From 707be7f22b959624fbbc6e40255a7b09420f0bfb Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:26:19 +0000 Subject: [PATCH 03/26] updating all references from `addWarning` to `addWarningV2` --- .../aws-gamelift-alpha/lib/fleet-base.ts | 4 +- .../lib/aspects/stack-associator.ts | 14 +++---- .../assertions/test/annotations.test.ts | 16 ++++---- .../lib/schedule.ts | 2 +- .../lib/aspects/require-imdsv2-aspect.ts | 2 +- .../aws-autoscaling/lib/auto-scaling-group.ts | 4 +- .../aws-autoscaling/lib/schedule.ts | 2 +- .../test/auto-scaling-group.test.ts | 4 +- .../test/scheduled-action.test.ts | 2 +- .../aws-cdk-lib/aws-cloudwatch/lib/alarm.ts | 4 +- .../aws-cloudwatch/lib/dashboard.ts | 11 ++++-- .../aws-cloudwatch/lib/metric-types.ts | 10 +++++ .../aws-cdk-lib/aws-cloudwatch/lib/metric.ts | 39 +++++++++++++------ .../aws-cdk-lib/aws-cloudwatch/lib/widget.ts | 14 ++++++- .../aws-cloudwatch/test/metric-math.test.ts | 14 ++++--- .../aws-dynamodb/test/dynamodb.test.ts | 2 +- .../lib/aspects/require-imdsv2-aspect.ts | 2 +- .../aws-ec2/lib/private/ebs-util.ts | 2 +- .../aws-cdk-lib/aws-ec2/lib/security-group.ts | 4 +- packages/aws-cdk-lib/aws-ec2/lib/vpc.ts | 6 +-- .../aws-cdk-lib/aws-ec2/test/instance.test.ts | 4 +- packages/aws-cdk-lib/aws-ec2/test/vpc.test.ts | 2 +- .../aws-ecr-assets/lib/image-asset.ts | 2 +- .../aws-cdk-lib/aws-ecr/lib/repository.ts | 2 +- .../aws-ecr/test/repository.test.ts | 4 +- .../test/ec2/scheduled-ecs-task.test.ts | 2 +- .../fargate/scheduled-fargate-task.test.ts | 2 +- .../aws-ecs/lib/base/base-service.ts | 2 +- .../aws-ecs/lib/images/repository.ts | 2 +- .../aws-ecs/test/ec2/ec2-service.test.ts | 4 +- .../test/ec2/ec2-task-definition.test.ts | 4 +- .../test/external/external-service.test.ts | 2 +- .../external/external-task-definition.test.ts | 2 +- .../test/fargate/fargate-service.test.ts | 6 +-- packages/aws-cdk-lib/aws-eks/lib/cluster.ts | 6 +-- .../aws-eks/lib/fargate-profile.ts | 2 +- .../aws-eks/lib/managed-nodegroup.ts | 2 +- .../aws-cdk-lib/aws-eks/test/cluster.test.ts | 3 +- .../lib/alb/application-listener-rule.ts | 2 +- .../lib/alb/application-target-group.ts | 4 +- .../lib/shared/base-listener.ts | 2 +- .../lib/shared/base-target-group.ts | 2 +- .../aws-events-targets/lib/aws-api.ts | 2 +- .../aws-events-targets/lib/ecs-task.ts | 2 +- .../aws-events-targets/lib/util.ts | 2 +- .../test/aws-api/aws-api.test.ts | 2 +- .../test/lambda/lambda.test.ts | 8 +--- packages/aws-cdk-lib/aws-events/lib/rule.ts | 2 +- .../aws-cdk-lib/aws-events/lib/schedule.ts | 2 +- .../aws-cdk-lib/aws-events/test/rule.test.ts | 2 +- packages/aws-cdk-lib/aws-iam/lib/group.ts | 2 +- .../aws-iam/lib/private/imported-role.ts | 2 +- packages/aws-cdk-lib/aws-iam/lib/role.ts | 6 +-- .../aws-iam/lib/unknown-principal.ts | 4 +- .../aws-cdk-lib/aws-iam/test/group.test.ts | 4 +- .../aws-lambda-event-sources/lib/sqs.ts | 2 +- .../aws-lambda/lib/function-base.ts | 2 +- .../aws-cdk-lib/aws-lambda/test/alias.test.ts | 2 +- packages/aws-cdk-lib/aws-rds/lib/cluster.ts | 12 +++--- .../aws-cdk-lib/aws-rds/test/cluster.test.ts | 9 +++-- .../aws-s3-notifications/lib/sqs.ts | 2 +- .../aws-s3-notifications/test/queue.test.ts | 2 +- packages/aws-cdk-lib/aws-s3/lib/bucket.ts | 2 +- .../lib/private/product-stack-synthesizer.ts | 2 +- .../aws-cdk-lib/aws-ses-actions/lib/lambda.ts | 2 +- .../aws-cdk-lib/aws-ses-actions/lib/s3.ts | 2 +- packages/aws-cdk-lib/core/lib/annotations.ts | 17 +++++++- packages/aws-cdk-lib/core/lib/cfn-resource.ts | 2 +- .../aws-cdk-lib/core/lib/private/synthesis.ts | 2 +- .../core/lib/private/tree-metadata.ts | 2 +- packages/aws-cdk-lib/core/lib/stack.ts | 2 +- .../aws-cdk-lib/core/test/annotations.test.ts | 20 +++++----- packages/aws-cdk-lib/core/test/app.test.ts | 8 ++-- packages/aws-cdk-lib/core/test/aspect.test.ts | 2 +- .../core/test/cfn-resource.test.ts | 2 +- .../aws-cdk-lib/core/test/construct.test.ts | 4 +- .../core/test/private/tree-metadata.test.ts | 4 +- .../aws-custom-resource.ts | 2 +- .../pipelines/lib/legacy/pipeline.ts | 2 +- 79 files changed, 213 insertions(+), 156 deletions(-) diff --git a/packages/@aws-cdk/aws-gamelift-alpha/lib/fleet-base.ts b/packages/@aws-cdk/aws-gamelift-alpha/lib/fleet-base.ts index b97b3fe26121c..b7997b1fcd02e 100644 --- a/packages/@aws-cdk/aws-gamelift-alpha/lib/fleet-base.ts +++ b/packages/@aws-cdk/aws-gamelift-alpha/lib/fleet-base.ts @@ -639,10 +639,10 @@ export abstract class FleetBase extends cdk.Resource implements IFleet { } protected warnVpcPeeringAuthorizations(scope: Construct): void { - cdk.Annotations.of(scope).addWarning([ + cdk.Annotations.of(scope).addWarningV2('Gamelift:Fleet:AutorizeVpcPeering', [ 'To authorize the VPC peering, call the GameLift service API CreateVpcPeeringAuthorization() or use the AWS CLI command create-vpc-peering-authorization.', 'Make this call using the account that manages your non-GameLift resources.', 'See: https://docs.aws.amazon.com/gamelift/latest/developerguide/vpc-peering.html', ].join('\n')); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/lib/aspects/stack-associator.ts b/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/lib/aspects/stack-associator.ts index 79a608a44e176..fb7a3a0cfba52 100644 --- a/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/lib/aspects/stack-associator.ts +++ b/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/lib/aspects/stack-associator.ts @@ -38,7 +38,7 @@ abstract class StackAssociatorBase implements IAspect { if (Stage.isStage(childNode)) { var stageAssociated = this.applicationAssociator?.isStageAssociated(childNode); if (stageAssociated === false) { - this.warning(childNode, 'Associate Stage: ' + childNode.stageName + ' to ensure all stacks in your cdk app are associated with AppRegistry. ' + this.warning('StackNotAssociated', childNode, 'Associate Stage: ' + childNode.stageName + ' to ensure all stacks in your cdk app are associated with AppRegistry. ' + 'You can use ApplicationAssociator.associateStage to associate any stage.'); } } @@ -73,8 +73,8 @@ abstract class StackAssociatorBase implements IAspect { * @param node The scope to add the warning to. * @param message The error message. */ - private warning(node: IConstruct, message: string): void { - Annotations.of(node).addWarning(message); + private warning(id: string, node: IConstruct, message: string): void { + Annotations.of(node).addWarningV2(`SCAppRegistry:${id}`, message); } /** @@ -87,12 +87,12 @@ abstract class StackAssociatorBase implements IAspect { */ private handleCrossRegionStack(node: Stack): void { if (isRegionUnresolved(this.application.env.region, node.region)) { - this.warning(node, 'Environment agnostic stack determined, AppRegistry association might not work as expected in case you deploy cross-region or cross-account stack.'); + 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.'); return; } if (node.region != this.application.env.region) { - this.warning(node, 'AppRegistry does not support cross region associations, deployment might fail if there is cross region stacks in the app. Application region ' + 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 ' + this.application.env.region + ', stack region ' + node.region); } } @@ -106,7 +106,7 @@ abstract class StackAssociatorBase implements IAspect { */ private handleCrossAccountStack(node: Stack): void { if (isAccountUnresolved(this.application.env.account!, node.account)) { - this.warning(node, 'Environment agnostic stack determined, AppRegistry association might not work as expected in case you deploy cross-region or cross-account stack.'); + 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.'); return; } @@ -121,7 +121,7 @@ abstract class StackAssociatorBase implements IAspect { this.sharedAccounts.add(node.account); } else { - this.warning(node, 'Cross-account stack detected but application sharing and association will be skipped because cross-account option is not enabled.'); + this.warning('AssociationSkipped', node, 'Cross-account stack detected but application sharing and association will be skipped because cross-account option is not enabled.'); return; } } diff --git a/packages/aws-cdk-lib/assertions/test/annotations.test.ts b/packages/aws-cdk-lib/assertions/test/annotations.test.ts index 7d497cd73973a..5d0db8ac2bef2 100644 --- a/packages/aws-cdk-lib/assertions/test/annotations.test.ts +++ b/packages/aws-cdk-lib/assertions/test/annotations.test.ts @@ -75,7 +75,7 @@ describe('Messages', () => { describe('hasWarning', () => { test('match', () => { - annotations.hasWarning('/Default/Fred', 'this is a warning'); + annotations.hasWarning('/Default/Fred', 'Fred: this is a warning'); }); test('no match', () => { @@ -89,7 +89,7 @@ describe('Messages', () => { }); test('no match', () => { - expect(() => annotations.hasNoWarning('/Default/Fred', 'this is a warning')) + expect(() => annotations.hasNoWarning('/Default/Fred', 'Fred: this is a warning')) .toThrowError(/Expected no matches, but stack has 1 messages as follows:/); }); }); @@ -183,7 +183,7 @@ describe('Multiple Messages on the Resource', () => { test('succeeds on hasXxx APIs', () => { annotations.hasError('/Default/Foo', 'error: this is an error'); annotations.hasError('/Default/Foo', 'error: unsupported type Foo::Bar'); - annotations.hasWarning('/Default/Foo', 'warning: Foo::Bar is deprecated'); + annotations.hasWarning('/Default/Foo', 'Foo: warning: Foo::Bar is deprecated'); }); test('succeeds on findXxx APIs', () => { @@ -191,8 +191,8 @@ describe('Multiple Messages on the Resource', () => { expect(result1.length).toEqual(4); const result2 = annotations.findError('/Default/Bar', Match.stringLikeRegexp('error:.*')); expect(result2.length).toEqual(2); - const result3 = annotations.findWarning('/Default/Bar', 'warning: Foo::Bar is deprecated'); - expect(result3[0].entry.data).toEqual('warning: Foo::Bar is deprecated'); + const result3 = annotations.findWarning('/Default/Bar', 'Bar: warning: Foo::Bar is deprecated'); + expect(result3[0].entry.data).toEqual('Bar: warning: Foo::Bar is deprecated'); }); }); class MyAspect implements IAspect { @@ -209,7 +209,7 @@ class MyAspect implements IAspect { }; protected warn(node: IConstruct, message: string): void { - Annotations.of(node).addWarning(message); + Annotations.of(node).addWarningV2(node.node.id, message); } protected error(node: IConstruct, message: string): void { @@ -231,10 +231,10 @@ class MultipleAspectsPerNode implements IAspect { } protected warn(node: IConstruct, message: string): void { - Annotations.of(node).addWarning(message); + Annotations.of(node).addWarningV2(node.node.id, message); } protected error(node: IConstruct, message: string): void { Annotations.of(node).addError(message); } -} \ No newline at end of file +} diff --git a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts index fb448d42bc732..dabbfafb584ee 100644 --- a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts +++ b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts @@ -63,7 +63,7 @@ export abstract class Schedule { public readonly expressionString: string = `cron(${minute} ${hour} ${day} ${month} ${weekDay} ${year})`; public _bind(scope: Construct) { if (!options.minute) { - 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.'); + Annotations.of(scope).addWarningV2('AppAutoScaling:Schedule: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.'); } return new LiteralSchedule(this.expressionString); } diff --git a/packages/aws-cdk-lib/aws-autoscaling/lib/aspects/require-imdsv2-aspect.ts b/packages/aws-cdk-lib/aws-autoscaling/lib/aspects/require-imdsv2-aspect.ts index 158ccf9b5b094..8fd47750c3518 100644 --- a/packages/aws-cdk-lib/aws-autoscaling/lib/aspects/require-imdsv2-aspect.ts +++ b/packages/aws-cdk-lib/aws-autoscaling/lib/aspects/require-imdsv2-aspect.ts @@ -34,6 +34,6 @@ export class AutoScalingGroupRequireImdsv2Aspect implements cdk.IAspect { * @param message The warning message. */ protected warn(node: IConstruct, message: string) { - cdk.Annotations.of(node).addWarning(`${AutoScalingGroupRequireImdsv2Aspect.name} failed on node ${node.node.id}: ${message}`); + cdk.Annotations.of(node).addWarningV2(`AutoScaling:Imdsv2:${AutoScalingGroupRequireImdsv2Aspect.name}`, `${AutoScalingGroupRequireImdsv2Aspect.name} failed on node ${node.node.id}: ${message}`); } } diff --git a/packages/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.ts b/packages/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.ts index a44ebd0d6ff16..01d612dc9653b 100644 --- a/packages/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.ts @@ -1351,7 +1351,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements }); if (desiredCapacity !== undefined) { - 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'); + Annotations.of(this).addWarningV2('AutoScaling:Group: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'); } this.maxInstanceLifetime = props.maxInstanceLifetime; @@ -2264,7 +2264,7 @@ function synthesizeBlockDeviceMappings(construct: Construct, blockDevices: Block throw new Error('iops property is required with volumeType: EbsDeviceVolumeType.IO1'); } } else if (volumeType !== EbsDeviceVolumeType.IO1) { - Annotations.of(construct).addWarning('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + Annotations.of(construct).addWarningV2('AutoScaling:Group:IopsIgnored', 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); } } diff --git a/packages/aws-cdk-lib/aws-autoscaling/lib/schedule.ts b/packages/aws-cdk-lib/aws-autoscaling/lib/schedule.ts index e0eb783f43519..c63d1e5f993eb 100644 --- a/packages/aws-cdk-lib/aws-autoscaling/lib/schedule.ts +++ b/packages/aws-cdk-lib/aws-autoscaling/lib/schedule.ts @@ -33,7 +33,7 @@ export abstract class Schedule { public readonly expressionString: string = `${minute} ${hour} ${day} ${month} ${weekDay}`; public _bind(scope: Construct) { if (!options.minute) { - 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.'); + Annotations.of(scope).addWarningV2('AutoScaling:Schedule:DefaultRunsEveryMinute', '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.'); } return new LiteralSchedule(this.expressionString); } diff --git a/packages/aws-cdk-lib/aws-autoscaling/test/auto-scaling-group.test.ts b/packages/aws-cdk-lib/aws-autoscaling/test/auto-scaling-group.test.ts index 9f7a0e0598b73..c750d89656075 100644 --- a/packages/aws-cdk-lib/aws-autoscaling/test/auto-scaling-group.test.ts +++ b/packages/aws-cdk-lib/aws-autoscaling/test/auto-scaling-group.test.ts @@ -1007,7 +1007,7 @@ describe('auto scaling group', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/MyStack', 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + Annotations.fromStack(stack).hasWarning('/Default/MyStack', 'AutoScaling:Group:IopsIgnored: iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); }); test('warning if iops and volumeType !== IO1', () => { @@ -1031,7 +1031,7 @@ describe('auto scaling group', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/MyStack', 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + Annotations.fromStack(stack).hasWarning('/Default/MyStack', 'AutoScaling:Group:IopsIgnored: iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); }); test('step scaling on metric', () => { diff --git a/packages/aws-cdk-lib/aws-autoscaling/test/scheduled-action.test.ts b/packages/aws-cdk-lib/aws-autoscaling/test/scheduled-action.test.ts index 10fbdfe7066b6..c94ce1c23eb32 100644 --- a/packages/aws-cdk-lib/aws-autoscaling/test/scheduled-action.test.ts +++ b/packages/aws-cdk-lib/aws-autoscaling/test/scheduled-action.test.ts @@ -133,7 +133,7 @@ describeDeprecated('scheduled action', () => { }); // THEN - 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."); + Annotations.fromStack(stack).hasWarning('/Default/ASG/ScheduledActionScaleOutInTheMorning', "AutoScaling:Schedule:DefaultRunsEveryMinute: 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."); }); test('scheduled scaling shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm.ts b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm.ts index 1e6740c06bee2..82c8993de7f69 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm.ts @@ -222,8 +222,8 @@ export class Alarm extends AlarmBase { value: props.threshold, }; - for (const w of this.metric.warnings ?? []) { - Annotations.of(this).addWarning(w); + for (const [i, message] of Object.entries(this.metric.warningsV2 ?? {})) { + Annotations.of(this).addWarningV2(i, message); } } diff --git a/packages/aws-cdk-lib/aws-cloudwatch/lib/dashboard.ts b/packages/aws-cdk-lib/aws-cloudwatch/lib/dashboard.ts index 919ddf643f88d..fab59f61143f7 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/lib/dashboard.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/lib/dashboard.ts @@ -163,9 +163,14 @@ export class Dashboard extends Resource { return; } - const warnings = allWidgetsDeep(widgets).flatMap(w => w.warnings ?? []); - for (const w of warnings) { - Annotations.of(this).addWarning(w); + const warnings = allWidgetsDeep(widgets).reduce((prev, curr) => { + return { + ...prev, + ...curr.warningsV2, + }; + }, {} as { [id: string]: string }); + for (const [id, message] of Object.entries(warnings ?? {})) { + Annotations.of(this).addWarningV2(id, message); } const w = widgets.length > 1 ? new Row(...widgets) : widgets[0]; diff --git a/packages/aws-cdk-lib/aws-cloudwatch/lib/metric-types.ts b/packages/aws-cdk-lib/aws-cloudwatch/lib/metric-types.ts index 0fac96a135c1e..7b02013c48a27 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/lib/metric-types.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/lib/metric-types.ts @@ -10,9 +10,19 @@ export interface IMetric { * Should be attached to the consuming construct. * * @default - None + * @deprecated - use warningsV2 */ readonly warnings?: string[]; + /** + * Any warnings related to this metric + * + * Should be attached to the consuming construct. + * + * @default - None + */ + readonly warningsV2?: { [id: string]: string }; + /** * Inspect the details of the metric object */ diff --git a/packages/aws-cdk-lib/aws-cloudwatch/lib/metric.ts b/packages/aws-cdk-lib/aws-cloudwatch/lib/metric.ts index cbb7580fb0b29..fc863f8f37870 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/lib/metric.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/lib/metric.ts @@ -283,9 +283,15 @@ export class Metric implements IMetric { /** Region which this metric comes from. */ public readonly region?: string; - /** Warnings attached to this metric. */ + /** + * Warnings attached to this metric. + * @deprecated - use warningsV2 + **/ public readonly warnings?: string[]; + /** Warnings attached to this metric. */ + public readonly warningsV2?: { [id: string]: string }; + constructor(props: MetricProps) { this.period = props.period || cdk.Duration.minutes(5); const periodSec = this.period.toSeconds(); @@ -303,11 +309,14 @@ export class Metric implements IMetric { // Unrecognized statistic, do not throw, just warn // There may be a new statistic that this lib does not support yet const label = props.label ? `, label "${props.label}"`: ''; - this.warnings = [ - `Unrecognized statistic "${props.statistic}" for metric with namespace "${props.namespace}"${label} and metric name "${props.metricName}".` + + + const warning = `Unrecognized statistic "${props.statistic}" for metric with namespace "${props.namespace}"${label} and metric name "${props.metricName}".` + ' Preferably use the `aws_cloudwatch.Stats` helper class to specify a statistic.' + - ' You can ignore this warning if your statistic is valid but not yet supported by the `aws_cloudwatch.Stats` helper class.', - ]; + ' You can ignore this warning if your statistic is valid but not yet supported by the `aws_cloudwatch.Stats` helper class.'; + this.warningsV2 = { + 'CloudWatch:Alarm:UnrecognizedStatistic': warning, + }; + this.warnings = [warning]; } this.statistic = normalizeStatistic(parsedStat); @@ -584,9 +593,15 @@ export class MathExpression implements IMetric { /** * Warnings generated by this math expression + * @deprecated - use warningsV2 */ public readonly warnings?: string[]; + /** + * Warnings generated by this math expression + */ + public readonly warningsV2?: { [id: string]: string }; + constructor(props: MathExpressionProps) { this.period = props.period || cdk.Duration.minutes(5); this.expression = props.expression; @@ -609,19 +624,21 @@ export class MathExpression implements IMetric { // we can add warnings. const missingIdentifiers = allIdentifiersInExpression(this.expression).filter(i => !this.usingMetrics[i]); - const warnings: string[] = []; - + const warnings: { [id: string]: string } = {}; if (!this.expression.toUpperCase().match('\\s*SELECT|SEARCH|METRICS\\s.*') && missingIdentifiers.length > 0) { - warnings.push(`Math expression '${this.expression}' references unknown identifiers: ${missingIdentifiers.join(', ')}. Please add them to the 'usingMetrics' map.`); + warnings['CloudWatch:Math:UnknownIdentifier'] = `Math expression '${this.expression}' references unknown identifiers: ${missingIdentifiers.join(', ')}. Please add them to the 'usingMetrics' map.`; } // Also copy warnings from deeper levels so graphs, alarms only have to inspect the top-level objects for (const m of Object.values(this.usingMetrics)) { - warnings.push(...m.warnings ?? []); + for (const [id, message] of Object.entries(m.warningsV2 ?? {})) { + warnings[id] = message; + } } - if (warnings.length > 0) { - this.warnings = warnings; + if (Object.keys(warnings).length > 0) { + this.warnings = Array.from(Object.values(warnings)); + this.warningsV2 = warnings; } } diff --git a/packages/aws-cdk-lib/aws-cloudwatch/lib/widget.ts b/packages/aws-cdk-lib/aws-cloudwatch/lib/widget.ts index a56d7c57d9e5a..f464c712da11f 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/lib/widget.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/lib/widget.ts @@ -21,9 +21,15 @@ export interface IWidget { /** * Any warnings that are produced as a result of putting together this widget + * @deprecated - use warningsV2 */ readonly warnings?: string[]; + /** + * Any warnings that are produced as a result of putting together this widget + */ + readonly warningsV2?: { [id: string]: string }; + /** * Place the widget at a given position */ @@ -47,6 +53,7 @@ export abstract class ConcreteWidget implements IWidget { protected y?: number; public readonly warnings: string[] | undefined = []; + public warningsV2: { [id: string]: string } | undefined = {}; constructor(width: number, height: number) { this.width = width; @@ -68,6 +75,11 @@ export abstract class ConcreteWidget implements IWidget { * Copy the warnings from the given metric */ protected copyMetricWarnings(...ms: IMetric[]) { - this.warnings?.push(...ms.flatMap(m => m.warnings ?? [])); + this.warningsV2 = ms.reduce((prev, curr) => { + return { + ...prev, + ...curr.warningsV2, + }; + }, this.warningsV2); } } diff --git a/packages/aws-cdk-lib/aws-cloudwatch/test/metric-math.test.ts b/packages/aws-cdk-lib/aws-cloudwatch/test/metric-math.test.ts index 0311f05d0b057..38d2bbd934a52 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/test/metric-math.test.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/test/metric-math.test.ts @@ -69,7 +69,9 @@ describe('Metric Math', () => { expression: 'm1 + m2', }); - expect(m.warnings).toContainEqual(expect.stringContaining("'m1 + m2' references unknown identifiers")); + expect(m.warningsV2).toMatchObject({ + 'CloudWatch:Math:UnknownIdentifier': expect.stringContaining("'m1 + m2' references unknown identifiers"), + }); }); test('metrics METRICS expression does not produce warning for unknown identifier', () => { @@ -78,7 +80,7 @@ describe('Metric Math', () => { usingMetrics: {}, }); - expect(m.warnings).toBeUndefined(); + expect(m.warningsV2).toBeUndefined(); }); test('metrics search expression does not produce warning for unknown identifier', () => { @@ -87,7 +89,7 @@ describe('Metric Math', () => { usingMetrics: {}, }); - expect(m.warnings).toBeUndefined(); + expect(m.warningsV2).toBeUndefined(); }); test('metrics insights expression does not produce warning for unknown identifier', () => { @@ -95,7 +97,7 @@ describe('Metric Math', () => { expression: "SELECT AVG(CpuUsage) FROM EC2 WHERE Instance = '123456'", }); - expect(m.warnings).toBeUndefined(); + expect(m.warningsV2).toBeUndefined(); }); test('math expression referring to unknown expressions produces a warning, even when nested', () => { @@ -108,7 +110,9 @@ describe('Metric Math', () => { }, }); - expect(m.warnings).toContainEqual(expect.stringContaining("'m1 + m2' references unknown identifiers")); + expect(m.warningsV2).toMatchObject({ + 'CloudWatch:Math:UnknownIdentifier': expect.stringContaining("'m1 + m2' references unknown identifiers"), + }); }); describe('in graphs', () => { diff --git a/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts b/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts index 1485b3bffbf68..c6ceca30dfad3 100644 --- a/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts +++ b/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts @@ -1540,7 +1540,7 @@ test('scheduled scaling shows warning when minute is not defined in cron', () => }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/MyTable/ReadScaling/Target', "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."); + Annotations.fromStack(stack).hasWarning('/Default/MyTable/ReadScaling/Target', "AppAutoScaling:Schedule: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."); }); test('scheduled scaling shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-ec2/lib/aspects/require-imdsv2-aspect.ts b/packages/aws-cdk-lib/aws-ec2/lib/aspects/require-imdsv2-aspect.ts index 9f73da2fd710e..118287cdd7eef 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/aspects/require-imdsv2-aspect.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/aspects/require-imdsv2-aspect.ts @@ -37,7 +37,7 @@ abstract class RequireImdsv2Aspect implements cdk.IAspect { */ protected warn(node: IConstruct, message: string) { if (this.suppressWarnings !== true) { - cdk.Annotations.of(node).addWarning(`${RequireImdsv2Aspect.name} failed on node ${node.node.id}: ${message}`); + cdk.Annotations.of(node).addWarningV2(`EC2:Imdsv2:${RequireImdsv2Aspect.name}`, `${RequireImdsv2Aspect.name} failed on node ${node.node.id}: ${message}`); } } } diff --git a/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts b/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts index 3152fdb26974a..5919e15aea863 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts @@ -32,7 +32,7 @@ function synthesizeBlockDeviceMappings(construct: Construct, blockDevic throw new Error('iops property is required with volumeType: EbsDeviceVolumeType.IO1 and EbsDeviceVolumeType.IO2'); } } else if (volumeType !== EbsDeviceVolumeType.IO1 && volumeType !== EbsDeviceVolumeType.IO2 && volumeType !== EbsDeviceVolumeType.GP3) { - Annotations.of(construct).addWarning('iops will be ignored without volumeType: IO1, IO2, or GP3'); + Annotations.of(construct).addWarningV2('EC2:EBS:IopsIgnored', 'iops will be ignored without volumeType: IO1, IO2, or GP3'); } /** diff --git a/packages/aws-cdk-lib/aws-ec2/lib/security-group.ts b/packages/aws-cdk-lib/aws-ec2/lib/security-group.ts index ea610637c1c2c..be8659fe5c675 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/security-group.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/security-group.ts @@ -553,7 +553,7 @@ export class SecurityGroup extends SecurityGroupBase { // is only one rule which allows all traffic and that subsumes any other // rule. if (!remoteRule) { // Warn only if addEgressRule() was explicitely called - Annotations.of(this).addWarning('Ignoring Egress rule since \'allowAllOutbound\' is set to true; To add customized rules, set allowAllOutbound=false on the SecurityGroup'); + Annotations.of(this).addWarningV2('EC2:SG:Ipv4IgnoreEgressRule', 'Ignoring Egress rule since \'allowAllOutbound\' is set to true; To add customized rules, set allowAllOutbound=false on the SecurityGroup'); } return; } else if (!isIpv6 && !this.allowAllOutbound) { @@ -568,7 +568,7 @@ export class SecurityGroup extends SecurityGroupBase { // is only one rule which allows all traffic and that subsumes any other // rule. if (!remoteRule) { // Warn only if addEgressRule() was explicitely called - Annotations.of(this).addWarning('Ignoring Egress rule since \'allowAllIpv6Outbound\' is set to true; To add customized rules, set allowAllIpv6Outbound=false on the SecurityGroup'); + Annotations.of(this).addWarningV2('EC2:SG:Ipv6IgnoreEgressRule', 'Ignoring Egress rule since \'allowAllIpv6Outbound\' is set to true; To add customized rules, set allowAllIpv6Outbound=false on the SecurityGroup'); } return; } diff --git a/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts b/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts index 271b48efe3c38..081d125c85b4b 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts @@ -641,7 +641,7 @@ abstract class VpcBase extends Resource implements IVpc { if (placement.subnetGroupName !== undefined) { throw new Error('Please use only \'subnetGroupName\' (\'subnetName\' is deprecated and has the same behavior)'); } else { - Annotations.of(this).addWarning('Usage of \'subnetName\' in SubnetSelection is deprecated, use \'subnetGroupName\' instead'); + Annotations.of(this).addWarningV2('EC2:Vpc:SubnetNameDeprecated', 'Usage of \'subnetName\' in SubnetSelection is deprecated, use \'subnetGroupName\' instead'); } placement = { ...placement, subnetGroupName: placement.subnetName }; } @@ -2184,7 +2184,7 @@ class ImportedVpc extends VpcBase { // None of the values may be unresolved list tokens for (const k of Object.keys(props) as Array) { if (Array.isArray(props[k]) && Token.isUnresolved(props[k])) { - Annotations.of(this).addWarning(`fromVpcAttributes: '${k}' is a list token: the imported VPC will not work with constructs that require a list of subnets at synthesis time. Use 'Vpc.fromLookup()' or 'Fn.importListValue' instead.`); + Annotations.of(this).addWarningV2(`EC2:Vpc:VpcAttributeIsListToken${k}`, `fromVpcAttributes: '${k}' is a list token: the imported VPC will not work with constructs that require a list of subnets at synthesis time. Use 'Vpc.fromLookup()' or 'Fn.importListValue' instead.`); } } @@ -2345,7 +2345,7 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat ? `at '${Node.of(scope).path}/${id}'` : `'${attrs.subnetId}'`; // eslint-disable-next-line max-len - Annotations.of(this).addWarning(`No routeTableId was provided to the subnet ${ref}. Attempting to read its .routeTable.routeTableId will return null/undefined. (More info: https://github.com/aws/aws-cdk/pull/3171)`); + Annotations.of(this).addWarningV2('EC2:Vpc:NoSubnetRouteTableId', `No routeTableId was provided to the subnet ${ref}. Attempting to read its .routeTable.routeTableId will return null/undefined. (More info: https://github.com/aws/aws-cdk/pull/3171)`); } this._ipv4CidrBlock = attrs.ipv4CidrBlock; diff --git a/packages/aws-cdk-lib/aws-ec2/test/instance.test.ts b/packages/aws-cdk-lib/aws-ec2/test/instance.test.ts index 42de157891c8c..888db4ab3c8c8 100644 --- a/packages/aws-cdk-lib/aws-ec2/test/instance.test.ts +++ b/packages/aws-cdk-lib/aws-ec2/test/instance.test.ts @@ -365,7 +365,7 @@ describe('instance', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Instance', 'iops will be ignored without volumeType: IO1, IO2, or GP3'); + Annotations.fromStack(stack).hasWarning('/Default/Instance', 'EC2:EBS:IopsIgnored: iops will be ignored without volumeType: IO1, IO2, or GP3'); }); test('warning if iops and invalid volumeType', () => { @@ -385,7 +385,7 @@ describe('instance', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Instance', 'iops will be ignored without volumeType: IO1, IO2, or GP3'); + Annotations.fromStack(stack).hasWarning('/Default/Instance', 'EC2:EBS:IopsIgnored: iops will be ignored without volumeType: IO1, IO2, or GP3'); }); }); diff --git a/packages/aws-cdk-lib/aws-ec2/test/vpc.test.ts b/packages/aws-cdk-lib/aws-ec2/test/vpc.test.ts index 64f046e48b670..b02ad4c78546b 100644 --- a/packages/aws-cdk-lib/aws-ec2/test/vpc.test.ts +++ b/packages/aws-cdk-lib/aws-ec2/test/vpc.test.ts @@ -1891,7 +1891,7 @@ describe('vpc', () => { subnetIds: { 'Fn::Split': [',', { 'Fn::ImportValue': 'myPublicSubnetIds' }] }, }); - Annotations.fromStack(stack).hasWarning('/TestStack/VPC', "fromVpcAttributes: 'availabilityZones' is a list token: the imported VPC will not work with constructs that require a list of subnets at synthesis time. Use 'Vpc.fromLookup()' or 'Fn.importListValue' instead."); + Annotations.fromStack(stack).hasWarning('/TestStack/VPC', "EC2:Vpc:VpcAttributeIsListTokenavailabilityZones: fromVpcAttributes: 'availabilityZones' is a list token: the imported VPC will not work with constructs that require a list of subnets at synthesis time. Use 'Vpc.fromLookup()' or 'Fn.importListValue' instead."); }); test('fromVpcAttributes using fixed-length list tokens', () => { diff --git a/packages/aws-cdk-lib/aws-ecr-assets/lib/image-asset.ts b/packages/aws-cdk-lib/aws-ecr-assets/lib/image-asset.ts index a626c596c814a..6e318f6be4528 100644 --- a/packages/aws-cdk-lib/aws-ecr-assets/lib/image-asset.ts +++ b/packages/aws-cdk-lib/aws-ecr-assets/lib/image-asset.ts @@ -438,7 +438,7 @@ export class DockerImageAsset extends Construct implements IAsset { exclude.push(cdkout); if (props.repositoryName) { - Annotations.of(this).addWarning('DockerImageAsset.repositoryName is deprecated. Override "core.Stack.addDockerImageAsset" to control asset locations'); + Annotations.of(this).addWarningV2('ECRAssets:ImageAsset:RepositoryNameDeprecated', 'DockerImageAsset.repositoryName is deprecated. Override "core.Stack.addDockerImageAsset" to control asset locations'); } // include build context in "extra" so it will impact the hash diff --git a/packages/aws-cdk-lib/aws-ecr/lib/repository.ts b/packages/aws-cdk-lib/aws-ecr/lib/repository.ts index f1776bc0e747b..205d6ab9e0959 100644 --- a/packages/aws-cdk-lib/aws-ecr/lib/repository.ts +++ b/packages/aws-cdk-lib/aws-ecr/lib/repository.ts @@ -686,7 +686,7 @@ export class Repository extends RepositoryBase { */ public addToResourcePolicy(statement: iam.PolicyStatement): iam.AddToResourcePolicyResult { if (statement.resources.length) { - Annotations.of(this).addWarning('ECR resource policy does not allow resource statements.'); + Annotations.of(this).addWarningV2('ECR:Repository:NoResourceStatements', 'ECR resource policy does not allow resource statements.'); } if (this.policyDocument === undefined) { this.policyDocument = new iam.PolicyDocument(); diff --git a/packages/aws-cdk-lib/aws-ecr/test/repository.test.ts b/packages/aws-cdk-lib/aws-ecr/test/repository.test.ts index b32ebaaf905e1..913cd9aa53053 100644 --- a/packages/aws-cdk-lib/aws-ecr/test/repository.test.ts +++ b/packages/aws-cdk-lib/aws-ecr/test/repository.test.ts @@ -383,7 +383,7 @@ describe('repository', () => { })); // THEN - Annotations.fromStack(stack).hasWarning('*', 'ECR resource policy does not allow resource statements.'); + Annotations.fromStack(stack).hasWarning('*', 'ECR:Repository:NoResourceStatements: ECR resource policy does not allow resource statements.'); }); test('does not warn if repository policy does not have resources', () => { @@ -399,7 +399,7 @@ describe('repository', () => { })); // THEN - Annotations.fromStack(stack).hasNoWarning('*', 'ECR resource policy does not allow resource statements.'); + Annotations.fromStack(stack).hasNoWarning('*', 'ECR:Repository:NoResourceStatements: ECR resource policy does not allow resource statements.'); }); test('default encryption configuration', () => { diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts b/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts index 56baf1a4d46b0..4d8be6946d8e6 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts @@ -364,7 +364,7 @@ test('Scheduled Ec2 Task shows warning when minute is not defined in cron', () = }); // THEN - Annotations.fromStack(stack).hasWarning('/Default', "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."); + Annotations.fromStack(stack).hasWarning('/Default', "Events:Schedule:WillRunEveryMinute: 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."); }); test('Scheduled Ec2 Task shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/scheduled-fargate-task.test.ts b/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/scheduled-fargate-task.test.ts index caca8ef0d32b5..33f20d98d9afa 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/scheduled-fargate-task.test.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/scheduled-fargate-task.test.ts @@ -451,7 +451,7 @@ test('Scheduled Fargate Task shows warning when minute is not defined in cron', }); // THEN - Annotations.fromStack(stack).hasWarning('/Default', "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."); + Annotations.fromStack(stack).hasWarning('/Default', "Events:Schedule:WillRunEveryMinute: 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."); }); test('Scheduled Fargate Task shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts b/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts index fba485aaaf09b..4dfd3fa70cb92 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts @@ -562,7 +562,7 @@ export abstract class BaseService extends Resource }); if (props.deploymentController?.type === DeploymentControllerType.EXTERNAL) { - Annotations.of(this).addWarning('taskDefinition and launchType are blanked out when using external deployment controller.'); + Annotations.of(this).addWarningV2('ECS:Service:ExternalDeploymentController', 'taskDefinition and launchType are blanked out when using external deployment controller.'); } if (props.circuitBreaker diff --git a/packages/aws-cdk-lib/aws-ecs/lib/images/repository.ts b/packages/aws-cdk-lib/aws-ecs/lib/images/repository.ts index bbfc2a7dc6a95..65275db8699f1 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/images/repository.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/images/repository.ts @@ -37,7 +37,7 @@ export class RepositoryImage extends ContainerImage { public bind(scope: Construct, containerDefinition: ContainerDefinition): ContainerImageConfig { // name could be a Token - in that case, skip validation altogether if (!Token.isUnresolved(this.imageName) && ECR_IMAGE_REGEX.test(this.imageName)) { - Annotations.of(scope).addWarning("Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + Annotations.of(scope).addWarningV2('ECS:RepositoryImage:ECRImageRequiresPolicy', "Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); } if (this.props.credentials) { diff --git a/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-service.test.ts index 60a8968adb5a1..8bb6e6f66f9c9 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-service.test.ts @@ -1210,7 +1210,7 @@ describe('ec2 service', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Ec2Service', 'taskDefinition and launchType are blanked out when using external deployment controller.'); + Annotations.fromStack(stack).hasWarning('/Default/Ec2Service', 'ECS:Service:ExternalDeploymentController: taskDefinition and launchType are blanked out when using external deployment controller.'); Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { Cluster: { Ref: 'EcsCluster97242B84', @@ -1248,7 +1248,7 @@ describe('ec2 service', () => { }); // THEN - expect(service.node.metadata[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); + expect(service.node.metadata[0].data.message).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); expect(service.node.metadata[1].data).toEqual('Deployment circuit breaker requires the ECS deployment controller.'); }); diff --git a/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-task-definition.test.ts b/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-task-definition.test.ts index ba22bd0f95861..ca76c6e4f86ae 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-task-definition.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-task-definition.test.ts @@ -688,7 +688,7 @@ describe('ec2 task definition', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Ec2TaskDef/web', "Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + Annotations.fromStack(stack).hasWarning('/Default/Ec2TaskDef/web', "ECS:RepositoryImage:ECRImageRequiresPolicy: Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); }); test('warns when setting containers from ECR repository by creating a RepositoryImage class', () => { @@ -706,7 +706,7 @@ describe('ec2 task definition', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Ec2TaskDef/web', "Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + Annotations.fromStack(stack).hasWarning('/Default/Ec2TaskDef/web', "ECS:RepositoryImage:ECRImageRequiresPolicy: Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); }); test('correctly sets containers from asset using all props', () => { diff --git a/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts index 68327328ce9f3..f0884333a196e 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts @@ -540,7 +540,7 @@ describe('external service', () => { }); // THEN - expect(service.node.metadata[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); + expect(service.node.metadata[0].data.message).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); expect(service.node.metadata[1].data).toEqual('Deployment circuit breaker requires the ECS deployment controller.'); }); diff --git a/packages/aws-cdk-lib/aws-ecs/test/external/external-task-definition.test.ts b/packages/aws-cdk-lib/aws-ecs/test/external/external-task-definition.test.ts index 681e525c1b8f0..76fd96a20704b 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/external/external-task-definition.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/external/external-task-definition.test.ts @@ -578,7 +578,7 @@ describe('external task definition', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/ExternalTaskDef/web', "Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + Annotations.fromStack(stack).hasWarning('/Default/ExternalTaskDef/web', "ECS:RepositoryImage:ECRImageRequiresPolicy: Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); }); test('correctly sets volumes', () => { diff --git a/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts index ca6c7409239fd..d4c4034ec05a2 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts @@ -620,7 +620,7 @@ describe('fargate service', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/FargateService', 'taskDefinition and launchType are blanked out when using external deployment controller.'); + Annotations.fromStack(stack).hasWarning('/Default/FargateService', 'ECS:Service:ExternalDeploymentController: taskDefinition and launchType are blanked out when using external deployment controller.'); Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { Cluster: { Ref: 'EcsCluster97242B84', @@ -678,7 +678,7 @@ describe('fargate service', () => { }); // THEN - expect(service.node.metadata[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); + expect(service.node.metadata[0].data.message).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); expect(service.node.metadata[1].data).toEqual('Deployment circuit breaker requires the ECS deployment controller.'); }); @@ -2258,7 +2258,7 @@ describe('fargate service', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Service/TaskCount/Target', "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."); + Annotations.fromStack(stack).hasWarning('/Default/Service/TaskCount/Target', "AppAutoScaling:Schedule: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."); }); test('scheduled scaling shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-eks/lib/cluster.ts b/packages/aws-cdk-lib/aws-eks/lib/cluster.ts index 5267f59915f4a..5ab3ffc7d132b 100644 --- a/packages/aws-cdk-lib/aws-eks/lib/cluster.ts +++ b/packages/aws-cdk-lib/aws-eks/lib/cluster.ts @@ -1110,7 +1110,7 @@ abstract class ClusterBase extends Resource implements ICluster { let mapRole = options.mapRole ?? true; if (mapRole && !(this instanceof Cluster)) { // do the mapping... - Annotations.of(autoScalingGroup).addWarning('Auto-mapping aws-auth role for imported cluster is not supported, please map role manually'); + Annotations.of(autoScalingGroup).addWarningV2('EKS:Cluster:UnsupportedAutoMappingAwsAutoRole', 'Auto-mapping aws-auth role for imported cluster is not supported, please map role manually'); mapRole = false; } if (mapRole) { @@ -1416,7 +1416,7 @@ export class Cluster extends ClusterBase { const kubectlVersion = new semver.SemVer(`${props.version.version}.0`); if (semver.gte(kubectlVersion, '1.22.0') && !props.kubectlLayer) { - Annotations.of(this).addWarning(`You created a cluster with Kubernetes Version ${props.version.version} without specifying the kubectlLayer property. This may cause failures as the kubectl version provided with aws-cdk-lib is 1.20, which is only guaranteed to be compatible with Kubernetes versions 1.19-1.21. Please provide a kubectlLayer from @aws-cdk/lambda-layer-kubectl-v${kubectlVersion.minor}.`); + Annotations.of(this).addWarningV2('EKS:Cluster:KubectlLayerNotSpecified', `You created a cluster with Kubernetes Version ${props.version.version} without specifying the kubectlLayer property. This may cause failures as the kubectl version provided with aws-cdk-lib is 1.20, which is only guaranteed to be compatible with Kubernetes versions 1.19-1.21. Please provide a kubectlLayer from @aws-cdk/lambda-layer-kubectl-v${kubectlVersion.minor}.`); }; this.version = props.version; @@ -1915,7 +1915,7 @@ export class Cluster extends ClusterBase { // message (if token): "could not auto-tag public/private subnet with tag..." // message (if not token): "count not auto-tag public/private subnet xxxxx with tag..." const subnetID = Token.isUnresolved(subnet.subnetId) || Token.isUnresolved([subnet.subnetId]) ? '' : ` ${subnet.subnetId}`; - Annotations.of(this).addWarning(`Could not auto-tag ${type} subnet${subnetID} with "${tag}=1", please remember to do this manually`); + Annotations.of(this).addWarningV2('EKS:Cluster:MustManuallyTagSubnet', `Could not auto-tag ${type} subnet${subnetID} with "${tag}=1", please remember to do this manually`); continue; } diff --git a/packages/aws-cdk-lib/aws-eks/lib/fargate-profile.ts b/packages/aws-cdk-lib/aws-eks/lib/fargate-profile.ts index 13c760cdd4d1a..515168b6a241a 100644 --- a/packages/aws-cdk-lib/aws-eks/lib/fargate-profile.ts +++ b/packages/aws-cdk-lib/aws-eks/lib/fargate-profile.ts @@ -155,7 +155,7 @@ export class FargateProfile extends Construct implements ITaggable { this.podExecutionRole.grantPassRole(props.cluster.adminRole); if (props.subnetSelection && !props.vpc) { - Annotations.of(this).addWarning('Vpc must be defined to use a custom subnet selection. All private subnets belonging to the EKS cluster will be used by default'); + Annotations.of(this).addWarningV2('EKS:FargateProfile:DefaultToPrivateSubnets', 'Vpc must be defined to use a custom subnet selection. All private subnets belonging to the EKS cluster will be used by default'); } let subnets: string[] | undefined; diff --git a/packages/aws-cdk-lib/aws-eks/lib/managed-nodegroup.ts b/packages/aws-cdk-lib/aws-eks/lib/managed-nodegroup.ts index 70b279d822607..20e7cd0998347 100644 --- a/packages/aws-cdk-lib/aws-eks/lib/managed-nodegroup.ts +++ b/packages/aws-cdk-lib/aws-eks/lib/managed-nodegroup.ts @@ -370,7 +370,7 @@ export class Nodegroup extends Resource implements INodegroup { } if (props.instanceType) { - Annotations.of(this).addWarning('"instanceType" is deprecated and will be removed in the next major version. please use "instanceTypes" instead'); + Annotations.of(this).addWarningV2('EKS:ManagedNodeGroup:DeprecatedInstanceType', '"instanceType" is deprecated and will be removed in the next major version. please use "instanceTypes" instead'); } const instanceTypes = props.instanceTypes ?? (props.instanceType ? [props.instanceType] : undefined); let possibleAmiTypes: NodegroupAmiType[] = []; diff --git a/packages/aws-cdk-lib/aws-eks/test/cluster.test.ts b/packages/aws-cdk-lib/aws-eks/test/cluster.test.ts index 545d2ae8cfb59..2a922b8d90b8f 100644 --- a/packages/aws-cdk-lib/aws-eks/test/cluster.test.ts +++ b/packages/aws-cdk-lib/aws-eks/test/cluster.test.ts @@ -3064,6 +3064,7 @@ describe('cluster', () => { describe('kubectlLayer annotation', () => { function message(version: string) { return [ + 'EKS:Cluster:KubectlLayerNotSpecified:', `You created a cluster with Kubernetes Version 1.${version} without specifying the kubectlLayer property.`, 'This may cause failures as the kubectl version provided with aws-cdk-lib is 1.20, which is only guaranteed to be compatible with Kubernetes versions 1.19-1.21.', `Please provide a kubectlLayer from @aws-cdk/lambda-layer-kubectl-v${version}.`, @@ -3207,4 +3208,4 @@ describe('cluster', () => { }, }); }); -}); \ No newline at end of file +}); diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts index c9627e18688c8..bb6881b0682db 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts @@ -306,7 +306,7 @@ export class ApplicationListenerRule extends Construct { // Instead, signal this through a warning. // @deprecate: upon the next major version bump, replace this with a `throw` if (this.action) { - cdk.Annotations.of(this).addWarning('An Action already existed on this ListenerRule and was replaced. Configure exactly one default Action.'); + cdk.Annotations.of(this).addWarningV2('Elbv2:ALBListnerRule:DefaultActionReplaced', 'An Action already existed on this ListenerRule and was replaced. Configure exactly one default Action.'); } action.bind(this, this.listener, this); diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index 85ee5a2146c88..e1102589c6faa 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -643,11 +643,11 @@ class ImportedApplicationTargetGroup extends ImportedTargetGroupBase implements public registerListener(_listener: IApplicationListener, _associatingConstruct?: IConstruct) { // Nothing to do, we know nothing of our members - Annotations.of(this).addWarning('Cannot register listener on imported target group -- security groups might need to be updated manually'); + Annotations.of(this).addWarningV2('Elbv2:ALBTargetGroup:CannotRegisterListener', 'Cannot register listener on imported target group -- security groups might need to be updated manually'); } public registerConnectable(_connectable: ec2.IConnectable, _portRange?: ec2.Port | undefined): void { - Annotations.of(this).addWarning('Cannot register connectable on imported target group -- security groups might need to be updated manually'); + Annotations.of(this).addWarningV2('Elbv2:AlbTargetGroup:CannotRegisterConnectable', 'Cannot register connectable on imported target group -- security groups might need to be updated manually'); } public addTarget(...targets: IApplicationLoadBalancerTarget[]) { diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-listener.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-listener.ts index 9c2aa911a247e..688f218f1f741 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-listener.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-listener.ts @@ -153,7 +153,7 @@ export abstract class BaseListener extends Resource implements IListener { // Instead, signal this through a warning. // @deprecate: upon the next major version bump, replace this with a `throw` if (this.defaultAction) { - Annotations.of(this).addWarning('A default Action already existed on this Listener and was replaced. Configure exactly one default Action.'); + Annotations.of(this).addWarningV2('Elbv2:Listener:ExistingDefaultActionReplaced', 'A default Action already existed on this Listener and was replaced. Configure exactly one default Action.'); } this.defaultAction = action; diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts index dc180d2d9f8f1..5bf3396b7cf8f 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts @@ -331,7 +331,7 @@ export abstract class TargetGroupBase extends Construct implements ITargetGroup const ret = new Array(); if (this.targetType === undefined && this.targetsJson.length === 0) { - cdk.Annotations.of(this).addWarning("When creating an empty TargetGroup, you should specify a 'targetType' (this warning may become an error in the future)."); + cdk.Annotations.of(this).addWarningV2('Elbv2:TargetGroup:SpecifyTargetTypeForEmptyTargetGroup', "When creating an empty TargetGroup, you should specify a 'targetType' (this warning may become an error in the future)."); } if (this.targetType !== TargetType.LAMBDA && this.vpc === undefined) { diff --git a/packages/aws-cdk-lib/aws-events-targets/lib/aws-api.ts b/packages/aws-cdk-lib/aws-events-targets/lib/aws-api.ts index 028e48fc224d5..9b801d28431d1 100644 --- a/packages/aws-cdk-lib/aws-events-targets/lib/aws-api.ts +++ b/packages/aws-cdk-lib/aws-events-targets/lib/aws-api.ts @@ -129,7 +129,7 @@ export class AwsApi implements events.IRuleTarget { function checkServiceExists(service: string, handler: lambda.SingletonFunction) { const sdkService = awsSdkMetadata[service.toLowerCase()]; if (!sdkService) { - Annotations.of(handler).addWarning(`Service ${service} does not exist in the AWS SDK. Check the list of available \ + Annotations.of(handler).addWarningV2(`EventsTargets:Api:${service}DoesNotExist`, `Service ${service} does not exist in the AWS SDK. Check the list of available \ services and actions from https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/index.html`); } } diff --git a/packages/aws-cdk-lib/aws-events-targets/lib/ecs-task.ts b/packages/aws-cdk-lib/aws-events-targets/lib/ecs-task.ts index 6c04a5d5fe623..e76b86ceb2aaf 100644 --- a/packages/aws-cdk-lib/aws-events-targets/lib/ecs-task.ts +++ b/packages/aws-cdk-lib/aws-events-targets/lib/ecs-task.ts @@ -165,7 +165,7 @@ export class EcsTask implements events.IRuleTarget { // Security groups are only configurable with the "awsvpc" network mode. if (this.taskDefinition.networkMode !== ecs.NetworkMode.AWS_VPC) { if (props.securityGroup !== undefined || props.securityGroups !== undefined) { - cdk.Annotations.of(this.taskDefinition).addWarning('security groups are ignored when network mode is not awsvpc'); + cdk.Annotations.of(this.taskDefinition).addWarningV2('EventsTargets:EcsTask:SecurityGroupIgnored', 'security groups are ignored when network mode is not awsvpc'); } return; } diff --git a/packages/aws-cdk-lib/aws-events-targets/lib/util.ts b/packages/aws-cdk-lib/aws-events-targets/lib/util.ts index c42e8942df31c..4d8cfd4e1955b 100644 --- a/packages/aws-cdk-lib/aws-events-targets/lib/util.ts +++ b/packages/aws-cdk-lib/aws-events-targets/lib/util.ts @@ -133,7 +133,7 @@ export function addToDeadLetterQueueResourcePolicy(rule: events.IRule, queue: sq }, })); } else { - Annotations.of(rule).addWarning(`Cannot add a resource policy to your dead letter queue associated with rule ${rule.ruleName} because the queue is in a different account. You must add the resource policy manually to the dead letter queue in account ${queue.env.account}.`); + Annotations.of(rule).addWarningV2('EventsTargets:DLQ:ManuallyAddDLQResourcePolicy', `Cannot add a resource policy to your dead letter queue associated with rule ${rule.ruleName} because the queue is in a different account. You must add the resource policy manually to the dead letter queue in account ${queue.env.account}.`); } } diff --git a/packages/aws-cdk-lib/aws-events-targets/test/aws-api/aws-api.test.ts b/packages/aws-cdk-lib/aws-events-targets/test/aws-api/aws-api.test.ts index 425a49d4e246a..940e66938291a 100644 --- a/packages/aws-cdk-lib/aws-events-targets/test/aws-api/aws-api.test.ts +++ b/packages/aws-cdk-lib/aws-events-targets/test/aws-api/aws-api.test.ts @@ -163,5 +163,5 @@ test('with service not in AWS SDK', () => { rule.addTarget(awsApi); // THEN - Annotations.fromStack(stack).hasWarning('*', 'Service no-such-service does not exist in the AWS SDK. Check the list of available services and actions from https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/index.html'); + Annotations.fromStack(stack).hasWarning('*', 'EventsTargets:Api:no-such-serviceDoesNotExist: Service no-such-service does not exist in the AWS SDK. Check the list of available services and actions from https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/index.html'); }); diff --git a/packages/aws-cdk-lib/aws-events-targets/test/lambda/lambda.test.ts b/packages/aws-cdk-lib/aws-events-targets/test/lambda/lambda.test.ts index 31eabbba22b9b..ba75d60a92542 100644 --- a/packages/aws-cdk-lib/aws-events-targets/test/lambda/lambda.test.ts +++ b/packages/aws-cdk-lib/aws-events-targets/test/lambda/lambda.test.ts @@ -320,13 +320,7 @@ test('must display a warning when using a Dead Letter Queue from another account Template.fromStack(stack1).resourceCountIs('AWS::SQS::QueuePolicy', 0); - Annotations.fromStack(stack1).hasWarning('/Stack1/Rule', Match.objectLike({ - 'Fn::Join': Match.arrayWith([ - Match.arrayWith([ - 'Cannot add a resource policy to your dead letter queue associated with rule ', - ]), - ]), - })); + Annotations.fromStack(stack1).hasWarning('/Stack1/Rule', Match.stringLikeRegexp('EventsTargets:DLQ:ManuallyAddDLQResourcePolicy: .*')); }); test('specifying retry policy', () => { diff --git a/packages/aws-cdk-lib/aws-events/lib/rule.ts b/packages/aws-cdk-lib/aws-events/lib/rule.ts index 4b4c60f7a3517..07f3d9f9bfef2 100644 --- a/packages/aws-cdk-lib/aws-events/lib/rule.ts +++ b/packages/aws-cdk-lib/aws-events/lib/rule.ts @@ -448,7 +448,7 @@ export class Rule extends Resource implements IRule { private sameEnvDimension(dim1: string, dim2: string) { switch (Token.compareStrings(dim1, dim2)) { case TokenComparison.ONE_UNRESOLVED: - Annotations.of(this).addWarning('Either the Event Rule or target has an unresolved environment. \n \ + Annotations.of(this).addWarningV2('Events:Rule:UnresolvedEnvironment', 'Either the Event Rule or target has an unresolved environment. \n \ If they are being used in a cross-environment setup you need to specify the environment for both.'); return true; case TokenComparison.BOTH_UNRESOLVED: diff --git a/packages/aws-cdk-lib/aws-events/lib/schedule.ts b/packages/aws-cdk-lib/aws-events/lib/schedule.ts index d9e27642fc8e0..a62fb7a4c18f1 100644 --- a/packages/aws-cdk-lib/aws-events/lib/schedule.ts +++ b/packages/aws-cdk-lib/aws-events/lib/schedule.ts @@ -62,7 +62,7 @@ export abstract class Schedule { public readonly expressionString: string = `cron(${minute} ${hour} ${day} ${month} ${weekDay} ${year})`; public _bind(scope: Construct) { if (!options.minute) { - 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.'); + Annotations.of(scope).addWarningV2('Events:Schedule:WillRunEveryMinute', '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.'); } return new LiteralSchedule(this.expressionString); } diff --git a/packages/aws-cdk-lib/aws-events/test/rule.test.ts b/packages/aws-cdk-lib/aws-events/test/rule.test.ts index 537859aa9b4f0..b36f1e2337440 100644 --- a/packages/aws-cdk-lib/aws-events/test/rule.test.ts +++ b/packages/aws-cdk-lib/aws-events/test/rule.test.ts @@ -38,7 +38,7 @@ describe('rule', () => { }), }); - Annotations.fromStack(stack).hasWarning('/Default/MyRule', "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."); + Annotations.fromStack(stack).hasWarning('/Default/MyRule', "Events:Schedule:WillRunEveryMinute: 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."); }); test('rule does not display warning when minute is set to * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-iam/lib/group.ts b/packages/aws-cdk-lib/aws-iam/lib/group.ts index 6346d216596ae..dd49d170e7d2d 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/group.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/group.ts @@ -218,7 +218,7 @@ export class Group extends GroupBase { private managedPoliciesExceededWarning() { if (this.managedPolicies.length > 10) { - Annotations.of(this).addWarning(`You added ${this.managedPolicies.length} to IAM Group ${this.physicalName}. The maximum number of managed policies attached to an IAM group is 10.`); + Annotations.of(this).addWarningV2('IAM:Group:MaxPoliciesExceeded', `You added ${this.managedPolicies.length} to IAM Group ${this.physicalName}. The maximum number of managed policies attached to an IAM group is 10.`); } } } diff --git a/packages/aws-cdk-lib/aws-iam/lib/private/imported-role.ts b/packages/aws-cdk-lib/aws-iam/lib/private/imported-role.ts index d7bb14da16ccf..f614e4832911e 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/private/imported-role.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/private/imported-role.ts @@ -68,7 +68,7 @@ export class ImportedRole extends Resource implements IRole, IComparablePrincipa } public addManagedPolicy(policy: IManagedPolicy): void { - Annotations.of(this).addWarning(`Not adding managed policy: ${policy.managedPolicyArn} to imported role: ${this.roleName}`); + Annotations.of(this).addWarningV2('IAM:ImportedRole:ManagedPolicyNotAdded', `Not adding managed policy: ${policy.managedPolicyArn} to imported role: ${this.roleName}`); } public grantPassRole(identity: IPrincipal): Grant { diff --git a/packages/aws-cdk-lib/aws-iam/lib/role.ts b/packages/aws-cdk-lib/aws-iam/lib/role.ts index 0e7d451341eda..b8dbf92535118 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/role.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/role.ts @@ -652,9 +652,9 @@ export class Role extends Resource implements IRole { const mpCount = this.managedPolicies.length + (splitOffDocs.size - 1); if (mpCount > 20) { - Annotations.of(this).addWarning(`Policy too large: ${mpCount} exceeds the maximum of 20 managed policies attached to a Role`); + Annotations.of(this).addWarningV2('IAM:Role:PolicyTooLarge', `Policy too large: ${mpCount} exceeds the maximum of 20 managed policies attached to a Role`); } else if (mpCount > 10) { - Annotations.of(this).addWarning(`Policy large: ${mpCount} exceeds 10 managed policies attached to a Role, this requires a quota increase`); + Annotations.of(this).addWarningV2('IAM:Role:PolicyLarge', `Policy large: ${mpCount} exceeds 10 managed policies attached to a Role, this requires a quota increase`); } // Create the managed policies and fix up the dependencies @@ -790,4 +790,4 @@ Object.defineProperty(Role.prototype, IAM_ROLE_SYMBOL, { value: true, enumerable: false, writable: false, -}); \ No newline at end of file +}); diff --git a/packages/aws-cdk-lib/aws-iam/lib/unknown-principal.ts b/packages/aws-cdk-lib/aws-iam/lib/unknown-principal.ts index 49bab6a23244f..c08e1fc4e3480 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/unknown-principal.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/unknown-principal.ts @@ -41,7 +41,7 @@ export class UnknownPrincipal implements IPrincipal { public addToPrincipalPolicy(statement: PolicyStatement): AddToPrincipalPolicyResult { const stack = Stack.of(this.resource); const repr = JSON.stringify(stack.resolve(statement)); - Annotations.of(this.resource).addWarning(`Add statement to this resource's role: ${repr}`); + Annotations.of(this.resource).addWarningV2('IAM:UnknownPrincipal:AddStatementToRole', `Add statement to this resource's role: ${repr}`); // Pretend we did the work. The human will do it for us, eventually. return { statementAdded: true, policyDependable: new DependencyGroup() }; } @@ -49,4 +49,4 @@ export class UnknownPrincipal implements IPrincipal { public addToPolicy(statement: PolicyStatement): boolean { return this.addToPrincipalPolicy(statement).statementAdded; } -} \ No newline at end of file +} diff --git a/packages/aws-cdk-lib/aws-iam/test/group.test.ts b/packages/aws-cdk-lib/aws-iam/test/group.test.ts index 466b4f902d906..b45d13ec32f59 100644 --- a/packages/aws-cdk-lib/aws-iam/test/group.test.ts +++ b/packages/aws-cdk-lib/aws-iam/test/group.test.ts @@ -126,7 +126,7 @@ test('throw warning if attached managed policies exceed 10 in constructor', () = ], }); - Annotations.fromStack(stack).hasWarning('*', 'You added 11 to IAM Group MyGroup. The maximum number of managed policies attached to an IAM group is 10.'); + Annotations.fromStack(stack).hasWarning('*', 'IAM:Group:MaxPoliciesExceeded: You added 11 to IAM Group MyGroup. The maximum number of managed policies attached to an IAM group is 10.'); }); test('throw warning if attached managed policies exceed 10 when calling `addManagedPolicy`', () => { @@ -142,5 +142,5 @@ test('throw warning if attached managed policies exceed 10 when calling `addMana group.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName(i.toString())); } - Annotations.fromStack(stack).hasWarning('/Default/MyGroup', 'You added 11 to IAM Group MyGroup. The maximum number of managed policies attached to an IAM group is 10.'); + Annotations.fromStack(stack).hasWarning('/Default/MyGroup', 'IAM:Group:MaxPoliciesExceeded: You added 11 to IAM Group MyGroup. The maximum number of managed policies attached to an IAM group is 10.'); }); diff --git a/packages/aws-cdk-lib/aws-lambda-event-sources/lib/sqs.ts b/packages/aws-cdk-lib/aws-lambda-event-sources/lib/sqs.ts index ff23e77b6357f..7ae77e3bdda6a 100644 --- a/packages/aws-cdk-lib/aws-lambda-event-sources/lib/sqs.ts +++ b/packages/aws-cdk-lib/aws-lambda-event-sources/lib/sqs.ts @@ -103,7 +103,7 @@ export class SqsEventSource implements lambda.IEventSource { if (target.role) { this.queue.grantConsumeMessages(target); } else { - Annotations.of(target).addWarning(`Function '${target.node.path}' was imported without an IAM role `+ + Annotations.of(target).addWarningV2('LambdaEventSources:SQS:FunctionImportWithoutRole', `Function '${target.node.path}' was imported without an IAM role `+ `so it was not granted access to consume messages from '${this.queue.node.path}'`); } } diff --git a/packages/aws-cdk-lib/aws-lambda/lib/function-base.ts b/packages/aws-cdk-lib/aws-lambda/lib/function-base.ts index 46788487acf7b..dffc6bf535f33 100644 --- a/packages/aws-cdk-lib/aws-lambda/lib/function-base.ts +++ b/packages/aws-cdk-lib/aws-lambda/lib/function-base.ts @@ -325,7 +325,7 @@ export abstract class FunctionBase extends Resource implements IFunction, ec2.IC } protected warnInvokeFunctionPermissions(scope: Construct): void { - Annotations.of(scope).addWarning([ + Annotations.of(scope).addWarningV2('Lambda:Function:AddPermissionsToVersionOrAlias', [ "AWS Lambda has changed their authorization strategy, which may cause client invocations using the 'Qualifier' parameter of the lambda function to fail with Access Denied errors.", "If you are using a lambda Version or Alias, make sure to call 'grantInvoke' or 'addPermission' on the Version or Alias, not the underlying Function", 'See: https://github.com/aws/aws-cdk/issues/19273', diff --git a/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts b/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts index 739a756559ec3..62d7dd371b370 100644 --- a/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts +++ b/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts @@ -602,7 +602,7 @@ describe('alias', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Alias/AliasScaling/Target', "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."); + Annotations.fromStack(stack).hasWarning('/Default/Alias/AliasScaling/Target', "AppAutoScaling:Schedule: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."); }); test('scheduled scaling shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-rds/lib/cluster.ts b/packages/aws-cdk-lib/aws-rds/lib/cluster.ts index 8e65228eab9db..0995b6d4aa00e 100644 --- a/packages/aws-cdk-lib/aws-rds/lib/cluster.ts +++ b/packages/aws-cdk-lib/aws-rds/lib/cluster.ts @@ -707,7 +707,7 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase { if (hasOnlyServerlessReaders) { if (noFailoverTierInstances) { Annotations.of(this).addWarningV2( - 'RDSNoFailoverServerlessReaders', + 'RDS:Cluster:NoFailoverServerlessReaders', `Cluster ${this.node.id} only has serverless readers and no reader is in promotion tier 0-1.`+ 'Serverless readers in promotion tiers >= 2 will NOT scale with the writer, which can lead to '+ 'availability issues if a failover event occurs. It is recommended that at least one reader '+ @@ -718,7 +718,7 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase { } else { if (serverlessInHighestTier && highestTier > 1) { Annotations.of(this).addWarningV2( - 'RDSServerlessInHighestTier2-15', + 'RDS:Cluster:ServerlessInHighestTier2-15', `There are serverlessV2 readers in tier ${highestTier}. Since there are no instances in a higher tier, `+ 'any instance in this tier is a failover target. Since this tier is > 1 the serverless reader will not scale '+ 'with the writer which could lead to availability issues during failover.', @@ -726,7 +726,7 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase { } if (someProvisionedReadersDontMatchWriter.length > 0 && writer.type === InstanceType.PROVISIONED) { Annotations.of(this).addWarningV2( - 'RDSProvisionedReadersDontMatchWriter', + 'RDS:Cluster:ProvisionedReadersDontMatchWriter', `There are provisioned readers in the highest promotion tier ${highestTier} that do not have the same `+ 'InstanceSize as the writer. Any of these instances could be chosen as the new writer in the event '+ 'of a failover.\n'+ @@ -745,7 +745,7 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase { if (writer.type === InstanceType.PROVISIONED) { if (reader.type === InstanceType.SERVERLESS_V2) { if (!instanceSizeSupportedByServerlessV2(writer.instanceSize!, this.serverlessV2MaxCapacity)) { - Annotations.of(this).addWarning( + Annotations.of(this).addWarningV2('RDS:Cluster:ServerlessInstanceCantScaleWithWriter', 'For high availability any serverless instances in promotion tiers 0-1 '+ 'should be able to scale to match the provisioned instance capacity.\n'+ `Serverless instance ${reader.node.id} is in promotion tier ${reader.tier},\n`+ @@ -1131,10 +1131,10 @@ export class DatabaseClusterFromSnapshot extends DatabaseClusterNew { super(scope, id, props); if (props.credentials && !props.credentials.password && !props.credentials.secret) { - Annotations.of(this).addWarning('Use `snapshotCredentials` to modify password of a cluster created from a snapshot.'); + Annotations.of(this).addWarningV2('RDS:Cluster:UseSnapshotCredentials', 'Use `snapshotCredentials` to modify password of a cluster created from a snapshot.'); } if (!props.credentials && !props.snapshotCredentials) { - Annotations.of(this).addWarning('Generated credentials will not be applied to cluster. Use `snapshotCredentials` instead. `addRotationSingleUser()` and `addRotationMultiUser()` cannot be used on this cluster.'); + Annotations.of(this).addWarningV2('RDS:Cluster:GeneratedCredsNotApplied', 'Generated credentials will not be applied to cluster. Use `snapshotCredentials` instead. `addRotationSingleUser()` and `addRotationMultiUser()` cannot be used on this cluster.'); } const deprecatedCredentials = renderCredentials(this, props.engine, props.credentials); diff --git a/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts b/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts index 39d1bf095b1fb..6807aaf66a5c6 100644 --- a/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts +++ b/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts @@ -428,7 +428,7 @@ describe('cluster new api', () => { }); Annotations.fromStack(stack).hasWarning('*', - `RDSNoFailoverServerlessReaders: Cluster ${cluster.node.id} only has serverless readers and no reader is in promotion tier 0-1.`+ + `RDS:Cluster:NoFailoverServerlessReaders: Cluster ${cluster.node.id} only has serverless readers and no reader is in promotion tier 0-1.`+ 'Serverless readers in promotion tiers >= 2 will NOT scale with the writer, which can lead to '+ 'availability issues if a failover event occurs. It is recommended that at least one reader '+ 'has `scaleWithWriter` set to true', @@ -592,6 +592,7 @@ describe('cluster new api', () => { }); Annotations.fromStack(stack).hasWarning('*', + 'RDS:Cluster:ServerlessInstanceCantScaleWithWriter: '+ 'For high availability any serverless instances in promotion tiers 0-1 '+ 'should be able to scale to match the provisioned instance capacity.\n'+ 'Serverless instance reader is in promotion tier 1,\n'+ @@ -674,7 +675,7 @@ describe('cluster new api', () => { }); Annotations.fromStack(stack).hasWarning('*', - 'RDSProvisionedReadersDontMatchWriter: There are provisioned readers in the highest promotion tier 2 that do not have the same '+ + 'RDS:Cluster:ProvisionedReadersDontMatchWriter: There are provisioned readers in the highest promotion tier 2 that do not have the same '+ 'InstanceSize as the writer. Any of these instances could be chosen as the new writer in the event '+ 'of a failover.\n'+ 'Writer InstanceSize: m5.24xlarge\n'+ @@ -810,13 +811,13 @@ describe('cluster new api', () => { }); Annotations.fromStack(stack).hasWarning('*', - 'RDSServerlessInHighestTier2-15: There are serverlessV2 readers in tier 2. Since there are no instances in a higher tier, '+ + 'RDS:Cluster:ServerlessInHighestTier2-15: There are serverlessV2 readers in tier 2. Since there are no instances in a higher tier, '+ 'any instance in this tier is a failover target. Since this tier is > 1 the serverless reader will not scale '+ 'with the writer which could lead to availability issues during failover.', ); Annotations.fromStack(stack).hasWarning('*', - 'RDSProvisionedReadersDontMatchWriter: There are provisioned readers in the highest promotion tier 2 that do not have the same '+ + 'RDS:Cluster:ProvisionedReadersDontMatchWriter: There are provisioned readers in the highest promotion tier 2 that do not have the same '+ 'InstanceSize as the writer. Any of these instances could be chosen as the new writer in the event '+ 'of a failover.\n'+ 'Writer InstanceSize: m5.24xlarge\n'+ diff --git a/packages/aws-cdk-lib/aws-s3-notifications/lib/sqs.ts b/packages/aws-cdk-lib/aws-s3-notifications/lib/sqs.ts index 4206d067e6ecc..7374f3a39b918 100644 --- a/packages/aws-cdk-lib/aws-s3-notifications/lib/sqs.ts +++ b/packages/aws-cdk-lib/aws-s3-notifications/lib/sqs.ts @@ -32,7 +32,7 @@ export class SqsDestination implements s3.IBucketNotificationDestination { }); const addResult = this.queue.encryptionMasterKey.addToResourcePolicy(statement, /* allowNoOp */ true); if (!addResult.statementAdded) { - Annotations.of(this.queue.encryptionMasterKey).addWarning(`Can not change key policy of imported kms key. Ensure that your key policy contains the following permissions: \n${JSON.stringify(statement.toJSON(), null, 2)}`); + Annotations.of(this.queue.encryptionMasterKey).addWarningV2('S3Notifications:SQS:KMSPermissionsNotAdded', `Can not change key policy of imported kms key. Ensure that your key policy contains the following permissions: \n${JSON.stringify(statement.toJSON(), null, 2)}`); } } diff --git a/packages/aws-cdk-lib/aws-s3-notifications/test/queue.test.ts b/packages/aws-cdk-lib/aws-s3-notifications/test/queue.test.ts index 2433404fc7e2a..78efda0d082f8 100644 --- a/packages/aws-cdk-lib/aws-s3-notifications/test/queue.test.ts +++ b/packages/aws-cdk-lib/aws-s3-notifications/test/queue.test.ts @@ -109,7 +109,7 @@ test('if the queue is encrypted with a imported kms key, printout warning', () = bucket.addObjectCreatedNotification(new notif.SqsDestination(queue)); - Annotations.fromStack(stack).hasWarning('/Default/ImportedKey', `Can not change key policy of imported kms key. Ensure that your key policy contains the following permissions: \n${JSON.stringify({ + Annotations.fromStack(stack).hasWarning('/Default/ImportedKey', `S3Notifications:SQS:KMSPermissionsNotAdded: Can not change key policy of imported kms key. Ensure that your key policy contains the following permissions: \n${JSON.stringify({ Action: [ 'kms:GenerateDataKey*', 'kms:Decrypt', diff --git a/packages/aws-cdk-lib/aws-s3/lib/bucket.ts b/packages/aws-cdk-lib/aws-s3/lib/bucket.ts index d220e220082bb..ca82d7b3e5945 100644 --- a/packages/aws-cdk-lib/aws-s3/lib/bucket.ts +++ b/packages/aws-cdk-lib/aws-s3/lib/bucket.ts @@ -1893,7 +1893,7 @@ export class Bucket extends BucketBase { } else if (props.serverAccessLogsBucket) { // A `serverAccessLogsBucket` was provided but it is not a concrete `Bucket` and it // may not be possible to configure the ACLs or bucket policy as required. - Annotations.of(this).addWarning( + Annotations.of(this).addWarningV2('S3:Bucket:AccessLogsPolicyNotAdded', `Unable to add necessary logging permissions to imported target bucket: ${props.serverAccessLogsBucket}`, ); } diff --git a/packages/aws-cdk-lib/aws-servicecatalog/lib/private/product-stack-synthesizer.ts b/packages/aws-cdk-lib/aws-servicecatalog/lib/private/product-stack-synthesizer.ts index 2971c6c0fceff..e7a2c1de1bfcd 100644 --- a/packages/aws-cdk-lib/aws-servicecatalog/lib/private/product-stack-synthesizer.ts +++ b/packages/aws-cdk-lib/aws-servicecatalog/lib/private/product-stack-synthesizer.ts @@ -27,7 +27,7 @@ export class ProductStackSynthesizer extends cdk.StackSynthesizer { if (!this.bucketDeployment) { const parentStack = (this.boundStack as ProductStack)._getParentStack(); if (!cdk.Resource.isOwnedResource(this.assetBucket)) { - cdk.Annotations.of(parentStack).addWarning('[WARNING] Bucket Policy Permissions cannot be added to' + + cdk.Annotations.of(parentStack).addWarningV2('ServiceCatalog:Assets:ManuallyAddBucketPermissions', '[WARNING] Bucket Policy Permissions cannot be added to' + ' referenced Bucket. Please make sure your bucket has the correct permissions'); } this.bucketDeployment = new BucketDeployment(parentStack, 'AssetsBucketDeployment', { diff --git a/packages/aws-cdk-lib/aws-ses-actions/lib/lambda.ts b/packages/aws-cdk-lib/aws-ses-actions/lib/lambda.ts index 6917184924a74..693cecae45431 100644 --- a/packages/aws-cdk-lib/aws-ses-actions/lib/lambda.ts +++ b/packages/aws-cdk-lib/aws-ses-actions/lib/lambda.ts @@ -71,7 +71,7 @@ export class Lambda implements ses.IReceiptRuleAction { rule.node.addDependency(permission); } else { // eslint-disable-next-line max-len - cdk.Annotations.of(rule).addWarning('This rule is using a Lambda action with an imported function. Ensure permission is given to SES to invoke that function.'); + cdk.Annotations.of(rule).addWarningV2('SESActions:Lambda:AddInvokePermissions', 'This rule is using a Lambda action with an imported function. Ensure permission is given to SES to invoke that function.'); } return { diff --git a/packages/aws-cdk-lib/aws-ses-actions/lib/s3.ts b/packages/aws-cdk-lib/aws-ses-actions/lib/s3.ts index 4505090c30826..df25d429731a8 100644 --- a/packages/aws-cdk-lib/aws-ses-actions/lib/s3.ts +++ b/packages/aws-cdk-lib/aws-ses-actions/lib/s3.ts @@ -65,7 +65,7 @@ export class S3 implements ses.IReceiptRuleAction { if (policy) { // The bucket could be imported rule.node.addDependency(policy); } else { - cdk.Annotations.of(rule).addWarning('This rule is using a S3 action with an imported bucket. Ensure permission is given to SES to write to that bucket.'); + cdk.Annotations.of(rule).addWarningV2('SESActions:S3:AddBucketPermissions', 'This rule is using a S3 action with an imported bucket. Ensure permission is given to SES to write to that bucket.'); } // Allow SES to use KMS master key diff --git a/packages/aws-cdk-lib/core/lib/annotations.ts b/packages/aws-cdk-lib/core/lib/annotations.ts index 80bd61f7f8329..93d967145657c 100644 --- a/packages/aws-cdk-lib/core/lib/annotations.ts +++ b/packages/aws-cdk-lib/core/lib/annotations.ts @@ -118,7 +118,7 @@ export class Annotations { throw new Error(`${this.scope.node.path}: ${text}`); } - this.addWarning(text); + this.addWarningV2(`Deprecated:${api}`, text); } /** @@ -132,7 +132,20 @@ export class Annotations { level: string, message: cxschema.LogMessageMetadataEntry | cxschema.LogMessageObjectMetadataEntry | cxschema.AcknowledgementMetadataEntry, ) { - const isNew = !this.scope.node.metadata.find((x) => x.data === message); + let isNew = false; + switch (typeof message) { + case 'string': + isNew = !this.scope.node.metadata.find((x) => x.data === message); + break; + case 'object': + if ('scope' in message) { + isNew = !this.scope.node.metadata.find((x) => x.data.id === message.id && x.data.message === message.message); + } else if ('scopes' in message) { + isNew = !this.scope.node.metadata.find((x) => + x.data.id === message.id && x.data.message === message.message && x.data.scopes === message.scopes, + ); + } + } if (isNew) { this.scope.node.addMetadata(level, message, { stackTrace: this.stackTraces }); } diff --git a/packages/aws-cdk-lib/core/lib/cfn-resource.ts b/packages/aws-cdk-lib/core/lib/cfn-resource.ts index b8b1733f016fc..f2864bbccc9f3 100644 --- a/packages/aws-cdk-lib/core/lib/cfn-resource.ts +++ b/packages/aws-cdk-lib/core/lib/cfn-resource.ts @@ -148,7 +148,7 @@ export class CfnResource extends CfnRefElement { if (FeatureFlags.of(this).isEnabled(cxapi.VALIDATE_SNAPSHOT_REMOVAL_POLICY) ) { throw new Error(`${this.cfnResourceType} does not support snapshot removal policy`); } else { - Annotations.of(this).addWarning(`${this.cfnResourceType} does not support snapshot removal policy. This policy will be ignored.`); + Annotations.of(this).addWarningV2(`Core:CfnResource:${this.cfnResourceType}SnapshotRemovalPolicyIgnored`, `${this.cfnResourceType} does not support snapshot removal policy. This policy will be ignored.`); } } diff --git a/packages/aws-cdk-lib/core/lib/private/synthesis.ts b/packages/aws-cdk-lib/core/lib/private/synthesis.ts index 7ad67d959ec27..cc7c52b6466d4 100644 --- a/packages/aws-cdk-lib/core/lib/private/synthesis.ts +++ b/packages/aws-cdk-lib/core/lib/private/synthesis.ts @@ -238,7 +238,7 @@ function invokeAspects(root: IConstruct) { // if an aspect was added to the node while invoking another aspect it will not be invoked, emit a warning // the `nestedAspectWarning` flag is used to prevent the warning from being emitted for every child if (!nestedAspectWarning && nodeAspectsCount !== aspects.all.length) { - Annotations.of(construct).addWarning('We detected an Aspect was added via another Aspect, and will not be applied'); + Annotations.of(construct).addWarningV2('Core:IgnoredAspect', 'We detected an Aspect was added via another Aspect, and will not be applied'); nestedAspectWarning = true; } diff --git a/packages/aws-cdk-lib/core/lib/private/tree-metadata.ts b/packages/aws-cdk-lib/core/lib/private/tree-metadata.ts index f0ceb3c0301c5..fbe72e170c722 100644 --- a/packages/aws-cdk-lib/core/lib/private/tree-metadata.ts +++ b/packages/aws-cdk-lib/core/lib/private/tree-metadata.ts @@ -35,7 +35,7 @@ export class TreeMetadata extends Construct { try { return visit(c); } catch (e) { - Annotations.of(this).addWarning(`Failed to render tree metadata for node [${c.node.id}]. Reason: ${e}`); + Annotations.of(this).addWarningV2(`Core:FailedToRenderTreeMetadata-${c.node.id}`, `Failed to render tree metadata for node [${c.node.id}]. Reason: ${e}`); return undefined; } }); diff --git a/packages/aws-cdk-lib/core/lib/stack.ts b/packages/aws-cdk-lib/core/lib/stack.ts index 2980f3464b7be..2e1af79c4f827 100644 --- a/packages/aws-cdk-lib/core/lib/stack.ts +++ b/packages/aws-cdk-lib/core/lib/stack.ts @@ -1298,7 +1298,7 @@ export class Stack extends Construct implements ITaggable { if (this.templateOptions.transform) { // eslint-disable-next-line max-len - Annotations.of(this).addWarning('This stack is using the deprecated `templateOptions.transform` property. Consider switching to `addTransform()`.'); + Annotations.of(this).addWarningV2('Core:Stack:DeprecatedTransform', 'This stack is using the deprecated `templateOptions.transform` property. Consider switching to `addTransform()`.'); this.addTransform(this.templateOptions.transform); } diff --git a/packages/aws-cdk-lib/core/test/annotations.test.ts b/packages/aws-cdk-lib/core/test/annotations.test.ts index 71ccfa987b30b..df9bb332fe456 100644 --- a/packages/aws-cdk-lib/core/test/annotations.test.ts +++ b/packages/aws-cdk-lib/core/test/annotations.test.ts @@ -24,7 +24,7 @@ describe('annotations', () => { expect(getWarnings(app.synth())).toEqual([ { path: '/MyStack/Hello', - message: 'The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release', + message: 'Deprecated:@aws-cdk/core.Construct.node: The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release', }, ]); }); @@ -51,15 +51,15 @@ describe('annotations', () => { expect(getWarnings(app.synth())).toEqual([ { path: '/MyStack1/Hello', - message: 'The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release', + message: 'Deprecated:@aws-cdk/core.Construct.node: The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release', }, { path: '/MyStack1/World', - message: 'The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release', + message: 'Deprecated:@aws-cdk/core.Construct.node: The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release', }, { path: '/MyStack2/FooBar', - message: 'The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release', + message: 'Deprecated:@aws-cdk/core.Construct.node: The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release', }, ]); }); @@ -79,17 +79,17 @@ describe('annotations', () => { const app = new App(); const stack = new Stack(app, 'S1'); const c1 = new Construct(stack, 'C1'); - Annotations.of(c1).addWarning('You should know this!'); - Annotations.of(c1).addWarning('You should know this!'); - Annotations.of(c1).addWarning('You should know this!'); - Annotations.of(c1).addWarning('You should know this, too!'); + Annotations.of(c1).addWarningV2('warning1', 'You should know this!'); + Annotations.of(c1).addWarningV2('warning1', 'You should know this!'); + Annotations.of(c1).addWarningV2('warning1', 'You should know this!'); + Annotations.of(c1).addWarningV2('warning2', 'You should know this, too!'); expect(getWarnings(app.synth())).toEqual([{ path: '/S1/C1', - message: 'You should know this!', + message: 'warning1: You should know this!', }, { path: '/S1/C1', - message: 'You should know this, too!', + message: 'warning2: You should know this, too!', }], ); }); diff --git a/packages/aws-cdk-lib/core/test/app.test.ts b/packages/aws-cdk-lib/core/test/app.test.ts index b647c08c67d99..090bf28223a61 100644 --- a/packages/aws-cdk-lib/core/test/app.test.ts +++ b/packages/aws-cdk-lib/core/test/app.test.ts @@ -35,8 +35,8 @@ function synth(context?: { [key: string]: any }): cxapi.CloudAssembly { // add some metadata stack1.node.addMetadata('meta', 111); - Annotations.of(r2).addWarning('warning1'); - Annotations.of(r2).addWarning('warning2'); + Annotations.of(r2).addWarningV2('warning1', 'warning1'); + Annotations.of(r2).addWarningV2('warning2', 'warning2'); c1.node.addMetadata('meta', { key: 'value' }); app.node.addMetadata('applevel', 123); // apps can also have metadata }); @@ -78,8 +78,8 @@ describe('app', () => { '/stack1/s1c1': [{ type: 'aws:cdk:logicalId', data: 's1c1' }], '/stack1/s1c2': [{ type: 'aws:cdk:logicalId', data: 's1c2' }, - { type: 'aws:cdk:warning', data: 'warning1' }, - { type: 'aws:cdk:warning', data: 'warning2' }], + { type: 'aws:cdk:warning', data: { id: 'warning1', message: 'warning1', scope: 'stack1/s1c2' } }, + { type: 'aws:cdk:warning', data: { id: 'warning2', message: 'warning2', scope: 'stack1/s1c2' } }], }); const stack2 = response.stacks[1]; diff --git a/packages/aws-cdk-lib/core/test/aspect.test.ts b/packages/aws-cdk-lib/core/test/aspect.test.ts index bc716e5843ac1..4fc5a8c4faedd 100644 --- a/packages/aws-cdk-lib/core/test/aspect.test.ts +++ b/packages/aws-cdk-lib/core/test/aspect.test.ts @@ -50,7 +50,7 @@ describe('aspect', () => { }); app.synth(); expect(root.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(root.node.metadata[0].data).toEqual('We detected an Aspect was added via another Aspect, and will not be applied'); + expect(root.node.metadata[0].data).toEqual({ id: 'Core:IgnoredAspect', message: 'We detected an Aspect was added via another Aspect, and will not be applied', scope: 'MyConstruct' }); // warning is not added to child construct expect(child.node.metadata.length).toEqual(0); }); diff --git a/packages/aws-cdk-lib/core/test/cfn-resource.test.ts b/packages/aws-cdk-lib/core/test/cfn-resource.test.ts index 7d6d08787c6fe..e20d256694ce1 100644 --- a/packages/aws-cdk-lib/core/test/cfn-resource.test.ts +++ b/packages/aws-cdk-lib/core/test/cfn-resource.test.ts @@ -119,7 +119,7 @@ describe('cfn resource', () => { expect(getWarnings(app.synth())).toEqual([ { path: '/Default/Resource', - message: 'AWS::Lambda::Function does not support snapshot removal policy. This policy will be ignored.', + message: 'Core:CfnResource:AWS::Lambda::FunctionSnapshotRemovalPolicyIgnored: AWS::Lambda::Function does not support snapshot removal policy. This policy will be ignored.', }, ]); }); diff --git a/packages/aws-cdk-lib/core/test/construct.test.ts b/packages/aws-cdk-lib/core/test/construct.test.ts index 2dd6c299fe2ec..943c3e1be7a36 100644 --- a/packages/aws-cdk-lib/core/test/construct.test.ts +++ b/packages/aws-cdk-lib/core/test/construct.test.ts @@ -249,11 +249,11 @@ describe('construct', () => { const previousValue = reEnableStackTraceCollection(); const root = new Root(); const con = new Construct(root, 'MyConstruct'); - Annotations.of(con).addWarning('This construct is deprecated, use the other one instead'); + Annotations.of(con).addWarningV2('WARNING1', 'This construct is deprecated, use the other one instead'); restoreStackTraceColection(previousValue); expect(con.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(con.node.metadata[0].data).toEqual('This construct is deprecated, use the other one instead'); + expect(con.node.metadata[0].data.message).toEqual('This construct is deprecated, use the other one instead'); expect(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0).toEqual(true); }); diff --git a/packages/aws-cdk-lib/core/test/private/tree-metadata.test.ts b/packages/aws-cdk-lib/core/test/private/tree-metadata.test.ts index 482cc6301b833..0a9770e59be22 100644 --- a/packages/aws-cdk-lib/core/test/private/tree-metadata.test.ts +++ b/packages/aws-cdk-lib/core/test/private/tree-metadata.test.ts @@ -369,8 +369,8 @@ describe('tree metadata', () => { const warn = treenode.node.metadata.find((md) => { return md.type === cxschema.ArtifactMetadataEntryType.WARN - && /Forcing an inspect error/.test(md.data as string) - && /mycfnresource/.test(md.data as string); + && /Forcing an inspect error/.test(md.data.message as string) + && /mycfnresource/.test(md.data.message as string); }); expect(warn).toBeDefined(); diff --git a/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts b/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts index 7a7ca269dc8e9..3de7d55d75b99 100644 --- a/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts +++ b/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts @@ -449,7 +449,7 @@ export class AwsCustomResource extends Construct implements iam.IGrantable { if (installLatestAwsSdk && props.installLatestAwsSdk === undefined) { // This is dangerous. Add a warning. - Annotations.of(this).addWarning([ + Annotations.of(this).addWarningV2('CustomResources:AwsCustomResource:InstallLatestAwsSdkNotSpecified', [ 'installLatestAwsSdk was not specified, and defaults to true. You probably do not want this.', `Set the global context flag \'${cxapi.AWS_CUSTOM_RESOURCE_LATEST_SDK_DEFAULT}\' to false to switch this behavior off project-wide,`, 'or set the property explicitly to true if you know you need to call APIs that are not in Lambda\'s built-in SDK version.', diff --git a/packages/aws-cdk-lib/pipelines/lib/legacy/pipeline.ts b/packages/aws-cdk-lib/pipelines/lib/legacy/pipeline.ts index b1f23e2e22788..5deecd12bf890 100644 --- a/packages/aws-cdk-lib/pipelines/lib/legacy/pipeline.ts +++ b/packages/aws-cdk-lib/pipelines/lib/legacy/pipeline.ts @@ -437,7 +437,7 @@ export class CdkPipeline extends Construct { const depAction = stackActions.find(s => s.stackArtifactId === depId); if (depAction === undefined) { - Annotations.of(this).addWarning(`Stack '${stackAction.stackName}' depends on stack ` + + Annotations.of(this).addWarningV2('Pipelines:Pipeline:DependencyOnNonPipelineStack', `Stack '${stackAction.stackName}' depends on stack ` + `'${depId}', but that dependency is not deployed through the pipeline!`); } else if (!(depAction.executeRunOrder < stackAction.prepareRunOrder)) { yield `Stack '${stackAction.stackName}' depends on stack ` + From 5229dc93c94ad5a338c1a11857afee8253465825 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:57:10 +0000 Subject: [PATCH 04/26] updated readme --- packages/aws-cdk-lib/core/README.md | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/aws-cdk-lib/core/README.md b/packages/aws-cdk-lib/core/README.md index be2b158e771d1..9670e0bf22caf 100644 --- a/packages/aws-cdk-lib/core/README.md +++ b/packages/aws-cdk-lib/core/README.md @@ -1413,4 +1413,40 @@ add it to the `postinstall` [script](https://docs.npmjs.com/cli/v9/using-npm/scripts) in the `package.json` file. +## Annotations + +Construct authors can add annotations to constructs to report at three different +levels: `ERROR`, `WARN`, `INFO`. + +Typically warnings are added for things that are important for the user to be +aware of, but will not cause deployment errors in all cases. Some common +scenarios are (non-exhaustive list): + +- Warn when the user needs to take a manual action, e.g. IAM policy should be + added to an referenced resource. +- Warn if the user configuration might not follow best practices (but is still + valid) +- Warn if the user is using a deprecated API + +### Acknowledging Warnings + +If you would like to run with `--strict` mode enabled (warnings will throw +errors) it is possible to `acknowledge` warnings to make the warning go away. + +For example, if > 10 IAM managed policies are added to an IAM Group, a warning +will be created: + +``` +IAM:Group:MaxPoliciesExceeded: You added 11 to IAM Group my-group. The maximum number of managed policies attached to an IAM group is 10. +``` + +If you have requested a [quota increase](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-quotas.html#reference_iam-quotas-entities) +you may have the ability to add > 10 managed policies which means that this +warning does not apply to you. You can acknowledge this by `acknowledging` the +warning by the `id`. + +```ts +Annotations.of(this).acknowledgeWarning('IAM:Group:MaxPoliciesExceeded', 'Account has quota increased to 20'); +``` + From bb217e33fdc5776fc467995c8fc363c31906d6b2 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Thu, 29 Jun 2023 14:52:56 +0000 Subject: [PATCH 05/26] fixing lint errors --- .../aws-cdk-lib/aws-cloudwatch/lib/widget.ts | 13 +++++------ .../lib/cloud-assembly/metadata-schema.ts | 22 +++++++++---------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/packages/aws-cdk-lib/aws-cloudwatch/lib/widget.ts b/packages/aws-cdk-lib/aws-cloudwatch/lib/widget.ts index f464c712da11f..c4d2eb0b22f37 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/lib/widget.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/lib/widget.ts @@ -53,7 +53,7 @@ export abstract class ConcreteWidget implements IWidget { protected y?: number; public readonly warnings: string[] | undefined = []; - public warningsV2: { [id: string]: string } | undefined = {}; + public readonly warningsV2: { [id: string]: string } | undefined = {}; constructor(width: number, height: number) { this.width = width; @@ -75,11 +75,10 @@ export abstract class ConcreteWidget implements IWidget { * Copy the warnings from the given metric */ protected copyMetricWarnings(...ms: IMetric[]) { - this.warningsV2 = ms.reduce((prev, curr) => { - return { - ...prev, - ...curr.warningsV2, - }; - }, this.warningsV2); + ms.forEach(m => { + for (const [id, message] of Object.entries(m.warningsV2 ?? {})) { + this.warningsV2![id] = message; + } + }); } } diff --git a/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts b/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts index a16c7425403fb..442daa33dc110 100644 --- a/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts +++ b/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts @@ -228,21 +228,21 @@ export type LogMessageMetadataEntry = string; * @see ArtifactMetadataEntryType.WARN * @see ArtifactMetadataEntryType.ERROR */ -export type LogMessageObjectMetadataEntry = { +export interface LogMessageObjectMetadataEntry { /** * The message id */ - id: string; + readonly id: string; /** * The scope the message applies to */ - scope: string; + readonly scope: string; /** * The log message */ - message: string; + readonly message: string; }; /** @@ -258,33 +258,33 @@ export type StackTagsMetadataEntry = Tag[]; /** * @see ArtifactMetadataEntryType.ACKNOWLEDGE */ -export type AcknowledgementMetadataEntry = { +export interface AcknowledgementMetadataEntry { /** * The message id that is acknowledged */ - id: string; + readonly id: string; /** * The list of scopes that this acknowledgement should apply to */ - scopes: string[]; + readonly scopes: string[]; /** * The acknowledgement message */ - message?: string; + readonly message?: string; }; /** * Union type for all metadata entries that might exist in the manifest. */ export type MetadataEntryData = + LogMessageObjectMetadataEntry | + AcknowledgementMetadataEntry | AssetMetadataEntry | LogMessageMetadataEntry | LogicalIdMetadataEntry | - StackTagsMetadataEntry | - LogMessageObjectMetadataEntry | - AcknowledgementMetadataEntry; + StackTagsMetadataEntry; /** * Type of artifact metadata entry. From 9e192077789e6dceeabe222a7e0c823afaf27fa9 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Thu, 29 Jun 2023 16:35:13 +0000 Subject: [PATCH 06/26] fixing tests --- .../test/application-associator.test.ts | 8 ++++---- .../test/application.test.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application-associator.test.ts b/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application-associator.test.ts index 4e975579d062f..4a66f1b52c6ba 100644 --- a/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application-associator.test.ts +++ b/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application-associator.test.ts @@ -191,7 +191,7 @@ describe('Scope based Associations with Application with Cross Region/Account', const crossAccountStack = new cdk.Stack(app, 'crossRegionStack', { env: { account: 'account', region: 'region' }, }); - Annotations.fromStack(crossAccountStack).hasWarning('*', 'Cross-account stack detected but application sharing and association will be skipped because cross-account option is not enabled.'); + Annotations.fromStack(crossAccountStack).hasWarning('*', 'SCAppRegistry:AssociationSkipped: Cross-account stack detected but application sharing and association will be skipped because cross-account option is not enabled.'); }); test('ApplicationAssociator with cross account stacks inside cdkApp does not give warning if associateCrossAccountStacks is set to true', () => { @@ -222,7 +222,7 @@ describe('Scope based Associations with Application with Cross Region/Account', const crossRegionStack = new cdk.Stack(app, 'crossRegionStack', { env: { account: 'account', region: 'region' }, }); - Annotations.fromStack(crossRegionStack).hasWarning('*', 'AppRegistry does not support cross region associations, deployment might fail if there is cross region stacks in the app.' + Annotations.fromStack(crossRegionStack).hasWarning('*', 'SCAppRegistry:CrossRegionAssociation: AppRegistry does not support cross region associations, deployment might fail if there is cross region stacks in the app.' + ' Application region region2, stack region region'); }); @@ -237,7 +237,7 @@ describe('Scope based Associations with Application with Cross Region/Account', const crossRegionStack = new cdk.Stack(app, 'crossRegionStack', { env: { account: 'account', region: 'region' }, }); - 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.'); + Annotations.fromStack(crossRegionStack).hasWarning('*', 'SCAppRegistry:EnvironmentAgnosticStack: Environment agnostic stack determined, AppRegistry association might not work as expected in case you deploy cross-region or cross-account stack.'); }); 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', }); app.synth(); Annotations.fromStack(pipelineStack).hasWarning('*', - 'Associate Stage: SampleStage to ensure all stacks in your cdk app are associated with AppRegistry. You can use ApplicationAssociator.associateStage to associate any stage.'); + 'SCAppRegistry:StackNotAssociated: Associate Stage: SampleStage to ensure all stacks in your cdk app are associated with AppRegistry. You can use ApplicationAssociator.associateStage to associate any stage.'); }); test('Cdk App Containing Pipeline with stage and stage associated successfully gets synthesized', () => { diff --git a/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application.test.ts b/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application.test.ts index 92b17ea416764..ce8b7cd68bc39 100644 --- a/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application.test.ts +++ b/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application.test.ts @@ -511,7 +511,7 @@ describe('Scope based Associations with Application with Cross Region/Account', const stageStack = new cdk.Stack(stage, 'MyStack'); application.associateAllStacksInScope(stage); Annotations.fromStack(stageStack).hasWarning('*', - 'AppRegistry does not support cross region associations, deployment might fail if there is cross region stacks in the app.' + 'SCAppRegistry:CrossRegionAssociation: AppRegistry does not support cross region associations, deployment might fail if there is cross region stacks in the app.' + ' Application region region, stack region region1'); }); }); From 4b8ad2821b34e941ec9a2aeb328fc4a44d398347 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:13:12 +0000 Subject: [PATCH 07/26] adding to breaking change --- allowed-breaking-changes.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index 7568e050d7ecd..e9b5b0ea26143 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -164,3 +164,7 @@ removed:aws-cdk-lib.triggers.TriggerProps.timeout change-return-type:aws-cdk-lib.aws_ec2.SecurityGroup.determineRuleScope # broken only in non-JS/TS, where that was not previously usable strengthened:aws-cdk-lib.aws_s3_deployment.BucketDeploymentProps + +# weakened added additional types to a union type, meaning all new props are optional +weakened:aws-cdk-lib.cloud_assembly_schema.MetadataEntry +weakened:aws-cdk-lib.cx_api.MetadataEntryResult From eb244d84a72ec1db2116fc2acd8ae44684c42ed3 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Thu, 29 Jun 2023 19:00:45 +0000 Subject: [PATCH 08/26] couple more --- allowed-breaking-changes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index e9b5b0ea26143..8ad2776d210df 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -168,3 +168,5 @@ strengthened:aws-cdk-lib.aws_s3_deployment.BucketDeploymentProps # weakened added additional types to a union type, meaning all new props are optional weakened:aws-cdk-lib.cloud_assembly_schema.MetadataEntry weakened:aws-cdk-lib.cx_api.MetadataEntryResult +weakened:@aws-cdk/cloud-assembly-schema.MetadataEntry +weakened:@aws-cdk/cx-api.MetadataEntryResult From 54a2910b7dd3a3d8937661bfb9d17712216aed50 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Fri, 30 Jun 2023 09:57:48 +0000 Subject: [PATCH 09/26] adding examples --- packages/aws-cdk-lib/core/lib/annotations.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/core/lib/annotations.ts b/packages/aws-cdk-lib/core/lib/annotations.ts index 93d967145657c..e608f04dd96ed 100644 --- a/packages/aws-cdk-lib/core/lib/annotations.ts +++ b/packages/aws-cdk-lib/core/lib/annotations.ts @@ -32,9 +32,10 @@ export class Annotations { * * @example * declare const stack: Stack; - * Annotations.of(stack).acknowledgeWarning('SomeWarningId'); + * Annotations.of(stack).acknowledgeWarning('SomeWarningId', 'This warning can be ignored because...'); * * @param id - the id of the warning message to acknowledge + * @param message optional message to explain the reason for acknowledgement */ public acknowledgeWarning(id: string, message?: string): void { const scopes = this.scope.node.findAll().map(child => child.node.path); @@ -51,6 +52,10 @@ export class Annotations { * The CLI will display the warning when an app is synthesized, or fail if run * in --strict mode. * + * @example + * declare const construct: Construct; + * Annotations.of(construct).addWarningV2('Library:Construct:ThisIsAWarning', 'Some message explaining the warning'); + * * @param id the unique identifier for the warning. This can be used to acknowledge the warning * @param message The warning message. */ From 06f8559b2ec2eaa292b7c053a391daf51e037252 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Wed, 5 Jul 2023 14:32:58 +0000 Subject: [PATCH 10/26] adding adr --- .../core/lib/adr/acknowledge-warnings.md | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 packages/aws-cdk-lib/core/lib/adr/acknowledge-warnings.md diff --git a/packages/aws-cdk-lib/core/lib/adr/acknowledge-warnings.md b/packages/aws-cdk-lib/core/lib/adr/acknowledge-warnings.md new file mode 100644 index 0000000000000..49c9a27d8bb4a --- /dev/null +++ b/packages/aws-cdk-lib/core/lib/adr/acknowledge-warnings.md @@ -0,0 +1,181 @@ +# Acknowledge Warnings + +## Status + +accepted + +## Context + +Construct authors can add messages that will be printed at synthesis. It +possible to add three types of warnings: + +- `INFO`: Prints the info on synthesis +- `WARN`: Prints the warning on synthesis. Can also throw an error if the `--strict` + argument is used +- `ERROR`: Throws an error with the message + +Warning messages are typically used for two types of messages: + +1. Things that are errors (and will error on deployment) in some cases, but not all. For example, a warning is + used when > 10 managed policies are added to an IAM Role because that is the + initial limit for AWS accounts. An error is not used because it is possible + to increase the limit. +2. Things that are not errors (and will deploy successfully), but might be + sub-optimal configuration. For example, a warning is used when an Serverless + V2 Aurora cluster is created and no reader instances are created in tiers + 0-1. This is a valid configuration, but could cause availability issues in + certain scenarios. + +Users who are developing CDK applications may want to always use the `--strict` +CLI argument to turn all warnings into errors. Currently this is probably not +possible since as soon as one warning is not applicable they can no longer use +`--strict`. Ideally they should be able to `acknowledge` warnings (similar to +what they can do with notices) indicating that the warning is not applicable to +them/they acknowledge the risk. + +```ts +Annotations.of(this).acknowledgeWarning( + '@aws-cdk/aws-iam:maxPoliciesExceeded', + 'A limit increase has been submitted', +); +``` + +This would allow acknowledgements to live alongside the code so all developers +on the code base would have the information readily available. This also allows +the acknowledgement to be added at a certain `scope` and apply to all child +constructs. Users would be able to acknowledge certain warnings for the entire +app, or for a specific scope. + +## Constraints + +Warnings are currently added with the `Annotations` API. + +```ts +Annotations.of(scope).addWarning('This is a warning'); +``` + +`Annotations.of(scope)` creates a new instance of `Annotations` every time so +there is no way to keep track of warnings added. For example, doing something +like this would not work because the `Annotations` instance created with +`acknowledgeWarning` would be different than the one created with `addWarning`. + +```ts +Annotations.of(scope).addWarning('This is a warning'); +Annotations.of(scope).acknowledgeWarning('This is a warning'); +``` + +Currently the storage mechanism for `Annotations` is the `Node` metadata. The +warning is added to the node as node metadata which is read by the CLI after +synthesis to print out the messages. This means that Annotations can be added +during `synthesis`. + +Another constraint is that currently you add a warning with only the message. +There is no unique identifier. This means that to add an acknowledgement the +user would need to copy the entire message. + +## Decision + +We will deprecate the `addWarning` method and add a new method `addWarningV2` + +```ts + /** + * @param id the unique identifier for the warning. This can be used to acknowledge the warning + * @param message The warning message. + */ + public addWarningV2(id: string, message: string): void +``` + +We will add a new method `acknowledgeWarning` that will allow users to +acknowledge a specific warning by `id`. + +```ts + /** + * @param id - the id of the warning message to acknowledge + * @param message optional message to explain the reason for acknowledgement + */ + public acknowledgeWarning(id: string, message?: string): void +``` + +We will continue to use the node metadata as the storage mechanism and will add +a new metadata type `aws:cdk:acknowledge` to store information on +acknowledgements. + +At synthesis when we collect all of the annotation messages, we will filter out +any messages that have an acknowledgement. + +Storing the acknowledgements in the metadata will also allow us to report on +warnings that were acknowledged by the user (info will be stored in the +assembly). + +## Alternatives + +### Alternative storage mechanism + +One alternative that was considered was to implement some alternative +intermediary storage mechanism. This storage mechanism would allow storing all +warnings and acknowledgements in a special construct that was created once per +app. It would look something like this (pseudo code) + +```ts +class AnnotationManager extends Construct { + private constructor(scope: Construct) { + attachCustomSynthesis(this, { + onSynthesize: () => { + this.warnings.forEach(warning => node.addMetadata(...)) + } + } + } + + public addWarning() { + if (!this.acks.has()) { + this.warnings.set(); + } + } + public ack() { + if (this.warnings.has()) { + this.warnings.delete(); + } + this.acks.add(); + } +} +``` + +The problem with this method is represented by the `attachCustomSynthesis` in +the example. This same applies for if we used `addValidation` or `Aspects`. +Annotations can be added _after_ that which means they would not be added or +acknowledged. + +### Use context and remove metadata + +Another alternative that was considered was to use context to set +acknowledgements. This would look something like this: + +```ts +public acknowledgeWarning(id: string, message?: string) { + this.scope.node.setContext(id, message ?? true); // can't do this today + this.scope.node.removeMetadata(id); // this method does not exist +} +public addWarningV2(id: string, message: string) { + if (this.scope.node.tryGetContext(id) === undefined) { + this.addMessage(...); + } +} +``` + +There are two issues with this alternative. + +1. It is currently not possible to `node.setContext` once children are added. We + would have to first remove that limitation. I think this could lead to a lot + of issues where users have to keep track of _when_ context is added. +2. We would have to implement the ability to `removeMetadata` from a node. This + functionality doesn't make much sense outside of this context (can't find + where anybody has asked for it). It also may require updating the metadata + types since currently there is no unique identifier for a given metadata + entry (other than the message). + +## Consequences + +With the recommended solution the only major consequence is that it requires +updating the `metadata-schema`, but we can do this in a non-breaking way +(addition of new types). The alternatives may also require changes to the schema +as well. From 4af11359bbf3e7f9f021c2512a6ef2ed5393b8fc Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Wed, 5 Jul 2023 14:41:16 +0000 Subject: [PATCH 11/26] formatting the messages --- .../aws-gamelift-alpha/lib/fleet-base.ts | 2 +- .../lib/aspects/stack-associator.ts | 2 +- .../lib/schedule.ts | 2 +- .../lib/aspects/require-imdsv2-aspect.ts | 2 +- .../aws-autoscaling/lib/auto-scaling-group.ts | 4 ++-- .../aws-autoscaling/lib/schedule.ts | 2 +- .../test/auto-scaling-group.test.ts | 4 ++-- .../test/scheduled-action.test.ts | 2 +- .../aws-dynamodb/test/dynamodb.test.ts | 2 +- .../lib/aspects/require-imdsv2-aspect.ts | 2 +- .../aws-ec2/lib/private/ebs-util.ts | 2 +- .../aws-cdk-lib/aws-ec2/lib/security-group.ts | 4 ++-- packages/aws-cdk-lib/aws-ec2/lib/vpc.ts | 6 +++--- .../aws-cdk-lib/aws-ec2/test/instance.test.ts | 4 ++-- packages/aws-cdk-lib/aws-ec2/test/vpc.test.ts | 2 +- .../aws-ecr-assets/lib/image-asset.ts | 2 +- .../aws-cdk-lib/aws-ecr/lib/repository.ts | 2 +- .../aws-ecr/test/repository.test.ts | 4 ++-- .../test/ec2/scheduled-ecs-task.test.ts | 2 +- .../fargate/scheduled-fargate-task.test.ts | 2 +- .../aws-ecs/lib/base/base-service.ts | 2 +- .../aws-ecs/lib/images/repository.ts | 2 +- .../aws-ecs/test/ec2/ec2-service.test.ts | 10 ++++++---- .../test/ec2/ec2-task-definition.test.ts | 4 ++-- .../test/external/external-service.test.ts | 8 +++++--- .../external/external-task-definition.test.ts | 2 +- .../test/fargate/fargate-service.test.ts | 10 ++++++---- packages/aws-cdk-lib/aws-eks/lib/cluster.ts | 6 +++--- .../aws-eks/lib/fargate-profile.ts | 2 +- .../aws-eks/lib/managed-nodegroup.ts | 2 +- .../aws-cdk-lib/aws-eks/test/cluster.test.ts | 3 +-- .../lib/alb/application-listener-rule.ts | 2 +- .../lib/alb/application-target-group.ts | 4 ++-- .../lib/shared/base-listener.ts | 2 +- .../lib/shared/base-target-group.ts | 2 +- .../aws-events-targets/lib/aws-api.ts | 2 +- .../aws-events-targets/lib/ecs-task.ts | 2 +- .../aws-events-targets/lib/util.ts | 2 +- .../test/aws-api/aws-api.test.ts | 2 +- .../test/lambda/lambda.test.ts | 8 +++++++- packages/aws-cdk-lib/aws-events/lib/rule.ts | 2 +- .../aws-cdk-lib/aws-events/lib/schedule.ts | 2 +- .../aws-cdk-lib/aws-events/test/rule.test.ts | 2 +- packages/aws-cdk-lib/aws-iam/lib/group.ts | 2 +- .../aws-iam/lib/private/imported-role.ts | 2 +- packages/aws-cdk-lib/aws-iam/lib/role.ts | 4 ++-- .../aws-iam/lib/unknown-principal.ts | 2 +- .../aws-cdk-lib/aws-iam/test/group.test.ts | 4 ++-- .../aws-lambda-event-sources/lib/sqs.ts | 2 +- .../aws-lambda/lib/function-base.ts | 2 +- .../aws-cdk-lib/aws-lambda/test/alias.test.ts | 2 +- packages/aws-cdk-lib/aws-rds/lib/cluster.ts | 12 ++++++------ .../aws-cdk-lib/aws-rds/test/cluster.test.ts | 19 +++++++++---------- .../aws-s3-notifications/lib/sqs.ts | 2 +- .../aws-s3-notifications/test/queue.test.ts | 4 ++-- packages/aws-cdk-lib/aws-s3/lib/bucket.ts | 2 +- .../lib/private/product-stack-synthesizer.ts | 2 +- .../aws-cdk-lib/aws-ses-actions/lib/lambda.ts | 2 +- .../aws-cdk-lib/aws-ses-actions/lib/s3.ts | 2 +- packages/aws-cdk-lib/core/lib/cfn-resource.ts | 2 +- .../aws-cdk-lib/core/lib/private/synthesis.ts | 2 +- .../core/lib/private/tree-metadata.ts | 2 +- packages/aws-cdk-lib/core/lib/stack.ts | 2 +- packages/aws-cdk-lib/core/test/app.test.ts | 4 ++-- packages/aws-cdk-lib/core/test/aspect.test.ts | 2 +- .../core/test/cfn-resource.test.ts | 2 +- .../aws-cdk-lib/core/test/construct.test.ts | 7 ++++--- .../aws-custom-resource.ts | 2 +- .../pipelines/lib/legacy/pipeline.ts | 2 +- 69 files changed, 120 insertions(+), 109 deletions(-) diff --git a/packages/@aws-cdk/aws-gamelift-alpha/lib/fleet-base.ts b/packages/@aws-cdk/aws-gamelift-alpha/lib/fleet-base.ts index ae602b4307b0b..e1e8fd7dcca94 100644 --- a/packages/@aws-cdk/aws-gamelift-alpha/lib/fleet-base.ts +++ b/packages/@aws-cdk/aws-gamelift-alpha/lib/fleet-base.ts @@ -639,7 +639,7 @@ export abstract class FleetBase extends cdk.Resource implements IFleet { } protected warnVpcPeeringAuthorizations(scope: Construct): void { - cdk.Annotations.of(scope).addWarningV2('Gamelift:Fleet:AutorizeVpcPeering', [ + cdk.Annotations.of(scope).addWarningV2('@aws-cdk/aws-gamelift:fleetAutorizeVpcPeering', [ 'To authorize the VPC peering, call the GameLift service API CreateVpcPeeringAuthorization() or use the AWS CLI command create-vpc-peering-authorization.', 'Make this call using the account that manages your non-GameLift resources.', 'See: https://docs.aws.amazon.com/gamelift/latest/developerguide/vpc-peering.html', diff --git a/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/lib/aspects/stack-associator.ts b/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/lib/aspects/stack-associator.ts index d4dc56f121854..3a4a8248b6ce9 100644 --- a/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/lib/aspects/stack-associator.ts +++ b/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/lib/aspects/stack-associator.ts @@ -74,7 +74,7 @@ abstract class StackAssociatorBase implements IAspect { * @param message The error message. */ private warning(id: string, node: IConstruct, message: string): void { - Annotations.of(node).addWarningV2(`SCAppRegistry:${id}`, message); + Annotations.of(node).addWarningV2(`@aws-cdk/servicecatalogappregistry:${id}`, message); } /** diff --git a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts index dabbfafb584ee..82c73291993ed 100644 --- a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts +++ b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts @@ -63,7 +63,7 @@ export abstract class Schedule { public readonly expressionString: string = `cron(${minute} ${hour} ${day} ${month} ${weekDay} ${year})`; public _bind(scope: Construct) { if (!options.minute) { - Annotations.of(scope).addWarningV2('AppAutoScaling:Schedule: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.'); + 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.'); } return new LiteralSchedule(this.expressionString); } diff --git a/packages/aws-cdk-lib/aws-autoscaling/lib/aspects/require-imdsv2-aspect.ts b/packages/aws-cdk-lib/aws-autoscaling/lib/aspects/require-imdsv2-aspect.ts index 8fd47750c3518..17389e2f34d20 100644 --- a/packages/aws-cdk-lib/aws-autoscaling/lib/aspects/require-imdsv2-aspect.ts +++ b/packages/aws-cdk-lib/aws-autoscaling/lib/aspects/require-imdsv2-aspect.ts @@ -34,6 +34,6 @@ export class AutoScalingGroupRequireImdsv2Aspect implements cdk.IAspect { * @param message The warning message. */ protected warn(node: IConstruct, message: string) { - cdk.Annotations.of(node).addWarningV2(`AutoScaling:Imdsv2:${AutoScalingGroupRequireImdsv2Aspect.name}`, `${AutoScalingGroupRequireImdsv2Aspect.name} failed on node ${node.node.id}: ${message}`); + cdk.Annotations.of(node).addWarningV2(`@aws-cdk/aws-autoscaling:imdsv2${AutoScalingGroupRequireImdsv2Aspect.name}`, `${AutoScalingGroupRequireImdsv2Aspect.name} failed on node ${node.node.id}: ${message}`); } } diff --git a/packages/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.ts b/packages/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.ts index e6cde69aef530..600a53c14107b 100644 --- a/packages/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.ts @@ -1353,7 +1353,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements }); if (desiredCapacity !== undefined) { - Annotations.of(this).addWarningV2('AutoScaling:Group: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'); + 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'); } this.maxInstanceLifetime = props.maxInstanceLifetime; @@ -2266,7 +2266,7 @@ function synthesizeBlockDeviceMappings(construct: Construct, blockDevices: Block throw new Error('iops property is required with volumeType: EbsDeviceVolumeType.IO1'); } } else if (volumeType !== EbsDeviceVolumeType.IO1) { - Annotations.of(construct).addWarningV2('AutoScaling:Group:IopsIgnored', 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + Annotations.of(construct).addWarningV2('@aws-cdk/aws-autoscaling:iopsIgnored', 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); } } diff --git a/packages/aws-cdk-lib/aws-autoscaling/lib/schedule.ts b/packages/aws-cdk-lib/aws-autoscaling/lib/schedule.ts index c63d1e5f993eb..7f532e65ac62a 100644 --- a/packages/aws-cdk-lib/aws-autoscaling/lib/schedule.ts +++ b/packages/aws-cdk-lib/aws-autoscaling/lib/schedule.ts @@ -33,7 +33,7 @@ export abstract class Schedule { public readonly expressionString: string = `${minute} ${hour} ${day} ${month} ${weekDay}`; public _bind(scope: Construct) { if (!options.minute) { - Annotations.of(scope).addWarningV2('AutoScaling:Schedule:DefaultRunsEveryMinute', '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.'); + 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.'); } return new LiteralSchedule(this.expressionString); } diff --git a/packages/aws-cdk-lib/aws-autoscaling/test/auto-scaling-group.test.ts b/packages/aws-cdk-lib/aws-autoscaling/test/auto-scaling-group.test.ts index a3f714e646691..e139a653305b0 100644 --- a/packages/aws-cdk-lib/aws-autoscaling/test/auto-scaling-group.test.ts +++ b/packages/aws-cdk-lib/aws-autoscaling/test/auto-scaling-group.test.ts @@ -1025,7 +1025,7 @@ describe('auto scaling group', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/MyStack', 'AutoScaling:Group:IopsIgnored: iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + Annotations.fromStack(stack).hasWarning('/Default/MyStack', 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1 [ack: @aws-cdk/aws-autoscaling:iopsIgnored]'); }); test('warning if iops and volumeType !== IO1', () => { @@ -1049,7 +1049,7 @@ describe('auto scaling group', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/MyStack', 'AutoScaling:Group:IopsIgnored: iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + Annotations.fromStack(stack).hasWarning('/Default/MyStack', 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1 [ack: @aws-cdk/aws-autoscaling:iopsIgnored]'); }); test('step scaling on metric', () => { diff --git a/packages/aws-cdk-lib/aws-autoscaling/test/scheduled-action.test.ts b/packages/aws-cdk-lib/aws-autoscaling/test/scheduled-action.test.ts index c94ce1c23eb32..943939c09febb 100644 --- a/packages/aws-cdk-lib/aws-autoscaling/test/scheduled-action.test.ts +++ b/packages/aws-cdk-lib/aws-autoscaling/test/scheduled-action.test.ts @@ -133,7 +133,7 @@ describeDeprecated('scheduled action', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/ASG/ScheduledActionScaleOutInTheMorning', "AutoScaling:Schedule:DefaultRunsEveryMinute: 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."); + 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]"); }); test('scheduled scaling shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts b/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts index 19f8bf790562c..edab404c22f61 100644 --- a/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts +++ b/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts @@ -1548,7 +1548,7 @@ test('scheduled scaling shows warning when minute is not defined in cron', () => }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/MyTable/ReadScaling/Target', "AppAutoScaling:Schedule: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."); + Annotations.fromStack(stack).hasWarning('/Default/MyTable/ReadScaling/Target', "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-applicationautoscaling:defaultRunEveryMinute]"); }); test('scheduled scaling shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-ec2/lib/aspects/require-imdsv2-aspect.ts b/packages/aws-cdk-lib/aws-ec2/lib/aspects/require-imdsv2-aspect.ts index 118287cdd7eef..4710db22d13e2 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/aspects/require-imdsv2-aspect.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/aspects/require-imdsv2-aspect.ts @@ -37,7 +37,7 @@ abstract class RequireImdsv2Aspect implements cdk.IAspect { */ protected warn(node: IConstruct, message: string) { if (this.suppressWarnings !== true) { - cdk.Annotations.of(node).addWarningV2(`EC2:Imdsv2:${RequireImdsv2Aspect.name}`, `${RequireImdsv2Aspect.name} failed on node ${node.node.id}: ${message}`); + cdk.Annotations.of(node).addWarningV2(`@aws-cdk/aws-ec2:imdsv2${RequireImdsv2Aspect.name}`, `${RequireImdsv2Aspect.name} failed on node ${node.node.id}: ${message}`); } } } diff --git a/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts b/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts index 5919e15aea863..51a020fa1036f 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts @@ -32,7 +32,7 @@ function synthesizeBlockDeviceMappings(construct: Construct, blockDevic throw new Error('iops property is required with volumeType: EbsDeviceVolumeType.IO1 and EbsDeviceVolumeType.IO2'); } } else if (volumeType !== EbsDeviceVolumeType.IO1 && volumeType !== EbsDeviceVolumeType.IO2 && volumeType !== EbsDeviceVolumeType.GP3) { - Annotations.of(construct).addWarningV2('EC2:EBS:IopsIgnored', 'iops will be ignored without volumeType: IO1, IO2, or GP3'); + Annotations.of(construct).addWarningV2('@aws-cdk/aws-ec2:iopsIgnored', 'iops will be ignored without volumeType: IO1, IO2, or GP3'); } /** diff --git a/packages/aws-cdk-lib/aws-ec2/lib/security-group.ts b/packages/aws-cdk-lib/aws-ec2/lib/security-group.ts index be8659fe5c675..5681261515a6a 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/security-group.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/security-group.ts @@ -553,7 +553,7 @@ export class SecurityGroup extends SecurityGroupBase { // is only one rule which allows all traffic and that subsumes any other // rule. if (!remoteRule) { // Warn only if addEgressRule() was explicitely called - Annotations.of(this).addWarningV2('EC2:SG:Ipv4IgnoreEgressRule', 'Ignoring Egress rule since \'allowAllOutbound\' is set to true; To add customized rules, set allowAllOutbound=false on the SecurityGroup'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-ec2:ipv4IgnoreEgressRule', 'Ignoring Egress rule since \'allowAllOutbound\' is set to true; To add customized rules, set allowAllOutbound=false on the SecurityGroup'); } return; } else if (!isIpv6 && !this.allowAllOutbound) { @@ -568,7 +568,7 @@ export class SecurityGroup extends SecurityGroupBase { // is only one rule which allows all traffic and that subsumes any other // rule. if (!remoteRule) { // Warn only if addEgressRule() was explicitely called - Annotations.of(this).addWarningV2('EC2:SG:Ipv6IgnoreEgressRule', 'Ignoring Egress rule since \'allowAllIpv6Outbound\' is set to true; To add customized rules, set allowAllIpv6Outbound=false on the SecurityGroup'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-ec2:ipv6IgnoreEgressRule', 'Ignoring Egress rule since \'allowAllIpv6Outbound\' is set to true; To add customized rules, set allowAllIpv6Outbound=false on the SecurityGroup'); } return; } diff --git a/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts b/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts index 081d125c85b4b..c4ea3d8b561bf 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts @@ -641,7 +641,7 @@ abstract class VpcBase extends Resource implements IVpc { if (placement.subnetGroupName !== undefined) { throw new Error('Please use only \'subnetGroupName\' (\'subnetName\' is deprecated and has the same behavior)'); } else { - Annotations.of(this).addWarningV2('EC2:Vpc:SubnetNameDeprecated', 'Usage of \'subnetName\' in SubnetSelection is deprecated, use \'subnetGroupName\' instead'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-ec2:subnetNameDeprecated', 'Usage of \'subnetName\' in SubnetSelection is deprecated, use \'subnetGroupName\' instead'); } placement = { ...placement, subnetGroupName: placement.subnetName }; } @@ -2184,7 +2184,7 @@ class ImportedVpc extends VpcBase { // None of the values may be unresolved list tokens for (const k of Object.keys(props) as Array) { if (Array.isArray(props[k]) && Token.isUnresolved(props[k])) { - Annotations.of(this).addWarningV2(`EC2:Vpc:VpcAttributeIsListToken${k}`, `fromVpcAttributes: '${k}' is a list token: the imported VPC will not work with constructs that require a list of subnets at synthesis time. Use 'Vpc.fromLookup()' or 'Fn.importListValue' instead.`); + Annotations.of(this).addWarningV2(`@aws-cdk/aws-ec2:vpcAttributeIsListToken${k}`, `fromVpcAttributes: '${k}' is a list token: the imported VPC will not work with constructs that require a list of subnets at synthesis time. Use 'Vpc.fromLookup()' or 'Fn.importListValue' instead.`); } } @@ -2345,7 +2345,7 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat ? `at '${Node.of(scope).path}/${id}'` : `'${attrs.subnetId}'`; // eslint-disable-next-line max-len - Annotations.of(this).addWarningV2('EC2:Vpc:NoSubnetRouteTableId', `No routeTableId was provided to the subnet ${ref}. Attempting to read its .routeTable.routeTableId will return null/undefined. (More info: https://github.com/aws/aws-cdk/pull/3171)`); + Annotations.of(this).addWarningV2('@aws-cdk/aws-ec2:noSubnetRouteTableId', `No routeTableId was provided to the subnet ${ref}. Attempting to read its .routeTable.routeTableId will return null/undefined. (More info: https://github.com/aws/aws-cdk/pull/3171)`); } this._ipv4CidrBlock = attrs.ipv4CidrBlock; diff --git a/packages/aws-cdk-lib/aws-ec2/test/instance.test.ts b/packages/aws-cdk-lib/aws-ec2/test/instance.test.ts index 888db4ab3c8c8..a11c22c09dba3 100644 --- a/packages/aws-cdk-lib/aws-ec2/test/instance.test.ts +++ b/packages/aws-cdk-lib/aws-ec2/test/instance.test.ts @@ -365,7 +365,7 @@ describe('instance', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Instance', 'EC2:EBS:IopsIgnored: iops will be ignored without volumeType: IO1, IO2, or GP3'); + Annotations.fromStack(stack).hasWarning('/Default/Instance', 'iops will be ignored without volumeType: IO1, IO2, or GP3 [ack: @aws-cdk/aws-ec2:iopsIgnored]'); }); test('warning if iops and invalid volumeType', () => { @@ -385,7 +385,7 @@ describe('instance', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Instance', 'EC2:EBS:IopsIgnored: iops will be ignored without volumeType: IO1, IO2, or GP3'); + Annotations.fromStack(stack).hasWarning('/Default/Instance', 'iops will be ignored without volumeType: IO1, IO2, or GP3 [ack: @aws-cdk/aws-ec2:iopsIgnored]'); }); }); diff --git a/packages/aws-cdk-lib/aws-ec2/test/vpc.test.ts b/packages/aws-cdk-lib/aws-ec2/test/vpc.test.ts index b02ad4c78546b..88c684a1fa63b 100644 --- a/packages/aws-cdk-lib/aws-ec2/test/vpc.test.ts +++ b/packages/aws-cdk-lib/aws-ec2/test/vpc.test.ts @@ -1891,7 +1891,7 @@ describe('vpc', () => { subnetIds: { 'Fn::Split': [',', { 'Fn::ImportValue': 'myPublicSubnetIds' }] }, }); - Annotations.fromStack(stack).hasWarning('/TestStack/VPC', "EC2:Vpc:VpcAttributeIsListTokenavailabilityZones: fromVpcAttributes: 'availabilityZones' is a list token: the imported VPC will not work with constructs that require a list of subnets at synthesis time. Use 'Vpc.fromLookup()' or 'Fn.importListValue' instead."); + Annotations.fromStack(stack).hasWarning('/TestStack/VPC', "fromVpcAttributes: 'availabilityZones' is a list token: the imported VPC will not work with constructs that require a list of subnets at synthesis time. Use 'Vpc.fromLookup()' or 'Fn.importListValue' instead. [ack: @aws-cdk/aws-ec2:vpcAttributeIsListTokenavailabilityZones]"); }); test('fromVpcAttributes using fixed-length list tokens', () => { diff --git a/packages/aws-cdk-lib/aws-ecr-assets/lib/image-asset.ts b/packages/aws-cdk-lib/aws-ecr-assets/lib/image-asset.ts index 6e318f6be4528..f025f61041d4f 100644 --- a/packages/aws-cdk-lib/aws-ecr-assets/lib/image-asset.ts +++ b/packages/aws-cdk-lib/aws-ecr-assets/lib/image-asset.ts @@ -438,7 +438,7 @@ export class DockerImageAsset extends Construct implements IAsset { exclude.push(cdkout); if (props.repositoryName) { - Annotations.of(this).addWarningV2('ECRAssets:ImageAsset:RepositoryNameDeprecated', 'DockerImageAsset.repositoryName is deprecated. Override "core.Stack.addDockerImageAsset" to control asset locations'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-ecr-assets:repositoryNameDeprecated', 'DockerImageAsset.repositoryName is deprecated. Override "core.Stack.addDockerImageAsset" to control asset locations'); } // include build context in "extra" so it will impact the hash diff --git a/packages/aws-cdk-lib/aws-ecr/lib/repository.ts b/packages/aws-cdk-lib/aws-ecr/lib/repository.ts index 38576ce8dca70..75f9e62b6f06e 100644 --- a/packages/aws-cdk-lib/aws-ecr/lib/repository.ts +++ b/packages/aws-cdk-lib/aws-ecr/lib/repository.ts @@ -738,7 +738,7 @@ export class Repository extends RepositoryBase { */ public addToResourcePolicy(statement: iam.PolicyStatement): iam.AddToResourcePolicyResult { if (statement.resources.length) { - Annotations.of(this).addWarningV2('ECR:Repository:NoResourceStatements', 'ECR resource policy does not allow resource statements.'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-ecr:noResourceStatements', 'ECR resource policy does not allow resource statements.'); } if (this.policyDocument === undefined) { this.policyDocument = new iam.PolicyDocument(); diff --git a/packages/aws-cdk-lib/aws-ecr/test/repository.test.ts b/packages/aws-cdk-lib/aws-ecr/test/repository.test.ts index 5a5e35f9962ac..573c090a93805 100644 --- a/packages/aws-cdk-lib/aws-ecr/test/repository.test.ts +++ b/packages/aws-cdk-lib/aws-ecr/test/repository.test.ts @@ -396,7 +396,7 @@ describe('repository', () => { })); // THEN - Annotations.fromStack(stack).hasWarning('*', 'ECR:Repository:NoResourceStatements: ECR resource policy does not allow resource statements.'); + Annotations.fromStack(stack).hasWarning('*', 'ECR resource policy does not allow resource statements. [ack: @aws-cdk/aws-ecr:noResourceStatements]'); }); test('does not warn if repository policy does not have resources', () => { @@ -412,7 +412,7 @@ describe('repository', () => { })); // THEN - Annotations.fromStack(stack).hasNoWarning('*', 'ECR:Repository:NoResourceStatements: ECR resource policy does not allow resource statements.'); + Annotations.fromStack(stack).hasNoWarning('*', 'ECR resource policy does not allow resource statements. [ack: @aws-cdk/aws-ecr:noResourceStatements]'); }); test('default encryption configuration', () => { diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts b/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts index 4d8be6946d8e6..764d99d4216c0 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts @@ -364,7 +364,7 @@ test('Scheduled Ec2 Task shows warning when minute is not defined in cron', () = }); // THEN - Annotations.fromStack(stack).hasWarning('/Default', "Events:Schedule:WillRunEveryMinute: 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."); + Annotations.fromStack(stack).hasWarning('/Default', "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-events:scheduleWillRunEveryMinute]"); }); test('Scheduled Ec2 Task shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/scheduled-fargate-task.test.ts b/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/scheduled-fargate-task.test.ts index 33f20d98d9afa..efd19854f62d4 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/scheduled-fargate-task.test.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/scheduled-fargate-task.test.ts @@ -451,7 +451,7 @@ test('Scheduled Fargate Task shows warning when minute is not defined in cron', }); // THEN - Annotations.fromStack(stack).hasWarning('/Default', "Events:Schedule:WillRunEveryMinute: 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."); + Annotations.fromStack(stack).hasWarning('/Default', "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-events:scheduleWillRunEveryMinute]"); }); test('Scheduled Fargate Task shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts b/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts index fd4329427c7d8..9a2e3ecd35075 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts @@ -564,7 +564,7 @@ export abstract class BaseService extends Resource this.node.addDependency(this.taskDefinition.taskRole); if (props.deploymentController?.type === DeploymentControllerType.EXTERNAL) { - Annotations.of(this).addWarningV2('ECS:Service:ExternalDeploymentController', 'taskDefinition and launchType are blanked out when using external deployment controller.'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-ecs:externalDeploymentController', 'taskDefinition and launchType are blanked out when using external deployment controller.'); } if (props.circuitBreaker diff --git a/packages/aws-cdk-lib/aws-ecs/lib/images/repository.ts b/packages/aws-cdk-lib/aws-ecs/lib/images/repository.ts index 65275db8699f1..2a371804177b6 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/images/repository.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/images/repository.ts @@ -37,7 +37,7 @@ export class RepositoryImage extends ContainerImage { public bind(scope: Construct, containerDefinition: ContainerDefinition): ContainerImageConfig { // name could be a Token - in that case, skip validation altogether if (!Token.isUnresolved(this.imageName) && ECR_IMAGE_REGEX.test(this.imageName)) { - Annotations.of(scope).addWarningV2('ECS:RepositoryImage:ECRImageRequiresPolicy', "Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + Annotations.of(scope).addWarningV2('@aws-cdk/aws-ecs:ecrImageRequiresPolicy', "Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); } if (this.props.credentials) { diff --git a/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-service.test.ts index 8bb6e6f66f9c9..a2478b8a28af8 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-service.test.ts @@ -1210,7 +1210,7 @@ describe('ec2 service', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Ec2Service', 'ECS:Service:ExternalDeploymentController: taskDefinition and launchType are blanked out when using external deployment controller.'); + Annotations.fromStack(stack).hasWarning('/Default/Ec2Service', 'taskDefinition and launchType are blanked out when using external deployment controller. [ack: @aws-cdk/aws-ecs:externalDeploymentController]'); Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { Cluster: { Ref: 'EcsCluster97242B84', @@ -1227,7 +1227,8 @@ describe('ec2 service', () => { test('add warning to annotations if circuitBreaker is specified with a non-ECS DeploymentControllerType', () => { // GIVEN - const stack = new cdk.Stack(); + const app = new cdk.App(); + const stack = new cdk.Stack(app); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); addDefaultCapacityProvider(cluster, stack, vpc); @@ -1246,10 +1247,11 @@ describe('ec2 service', () => { }, circuitBreaker: { rollback: true }, }); + app.synth(); // THEN - expect(service.node.metadata[0].data.message).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); - expect(service.node.metadata[1].data).toEqual('Deployment circuit breaker requires the ECS deployment controller.'); + expect(service.node.metadata[0].data).toEqual('Deployment circuit breaker requires the ECS deployment controller.'); + expect(service.node.metadata[1].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); }); diff --git a/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-task-definition.test.ts b/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-task-definition.test.ts index ca76c6e4f86ae..1b235721f59e8 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-task-definition.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-task-definition.test.ts @@ -688,7 +688,7 @@ describe('ec2 task definition', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Ec2TaskDef/web', "ECS:RepositoryImage:ECRImageRequiresPolicy: Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + Annotations.fromStack(stack).hasWarning('/Default/Ec2TaskDef/web', "Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'. [ack: @aws-cdk/aws-ecs:ecrImageRequiresPolicy]"); }); test('warns when setting containers from ECR repository by creating a RepositoryImage class', () => { @@ -706,7 +706,7 @@ describe('ec2 task definition', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Ec2TaskDef/web', "ECS:RepositoryImage:ECRImageRequiresPolicy: Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + Annotations.fromStack(stack).hasWarning('/Default/Ec2TaskDef/web', "Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'. [ack: @aws-cdk/aws-ecs:ecrImageRequiresPolicy]"); }); test('correctly sets containers from asset using all props', () => { diff --git a/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts index f0884333a196e..ab931459701f0 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts @@ -519,7 +519,8 @@ describe('external service', () => { test('add warning to annotations if circuitBreaker is specified with a non-ECS DeploymentControllerType', () => { // GIVEN - const stack = new cdk.Stack(); + const app = new cdk.App(); + const stack = new cdk.Stack(app); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); addDefaultCapacityProvider(cluster, stack, vpc); @@ -538,10 +539,11 @@ describe('external service', () => { }, circuitBreaker: { rollback: true }, }); + app.synth(); // THEN - expect(service.node.metadata[0].data.message).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); - expect(service.node.metadata[1].data).toEqual('Deployment circuit breaker requires the ECS deployment controller.'); + expect(service.node.metadata[0].data).toEqual('Deployment circuit breaker requires the ECS deployment controller.'); + expect(service.node.metadata[1].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller. [ack: @aws-cdk/aws-ecs:externalDeploymentController]'); }); }); diff --git a/packages/aws-cdk-lib/aws-ecs/test/external/external-task-definition.test.ts b/packages/aws-cdk-lib/aws-ecs/test/external/external-task-definition.test.ts index 76fd96a20704b..a78872cfbba2b 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/external/external-task-definition.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/external/external-task-definition.test.ts @@ -578,7 +578,7 @@ describe('external task definition', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/ExternalTaskDef/web', "ECS:RepositoryImage:ECRImageRequiresPolicy: Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + Annotations.fromStack(stack).hasWarning('/Default/ExternalTaskDef/web', "Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'. [ack: @aws-cdk/aws-ecs:ecrImageRequiresPolicy]"); }); test('correctly sets volumes', () => { diff --git a/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts index d4c4034ec05a2..a5ae17df8bb26 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts @@ -620,7 +620,7 @@ describe('fargate service', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/FargateService', 'ECS:Service:ExternalDeploymentController: taskDefinition and launchType are blanked out when using external deployment controller.'); + Annotations.fromStack(stack).hasWarning('/Default/FargateService', 'taskDefinition and launchType are blanked out when using external deployment controller. [ack: @aws-cdk/aws-ecs:externalDeploymentController]'); Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { Cluster: { Ref: 'EcsCluster97242B84', @@ -659,7 +659,8 @@ describe('fargate service', () => { test('add warning to annotations if circuitBreaker is specified with a non-ECS DeploymentControllerType', () => { // GIVEN - const stack = new cdk.Stack(); + const app = new cdk.App(); + const stack = new cdk.Stack(app); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); @@ -676,10 +677,11 @@ describe('fargate service', () => { }, circuitBreaker: { rollback: true }, }); + app.synth(); // THEN - expect(service.node.metadata[0].data.message).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); expect(service.node.metadata[1].data).toEqual('Deployment circuit breaker requires the ECS deployment controller.'); + expect(service.node.metadata[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); }); @@ -2258,7 +2260,7 @@ describe('fargate service', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Service/TaskCount/Target', "AppAutoScaling:Schedule: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."); + Annotations.fromStack(stack).hasWarning('/Default/Service/TaskCount/Target', "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-applicationautoscaling:defaultRunEveryMinute]"); }); test('scheduled scaling shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-eks/lib/cluster.ts b/packages/aws-cdk-lib/aws-eks/lib/cluster.ts index 995b5a95ec3ae..7cea59f9183d0 100644 --- a/packages/aws-cdk-lib/aws-eks/lib/cluster.ts +++ b/packages/aws-cdk-lib/aws-eks/lib/cluster.ts @@ -1148,7 +1148,7 @@ abstract class ClusterBase extends Resource implements ICluster { let mapRole = options.mapRole ?? true; if (mapRole && !(this instanceof Cluster)) { // do the mapping... - Annotations.of(autoScalingGroup).addWarningV2('EKS:Cluster:UnsupportedAutoMappingAwsAutoRole', 'Auto-mapping aws-auth role for imported cluster is not supported, please map role manually'); + Annotations.of(autoScalingGroup).addWarningV2('@aws-cdk/aws-eks:clusterUnsupportedAutoMappingAwsAutoRole', 'Auto-mapping aws-auth role for imported cluster is not supported, please map role manually'); mapRole = false; } if (mapRole) { @@ -1462,7 +1462,7 @@ export class Cluster extends ClusterBase { const kubectlVersion = new semver.SemVer(`${props.version.version}.0`); if (semver.gte(kubectlVersion, '1.22.0') && !props.kubectlLayer) { - Annotations.of(this).addWarningV2('EKS:Cluster:KubectlLayerNotSpecified', `You created a cluster with Kubernetes Version ${props.version.version} without specifying the kubectlLayer property. This may cause failures as the kubectl version provided with aws-cdk-lib is 1.20, which is only guaranteed to be compatible with Kubernetes versions 1.19-1.21. Please provide a kubectlLayer from @aws-cdk/lambda-layer-kubectl-v${kubectlVersion.minor}.`); + Annotations.of(this).addWarningV2('@aws-cdk/aws-eks:clusterKubectlLayerNotSpecified', `You created a cluster with Kubernetes Version ${props.version.version} without specifying the kubectlLayer property. This may cause failures as the kubectl version provided with aws-cdk-lib is 1.20, which is only guaranteed to be compatible with Kubernetes versions 1.19-1.21. Please provide a kubectlLayer from @aws-cdk/lambda-layer-kubectl-v${kubectlVersion.minor}.`); }; this.version = props.version; @@ -1966,7 +1966,7 @@ export class Cluster extends ClusterBase { // message (if token): "could not auto-tag public/private subnet with tag..." // message (if not token): "count not auto-tag public/private subnet xxxxx with tag..." const subnetID = Token.isUnresolved(subnet.subnetId) || Token.isUnresolved([subnet.subnetId]) ? '' : ` ${subnet.subnetId}`; - Annotations.of(this).addWarningV2('EKS:Cluster:MustManuallyTagSubnet', `Could not auto-tag ${type} subnet${subnetID} with "${tag}=1", please remember to do this manually`); + Annotations.of(this).addWarningV2('@aws-cdk/aws-eks:clusterMustManuallyTagSubnet', `Could not auto-tag ${type} subnet${subnetID} with "${tag}=1", please remember to do this manually`); continue; } diff --git a/packages/aws-cdk-lib/aws-eks/lib/fargate-profile.ts b/packages/aws-cdk-lib/aws-eks/lib/fargate-profile.ts index 515168b6a241a..cebe05debe77d 100644 --- a/packages/aws-cdk-lib/aws-eks/lib/fargate-profile.ts +++ b/packages/aws-cdk-lib/aws-eks/lib/fargate-profile.ts @@ -155,7 +155,7 @@ export class FargateProfile extends Construct implements ITaggable { this.podExecutionRole.grantPassRole(props.cluster.adminRole); if (props.subnetSelection && !props.vpc) { - Annotations.of(this).addWarningV2('EKS:FargateProfile:DefaultToPrivateSubnets', 'Vpc must be defined to use a custom subnet selection. All private subnets belonging to the EKS cluster will be used by default'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-eks:fargateProfileDefaultToPrivateSubnets', 'Vpc must be defined to use a custom subnet selection. All private subnets belonging to the EKS cluster will be used by default'); } let subnets: string[] | undefined; diff --git a/packages/aws-cdk-lib/aws-eks/lib/managed-nodegroup.ts b/packages/aws-cdk-lib/aws-eks/lib/managed-nodegroup.ts index cda46c127f6dd..c388778661ff1 100644 --- a/packages/aws-cdk-lib/aws-eks/lib/managed-nodegroup.ts +++ b/packages/aws-cdk-lib/aws-eks/lib/managed-nodegroup.ts @@ -370,7 +370,7 @@ export class Nodegroup extends Resource implements INodegroup { } if (props.instanceType) { - Annotations.of(this).addWarningV2('EKS:ManagedNodeGroup:DeprecatedInstanceType', '"instanceType" is deprecated and will be removed in the next major version. please use "instanceTypes" instead'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-eks:managedNodeGroupDeprecatedInstanceType', '"instanceType" is deprecated and will be removed in the next major version. please use "instanceTypes" instead'); } const instanceTypes = props.instanceTypes ?? (props.instanceType ? [props.instanceType] : undefined); let possibleAmiTypes: NodegroupAmiType[] = []; diff --git a/packages/aws-cdk-lib/aws-eks/test/cluster.test.ts b/packages/aws-cdk-lib/aws-eks/test/cluster.test.ts index 2a922b8d90b8f..368f9f2e0cf5e 100644 --- a/packages/aws-cdk-lib/aws-eks/test/cluster.test.ts +++ b/packages/aws-cdk-lib/aws-eks/test/cluster.test.ts @@ -3064,10 +3064,9 @@ describe('cluster', () => { describe('kubectlLayer annotation', () => { function message(version: string) { return [ - 'EKS:Cluster:KubectlLayerNotSpecified:', `You created a cluster with Kubernetes Version 1.${version} without specifying the kubectlLayer property.`, 'This may cause failures as the kubectl version provided with aws-cdk-lib is 1.20, which is only guaranteed to be compatible with Kubernetes versions 1.19-1.21.', - `Please provide a kubectlLayer from @aws-cdk/lambda-layer-kubectl-v${version}.`, + `Please provide a kubectlLayer from @aws-cdk/lambda-layer-kubectl-v${version}. [ack: @aws-cdk/aws-eks:clusterKubectlLayerNotSpecified]`, ].join(' '); } diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts index bb6881b0682db..47c7fbac64969 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts @@ -306,7 +306,7 @@ export class ApplicationListenerRule extends Construct { // Instead, signal this through a warning. // @deprecate: upon the next major version bump, replace this with a `throw` if (this.action) { - cdk.Annotations.of(this).addWarningV2('Elbv2:ALBListnerRule:DefaultActionReplaced', 'An Action already existed on this ListenerRule and was replaced. Configure exactly one default Action.'); + cdk.Annotations.of(this).addWarningV2('@aws-cdk/aws-elbv2:albListnerRuleDefaultActionReplaced', 'An Action already existed on this ListenerRule and was replaced. Configure exactly one default Action.'); } action.bind(this, this.listener, this); diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index a58b846ba637e..59ab4e1b6e38f 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -643,11 +643,11 @@ class ImportedApplicationTargetGroup extends ImportedTargetGroupBase implements public registerListener(_listener: IApplicationListener, _associatingConstruct?: IConstruct) { // Nothing to do, we know nothing of our members - Annotations.of(this).addWarningV2('Elbv2:ALBTargetGroup:CannotRegisterListener', 'Cannot register listener on imported target group -- security groups might need to be updated manually'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-elbv2:albTargetGroupCannotRegisterListener', 'Cannot register listener on imported target group -- security groups might need to be updated manually'); } public registerConnectable(_connectable: ec2.IConnectable, _portRange?: ec2.Port | undefined): void { - Annotations.of(this).addWarningV2('Elbv2:AlbTargetGroup:CannotRegisterConnectable', 'Cannot register connectable on imported target group -- security groups might need to be updated manually'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-elbv2:albTargetGroupCannotRegisterConnectable', 'Cannot register connectable on imported target group -- security groups might need to be updated manually'); } public addTarget(...targets: IApplicationLoadBalancerTarget[]) { diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-listener.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-listener.ts index 688f218f1f741..72dad641eb2d7 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-listener.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-listener.ts @@ -153,7 +153,7 @@ export abstract class BaseListener extends Resource implements IListener { // Instead, signal this through a warning. // @deprecate: upon the next major version bump, replace this with a `throw` if (this.defaultAction) { - Annotations.of(this).addWarningV2('Elbv2:Listener:ExistingDefaultActionReplaced', 'A default Action already existed on this Listener and was replaced. Configure exactly one default Action.'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-elbv2:listenerExistingDefaultActionReplaced', 'A default Action already existed on this Listener and was replaced. Configure exactly one default Action.'); } this.defaultAction = action; diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts index f5d699c82fbcb..024f6303336a7 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts @@ -328,7 +328,7 @@ export abstract class TargetGroupBase extends Construct implements ITargetGroup const ret = new Array(); if (this.targetType === undefined && this.targetsJson.length === 0) { - cdk.Annotations.of(this).addWarningV2('Elbv2:TargetGroup:SpecifyTargetTypeForEmptyTargetGroup', "When creating an empty TargetGroup, you should specify a 'targetType' (this warning may become an error in the future)."); + cdk.Annotations.of(this).addWarningV2('@aws-cdk/aws-elbv2:targetGroupSpecifyTargetTypeForEmptyTargetGroup', "When creating an empty TargetGroup, you should specify a 'targetType' (this warning may become an error in the future)."); } if (this.targetType !== TargetType.LAMBDA && this.vpc === undefined) { diff --git a/packages/aws-cdk-lib/aws-events-targets/lib/aws-api.ts b/packages/aws-cdk-lib/aws-events-targets/lib/aws-api.ts index 9b801d28431d1..165198643a4fe 100644 --- a/packages/aws-cdk-lib/aws-events-targets/lib/aws-api.ts +++ b/packages/aws-cdk-lib/aws-events-targets/lib/aws-api.ts @@ -129,7 +129,7 @@ export class AwsApi implements events.IRuleTarget { function checkServiceExists(service: string, handler: lambda.SingletonFunction) { const sdkService = awsSdkMetadata[service.toLowerCase()]; if (!sdkService) { - Annotations.of(handler).addWarningV2(`EventsTargets:Api:${service}DoesNotExist`, `Service ${service} does not exist in the AWS SDK. Check the list of available \ + Annotations.of(handler).addWarningV2(`@aws-cdk/aws-events-targets:${service}DoesNotExist`, `Service ${service} does not exist in the AWS SDK. Check the list of available \ services and actions from https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/index.html`); } } diff --git a/packages/aws-cdk-lib/aws-events-targets/lib/ecs-task.ts b/packages/aws-cdk-lib/aws-events-targets/lib/ecs-task.ts index bcd6ef6160fef..9cf0f430b9ac6 100644 --- a/packages/aws-cdk-lib/aws-events-targets/lib/ecs-task.ts +++ b/packages/aws-cdk-lib/aws-events-targets/lib/ecs-task.ts @@ -185,7 +185,7 @@ export class EcsTask implements events.IRuleTarget { // Security groups are only configurable with the "awsvpc" network mode. if (this.taskDefinition.networkMode !== ecs.NetworkMode.AWS_VPC) { if (props.securityGroup !== undefined || props.securityGroups !== undefined) { - cdk.Annotations.of(this.taskDefinition).addWarningV2('EventsTargets:EcsTask:SecurityGroupIgnored', 'security groups are ignored when network mode is not awsvpc'); + cdk.Annotations.of(this.taskDefinition).addWarningV2('@aws-cdk/aws-events-targets:ecsTaskSecurityGroupIgnored', 'security groups are ignored when network mode is not awsvpc'); } return; } diff --git a/packages/aws-cdk-lib/aws-events-targets/lib/util.ts b/packages/aws-cdk-lib/aws-events-targets/lib/util.ts index 4d8cfd4e1955b..ab279d03f4b60 100644 --- a/packages/aws-cdk-lib/aws-events-targets/lib/util.ts +++ b/packages/aws-cdk-lib/aws-events-targets/lib/util.ts @@ -133,7 +133,7 @@ export function addToDeadLetterQueueResourcePolicy(rule: events.IRule, queue: sq }, })); } else { - Annotations.of(rule).addWarningV2('EventsTargets:DLQ:ManuallyAddDLQResourcePolicy', `Cannot add a resource policy to your dead letter queue associated with rule ${rule.ruleName} because the queue is in a different account. You must add the resource policy manually to the dead letter queue in account ${queue.env.account}.`); + Annotations.of(rule).addWarningV2('@aws-cdk/aws-events-targets:manuallyAddDLQResourcePolicy', `Cannot add a resource policy to your dead letter queue associated with rule ${rule.ruleName} because the queue is in a different account. You must add the resource policy manually to the dead letter queue in account ${queue.env.account}.`); } } diff --git a/packages/aws-cdk-lib/aws-events-targets/test/aws-api/aws-api.test.ts b/packages/aws-cdk-lib/aws-events-targets/test/aws-api/aws-api.test.ts index 940e66938291a..22ca924fc33b9 100644 --- a/packages/aws-cdk-lib/aws-events-targets/test/aws-api/aws-api.test.ts +++ b/packages/aws-cdk-lib/aws-events-targets/test/aws-api/aws-api.test.ts @@ -163,5 +163,5 @@ test('with service not in AWS SDK', () => { rule.addTarget(awsApi); // THEN - Annotations.fromStack(stack).hasWarning('*', 'EventsTargets:Api:no-such-serviceDoesNotExist: Service no-such-service does not exist in the AWS SDK. Check the list of available services and actions from https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/index.html'); + Annotations.fromStack(stack).hasWarning('*', 'Service no-such-service does not exist in the AWS SDK. Check the list of available services and actions from https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/index.html [ack: @aws-cdk/aws-events-targets:no-such-serviceDoesNotExist]'); }); diff --git a/packages/aws-cdk-lib/aws-events-targets/test/lambda/lambda.test.ts b/packages/aws-cdk-lib/aws-events-targets/test/lambda/lambda.test.ts index ba75d60a92542..31eabbba22b9b 100644 --- a/packages/aws-cdk-lib/aws-events-targets/test/lambda/lambda.test.ts +++ b/packages/aws-cdk-lib/aws-events-targets/test/lambda/lambda.test.ts @@ -320,7 +320,13 @@ test('must display a warning when using a Dead Letter Queue from another account Template.fromStack(stack1).resourceCountIs('AWS::SQS::QueuePolicy', 0); - Annotations.fromStack(stack1).hasWarning('/Stack1/Rule', Match.stringLikeRegexp('EventsTargets:DLQ:ManuallyAddDLQResourcePolicy: .*')); + Annotations.fromStack(stack1).hasWarning('/Stack1/Rule', Match.objectLike({ + 'Fn::Join': Match.arrayWith([ + Match.arrayWith([ + 'Cannot add a resource policy to your dead letter queue associated with rule ', + ]), + ]), + })); }); test('specifying retry policy', () => { diff --git a/packages/aws-cdk-lib/aws-events/lib/rule.ts b/packages/aws-cdk-lib/aws-events/lib/rule.ts index 07f3d9f9bfef2..7b3f512e692e8 100644 --- a/packages/aws-cdk-lib/aws-events/lib/rule.ts +++ b/packages/aws-cdk-lib/aws-events/lib/rule.ts @@ -448,7 +448,7 @@ export class Rule extends Resource implements IRule { private sameEnvDimension(dim1: string, dim2: string) { switch (Token.compareStrings(dim1, dim2)) { case TokenComparison.ONE_UNRESOLVED: - Annotations.of(this).addWarningV2('Events:Rule:UnresolvedEnvironment', 'Either the Event Rule or target has an unresolved environment. \n \ + Annotations.of(this).addWarningV2('@aws-cdk/aws-events:ruleUnresolvedEnvironment', 'Either the Event Rule or target has an unresolved environment. \n \ If they are being used in a cross-environment setup you need to specify the environment for both.'); return true; case TokenComparison.BOTH_UNRESOLVED: diff --git a/packages/aws-cdk-lib/aws-events/lib/schedule.ts b/packages/aws-cdk-lib/aws-events/lib/schedule.ts index a62fb7a4c18f1..94b3ecbf7f1e0 100644 --- a/packages/aws-cdk-lib/aws-events/lib/schedule.ts +++ b/packages/aws-cdk-lib/aws-events/lib/schedule.ts @@ -62,7 +62,7 @@ export abstract class Schedule { public readonly expressionString: string = `cron(${minute} ${hour} ${day} ${month} ${weekDay} ${year})`; public _bind(scope: Construct) { if (!options.minute) { - Annotations.of(scope).addWarningV2('Events:Schedule:WillRunEveryMinute', '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.'); + Annotations.of(scope).addWarningV2('@aws-cdk/aws-events:scheduleWillRunEveryMinute', '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.'); } return new LiteralSchedule(this.expressionString); } diff --git a/packages/aws-cdk-lib/aws-events/test/rule.test.ts b/packages/aws-cdk-lib/aws-events/test/rule.test.ts index b36f1e2337440..6cca0b11ba804 100644 --- a/packages/aws-cdk-lib/aws-events/test/rule.test.ts +++ b/packages/aws-cdk-lib/aws-events/test/rule.test.ts @@ -38,7 +38,7 @@ describe('rule', () => { }), }); - Annotations.fromStack(stack).hasWarning('/Default/MyRule', "Events:Schedule:WillRunEveryMinute: 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."); + Annotations.fromStack(stack).hasWarning('/Default/MyRule', "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-events:scheduleWillRunEveryMinute]"); }); test('rule does not display warning when minute is set to * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-iam/lib/group.ts b/packages/aws-cdk-lib/aws-iam/lib/group.ts index dd49d170e7d2d..985746176b252 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/group.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/group.ts @@ -218,7 +218,7 @@ export class Group extends GroupBase { private managedPoliciesExceededWarning() { if (this.managedPolicies.length > 10) { - Annotations.of(this).addWarningV2('IAM:Group:MaxPoliciesExceeded', `You added ${this.managedPolicies.length} to IAM Group ${this.physicalName}. The maximum number of managed policies attached to an IAM group is 10.`); + Annotations.of(this).addWarningV2('@aws-cdk/aws-iam:groupMaxPoliciesExceeded', `You added ${this.managedPolicies.length} to IAM Group ${this.physicalName}. The maximum number of managed policies attached to an IAM group is 10.`); } } } diff --git a/packages/aws-cdk-lib/aws-iam/lib/private/imported-role.ts b/packages/aws-cdk-lib/aws-iam/lib/private/imported-role.ts index f614e4832911e..6573696da4e2a 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/private/imported-role.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/private/imported-role.ts @@ -68,7 +68,7 @@ export class ImportedRole extends Resource implements IRole, IComparablePrincipa } public addManagedPolicy(policy: IManagedPolicy): void { - Annotations.of(this).addWarningV2('IAM:ImportedRole:ManagedPolicyNotAdded', `Not adding managed policy: ${policy.managedPolicyArn} to imported role: ${this.roleName}`); + Annotations.of(this).addWarningV2('@aws-cdk/aws-iam:importedRoleManagedPolicyNotAdded', `Not adding managed policy: ${policy.managedPolicyArn} to imported role: ${this.roleName}`); } public grantPassRole(identity: IPrincipal): Grant { diff --git a/packages/aws-cdk-lib/aws-iam/lib/role.ts b/packages/aws-cdk-lib/aws-iam/lib/role.ts index f87e67b7d9f42..5ed1302543d47 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/role.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/role.ts @@ -652,9 +652,9 @@ export class Role extends Resource implements IRole { const mpCount = this.managedPolicies.length + (splitOffDocs.size - 1); if (mpCount > 20) { - Annotations.of(this).addWarningV2('IAM:Role:PolicyTooLarge', `Policy too large: ${mpCount} exceeds the maximum of 20 managed policies attached to a Role`); + Annotations.of(this).addWarningV2('@aws-cdk/aws-iam:rolePolicyTooLarge', `Policy too large: ${mpCount} exceeds the maximum of 20 managed policies attached to a Role`); } else if (mpCount > 10) { - Annotations.of(this).addWarningV2('IAM:Role:PolicyLarge', `Policy large: ${mpCount} exceeds 10 managed policies attached to a Role, this requires a quota increase`); + Annotations.of(this).addWarningV2('@aws-cdk/aws-iam:rolePolicyLarge', `Policy large: ${mpCount} exceeds 10 managed policies attached to a Role, this requires a quota increase`); } // Create the managed policies and fix up the dependencies diff --git a/packages/aws-cdk-lib/aws-iam/lib/unknown-principal.ts b/packages/aws-cdk-lib/aws-iam/lib/unknown-principal.ts index c08e1fc4e3480..d79258d6612a4 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/unknown-principal.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/unknown-principal.ts @@ -41,7 +41,7 @@ export class UnknownPrincipal implements IPrincipal { public addToPrincipalPolicy(statement: PolicyStatement): AddToPrincipalPolicyResult { const stack = Stack.of(this.resource); const repr = JSON.stringify(stack.resolve(statement)); - Annotations.of(this.resource).addWarningV2('IAM:UnknownPrincipal:AddStatementToRole', `Add statement to this resource's role: ${repr}`); + Annotations.of(this.resource).addWarningV2('@aws-cdk/aws-iam:unknownPrincipalAddStatementToRole', `Add statement to this resource's role: ${repr}`); // Pretend we did the work. The human will do it for us, eventually. return { statementAdded: true, policyDependable: new DependencyGroup() }; } diff --git a/packages/aws-cdk-lib/aws-iam/test/group.test.ts b/packages/aws-cdk-lib/aws-iam/test/group.test.ts index b45d13ec32f59..37595b0ae6dbb 100644 --- a/packages/aws-cdk-lib/aws-iam/test/group.test.ts +++ b/packages/aws-cdk-lib/aws-iam/test/group.test.ts @@ -126,7 +126,7 @@ test('throw warning if attached managed policies exceed 10 in constructor', () = ], }); - Annotations.fromStack(stack).hasWarning('*', 'IAM:Group:MaxPoliciesExceeded: You added 11 to IAM Group MyGroup. The maximum number of managed policies attached to an IAM group is 10.'); + Annotations.fromStack(stack).hasWarning('*', 'You added 11 to IAM Group MyGroup. The maximum number of managed policies attached to an IAM group is 10. [ack: @aws-cdk/aws-iam:groupMaxPoliciesExceeded]'); }); test('throw warning if attached managed policies exceed 10 when calling `addManagedPolicy`', () => { @@ -142,5 +142,5 @@ test('throw warning if attached managed policies exceed 10 when calling `addMana group.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName(i.toString())); } - Annotations.fromStack(stack).hasWarning('/Default/MyGroup', 'IAM:Group:MaxPoliciesExceeded: You added 11 to IAM Group MyGroup. The maximum number of managed policies attached to an IAM group is 10.'); + Annotations.fromStack(stack).hasWarning('/Default/MyGroup', 'You added 12 to IAM Group MyGroup. The maximum number of managed policies attached to an IAM group is 10. [ack: @aws-cdk/aws-iam:groupMaxPoliciesExceeded]'); }); diff --git a/packages/aws-cdk-lib/aws-lambda-event-sources/lib/sqs.ts b/packages/aws-cdk-lib/aws-lambda-event-sources/lib/sqs.ts index 7ae77e3bdda6a..6b175bc18cd25 100644 --- a/packages/aws-cdk-lib/aws-lambda-event-sources/lib/sqs.ts +++ b/packages/aws-cdk-lib/aws-lambda-event-sources/lib/sqs.ts @@ -103,7 +103,7 @@ export class SqsEventSource implements lambda.IEventSource { if (target.role) { this.queue.grantConsumeMessages(target); } else { - Annotations.of(target).addWarningV2('LambdaEventSources:SQS:FunctionImportWithoutRole', `Function '${target.node.path}' was imported without an IAM role `+ + Annotations.of(target).addWarningV2('@aws-cdk/aws-lambda-event-sources:sqsFunctionImportWithoutRole', `Function '${target.node.path}' was imported without an IAM role `+ `so it was not granted access to consume messages from '${this.queue.node.path}'`); } } diff --git a/packages/aws-cdk-lib/aws-lambda/lib/function-base.ts b/packages/aws-cdk-lib/aws-lambda/lib/function-base.ts index dffc6bf535f33..56a5847062b01 100644 --- a/packages/aws-cdk-lib/aws-lambda/lib/function-base.ts +++ b/packages/aws-cdk-lib/aws-lambda/lib/function-base.ts @@ -325,7 +325,7 @@ export abstract class FunctionBase extends Resource implements IFunction, ec2.IC } protected warnInvokeFunctionPermissions(scope: Construct): void { - Annotations.of(scope).addWarningV2('Lambda:Function:AddPermissionsToVersionOrAlias', [ + Annotations.of(scope).addWarningV2('@aws-cdk/aws-lambda:addPermissionsToVersionOrAlias', [ "AWS Lambda has changed their authorization strategy, which may cause client invocations using the 'Qualifier' parameter of the lambda function to fail with Access Denied errors.", "If you are using a lambda Version or Alias, make sure to call 'grantInvoke' or 'addPermission' on the Version or Alias, not the underlying Function", 'See: https://github.com/aws/aws-cdk/issues/19273', diff --git a/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts b/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts index 62d7dd371b370..50bab5959c6aa 100644 --- a/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts +++ b/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts @@ -602,7 +602,7 @@ describe('alias', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Alias/AliasScaling/Target', "AppAutoScaling:Schedule: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."); + Annotations.fromStack(stack).hasWarning('/Default/Alias/AliasScaling/Target', "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-applicationautoscaling:defaultRunEveryMinute]"); }); test('scheduled scaling shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-rds/lib/cluster.ts b/packages/aws-cdk-lib/aws-rds/lib/cluster.ts index 0995b6d4aa00e..d3f26123b1725 100644 --- a/packages/aws-cdk-lib/aws-rds/lib/cluster.ts +++ b/packages/aws-cdk-lib/aws-rds/lib/cluster.ts @@ -707,7 +707,7 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase { if (hasOnlyServerlessReaders) { if (noFailoverTierInstances) { Annotations.of(this).addWarningV2( - 'RDS:Cluster:NoFailoverServerlessReaders', + '@aws-cdk/aws-rds:noFailoverServerlessReaders', `Cluster ${this.node.id} only has serverless readers and no reader is in promotion tier 0-1.`+ 'Serverless readers in promotion tiers >= 2 will NOT scale with the writer, which can lead to '+ 'availability issues if a failover event occurs. It is recommended that at least one reader '+ @@ -718,7 +718,7 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase { } else { if (serverlessInHighestTier && highestTier > 1) { Annotations.of(this).addWarningV2( - 'RDS:Cluster:ServerlessInHighestTier2-15', + '@aws-cdk/aws-rds:serverlessInHighestTier2-15', `There are serverlessV2 readers in tier ${highestTier}. Since there are no instances in a higher tier, `+ 'any instance in this tier is a failover target. Since this tier is > 1 the serverless reader will not scale '+ 'with the writer which could lead to availability issues during failover.', @@ -726,7 +726,7 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase { } if (someProvisionedReadersDontMatchWriter.length > 0 && writer.type === InstanceType.PROVISIONED) { Annotations.of(this).addWarningV2( - 'RDS:Cluster:ProvisionedReadersDontMatchWriter', + '@aws-cdk/aws-rds:provisionedReadersDontMatchWriter', `There are provisioned readers in the highest promotion tier ${highestTier} that do not have the same `+ 'InstanceSize as the writer. Any of these instances could be chosen as the new writer in the event '+ 'of a failover.\n'+ @@ -745,7 +745,7 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase { if (writer.type === InstanceType.PROVISIONED) { if (reader.type === InstanceType.SERVERLESS_V2) { if (!instanceSizeSupportedByServerlessV2(writer.instanceSize!, this.serverlessV2MaxCapacity)) { - Annotations.of(this).addWarningV2('RDS:Cluster:ServerlessInstanceCantScaleWithWriter', + Annotations.of(this).addWarningV2('@aws-cdk/aws-rds:serverlessInstanceCantScaleWithWriter', 'For high availability any serverless instances in promotion tiers 0-1 '+ 'should be able to scale to match the provisioned instance capacity.\n'+ `Serverless instance ${reader.node.id} is in promotion tier ${reader.tier},\n`+ @@ -1131,10 +1131,10 @@ export class DatabaseClusterFromSnapshot extends DatabaseClusterNew { super(scope, id, props); if (props.credentials && !props.credentials.password && !props.credentials.secret) { - Annotations.of(this).addWarningV2('RDS:Cluster:UseSnapshotCredentials', 'Use `snapshotCredentials` to modify password of a cluster created from a snapshot.'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-rds:useSnapshotCredentials', 'Use `snapshotCredentials` to modify password of a cluster created from a snapshot.'); } if (!props.credentials && !props.snapshotCredentials) { - Annotations.of(this).addWarningV2('RDS:Cluster:GeneratedCredsNotApplied', 'Generated credentials will not be applied to cluster. Use `snapshotCredentials` instead. `addRotationSingleUser()` and `addRotationMultiUser()` cannot be used on this cluster.'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-rds:generatedCredsNotApplied', 'Generated credentials will not be applied to cluster. Use `snapshotCredentials` instead. `addRotationSingleUser()` and `addRotationMultiUser()` cannot be used on this cluster.'); } const deprecatedCredentials = renderCredentials(this, props.engine, props.credentials); diff --git a/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts b/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts index 6807aaf66a5c6..ff13a8c4bf36b 100644 --- a/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts +++ b/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts @@ -428,10 +428,10 @@ describe('cluster new api', () => { }); Annotations.fromStack(stack).hasWarning('*', - `RDS:Cluster:NoFailoverServerlessReaders: Cluster ${cluster.node.id} only has serverless readers and no reader is in promotion tier 0-1.`+ + `Cluster ${cluster.node.id} only has serverless readers and no reader is in promotion tier 0-1.`+ 'Serverless readers in promotion tiers >= 2 will NOT scale with the writer, which can lead to '+ 'availability issues if a failover event occurs. It is recommended that at least one reader '+ - 'has `scaleWithWriter` set to true', + 'has `scaleWithWriter` set to true [ack: @aws-cdk/aws-rds:noFailoverServerlessReaders]', ); }); @@ -592,11 +592,10 @@ describe('cluster new api', () => { }); Annotations.fromStack(stack).hasWarning('*', - 'RDS:Cluster:ServerlessInstanceCantScaleWithWriter: '+ 'For high availability any serverless instances in promotion tiers 0-1 '+ 'should be able to scale to match the provisioned instance capacity.\n'+ 'Serverless instance reader is in promotion tier 1,\n'+ - `But can not scale to match the provisioned writer instance (${instanceType.toString()})`, + `But can not scale to match the provisioned writer instance (${instanceType.toString()}) [ack: @aws-cdk/aws-rds:serverlessInstanceCantScaleWithWriter]`, ); }); }); @@ -675,11 +674,11 @@ describe('cluster new api', () => { }); Annotations.fromStack(stack).hasWarning('*', - 'RDS:Cluster:ProvisionedReadersDontMatchWriter: There are provisioned readers in the highest promotion tier 2 that do not have the same '+ + 'There are provisioned readers in the highest promotion tier 2 that do not have the same '+ 'InstanceSize as the writer. Any of these instances could be chosen as the new writer in the event '+ 'of a failover.\n'+ 'Writer InstanceSize: m5.24xlarge\n'+ - 'Reader InstanceSizes: t3.medium, m5.xlarge', + 'Reader InstanceSizes: t3.medium, m5.xlarge [ack: @aws-cdk/aws-rds:provisionedReadersDontMatchWriter]', ); }); @@ -811,17 +810,17 @@ describe('cluster new api', () => { }); Annotations.fromStack(stack).hasWarning('*', - 'RDS:Cluster:ServerlessInHighestTier2-15: There are serverlessV2 readers in tier 2. Since there are no instances in a higher tier, '+ + 'There are serverlessV2 readers in tier 2. Since there are no instances in a higher tier, '+ 'any instance in this tier is a failover target. Since this tier is > 1 the serverless reader will not scale '+ - 'with the writer which could lead to availability issues during failover.', + 'with the writer which could lead to availability issues during failover. [ack: @aws-cdk/aws-rds:serverlessInHighestTier2-15]', ); Annotations.fromStack(stack).hasWarning('*', - 'RDS:Cluster:ProvisionedReadersDontMatchWriter: There are provisioned readers in the highest promotion tier 2 that do not have the same '+ + 'There are provisioned readers in the highest promotion tier 2 that do not have the same '+ 'InstanceSize as the writer. Any of these instances could be chosen as the new writer in the event '+ 'of a failover.\n'+ 'Writer InstanceSize: m5.24xlarge\n'+ - 'Reader InstanceSizes: m5.xlarge', + 'Reader InstanceSizes: m5.xlarge [ack: @aws-cdk/aws-rds:provisionedReadersDontMatchWriter]', ); }); }); diff --git a/packages/aws-cdk-lib/aws-s3-notifications/lib/sqs.ts b/packages/aws-cdk-lib/aws-s3-notifications/lib/sqs.ts index 7374f3a39b918..41bb67ee9983a 100644 --- a/packages/aws-cdk-lib/aws-s3-notifications/lib/sqs.ts +++ b/packages/aws-cdk-lib/aws-s3-notifications/lib/sqs.ts @@ -32,7 +32,7 @@ export class SqsDestination implements s3.IBucketNotificationDestination { }); const addResult = this.queue.encryptionMasterKey.addToResourcePolicy(statement, /* allowNoOp */ true); if (!addResult.statementAdded) { - Annotations.of(this.queue.encryptionMasterKey).addWarningV2('S3Notifications:SQS:KMSPermissionsNotAdded', `Can not change key policy of imported kms key. Ensure that your key policy contains the following permissions: \n${JSON.stringify(statement.toJSON(), null, 2)}`); + Annotations.of(this.queue.encryptionMasterKey).addWarningV2('@aws-cdk/aws-s3-notifications:sqsKMSPermissionsNotAdded', `Can not change key policy of imported kms key. Ensure that your key policy contains the following permissions: \n${JSON.stringify(statement.toJSON(), null, 2)}`); } } diff --git a/packages/aws-cdk-lib/aws-s3-notifications/test/queue.test.ts b/packages/aws-cdk-lib/aws-s3-notifications/test/queue.test.ts index 78efda0d082f8..8b6e9dcb46260 100644 --- a/packages/aws-cdk-lib/aws-s3-notifications/test/queue.test.ts +++ b/packages/aws-cdk-lib/aws-s3-notifications/test/queue.test.ts @@ -109,7 +109,7 @@ test('if the queue is encrypted with a imported kms key, printout warning', () = bucket.addObjectCreatedNotification(new notif.SqsDestination(queue)); - Annotations.fromStack(stack).hasWarning('/Default/ImportedKey', `S3Notifications:SQS:KMSPermissionsNotAdded: Can not change key policy of imported kms key. Ensure that your key policy contains the following permissions: \n${JSON.stringify({ + Annotations.fromStack(stack).hasWarning('/Default/ImportedKey', `Can not change key policy of imported kms key. Ensure that your key policy contains the following permissions: \n${JSON.stringify({ Action: [ 'kms:GenerateDataKey*', 'kms:Decrypt', @@ -119,5 +119,5 @@ test('if the queue is encrypted with a imported kms key, printout warning', () = Service: 's3.amazonaws.com', }, Resource: '*', - }, null, 2)}`); + }, null, 2)} [ack: @aws-cdk/aws-s3-notifications:sqsKMSPermissionsNotAdded]`); }); diff --git a/packages/aws-cdk-lib/aws-s3/lib/bucket.ts b/packages/aws-cdk-lib/aws-s3/lib/bucket.ts index 635f2847c91ae..5c73e80d000de 100644 --- a/packages/aws-cdk-lib/aws-s3/lib/bucket.ts +++ b/packages/aws-cdk-lib/aws-s3/lib/bucket.ts @@ -1893,7 +1893,7 @@ export class Bucket extends BucketBase { } else if (props.serverAccessLogsBucket) { // A `serverAccessLogsBucket` was provided but it is not a concrete `Bucket` and it // may not be possible to configure the ACLs or bucket policy as required. - Annotations.of(this).addWarningV2('S3:Bucket:AccessLogsPolicyNotAdded', + Annotations.of(this).addWarningV2('@aws-cdk/aws-s3:accessLogsPolicyNotAdded', `Unable to add necessary logging permissions to imported target bucket: ${props.serverAccessLogsBucket}`, ); } diff --git a/packages/aws-cdk-lib/aws-servicecatalog/lib/private/product-stack-synthesizer.ts b/packages/aws-cdk-lib/aws-servicecatalog/lib/private/product-stack-synthesizer.ts index e7a2c1de1bfcd..f3573779c8b1f 100644 --- a/packages/aws-cdk-lib/aws-servicecatalog/lib/private/product-stack-synthesizer.ts +++ b/packages/aws-cdk-lib/aws-servicecatalog/lib/private/product-stack-synthesizer.ts @@ -27,7 +27,7 @@ export class ProductStackSynthesizer extends cdk.StackSynthesizer { if (!this.bucketDeployment) { const parentStack = (this.boundStack as ProductStack)._getParentStack(); if (!cdk.Resource.isOwnedResource(this.assetBucket)) { - cdk.Annotations.of(parentStack).addWarningV2('ServiceCatalog:Assets:ManuallyAddBucketPermissions', '[WARNING] Bucket Policy Permissions cannot be added to' + + cdk.Annotations.of(parentStack).addWarningV2('@aws-cdk/aws-servicecatalog:assetsManuallyAddBucketPermissions', '[WARNING] Bucket Policy Permissions cannot be added to' + ' referenced Bucket. Please make sure your bucket has the correct permissions'); } this.bucketDeployment = new BucketDeployment(parentStack, 'AssetsBucketDeployment', { diff --git a/packages/aws-cdk-lib/aws-ses-actions/lib/lambda.ts b/packages/aws-cdk-lib/aws-ses-actions/lib/lambda.ts index 693cecae45431..ca9e961ea528e 100644 --- a/packages/aws-cdk-lib/aws-ses-actions/lib/lambda.ts +++ b/packages/aws-cdk-lib/aws-ses-actions/lib/lambda.ts @@ -71,7 +71,7 @@ export class Lambda implements ses.IReceiptRuleAction { rule.node.addDependency(permission); } else { // eslint-disable-next-line max-len - cdk.Annotations.of(rule).addWarningV2('SESActions:Lambda:AddInvokePermissions', 'This rule is using a Lambda action with an imported function. Ensure permission is given to SES to invoke that function.'); + cdk.Annotations.of(rule).addWarningV2('@aws-cdk/aws-ses-actions:lambdaAddInvokePermissions', 'This rule is using a Lambda action with an imported function. Ensure permission is given to SES to invoke that function.'); } return { diff --git a/packages/aws-cdk-lib/aws-ses-actions/lib/s3.ts b/packages/aws-cdk-lib/aws-ses-actions/lib/s3.ts index df25d429731a8..5fa01ce1c91be 100644 --- a/packages/aws-cdk-lib/aws-ses-actions/lib/s3.ts +++ b/packages/aws-cdk-lib/aws-ses-actions/lib/s3.ts @@ -65,7 +65,7 @@ export class S3 implements ses.IReceiptRuleAction { if (policy) { // The bucket could be imported rule.node.addDependency(policy); } else { - cdk.Annotations.of(rule).addWarningV2('SESActions:S3:AddBucketPermissions', 'This rule is using a S3 action with an imported bucket. Ensure permission is given to SES to write to that bucket.'); + cdk.Annotations.of(rule).addWarningV2('@aws-cdk/s3:AddBucketPermissions', 'This rule is using a S3 action with an imported bucket. Ensure permission is given to SES to write to that bucket.'); } // Allow SES to use KMS master key diff --git a/packages/aws-cdk-lib/core/lib/cfn-resource.ts b/packages/aws-cdk-lib/core/lib/cfn-resource.ts index f2864bbccc9f3..fecc6a184e402 100644 --- a/packages/aws-cdk-lib/core/lib/cfn-resource.ts +++ b/packages/aws-cdk-lib/core/lib/cfn-resource.ts @@ -148,7 +148,7 @@ export class CfnResource extends CfnRefElement { if (FeatureFlags.of(this).isEnabled(cxapi.VALIDATE_SNAPSHOT_REMOVAL_POLICY) ) { throw new Error(`${this.cfnResourceType} does not support snapshot removal policy`); } else { - Annotations.of(this).addWarningV2(`Core:CfnResource:${this.cfnResourceType}SnapshotRemovalPolicyIgnored`, `${this.cfnResourceType} does not support snapshot removal policy. This policy will be ignored.`); + Annotations.of(this).addWarningV2(`@aws-cdk/core:${this.cfnResourceType}SnapshotRemovalPolicyIgnored`, `${this.cfnResourceType} does not support snapshot removal policy. This policy will be ignored.`); } } diff --git a/packages/aws-cdk-lib/core/lib/private/synthesis.ts b/packages/aws-cdk-lib/core/lib/private/synthesis.ts index cc7c52b6466d4..3526991880735 100644 --- a/packages/aws-cdk-lib/core/lib/private/synthesis.ts +++ b/packages/aws-cdk-lib/core/lib/private/synthesis.ts @@ -238,7 +238,7 @@ function invokeAspects(root: IConstruct) { // if an aspect was added to the node while invoking another aspect it will not be invoked, emit a warning // the `nestedAspectWarning` flag is used to prevent the warning from being emitted for every child if (!nestedAspectWarning && nodeAspectsCount !== aspects.all.length) { - Annotations.of(construct).addWarningV2('Core:IgnoredAspect', 'We detected an Aspect was added via another Aspect, and will not be applied'); + Annotations.of(construct).addWarningV2('@aws-cdk/core:ignoredAspect', 'We detected an Aspect was added via another Aspect, and will not be applied'); nestedAspectWarning = true; } diff --git a/packages/aws-cdk-lib/core/lib/private/tree-metadata.ts b/packages/aws-cdk-lib/core/lib/private/tree-metadata.ts index fbe72e170c722..92da3c4c1aa06 100644 --- a/packages/aws-cdk-lib/core/lib/private/tree-metadata.ts +++ b/packages/aws-cdk-lib/core/lib/private/tree-metadata.ts @@ -35,7 +35,7 @@ export class TreeMetadata extends Construct { try { return visit(c); } catch (e) { - Annotations.of(this).addWarningV2(`Core:FailedToRenderTreeMetadata-${c.node.id}`, `Failed to render tree metadata for node [${c.node.id}]. Reason: ${e}`); + Annotations.of(this).addWarningV2(`@aws-cdk/core:failedToRenderTreeMetadata-${c.node.id}`, `Failed to render tree metadata for node [${c.node.id}]. Reason: ${e}`); return undefined; } }); diff --git a/packages/aws-cdk-lib/core/lib/stack.ts b/packages/aws-cdk-lib/core/lib/stack.ts index dc8a170d9a7b7..0f347acb85928 100644 --- a/packages/aws-cdk-lib/core/lib/stack.ts +++ b/packages/aws-cdk-lib/core/lib/stack.ts @@ -1342,7 +1342,7 @@ export class Stack extends Construct implements ITaggable { if (this.templateOptions.transform) { // eslint-disable-next-line max-len - Annotations.of(this).addWarningV2('Core:Stack:DeprecatedTransform', 'This stack is using the deprecated `templateOptions.transform` property. Consider switching to `addTransform()`.'); + Annotations.of(this).addWarningV2('@aws-cdk/core:stackDeprecatedTransform', 'This stack is using the deprecated `templateOptions.transform` property. Consider switching to `addTransform()`.'); this.addTransform(this.templateOptions.transform); } diff --git a/packages/aws-cdk-lib/core/test/app.test.ts b/packages/aws-cdk-lib/core/test/app.test.ts index 090bf28223a61..49654658ef8d4 100644 --- a/packages/aws-cdk-lib/core/test/app.test.ts +++ b/packages/aws-cdk-lib/core/test/app.test.ts @@ -78,8 +78,8 @@ describe('app', () => { '/stack1/s1c1': [{ type: 'aws:cdk:logicalId', data: 's1c1' }], '/stack1/s1c2': [{ type: 'aws:cdk:logicalId', data: 's1c2' }, - { type: 'aws:cdk:warning', data: { id: 'warning1', message: 'warning1', scope: 'stack1/s1c2' } }, - { type: 'aws:cdk:warning', data: { id: 'warning2', message: 'warning2', scope: 'stack1/s1c2' } }], + { type: 'aws:cdk:warning', data: 'warning1 [ack: warning1]' }, + { type: 'aws:cdk:warning', data: 'warning2 [ack: warning2]' }], }); const stack2 = response.stacks[1]; diff --git a/packages/aws-cdk-lib/core/test/aspect.test.ts b/packages/aws-cdk-lib/core/test/aspect.test.ts index 4fc5a8c4faedd..c050f4be0ccaa 100644 --- a/packages/aws-cdk-lib/core/test/aspect.test.ts +++ b/packages/aws-cdk-lib/core/test/aspect.test.ts @@ -50,7 +50,7 @@ describe('aspect', () => { }); app.synth(); expect(root.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(root.node.metadata[0].data).toEqual({ id: 'Core:IgnoredAspect', message: 'We detected an Aspect was added via another Aspect, and will not be applied', scope: 'MyConstruct' }); + expect(root.node.metadata[0].data).toEqual('We detected an Aspect was added via another Aspect, and will not be applied [ack: @aws-cdk/core:ignoredAspect]'); // warning is not added to child construct expect(child.node.metadata.length).toEqual(0); }); diff --git a/packages/aws-cdk-lib/core/test/cfn-resource.test.ts b/packages/aws-cdk-lib/core/test/cfn-resource.test.ts index e20d256694ce1..d8c8fab44d5b5 100644 --- a/packages/aws-cdk-lib/core/test/cfn-resource.test.ts +++ b/packages/aws-cdk-lib/core/test/cfn-resource.test.ts @@ -119,7 +119,7 @@ describe('cfn resource', () => { expect(getWarnings(app.synth())).toEqual([ { path: '/Default/Resource', - message: 'Core:CfnResource:AWS::Lambda::FunctionSnapshotRemovalPolicyIgnored: AWS::Lambda::Function does not support snapshot removal policy. This policy will be ignored.', + message: 'AWS::Lambda::Function does not support snapshot removal policy. This policy will be ignored. [ack: @aws-cdk/core:AWS::Lambda::FunctionSnapshotRemovalPolicyIgnored]', }, ]); }); diff --git a/packages/aws-cdk-lib/core/test/construct.test.ts b/packages/aws-cdk-lib/core/test/construct.test.ts index 943c3e1be7a36..8db1f906a4280 100644 --- a/packages/aws-cdk-lib/core/test/construct.test.ts +++ b/packages/aws-cdk-lib/core/test/construct.test.ts @@ -2,7 +2,7 @@ import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Construct, ConstructOrder, IConstruct } from 'constructs'; import { reEnableStackTraceCollection, restoreStackTraceColection } from './util'; import * as cxschema from '../../cloud-assembly-schema'; -import { Names } from '../lib'; +import { App, Names } from '../lib'; import { Annotations } from '../lib/annotations'; /* eslint-disable @typescript-eslint/naming-convention */ @@ -247,13 +247,14 @@ describe('construct', () => { test('addWarning(message) can be used to add a "WARNING" message entry to the construct', () => { const previousValue = reEnableStackTraceCollection(); - const root = new Root(); + const root = new App(); const con = new Construct(root, 'MyConstruct'); Annotations.of(con).addWarningV2('WARNING1', 'This construct is deprecated, use the other one instead'); restoreStackTraceColection(previousValue); + root.synth(); expect(con.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(con.node.metadata[0].data.message).toEqual('This construct is deprecated, use the other one instead'); + expect(con.node.metadata[0].data).toEqual('This construct is deprecated, use the other one instead [ack: WARNING1]'); expect(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0).toEqual(true); }); diff --git a/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts b/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts index 3de7d55d75b99..060fe84ec9c31 100644 --- a/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts +++ b/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts @@ -449,7 +449,7 @@ export class AwsCustomResource extends Construct implements iam.IGrantable { if (installLatestAwsSdk && props.installLatestAwsSdk === undefined) { // This is dangerous. Add a warning. - Annotations.of(this).addWarningV2('CustomResources:AwsCustomResource:InstallLatestAwsSdkNotSpecified', [ + Annotations.of(this).addWarningV2('@aws-cdk/custom-resources:installLatestAwsSdkNotSpecified', [ 'installLatestAwsSdk was not specified, and defaults to true. You probably do not want this.', `Set the global context flag \'${cxapi.AWS_CUSTOM_RESOURCE_LATEST_SDK_DEFAULT}\' to false to switch this behavior off project-wide,`, 'or set the property explicitly to true if you know you need to call APIs that are not in Lambda\'s built-in SDK version.', diff --git a/packages/aws-cdk-lib/pipelines/lib/legacy/pipeline.ts b/packages/aws-cdk-lib/pipelines/lib/legacy/pipeline.ts index 5deecd12bf890..ea87b74b2f4de 100644 --- a/packages/aws-cdk-lib/pipelines/lib/legacy/pipeline.ts +++ b/packages/aws-cdk-lib/pipelines/lib/legacy/pipeline.ts @@ -437,7 +437,7 @@ export class CdkPipeline extends Construct { const depAction = stackActions.find(s => s.stackArtifactId === depId); if (depAction === undefined) { - Annotations.of(this).addWarningV2('Pipelines:Pipeline:DependencyOnNonPipelineStack', `Stack '${stackAction.stackName}' depends on stack ` + + Annotations.of(this).addWarningV2('@aws-cdk/pipelines:dependencyOnNonPipelineStack', `Stack '${stackAction.stackName}' depends on stack ` + `'${depId}', but that dependency is not deployed through the pipeline!`); } else if (!(depAction.executeRunOrder < stackAction.prepareRunOrder)) { yield `Stack '${stackAction.stackName}' depends on stack ` + From 957c2383e7a6f5227d43dadeb7d55525eb27bde1 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 18 Aug 2023 15:07:27 +0200 Subject: [PATCH 12/26] Keep warning suppression synth-side by means of an App global --- .../assertions/test/annotations.test.ts | 10 +- .../lib/cloud-assembly/metadata-schema.ts | 57 +----- packages/aws-cdk-lib/core/lib/annotations.ts | 169 ++++++++++++++---- .../aws-cdk-lib/core/test/annotations.test.ts | 75 +++----- .../aws-cdk-lib/cx-api/lib/cloud-artifact.ts | 34 +--- 5 files changed, 163 insertions(+), 182 deletions(-) diff --git a/packages/aws-cdk-lib/assertions/test/annotations.test.ts b/packages/aws-cdk-lib/assertions/test/annotations.test.ts index 5d0db8ac2bef2..a890382d26fbc 100644 --- a/packages/aws-cdk-lib/assertions/test/annotations.test.ts +++ b/packages/aws-cdk-lib/assertions/test/annotations.test.ts @@ -75,7 +75,7 @@ describe('Messages', () => { describe('hasWarning', () => { test('match', () => { - annotations.hasWarning('/Default/Fred', 'Fred: this is a warning'); + annotations.hasWarning('/Default/Fred', 'this is a warning [ack: Fred]'); }); test('no match', () => { @@ -89,7 +89,7 @@ describe('Messages', () => { }); test('no match', () => { - expect(() => annotations.hasNoWarning('/Default/Fred', 'Fred: this is a warning')) + expect(() => annotations.hasNoWarning('/Default/Fred', 'this is a warning [ack: Fred]')) .toThrowError(/Expected no matches, but stack has 1 messages as follows:/); }); }); @@ -183,7 +183,7 @@ describe('Multiple Messages on the Resource', () => { test('succeeds on hasXxx APIs', () => { annotations.hasError('/Default/Foo', 'error: this is an error'); annotations.hasError('/Default/Foo', 'error: unsupported type Foo::Bar'); - annotations.hasWarning('/Default/Foo', 'Foo: warning: Foo::Bar is deprecated'); + annotations.hasWarning('/Default/Foo', 'warning: Foo::Bar is deprecated [ack: Foo]'); }); test('succeeds on findXxx APIs', () => { @@ -191,8 +191,8 @@ describe('Multiple Messages on the Resource', () => { expect(result1.length).toEqual(4); const result2 = annotations.findError('/Default/Bar', Match.stringLikeRegexp('error:.*')); expect(result2.length).toEqual(2); - const result3 = annotations.findWarning('/Default/Bar', 'Bar: warning: Foo::Bar is deprecated'); - expect(result3[0].entry.data).toEqual('Bar: warning: Foo::Bar is deprecated'); + const result3 = annotations.findWarning('/Default/Bar', 'warning: Foo::Bar is deprecated [ack: Bar]'); + expect(result3[0].entry.data).toEqual('warning: Foo::Bar is deprecated [ack: Bar]'); }); }); class MyAspect implements IAspect { diff --git a/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts b/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts index f5cda80b0f9d6..9354300baa1ba 100644 --- a/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts +++ b/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts @@ -243,30 +243,6 @@ export type AssetMetadataEntry = FileAssetMetadataEntry | ContainerImageAssetMet */ export type LogMessageMetadataEntry = string; -/** - * Newer log message metadata entries contain more info - * - * @see ArtifactMetadataEntryType.INFO - * @see ArtifactMetadataEntryType.WARN - * @see ArtifactMetadataEntryType.ERROR - */ -export interface LogMessageObjectMetadataEntry { - /** - * The message id - */ - readonly id: string; - - /** - * The scope the message applies to - */ - readonly scope: string; - - /** - * The log message - */ - readonly message: string; -}; - /** * @see ArtifactMetadataEntryType.LOGICAL_ID */ @@ -277,36 +253,10 @@ export type LogicalIdMetadataEntry = string; */ export type StackTagsMetadataEntry = Tag[]; -/** - * @see ArtifactMetadataEntryType.ACKNOWLEDGE - */ -export interface AcknowledgementMetadataEntry { - /** - * The message id that is acknowledged - */ - readonly id: string; - - /** - * The list of scopes that this acknowledgement should apply to - */ - readonly scopes: string[]; - - /** - * The acknowledgement message - */ - readonly message?: string; -}; - /** * Union type for all metadata entries that might exist in the manifest. */ -export type MetadataEntryData = - LogMessageObjectMetadataEntry | - AcknowledgementMetadataEntry | - AssetMetadataEntry | - LogMessageMetadataEntry | - LogicalIdMetadataEntry | - StackTagsMetadataEntry; +export type MetadataEntryData = AssetMetadataEntry | LogMessageMetadataEntry | LogicalIdMetadataEntry | StackTagsMetadataEntry; /** * Type of artifact metadata entry. @@ -332,11 +282,6 @@ export enum ArtifactMetadataEntryType { */ ERROR = 'aws:cdk:error', - /** - * Metadata key used to suppress WARNING-level messages - */ - ACKNOWLEDGE = 'aws:cdk:acknowledge', - /** * Represents the CloudFormation logical ID of a resource at a certain path. */ diff --git a/packages/aws-cdk-lib/core/lib/annotations.ts b/packages/aws-cdk-lib/core/lib/annotations.ts index e608f04dd96ed..808f7dd33692b 100644 --- a/packages/aws-cdk-lib/core/lib/annotations.ts +++ b/packages/aws-cdk-lib/core/lib/annotations.ts @@ -1,6 +1,7 @@ -import { IConstruct } from 'constructs'; +import { IConstruct, MetadataEntry } from 'constructs'; import * as cxschema from '../../cloud-assembly-schema'; import * as cxapi from '../../cx-api'; +import { App } from './app'; /** * Includes API for attaching annotations such as warning messages to constructs. @@ -38,34 +39,37 @@ export class Annotations { * @param message optional message to explain the reason for acknowledgement */ public acknowledgeWarning(id: string, message?: string): void { - const scopes = this.scope.node.findAll().map(child => child.node.path); - this.addMessage(cxschema.ArtifactMetadataEntryType.ACKNOWLEDGE, { - id, - message, - scopes, - }); + Acknowledgements.of(this.scope).add(this.scope, id); + + // We don't use message currently, but encouraging people to supply it is good for documentation + // purposes, and we can always add a report on it in the future. + void(message); + + // Iterate over the construct and remove any existing instances of this warning + // (addWarningV2 will prevent future instances of it) + removeWarningDeep(this.scope, id); } /** - * Adds a warning metadata entry to this construct. + * Adds an acknowledgeable warning metadata entry to this construct. * * The CLI will display the warning when an app is synthesized, or fail if run - * in --strict mode. + * in `--strict` mode. + * + * If the warning is acknowledged using `acknowledgeWarning()`, it will not be shown by + * the CLI, and will not cause `--strict` mode to fail synthesis. * * @example * declare const construct: Construct; - * Annotations.of(construct).addWarningV2('Library:Construct:ThisIsAWarning', 'Some message explaining the warning'); + * Annotations.of(construct).addWarningV2('my-library:Construct.someWarning', 'Some message explaining the warning'); * * @param id the unique identifier for the warning. This can be used to acknowledge the warning * @param message The warning message. */ public addWarningV2(id: string, message: string) { - const warning = { - id: id, - scope: this.scope.node.path, - message, - }; - this.addMessage(cxschema.ArtifactMetadataEntryType.WARN, warning); + if (!Acknowledgements.of(this.scope).has(this.scope, id)) { + this.addMessage(cxschema.ArtifactMetadataEntryType.WARN, `${message} ${ackTag(id)}`); + } } /** @@ -133,26 +137,123 @@ export class Annotations { * @param level The message level * @param message The message itself */ - private addMessage( - level: string, - message: cxschema.LogMessageMetadataEntry | cxschema.LogMessageObjectMetadataEntry | cxschema.AcknowledgementMetadataEntry, - ) { - let isNew = false; - switch (typeof message) { - case 'string': - isNew = !this.scope.node.metadata.find((x) => x.data === message); - break; - case 'object': - if ('scope' in message) { - isNew = !this.scope.node.metadata.find((x) => x.data.id === message.id && x.data.message === message.message); - } else if ('scopes' in message) { - isNew = !this.scope.node.metadata.find((x) => - x.data.id === message.id && x.data.message === message.message && x.data.scopes === message.scopes, - ); - } - } + private addMessage(level: string, message: string) { + const isNew = !this.scope.node.metadata.find((x) => x.data === message); if (isNew) { this.scope.node.addMetadata(level, message, { stackTrace: this.stackTraces }); } } } + +/** + * Class to keep track of acknowledgements + * + * There is a singleton instance for every `App` instance, which can be obtained by + * calling `Acknowledgements.of(...)`. + */ +class Acknowledgements { + public static of(scope: IConstruct): Acknowledgements { + const app = App.of(scope); + if (!app) { + return new Acknowledgements(); + } + + const existing = (app as any)[Acknowledgements.ACKNOWLEDGEMENTS_SYM]; + if (existing) { + return existing as Acknowledgements; + } + + const fresh = new Acknowledgements(); + (app as any)[Acknowledgements.ACKNOWLEDGEMENTS_SYM] = fresh; + return fresh; + } + + private static ACKNOWLEDGEMENTS_SYM = Symbol.for('@aws-cdk/core.Acknowledgements'); + + private readonly acks = new Map>(); + + private constructor() {} + + public add(node: string | IConstruct, ack: string) { + const nodePath = this.nodePath(node); + + let arr = this.acks.get(nodePath); + if (!arr) { + arr = new Set(); + this.acks.set(nodePath, arr); + } + arr.add(ack); + } + + public has(node: string | IConstruct, ack: string): boolean { + for (const candidate of this.searchPaths(this.nodePath(node))) { + if (this.acks.get(candidate)?.has(ack)) { + return true; + } + } + return false; + } + + private nodePath(node: string | IConstruct) { + // Normalize, remove leading / if it exists + return (typeof node === 'string' ? node : node.node.path).replace(/^\//, ''); + } + + /** + * Given 'a/b/c', return ['a/b/c', 'a/b', 'a'] + */ + private searchPaths(path: string) { + const ret = new Array(); + let start = 0; + while (start < path.length) { + let i = path.indexOf('/', start); + if (i !== -1) { + ret.push(path.substring(0, i)); + start = i + 1; + } else { + start = path.length; + } + } + return ret.reverse(); + } +} + +/** + * Remove warning metadata from all constructs in a given scope + * + * No recursion to avoid blowing out the stack. + */ +function removeWarningDeep(construct: IConstruct, id: string) { + const stack = [construct]; + + while (stack.length > 0) { + const next = stack.pop()!; + removeWarning(next, id); + stack.push(...next.node.children); + } +} + +/** + * Remove metadata from a construct node. + * + * This uses private APIs for now; we could consider adding this functionality + * to the constructs library itself. + */ +function removeWarning(construct: IConstruct, id: string) { + const meta: MetadataEntry[] | undefined = (construct.node as any)._metadata; + if (!meta) { return; } + + let i = 0; + while (i < meta.length) { + const m = meta[i]; + if (m.type === cxschema.ArtifactMetadataEntryType.WARN && (m.data as string).includes(ackTag(id))) { + meta.splice(i, 1); + } else { + i += 1; + } + } +} + +function ackTag(id: string) { + return `[ack: ${id}]`; +} \ No newline at end of file diff --git a/packages/aws-cdk-lib/core/test/annotations.test.ts b/packages/aws-cdk-lib/core/test/annotations.test.ts index df9bb332fe456..c6cc2fb9420b8 100644 --- a/packages/aws-cdk-lib/core/test/annotations.test.ts +++ b/packages/aws-cdk-lib/core/test/annotations.test.ts @@ -24,7 +24,7 @@ describe('annotations', () => { expect(getWarnings(app.synth())).toEqual([ { path: '/MyStack/Hello', - message: 'Deprecated:@aws-cdk/core.Construct.node: The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release', + message: 'The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release [ack: Deprecated:@aws-cdk/core.Construct.node]', }, ]); }); @@ -51,15 +51,15 @@ describe('annotations', () => { expect(getWarnings(app.synth())).toEqual([ { path: '/MyStack1/Hello', - message: 'Deprecated:@aws-cdk/core.Construct.node: The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release', + message: 'The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release [ack: Deprecated:@aws-cdk/core.Construct.node]', }, { path: '/MyStack1/World', - message: 'Deprecated:@aws-cdk/core.Construct.node: The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release', + message: 'The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release [ack: Deprecated:@aws-cdk/core.Construct.node]', }, { path: '/MyStack2/FooBar', - message: 'Deprecated:@aws-cdk/core.Construct.node: The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release', + message: 'The API @aws-cdk/core.Construct.node is deprecated: use @aws-Construct.construct instead. This API will be removed in the next major release [ack: Deprecated:@aws-cdk/core.Construct.node]', }, ]); }); @@ -83,15 +83,16 @@ describe('annotations', () => { Annotations.of(c1).addWarningV2('warning1', 'You should know this!'); Annotations.of(c1).addWarningV2('warning1', 'You should know this!'); Annotations.of(c1).addWarningV2('warning2', 'You should know this, too!'); - expect(getWarnings(app.synth())).toEqual([{ - path: '/S1/C1', - message: 'warning1: You should know this!', - }, - { - path: '/S1/C1', - message: 'warning2: You should know this, too!', - }], - ); + expect(getWarnings(app.synth())).toEqual([ + { + path: '/S1/C1', + message: 'You should know this! [ack: warning1]', + }, + { + path: '/S1/C1', + message: 'You should know this, too! [ack: warning2]', + }, + ]); }); test('acknowledgeWarning removes warning', () => { @@ -106,28 +107,12 @@ describe('annotations', () => { Annotations.of(c1).acknowledgeWarning('MESSAGE2', 'I Ack this'); // THEN - const assembly = app.synth(); - let acknoledgement: any = {}; - for (const s of Object.values(assembly.manifest.artifacts ?? {})) { - for (const [_path, md] of Object.entries(s.metadata ?? {})) { - for (const x of md) { - if (x.type === 'aws:cdk:acknowledge') { - acknoledgement.message = x.data as string; - } - } - } - } - expect(acknoledgement).toEqual({ - message: { - id: 'MESSAGE2', - scopes: ['S1/C1'], - message: 'I Ack this', + expect(getWarnings(app.synth())).toEqual([ + { + path: '/S1/C1', + message: 'You should know this! [ack: MESSAGE1]', }, - }); - expect(getWarnings(assembly)).toEqual([{ - path: '/S1/C1', - message: 'MESSAGE1: You should know this!', - }]); + ]); }); test('acknowledgeWarning removes warning on children', () => { @@ -142,24 +127,6 @@ describe('annotations', () => { Annotations.of(c1).acknowledgeWarning('MESSAGE2', 'I Ack this'); // THEN - const assembly = app.synth(); - let acknoledgement: any = {}; - for (const s of Object.values(assembly.manifest.artifacts ?? {})) { - for (const [_path, md] of Object.entries(s.metadata ?? {})) { - for (const x of md) { - if (x.type === 'aws:cdk:acknowledge') { - acknoledgement.message = x.data as string; - } - } - } - } - expect(acknoledgement).toEqual({ - message: { - id: 'MESSAGE2', - scopes: ['S1/C1', 'S1/C1/C2'], - message: 'I Ack this', - }, - }); - expect(getWarnings(assembly)).toEqual([]); + expect(getWarnings(app.synth())).toEqual([]); }); -}); +}); \ No newline at end of file diff --git a/packages/aws-cdk-lib/cx-api/lib/cloud-artifact.ts b/packages/aws-cdk-lib/cx-api/lib/cloud-artifact.ts index d052be16be58f..01fefe31934fc 100644 --- a/packages/aws-cdk-lib/cx-api/lib/cloud-artifact.ts +++ b/packages/aws-cdk-lib/cx-api/lib/cloud-artifact.ts @@ -1,7 +1,6 @@ import type { CloudAssembly } from './cloud-assembly'; import { MetadataEntryResult, SynthesisMessage, SynthesisMessageLevel } from './metadata'; import * as cxschema from '../../cloud-assembly-schema'; -import type { LogMessageObjectMetadataEntry } from '../../cloud-assembly-schema/lib/cloud-assembly/metadata-schema'; /** * Artifact properties for CloudFormation stacks. @@ -111,36 +110,12 @@ export class CloudArtifact { private renderMessages() { const messages = new Array(); - // messageId: scopes[] - const acks = new Map(); - // collect any acknowledgements - for (const [_id, metadata] of Object.entries(this.manifest.metadata || { })) { - for (const entry of metadata) { - if (entry.type === cxschema.ArtifactMetadataEntryType.ACKNOWLEDGE) { - const data = (entry.data as cxschema.AcknowledgementMetadataEntry); - acks.set(data.id, data.scopes); - } - } - } for (const [id, metadata] of Object.entries(this.manifest.metadata || { })) { for (const entry of metadata) { let level: SynthesisMessageLevel; - let entryData: string | undefined = undefined; switch (entry.type) { case cxschema.ArtifactMetadataEntryType.WARN: - if (typeof entry.data === 'object' && 'id' in entry.data) { - const data = entry.data as LogMessageObjectMetadataEntry; - const ack = acks.get(data.id); - // if the scope has been acknowledged then don't add the warning - if (ack && ack.includes(data.scope)) { - continue; - } else { - entryData = `${data.id}: ${data.message}`; - level = SynthesisMessageLevel.WARNING; - break; - } - } level = SynthesisMessageLevel.WARNING; break; case cxschema.ArtifactMetadataEntryType.ERROR: @@ -153,14 +128,7 @@ export class CloudArtifact { continue; } - messages.push({ - level, - entry: { - ...entry, - data: entryData ?? entry.data, - }, - id, - }); + messages.push({ level, entry, id }); } } From f0ce23bc8a381af9bf810f5c12753c4c98465c16 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 18 Aug 2023 15:11:14 +0200 Subject: [PATCH 13/26] Don't need these breaking changes anymore --- allowed-breaking-changes.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index 05476bbaa4c9f..99fbbed4cc359 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -165,11 +165,6 @@ change-return-type:aws-cdk-lib.aws_ec2.SecurityGroup.determineRuleScope # broken only in non-JS/TS, where that was not previously usable strengthened:aws-cdk-lib.aws_s3_deployment.BucketDeploymentProps -# weakened added additional types to a union type, meaning all new props are optional -weakened:aws-cdk-lib.cloud_assembly_schema.MetadataEntry -weakened:aws-cdk-lib.cx_api.MetadataEntryResult -weakened:@aws-cdk/cloud-assembly-schema.MetadataEntry -weakened:@aws-cdk/cx-api.MetadataEntryResult # Fix for non-working property # loadBalancerName is used to idenitfy a Classic Load Balancer # However TaskSet only works with modern Application or Network LBs From 641202917bf84c6162d2797de31b8c49c85d904a Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 18 Aug 2023 15:13:28 +0200 Subject: [PATCH 14/26] Comment --- packages/aws-cdk-lib/assertions/test/annotations.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/aws-cdk-lib/assertions/test/annotations.test.ts b/packages/aws-cdk-lib/assertions/test/annotations.test.ts index a890382d26fbc..fea53bc6ce845 100644 --- a/packages/aws-cdk-lib/assertions/test/annotations.test.ts +++ b/packages/aws-cdk-lib/assertions/test/annotations.test.ts @@ -209,6 +209,7 @@ class MyAspect implements IAspect { }; protected warn(node: IConstruct, message: string): void { + // Use construct ID as suppression string, just to make it unique easily Annotations.of(node).addWarningV2(node.node.id, message); } From df8a8538d0cc61e03ebc06b0d1f9b4c03f1897c0 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 18 Aug 2023 15:25:34 +0200 Subject: [PATCH 15/26] Update adr --- .../core/lib/adr/acknowledge-warnings.md | 75 ++++++------------- 1 file changed, 21 insertions(+), 54 deletions(-) diff --git a/packages/aws-cdk-lib/core/lib/adr/acknowledge-warnings.md b/packages/aws-cdk-lib/core/lib/adr/acknowledge-warnings.md index 49c9a27d8bb4a..a672c7fade958 100644 --- a/packages/aws-cdk-lib/core/lib/adr/acknowledge-warnings.md +++ b/packages/aws-cdk-lib/core/lib/adr/acknowledge-warnings.md @@ -27,7 +27,7 @@ Warning messages are typically used for two types of messages: certain scenarios. Users who are developing CDK applications may want to always use the `--strict` -CLI argument to turn all warnings into errors. Currently this is probably not +CLI argument to turn all warnings into errors. Currently this is not possible since as soon as one warning is not applicable they can no longer use `--strict`. Ideally they should be able to `acknowledge` warnings (similar to what they can do with notices) indicating that the warning is not applicable to @@ -55,14 +55,7 @@ Annotations.of(scope).addWarning('This is a warning'); ``` `Annotations.of(scope)` creates a new instance of `Annotations` every time so -there is no way to keep track of warnings added. For example, doing something -like this would not work because the `Annotations` instance created with -`acknowledgeWarning` would be different than the one created with `addWarning`. - -```ts -Annotations.of(scope).addWarning('This is a warning'); -Annotations.of(scope).acknowledgeWarning('This is a warning'); -``` +there is no way to keep track of warnings added inside this object. Currently the storage mechanism for `Annotations` is the `Node` metadata. The warning is added to the node as node metadata which is read by the CLI after @@ -96,54 +89,28 @@ acknowledge a specific warning by `id`. public acknowledgeWarning(id: string, message?: string): void ``` -We will continue to use the node metadata as the storage mechanism and will add -a new metadata type `aws:cdk:acknowledge` to store information on -acknowledgements. +We want warning acknowledgement to work both before and after the warning is actually emitted. -At synthesis when we collect all of the annotation messages, we will filter out -any messages that have an acknowledgement. +We therefore do the following: -Storing the acknowledgements in the metadata will also allow us to report on -warnings that were acknowledged by the user (info will be stored in the -assembly). +- When a warning is acknowledged on a construct, we iterate over the already + added warnings and remove matching warnings. At the same time, we record + that this warning has been acknowledged on this construct tree. +- When a warning is added, we only add it if it hasn't been acknowledged in + that location. ## Alternatives -### Alternative storage mechanism +### Filter at synthesis time -One alternative that was considered was to implement some alternative -intermediary storage mechanism. This storage mechanism would allow storing all -warnings and acknowledgements in a special construct that was created once per -app. It would look something like this (pseudo code) +Right now, we are filtering in every method call. We could defer the filtering +to when we translate construct tree metadata to cloud assembly metadata. -```ts -class AnnotationManager extends Construct { - private constructor(scope: Construct) { - attachCustomSynthesis(this, { - onSynthesize: () => { - this.warnings.forEach(warning => node.addMetadata(...)) - } - } - } - - public addWarning() { - if (!this.acks.has()) { - this.warnings.set(); - } - } - public ack() { - if (this.warnings.has()) { - this.warnings.delete(); - } - this.acks.add(); - } -} -``` +Right now, removing existing metadata entries requires accessing private APIs +of the `constructs` library, which is not an ideal situation. -The problem with this method is represented by the `attachCustomSynthesis` in -the example. This same applies for if we used `addValidation` or `Aspects`. -Annotations can be added _after_ that which means they would not be added or -acknowledged. +At the same time, implementing it there would allow us to generate a suppression +report. We can always still do this. ### Use context and remove metadata @@ -173,9 +140,9 @@ There are two issues with this alternative. types since currently there is no unique identifier for a given metadata entry (other than the message). -## Consequences -With the recommended solution the only major consequence is that it requires -updating the `metadata-schema`, but we can do this in a non-breaking way -(addition of new types). The alternatives may also require changes to the schema -as well. +### Acknowledge via context + +There is currently no way to configure suppressed warnings via context, which +might be useful to do at the application level (using `cdk.json`). We can always +add this feature in the future if desired. From 5aba9ac341c0d9ca7ecfd27874ab4d7fe1bbeb68 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 18 Aug 2023 15:26:59 +0200 Subject: [PATCH 16/26] Undo test change --- packages/aws-cdk-lib/core/test/private/tree-metadata.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk-lib/core/test/private/tree-metadata.test.ts b/packages/aws-cdk-lib/core/test/private/tree-metadata.test.ts index 0a9770e59be22..482cc6301b833 100644 --- a/packages/aws-cdk-lib/core/test/private/tree-metadata.test.ts +++ b/packages/aws-cdk-lib/core/test/private/tree-metadata.test.ts @@ -369,8 +369,8 @@ describe('tree metadata', () => { const warn = treenode.node.metadata.find((md) => { return md.type === cxschema.ArtifactMetadataEntryType.WARN - && /Forcing an inspect error/.test(md.data.message as string) - && /mycfnresource/.test(md.data.message as string); + && /Forcing an inspect error/.test(md.data as string) + && /mycfnresource/.test(md.data as string); }); expect(warn).toBeDefined(); From ee9fc76f8eda8e320989e774284d4c59cf06da09 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 18 Aug 2023 15:32:10 +0200 Subject: [PATCH 17/26] addWarning -> addWarningV2 --- packages/aws-cdk-lib/aws-apigateway/lib/method.ts | 4 ++-- .../lib/fargate/scheduled-fargate-task.ts | 8 ++++---- packages/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.ts | 6 +++--- packages/aws-cdk-lib/core/lib/stack.ts | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/aws-cdk-lib/aws-apigateway/lib/method.ts b/packages/aws-cdk-lib/aws-apigateway/lib/method.ts index 5b4ca0428f86f..4c453350f0c34 100644 --- a/packages/aws-cdk-lib/aws-apigateway/lib/method.ts +++ b/packages/aws-cdk-lib/aws-apigateway/lib/method.ts @@ -288,7 +288,7 @@ export class Method extends Resource { public addMethodResponse(methodResponse: MethodResponse): void { const mr = this.methodResponses.find((x) => x.statusCode === methodResponse.statusCode); if (mr) { - 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.`); + 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.`); } this.methodResponses.push(methodResponse); } @@ -512,4 +512,4 @@ export enum AuthorizationType { function pathForArn(path: string): string { return path.replace(/\{[^\}]*\}/g, '*'); // replace path parameters (like '{bookId}') with asterisk -} \ No newline at end of file +} diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts b/packages/aws-cdk-lib/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts index ec6d1dd1ebd2b..c7725538ca7e2 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts @@ -89,16 +89,16 @@ export class ScheduledFargateTask extends ScheduledTaskBase { } if (props.taskDefinition) { - Annotations.of(this).addWarning('Property \'taskDefinition\' is ignored, use \'scheduledFargateTaskDefinitionOptions\' or \'scheduledFargateTaskImageOptions\' instead.'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-ecs-patterns:propertyIgnored', 'Property \'taskDefinition\' is ignored, use \'scheduledFargateTaskDefinitionOptions\' or \'scheduledFargateTaskImageOptions\' instead.'); } if (props.cpu) { - Annotations.of(this).addWarning('Property \'cpu\' is ignored, use \'scheduledFargateTaskImageOptions.cpu\' instead.'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-ecs-patterns:propertyIgnored', 'Property \'cpu\' is ignored, use \'scheduledFargateTaskImageOptions.cpu\' instead.'); } if (props.memoryLimitMiB) { - Annotations.of(this).addWarning('Property \'memoryLimitMiB\' is ignored, use \'scheduledFargateTaskImageOptions.memoryLimitMiB\' instead.'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-ecs-patterns:propertyIgnored', 'Property \'memoryLimitMiB\' is ignored, use \'scheduledFargateTaskImageOptions.memoryLimitMiB\' instead.'); } if (props.runtimePlatform) { - Annotations.of(this).addWarning('Property \'runtimePlatform\' is ignored.'); + Annotations.of(this).addWarningV2('@aws-cdk/aws-ecs-patterns:propertyIgnored', 'Property \'runtimePlatform\' is ignored.'); } // Use the EcsTask as the target of the EventRule diff --git a/packages/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.ts b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.ts index 3c87c4db08227..7245eac6c5640 100644 --- a/packages/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.ts @@ -137,15 +137,15 @@ export class Bundling implements cdk.BundlingOptions { // Warn users if they are trying to rely on global versions of the SDK that aren't available in // their environment. if (isV2Runtime && externals.some((pkgName) => pkgName.startsWith('@aws-sdk/'))) { - cdk.Annotations.of(scope).addWarning('If you are relying on AWS SDK v3 to be present in the Lambda environment already, please explicitly configure a NodeJS runtime of Node 18 or higher.'); + cdk.Annotations.of(scope).addWarningV2('@aws-cdk/aws-lambda-nodejs:sdkV3NotInRuntime', 'If you are relying on AWS SDK v3 to be present in the Lambda environment already, please explicitly configure a NodeJS runtime of Node 18 or higher.'); } else if (externals.includes('aws-sdk')) { - cdk.Annotations.of(scope).addWarning('If you are relying on AWS SDK v2 to be present in the Lambda environment already, please explicitly configure a NodeJS runtime of Node 16 or lower.'); + cdk.Annotations.of(scope).addWarningV2('@aws-cdk/aws-lambda-nodejs:sdkV2NotInRuntime', 'If you are relying on AWS SDK v2 to be present in the Lambda environment already, please explicitly configure a NodeJS runtime of Node 16 or lower.'); } // Warn users if they are using a runtime that may change and are excluding any dependencies from // bundling. if (externals.length && props.runtime?.isVariable) { - cdk.Annotations.of(scope).addWarning('When using NODEJS_LATEST the runtime version may change as new runtimes are released, this may affect the availability of packages shipped with the environment. Ensure that any external dependencies are available through layers or specify a specific runtime version.'); + cdk.Annotations.of(scope).addWarningV2('@aws-cdk/aws-lambda-nodejs:variableRuntimeExternals', 'When using NODEJS_LATEST the runtime version may change as new runtimes are released, this may affect the availability of packages shipped with the environment. Ensure that any external dependencies are available through layers or specify a specific runtime version.'); } this.externals = [ diff --git a/packages/aws-cdk-lib/core/lib/stack.ts b/packages/aws-cdk-lib/core/lib/stack.ts index 0f347acb85928..61c85fc110677 100644 --- a/packages/aws-cdk-lib/core/lib/stack.ts +++ b/packages/aws-cdk-lib/core/lib/stack.ts @@ -1087,7 +1087,7 @@ export class Stack extends Construct implements ITaggable { const message = `Template size ${verb} limit: ${templateData.length}/${TEMPLATE_BODY_MAXIMUM_SIZE}. ${advice}.`; - Annotations.of(this).addWarning(message); + Annotations.of(this).addWarningV2('@aws-cdk/core:Stack.templateSize', message); } fs.writeFileSync(outPath, templateData); From 56625ac1252e76332f67d7e6a023e4abe5526707 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 18 Aug 2023 15:33:29 +0200 Subject: [PATCH 18/26] Linterrrr --- packages/aws-cdk-lib/core/lib/annotations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/core/lib/annotations.ts b/packages/aws-cdk-lib/core/lib/annotations.ts index 808f7dd33692b..9953122e2cb0b 100644 --- a/packages/aws-cdk-lib/core/lib/annotations.ts +++ b/packages/aws-cdk-lib/core/lib/annotations.ts @@ -1,7 +1,7 @@ import { IConstruct, MetadataEntry } from 'constructs'; +import { App } from './app'; import * as cxschema from '../../cloud-assembly-schema'; import * as cxapi from '../../cx-api'; -import { App } from './app'; /** * Includes API for attaching annotations such as warning messages to constructs. From cd72788c43cb36f813b495d43812d4629bafec09 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 21 Aug 2023 11:15:37 +0200 Subject: [PATCH 19/26] Update tests --- .../test/application.test.ts | 4 ++-- packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-service.test.ts | 2 +- .../aws-ecs/test/fargate/fargate-service.test.ts | 2 +- .../aws-cdk-lib/aws-lambda-nodejs/test/bundling.test.ts | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application.test.ts b/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application.test.ts index ce8b7cd68bc39..77ea14a2ccef7 100644 --- a/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application.test.ts +++ b/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application.test.ts @@ -511,8 +511,8 @@ describe('Scope based Associations with Application with Cross Region/Account', const stageStack = new cdk.Stack(stage, 'MyStack'); application.associateAllStacksInScope(stage); Annotations.fromStack(stageStack).hasWarning('*', - 'SCAppRegistry:CrossRegionAssociation: AppRegistry does not support cross region associations, deployment might fail if there is cross region stacks in the app.' - + ' Application region region, stack region region1'); + 'AppRegistry does not support cross region associations, deployment might fail if there is cross region stacks in the app.' + + ' Application region region, stack region region1 [ack: @aws-cdk/servicecatalogappregistry:CrossRegionAssociation]'); }); }); diff --git a/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-service.test.ts index a1a99dac84e13..704e959b669a0 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-service.test.ts @@ -1256,7 +1256,7 @@ describe('ec2 service', () => { app.synth(); // THEN - expect(service.node.metadata[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); + expect(service.node.metadata[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller. [ack: @aws-cdk/aws-ecs:externalDeploymentController]'); expect(service.node.metadata[1].data).toEqual('Deployment circuit breaker requires the ECS deployment controller.'); }); diff --git a/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts index a122a0a3839c8..a4a3a623c4400 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts @@ -660,7 +660,7 @@ describe('fargate service', () => { // THEN expect(service.node.metadata[1].data).toEqual('Deployment circuit breaker requires the ECS deployment controller.'); - expect(service.node.metadata[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); + expect(service.node.metadata[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller. [ack: @aws-cdk/aws-ecs:externalDeploymentController]'); }); diff --git a/packages/aws-cdk-lib/aws-lambda-nodejs/test/bundling.test.ts b/packages/aws-cdk-lib/aws-lambda-nodejs/test/bundling.test.ts index d865c8be34a0a..e7aa97b4c6a8d 100644 --- a/packages/aws-cdk-lib/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/aws-cdk-lib/aws-lambda-nodejs/test/bundling.test.ts @@ -878,7 +878,7 @@ test('bundling with <= Node16 warns when sdk v3 is external', () => { }); Annotations.fromStack(stack).hasWarning('*', - 'If you are relying on AWS SDK v3 to be present in the Lambda environment already, please explicitly configure a NodeJS runtime of Node 18 or higher.', + 'If you are relying on AWS SDK v3 to be present in the Lambda environment already, please explicitly configure a NodeJS runtime of Node 18 or higher. [ack: @aws-cdk/aws-lambda-nodejs:sdkV3NotInRuntime]', ); }); @@ -893,7 +893,7 @@ test('bundling with >= Node18 warns when sdk v3 is external', () => { }); Annotations.fromStack(stack).hasWarning('*', - 'If you are relying on AWS SDK v2 to be present in the Lambda environment already, please explicitly configure a NodeJS runtime of Node 16 or lower.', + 'If you are relying on AWS SDK v2 to be present in the Lambda environment already, please explicitly configure a NodeJS runtime of Node 16 or lower. [ack: @aws-cdk/aws-lambda-nodejs:sdkV2NotInRuntime]', ); }); @@ -908,7 +908,7 @@ test('bundling with NODEJS_LATEST warns when any dependencies are external', () }); Annotations.fromStack(stack).hasWarning('*', - 'When using NODEJS_LATEST the runtime version may change as new runtimes are released, this may affect the availability of packages shipped with the environment. Ensure that any external dependencies are available through layers or specify a specific runtime version.', + 'When using NODEJS_LATEST the runtime version may change as new runtimes are released, this may affect the availability of packages shipped with the environment. Ensure that any external dependencies are available through layers or specify a specific runtime version. [ack: @aws-cdk/aws-lambda-nodejs:variableRuntimeExternals]', ); }); From 87b3b127aad11b840db8b0c422500d4fe9040adb Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 21 Aug 2023 13:42:26 +0200 Subject: [PATCH 20/26] More tests --- .../test/application-associator.test.ts | 10 +++++----- .../aws-ecs/test/external/external-service.test.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application-associator.test.ts b/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application-associator.test.ts index 4a66f1b52c6ba..41db8ade2d91d 100644 --- a/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application-associator.test.ts +++ b/packages/@aws-cdk/aws-servicecatalogappregistry-alpha/test/application-associator.test.ts @@ -191,7 +191,7 @@ describe('Scope based Associations with Application with Cross Region/Account', const crossAccountStack = new cdk.Stack(app, 'crossRegionStack', { env: { account: 'account', region: 'region' }, }); - Annotations.fromStack(crossAccountStack).hasWarning('*', 'SCAppRegistry:AssociationSkipped: Cross-account stack detected but application sharing and association will be skipped because cross-account option is not enabled.'); + 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]'); }); test('ApplicationAssociator with cross account stacks inside cdkApp does not give warning if associateCrossAccountStacks is set to true', () => { @@ -222,8 +222,8 @@ describe('Scope based Associations with Application with Cross Region/Account', const crossRegionStack = new cdk.Stack(app, 'crossRegionStack', { env: { account: 'account', region: 'region' }, }); - Annotations.fromStack(crossRegionStack).hasWarning('*', 'SCAppRegistry:CrossRegionAssociation: AppRegistry does not support cross region associations, deployment might fail if there is cross region stacks in the app.' - + ' Application region region2, stack region region'); + Annotations.fromStack(crossRegionStack).hasWarning('*', 'AppRegistry does not support cross region associations, deployment might fail if there is cross region stacks in the app.' + + ' Application region region2, stack region region [ack: @aws-cdk/servicecatalogappregistry:CrossRegionAssociation]'); }); 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', const crossRegionStack = new cdk.Stack(app, 'crossRegionStack', { env: { account: 'account', region: 'region' }, }); - Annotations.fromStack(crossRegionStack).hasWarning('*', 'SCAppRegistry:EnvironmentAgnosticStack: Environment agnostic stack determined, AppRegistry association might not work as expected in case you deploy cross-region or cross-account stack.'); + 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]'); }); 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', }); app.synth(); Annotations.fromStack(pipelineStack).hasWarning('*', - 'SCAppRegistry:StackNotAssociated: Associate Stage: SampleStage to ensure all stacks in your cdk app are associated with AppRegistry. You can use ApplicationAssociator.associateStage to associate any stage.'); + '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]'); }); test('Cdk App Containing Pipeline with stage and stage associated successfully gets synthesized', () => { diff --git a/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts index 21e6f06b6c4e8..5fb7b8f53b80f 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts @@ -1,4 +1,4 @@ -import { Template } from '../../../assertions'; + import * as autoscaling from '../../../aws-autoscaling'; import * as cloudwatch from '../../../aws-cloudwatch'; import * as ec2 from '../../../aws-ec2'; @@ -576,7 +576,7 @@ describe('external service', () => { app.synth(); // THEN - expect(service.node.metadata[0].data).toEqual('Deployment circuit breaker requires the ECS deployment controller.'); + expect(service.node.metadata[0].data).toEqual('Deployment circuit breaker requires the ECS deployment controller. [ack: @aws-cdk/aws-ecs:externalDeploymentController]'); expect(service.node.metadata[1].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller. [ack: @aws-cdk/aws-ecs:externalDeploymentController]'); }); From c0b5319f399599e14d44d9c92cb3b143e75f5a7c Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 21 Aug 2023 14:55:05 +0200 Subject: [PATCH 21/26] Oops --- .../aws-cdk-lib/aws-ecs/test/external/external-service.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts index 5fb7b8f53b80f..aa380063cb6cf 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts @@ -1,4 +1,4 @@ - +import { Template } from '../../../assertions'; import * as autoscaling from '../../../aws-autoscaling'; import * as cloudwatch from '../../../aws-cloudwatch'; import * as ec2 from '../../../aws-ec2'; From a95dde95acd5cfb18a9e6602dd8b3431196e57cf Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 21 Aug 2023 16:57:08 +0200 Subject: [PATCH 22/26] Test again --- .../aws-ecs/test/external/external-service.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts index aa380063cb6cf..c504a5c7aa0e4 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts @@ -576,8 +576,9 @@ describe('external service', () => { app.synth(); // THEN - expect(service.node.metadata[0].data).toEqual('Deployment circuit breaker requires the ECS deployment controller. [ack: @aws-cdk/aws-ecs:externalDeploymentController]'); - expect(service.node.metadata[1].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller. [ack: @aws-cdk/aws-ecs:externalDeploymentController]'); - + expect(service.node.metadata.map((m) => m.data.data)).toEqual([ + 'taskDefinition and launchType are blanked out when using external deployment controller. [ack: @aws-cdk/aws-ecs:externalDeploymentController]', + 'Deployment circuit breaker requires the ECS deployment controller. [ack: @aws-cdk/aws-ecs:externalDeploymentController]', + ]); }); }); From da5211d395941c6ed651202935a5aa171c7cd478 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 22 Aug 2023 10:37:58 +0200 Subject: [PATCH 23/26] Foutje --- .../aws-cdk-lib/aws-ecs/test/external/external-service.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts index c504a5c7aa0e4..0933623452318 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts @@ -576,7 +576,7 @@ describe('external service', () => { app.synth(); // THEN - expect(service.node.metadata.map((m) => m.data.data)).toEqual([ + expect(service.node.metadata.map((m) => m.data)).toEqual([ 'taskDefinition and launchType are blanked out when using external deployment controller. [ack: @aws-cdk/aws-ecs:externalDeploymentController]', 'Deployment circuit breaker requires the ECS deployment controller. [ack: @aws-cdk/aws-ecs:externalDeploymentController]', ]); From 4fb27701704b1d9c4c4837dfc8ab5dbdedc86ca8 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 22 Aug 2023 13:43:13 +0200 Subject: [PATCH 24/26] This is not a warning --- .../aws-cdk-lib/aws-ecs/test/external/external-service.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts index 0933623452318..5e64a502b225c 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/external/external-service.test.ts @@ -578,7 +578,7 @@ describe('external service', () => { // THEN expect(service.node.metadata.map((m) => m.data)).toEqual([ 'taskDefinition and launchType are blanked out when using external deployment controller. [ack: @aws-cdk/aws-ecs:externalDeploymentController]', - 'Deployment circuit breaker requires the ECS deployment controller. [ack: @aws-cdk/aws-ecs:externalDeploymentController]', + 'Deployment circuit breaker requires the ECS deployment controller.', ]); }); }); From 696945d6e5124aa179e110a461b5bff9add2e4c4 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 23 Aug 2023 11:55:50 +0200 Subject: [PATCH 25/26] Exclude "__proto__" from fast-check --- packages/aws-cdk-lib/core/test/fn.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk-lib/core/test/fn.test.ts b/packages/aws-cdk-lib/core/test/fn.test.ts index 8356e14cfcd0a..a69b71f27a4fc 100644 --- a/packages/aws-cdk-lib/core/test/fn.test.ts +++ b/packages/aws-cdk-lib/core/test/fn.test.ts @@ -18,7 +18,7 @@ function asyncTest(cb: () => Promise): () => void { }; } -const nonEmptyString = fc.string({ minLength: 1, maxLength: 16 }); +const nonEmptyString = fc.string({ minLength: 1, maxLength: 16 }).filter((x) => x !== '__proto__'); const tokenish = fc.array(nonEmptyString, { minLength: 2, maxLength: 2 }).map(arr => ({ [arr[0]]: arr[1] })); const anyValue = fc.oneof(nonEmptyString, tokenish); @@ -122,7 +122,7 @@ describe('fn', () => { _.isEqual(stack.resolve(Fn.join(delimiter, [...prefix, stringToken(obj), ...suffix])), { 'Fn::Join': [delimiter, [prefix.join(delimiter), obj, suffix.join(delimiter)]] }), ), - { verbose: true, seed: 1539874645005, path: '0:0:0:0:0:0:0:0:0' }, + { verbose: true }, ); })); From 63610238e0122e4c506f443219cab47d9cc8ff13 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 23 Aug 2023 13:02:45 +0200 Subject: [PATCH 26/26] Fix examples --- packages/aws-cdk-lib/core/lib/annotations.ts | 8 ++--- packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md | 34 +++++++++---------- .../aws-cdk-lib/rosetta/default.ts-fixture | 1 + 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/packages/aws-cdk-lib/core/lib/annotations.ts b/packages/aws-cdk-lib/core/lib/annotations.ts index 9953122e2cb0b..405c3891f0953 100644 --- a/packages/aws-cdk-lib/core/lib/annotations.ts +++ b/packages/aws-cdk-lib/core/lib/annotations.ts @@ -32,8 +32,8 @@ export class Annotations { * The acknowledgement will apply to all child scopes * * @example - * declare const stack: Stack; - * Annotations.of(stack).acknowledgeWarning('SomeWarningId', 'This warning can be ignored because...'); + * declare const myConstruct: Construct; + * Annotations.of(myConstruct).acknowledgeWarning('SomeWarningId', 'This warning can be ignored because...'); * * @param id - the id of the warning message to acknowledge * @param message optional message to explain the reason for acknowledgement @@ -60,8 +60,8 @@ export class Annotations { * the CLI, and will not cause `--strict` mode to fail synthesis. * * @example - * declare const construct: Construct; - * Annotations.of(construct).addWarningV2('my-library:Construct.someWarning', 'Some message explaining the warning'); + * declare const myConstruct: Construct; + * Annotations.of(myConstruct).addWarningV2('my-library:Construct.someWarning', 'Some message explaining the warning'); * * @param id the unique identifier for the warning. This can be used to acknowledge the warning * @param message The warning message. diff --git a/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md b/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md index 32f485172e01f..d97798ee21c88 100644 --- a/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md +++ b/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md @@ -56,8 +56,8 @@ Flags come in three types: | [@aws-cdk/core:includePrefixInUniqueNameGeneration](#aws-cdkcoreincludeprefixinuniquenamegeneration) | Include the stack prefix in the stack name generation process | 2.84.0 | (fix) | | [@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig](#aws-cdkaws-autoscalinggeneratelaunchtemplateinsteadoflaunchconfig) | Generate a launch template when creating an AutoScalingGroup | 2.88.0 | (fix) | | [@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby](#aws-cdkaws-opensearchserviceenableopensearchmultiazwithstandby) | Enables support for Multi-AZ with Standby deployment for opensearch domains | 2.88.0 | (default) | -| [@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion](#aws-cdkaws-lambda-nodejsuselatestruntimeversion) | Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default | V2NEXT | (default) | | [@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId](#aws-cdkaws-efsmounttargetorderinsensitivelogicalid) | When enabled, mount targets will have a stable logicalId that is linked to the associated subnet. | V2NEXT | (fix) | +| [@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion](#aws-cdkaws-lambda-nodejsuselatestruntimeversion) | Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default | V2NEXT | (default) | @@ -1056,14 +1056,16 @@ multi-az with standby enabled. **Compatibility with old behavior:** Pass `capacity.multiAzWithStandbyEnabled: false` to `Domain` construct to restore the old behavior. -### @aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion +### @aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId -*Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default* (default) +*When enabled, mount targets will have a stable logicalId that is linked to the associated subnet.* (fix) -If this is set, and a `runtime` prop is not passed to, Lambda NodeJs -functions will us the latest version of the runtime provided by the Lambda -service. Do not use this if you your lambda function is reliant on dependencies -shipped as part of the runtime environment. +When this feature flag is enabled, each mount target will have a stable +logicalId that is linked to the associated subnet. If the flag is set to +false then the logicalIds of the mount targets can change if the number of +subnets changes. + +Set this flag to false for existing mount targets. | Since | Default | Recommended | @@ -1071,19 +1073,15 @@ shipped as part of the runtime environment. | (not in v1) | | | | V2NEXT | `false` | `true` | -**Compatibility with old behavior:** Pass `runtime: lambda.Runtime.NODEJS_16_X` to `Function` construct to restore the previous behavior. - - -### @aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId -*When enabled, mount targets will have a stable logicalId that is linked to the associated subnet.* (fix) +### @aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion -When this feature flag is enabled, each mount target will have a stable -logicalId that is linked to the associated subnet. If the flag is set to -false then the logicalIds of the mount targets can change if the number of -subnets changes. +*Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default* (default) -Set this flag to false for existing mount targets. +If this is set, and a `runtime` prop is not passed to, Lambda NodeJs +functions will us the latest version of the runtime provided by the Lambda +service. Do not use this if you your lambda function is reliant on dependencies +shipped as part of the runtime environment. | Since | Default | Recommended | @@ -1091,5 +1089,7 @@ Set this flag to false for existing mount targets. | (not in v1) | | | | V2NEXT | `false` | `true` | +**Compatibility with old behavior:** Pass `runtime: lambda.Runtime.NODEJS_16_X` to `Function` construct to restore the previous behavior. + diff --git a/packages/aws-cdk-lib/rosetta/default.ts-fixture b/packages/aws-cdk-lib/rosetta/default.ts-fixture index f15b52668477f..4b594b61d4233 100644 --- a/packages/aws-cdk-lib/rosetta/default.ts-fixture +++ b/packages/aws-cdk-lib/rosetta/default.ts-fixture @@ -11,6 +11,7 @@ import * as sns from 'aws-cdk-lib/aws-sns'; import * as sqs from 'aws-cdk-lib/aws-sqs'; import * as s3 from 'aws-cdk-lib/aws-s3'; import { + Annotations, App, Aws, CfnCondition,