Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function buildParameterMap(parameters?: Map<string, string | undefined>):
/**
* 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) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name just confused me. The function does not actually remove the assets, it just removes the upload job from the work graph.

await graph.removeUnnecessaryAssets(assetNode => deployments.isSingleAssetPublished(assetNode.assetManifest, assetNode.asset, {
stack: assetNode.parentStack,
roleArn: options.roleArn,
Expand Down
47 changes: 19 additions & 28 deletions packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
};
Expand Down Expand Up @@ -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,
Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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,
});
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/toolkit-lib/test/actions/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class BootstrapStack {
parameters: Record<string, string | undefined>,
options: Omit<BootstrapEnvironmentOptions, 'parameters'>,
): Promise<SuccessfulDeployStackResult> {
if (this.currentToolkitInfo.found && !options.force) {
if (this.currentToolkitInfo.found && !options.forceDeployment) {
// Safety checks
const abortResponse = {
type: 'did-deploy-stack',
Expand Down Expand Up @@ -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 },
Expand Down
6 changes: 3 additions & 3 deletions packages/aws-cdk/lib/api/deployments/deploy-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.',
Expand Down Expand Up @@ -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;
}
Expand Down
33 changes: 10 additions & 23 deletions packages/aws-cdk/lib/api/deployments/deployments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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
*
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -459,7 +446,7 @@ export class Deployments {

public async rollbackStack(options: RollbackStackOptions): Promise<RollbackStackResult> {
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');
}

Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/aws-cdk/lib/cli/cdk-toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
});
Expand Down
2 changes: 1 addition & 1 deletion packages/aws-cdk/lib/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
return cli.bootstrap(args.ENVIRONMENTS, {
source,
roleArn: args.roleArn,
force: argv.force,
forceDeployment: argv.force,
toolkitStackName: toolkitStackName,
execute: args.execute,
tags: configuration.settings.get(['tags']),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -932,7 +932,7 @@ test('rollback stack fails in UPDATE_COMPLETE state', async () => {
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' },
Expand All @@ -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
Expand Down
Loading