generated from amazon-archives/__template_Apache-2.0
-
Notifications
You must be signed in to change notification settings - Fork 82
Expand file tree
/
Copy pathcommon.ts
More file actions
243 lines (214 loc) · 7.9 KB
/
common.ts
File metadata and controls
243 lines (214 loc) · 7.9 KB
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
import type { PropertyDifference } from '@aws-cdk/cloudformation-diff';
import type { HotswappableChange, ResourceChange } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/hotswap';
import { ToolkitError } from '../../toolkit/error';
import type { SDK } from '../aws-auth';
export const ICON = '✨';
export interface HotswapOperation {
/**
* Marks the operation as hotswappable
*/
readonly hotswappable: true;
/**
* The name of the service being hotswapped.
* Used to set a custom User-Agent for SDK calls.
*/
readonly service: string;
/**
* Description of the change that is applied as part of the operation
*/
readonly change: HotswappableChange;
/**
* The names of the resources being hotswapped.
*/
readonly resourceNames: string[];
/**
* Applies the hotswap operation
*/
readonly apply: (sdk: SDK) => Promise<void>;
}
export interface NonHotswappableChange {
readonly hotswappable: false;
readonly resourceType: string;
readonly rejectedChanges: Array<string>;
readonly logicalId: string;
/**
* Tells the user exactly why this change was deemed non-hotswappable and what its logical ID is.
* If not specified, `reason` will be autofilled to state that the properties listed in `rejectedChanges` are not hotswappable.
*/
readonly reason?: string;
/**
* Whether or not to show this change when listing non-hotswappable changes in HOTSWAP_ONLY mode. Does not affect
* listing in FALL_BACK mode.
*
* @default true
*/
readonly hotswapOnlyVisible?: boolean;
}
export type ChangeHotswapResult = Array<HotswapOperation | NonHotswappableChange>;
export interface ClassifiedResourceChanges {
hotswappableChanges: HotswapOperation[];
nonHotswappableChanges: NonHotswappableChange[];
}
export enum HotswapMode {
/**
* Will fall back to CloudFormation when a non-hotswappable change is detected
*/
FALL_BACK = 'fall-back',
/**
* Will not fall back to CloudFormation when a non-hotswappable change is detected
*/
HOTSWAP_ONLY = 'hotswap-only',
/**
* Will not attempt to hotswap anything and instead go straight to CloudFormation
*/
FULL_DEPLOYMENT = 'full-deployment',
}
type Exclude = { [key: string]: Exclude | true };
/**
* Represents configuration property overrides for hotswap deployments
*/
export class HotswapPropertyOverrides {
// Each supported resource type will have its own properties. Currently this is ECS
ecsHotswapProperties?: EcsHotswapProperties;
public constructor (ecsHotswapProperties?: EcsHotswapProperties) {
this.ecsHotswapProperties = ecsHotswapProperties;
}
}
/**
* Represents configuration properties for ECS hotswap deployments
*/
export class EcsHotswapProperties {
// The lower limit on the number of your service's tasks that must remain in the RUNNING state during a deployment, as a percentage of the desiredCount
readonly minimumHealthyPercent?: number;
// The upper limit on the number of your service's tasks that are allowed in the RUNNING or PENDING state during a deployment, as a percentage of the desiredCount
readonly maximumHealthyPercent?: number;
public constructor (minimumHealthyPercent?: number, maximumHealthyPercent?: number) {
if (minimumHealthyPercent !== undefined && minimumHealthyPercent < 0 ) {
throw new ToolkitError('hotswap-ecs-minimum-healthy-percent can\'t be a negative number');
}
if (maximumHealthyPercent !== undefined && maximumHealthyPercent < 0 ) {
throw new ToolkitError('hotswap-ecs-maximum-healthy-percent can\'t be a negative number');
}
// In order to preserve the current behaviour, when minimumHealthyPercent is not defined, it will be set to the currently default value of 0
if (minimumHealthyPercent == undefined) {
this.minimumHealthyPercent = 0;
} else {
this.minimumHealthyPercent = minimumHealthyPercent;
}
this.maximumHealthyPercent = maximumHealthyPercent;
}
/**
* Check if any hotswap properties are defined
* @returns true if all properties are undefined, false otherwise
*/
public isEmpty(): boolean {
return this.minimumHealthyPercent === 0 && this.maximumHealthyPercent === undefined;
}
}
/**
* This function transforms all keys (recursively) in the provided `val` object.
*
* @param val The object whose keys need to be transformed.
* @param transform The function that will be applied to each key.
* @param exclude The keys that will not be transformed and copied to output directly
* @returns A new object with the same values as `val`, but with all keys transformed according to `transform`.
*/
export function transformObjectKeys(val: any, transform: (str: string) => string, exclude: Exclude = {}): any {
if (val == null || typeof val !== 'object') {
return val;
}
if (Array.isArray(val)) {
// For arrays we just pass parent's exclude object directly
// since it makes no sense to specify different exclude options for each array element
return val.map((input: any) => transformObjectKeys(input, transform, exclude));
}
const ret: { [k: string]: any } = {};
for (const [k, v] of Object.entries(val)) {
const childExclude = exclude[k];
if (childExclude === true) {
// we don't transform this object if the key is specified in exclude
ret[transform(k)] = v;
} else {
ret[transform(k)] = transformObjectKeys(v, transform, childExclude);
}
}
return ret;
}
/**
* This function lower cases the first character of the string provided.
*/
export function lowerCaseFirstCharacter(str: string): string {
return str.length > 0 ? `${str[0].toLowerCase()}${str.slice(1)}` : str;
}
type PropDiffs = Record<string, PropertyDifference<any>>;
export class ClassifiedChanges {
public constructor(
public readonly change: ResourceChange,
public readonly hotswappableProps: PropDiffs,
public readonly nonHotswappableProps: PropDiffs,
) {
}
public reportNonHotswappablePropertyChanges(ret: ChangeHotswapResult): void {
const nonHotswappablePropNames = Object.keys(this.nonHotswappableProps);
if (nonHotswappablePropNames.length > 0) {
const tagOnlyChange = nonHotswappablePropNames.length === 1 && nonHotswappablePropNames[0] === 'Tags';
reportNonHotswappableChange(
ret,
this.change,
this.nonHotswappableProps,
tagOnlyChange
? 'Tags are not hotswappable'
: `resource properties '${nonHotswappablePropNames}' are not hotswappable on this resource type`,
);
}
}
public get namesOfHotswappableProps(): string[] {
return Object.keys(this.hotswappableProps);
}
}
export function classifyChanges(xs: ResourceChange, hotswappablePropNames: string[]): ClassifiedChanges {
const hotswappableProps: PropDiffs = {};
const nonHotswappableProps: PropDiffs = {};
for (const [name, propDiff] of Object.entries(xs.propertyUpdates)) {
if (hotswappablePropNames.includes(name)) {
hotswappableProps[name] = propDiff;
} else {
nonHotswappableProps[name] = propDiff;
}
}
return new ClassifiedChanges(xs, hotswappableProps, nonHotswappableProps);
}
export function reportNonHotswappableChange(
ret: ChangeHotswapResult,
change: ResourceChange,
nonHotswappableProps?: PropDiffs,
reason?: string,
hotswapOnlyVisible?: boolean,
): void {
let hotswapOnlyVisibility = true;
if (hotswapOnlyVisible === false) {
hotswapOnlyVisibility = false;
}
ret.push({
hotswappable: false,
rejectedChanges: Object.keys(nonHotswappableProps ?? change.propertyUpdates),
logicalId: change.logicalId,
resourceType: change.newValue.Type,
reason,
hotswapOnlyVisible: hotswapOnlyVisibility,
});
}
export function reportNonHotswappableResource(
change: ResourceChange,
reason?: string,
): ChangeHotswapResult {
return [
{
hotswappable: false,
rejectedChanges: Object.keys(change.propertyUpdates),
logicalId: change.logicalId,
resourceType: change.newValue.Type,
reason,
},
];
}