Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): hotswap deployments for StepFunctions State Machines #16489

Merged
merged 126 commits into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
fcdde02
created mock
comcalvi Sep 3, 2021
4a277a6
skeleton of implementation
comcalvi Sep 3, 2021
74bb5b4
temporary
comcalvi Sep 7, 2021
a8dd68d
added step function hotswap operation
comcalvi Sep 8, 2021
34ab3ae
updated mockSDK
comcalvi Sep 8, 2021
51ae0b2
tmp
comcalvi Sep 8, 2021
28684af
tmp
comcalvi Sep 8, 2021
3c91db1
fixed errors
comcalvi Sep 8, 2021
e211976
changed type of definition to string
comcalvi Sep 8, 2021
e6cddcc
stash
comcalvi Sep 8, 2021
ce66cc7
fixed the null pointer bug
comcalvi Sep 9, 2021
37154a7
added stateMachineArn
comcalvi Sep 9, 2021
7e603b0
updated the arn handling to work correctly
comcalvi Sep 9, 2021
89a927d
simple step function hotswapping achieved
comcalvi Sep 9, 2021
d220df1
updated tests
comcalvi Sep 10, 2021
39d1a8d
new state machines cannot be short circuited
comcalvi Sep 13, 2021
23f5aef
added test for non definition string state machine resource changes
comcalvi Sep 13, 2021
465bd63
changes to properties other than the definition string require full d…
comcalvi Sep 13, 2021
6fe8b9d
added test to check that changes to non-Code changes to Lambda functi…
comcalvi Sep 13, 2021
7377ab9
cleaned up code
comcalvi Sep 13, 2021
fff366b
added mock of cloudFormation() sdk call to hotswap tests to enable te…
comcalvi Sep 13, 2021
5a64e97
added unit test for step functions with no names
comcalvi Sep 14, 2021
324749b
clean up code
comcalvi Sep 14, 2021
aab766c
refactored
comcalvi Sep 14, 2021
1728514
divided test files
comcalvi Sep 14, 2021
a42f685
refactored
comcalvi Sep 14, 2021
66a5d72
clean up code
comcalvi Sep 14, 2021
a1d5d18
updated readme
comcalvi Sep 14, 2021
657b726
renamed step-functions.ts and updated readme
comcalvi Sep 15, 2021
e4c6d24
updated readme and fixed include
comcalvi Sep 15, 2021
ad2874c
incorporated adam's feedback. Implemented better algorithm for findin…
comcalvi Sep 17, 2021
da97198
began adding new type
comcalvi Sep 18, 2021
ca67123
implemented new algorithm
comcalvi Sep 20, 2021
54e568a
updated algorithm and docs
comcalvi Sep 20, 2021
2a1a990
refactored resource finders
comcalvi Sep 21, 2021
ce30d20
check me out for the devcon demo
comcalvi Sep 22, 2021
539f9ac
post demo
comcalvi Sep 22, 2021
15cc6ab
debug added
comcalvi Sep 22, 2021
5272c59
added partition resolving logic
comcalvi Sep 24, 2021
89fc397
refactor
comcalvi Sep 27, 2021
f8b9fc5
refactored mockListStackResources()
comcalvi Sep 27, 2021
aa1367c
refactored the mockStackResource in both lambda and step functions
comcalvi Sep 27, 2021
5f59592
refactored tests
comcalvi Sep 27, 2021
3848b30
updated formatting
comcalvi Sep 27, 2021
62f284c
added test case
comcalvi Sep 28, 2021
c294581
addressed PR comments
comcalvi Sep 28, 2021
7a70de8
Introduce a new CloudFormationExecutableTemplate class.
skinny85 Sep 15, 2021
4856cab
Convert CloudFormationExecutableTemplateProps.evaluateCfnExpression()…
skinny85 Sep 15, 2021
a12dd40
Handle 'Ref's in the evaluated template.
skinny85 Sep 15, 2021
cca7263
Handle 'GetAtt's in the evaluated template.
skinny85 Sep 16, 2021
53458d6
Add CloudFormationExecutableTemplate.findPhysicalNameFor() method.
skinny85 Sep 17, 2021
1e03709
Remove the no longer used evaluate-cfn.ts file.
skinny85 Sep 28, 2021
43f7bcb
Merge branch 'master' of https://github.com/aws/aws-cdk into hotswap-…
comcalvi Sep 29, 2021
a26e7fb
fix merge conflicts
comcalvi Sep 29, 2021
cf2cebf
added support for AWS::Partition and tests for AWS::Partition
comcalvi Sep 29, 2021
4a47e86
cleaned up code
comcalvi Sep 30, 2021
ca2956f
added tests to cover resources with properties that match those of ho…
comcalvi Sep 30, 2021
e8c2a0e
refactored establishing the resource's physical name to common.ts
comcalvi Sep 30, 2021
ef47aee
cleaned up code
comcalvi Sep 30, 2021
07efe96
merge conflict partial resolution
comcalvi Oct 1, 2021
895889a
fixed merge conflicts
comcalvi Oct 1, 2021
c8f14a2
cleaned up code
comcalvi Oct 1, 2021
a61578b
clean up
comcalvi Oct 1, 2021
840985a
removed unneeded file
comcalvi Oct 1, 2021
e28921d
revert unneeded change
comcalvi Oct 1, 2021
886fd93
reverted format of findAllHotswappableChanges()
comcalvi Oct 1, 2021
e4f9dc6
move declaration of hotswappableResources down
comcalvi Oct 1, 2021
2a5151e
renamed nonHotswappableResourceFound to resourceHotswapEvaluation
comcalvi Oct 1, 2021
90c6eda
added test that shows changes to hotswappable resources and a change …
comcalvi Oct 1, 2021
a14b80f
refactored resolving of detector results by creating changesDetection…
comcalvi Oct 1, 2021
5438bee
removed unused function
comcalvi Oct 1, 2021
3af3c28
added public readonly to newValue and propertyUpdates of Hotswappable…
comcalvi Oct 1, 2021
ae664ef
removed unneeded interface HotswappableResource
comcalvi Oct 1, 2021
3136cd2
reordered arguments of establishHotswappableResourceName()
comcalvi Oct 1, 2021
5af3ad6
inlined functionNameInCfnTemplate and removed unneeded ? from change.…
comcalvi Oct 1, 2021
ba59871
removed unneeded change for if( not change.newValue) from lambda
comcalvi Oct 1, 2021
9c60e12
reverted merge artifacts
comcalvi Oct 1, 2021
bfbf8da
changed formatting of is StateMachineDefinitionOnlyChange
comcalvi Oct 1, 2021
85252c8
loop folding
comcalvi Oct 1, 2021
777bed0
fixed linter errors
comcalvi Oct 1, 2021
9e95b09
fixed the old-style synthesis issue spawning from using the return de…
comcalvi Oct 2, 2021
c0323e8
added comment to explain that leaving the properties unspecified make…
comcalvi Oct 2, 2021
dd4a220
removed logicalId from stepFunctionsStateMachine
comcalvi Oct 2, 2021
455dcc3
improved test name: changes to CDK::Metadata
comcalvi Oct 2, 2021
fc03fed
added TODO note in lambda tests, and stashing commits
comcalvi Oct 2, 2021
4f5a051
created setHotswappableResourceMock() functions in test-setup
comcalvi Oct 4, 2021
a9e9b85
found lost tests
comcalvi Oct 4, 2021
bd7580f
updated tests to use strings instead of objects
comcalvi Oct 4, 2021
dad4436
improved test setup api
comcalvi Oct 4, 2021
4bc9f2d
added new tests
comcalvi Oct 5, 2021
0476da9
moved test files to new directory and fixed linter issue
comcalvi Oct 5, 2021
e0ac6b6
addressed linter issues
comcalvi Oct 5, 2021
bffa630
refactored import order and removed unneeded detectorResults variable
comcalvi Oct 6, 2021
ff6e8c4
moved down hotswappableResource declaration
comcalvi Oct 6, 2021
28911b9
renamed isResourceChangeHotswappable() to isCanditateForHotswapping()
comcalvi Oct 6, 2021
c486af7
removed blank line
comcalvi Oct 6, 2021
28c830a
renamed HotswappableResourceChange to HotswappableChangeCandidate
comcalvi Oct 6, 2021
59a0dcd
renamed establishHotswappableResourceName to establishResourcePhysica…
comcalvi Oct 6, 2021
a6fc553
rename nameInCfnTemplate to physicalNameInTemplate
comcalvi Oct 6, 2021
b605897
updated comment
comcalvi Oct 6, 2021
f9796cc
removed redundant parens
comcalvi Oct 6, 2021
c660189
removed blank line
comcalvi Oct 6, 2021
b28ea47
removed blank line
comcalvi Oct 6, 2021
131e54d
change function delcaration style
comcalvi Oct 6, 2021
15807e9
removed blank line
comcalvi Oct 6, 2021
21983bb
removed uneeded checks from both lambda and SMs
comcalvi Oct 6, 2021
96a0b1f
removed unneeded await
comcalvi Oct 6, 2021
163374a
moved fake-cloudformation-stack.ts back to where it was
comcalvi Oct 6, 2021
6937afb
removed empty line and renamed the 'changes to (non)hotswappable reso…
comcalvi Oct 6, 2021
f4ab403
updated Fn::Join test to not be one line
comcalvi Oct 7, 2021
e7c2641
reverted unneeded change
comcalvi Oct 7, 2021
f5e8127
removed unneeded Fn::Joins
comcalvi Oct 7, 2021
81b9754
improved test with fn:getatt
comcalvi Oct 7, 2021
0ec7e67
reordered tests in lambda
comcalvi Oct 7, 2021
fde2c3a
renamed metadata thing
comcalvi Oct 7, 2021
48cbd48
simplified call to no param cdkStackArtifactOf()
comcalvi Oct 7, 2021
1ef57d7
renamed test and removed uneeded code in test
comcalvi Oct 7, 2021
a07d02c
reanmed pushStackMocks()
comcalvi Oct 7, 2021
120d08d
made assetParams optional
comcalvi Oct 7, 2021
72a43e2
renamed setTemplate()
comcalvi Oct 7, 2021
af776b5
renamed test to use property instead of parameter
comcalvi Oct 7, 2021
07969f3
test/api/hotswap/state-machine-hotswap-deployments.test.ts
comcalvi Oct 7, 2021
70efdfb
changed the definitionString property test name again
comcalvi Oct 7, 2021
243abc8
refactored methods in tests
comcalvi Oct 7, 2021
2cf8823
fixed linter issue
comcalvi Oct 7, 2021
aa68455
Merge branch 'master' into hotswap-step-functions
skinny85 Oct 7, 2021
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
1 change: 1 addition & 0 deletions packages/aws-cdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ Hotswapping is currently supported for the following changes
(additional changes will be supported in the future):

