diff --git a/.projenrc.ts b/.projenrc.ts index 51d4d891d..f6604eeb2 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -655,6 +655,7 @@ const tmpToolkitHelpers = configureProject( 'fast-check', ], deps: [ + cloudAssemblySchema.name, 'archiver', 'glob', 'semver', diff --git a/packages/@aws-cdk/tmp-toolkit-helpers/.projen/deps.json b/packages/@aws-cdk/tmp-toolkit-helpers/.projen/deps.json index 1ab5e8add..5dfc8c071 100644 --- a/packages/@aws-cdk/tmp-toolkit-helpers/.projen/deps.json +++ b/packages/@aws-cdk/tmp-toolkit-helpers/.projen/deps.json @@ -100,6 +100,10 @@ "version": "5.6", "type": "build" }, + { + "name": "@aws-cdk/cloud-assembly-schema", + "type": "runtime" + }, { "name": "archiver", "type": "runtime" diff --git a/packages/@aws-cdk/tmp-toolkit-helpers/.projen/tasks.json b/packages/@aws-cdk/tmp-toolkit-helpers/.projen/tasks.json index 6e3d82edd..7a24858f6 100644 --- a/packages/@aws-cdk/tmp-toolkit-helpers/.projen/tasks.json +++ b/packages/@aws-cdk/tmp-toolkit-helpers/.projen/tasks.json @@ -37,7 +37,7 @@ }, "steps": [ { - "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --no-deprecated --dep=dev,peer,prod,optional --filter=@cdklabs/eslint-plugin,@stylistic/eslint-plugin,@types/archiver,@types/jest,@types/semver,eslint-config-prettier,eslint-import-resolver-typescript,eslint-plugin-import,eslint-plugin-jest,eslint-plugin-jsdoc,eslint-plugin-prettier,fast-check,jest,projen,ts-jest,archiver,glob,semver,uuid" + "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --no-deprecated --dep=dev,peer,prod,optional --filter=@cdklabs/eslint-plugin,@stylistic/eslint-plugin,@types/archiver,@types/jest,@types/semver,eslint-config-prettier,eslint-import-resolver-typescript,eslint-plugin-import,eslint-plugin-jest,eslint-plugin-jsdoc,eslint-plugin-prettier,fast-check,jest,projen,ts-jest,@aws-cdk/cloud-assembly-schema,archiver,glob,semver,uuid" } ] }, @@ -77,7 +77,7 @@ "name": "gather-versions", "steps": [ { - "exec": "node -e \"require(path.join(path.dirname(require.resolve('cdklabs-projen-project-types')), 'yarn', 'gather-versions.exec.js'))\" @aws-cdk/tmp-toolkit-helpers MAJOR --deps ", + "exec": "node -e \"require(path.join(path.dirname(require.resolve('cdklabs-projen-project-types')), 'yarn', 'gather-versions.exec.js'))\" @aws-cdk/tmp-toolkit-helpers MAJOR --deps @aws-cdk/cloud-assembly-schema", "receiveArgs": true } ] diff --git a/packages/@aws-cdk/tmp-toolkit-helpers/package.json b/packages/@aws-cdk/tmp-toolkit-helpers/package.json index bf1da8251..8c3adf945 100644 --- a/packages/@aws-cdk/tmp-toolkit-helpers/package.json +++ b/packages/@aws-cdk/tmp-toolkit-helpers/package.json @@ -55,6 +55,7 @@ "typescript": "5.6" }, "dependencies": { + "@aws-cdk/cloud-assembly-schema": "^0.0.0", "archiver": "^7.0.1", "glob": "^11.0.1", "semver": "^7.7.1", diff --git a/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/stack-activity.ts b/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/stack-activity.ts index e194d13f4..690abfbcf 100644 --- a/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/stack-activity.ts +++ b/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/stack-activity.ts @@ -1,7 +1,7 @@ -import type { MetadataEntry } from '@aws-cdk/cloud-assembly-schema'; import type { CloudFormationStackArtifact } from '@aws-cdk/cx-api'; import type { StackEvent } from '@aws-sdk/client-cloudformation'; import type { StackProgress } from './progress'; +import type { ResourceMetadata } from '../../resource-metadata/resource-metadata'; /** * Payload when stack monitoring is starting or stopping for a given stack deployment. @@ -48,6 +48,9 @@ export interface StackActivity { /** * Additional resource metadata + * + * This information is only available if the information is available in the current cloud assembly. + * I.e. no `metadata` will not be available for resource deletion events. */ readonly metadata?: ResourceMetadata; @@ -56,8 +59,3 @@ export interface StackActivity { */ readonly progress: StackProgress; } - -export interface ResourceMetadata { - entry: MetadataEntry; - constructPath: string; -} diff --git a/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/messages.ts b/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/messages.ts index 360af241d..cde7203cd 100644 --- a/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/messages.ts +++ b/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/messages.ts @@ -21,7 +21,7 @@ import type { FileWatchEvent, WatchSettings } from '../payloads/watch'; * - X900-X999 are reserved for results */ export const IO = { - // Defaults + // Defaults (0000) DEFAULT_TOOLKIT_INFO: make.info({ code: 'CDK_TOOLKIT_I0000', description: 'Default info messages emitted from the Toolkit', @@ -35,7 +35,7 @@ export const IO = { description: 'Default warning messages emitted from the Toolkit', }), - // 1: Synth + // 1: Synth (1xxx) CDK_TOOLKIT_I1000: make.info({ code: 'CDK_TOOLKIT_I1000', description: 'Provides synthesis times.', @@ -57,7 +57,7 @@ export const IO = { interface: 'AssemblyData', }), - // 2: List + // 2: List (2xxx) CDK_TOOLKIT_I2901: make.result({ code: 'CDK_TOOLKIT_I2901', description: 'Provides details on the selected stacks and their dependencies', @@ -70,9 +70,9 @@ export const IO = { description: 'Resource import failed', }), - // 4: Diff + // 4: Diff (4xxx) - // 5: Deploy & Watch + // 5: Deploy & Watch (5xxx) CDK_TOOLKIT_I5000: make.info({ code: 'CDK_TOOLKIT_I5000', description: 'Provides deployment times', @@ -129,14 +129,13 @@ export const IO = { description: 'Confirm deploy security sensitive changes', interface: 'DeployConfirmationRequest', }), - CDK_TOOLKIT_I5100: make.info({ code: 'CDK_TOOLKIT_I5100', description: 'Stack deploy progress', interface: 'StackDeployProgress', }), - // Assets + // Assets (52xx) CDK_TOOLKIT_I5210: make.trace({ code: 'CDK_TOOLKIT_I5210', description: 'Started building a specific asset', @@ -147,7 +146,6 @@ export const IO = { description: 'Building the asset has completed', interface: 'Duration', }), - CDK_TOOLKIT_I5220: make.trace({ code: 'CDK_TOOLKIT_I5220', description: 'Started publishing a specific asset', @@ -159,7 +157,7 @@ export const IO = { interface: 'Duration', }), - // Watch + // Watch (53xx) CDK_TOOLKIT_I5310: make.debug({ code: 'CDK_TOOLKIT_I5310', description: 'The computed settings used for file watching', @@ -189,7 +187,9 @@ export const IO = { description: 'Queued watch deployment started', }), - // Stack Monitor + // Hotswap (54xx) + + // Stack Monitor (55xx) CDK_TOOLKIT_I5501: make.info({ code: 'CDK_TOOLKIT_I5501', description: 'Stack Monitoring: Start monitoring of a single stack', @@ -206,7 +206,7 @@ export const IO = { interface: 'StackMonitoringControlEvent', }), - // Success + // Success (59xx) CDK_TOOLKIT_I5900: make.result({ code: 'CDK_TOOLKIT_I5900', description: 'Deployment results on success', @@ -232,7 +232,7 @@ export const IO = { interface: 'ErrorPayload', }), - // 6: Rollback + // 6: Rollback (6xxx) CDK_TOOLKIT_I6000: make.info({ code: 'CDK_TOOLKIT_I6000', description: 'Provides rollback times', @@ -254,7 +254,7 @@ export const IO = { interface: 'ErrorPayload', }), - // 7: Destroy + // 7: Destroy (7xxx) CDK_TOOLKIT_I7000: make.info({ code: 'CDK_TOOLKIT_I7000', description: 'Provides destroy times', @@ -297,7 +297,7 @@ export const IO = { interface: 'ErrorPayload', }), - // 9: Bootstrap + // 9: Bootstrap (9xxx) CDK_TOOLKIT_I9000: make.info({ code: 'CDK_TOOLKIT_I9000', description: 'Provides bootstrap times', diff --git a/packages/@aws-cdk/tmp-toolkit-helpers/src/api/resource-metadata/resource-metadata.ts b/packages/@aws-cdk/tmp-toolkit-helpers/src/api/resource-metadata/resource-metadata.ts new file mode 100644 index 000000000..fc5e4b1d1 --- /dev/null +++ b/packages/@aws-cdk/tmp-toolkit-helpers/src/api/resource-metadata/resource-metadata.ts @@ -0,0 +1,57 @@ +import { ArtifactMetadataEntryType, type MetadataEntry } from '@aws-cdk/cloud-assembly-schema'; +import type { CloudFormationStackArtifact } from '@aws-cdk/cx-api'; + +/** + * Metadata entry for a resource within a CloudFormation stack + */ +export interface ResourceMetadata { + /** + * The resource's metadata as declared in the cloud assembly + */ + readonly entry: MetadataEntry; + /** + * The construct path of the resource + */ + readonly constructPath: string; +} + +/** + * Attempts to read metadata for resources from a CloudFormation stack artifact + * + * @param stack The CloudFormation stack to read from + * @param logicalId The logical ID of the resource to read + * + * @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; + if (!metadata) { + return undefined; + } + + for (const path of Object.keys(metadata)) { + const entry = metadata[path] + .filter((e) => e.type === ArtifactMetadataEntryType.LOGICAL_ID) + .find((e) => e.data === logicalId); + if (entry) { + return { + entry, + constructPath: simplifyConstructPath(path, stack.stackName), + }; + } + } + return undefined; +} + +function simplifyConstructPath(path: string, stackName: string) { + path = path.replace(/\/Resource$/, ''); + path = path.replace(/^\//, ''); // remove "/" prefix + + // remove "/" prefix + if (stackName) { + if (path.startsWith(stackName + '/')) { + path = path.slice(stackName.length + 1); + } + } + return path; +} diff --git a/packages/aws-cdk/lib/api/deployments/hotswap-deployments.ts b/packages/aws-cdk/lib/api/deployments/hotswap-deployments.ts index e90b63fa1..b02daedc4 100644 --- a/packages/aws-cdk/lib/api/deployments/hotswap-deployments.ts +++ b/packages/aws-cdk/lib/api/deployments/hotswap-deployments.ts @@ -103,8 +103,7 @@ export async function tryHotswapDeployment( const currentTemplate = await loadCurrentTemplateWithNestedStacks(stackArtifact, sdk); const evaluateCfnTemplate = new EvaluateCloudFormationTemplate({ - stackName: stackArtifact.stackName, - template: stackArtifact.template, + stackArtifact, parameters: assetParams, account: resolvedEnv.account, region: resolvedEnv.region, diff --git a/packages/aws-cdk/lib/api/evaluate-cloudformation-template.ts b/packages/aws-cdk/lib/api/evaluate-cloudformation-template.ts index 4ccdb84e3..2a94143c4 100644 --- a/packages/aws-cdk/lib/api/evaluate-cloudformation-template.ts +++ b/packages/aws-cdk/lib/api/evaluate-cloudformation-template.ts @@ -1,6 +1,9 @@ +import type { CloudFormationStackArtifact } from '@aws-cdk/cx-api'; import type { Export, ListExportsCommandOutput, StackResourceSummary } from '@aws-sdk/client-cloudformation'; import type { SDK } from './aws-auth'; import type { NestedStackTemplates } from './deployments'; +import { resourceMetadata } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/resource-metadata/resource-metadata'; +import type { ResourceMetadata } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/resource-metadata/resource-metadata'; import { ToolkitError } from '../toolkit/error'; export interface ListStackResources { @@ -85,8 +88,9 @@ export interface ResourceDefinition { } export interface EvaluateCloudFormationTemplateProps { - readonly stackName: string; - readonly template: Template; + readonly stackArtifact: CloudFormationStackArtifact; + readonly stackName?: string; + readonly template?: Template; readonly parameters: { [parameterName: string]: string }; readonly account: string; readonly region: string; @@ -98,6 +102,7 @@ export interface EvaluateCloudFormationTemplateProps { } export class EvaluateCloudFormationTemplate { + public readonly stackArtifact: CloudFormationStackArtifact; private readonly stackName: string; private readonly template: Template; private readonly context: { [k: string]: any }; @@ -114,8 +119,9 @@ export class EvaluateCloudFormationTemplate { private cachedUrlSuffix: string | undefined; constructor(props: EvaluateCloudFormationTemplateProps) { - this.stackName = props.stackName; - this.template = props.template; + this.stackArtifact = props.stackArtifact; + this.stackName = props.stackName ?? props.stackArtifact.stackName; + this.template = props.template ?? props.stackArtifact.template; this.context = { 'AWS::AccountId': props.account, 'AWS::Region': props.region, @@ -147,6 +153,7 @@ export class EvaluateCloudFormationTemplate { ) { const evaluatedParams = await this.evaluateCfnExpression(nestedStackParameters); return new EvaluateCloudFormationTemplate({ + stackArtifact: this.stackArtifact, stackName, template: nestedTemplate, parameters: evaluatedParams, @@ -312,6 +319,10 @@ export class EvaluateCloudFormationTemplate { return this.template.Resources?.[logicalId]?.Properties?.[propertyName]; } + public metadataFor(logicalId: string): ResourceMetadata | undefined { + return resourceMetadata(this.stackArtifact, logicalId); + } + private references(logicalId: string, templateElement: any): boolean { if (typeof templateElement === 'string') { return logicalId === templateElement; diff --git a/packages/aws-cdk/lib/api/logs/find-cloudwatch-logs.ts b/packages/aws-cdk/lib/api/logs/find-cloudwatch-logs.ts index 7cf48a756..ca1a3b356 100644 --- a/packages/aws-cdk/lib/api/logs/find-cloudwatch-logs.ts +++ b/packages/aws-cdk/lib/api/logs/find-cloudwatch-logs.ts @@ -53,8 +53,7 @@ export async function findCloudWatchLogGroups( const listStackResources = new LazyListStackResources(sdk, stackArtifact.stackName); const evaluateCfnTemplate = new EvaluateCloudFormationTemplate({ - stackName: stackArtifact.stackName, - template: stackArtifact.template, + stackArtifact, parameters: {}, account: resolvedEnv.account, region: resolvedEnv.region, diff --git a/packages/aws-cdk/lib/api/stack-events/stack-activity-monitor.ts b/packages/aws-cdk/lib/api/stack-events/stack-activity-monitor.ts index f7e3584af..36fb26850 100644 --- a/packages/aws-cdk/lib/api/stack-events/stack-activity-monitor.ts +++ b/packages/aws-cdk/lib/api/stack-events/stack-activity-monitor.ts @@ -1,10 +1,10 @@ import * as util from 'util'; -import { ArtifactMetadataEntryType } from '@aws-cdk/cloud-assembly-schema'; import type { CloudFormationStackArtifact } from '@aws-cdk/cx-api'; -import type { ResourceMetadata, StackActivity, StackMonitoringControlEvent } from '@aws-cdk/tmp-toolkit-helpers'; +import type { StackActivity, StackMonitoringControlEvent } from '@aws-cdk/tmp-toolkit-helpers'; import * as uuid from 'uuid'; import { StackEventPoller } from './stack-event-poller'; +import { resourceMetadata } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/resource-metadata/resource-metadata'; import { debug, error, info } from '../../cli/messages'; import { stackEventHasErrorMessage } from '../../util'; import type { ICloudFormationClient } from '../aws-auth'; @@ -181,23 +181,12 @@ export class StackActivityMonitor { this.scheduleNextTick(); } - private findMetadataFor(logicalId: string | undefined): ResourceMetadata | undefined { + private findMetadataFor(logicalId: string | undefined) { const metadata = this.stack.manifest?.metadata; if (!logicalId || !metadata) { return undefined; } - for (const path of Object.keys(metadata)) { - const entry = metadata[path] - .filter((e) => e.type === ArtifactMetadataEntryType.LOGICAL_ID) - .find((e) => e.data === logicalId); - if (entry) { - return { - entry, - constructPath: this.simplifyConstructPath(path), - }; - } - } - return undefined; + return resourceMetadata(this.stack, logicalId); } /** @@ -278,15 +267,4 @@ export class StackActivityMonitor { } } } - - private simplifyConstructPath(path: string) { - path = path.replace(/\/Resource$/, ''); - path = path.replace(/^\//, ''); // remove "/" prefix - - // remove "/" prefix - if (path.startsWith(this.stackName + '/')) { - path = path.slice(this.stackName.length + 1); - } - return path; - } }