diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..01d970570 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "eslint.useFlatConfig": false, +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-api/lib/artifacts/cloudformation-artifact.ts b/packages/@aws-cdk/cloud-assembly-api/lib/artifacts/cloudformation-artifact.ts index 4bfd0e853..dd90de551 100644 --- a/packages/@aws-cdk/cloud-assembly-api/lib/artifacts/cloudformation-artifact.ts +++ b/packages/@aws-cdk/cloud-assembly-api/lib/artifacts/cloudformation-artifact.ts @@ -175,7 +175,7 @@ export class CloudFormationStackArtifact extends CloudArtifact { // We get the tags from 'properties' if available (cloud assembly format >= 6.0.0), otherwise // from the stack metadata - this.tags = properties.tags ?? this.tagsFromMetadata(); + this.tags = properties.tags ?? {}; this.notificationArns = properties.notificationArns; this.assumeRoleArn = properties.assumeRoleArn; this.assumeRoleExternalId = properties.assumeRoleExternalId; @@ -215,16 +215,6 @@ export class CloudFormationStackArtifact extends CloudArtifact { } return this._template; } - - private tagsFromMetadata() { - const ret: Record = {}; - for (const metadataEntry of this.findMetadataByType(cxschema.ArtifactMetadataEntryType.STACK_TAGS)) { - for (const tag of (metadataEntry.data ?? []) as cxschema.StackTagsMetadataEntry) { - ret[tag.key] = tag.value; - } - } - return ret; - } } /** diff --git a/packages/@aws-cdk/cloud-assembly-api/lib/cloud-artifact.ts b/packages/@aws-cdk/cloud-assembly-api/lib/cloud-artifact.ts index 0f064adbe..e278d1c1f 100644 --- a/packages/@aws-cdk/cloud-assembly-api/lib/cloud-artifact.ts +++ b/packages/@aws-cdk/cloud-assembly-api/lib/cloud-artifact.ts @@ -1,3 +1,5 @@ +import * as fs from 'fs'; +import * as path from 'path'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import type { CloudAssembly } from './cloud-assembly'; import type { MetadataEntryResult, SynthesisMessage } from './metadata'; @@ -36,6 +38,34 @@ export interface AwsCloudFormationStackProperties { * Represents an artifact within a cloud assembly. */ export class CloudArtifact { + /** + * Read the metadata for the given artifact + * + * HISTORICAL OR PRIVATE USE ONLY + * + * This is publicly exposed as a static function for downstream libraries that + * don't use the `CloudAssembly`/`CloudArtifact` API, yet still need to read + * an artifact's metadata. + * + * 99% of consumers should just access `artifact.metadata`. + */ + public static readMetadata(assemblyDirectory: string, x: cxschema.ArtifactManifest): Record { + const ret: Record = {}; + if (x.additionalMetadataFile) { + Object.assign(ret, JSON.parse(fs.readFileSync(path.join(assemblyDirectory, x.additionalMetadataFile), 'utf-8'))); + } + + for (const [p, entries] of Object.entries(x.metadata ?? {})) { + if (ret[p]) { + ret[p].push(...entries); + } else { + ret[p] = entries; + } + } + + return ret; + } + /** * Returns a subclass of `CloudArtifact` based on the artifact type defined in the artifact manifest. * @@ -77,6 +107,13 @@ export class CloudArtifact { this._dependencyIDs = manifest.dependencies || []; } + /** + * Returns the metadata associated with this Cloud Artifact + */ + public get metadata() { + return CloudArtifact.readMetadata(this.assembly.directory, this.manifest); + } + /** * Returns all the artifacts that this artifact depends on. */ @@ -100,11 +137,13 @@ export class CloudArtifact { * @returns all the metadata entries of a specific type in this artifact. */ public findMetadataByType(type: string): MetadataEntryResult[] { + const metadata = this.metadata; + const result = new Array(); - for (const path of Object.keys(this.manifest.metadata || {})) { - for (const entry of (this.manifest.metadata || {})[path]) { + for (const p of Object.keys(metadata || {})) { + for (const entry of (metadata || {})[p]) { if (entry.type === type) { - result.push({ path, ...entry }); + result.push({ path: p, ...entry }); } } } @@ -114,7 +153,7 @@ export class CloudArtifact { private renderMessages() { const messages = new Array(); - for (const [id, metadata] of Object.entries(this.manifest.metadata || { })) { + for (const [id, metadata] of Object.entries(this.metadata || { })) { for (const entry of metadata) { let level: SynthesisMessageLevel; switch (entry.type) { diff --git a/packages/@aws-cdk/cloud-assembly-api/test/stack-artifact.test.ts b/packages/@aws-cdk/cloud-assembly-api/test/stack-artifact.test.ts index 6378abc12..88013f8c4 100644 --- a/packages/@aws-cdk/cloud-assembly-api/test/stack-artifact.test.ts +++ b/packages/@aws-cdk/cloud-assembly-api/test/stack-artifact.test.ts @@ -58,7 +58,7 @@ test('read tags from artifact properties', () => { expect(assembly.getStackByName('Stack').tags).toEqual({ foo: 'bar' }); }); -test('stack tags get uppercased when written to Cloud Assembly', () => { +test('already uppercased stack tags get left alone', () => { // Backwards compatibility test // GIVEN builder.addArtifact('Stack', { @@ -67,7 +67,7 @@ test('stack tags get uppercased when written to Cloud Assembly', () => { '/Stack': [ { type: 'aws:cdk:stack-tags', - data: [{ key: 'foo', value: 'bar' }], + data: [{ Key: 'foo', Value: 'bar' } as any], }, ], }, @@ -92,7 +92,7 @@ test('stack tags get uppercased when written to Cloud Assembly', () => { ]); }); -test('already uppercased stack tags get left alone', () => { +test('tags are NO LONGER read from stack metadata', () => { // Backwards compatibility test // GIVEN builder.addArtifact('Stack', { @@ -101,7 +101,7 @@ test('already uppercased stack tags get left alone', () => { '/Stack': [ { type: 'aws:cdk:stack-tags', - data: [{ Key: 'foo', Value: 'bar' } as any], + data: [{ key: 'foo', value: 'bar' }], }, ], }, @@ -111,22 +111,10 @@ test('already uppercased stack tags get left alone', () => { const assembly = builder.buildAssembly(); // THEN - const manifestStructure = JSON.parse(fs.readFileSync(path.join(assembly.directory, 'manifest.json'), { encoding: 'utf-8' })); - expect(manifestStructure.artifacts.Stack.metadata['/Stack']).toEqual([ - { - type: 'aws:cdk:stack-tags', - data: [ - { - // Note: uppercase due to historical accident - Key: 'foo', - Value: 'bar', - }, - ], - }, - ]); + expect(assembly.getStackByName('Stack').tags).toEqual({}); }); -test('read tags from stack metadata', () => { +test('stack metadata can be read both from artifact and a file', () => { // Backwards compatibility test // GIVEN builder.addArtifact('Stack', { @@ -134,18 +122,51 @@ test('read tags from stack metadata', () => { metadata: { '/Stack': [ { - type: 'aws:cdk:stack-tags', - data: [{ key: 'foo', value: 'bar' }], + type: 'custom:metadata', + data: 'custom 1', }, ], }, + additionalMetadataFile: 'addl-meta.json', }); + fs.writeFileSync(path.join(builder.outdir, 'addl-meta.json'), JSON.stringify({ + '/Stack': [ + { + type: 'custom:metadata', + data: 'custom 2', + }, + ], + '/Stack/Sub': [ + { + type: 'custom:metadata', + data: 'custom 3', + }, + ], + }), 'utf-8'); + // WHEN const assembly = builder.buildAssembly(); // THEN - expect(assembly.getStackByName('Stack').tags).toEqual({ foo: 'bar' }); + expect(assembly.getStackByName('Stack').metadata).toEqual({ + '/Stack': [ + { + type: 'custom:metadata', + data: 'custom 2', + }, + { + type: 'custom:metadata', + data: 'custom 1', + }, + ], + '/Stack/Sub': [ + { + type: 'custom:metadata', + data: 'custom 3', + }, + ], + }); }); test('user friendly id is the assembly display name', () => { diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/artifact-schema.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/artifact-schema.ts index 22e14379d..7e90d5eec 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/artifact-schema.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/artifact-schema.ts @@ -61,6 +61,12 @@ export interface AwsCloudFormationStackProperties { /** * Values for CloudFormation stack tags that should be passed when the stack is deployed. * + * N.B.: Tags are also written to stack metadata, under the path of the Stack + * construct. Only in CDK CLI v1 are those tags found in metadata used for + * actual deployments; in all stable versions of CDK only the stack tags + * directly found in the `tags` property of `AwsCloudFormationStack` artifact + * (i.e., this property) are used. + * * @default - No tags */ readonly tags?: { [id: string]: string }; diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/schema.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/schema.ts index c4911745b..f19379027 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/schema.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/schema.ts @@ -86,10 +86,26 @@ export interface ArtifactManifest { /** * Associated metadata. * + * Metadata can be stored directly in the assembly manifest, as well as in a + * separate file (see `additionalMetadataFile`). It should prefer to be stored + * in the additional file, as that will reduce the size of the assembly + * manifest in cases of a lot of metdata (which CDK does emit by default). + * * @default - no metadata. */ readonly metadata?: { [path: string]: MetadataEntry[] }; + /** + * A file with additional metadata entries. + * + * The schema of this file is exactly the same as the type of the `metadata` field. + * In other words, that file contains an object mapping construct paths to arrays + * of metadata entries. + * + * @default - no additional metadata + */ + readonly additionalMetadataFile?: string; + /** * IDs of artifacts that must be deployed before this artifact. * diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts index ac1020df7..202487fab 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts @@ -72,7 +72,7 @@ export interface LoadManifestOptions { /** * Protocol utility class. */ -export class Manifest { +export abstract class Manifest { /** * Validates and saves the cloud assembly manifest to file. * @@ -80,7 +80,7 @@ export class Manifest { * @param filePath - output file path. */ public static saveAssemblyManifest(manifest: assembly.AssemblyManifest, filePath: string) { - Manifest.saveManifest(manifest, filePath, ASSEMBLY_SCHEMA, Manifest.patchStackTagsOnWrite); + Manifest.saveManifest(manifest, filePath, ASSEMBLY_SCHEMA); } /** @@ -102,7 +102,7 @@ export class Manifest { * @param filePath - output file path. */ public static saveAssetManifest(manifest: assets.AssetManifest, filePath: string) { - Manifest.saveManifest(manifest, filePath, ASSETS_SCHEMA, Manifest.patchStackTagsOnRead); + Manifest.saveManifest(manifest, filePath, ASSETS_SCHEMA); } /** @@ -111,7 +111,7 @@ export class Manifest { * @param filePath - path to the manifest file. */ public static loadAssetManifest(filePath: string): assets.AssetManifest { - return this.loadManifest(filePath, ASSETS_SCHEMA); + return Manifest.loadManifest(filePath, ASSETS_SCHEMA); } /** @@ -266,27 +266,56 @@ export class Manifest { } /** - * This requires some explaining... + * Fix the casing of stack tags entries * - * We previously used `{ Key, Value }` for the object that represents a stack tag. (Notice the casing) - * @link https://github.com/aws/aws-cdk/blob/v1.27.0/packages/aws-cdk/lib/api/cxapp/stacks.ts#L427. + * At the very beginning of the CDK we used to emit stack tags as an object with + * `{ Key, Value }` keys; this had the "advantage" that we could stick those + * tags directly into the `CreateChangeSet` call. * - * When that object moved to this package, it had to be JSII compliant, which meant the property - * names must be `camelCased`, and not `PascalCased`. This meant it no longer matches the structure in the `manifest.json` file. - * In order to support current manifest files, we have to translate the `PascalCased` representation to the new `camelCased` one. + * Then we later on used jsii on the assembly schema and we were forced to type + * the in-memory objects as `{ key, value }` with lowercase letters. Now the + * objects have a different on-disk and in-memory format, and we need to convert + * between them. * - * Note that the serialization itself still writes `PascalCased` because it relates to how CloudFormation expects it. + * For backwards compatibility reasons, we used to convert lowercase in-memory + * to uppercase on-disk variant until very recently. This is now unnecessary, + * since no officially supported CDK tools read the stack tags from the + * metadata; the CLI and toolkit library read stack tags from the artifact + * properties. * - * Ideally, we would start writing the `camelCased` and translate to how CloudFormation expects it when needed. But this requires nasty - * backwards-compatibility code and it just doesn't seem to be worth the effort. + * So although we don't emit uppercase stack tag objects anymore, we might still read + * manifests that have them. Because the manifest we read must pass JSON Schema + * validation (which expects lowercase tag objects), we have to fix the casing + * of these objects after reading from disk and before validating. + * + * That's what this function does. */ private static patchStackTagsOnRead(this: void, manifest: assembly.AssemblyManifest) { - return Manifest.replaceStackTags(manifest, (tags) => - tags.map((diskTag: any) => ({ - key: diskTag.Key, - value: diskTag.Value, - })), - ); + const artifacts = Object.values(manifest.artifacts ?? {}) + .filter(artifact => artifact.type === assembly.ArtifactType.AWS_CLOUDFORMATION_STACK); + + for (const artifact of artifacts) { + const tagMetadata = Object.values(artifact.metadata ?? {}) + .flatMap(x => x) + .filter(entry => entry.type === assembly.ArtifactMetadataEntryType.STACK_TAGS); + + for (const entry of tagMetadata) { + const tags = entry.data as unknown as assembly.StackTagsMetadataEntry[] | undefined; + for (const tag of tags ?? []) { + const t: any = tag; + if ('Key' in t) { + t.key = t.Key; + delete t.Key; + } + if ('Value' in t) { + t.value = t.Value; + delete t.Value; + } + } + } + } + + return manifest; } /** @@ -317,86 +346,6 @@ export class Manifest { throw new Error(`ExternalId is not allowed inside '${key}'`); } } - - /** - * See explanation on `patchStackTagsOnRead` - * - * Translate stack tags metadata if it has the "right" casing. - */ - private static patchStackTagsOnWrite(this: void, manifest: assembly.AssemblyManifest) { - return Manifest.replaceStackTags(manifest, (tags) => - tags.map( - (memTag) => - // Might already be uppercased (because stack synthesis generates it in final form yet) - ('Key' in memTag ? memTag : { Key: memTag.key, Value: memTag.value }) as any, - ), - ); - } - - /** - * Recursively replace stack tags in the stack metadata - */ - private static replaceStackTags( - manifest: assembly.AssemblyManifest, - fn: Endofunctor, - ): assembly.AssemblyManifest { - // Need to add in the `noUndefined`s because otherwise jest snapshot tests are going to freak out - // about the keys with values that are `undefined` (even though they would never be JSON.stringified) - return noUndefined({ - ...manifest, - artifacts: mapValues(manifest.artifacts, (artifact) => { - if (artifact.type !== assembly.ArtifactType.AWS_CLOUDFORMATION_STACK) { - return artifact; - } - return noUndefined({ - ...artifact, - metadata: mapValues(artifact.metadata, (metadataEntries) => - metadataEntries.map((metadataEntry) => { - if ( - metadataEntry.type !== assembly.ArtifactMetadataEntryType.STACK_TAGS || - !metadataEntry.data - ) { - return metadataEntry; - } - return { - ...metadataEntry, - data: fn(metadataEntry.data as assembly.StackTagsMetadataEntry), - }; - }), - ), - } as assembly.ArtifactManifest); - }), - }); - } - - private constructor() { - } -} - -type Endofunctor = (x: A) => A; - -function mapValues( - xs: Record | undefined, - fn: (x: A) => B, -): Record | undefined { - if (!xs) { - return undefined; - } - const ret: Record | undefined = {}; - for (const [k, v] of Object.entries(xs)) { - ret[k] = fn(v); - } - return ret; -} - -function noUndefined(xs: A): A { - const ret: any = {}; - for (const [k, v] of Object.entries(xs)) { - if (v !== undefined) { - ret[k] = v; - } - } - return ret; } function stripEnumErrors(errors: jsonschema.ValidationError[]) { diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json index 036731f2d..2e3e5fa40 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json @@ -49,7 +49,7 @@ "type": "string" }, "metadata": { - "description": "Associated metadata. (Default - no metadata.)", + "description": "Associated metadata.\n\nMetadata can be stored directly in the assembly manifest, as well as in a\nseparate file (see `additionalMetadataFile`). It should prefer to be stored\nin the additional file, as that will reduce the size of the assembly\nmanifest in cases of a lot of metdata (which CDK does emit by default). (Default - no metadata.)", "type": "object", "additionalProperties": { "type": "array", @@ -58,6 +58,10 @@ } } }, + "additionalMetadataFile": { + "description": "A file with additional metadata entries.\n\nThe schema of this file is exactly the same as the type of the `metadata` field.\nIn other words, that file contains an object mapping construct paths to arrays\nof metadata entries. (Default - no additional metadata)", + "type": "string" + }, "dependencies": { "description": "IDs of artifacts that must be deployed before this artifact. (Default - no dependencies.)", "type": "array", @@ -351,7 +355,7 @@ } }, "tags": { - "description": "Values for CloudFormation stack tags that should be passed when the stack is deployed. (Default - No tags)", + "description": "Values for CloudFormation stack tags that should be passed when the stack is deployed.\n\nN.B.: Tags are also written to stack metadata, under the path of the Stack\nconstruct. Only in CDK CLI v1 are those tags found in metadata used for\nactual deployments; in all stable versions of CDK only the stack tags\ndirectly found in the `tags` property of `AwsCloudFormationStack` artifact\n(i.e., this property) are used. (Default - No tags)", "type": "object", "additionalProperties": { "type": "string" diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/version.json b/packages/@aws-cdk/cloud-assembly-schema/schema/version.json index 19e41429f..5b7c6a5df 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/schema/version.json +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/version.json @@ -1,5 +1,5 @@ { - "schemaHash": "c0092c55280aa03c568e1d591f2034783597e942a400d1d58d7f05a8215f51f4", + "schemaHash": "22c511a4ddd185761b8d56ac21d48c8384873ffe4b953b3567654746f8dd26f1", "$comment": "Do not hold back the version on additions: jsonschema validation of the manifest by the consumer will trigger errors on unexpected fields.", - "revision": 50 + "revision": 52 } \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/lib/runner/private/cloud-assembly.ts b/packages/@aws-cdk/integ-runner/lib/runner/private/cloud-assembly.ts index 146b2a3d5..7587d4e62 100644 --- a/packages/@aws-cdk/integ-runner/lib/runner/private/cloud-assembly.ts +++ b/packages/@aws-cdk/integ-runner/lib/runner/private/cloud-assembly.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import type { FileManifestEntry, DockerImageManifestEntry } from '@aws-cdk/cdk-assets-lib'; import { AssetManifest } from '@aws-cdk/cdk-assets-lib'; +import { CloudArtifact } from '@aws-cdk/cloud-assembly-api'; import type { AssemblyManifest, AwsCloudFormationStackProperties, ArtifactManifest, MetadataEntry, AssetManifestProperties, ContainerImageAssetMetadataEntry, FileAssetMetadataEntry } from '@aws-cdk/cloud-assembly-schema'; import { Manifest, ArtifactType, ArtifactMetadataEntryType } from '@aws-cdk/cloud-assembly-schema'; import * as fs from 'fs-extra'; @@ -166,7 +167,7 @@ export class AssemblyManifestReader { */ private assetsFromAssemblyManifest(artifact: ArtifactManifest): (ContainerImageAssetMetadataEntry | FileAssetMetadataEntry)[] { const assets: (ContainerImageAssetMetadataEntry | FileAssetMetadataEntry)[] = []; - for (const metadata of Object.values(artifact.metadata ?? {})) { + for (const metadata of Object.values(CloudArtifact.readMetadata(this.directory, artifact) ?? {})) { metadata.forEach(data => { if (data.type === ArtifactMetadataEntryType.ASSET) { const asset = (data.data as ContainerImageAssetMetadataEntry | FileAssetMetadataEntry); diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/cloud-assembly/stack-collection.ts b/packages/@aws-cdk/toolkit-lib/lib/api/cloud-assembly/stack-collection.ts index 1198f0cd3..fb6bdd04f 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/cloud-assembly/stack-collection.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/cloud-assembly/stack-collection.ts @@ -42,7 +42,11 @@ export class StackCollection { id: stack.displayName ?? stack.id, name: stack.stackName, environment: stack.environment, - metadata: stack.manifest.metadata, + + // Might be huge so load it lazily + get metadata() { + return stack.metadata; + }, dependencies: [], }; diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/refactoring/exclude.ts b/packages/@aws-cdk/toolkit-lib/lib/api/refactoring/exclude.ts index 3c9d23aa7..7e45f55ce 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/refactoring/exclude.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/refactoring/exclude.ts @@ -1,3 +1,4 @@ +import { CloudArtifact } from '@aws-cdk/cloud-assembly-api'; import type { AssemblyManifest } from '@aws-cdk/cloud-assembly-schema'; import { ArtifactMetadataEntryType, ArtifactType } from '@aws-cdk/cloud-assembly-schema'; import type { ResourceLocation as CfnResourceLocation } from '@aws-sdk/client-cloudformation'; @@ -20,12 +21,12 @@ abstract class AbstractExcludeList implements ExcludeList { export class ManifestExcludeList extends AbstractExcludeList { private readonly excludedLocations: CfnResourceLocation[]; - constructor(manifest: AssemblyManifest) { + constructor(directory: string, manifest: AssemblyManifest) { super(); - this.excludedLocations = this.getExcludedLocations(manifest); + this.excludedLocations = this.getExcludedLocations(directory, manifest); } - private getExcludedLocations(asmManifest: AssemblyManifest): CfnResourceLocation[] { + private getExcludedLocations(directory: string, asmManifest: AssemblyManifest): CfnResourceLocation[] { // First, we need to filter the artifacts to only include CloudFormation stacks const stackManifests = Object.entries(asmManifest.artifacts ?? {}).filter( ([_, manifest]) => manifest.type === ArtifactType.AWS_CLOUDFORMATION_STACK, @@ -33,7 +34,8 @@ export class ManifestExcludeList extends AbstractExcludeList { const result: CfnResourceLocation[] = []; for (let [stackName, manifest] of stackManifests) { - const locations = Object.values(manifest.metadata ?? {}) + // FIXME: OH NO + const locations = Object.values(CloudArtifact.readMetadata(directory, manifest)) // Then pick only the resources in each stack marked with DO_NOT_REFACTOR .filter((entries) => entries.some((entry) => entry.type === ArtifactMetadataEntryType.DO_NOT_REFACTOR && entry.data === true), diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/resource-metadata/resource-metadata.ts b/packages/@aws-cdk/toolkit-lib/lib/api/resource-metadata/resource-metadata.ts index a655d4ef5..4f9c85835 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/resource-metadata/resource-metadata.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/resource-metadata/resource-metadata.ts @@ -24,7 +24,7 @@ export interface ResourceMetadata { * @returns The resource metadata, or undefined if the resource was not found */ export function resourceMetadata(stack: CloudFormationStackArtifact, logicalId: string): ResourceMetadata | undefined { - const metadata = stack.manifest?.metadata; + const metadata = stack.metadata; if (!metadata) { return undefined; } diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/stack-events/stack-activity-monitor.ts b/packages/@aws-cdk/toolkit-lib/lib/api/stack-events/stack-activity-monitor.ts index 17bef32de..5756039f3 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/stack-events/stack-activity-monitor.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/stack-events/stack-activity-monitor.ts @@ -179,7 +179,7 @@ export class StackActivityMonitor { } private findMetadataFor(logicalId: string | undefined) { - const metadata = this.stack.manifest?.metadata; + const metadata = this.stack.metadata; if (!logicalId || !metadata) { return undefined; } diff --git a/packages/@aws-cdk/toolkit-lib/test/actions/list.test.ts b/packages/@aws-cdk/toolkit-lib/test/actions/list.test.ts index cd8855d9c..e21292de7 100644 --- a/packages/@aws-cdk/toolkit-lib/test/actions/list.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/actions/list.test.ts @@ -496,6 +496,7 @@ const MOCK_STACK_A: TestStackArtifact = { '/Test-Stack-A': [ { type: ArtifactMetadataEntryType.STACK_TAGS, + data: [], }, ], }, @@ -508,6 +509,7 @@ const MOCK_STACK_B: TestStackArtifact = { '/Test-Stack-B': [ { type: ArtifactMetadataEntryType.STACK_TAGS, + data: [], }, ], }, @@ -530,6 +532,7 @@ const MOCK_STACK_C: TestStackArtifact = { '/Test-Stack-C': [ { type: ArtifactMetadataEntryType.STACK_TAGS, + data: [], }, ], }, diff --git a/packages/@aws-cdk/toolkit-lib/test/api/refactoring/exclude.test.ts b/packages/@aws-cdk/toolkit-lib/test/api/refactoring/exclude.test.ts index b658ebc80..aa76f7f24 100644 --- a/packages/@aws-cdk/toolkit-lib/test/api/refactoring/exclude.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/api/refactoring/exclude.test.ts @@ -72,7 +72,7 @@ describe('ManifestExcludeList', () => { }, }; - const excludeList = new ManifestExcludeList(manifest as any); + const excludeList = new ManifestExcludeList('/', manifest as any); expect(excludeList.isExcluded(resource1)).toBe(true); expect(excludeList.isExcluded(resource2)).toBe(true); @@ -91,7 +91,7 @@ describe('ManifestExcludeList', () => { }, }; - const excludeList = new ManifestExcludeList(manifest as any); + const excludeList = new ManifestExcludeList('/', manifest as any); expect(excludeList.isExcluded(resource1)).toBe(false); }); }); diff --git a/packages/aws-cdk/lib/cli/cdk-toolkit.ts b/packages/aws-cdk/lib/cli/cdk-toolkit.ts index 726e5f55b..fa34e2f55 100644 --- a/packages/aws-cdk/lib/cli/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cli/cdk-toolkit.ts @@ -195,7 +195,7 @@ export class CdkToolkit { public async metadata(stackName: string, json: boolean) { const stacks = await this.selectSingleStackByName(stackName); - await printSerializedObject(this.ioHost.asIoHelper(), stacks.firstStack.manifest.metadata ?? {}, json); + await printSerializedObject(this.ioHost.asIoHelper(), stacks.firstStack.metadata ?? {}, json); } public async acknowledge(noticeId: string) { diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index 5559f5243..f43942cea 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -1740,13 +1740,8 @@ class MockStack { stackName: 'Test-Stack-A', template: { Resources: { TemplateName: 'Test-Stack-A' } }, env: 'aws://123456789012/bermuda-triangle-1', - metadata: { - '/Test-Stack-A': [ - { - type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, - data: [{ key: 'Foo', value: 'Bar' }], - }, - ], + properties: { + tags: { Foo: 'Bar' }, }, displayName: 'Test-Stack-A-Display-Name', }; @@ -1754,26 +1749,16 @@ class MockStack { stackName: 'Test-Stack-B', template: { Resources: { TemplateName: 'Test-Stack-B' } }, env: 'aws://123456789012/bermuda-triangle-1', - metadata: { - '/Test-Stack-B': [ - { - type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, - data: [{ key: 'Baz', value: 'Zinga!' }], - }, - ], + properties: { + tags: { Baz: 'Zinga!' }, }, }; public static readonly MOCK_STACK_C: TestStackArtifact = { stackName: 'Test-Stack-C', template: { Resources: { TemplateName: 'Test-Stack-C' } }, env: 'aws://123456789012/bermuda-triangle-1', - metadata: { - '/Test-Stack-C': [ - { - type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, - data: [{ key: 'Baz', value: 'Zinga!' }], - }, - ], + properties: { + tags: { Baz: 'Zinga!' }, }, displayName: 'Test-Stack-A/Test-Stack-C', }; @@ -1781,13 +1766,8 @@ class MockStack { stackName: 'Test-Stack-D', template: { Resources: { TemplateName: 'Test-Stack-D' } }, env: 'aws://123456789012/bermuda-triangle-1', - metadata: { - '/Test-Stack-D': [ - { - type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, - data: [{ key: 'Baz', value: 'Zinga!' }], - }, - ], + properties: { + tags: { Baz: 'Zinga!' }, }, depends: [MockStack.MOCK_STACK_C.stackName], }; @@ -1832,13 +1812,8 @@ class MockStack { notificationArns: ['arn:aws:sns:bermuda-triangle-1337:123456789012:MyTopic'], template: { Resources: { TemplateName: 'Test-Stack-Notification-Arns' } }, env: 'aws://123456789012/bermuda-triangle-1337', - metadata: { - '/Test-Stack-Notification-Arns': [ - { - type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, - data: [{ key: 'Foo', value: 'Bar' }], - }, - ], + properties: { + tags: { Foo: 'Bar' }, }, }; @@ -1847,13 +1822,8 @@ class MockStack { notificationArns: ['arn:1337:123456789012:sns:bad'], template: { Resources: { TemplateName: 'Test-Stack-Bad-Notification-Arns' } }, env: 'aws://123456789012/bermuda-triangle-1337', - metadata: { - '/Test-Stack-Bad-Notification-Arns': [ - { - type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, - data: [{ key: 'Foo', value: 'Bar' }], - }, - ], + properties: { + tags: { Foo: 'Bar' }, }, }; }