- Code asset changes of AWS Lambda functions.
- Definition changes of AWS Step Functions State Machines.

**⚠ Note #1**: This command deliberately introduces drift in CloudFormation stacks in order to speed up deployments.
For this reason, only use it for development purposes.
Expand Down
5 changes: 5 additions & 0 deletions packages/aws-cdk/lib/api/aws-auth/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface ISDK {
elbv2(): AWS.ELBv2;
secretsManager(): AWS.SecretsManager;
kms(): AWS.KMS;
stepFunctions(): AWS.StepFunctions;
}

/**
Expand Down Expand Up @@ -128,6 +129,10 @@ export class SDK implements ISDK {
return this.wrapServiceErrorHandling(new AWS.KMS(this.config));
}

public stepFunctions(): AWS.StepFunctions {
return this.wrapServiceErrorHandling(new AWS.StepFunctions(this.config));
}

public async currentAccount(): Promise<Account> {
// Get/refresh if necessary before we can access `accessKeyId`
await this.forceCredentialRetrieval();
Expand Down
87 changes: 72 additions & 15 deletions packages/aws-cdk/lib/api/hotswap-deployments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import * as cxapi from '@aws-cdk/cx-api';
import { CloudFormation } from 'aws-sdk';
import { ISDK, Mode, SdkProvider } from './aws-auth';
import { DeployStackResult } from './deploy-stack';
import { ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, ListStackResources } from './hotswap/common';
import { ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, ListStackResources, HotswappableChangeCandidate } from './hotswap/common';
import { EvaluateCloudFormationTemplate } from './hotswap/evaluate-cloudformation-template';
import { isHotswappableLambdaFunctionChange } from './hotswap/lambda-functions';
import { isHotswappableStateMachineChange } from './hotswap/stepfunctions-state-machines';
import { CloudFormationStack } from './util/cloudformation';

/**
Expand Down Expand Up @@ -57,24 +58,80 @@ export async function tryHotswapDeployment(
async function findAllHotswappableChanges(
stackChanges: cfn_diff.TemplateDiff, evaluateCfnTemplate: EvaluateCloudFormationTemplate,
): Promise<HotswapOperation[] | undefined> {
const promises = new Array<Promise<ChangeHotswapResult>>();
stackChanges.resources.forEachDifference(async (logicalId: string, change: cfn_diff.ResourceDifference) => {
promises.push(isHotswappableLambdaFunctionChange(logicalId, change, evaluateCfnTemplate));
let foundNonHotswappableChange = false;
const promises: Array<Array<Promise<ChangeHotswapResult>>> = [];

// gather the results of the detector functions
stackChanges.resources.forEachDifference((logicalId: string, change: cfn_diff.ResourceDifference) => {
const resourceHotswapEvaluation = isCandidateForHotswapping(change);

if (resourceHotswapEvaluation === ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT) {
foundNonHotswappableChange = true;
} else if (resourceHotswapEvaluation === ChangeHotswapImpact.IRRELEVANT) {
// empty 'if' just for flow-aware typing to kick in...
} else {
promises.push([
isHotswappableLambdaFunctionChange(logicalId, resourceHotswapEvaluation, evaluateCfnTemplate),
isHotswappableStateMachineChange(logicalId, resourceHotswapEvaluation, evaluateCfnTemplate),
]);
}
});
return Promise.all(promises).then(hotswapDetectionResults => {
const hotswappableResources = new Array<HotswapOperation>();
let foundNonHotswappableChange = false;
for (const lambdaFunctionShortCircuitChange of hotswapDetectionResults) {
if (lambdaFunctionShortCircuitChange === ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT) {

const changesDetectionResults: Array<Array<ChangeHotswapResult>> = [];
for (const detectorResultPromises of promises) {
const hotswapDetectionResults = await Promise.all(detectorResultPromises);
changesDetectionResults.push(hotswapDetectionResults);
}

const hotswappableResources = new Array<HotswapOperation>();

// resolve all detector results
for (const hotswapDetectionResults of changesDetectionResults) {
const perChangeHotswappableResources = new Array<HotswapOperation>();

for (const result of hotswapDetectionResults) {
if (typeof result !== 'string') {
perChangeHotswappableResources.push(result);
}
}

// if we found any hotswappable changes, return now
if (perChangeHotswappableResources.length > 0) {
hotswappableResources.push(...perChangeHotswappableResources);
continue;
}

// no hotswappable changes found, so any REQUIRES_FULL_DEPLOYMENTs imply a non-hotswappable change
for (const result of hotswapDetectionResults) {
if (result === ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT) {
foundNonHotswappableChange = true;
} else if (lambdaFunctionShortCircuitChange === ChangeHotswapImpact.IRRELEVANT) {
// empty 'if' just for flow-aware typing to kick in...
} else {
hotswappableResources.push(lambdaFunctionShortCircuitChange);
}
}
return foundNonHotswappableChange ? undefined : hotswappableResources;
});
// no REQUIRES_FULL_DEPLOYMENT implies that all results are IRRELEVANT
}

return foundNonHotswappableChange ? undefined : hotswappableResources;
}

/**
* returns `ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT` if a resource was deleted, or a change that we cannot short-circuit occured.
* Returns `ChangeHotswapImpact.IRRELEVANT` if a change that does not impact shortcircuiting occured, such as a metadata change.
*/
export function isCandidateForHotswapping(change: cfn_diff.ResourceDifference): HotswappableChangeCandidate | ChangeHotswapImpact {
// a resource has been removed OR a resource has been added; we can't short-circuit that change
if (!change.newValue || !change.oldValue) {
return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT;
}

// Ignore Metadata changes
if (change.newValue.Type === 'AWS::CDK::Metadata') {
return ChangeHotswapImpact.IRRELEVANT;
}

return {
newValue: change.newValue,
propertyUpdates: change.propertyUpdates,
};
}
comcalvi marked this conversation as resolved.
Show resolved Hide resolved

