Skip to content

Commit a139c52

Browse files
authored
refactor(toolkit): resource metadata (#243)
Pulls out resource-metadata extraction into a separate API so it can easily be reused in other places. Also adds support for resource metadata to `EvaluateCloudFormationTemplate` (internal API) in preparation for a change in hotswap. --- By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license
1 parent cb8a7e0 commit a139c52

File tree

11 files changed

+104
-56
lines changed

11 files changed

+104
-56
lines changed

.projenrc.ts

+1
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,7 @@ const tmpToolkitHelpers = configureProject(
655655
'fast-check',
656656
],
657657
deps: [
658+
cloudAssemblySchema.name,
658659
'archiver',
659660
'glob',
660661
'semver',

packages/@aws-cdk/tmp-toolkit-helpers/.projen/deps.json

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk/tmp-toolkit-helpers/.projen/tasks.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk/tmp-toolkit-helpers/package.json

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/stack-activity.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { MetadataEntry } from '@aws-cdk/cloud-assembly-schema';
21
import type { CloudFormationStackArtifact } from '@aws-cdk/cx-api';
32
import type { StackEvent } from '@aws-sdk/client-cloudformation';
43
import type { StackProgress } from './progress';
4+
import type { ResourceMetadata } from '../../resource-metadata/resource-metadata';
55

66
/**
77
* Payload when stack monitoring is starting or stopping for a given stack deployment.
@@ -48,6 +48,9 @@ export interface StackActivity {
4848

4949
/**
5050
* Additional resource metadata
51+
*
52+
* This information is only available if the information is available in the current cloud assembly.
53+
* I.e. no `metadata` will not be available for resource deletion events.
5154
*/
5255
readonly metadata?: ResourceMetadata;
5356

@@ -56,8 +59,3 @@ export interface StackActivity {
5659
*/
5760
readonly progress: StackProgress;
5861
}
59-
60-
export interface ResourceMetadata {
61-
entry: MetadataEntry;
62-
constructPath: string;
63-
}

packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/messages.ts

+14-14
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import type { FileWatchEvent, WatchSettings } from '../payloads/watch';
2121
* - X900-X999 are reserved for results
2222
*/
2323
export const IO = {
24-
// Defaults
24+
// Defaults (0000)
2525
DEFAULT_TOOLKIT_INFO: make.info({
2626
code: 'CDK_TOOLKIT_I0000',
2727
description: 'Default info messages emitted from the Toolkit',
@@ -35,7 +35,7 @@ export const IO = {
3535
description: 'Default warning messages emitted from the Toolkit',
3636
}),
3737

38-
// 1: Synth
38+
// 1: Synth (1xxx)
3939
CDK_TOOLKIT_I1000: make.info<Duration>({
4040
code: 'CDK_TOOLKIT_I1000',
4141
description: 'Provides synthesis times.',
@@ -57,7 +57,7 @@ export const IO = {
5757
interface: 'AssemblyData',
5858
}),
5959

60-
// 2: List
60+
// 2: List (2xxx)
6161
CDK_TOOLKIT_I2901: make.result<StackDetailsPayload>({
6262
code: 'CDK_TOOLKIT_I2901',
6363
description: 'Provides details on the selected stacks and their dependencies',
@@ -70,9 +70,9 @@ export const IO = {
7070
description: 'Resource import failed',
7171
}),
7272

73-
// 4: Diff
73+
// 4: Diff (4xxx)
7474

75-
// 5: Deploy & Watch
75+
// 5: Deploy & Watch (5xxx)
7676
CDK_TOOLKIT_I5000: make.info<Duration>({
7777
code: 'CDK_TOOLKIT_I5000',
7878
description: 'Provides deployment times',
@@ -129,14 +129,13 @@ export const IO = {
129129
description: 'Confirm deploy security sensitive changes',
130130
interface: 'DeployConfirmationRequest',
131131
}),
132-
133132
CDK_TOOLKIT_I5100: make.info<StackDeployProgress>({
134133
code: 'CDK_TOOLKIT_I5100',
135134
description: 'Stack deploy progress',
136135
interface: 'StackDeployProgress',
137136
}),
138137

139-
// Assets
138+
// Assets (52xx)
140139
CDK_TOOLKIT_I5210: make.trace<BuildAsset>({
141140
code: 'CDK_TOOLKIT_I5210',
142141
description: 'Started building a specific asset',
@@ -147,7 +146,6 @@ export const IO = {
147146
description: 'Building the asset has completed',
148147
interface: 'Duration',
149148
}),
150-
151149
CDK_TOOLKIT_I5220: make.trace<PublishAsset>({
152150
code: 'CDK_TOOLKIT_I5220',
153151
description: 'Started publishing a specific asset',
@@ -159,7 +157,7 @@ export const IO = {
159157
interface: 'Duration',
160158
}),
161159

162-
// Watch
160+
// Watch (53xx)
163161
CDK_TOOLKIT_I5310: make.debug<WatchSettings>({
164162
code: 'CDK_TOOLKIT_I5310',
165163
description: 'The computed settings used for file watching',
@@ -189,7 +187,9 @@ export const IO = {
189187
description: 'Queued watch deployment started',
190188
}),
191189

192-
// Stack Monitor
190+
// Hotswap (54xx)
191+
192+
// Stack Monitor (55xx)
193193
CDK_TOOLKIT_I5501: make.info<StackMonitoringControlEvent>({
194194
code: 'CDK_TOOLKIT_I5501',
195195
description: 'Stack Monitoring: Start monitoring of a single stack',
@@ -206,7 +206,7 @@ export const IO = {
206206
interface: 'StackMonitoringControlEvent',
207207
}),
208208

209-
// Success
209+
// Success (59xx)
210210
CDK_TOOLKIT_I5900: make.result<SuccessfulDeployStackResult>({
211211
code: 'CDK_TOOLKIT_I5900',
212212
description: 'Deployment results on success',
@@ -232,7 +232,7 @@ export const IO = {
232232
interface: 'ErrorPayload',
233233
}),
234234

235-
// 6: Rollback
235+
// 6: Rollback (6xxx)
236236
CDK_TOOLKIT_I6000: make.info<Duration>({
237237
code: 'CDK_TOOLKIT_I6000',
238238
description: 'Provides rollback times',
@@ -254,7 +254,7 @@ export const IO = {
254254
interface: 'ErrorPayload',
255255
}),
256256

257-
// 7: Destroy
257+
// 7: Destroy (7xxx)
258258
CDK_TOOLKIT_I7000: make.info<Duration>({
259259
code: 'CDK_TOOLKIT_I7000',
260260
description: 'Provides destroy times',
@@ -297,7 +297,7 @@ export const IO = {
297297
interface: 'ErrorPayload',
298298
}),
299299

300-
// 9: Bootstrap
300+
// 9: Bootstrap (9xxx)
301301
CDK_TOOLKIT_I9000: make.info<Duration>({
302302
code: 'CDK_TOOLKIT_I9000',
303303
description: 'Provides bootstrap times',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { ArtifactMetadataEntryType, type MetadataEntry } from '@aws-cdk/cloud-assembly-schema';
2+
import type { CloudFormationStackArtifact } from '@aws-cdk/cx-api';
3+
4+
/**
5+
* Metadata entry for a resource within a CloudFormation stack
6+
*/
7+
export interface ResourceMetadata {
8+
/**
9+
* The resource's metadata as declared in the cloud assembly
10+
*/
11+
readonly entry: MetadataEntry;
12+
/**
13+
* The construct path of the resource
14+
*/
15+
readonly constructPath: string;
16+
}
17+
18+
/**
19+
* Attempts to read metadata for resources from a CloudFormation stack artifact
20+
*
21+
* @param stack The CloudFormation stack to read from
22+
* @param logicalId The logical ID of the resource to read
23+
*
24+
* @returns The resource metadata, or undefined if the resource was not found
25+
*/
26+
export function resourceMetadata(stack: CloudFormationStackArtifact, logicalId: string): ResourceMetadata | undefined {
27+
const metadata = stack.manifest?.metadata;
28+
if (!metadata) {
29+
return undefined;
30+
}
31+
32+
for (const path of Object.keys(metadata)) {
33+
const entry = metadata[path]
34+
.filter((e) => e.type === ArtifactMetadataEntryType.LOGICAL_ID)
35+
.find((e) => e.data === logicalId);
36+
if (entry) {
37+
return {
38+
entry,
39+
constructPath: simplifyConstructPath(path, stack.stackName),
40+
};
41+
}
42+
}
43+
return undefined;
44+
}
45+
46+
function simplifyConstructPath(path: string, stackName: string) {
47+
path = path.replace(/\/Resource$/, '');
48+
path = path.replace(/^\//, ''); // remove "/" prefix
49+
50+
// remove "<stack-name>/" prefix
51+
if (stackName) {
52+
if (path.startsWith(stackName + '/')) {
53+
path = path.slice(stackName.length + 1);
54+
}
55+
}
56+
return path;
57+
}

packages/aws-cdk/lib/api/deployments/hotswap-deployments.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,7 @@ export async function tryHotswapDeployment(
103103
const currentTemplate = await loadCurrentTemplateWithNestedStacks(stackArtifact, sdk);
104104

105105
const evaluateCfnTemplate = new EvaluateCloudFormationTemplate({
106-
stackName: stackArtifact.stackName,
107-
template: stackArtifact.template,
106+
stackArtifact,
108107
parameters: assetParams,
109108
account: resolvedEnv.account,
110109
region: resolvedEnv.region,

packages/aws-cdk/lib/api/evaluate-cloudformation-template.ts

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import type { CloudFormationStackArtifact } from '@aws-cdk/cx-api';
12
import type { Export, ListExportsCommandOutput, StackResourceSummary } from '@aws-sdk/client-cloudformation';
23
import type { SDK } from './aws-auth';
34
import type { NestedStackTemplates } from './deployments';
5+
import { resourceMetadata } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/resource-metadata/resource-metadata';
6+
import type { ResourceMetadata } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/resource-metadata/resource-metadata';
47
import { ToolkitError } from '../toolkit/error';
58

69
export interface ListStackResources {
@@ -85,8 +88,9 @@ export interface ResourceDefinition {
8588
}
8689

8790
export interface EvaluateCloudFormationTemplateProps {
88-
readonly stackName: string;
89-
readonly template: Template;
91+
readonly stackArtifact: CloudFormationStackArtifact;
92+
readonly stackName?: string;
93+
readonly template?: Template;
9094
readonly parameters: { [parameterName: string]: string };
9195
readonly account: string;
9296
readonly region: string;
@@ -98,6 +102,7 @@ export interface EvaluateCloudFormationTemplateProps {
98102
}
99103

100104
export class EvaluateCloudFormationTemplate {
105+
public readonly stackArtifact: CloudFormationStackArtifact;
101106
private readonly stackName: string;
102107
private readonly template: Template;
103108
private readonly context: { [k: string]: any };
@@ -114,8 +119,9 @@ export class EvaluateCloudFormationTemplate {
114119
private cachedUrlSuffix: string | undefined;
115120

116121
constructor(props: EvaluateCloudFormationTemplateProps) {
117-
this.stackName = props.stackName;
118-
this.template = props.template;
122+
this.stackArtifact = props.stackArtifact;
123+
this.stackName = props.stackName ?? props.stackArtifact.stackName;
124+
this.template = props.template ?? props.stackArtifact.template;
119125
this.context = {
120126
'AWS::AccountId': props.account,
121127
'AWS::Region': props.region,
@@ -147,6 +153,7 @@ export class EvaluateCloudFormationTemplate {
147153
) {
148154
const evaluatedParams = await this.evaluateCfnExpression(nestedStackParameters);
149155
return new EvaluateCloudFormationTemplate({
156+
stackArtifact: this.stackArtifact,
150157
stackName,
151158
template: nestedTemplate,
152159
parameters: evaluatedParams,
@@ -312,6 +319,10 @@ export class EvaluateCloudFormationTemplate {
312319
return this.template.Resources?.[logicalId]?.Properties?.[propertyName];
313320
}
314321

322+
public metadataFor(logicalId: string): ResourceMetadata | undefined {
323+
return resourceMetadata(this.stackArtifact, logicalId);
324+
}
325+
315326
private references(logicalId: string, templateElement: any): boolean {
316327
if (typeof templateElement === 'string') {
317328
return logicalId === templateElement;

packages/aws-cdk/lib/api/logs/find-cloudwatch-logs.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ export async function findCloudWatchLogGroups(
5353

5454
const listStackResources = new LazyListStackResources(sdk, stackArtifact.stackName);
5555
const evaluateCfnTemplate = new EvaluateCloudFormationTemplate({
56-
stackName: stackArtifact.stackName,
57-
template: stackArtifact.template,
56+
stackArtifact,
5857
parameters: {},
5958
account: resolvedEnv.account,
6059
region: resolvedEnv.region,

packages/aws-cdk/lib/api/stack-events/stack-activity-monitor.ts

+4-26
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11

22
import * as util from 'util';
3-
import { ArtifactMetadataEntryType } from '@aws-cdk/cloud-assembly-schema';
43
import type { CloudFormationStackArtifact } from '@aws-cdk/cx-api';
5-
import type { ResourceMetadata, StackActivity, StackMonitoringControlEvent } from '@aws-cdk/tmp-toolkit-helpers';
4+
import type { StackActivity, StackMonitoringControlEvent } from '@aws-cdk/tmp-toolkit-helpers';
65
import * as uuid from 'uuid';
76
import { StackEventPoller } from './stack-event-poller';
7+
import { resourceMetadata } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/resource-metadata/resource-metadata';
88
import { debug, error, info } from '../../cli/messages';
99
import { stackEventHasErrorMessage } from '../../util';
1010
import type { ICloudFormationClient } from '../aws-auth';
@@ -181,23 +181,12 @@ export class StackActivityMonitor {
181181
this.scheduleNextTick();
182182
}
183183

184-
private findMetadataFor(logicalId: string | undefined): ResourceMetadata | undefined {
184+
private findMetadataFor(logicalId: string | undefined) {
185185
const metadata = this.stack.manifest?.metadata;
186186
if (!logicalId || !metadata) {
187187
return undefined;
188188
}
189-
for (const path of Object.keys(metadata)) {
190-
const entry = metadata[path]
191-
.filter((e) => e.type === ArtifactMetadataEntryType.LOGICAL_ID)
192-
.find((e) => e.data === logicalId);
193-
if (entry) {
194-
return {
195-
entry,
196-
constructPath: this.simplifyConstructPath(path),
197-
};
198-
}
199-
}
200-
return undefined;
189+
return resourceMetadata(this.stack, logicalId);
201190
}
202191

203192
/**
@@ -278,15 +267,4 @@ export class StackActivityMonitor {
278267
}
279268
}
280269
}
281-
282-
private simplifyConstructPath(path: string) {
283-
path = path.replace(/\/Resource$/, '');
284-
path = path.replace(/^\//, ''); // remove "/" prefix
285-
286-
// remove "<stack-name>/" prefix
287-
if (path.startsWith(this.stackName + '/')) {
288-
path = path.slice(this.stackName.length + 1);
289-
}
290-
return path;
291-
}
292270
}

0 commit comments

Comments
 (0)