Skip to content

Commit c14df8e

Browse files
authored
fix(toolkit-lib): remove deprecated DeployOptions.force in favor of more specific options (#315)
- Rename `force` option to more descriptive `forceDeployment` for clarity - Add new `forceAssetPublishing` option to control asset publishing separately - Add `orphanFailedResourcesDuringRollback` option for better rollback control - Rename `removePublishedAssets` to `removePublishedAssetsFromWorkGraph` for clarity - Update tests and implementation to use the new parameter names - Improve documentation for deployment options BREAKING CHANGE: The deprecated `force` option on `DeployOptions` has been removed. The removed option originally caused multiple different "force" actions. Each action now has a more targeted alternative. To force a deployment even if the CDK Toolkit has not detected any changes, use `forceDeployment`. To force re-publishing of previously published assets, use `forceAssetPublishing`. To force failing resource being orphaned during a rollback, use `orphanFailedResourcesDuringRollback`. To implement interactive confirmation of rollbacks during a deployment, react to the message request with code `CDK_TOOLKIT_I5050` in your `IoHost`. --- By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license
1 parent e0f9a6f commit c14df8e

File tree

12 files changed

+66
-74
lines changed

12 files changed

+66
-74
lines changed

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

+17-3
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,11 @@ export interface BaseDeployOptions {
1616
readonly roleArn?: string;
1717

1818
/**
19-
* Always deploy, even if templates are identical.
19+
* Deploy even if the deployed template is identical to the one we are about to deploy.
2020
*
2121
* @default false
22-
* @deprecated the options currently covers multiple different functionalities and will be split out in future
2322
*/
24-
readonly force?: boolean;
23+
readonly forceDeployment?: boolean;
2524

2625
/**
2726
* Deployment method
@@ -44,6 +43,21 @@ export interface BaseDeployOptions {
4443
*/
4544
readonly rollback?: boolean;
4645

46+
/**
47+
* Automatically orphan resources that failed during rollback
48+
*
49+
* Has no effect if `rollback` is `false`.
50+
*
51+
* @default false
52+
*/
53+
readonly orphanFailedResourcesDuringRollback?: boolean;
54+
55+
/**
56+
* Force asset publishing even if the assets have not changed
57+
* @default false
58+
*/
59+
readonly forceAssetPublishing?: boolean;
60+
4761
/**
4862
* Reuse the assets with the given asset IDs
4963
*/

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function buildParameterMap(parameters?: Map<string, string | undefined>):
2727
/**
2828
* Remove the asset publishing and building from the work graph for assets that are already in place
2929
*/
30-
export async function removePublishedAssets(graph: WorkGraph, deployments: Deployments, options: DeployOptions) {
30+
export async function removePublishedAssetsFromWorkGraph(graph: WorkGraph, deployments: Deployments, options: DeployOptions) {
3131
await graph.removeUnnecessaryAssets(assetNode => deployments.isSingleAssetPublished(assetNode.assetManifest, assetNode.asset, {
3232
stack: assetNode.parentStack,
3333
roleArn: options.roleArn,

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

+19-28
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { assemblyFromSource } from './private';
1010
import type { BootstrapEnvironments, BootstrapOptions, BootstrapResult, EnvironmentBootstrapResult } from '../actions/bootstrap';
1111
import { BootstrapSource } from '../actions/bootstrap';
1212
import { AssetBuildTime, type DeployOptions } from '../actions/deploy';
13-
import { type ExtendedDeployOptions, buildParameterMap, createHotswapPropertyOverrides, removePublishedAssets } from '../actions/deploy/private';
13+
import { type ExtendedDeployOptions, buildParameterMap, createHotswapPropertyOverrides, removePublishedAssetsFromWorkGraph } from '../actions/deploy/private';
1414
import { type DestroyOptions } from '../actions/destroy';
1515
import type { ChangeSetDiffOptions, DiffOptions, LocalFileDiffOptions } from '../actions/diff';
1616
import { DiffMethod } from '../actions/diff';
@@ -479,7 +479,7 @@ export class Toolkit extends CloudAssemblySourceBuilder {
479479
stack: assetNode.parentStack,
480480
roleArn: options.roleArn,
481481
stackName: assetNode.parentStack.stackName,
482-
forcePublish: options.force,
482+
forcePublish: options.forceAssetPublishing,
483483
});
484484
await publishAssetSpan.end();
485485
};
@@ -575,7 +575,7 @@ export class Toolkit extends CloudAssemblySourceBuilder {
575575
notificationArns,
576576
tags,
577577
deploymentMethod: options.deploymentMethod,
578-
force: options.force,
578+
forceDeployment: options.forceDeployment,
579579
parameters: Object.assign({}, parameterMap['*'], parameterMap[stack.stackName]),
580580
usePreviousParameters: options.parameters?.keepExistingParameters,
581581
rollback,
@@ -596,22 +596,18 @@ export class Toolkit extends CloudAssemblySourceBuilder {
596596
: `Stack is in a paused fail state (${r.status}) and command line arguments do not include "--no-rollback"`;
597597
const question = `${motivation}. Perform a regular deployment`;
598598

599-
if (options.force) {
600-
await ioHelper.notify(IO.DEFAULT_TOOLKIT_WARN.msg(`${motivation}. Rolling back first (--force).`));
601-
} else {
602-
const confirmed = await ioHelper.requestResponse(IO.CDK_TOOLKIT_I5050.req(question, {
603-
motivation,
604-
concurrency,
605-
}));
606-
if (!confirmed) {
607-
throw new ToolkitError('Aborted by user');
608-
}
599+
const confirmed = await ioHelper.requestResponse(IO.CDK_TOOLKIT_I5050.req(question, {
600+
motivation,
601+
concurrency,
602+
}));
603+
if (!confirmed) {
604+
throw new ToolkitError('Aborted by user');
609605
}
610606

611607
// Perform a rollback
612608
await this._rollback(assembly, action, {
613609
stacks: { patterns: [stack.hierarchicalId], strategy: StackSelectionStrategy.PATTERN_MUST_MATCH_SINGLE },
614-
orphanFailedResources: options.force,
610+
orphanFailedResources: options.orphanFailedResourcesDuringRollback,
615611
});
616612

617613
// Go around through the 'while' loop again but switch rollback to true.
@@ -623,17 +619,12 @@ export class Toolkit extends CloudAssemblySourceBuilder {
623619
const motivation = 'Change includes a replacement which cannot be deployed with "--no-rollback"';
624620
const question = `${motivation}. Perform a regular deployment`;
625621

626-
// @todo no force here
627-
if (options.force) {
628-
await ioHelper.notify(IO.DEFAULT_TOOLKIT_WARN.msg(`${motivation}. Proceeding with regular deployment (--force).`));
629-
} else {
630-
const confirmed = await ioHelper.requestResponse(IO.CDK_TOOLKIT_I5050.req(question, {
631-
motivation,
632-
concurrency,
633-
}));
634-
if (!confirmed) {
635-
throw new ToolkitError('Aborted by user');
636-
}
622+
const confirmed = await ioHelper.requestResponse(IO.CDK_TOOLKIT_I5050.req(question, {
623+
motivation,
624+
concurrency,
625+
}));
626+
if (!confirmed) {
627+
throw new ToolkitError('Aborted by user');
637628
}
638629

639630
// Go around through the 'while' loop again but switch rollback to true.
@@ -709,8 +700,8 @@ export class Toolkit extends CloudAssemblySourceBuilder {
709700
const workGraph = new WorkGraphBuilder(ioHelper, prebuildAssets).build(stacksAndTheirAssetManifests);
710701

711702
// Unless we are running with '--force', skip already published assets
712-
if (!options.force) {
713-
await removePublishedAssets(workGraph, deployments, options);
703+
if (!options.forceAssetPublishing) {
704+
await removePublishedAssetsFromWorkGraph(workGraph, deployments, options);
714705
}
715706

716707
const graphConcurrency: Concurrency = {
@@ -891,7 +882,7 @@ export class Toolkit extends CloudAssemblySourceBuilder {
891882
stack,
892883
roleArn: options.roleArn,
893884
toolkitStackName: this.toolkitStackName,
894-
force: options.orphanFailedResources,
885+
orphanFailedResources: options.orphanFailedResources,
895886
validateBootstrapStackVersion: options.validateBootstrapStackVersion,
896887
orphanLogicalIds: options.orphanLogicalIds,
897888
});

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -198,12 +198,12 @@ describe('deploy', () => {
198198
successfulDeployment();
199199
});
200200

201-
test('force: true option is used for asset publishing', async () => {
201+
test('forceAssetPublishing: true option is used for asset publishing', async () => {
202202
const publishSingleAsset = jest.spyOn(awsCdkApi.Deployments.prototype, 'publishSingleAsset').mockImplementation();
203203

204204
const cx = await builderFixture(toolkit, 'stack-with-asset');
205205
await toolkit.deploy(cx, {
206-
force: true,
206+
forceAssetPublishing: true,
207207
});
208208

209209
// THEN

packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface BootstrapEnvironmentOptions {
2121
readonly toolkitStackName?: string;
2222
readonly roleArn?: StringWithoutPlaceholders;
2323
readonly parameters?: BootstrappingParameters;
24-
readonly force?: boolean;
24+
readonly forceDeployment?: boolean;
2525

2626
/**
2727
* The source of the bootstrap stack

packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export class BootstrapStack {
7676
parameters: Record<string, string | undefined>,
7777
options: Omit<BootstrapEnvironmentOptions, 'parameters'>,
7878
): Promise<SuccessfulDeployStackResult> {
79-
if (this.currentToolkitInfo.found && !options.force) {
79+
if (this.currentToolkitInfo.found && !options.forceDeployment) {
8080
// Safety checks
8181
const abortResponse = {
8282
type: 'did-deploy-stack',
@@ -136,7 +136,7 @@ export class BootstrapStack {
136136
resolvedEnvironment: this.resolvedEnvironment,
137137
sdk: this.sdk,
138138
sdkProvider: this.sdkProvider,
139-
force: options.force,
139+
forceDeployment: options.forceDeployment,
140140
roleArn: options.roleArn,
141141
tags: options.tags,
142142
deploymentMethod: { method: 'change-set', execute: options.execute },

packages/aws-cdk/lib/api/deployments/deploy-stack.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export interface DeployStackOptions {
149149
* Deploy even if the deployed template is identical to the one we are about to deploy.
150150
* @default false
151151
*/
152-
readonly force?: boolean;
152+
readonly forceDeployment?: boolean;
153153

154154
/**
155155
* Rollback failed deployments
@@ -414,7 +414,7 @@ class FullCloudFormationDeployment {
414414
});
415415
}
416416

417-
if (this.options.force) {
417+
if (this.options.forceDeployment) {
418418
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_WARN.msg(
419419
[
420420
'You used the --force flag, but CloudFormation reported that the deployment would not make any changes.',
@@ -713,7 +713,7 @@ async function canSkipDeploy(
713713
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: checking if we can skip deploy`));
714714

715715
// Forced deploy
716-
if (deployStackOptions.force) {
716+
if (deployStackOptions.forceDeployment) {
717717
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: forced deployment`));
718718
return false;
719719
}

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

+10-23
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export interface DeployStackOptions {
112112
* Force deployment, even if the deployed template is identical to the one we are about to deploy.
113113
* @default false deployment will be skipped if the template is identical
114114
*/
115-
readonly force?: boolean;
115+
readonly forceDeployment?: boolean;
116116

117117
/**
118118
* Extra parameters for CloudFormation
@@ -179,9 +179,10 @@ export interface DeployStackOptions {
179179
/**
180180
* Whether to deploy if the app contains no stacks.
181181
*
182+
* @deprecated this option seems to be unsed inside deployments
182183
* @default false
183184
*/
184-
ignoreNoStacks?: boolean;
185+
readonly ignoreNoStacks?: boolean;
185186
}
186187

187188
export interface RollbackStackOptions {
@@ -197,20 +198,6 @@ export interface RollbackStackOptions {
197198
*/
198199
readonly roleArn?: string;
199200

200-
/**
201-
* Don't show stack deployment events, just wait
202-
*
203-
* @default false
204-
*/
205-
readonly quiet?: boolean;
206-
207-
/**
208-
* Whether we are on a CI system
209-
*
210-
* @default false
211-
*/
212-
readonly ci?: boolean;
213-
214201
/**
215202
* Name of the toolkit stack, if not the default name
216203
*
@@ -219,13 +206,13 @@ export interface RollbackStackOptions {
219206
readonly toolkitStackName?: string;
220207

221208
/**
222-
* Whether to force a rollback or not
209+
* Whether to automatically orphan all failed resources during the rollback
223210
*
224-
* Forcing a rollback will orphan all undeletable resources.
211+
* This will force a rollback that otherwise would have failed.
225212
*
226213
* @default false
227214
*/
228-
readonly force?: boolean;
215+
readonly orphanFailedResources?: boolean;
229216

230217
/**
231218
* Orphan the resources with the given logical IDs
@@ -444,7 +431,7 @@ export class Deployments {
444431
envResources: env.resources,
445432
tags: options.tags,
446433
deploymentMethod,
447-
force: options.force,
434+
forceDeployment: options.forceDeployment,
448435
parameters: options.parameters,
449436
usePreviousParameters: options.usePreviousParameters,
450437
rollback: options.rollback,
@@ -459,7 +446,7 @@ export class Deployments {
459446

460447
public async rollbackStack(options: RollbackStackOptions): Promise<RollbackStackResult> {
461448
let resourcesToSkip: string[] = options.orphanLogicalIds ?? [];
462-
if (options.force && resourcesToSkip.length > 0) {
449+
if (options.orphanFailedResources && resourcesToSkip.length > 0) {
463450
throw new ToolkitError('Cannot combine --force with --orphan');
464451
}
465452

@@ -501,7 +488,7 @@ export class Deployments {
501488
break;
502489

503490
case RollbackChoice.CONTINUE_UPDATE_ROLLBACK:
504-
if (options.force) {
491+
if (options.orphanFailedResources) {
505492
// Find the failed resources from the deployment and automatically skip them
506493
// (Using deployment log because we definitely have `DescribeStackEvents` permissions, and we might not have
507494
// `DescribeStackResources` permissions).
@@ -569,7 +556,7 @@ export class Deployments {
569556
}
570557

571558
// Either we need to ignore some resources to continue the rollback, or something went wrong
572-
if (finalStackState.stackStatus.rollbackChoice === RollbackChoice.CONTINUE_UPDATE_ROLLBACK && options.force) {
559+
if (finalStackState.stackStatus.rollbackChoice === RollbackChoice.CONTINUE_UPDATE_ROLLBACK && options.orphanFailedResources) {
573560
// Do another loop-de-loop
574561
continue;
575562
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ export class CdkToolkit {
487487
execute: options.execute,
488488
changeSetName: options.changeSetName,
489489
deploymentMethod: options.deploymentMethod,
490-
force: options.force,
490+
forceDeployment: options.force,
491491
parameters: Object.assign({}, parameterMap['*'], parameterMap[stack.stackName]),
492492
usePreviousParameters: options.usePreviousParameters,
493493
rollback,
@@ -670,7 +670,7 @@ export class CdkToolkit {
670670
stack,
671671
roleArn: options.roleArn,
672672
toolkitStackName: options.toolkitStackName,
673-
force: options.force,
673+
orphanFailedResources: options.force,
674674
validateBootstrapStackVersion: options.validateBootstrapStackVersion,
675675
orphanLogicalIds: options.orphanLogicalIds,
676676
});

packages/aws-cdk/lib/cli/cli.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
295295
return cli.bootstrap(args.ENVIRONMENTS, {
296296
source,
297297
roleArn: args.roleArn,
298-
force: argv.force,
298+
forceDeployment: argv.force,
299299
toolkitStackName: toolkitStackName,
300300
execute: args.execute,
301301
tags: configuration.settings.get(['tags']),

packages/aws-cdk/test/api/deployments/cloudformation-deployments.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,7 @@ test('rollback stack fails in UPDATE_COMPLETE state', async () => {
932932
expect(response.notInRollbackableState).toBe(true);
933933
});
934934

935-
test('continue rollback stack with force ignores any failed resources', async () => {
935+
test('continue rollback stack with orphanFailedResources ignores any failed resources', async () => {
936936
// GIVEN
937937
givenStacks({
938938
'*': { template: {}, stackStatus: 'UPDATE_ROLLBACK_FAILED' },
@@ -954,7 +954,7 @@ test('continue rollback stack with force ignores any failed resources', async ()
954954
await deployments.rollbackStack({
955955
stack: testStack({ stackName: 'boop' }),
956956
validateBootstrapStackVersion: false,
957-
force: true,
957+
orphanFailedResources: true,
958958
});
959959

960960
// THEN

0 commit comments

Comments
 (0)