diff --git a/packages/@aws-cdk/toolkit-lib/lib/actions/deploy/private/deploy-options.ts b/packages/@aws-cdk/toolkit-lib/lib/actions/deploy/private/deploy-options.ts index 9effc35a0..f848c3f06 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/actions/deploy/private/deploy-options.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/actions/deploy/private/deploy-options.ts @@ -16,12 +16,11 @@ export interface BaseDeployOptions { readonly roleArn?: string; /** - * Always deploy, even if templates are identical. + * Deploy even if the deployed template is identical to the one we are about to deploy. * * @default false - * @deprecated the options currently covers multiple different functionalities and will be split out in future */ - readonly force?: boolean; + readonly forceDeployment?: boolean; /** * Deployment method @@ -44,6 +43,21 @@ export interface BaseDeployOptions { */ readonly rollback?: boolean; + /** + * Automatically orphan resources that failed during rollback + * + * Has no effect if `rollback` is `false`. + * + * @default false + */ + readonly orphanFailedResourcesDuringRollback?: boolean; + + /** + * Force asset publishing even if the assets have not changed + * @default false + */ + readonly forceAssetPublishing?: boolean; + /** * Reuse the assets with the given asset IDs */ diff --git a/packages/@aws-cdk/toolkit-lib/lib/actions/deploy/private/helpers.ts b/packages/@aws-cdk/toolkit-lib/lib/actions/deploy/private/helpers.ts index 3f9d19a72..ef9749cce 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/actions/deploy/private/helpers.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/actions/deploy/private/helpers.ts @@ -27,7 +27,7 @@ export function buildParameterMap(parameters?: Map): /** * Remove the asset publishing and building from the work graph for assets that are already in place */ -export async function removePublishedAssets(graph: WorkGraph, deployments: Deployments, options: DeployOptions) { +export async function removePublishedAssetsFromWorkGraph(graph: WorkGraph, deployments: Deployments, options: DeployOptions) { await graph.removeUnnecessaryAssets(assetNode => deployments.isSingleAssetPublished(assetNode.assetManifest, assetNode.asset, { stack: assetNode.parentStack, roleArn: options.roleArn, diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index b66c84710..da463d3cb 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -9,7 +9,7 @@ import { assemblyFromSource } from './private'; import type { BootstrapEnvironments, BootstrapOptions, BootstrapResult, EnvironmentBootstrapResult } from '../actions/bootstrap'; import { BootstrapSource } from '../actions/bootstrap'; import { AssetBuildTime, type DeployOptions } from '../actions/deploy'; -import { type ExtendedDeployOptions, buildParameterMap, createHotswapPropertyOverrides, removePublishedAssets } from '../actions/deploy/private'; +import { type ExtendedDeployOptions, buildParameterMap, createHotswapPropertyOverrides, removePublishedAssetsFromWorkGraph } from '../actions/deploy/private'; import { type DestroyOptions } from '../actions/destroy'; import type { ChangeSetDiffOptions, DiffOptions, LocalFileDiffOptions } from '../actions/diff'; import { DiffMethod } from '../actions/diff'; @@ -487,7 +487,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab stack: assetNode.parentStack, roleArn: options.roleArn, stackName: assetNode.parentStack.stackName, - forcePublish: options.force, + forcePublish: options.forceAssetPublishing, }); await publishAssetSpan.end(); }; @@ -584,7 +584,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab notificationArns, tags, deploymentMethod: options.deploymentMethod, - force: options.force, + forceDeployment: options.forceDeployment, parameters: Object.assign({}, parameterMap['*'], parameterMap[stack.stackName]), usePreviousParameters: options.parameters?.keepExistingParameters, rollback, @@ -605,22 +605,18 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab : `Stack is in a paused fail state (${r.status}) and command line arguments do not include "--no-rollback"`; const question = `${motivation}. Perform a regular deployment`; - if (options.force) { - await ioHelper.notify(IO.DEFAULT_TOOLKIT_WARN.msg(`${motivation}. Rolling back first (--force).`)); - } else { - const confirmed = await ioHelper.requestResponse(IO.CDK_TOOLKIT_I5050.req(question, { - motivation, - concurrency, - })); - if (!confirmed) { - throw new ToolkitError('Aborted by user'); - } + const confirmed = await ioHelper.requestResponse(IO.CDK_TOOLKIT_I5050.req(question, { + motivation, + concurrency, + })); + if (!confirmed) { + throw new ToolkitError('Aborted by user'); } // Perform a rollback await this._rollback(assembly, action, { stacks: { patterns: [stack.hierarchicalId], strategy: StackSelectionStrategy.PATTERN_MUST_MATCH_SINGLE }, - orphanFailedResources: options.force, + orphanFailedResources: options.orphanFailedResourcesDuringRollback, }); // Go around through the 'while' loop again but switch rollback to true. @@ -632,17 +628,12 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab const motivation = 'Change includes a replacement which cannot be deployed with "--no-rollback"'; const question = `${motivation}. Perform a regular deployment`; - // @todo no force here - if (options.force) { - await ioHelper.notify(IO.DEFAULT_TOOLKIT_WARN.msg(`${motivation}. Proceeding with regular deployment (--force).`)); - } else { - const confirmed = await ioHelper.requestResponse(IO.CDK_TOOLKIT_I5050.req(question, { - motivation, - concurrency, - })); - if (!confirmed) { - throw new ToolkitError('Aborted by user'); - } + const confirmed = await ioHelper.requestResponse(IO.CDK_TOOLKIT_I5050.req(question, { + motivation, + concurrency, + })); + if (!confirmed) { + throw new ToolkitError('Aborted by user'); } // Go around through the 'while' loop again but switch rollback to true. @@ -718,8 +709,8 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab const workGraph = new WorkGraphBuilder(ioHelper, prebuildAssets).build(stacksAndTheirAssetManifests); // Unless we are running with '--force', skip already published assets - if (!options.force) { - await removePublishedAssets(workGraph, deployments, options); + if (!options.forceAssetPublishing) { + await removePublishedAssetsFromWorkGraph(workGraph, deployments, options); } const graphConcurrency: Concurrency = { @@ -892,7 +883,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab stack, roleArn: options.roleArn, toolkitStackName: this.toolkitStackName, - force: options.orphanFailedResources, + orphanFailedResources: options.orphanFailedResources, validateBootstrapStackVersion: options.validateBootstrapStackVersion, orphanLogicalIds: options.orphanLogicalIds, }); diff --git a/packages/@aws-cdk/toolkit-lib/test/actions/deploy.test.ts b/packages/@aws-cdk/toolkit-lib/test/actions/deploy.test.ts index 4fc1a8f28..9c7feed9f 100644 --- a/packages/@aws-cdk/toolkit-lib/test/actions/deploy.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/actions/deploy.test.ts @@ -198,12 +198,12 @@ describe('deploy', () => { successfulDeployment(); }); - test('force: true option is used for asset publishing', async () => { + test('forceAssetPublishing: true option is used for asset publishing', async () => { const publishSingleAsset = jest.spyOn(awsCdkApi.Deployments.prototype, 'publishSingleAsset').mockImplementation(); const cx = await builderFixture(toolkit, 'stack-with-asset'); await toolkit.deploy(cx, { - force: true, + forceAssetPublishing: true, }); // THEN diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts index 6178c5c43..56e7b009f 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts @@ -21,7 +21,7 @@ export interface BootstrapEnvironmentOptions { readonly toolkitStackName?: string; readonly roleArn?: StringWithoutPlaceholders; readonly parameters?: BootstrappingParameters; - readonly force?: boolean; + readonly forceDeployment?: boolean; /** * The source of the bootstrap stack diff --git a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts index f29be758f..08a13c1e5 100644 --- a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts +++ b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts @@ -76,7 +76,7 @@ export class BootstrapStack { parameters: Record, options: Omit, ): Promise { - if (this.currentToolkitInfo.found && !options.force) { + if (this.currentToolkitInfo.found && !options.forceDeployment) { // Safety checks const abortResponse = { type: 'did-deploy-stack', @@ -136,7 +136,7 @@ export class BootstrapStack { resolvedEnvironment: this.resolvedEnvironment, sdk: this.sdk, sdkProvider: this.sdkProvider, - force: options.force, + forceDeployment: options.forceDeployment, roleArn: options.roleArn, tags: options.tags, deploymentMethod: { method: 'change-set', execute: options.execute }, diff --git a/packages/aws-cdk/lib/api/deployments/deploy-stack.ts b/packages/aws-cdk/lib/api/deployments/deploy-stack.ts index 3988583ec..66fcb6737 100644 --- a/packages/aws-cdk/lib/api/deployments/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deployments/deploy-stack.ts @@ -149,7 +149,7 @@ export interface DeployStackOptions { * Deploy even if the deployed template is identical to the one we are about to deploy. * @default false */ - readonly force?: boolean; + readonly forceDeployment?: boolean; /** * Rollback failed deployments @@ -414,7 +414,7 @@ class FullCloudFormationDeployment { }); } - if (this.options.force) { + if (this.options.forceDeployment) { await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_WARN.msg( [ 'You used the --force flag, but CloudFormation reported that the deployment would not make any changes.', @@ -713,7 +713,7 @@ async function canSkipDeploy( await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: checking if we can skip deploy`)); // Forced deploy - if (deployStackOptions.force) { + if (deployStackOptions.forceDeployment) { await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: forced deployment`)); return false; } diff --git a/packages/aws-cdk/lib/api/deployments/deployments.ts b/packages/aws-cdk/lib/api/deployments/deployments.ts index 6c3783c6b..bca4bd1aa 100644 --- a/packages/aws-cdk/lib/api/deployments/deployments.ts +++ b/packages/aws-cdk/lib/api/deployments/deployments.ts @@ -112,7 +112,7 @@ export interface DeployStackOptions { * Force deployment, even if the deployed template is identical to the one we are about to deploy. * @default false deployment will be skipped if the template is identical */ - readonly force?: boolean; + readonly forceDeployment?: boolean; /** * Extra parameters for CloudFormation @@ -179,9 +179,10 @@ export interface DeployStackOptions { /** * Whether to deploy if the app contains no stacks. * + * @deprecated this option seems to be unsed inside deployments * @default false */ - ignoreNoStacks?: boolean; + readonly ignoreNoStacks?: boolean; } export interface RollbackStackOptions { @@ -197,20 +198,6 @@ export interface RollbackStackOptions { */ readonly roleArn?: string; - /** - * Don't show stack deployment events, just wait - * - * @default false - */ - readonly quiet?: boolean; - - /** - * Whether we are on a CI system - * - * @default false - */ - readonly ci?: boolean; - /** * Name of the toolkit stack, if not the default name * @@ -219,13 +206,13 @@ export interface RollbackStackOptions { readonly toolkitStackName?: string; /** - * Whether to force a rollback or not + * Whether to automatically orphan all failed resources during the rollback * - * Forcing a rollback will orphan all undeletable resources. + * This will force a rollback that otherwise would have failed. * * @default false */ - readonly force?: boolean; + readonly orphanFailedResources?: boolean; /** * Orphan the resources with the given logical IDs @@ -444,7 +431,7 @@ export class Deployments { envResources: env.resources, tags: options.tags, deploymentMethod, - force: options.force, + forceDeployment: options.forceDeployment, parameters: options.parameters, usePreviousParameters: options.usePreviousParameters, rollback: options.rollback, @@ -459,7 +446,7 @@ export class Deployments { public async rollbackStack(options: RollbackStackOptions): Promise { let resourcesToSkip: string[] = options.orphanLogicalIds ?? []; - if (options.force && resourcesToSkip.length > 0) { + if (options.orphanFailedResources && resourcesToSkip.length > 0) { throw new ToolkitError('Cannot combine --force with --orphan'); } @@ -501,7 +488,7 @@ export class Deployments { break; case RollbackChoice.CONTINUE_UPDATE_ROLLBACK: - if (options.force) { + if (options.orphanFailedResources) { // Find the failed resources from the deployment and automatically skip them // (Using deployment log because we definitely have `DescribeStackEvents` permissions, and we might not have // `DescribeStackResources` permissions). @@ -569,7 +556,7 @@ export class Deployments { } // Either we need to ignore some resources to continue the rollback, or something went wrong - if (finalStackState.stackStatus.rollbackChoice === RollbackChoice.CONTINUE_UPDATE_ROLLBACK && options.force) { + if (finalStackState.stackStatus.rollbackChoice === RollbackChoice.CONTINUE_UPDATE_ROLLBACK && options.orphanFailedResources) { // Do another loop-de-loop continue; } diff --git a/packages/aws-cdk/lib/cli/cdk-toolkit.ts b/packages/aws-cdk/lib/cli/cdk-toolkit.ts index ebacc184c..b360b44ff 100644 --- a/packages/aws-cdk/lib/cli/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cli/cdk-toolkit.ts @@ -487,7 +487,7 @@ export class CdkToolkit { execute: options.execute, changeSetName: options.changeSetName, deploymentMethod: options.deploymentMethod, - force: options.force, + forceDeployment: options.force, parameters: Object.assign({}, parameterMap['*'], parameterMap[stack.stackName]), usePreviousParameters: options.usePreviousParameters, rollback, @@ -670,7 +670,7 @@ export class CdkToolkit { stack, roleArn: options.roleArn, toolkitStackName: options.toolkitStackName, - force: options.force, + orphanFailedResources: options.force, validateBootstrapStackVersion: options.validateBootstrapStackVersion, orphanLogicalIds: options.orphanLogicalIds, }); diff --git a/packages/aws-cdk/lib/cli/cli.ts b/packages/aws-cdk/lib/cli/cli.ts index 71a71b9dd..d7737f032 100644 --- a/packages/aws-cdk/lib/cli/cli.ts +++ b/packages/aws-cdk/lib/cli/cli.ts @@ -295,7 +295,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise { expect(response.notInRollbackableState).toBe(true); }); -test('continue rollback stack with force ignores any failed resources', async () => { +test('continue rollback stack with orphanFailedResources ignores any failed resources', async () => { // GIVEN givenStacks({ '*': { template: {}, stackStatus: 'UPDATE_ROLLBACK_FAILED' }, @@ -954,7 +954,7 @@ test('continue rollback stack with force ignores any failed resources', async () await deployments.rollbackStack({ stack: testStack({ stackName: 'boop' }), validateBootstrapStackVersion: false, - force: true, + orphanFailedResources: true, }); // THEN diff --git a/packages/aws-cdk/test/api/deployments/deploy-stack.test.ts b/packages/aws-cdk/test/api/deployments/deploy-stack.test.ts index 6b32eec1a..e217d6787 100644 --- a/packages/aws-cdk/test/api/deployments/deploy-stack.test.ts +++ b/packages/aws-cdk/test/api/deployments/deploy-stack.test.ts @@ -152,7 +152,7 @@ test("calls tryHotswapDeployment() if 'hotswap' is `HotswapMode.HOTSWAP_ONLY`", ...standardDeployStackArguments(), hotswap: HotswapMode.HOTSWAP_ONLY, extraUserAgent: 'extra-user-agent', - force: true, // otherwise, deployment would be skipped + forceDeployment: true, // otherwise, deployment would be skipped }); // THEN @@ -250,7 +250,7 @@ test('call UpdateStack when method=direct and the stack exists already', async ( await testDeployStack({ ...standardDeployStackArguments(), deploymentMethod: { method: 'direct' }, - force: true, + forceDeployment: true, }); // THEN @@ -270,7 +270,7 @@ test('method=direct and no updates to be performed', async () => { const ret = await testDeployStack({ ...standardDeployStackArguments(), deploymentMethod: { method: 'direct' }, - force: true, + forceDeployment: true, }); // THEN @@ -670,7 +670,7 @@ test('deploy not skipped if template did not change and --force is applied', asy // WHEN await testDeployStack({ ...standardDeployStackArguments(), - force: true, + forceDeployment: true, }); // THEN @@ -895,7 +895,7 @@ test('empty change set is deleted if --execute is given', async () => { await testDeployStack({ ...standardDeployStackArguments(), deploymentMethod: { method: 'change-set', execute: true }, - force: true, // Necessary to bypass "skip deploy" + forceDeployment: true, // Necessary to bypass "skip deploy" }); // THEN @@ -1169,7 +1169,7 @@ test.each([ ...standardDeployStackArguments(), stack: FAKE_STACK, rollback: rollback === 'rollback', - force: true, // Bypass 'canSkipDeploy' + forceDeployment: true, // Bypass 'canSkipDeploy' }); // THEN