-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into njlynch/rename_subnet_types
- Loading branch information
Showing
19 changed files
with
788 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import * as cfn_diff from '@aws-cdk/cloudformation-diff'; | ||
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, HotswapOperation, ListStackResources } from './hotswap/common'; | ||
import { isHotswappableLambdaFunctionChange } from './hotswap/lambda-functions'; | ||
import { CloudFormationStack } from './util/cloudformation'; | ||
|
||
/** | ||
* Perform a hotswap deployment, | ||
* short-circuiting CloudFormation if possible. | ||
* If it's not possible to short-circuit the deployment | ||
* (because the CDK Stack contains changes that cannot be deployed without CloudFormation), | ||
* returns `undefined`. | ||
*/ | ||
export async function tryHotswapDeployment( | ||
sdkProvider: SdkProvider, assetParams: { [key: string]: string }, | ||
cloudFormationStack: CloudFormationStack, stackArtifact: cxapi.CloudFormationStackArtifact, | ||
): Promise<DeployStackResult | undefined> { | ||
const currentTemplate = await cloudFormationStack.template(); | ||
const stackChanges = cfn_diff.diffTemplate(currentTemplate, stackArtifact.template); | ||
|
||
// resolve the environment, so we can substitute things like AWS::Region in CFN expressions | ||
const resolvedEnv = await sdkProvider.resolveEnvironment(stackArtifact.environment); | ||
const hotswappableChanges = findAllHotswappableChanges(stackChanges, { | ||
...assetParams, | ||
'AWS::Region': resolvedEnv.region, | ||
'AWS::AccountId': resolvedEnv.account, | ||
}); | ||
if (!hotswappableChanges) { | ||
// this means there were changes to the template that cannot be short-circuited | ||
return undefined; | ||
} | ||
|
||
// create a new SDK using the CLI credentials, because the default one will not work for new-style synthesis - | ||
// it assumes the bootstrap deploy Role, which doesn't have permissions to update Lambda functions | ||
const sdk = await sdkProvider.forEnvironment(resolvedEnv, Mode.ForWriting); | ||
// apply the short-circuitable changes | ||
await applyAllHotswappableChanges(sdk, stackArtifact, hotswappableChanges); | ||
|
||
return { noOp: hotswappableChanges.length === 0, stackArn: cloudFormationStack.stackId, outputs: cloudFormationStack.outputs, stackArtifact }; | ||
} | ||
|
||
function findAllHotswappableChanges( | ||
stackChanges: cfn_diff.TemplateDiff, assetParamsWithEnv: { [key: string]: string }, | ||
): HotswapOperation[] | undefined { | ||
const hotswappableResources = new Array<HotswapOperation>(); | ||
let foundNonHotswappableChange = false; | ||
stackChanges.resources.forEachDifference((logicalId: string, change: cfn_diff.ResourceDifference) => { | ||
const lambdaFunctionShortCircuitChange = isHotswappableLambdaFunctionChange(logicalId, change, assetParamsWithEnv); | ||
if (lambdaFunctionShortCircuitChange === 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; | ||
} | ||
|
||
async function applyAllHotswappableChanges( | ||
sdk: ISDK, stackArtifact: cxapi.CloudFormationStackArtifact, hotswappableChanges: HotswapOperation[], | ||
): Promise<void[]> { | ||
// The current resources of the Stack. | ||
// We need them to figure out the physical name of a function in case it wasn't specified by the user. | ||
// We fetch it lazily, to save a service call, in case all updated Lambdas have their names set. | ||
const listStackResources = new LazyListStackResources(sdk, stackArtifact.stackName); | ||
|
||
return Promise.all(hotswappableChanges.map(hotswapOperation => hotswapOperation.apply(sdk, listStackResources))); | ||
} | ||
|
||
class LazyListStackResources implements ListStackResources { | ||
private stackResources: CloudFormation.StackResourceSummary[] | undefined; | ||
|
||
constructor(private readonly sdk: ISDK, private readonly stackName: string) { | ||
} | ||
|
||
async listStackResources(): Promise<CloudFormation.StackResourceSummary[]> { | ||
if (this.stackResources === undefined) { | ||
this.stackResources = await this.getStackResource(); | ||
} | ||
return this.stackResources; | ||
} | ||
|
||
private async getStackResource(): Promise<CloudFormation.StackResourceSummary[]> { | ||
const ret = new Array<CloudFormation.StackResourceSummary>(); | ||
let nextToken: string | undefined; | ||
do { | ||
const stackResourcesResponse = await this.sdk.cloudFormation().listStackResources({ | ||
StackName: this.stackName, | ||
NextToken: nextToken, | ||
}).promise(); | ||
ret.push(...(stackResourcesResponse.StackResourceSummaries ?? [])); | ||
nextToken = stackResourcesResponse.NextToken; | ||
} while (nextToken); | ||
return ret; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import * as cfn_diff from '@aws-cdk/cloudformation-diff'; | ||
import { CloudFormation } from 'aws-sdk'; | ||
import { ISDK } from '../aws-auth'; | ||
import { evaluateCfn } from '../util/cloudformation/evaluate-cfn'; | ||
|
||
export interface ListStackResources { | ||
listStackResources(): Promise<CloudFormation.StackResourceSummary[]>; | ||
} | ||
|
||
/** | ||
* An interface that represents a change that can be deployed in a short-circuit manner. | ||
*/ | ||
export interface HotswapOperation { | ||
apply(sdk: ISDK, stackResources: ListStackResources): Promise<any>; | ||
} | ||
|
||
/** | ||
* An enum that represents the result of detection whether a given change can be hotswapped. | ||
*/ | ||
export enum ChangeHotswapImpact { | ||
/** | ||
* This result means that the given change cannot be hotswapped, | ||
* and requires a full deployment. | ||
*/ | ||
REQUIRES_FULL_DEPLOYMENT = 'requires-full-deployment', | ||
|
||
/** | ||
* This result means that the given change can be safely be ignored when determining | ||
* whether the given Stack can be hotswapped or not | ||
* (for example, it's a change to the CDKMetadata resource). | ||
*/ | ||
IRRELEVANT = 'irrelevant', | ||
} | ||
|
||
export type ChangeHotswapResult = HotswapOperation | ChangeHotswapImpact; | ||
|
||
/** | ||
* For old-style synthesis which uses CFN Parameters, | ||
* the Code properties can have the values of complex CFN expressions. | ||
* For new-style synthesis of env-agnostic stacks, | ||
* the Fn::Sub expression is used for the Asset bucket. | ||
* Evaluate the CFN expressions to concrete string values which we need for the | ||
* updateFunctionCode() service call. | ||
*/ | ||
export function stringifyPotentialCfnExpression(value: any, assetParamsWithEnv: { [key: string]: string }): string { | ||
// if we already have a string, nothing to do | ||
if (value == null || typeof value === 'string') { | ||
return value; | ||
} | ||
|
||
// otherwise, we assume this is a CloudFormation expression that we need to evaluate | ||
return evaluateCfn(value, assetParamsWithEnv); | ||
} | ||
|
||
export function assetMetadataChanged(change: cfn_diff.ResourceDifference): boolean { | ||
return !!change.newValue?.Metadata['aws:asset:path']; | ||
} |
Oops, something went wrong.