async function applyAllHotswappableChanges(
Expand Down
40 changes: 39 additions & 1 deletion packages/aws-cdk/lib/api/hotswap/common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as cfn_diff from '@aws-cdk/cloudformation-diff';
import { CloudFormation } from 'aws-sdk';
import { ISDK } from '../aws-auth';
import { CfnEvaluationException, EvaluateCloudFormationTemplate } from './evaluate-cloudformation-template';

export interface ListStackResources {
listStackResources(): Promise<CloudFormation.StackResourceSummary[]>;
Expand Down Expand Up @@ -33,6 +34,43 @@ export enum ChangeHotswapImpact {

export type ChangeHotswapResult = HotswapOperation | ChangeHotswapImpact;

export function assetMetadataChanged(change: cfn_diff.ResourceDifference): boolean {
/**
* Represents a change that can be hotswapped.
*/
export class HotswappableChangeCandidate {
/**
* The value the resource is being updated to.
*/
public readonly newValue: cfn_diff.Resource;

/**
* The changes made to the resource properties.
*/
public readonly propertyUpdates: { [key: string]: cfn_diff.PropertyDifference<any> };

public constructor(newValue: cfn_diff.Resource, propertyUpdates: { [key: string]: cfn_diff.PropertyDifference<any> }) {
this.newValue = newValue;
this.propertyUpdates = propertyUpdates;
}
}

export async function establishResourcePhysicalName(
logicalId: string, physicalNameInCfnTemplate: any, evaluateCfnTemplate: EvaluateCloudFormationTemplate,
): Promise<string | undefined> {
if (physicalNameInCfnTemplate != null) {
try {
return await evaluateCfnTemplate.evaluateCfnExpression(physicalNameInCfnTemplate);
} catch (e) {
// If we can't evaluate the resource's name CloudFormation expression,
// just look it up in the currently deployed Stack
if (!(e instanceof CfnEvaluationException)) {
throw e;
}
}
}
return evaluateCfnTemplate.findPhysicalNameFor(logicalId);
}

export function assetMetadataChanged(change: HotswappableChangeCandidate): boolean {
return !!change.newValue?.Metadata['aws:asset:path'];
}
49 changes: 7 additions & 42 deletions packages/aws-cdk/lib/api/hotswap/lambda-functions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as cfn_diff from '@aws-cdk/cloudformation-diff';
import { ISDK } from '../aws-auth';
import { assetMetadataChanged, ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation } from './common';
import { CfnEvaluationException, EvaluateCloudFormationTemplate } from './evaluate-cloudformation-template';
import { assetMetadataChanged, ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, HotswappableChangeCandidate, establishResourcePhysicalName } from './common';
import { EvaluateCloudFormationTemplate } from './evaluate-cloudformation-template';

/**
* Returns `false` if the change cannot be short-circuited,
Expand All @@ -10,7 +9,7 @@ import { CfnEvaluationException, EvaluateCloudFormationTemplate } from './evalua
* or a LambdaFunctionResource if the change can be short-circuited.
*/
export async function isHotswappableLambdaFunctionChange(
logicalId: string, change: cfn_diff.ResourceDifference, evaluateCfnTemplate: EvaluateCloudFormationTemplate,
logicalId: string, change: HotswappableChangeCandidate, evaluateCfnTemplate: EvaluateCloudFormationTemplate,
): Promise<ChangeHotswapResult> {
const lambdaCodeChange = await isLambdaFunctionCodeOnlyChange(change, evaluateCfnTemplate);
if (typeof lambdaCodeChange === 'string') {
Expand All @@ -24,7 +23,7 @@ export async function isHotswappableLambdaFunctionChange(
return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT;
}

const functionName = await establishFunctionPhysicalName(logicalId, change, evaluateCfnTemplate);
const functionName = await establishResourcePhysicalName(logicalId, change.newValue.Properties?.FunctionName, evaluateCfnTemplate);
if (!functionName) {
return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT;
}
Expand All @@ -37,34 +36,21 @@ export async function isHotswappableLambdaFunctionChange(
}

/**
* Returns `true` if the change is not for a AWS::Lambda::Function,
* Returns `ChangeHotswapImpact.IRRELEVANT` if the change is not for a AWS::Lambda::Function,
* but doesn't prevent short-circuiting
* (like a change to CDKMetadata resource),
* `false` if the change is to a AWS::Lambda::Function,
* `ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT` if the change is to a AWS::Lambda::Function,
* but not only to its Code property,
* or a LambdaFunctionCode if the change is to a AWS::Lambda::Function,
* and only affects its Code property.
*/
async function isLambdaFunctionCodeOnlyChange(
change: cfn_diff.ResourceDifference, evaluateCfnTemplate: EvaluateCloudFormationTemplate,
change: HotswappableChangeCandidate, evaluateCfnTemplate: EvaluateCloudFormationTemplate,
): Promise<LambdaFunctionCode | ChangeHotswapImpact> {
if (!change.newValue) {
return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT;
}
const newResourceType = change.newValue.Type;
// Ignore Metadata changes
if (newResourceType === 'AWS::CDK::Metadata') {
return ChangeHotswapImpact.IRRELEVANT;
}
// The only other resource change we should see is a Lambda function
if (newResourceType !== 'AWS::Lambda::Function') {
return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT;
}
if (change.oldValue?.Type == null) {
// this means this is a brand-new Lambda function -
// obviously, we can't short-circuit that!
return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT;
}
comcalvi marked this conversation as resolved.
Show resolved Hide resolved

/*
* On first glance, we would want to initialize these using the "previous" values (change.oldValue),
Expand All @@ -83,9 +69,6 @@ async function isLambdaFunctionCodeOnlyChange(
const propertyUpdates = change.propertyUpdates;
for (const updatedPropName in propertyUpdates) {
const updatedProp = propertyUpdates[updatedPropName];
if (updatedProp.newValue === undefined) {
return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT;
}
for (const newPropName in updatedProp.newValue) {
switch (newPropName) {
case 'S3Bucket':
Expand Down Expand Up @@ -132,21 +115,3 @@ class LambdaFunctionHotswapOperation implements HotswapOperation {
}).promise();
}
}

async function establishFunctionPhysicalName(
logicalId: string, change: cfn_diff.ResourceDifference, evaluateCfnTemplate: EvaluateCloudFormationTemplate,
): Promise<string | undefined> {
const functionNameInCfnTemplate = change.newValue?.Properties?.FunctionName;
if (functionNameInCfnTemplate != null) {
try {
return await evaluateCfnTemplate.evaluateCfnExpression(functionNameInCfnTemplate);
} catch (e) {
// If we can't evaluate the function's name CloudFormation expression,
// just look it up in the currently deployed Stack
if (!(e instanceof CfnEvaluationException)) {
throw e;
}
}
}
return evaluateCfnTemplate.findPhysicalNameFor(logicalId);
}
62 changes: 62 additions & 0 deletions packages/aws-cdk/lib/api/hotswap/stepfunctions-state-machines.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ISDK } from '../aws-auth';
import { ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, HotswappableChangeCandidate, establishResourcePhysicalName } from './common';
import { EvaluateCloudFormationTemplate } from './evaluate-cloudformation-template';

export async function isHotswappableStateMachineChange(
logicalId: string, change: HotswappableChangeCandidate, evaluateCfnTemplate: EvaluateCloudFormationTemplate,
): Promise<ChangeHotswapResult> {
const stateMachineDefinitionChange = await isStateMachineDefinitionOnlyChange(change, evaluateCfnTemplate);
if (stateMachineDefinitionChange === ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT ||
stateMachineDefinitionChange === ChangeHotswapImpact.IRRELEVANT) {
return stateMachineDefinitionChange;
}

const machineNameInCfnTemplate = change.newValue?.Properties?.StateMachineName;
const machineName = await establishResourcePhysicalName(logicalId, machineNameInCfnTemplate, evaluateCfnTemplate);
if (!machineName) {
return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT;
}

return new StateMachineHotswapOperation({
definition: stateMachineDefinitionChange,
stateMachineName: machineName,
});
}

async function isStateMachineDefinitionOnlyChange(
change: HotswappableChangeCandidate, evaluateCfnTemplate: EvaluateCloudFormationTemplate,
): Promise<string | ChangeHotswapImpact> {
const newResourceType = change.newValue.Type;
if (newResourceType !== 'AWS::StepFunctions::StateMachine') {
return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT;
}

const propertyUpdates = change.propertyUpdates;
for (const updatedPropName in propertyUpdates) {
// ensure that only changes to the definition string result in a hotswap
if (updatedPropName !== 'DefinitionString') {
return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT;
}
}

return evaluateCfnTemplate.evaluateCfnExpression(propertyUpdates.DefinitionString.newValue);
}

interface StateMachineResource {
readonly stateMachineName: string;
readonly definition: string;
}

class StateMachineHotswapOperation implements HotswapOperation {
constructor(private readonly stepFunctionResource: StateMachineResource) {
}

public async apply(sdk: ISDK): Promise<any> {
// not passing the optional properties leaves them unchanged
return sdk.stepFunctions().updateStateMachine({
// even though the name of the property is stateMachineArn, passing the name of the state machine is allowed here
stateMachineArn: this.stepFunctionResource.stateMachineName,
definition: this.stepFunctionResource.definition,
comcalvi marked this conversation as resolved.
Show resolved Hide resolved
}).promise();
}
}
Loading