Skip to content

Commit a850ec6

Browse files
authored
refactor(cli): resource-import uses IoHost (#33412)
## Issue Relates to #32292 ### Description of changes - combines `ResourceImporter` and `ResourceMigrator` into a new api submodule - introduces explicit `ImportDeploymentOptions` options that only expose actually used deployment options, it is used for both `ResourceImporter` and `ResourceMigrator` - switch to `IoHost` for all logging ### Describe any new or updated permissions being added n/a ### Description of how you validated changes Existing tests and cli-integ tests ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent c3bd4d9 commit a850ec6

File tree

11 files changed

+305
-87
lines changed

11 files changed

+305
-87
lines changed

packages/@aws-cdk/toolkit/lib/actions/deploy/index.ts

-8
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,6 @@ export interface BaseDeployOptions {
115115
*/
116116
readonly stacks?: StackSelector;
117117

118-
/**
119-
* @deprecated set on toolkit
120-
* Name of the toolkit stack to use/deploy
121-
*
122-
* @default CDKToolkit
123-
*/
124-
readonly toolkitStackName?: string;
125-
126118
/**
127119
* Role to pass to CloudFormation for deployment
128120
*/

packages/@aws-cdk/toolkit/lib/api/aws-cdk.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export { Deployments, type SuccessfulDeployStackResult } from '../../../../aws-c
77
export { Settings } from '../../../../aws-cdk/lib/api/settings';
88
export { tagsForStack } from '../../../../aws-cdk/lib/api/tags';
99
export { DEFAULT_TOOLKIT_STACK_NAME } from '../../../../aws-cdk/lib/api/toolkit-info';
10+
export { ResourceMigrator } from '../../../../aws-cdk/lib/api/resource-import';
1011

1112
// Context Providers
1213
export * as contextproviders from '../../../../aws-cdk/lib/context-providers';
@@ -19,7 +20,6 @@ export { formatTime } from '../../../../aws-cdk/lib/api/util/string-manipulation
1920

2021
// @todo Not yet API probably should be
2122
export { formatErrorMessage } from '../../../../aws-cdk/lib/util/error';
22-
export { ResourceMigrator } from '../../../../aws-cdk/lib/migrator';
2323
export { obscureTemplate, serializeStructure } from '../../../../aws-cdk/lib/serialize';
2424
export { loadTree, some } from '../../../../aws-cdk/lib/tree';
2525
export { splitBySize } from '../../../../aws-cdk/lib/util';

packages/@aws-cdk/toolkit/lib/api/io/private/codes.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@ export const CODES = {
1515
// 2: List
1616
CDK_TOOLKIT_I2901: 'Provides details on the selected stacks and their dependencies',
1717

18+
// 3: Import & Migrate
19+
CDK_TOOLKIT_E3900: 'Resource import failed',
20+
1821
// 4: Diff
1922

20-
// 5: Deploy
23+
// 5: Deploy & Watch
2124
CDK_TOOLKIT_I5000: 'Provides deployment times',
2225
CDK_TOOLKIT_I5001: 'Provides total time in deploy action, including synth and rollback',
26+
CDK_TOOLKIT_I5002: 'Provides time for resource migration',
2327
CDK_TOOLKIT_I5031: 'Informs about any log groups that are traced as part of the deployment',
2428
CDK_TOOLKIT_I5050: 'Confirm rollback during deployment',
2529
CDK_TOOLKIT_I5060: 'Confirm deploy security sensitive changes',

packages/@aws-cdk/toolkit/lib/toolkit/toolkit.ts

+17-17
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { CachedCloudAssemblySource, IdentityCloudAssemblySource, StackAssembly,
1919
import { ALL_STACKS, CloudAssemblySourceBuilder } from '../api/cloud-assembly/private';
2020
import { ToolkitError } from '../api/errors';
2121
import { IIoHost, IoMessageCode, IoMessageLevel } from '../api/io';
22-
import { asSdkLogger, withAction, Timer, confirm, error, highlight, info, success, warn, ActionAwareIoHost, debug, result, withoutEmojis, withoutColor, withTrimmedWhitespace } from '../api/io/private';
22+
import { asSdkLogger, withAction, Timer, confirm, error, info, success, warn, ActionAwareIoHost, debug, result, withoutEmojis, withoutColor, withTrimmedWhitespace } from '../api/io/private';
2323

2424
/**
2525
* The current action being performed by the CLI. 'none' represents the absence of an action.
@@ -256,10 +256,8 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
256256
}
257257

258258
const deployments = await this.deploymentsForAction('deploy');
259+
const migrator = new ResourceMigrator({ deployments, ioHost, action });
259260

260-
const migrator = new ResourceMigrator({
261-
deployments,
262-
});
263261
await migrator.tryMigrateResources(stackCollection, options);
264262

265263
const requireApproval = options.requireApproval ?? RequireApproval.NEVER;
@@ -275,7 +273,6 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
275273
}
276274

277275
const stacks = stackCollection.stackArtifacts;
278-
279276
const stackOutputs: { [key: string]: any } = {};
280277
const outputsFile = options.outputsFile;
281278

@@ -303,28 +300,31 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
303300
const deployStack = async (stackNode: StackNode) => {
304301
const stack = stackNode.stack;
305302
if (stackCollection.stackCount !== 1) {
306-
await ioHost.notify(highlight(stack.displayName));
303+
await ioHost.notify(info(chalk.bold(stack.displayName)));
307304
}
308305

309306
if (!stack.environment) {
310-
// eslint-disable-next-line max-len
311307
throw new ToolkitError(
312308
`Stack ${stack.displayName} does not define an environment, and AWS credentials could not be obtained from standard locations or no region was configured.`,
313309
);
314310
}
315311

312+
// The generated stack has no resources
316313
if (Object.keys(stack.template.Resources || {}).length === 0) {
317-
// The generated stack has no resources
318-
if (!(await deployments.stackExists({ stack }))) {
319-
await ioHost.notify(warn(`${chalk.bold(stack.displayName)}: stack has no resources, skipping deployment.`));
320-
} else {
321-
await ioHost.notify(warn(`${chalk.bold(stack.displayName)}: stack has no resources, deleting existing stack.`));
322-
await this._destroy(assembly, 'deploy', {
323-
stacks: { patterns: [stack.hierarchicalId], strategy: StackSelectionStrategy.PATTERN_MUST_MATCH_SINGLE },
324-
roleArn: options.roleArn,
325-
ci: options.ci,
326-
});
314+
// stack is empty and doesn't exist => do nothing
315+
const stackExists = await deployments.stackExists({ stack });
316+
if (!stackExists) {
317+
return ioHost.notify(warn(`${chalk.bold(stack.displayName)}: stack has no resources, skipping deployment.`));
327318
}
319+
320+
// stack is empty, but exists => delete
321+
await ioHost.notify(warn(`${chalk.bold(stack.displayName)}: stack has no resources, deleting existing stack.`));
322+
await this._destroy(assembly, 'deploy', {
323+
stacks: { patterns: [stack.hierarchicalId], strategy: StackSelectionStrategy.PATTERN_MUST_MATCH_SINGLE },
324+
roleArn: options.roleArn,
325+
ci: options.ci,
326+
});
327+
328328
return;
329329
}
330330

packages/aws-cdk/lib/import.ts renamed to packages/aws-cdk/lib/api/resource-import/importer.ts

+84-25
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,68 @@
1-
import { DeployOptions } from '@aws-cdk/cloud-assembly-schema';
1+
import { format } from 'util';
22
import * as cfnDiff from '@aws-cdk/cloudformation-diff';
33
import { ResourceDifference } from '@aws-cdk/cloudformation-diff';
44
import * as cxapi from '@aws-cdk/cx-api';
55
import * as chalk from 'chalk';
66
import * as fs from 'fs-extra';
77
import * as promptly from 'promptly';
8-
import { assertIsSuccessfulDeployStackResult, Deployments, DeploymentMethod, ResourceIdentifierProperties, ResourcesToImport } from './api/deployments';
9-
import { Tag } from './api/tags';
10-
import { StackActivityProgress } from './api/util/cloudformation/stack-activity-monitor';
11-
import { error, info, success, warning } from './logging';
12-
import { ToolkitError } from './toolkit/error';
13-
14-
export interface ImportDeploymentOptions extends DeployOptions {
15-
deploymentMethod?: DeploymentMethod;
16-
progress?: StackActivityProgress;
17-
tags?: Tag[];
8+
import { error, info, warn } from '../../cli/messages';
9+
import { IIoHost, ToolkitAction } from '../../toolkit/cli-io-host';
10+
import { ToolkitError } from '../../toolkit/error';
11+
import { assertIsSuccessfulDeployStackResult, Deployments, DeploymentMethod, ResourceIdentifierProperties, ResourcesToImport } from '../deployments';
12+
import { Tag } from '../tags';
13+
import { StackActivityProgress } from '../util/cloudformation/stack-activity-monitor';
14+
15+
export interface ResourceImporterProps {
16+
deployments: Deployments;
17+
ioHost: IIoHost;
18+
action: ToolkitAction;
19+
}
20+
21+
export interface ImportDeploymentOptions {
22+
/**
23+
* Role to pass to CloudFormation for deployment
24+
*
25+
* @default - Default stack role
26+
*/
27+
readonly roleArn?: string;
28+
29+
/**
30+
* Deployment method
31+
*
32+
* @default - Change set with default options
33+
*/
34+
readonly deploymentMethod?: DeploymentMethod;
35+
36+
/**
37+
* Stack tags (pass through to CloudFormation)
38+
*
39+
* @default - No tags
40+
*/
41+
readonly tags?: Tag[];
42+
43+
/**
44+
* Use previous values for unspecified parameters
45+
*
46+
* If not set, all parameters must be specified for every deployment.
47+
*
48+
* @default true
49+
*/
50+
readonly usePreviousParameters?: boolean;
51+
52+
/**
53+
* Display mode for stack deployment progress.
54+
*
55+
* @default - StackActivityProgress.Bar - stack events will be displayed for
56+
* the resource currently being deployed.
57+
*/
58+
readonly progress?: StackActivityProgress;
59+
60+
/**
61+
* Rollback failed deployments
62+
*
63+
* @default true
64+
*/
65+
readonly rollback?: boolean;
1866
}
1967

2068
/**
@@ -61,9 +109,20 @@ export type ResourceMap = { [logicalResource: string]: ResourceIdentifierPropert
61109
export class ResourceImporter {
62110
private _currentTemplate: any;
63111

112+
private readonly stack: cxapi.CloudFormationStackArtifact;
113+
private readonly cfn: Deployments;
114+
private readonly ioHost: IIoHost;
115+
private readonly action: ToolkitAction;
116+
64117
constructor(
65-
private readonly stack: cxapi.CloudFormationStackArtifact,
66-
private readonly cfn: Deployments) { }
118+
stack: cxapi.CloudFormationStackArtifact,
119+
props: ResourceImporterProps,
120+
) {
121+
this.stack = stack;
122+
this.cfn = props.deployments;
123+
this.ioHost = props.ioHost;
124+
this.action = props.action;
125+
}
67126

68127
/**
69128
* Ask the user for resources to import
@@ -96,19 +155,19 @@ export class ResourceImporter {
96155
const descr = this.describeResource(resource.logicalId);
97156
const idProps = contents[resource.logicalId];
98157
if (idProps) {
99-
info('%s: importing using %s', chalk.blue(descr), chalk.blue(fmtdict(idProps)));
158+
await this.ioHost.notify(info(this.action, format('%s: importing using %s', chalk.blue(descr), chalk.blue(fmtdict(idProps)))));
100159

101160
ret.importResources.push(resource);
102161
ret.resourceMap[resource.logicalId] = idProps;
103162
delete contents[resource.logicalId];
104163
} else {
105-
info('%s: skipping', chalk.blue(descr));
164+
await this.ioHost.notify(info(this.action, format('%s: skipping', chalk.blue(descr))));
106165
}
107166
}
108167

109168
const unknown = Object.keys(contents);
110169
if (unknown.length > 0) {
111-
warning(`Unrecognized resource identifiers in mapping file: ${unknown.join(', ')}`);
170+
await this.ioHost.notify(warn(this.action, `Unrecognized resource identifiers in mapping file: ${unknown.join(', ')}`));
112171
}
113172

114173
return ret;
@@ -121,7 +180,7 @@ export class ResourceImporter {
121180
* @param importMap Mapping from CDK construct tree path to physical resource import identifiers
122181
* @param options Options to pass to CloudFormation deploy operation
123182
*/
124-
public async importResourcesFromMap(importMap: ImportMap, options: ImportDeploymentOptions) {
183+
public async importResourcesFromMap(importMap: ImportMap, options: ImportDeploymentOptions = {}) {
125184
const resourcesToImport: ResourcesToImport = await this.makeResourcesToImport(importMap);
126185
const updatedTemplate = await this.currentTemplateWithAdditions(importMap.importResources);
127186

@@ -136,7 +195,7 @@ export class ResourceImporter {
136195
* @param resourcesToImport The mapping created by cdk migrate
137196
* @param options Options to pass to CloudFormation deploy operation
138197
*/
139-
public async importResourcesFromMigrate(resourcesToImport: ResourcesToImport, options: ImportDeploymentOptions) {
198+
public async importResourcesFromMigrate(resourcesToImport: ResourcesToImport, options: ImportDeploymentOptions = {}) {
140199
const updatedTemplate = this.removeNonImportResources();
141200

142201
await this.importResources(updatedTemplate, resourcesToImport, options);
@@ -158,9 +217,9 @@ export class ResourceImporter {
158217
? ' ✅ %s (no changes)'
159218
: ' ✅ %s';
160219

161-
success('\n' + message, this.stack.displayName);
220+
await this.ioHost.notify(info(this.action, '\n' + chalk.green(format(message, this.stack.displayName))));
162221
} catch (e) {
163-
error('\n ❌ %s failed: %s', chalk.bold(this.stack.displayName), e);
222+
await this.ioHost.notify(error(this.action, format('\n ❌ %s failed: %s', chalk.bold(this.stack.displayName), e), 'CDK_TOOLKIT_E3900'));
164223
throw e;
165224
}
166225
}
@@ -189,7 +248,7 @@ export class ResourceImporter {
189248
const offendingResources = nonAdditions.map(([logId, _]) => this.describeResource(logId));
190249

191250
if (allowNonAdditions) {
192-
warning(`Ignoring updated/deleted resources (--force): ${offendingResources.join(', ')}`);
251+
await this.ioHost.notify(warn(this.action, `Ignoring updated/deleted resources (--force): ${offendingResources.join(', ')}`));
193252
} else {
194253
throw new ToolkitError('No resource updates or deletes are allowed on import operation. Make sure to resolve pending changes ' +
195254
`to existing resources, before attempting an import. Updated/deleted resources: ${offendingResources.join(', ')} (--force to override)`);
@@ -277,7 +336,7 @@ export class ResourceImporter {
277336
// Skip resources that do not support importing
278337
const resourceType = chg.resourceDiff.newResourceType;
279338
if (resourceType === undefined || !(resourceType in resourceIdentifiers)) {
280-
warning(`${resourceName}: unsupported resource type ${resourceType}, skipping import.`);
339+
await this.ioHost.notify(warn(this.action, `${resourceName}: unsupported resource type ${resourceType}, skipping import.`));
281340
return undefined;
282341
}
283342

@@ -303,7 +362,7 @@ export class ResourceImporter {
303362

304363
// If we got here and the user rejected any available identifiers, then apparently they don't want the resource at all
305364
if (satisfiedPropSets.length > 0) {
306-
info(chalk.grey(`Skipping import of ${resourceName}`));
365+
await this.ioHost.notify(info(this.action, chalk.grey(`Skipping import of ${resourceName}`)));
307366
return undefined;
308367
}
309368

@@ -321,7 +380,7 @@ export class ResourceImporter {
321380

322381
// Do the input loop here
323382
if (preamble) {
324-
info(preamble);
383+
await this.ioHost.notify(info(this.action, preamble));
325384
}
326385
for (const idProps of idPropSets) {
327386
const input: Record<string, string> = {};
@@ -356,7 +415,7 @@ export class ResourceImporter {
356415
}
357416
}
358417

359-
info(chalk.grey(`Skipping import of ${resourceName}`));
418+
await this.ioHost.notify(info(this.action, chalk.grey(`Skipping import of ${resourceName}`)));
360419
return undefined;
361420
}
362421

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './importer';
2+
export * from './migrator';

0 commit comments

Comments
 (0)