Skip to content

Commit aea2e39

Browse files
authored
Replace CreateChangeSet parameter CompareWith with DeploymentMode (#233)
1 parent 20f0bed commit aea2e39

File tree

8 files changed

+131
-10
lines changed

8 files changed

+131
-10
lines changed

src/cfnEnvironments/CfnEnvironmentRequestType.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { OnStackFailure } from '@aws-sdk/client-cloudformation';
22
import { RequestType } from 'vscode-languageserver';
33
import { DocumentType } from '../document/Document';
4+
import { DeploymentMode } from '../stacks/actions/StackActionRequestType';
45

56
export type DocumentInfo = {
67
type: DocumentType;
@@ -15,6 +16,7 @@ export type DeploymentConfig = {
1516
includeNestedStacks?: boolean;
1617
importExistingResources?: boolean;
1718
onStackFailure?: OnStackFailure;
19+
deploymentMode?: DeploymentMode;
1820
};
1921

2022
export type ParsedCfnEnvironmentFile = {

src/services/CfnService.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,10 @@ import {
5555
waitUntilStackDeleteComplete,
5656
Tag,
5757
OnStackFailure,
58+
ChangeSetType,
5859
} from '@aws-sdk/client-cloudformation';
5960
import { WaiterConfiguration, WaiterResult } from '@smithy/util-waiter';
61+
import { DeploymentMode } from '../stacks/actions/StackActionRequestType';
6062
import { Count } from '../telemetry/TelemetryDecorator';
6163
import { AwsClient } from './AwsClient';
6264

@@ -126,13 +128,13 @@ export class CfnService {
126128
TemplateURL?: string;
127129
Parameters?: Parameter[];
128130
Capabilities?: Capability[];
129-
ChangeSetType?: 'CREATE' | 'UPDATE' | 'IMPORT';
131+
ChangeSetType?: ChangeSetType;
130132
ResourcesToImport?: ResourceToImport[];
131-
CompareWith?: string;
132133
OnStackFailure?: OnStackFailure;
133134
IncludeNestedStacks?: boolean;
134135
Tags?: Tag[];
135136
ImportExistingResources?: boolean;
137+
DeploymentMode?: DeploymentMode;
136138
}): Promise<CreateChangeSetCommandOutput> {
137139
return await this.withClient((client) => client.send(new CreateChangeSetCommand(params)));
138140
}

src/services/CfnServiceV2.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import {
99
ResourceTargetDefinition,
1010
Change,
1111
ResourceChangeDetail,
12+
ChangeSetType,
1213
} from '@aws-sdk/client-cloudformation';
14+
import { DeploymentMode } from '../stacks/actions/StackActionRequestType';
1315
import { Measure } from '../telemetry/TelemetryDecorator';
1416
import { CFN_CLIENT_PATH } from '../utils/ClientUtil';
1517
import { DynamicModuleLoader } from '../utils/DynamicModuleLoader';
@@ -65,9 +67,9 @@ export class CfnServiceV2 extends CfnService {
6567
TemplateURL?: string;
6668
Parameters?: Parameter[];
6769
Capabilities?: Capability[];
68-
ChangeSetType?: 'CREATE' | 'UPDATE' | 'IMPORT';
70+
ChangeSetType?: ChangeSetType;
6971
ResourcesToImport?: ResourceToImport[];
70-
CompareWith?: string;
72+
DeploymentMode?: DeploymentMode;
7173
}): Promise<CreateChangeSetCommandOutput> {
7274
type CfnClient = typeof import('@aws-sdk/client-cloudformation') & {
7375
CreateChangeSetCommand: new (input: any) => any;

src/stacks/actions/StackActionOperations.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Change, ChangeSetType, StackStatus } from '@aws-sdk/client-cloudformation';
1+
import { Change, ChangeSetType, StackStatus, OnStackFailure } from '@aws-sdk/client-cloudformation';
22
import { WaiterState } from '@smithy/util-waiter';
33
import { dump } from 'js-yaml';
44
import { DateTime } from 'luxon';
@@ -24,6 +24,8 @@ import {
2424
StackActionState,
2525
CreateValidationParams,
2626
ValidationDetail,
27+
DeploymentMode,
28+
ResourceToImport,
2729
} from './StackActionRequestType';
2830
import {
2931
StackActionWorkflowState,
@@ -39,6 +41,43 @@ function logCleanupError(error: unknown, workflowId: string, changeSetName: stri
3941
logger.warn(error, `Failed to cleanup ${operation} ${workflowId} ${changeSetName}`);
4042
}
4143

44+
export function computeEligibleDeploymentMode(
45+
changeSetType: ChangeSetType,
46+
deploymentMode?: DeploymentMode,
47+
importExistingResources?: boolean,
48+
resourcesToImport?: ResourceToImport[],
49+
includeNestedStacks?: boolean,
50+
onStackFailure?: OnStackFailure,
51+
): DeploymentMode | undefined {
52+
if (!deploymentMode) {
53+
return undefined;
54+
}
55+
56+
if (deploymentMode === DeploymentMode.REVERT_DRIFT) {
57+
// import is not supported
58+
if (importExistingResources || (resourcesToImport && resourcesToImport.length > 0)) {
59+
return undefined;
60+
}
61+
62+
// nested stacks is not supported
63+
if (includeNestedStacks) {
64+
return undefined;
65+
}
66+
67+
// only UPDATE is supported
68+
if (changeSetType !== ChangeSetType.UPDATE) {
69+
return undefined;
70+
}
71+
72+
// only ROLLBACK is supported
73+
if (onStackFailure && onStackFailure !== OnStackFailure.ROLLBACK) {
74+
return undefined;
75+
}
76+
}
77+
78+
return deploymentMode;
79+
}
80+
4281
export async function processChangeSet(
4382
cfnService: CfnService,
4483
documentManager: DocumentManager,
@@ -77,6 +116,15 @@ export async function processChangeSet(
77116

78117
const changeSetName = `${changeSetNamePrefix}-${params.id}-${uuidv4()}`;
79118

119+
const deploymentMode = computeEligibleDeploymentMode(
120+
changeSetType,
121+
params.deploymentMode,
122+
params.importExistingResources,
123+
params.resourcesToImport,
124+
params.includeNestedStacks,
125+
params.onStackFailure,
126+
);
127+
80128
await cfnService.createChangeSet({
81129
StackName: params.stackName,
82130
ChangeSetName: changeSetName,
@@ -86,11 +134,11 @@ export async function processChangeSet(
86134
Capabilities: params.capabilities,
87135
ChangeSetType: changeSetType,
88136
ResourcesToImport: params.resourcesToImport,
89-
CompareWith: changeSetType === 'UPDATE' ? 'LIVE_STATE' : undefined,
90137
OnStackFailure: params.onStackFailure,
91138
IncludeNestedStacks: params.includeNestedStacks,
92139
Tags: params.tags,
93140
ImportExistingResources: params.importExistingResources,
141+
DeploymentMode: deploymentMode,
94142
});
95143

96144
return changeSetName;

src/stacks/actions/StackActionParser.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
CreateDeploymentParams,
1212
CreateValidationParams,
1313
DeleteChangeSetParams,
14+
DeploymentMode,
1415
TemplateUri,
1516
} from './StackActionRequestType';
1617

@@ -40,6 +41,8 @@ const ResourceToImportSchema = z.object({
4041
ResourceIdentifier: z.record(z.string(), z.string()),
4142
});
4243

44+
const DeploymentModeSchema = z.enum([DeploymentMode.REVERT_DRIFT]);
45+
4346
const CreateValidationParamsSchema = z.object({
4447
id: z.string().min(1),
4548
uri: z.string().min(1),
@@ -52,6 +55,7 @@ const CreateValidationParamsSchema = z.object({
5255
includeNestedStacks: z.boolean().optional(),
5356
tags: z.array(TagSchema).optional(),
5457
importExistingResources: z.boolean().optional(),
58+
deploymentMode: DeploymentModeSchema.optional(),
5559
s3Bucket: z.string().optional(),
5660
s3Key: z.string().optional(),
5761
});

src/stacks/actions/StackActionRequestType.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ export type ResourceToImport = {
1717
ResourceIdentifier: Record<string, string>;
1818
};
1919

20+
export enum DeploymentMode {
21+
REVERT_DRIFT = 'REVERT_DRIFT',
22+
}
23+
2024
export type CreateValidationParams = Identifiable & {
2125
uri: string;
2226
stackName: string;
@@ -28,6 +32,7 @@ export type CreateValidationParams = Identifiable & {
2832
includeNestedStacks?: boolean;
2933
tags?: Tag[];
3034
importExistingResources?: boolean;
35+
deploymentMode?: DeploymentMode;
3136
s3Bucket?: string;
3237
s3Key?: string;
3338
};

src/stacks/actions/ValidationWorkflowV2.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { CfnInfraCore } from '../../server/CfnInfraCore';
88
import { CfnServiceV2 } from '../../services/CfnServiceV2';
99
import { DiagnosticCoordinator } from '../../services/DiagnosticCoordinator';
1010
import { S3Service } from '../../services/S3Service';
11-
import { LoggerFactory } from '../../telemetry/LoggerFactory';
1211
import { extractErrorMessage } from '../../utils/Errors';
1312
import {
1413
waitForChangeSetValidation,
@@ -23,8 +22,6 @@ import { ValidationWorkflow } from './ValidationWorkflow';
2322
export const VALIDATION_V2_NAME = 'Enhanced Validation';
2423

2524
export class ValidationWorkflowV2 extends ValidationWorkflow {
26-
protected override readonly log = LoggerFactory.getLogger(ValidationWorkflowV2);
27-
2825
constructor(
2926
protected cfnServiceV2: CfnServiceV2,
3027
documentManager: DocumentManager,

tst/unit/stackActions/StackActionWorkflowOperations.test.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Change, ChangeSetType, StackStatus } from '@aws-sdk/client-cloudformation';
1+
import { Change, ChangeSetType, StackStatus, OnStackFailure } from '@aws-sdk/client-cloudformation';
22
import { WaiterState } from '@smithy/util-waiter';
33
import { DateTime } from 'luxon';
44
import { stubInterface } from 'ts-sinon';
@@ -18,12 +18,14 @@ import {
1818
parseValidationEvents,
1919
publishValidationDiagnostics,
2020
isStackInReview,
21+
computeEligibleDeploymentMode,
2122
} from '../../../src/stacks/actions/StackActionOperations';
2223
import {
2324
CreateValidationParams,
2425
StackActionPhase,
2526
StackActionState,
2627
ValidationDetail,
28+
DeploymentMode,
2729
} from '../../../src/stacks/actions/StackActionRequestType';
2830
import { StackActionWorkflowState } from '../../../src/stacks/actions/StackActionWorkflowType';
2931
import { ExtensionName } from '../../../src/utils/ExtensionConfig';
@@ -578,4 +580,63 @@ describe('StackActionWorkflowOperations', () => {
578580
expect(result).toBe(true);
579581
});
580582
});
583+
584+
describe('computeEligibleDeploymentMode', () => {
585+
it('should return undefined when deploymentMode is not provided', () => {
586+
const result = computeEligibleDeploymentMode(ChangeSetType.UPDATE, undefined);
587+
expect(result).toBeUndefined();
588+
});
589+
590+
it('should return deploymentMode when all conditions are met for REVERT_DRIFT', () => {
591+
const result = computeEligibleDeploymentMode(
592+
ChangeSetType.UPDATE,
593+
DeploymentMode.REVERT_DRIFT,
594+
false,
595+
undefined,
596+
false,
597+
OnStackFailure.ROLLBACK,
598+
);
599+
expect(result).toBe(DeploymentMode.REVERT_DRIFT);
600+
});
601+
602+
it('should return undefined when changeSetType is CREATE', () => {
603+
const result = computeEligibleDeploymentMode(ChangeSetType.CREATE, DeploymentMode.REVERT_DRIFT);
604+
expect(result).toBeUndefined();
605+
});
606+
607+
it('should return undefined when importExistingResources is true', () => {
608+
const result = computeEligibleDeploymentMode(ChangeSetType.UPDATE, DeploymentMode.REVERT_DRIFT, true);
609+
expect(result).toBeUndefined();
610+
});
611+
612+
it('should return undefined when resourcesToImport has items', () => {
613+
const result = computeEligibleDeploymentMode(ChangeSetType.UPDATE, DeploymentMode.REVERT_DRIFT, false, [
614+
{ LogicalResourceId: 'test', ResourceType: 'AWS::S3::Bucket', ResourceIdentifier: {} },
615+
]);
616+
expect(result).toBeUndefined();
617+
});
618+
619+
it('should return undefined when includeNestedStacks is true', () => {
620+
const result = computeEligibleDeploymentMode(
621+
ChangeSetType.UPDATE,
622+
DeploymentMode.REVERT_DRIFT,
623+
false,
624+
undefined,
625+
true,
626+
);
627+
expect(result).toBeUndefined();
628+
});
629+
630+
it('should return undefined when onStackFailure is DO_NOTHING', () => {
631+
const result = computeEligibleDeploymentMode(
632+
ChangeSetType.UPDATE,
633+
DeploymentMode.REVERT_DRIFT,
634+
false,
635+
undefined,
636+
false,
637+
OnStackFailure.DO_NOTHING,
638+
);
639+
expect(result).toBeUndefined();
640+
});
641+
});
581642
});

0 commit comments

Comments
 (0)