-
Notifications
You must be signed in to change notification settings - Fork 3.7k
/
cloudformation-deployments.ts
195 lines (167 loc) · 5.46 KB
/
cloudformation-deployments.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import { CloudFormationStackArtifact } from '@aws-cdk/cx-api';
import { Tag } from '../cdk-toolkit';
import { debug } from '../logging';
import { Mode, SdkProvider } from './aws-auth';
import { deployStack, DeployStackResult, destroyStack } from './deploy-stack';
import { ToolkitInfo } from './toolkit-info';
import { CloudFormationStack, Template } from './util/cloudformation';
export interface DeployStackOptions {
/**
* Stack to deploy
*/
stack: CloudFormationStackArtifact;
/**
* Execution role for the deployment (pass through to CloudFormation)
*
* @default - Current role
*/
roleArn?: string;
/**
* Topic ARNs to send a message when deployment finishes (pass through to CloudFormation)
*
* @default - No notifications
*/
notificationArns?: string[];
/**
* Override name under which stack will be deployed
*
* @default - Use artifact default
*/
deployName?: string;
/**
* Don't show stack deployment events, just wait
*
* @default false
*/
quiet?: boolean;
/**
* Name of the toolkit stack, if not the default name
*
* @default 'CDKToolkit'
*/
toolkitStackName?: string;
/**
* List of asset IDs which should NOT be built or uploaded
*
* @default - Build all assets
*/
reuseAssets?: string[];
/**
* Stack tags (pass through to CloudFormation)
*/
tags?: Tag[];
/**
* Stage the change set but don't execute it
*
* @default - false
*/
execute?: boolean;
/**
* 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
*/
force?: boolean;
/**
* Extra parameters for CloudFormation
* @default - no additional parameters will be passed to the template
*/
parameters?: { [name: string]: string | undefined };
/**
* Use previous values for unspecified parameters
*
* If not set, all parameters must be specified for every deployment.
*
* @default true
*/
usePreviousParameters?: boolean;
}
export interface DestroyStackOptions {
stack: CloudFormationStackArtifact;
deployName?: string;
roleArn?: string;
quiet?: boolean;
force?: boolean;
}
export interface StackExistsOptions {
stack: CloudFormationStackArtifact;
deployName?: string;
}
export interface ProvisionerProps {
sdkProvider: SdkProvider;
}
/**
* Helper class for CloudFormation deployments
*
* Looks us the right SDK and Bootstrap stack to deploy a given
* stack artifact.
*/
export class CloudFormationDeployments {
private readonly sdkProvider: SdkProvider;
constructor(props: ProvisionerProps) {
this.sdkProvider = props.sdkProvider;
}
public async readCurrentTemplate(stackArtifact: CloudFormationStackArtifact): Promise<Template> {
debug(`Reading existing template for stack ${stackArtifact.displayName}.`);
const { stackSdk } = await this.prepareSdkFor(stackArtifact, undefined, Mode.ForReading);
const cfn = stackSdk.cloudFormation();
const stack = await CloudFormationStack.lookup(cfn, stackArtifact.stackName);
return stack.template();
}
public async deployStack(options: DeployStackOptions): Promise<DeployStackResult> {
const { stackSdk, resolvedEnvironment, cloudFormationRoleArn } = await this.prepareSdkFor(options.stack, options.roleArn);
const toolkitInfo = await ToolkitInfo.lookup(resolvedEnvironment, stackSdk, options.toolkitStackName);
return deployStack({
stack: options.stack,
resolvedEnvironment,
deployName: options.deployName,
notificationArns: options.notificationArns,
quiet: options.quiet,
sdk: stackSdk,
sdkProvider: this.sdkProvider,
roleArn: cloudFormationRoleArn,
reuseAssets: options.reuseAssets,
toolkitInfo,
tags: options.tags,
execute: options.execute,
force: options.force,
parameters: options.parameters,
usePreviousParameters: options.usePreviousParameters,
});
}
public async destroyStack(options: DestroyStackOptions): Promise<void> {
const { stackSdk, cloudFormationRoleArn: roleArn } = await this.prepareSdkFor(options.stack, options.roleArn);
return destroyStack({
sdk: stackSdk,
roleArn,
stack: options.stack,
deployName: options.deployName,
quiet: options.quiet,
});
}
public async stackExists(options: StackExistsOptions): Promise<boolean> {
const { stackSdk } = await this.prepareSdkFor(options.stack, undefined, Mode.ForReading);
const stack = await CloudFormationStack.lookup(stackSdk.cloudFormation(), options.deployName ?? options.stack.stackName);
return stack.exists;
}
/**
* Get the environment necessary for touching the given stack
*
* Returns the following:
*
* - The resolved environment for the stack (no more 'unknown-account/unknown-region')
* - SDK loaded with the right credentials for calling `CreateChangeSet`.
* - The Execution Role that should be passed to CloudFormation.
*/
private async prepareSdkFor(stack: CloudFormationStackArtifact, roleArn?: string, mode = Mode.ForWriting) {
if (!stack.environment) {
throw new Error(`The stack ${stack.displayName} does not have an environment`);
}
const resolvedEnvironment = await this.sdkProvider.resolveEnvironment(stack.environment);
const stackSdk = await this.sdkProvider.forEnvironment(stack.environment, mode);
return {
stackSdk,
resolvedEnvironment,
cloudFormationRoleArn: roleArn,
};
}
}