@cdkd/cli 0.0.0-stage → 0.323.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +179 -2
- package/dist/asg-provider-4rNFSmz9.js +1277 -0
- package/dist/asg-provider-4rNFSmz9.js.map +1 -0
- package/dist/aws-client-defaults-BLXFsfMa.js +275 -0
- package/dist/aws-client-defaults-BLXFsfMa.js.map +1 -0
- package/dist/aws-clients-DWpHZk2l.js +440 -0
- package/dist/aws-clients-DWpHZk2l.js.map +1 -0
- package/dist/aws-failure-text-DiHodi76.js +1158 -0
- package/dist/aws-failure-text-DiHodi76.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +135 -0
- package/dist/cli.js.map +1 -0
- package/dist/created-resource-sink-CmrPwGge.js +2343 -0
- package/dist/created-resource-sink-CmrPwGge.js.map +1 -0
- package/dist/db-instance-modify-retry-DCLA_p9I.js +43 -0
- package/dist/db-instance-modify-retry-DCLA_p9I.js.map +1 -0
- package/dist/deploy-engine-dYZ0v6Kz.js +55863 -0
- package/dist/deploy-engine-dYZ0v6Kz.js.map +1 -0
- package/dist/iam-role-provider-Bp4osNrj.js +698 -0
- package/dist/iam-role-provider-Bp4osNrj.js.map +1 -0
- package/dist/index.d.ts +1629 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/neptune-provider-BqtKFZaE.js +1470 -0
- package/dist/neptune-provider-BqtKFZaE.js.map +1 -0
- package/dist/program-BYT8z2ca.js +31547 -0
- package/dist/program-BYT8z2ca.js.map +1 -0
- package/dist/provider-classes-KPYbVTtY.js +55302 -0
- package/dist/provider-classes-KPYbVTtY.js.map +1 -0
- package/dist/rds-provider-C8enyD9d.js +1749 -0
- package/dist/rds-provider-C8enyD9d.js.map +1 -0
- package/dist/version-kAoaNHQI.js +11 -0
- package/dist/version-kAoaNHQI.js.map +1 -0
- package/package.json +145 -4
|
@@ -0,0 +1,1277 @@
|
|
|
1
|
+
import { $ as safeMsg, E as generatedNameFor, n as describeAwsFailure, s as getLogger } from "./aws-failure-text-DiHodi76.js";
|
|
2
|
+
import { B as normalizeAwsTagsToCfn, F as assertRegionMatch, Fn as ResourceUpdateNotSupportedError, Ft as maskerOrIdentity, G as PROTECTION_FLIP_REUSE_WINDOW_MS, Gt as markAuxiliaryFailure, It as withDerivedNameMasks, K as ProtectionFlipRegistry, Kt as markCreatedBeforeFailure, M as RESOURCE_NOT_FOUND, Mr as markRedactedCause, Nn as ProvisioningError, Nr as wrapMaskedAwsError, P as markNotGone, Ut as isAuxiliaryFailure, Y as deleteWithProtectionCompensation, at as settleProtectionFlip, b as withRemovalDefaults, dt as pasteableAwsCommand, fn as ambientClientDefaults, ft as protectedReplacementAdvice, g as clearOnUpdateRemoval, hr as relayedNameCollision, i as reportCreatedResource, j as withoutServerErrorRetries, jr as markNonRetryable, jt as createMaskedLogSinks, lt as WITHHELD_AWS_COMMAND, nt as protectionFlipKey, o as retractCreatedResource, tt as observeThenDisableProtection, u as holdsSecretDerivedEntry } from "./created-resource-sink-CmrPwGge.js";
|
|
3
|
+
import { c as ambientRegion } from "./aws-client-defaults-BLXFsfMa.js";
|
|
4
|
+
import { Aa as reEnableInstanceApiTermination, Ea as ec2InstanceProtectionSite, Oa as observeThenDisableInstanceApiTermination, Ta as disableInstanceApiTermination, _a as unchangedBehindSecretReference } from "./deploy-engine-dYZ0v6Kz.js";
|
|
5
|
+
import { DescribeInstancesCommand, EC2Client } from "@aws-sdk/client-ec2";
|
|
6
|
+
import { AttachLoadBalancerTargetGroupsCommand, AttachLoadBalancersCommand, AttachTrafficSourcesCommand, AutoScalingClient, CreateAutoScalingGroupCommand, CreateOrUpdateTagsCommand, DeleteAutoScalingGroupCommand, DeleteLifecycleHookCommand, DeleteNotificationConfigurationCommand, DeleteTagsCommand as DeleteTagsCommand$1, DescribeAutoScalingGroupsCommand, DescribeLifecycleHooksCommand, DescribeNotificationConfigurationsCommand, DescribeTrafficSourcesCommand, DetachLoadBalancerTargetGroupsCommand, DetachLoadBalancersCommand, DetachTrafficSourcesCommand, DisableMetricsCollectionCommand, EnableMetricsCollectionCommand, PutLifecycleHookCommand, PutNotificationConfigurationCommand, UpdateAutoScalingGroupCommand } from "@aws-sdk/client-auto-scaling";
|
|
7
|
+
|
|
8
|
+
// src/provisioning/providers/asg-provider.ts
|
|
9
|
+
const ATTACHMENT_MAX_LENGTH = {
|
|
10
|
+
LoadBalancerNames: 255,
|
|
11
|
+
TargetGroupARNs: 511
|
|
12
|
+
};
|
|
13
|
+
const ATTACHMENT_WHAT = {
|
|
14
|
+
LoadBalancerNames: "load balancer names",
|
|
15
|
+
TargetGroupARNs: "target group ARNs"
|
|
16
|
+
};
|
|
17
|
+
const ENTRY_SHAPE = {
|
|
18
|
+
Tags: {
|
|
19
|
+
identity: "Key",
|
|
20
|
+
what: "tags with a Key"
|
|
21
|
+
},
|
|
22
|
+
MetricsCollection: {
|
|
23
|
+
identity: "Granularity",
|
|
24
|
+
stringList: "Metrics",
|
|
25
|
+
what: "entries with a Granularity"
|
|
26
|
+
},
|
|
27
|
+
LifecycleHookSpecificationList: {
|
|
28
|
+
identity: "LifecycleHookName",
|
|
29
|
+
what: "entries with a LifecycleHookName"
|
|
30
|
+
},
|
|
31
|
+
TrafficSources: {
|
|
32
|
+
identity: "Identifier",
|
|
33
|
+
what: "entries with an Identifier"
|
|
34
|
+
},
|
|
35
|
+
NotificationConfigurations: {
|
|
36
|
+
identity: "TopicARN",
|
|
37
|
+
stringList: "NotificationTypes",
|
|
38
|
+
what: "entries with a TopicARN"
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const LIST_KINDS = [
|
|
42
|
+
"Tags",
|
|
43
|
+
"LoadBalancerNames",
|
|
44
|
+
"TargetGroupARNs",
|
|
45
|
+
"MetricsCollection",
|
|
46
|
+
"LifecycleHookSpecificationList",
|
|
47
|
+
"TrafficSources",
|
|
48
|
+
"NotificationConfigurations"
|
|
49
|
+
];
|
|
50
|
+
function isAttachmentKind(kind) {
|
|
51
|
+
return kind === "LoadBalancerNames" || kind === "TargetGroupARNs";
|
|
52
|
+
}
|
|
53
|
+
function isWellFormedEntry(kind, entry, side, ignoreSecrets = false) {
|
|
54
|
+
if (isAttachmentKind(kind)) return typeof entry === "string" && entry.length > 0 && entry.length <= ATTACHMENT_MAX_LENGTH[kind] && !/\s/.test(entry) && (ignoreSecrets || !holdsSecretDerivedEntry(entry));
|
|
55
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return false;
|
|
56
|
+
const { identity, stringList } = ENTRY_SHAPE[kind];
|
|
57
|
+
const record = entry;
|
|
58
|
+
const id = record[identity];
|
|
59
|
+
if (typeof id !== "string" || id.length === 0) return false;
|
|
60
|
+
if (side === "desired" && !ignoreSecrets && holdsSecretDerivedEntry(id)) return false;
|
|
61
|
+
if (stringList !== void 0) {
|
|
62
|
+
const list = record[stringList];
|
|
63
|
+
if (list != null && !(Array.isArray(list) && list.every((v) => typeof v === "string"))) return false;
|
|
64
|
+
if (side === "desired" && !ignoreSecrets && Array.isArray(list) && list.some((v) => holdsSecretDerivedEntry(v))) return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
function readList(kind, value, side) {
|
|
69
|
+
if (value === void 0 || value === null) return {
|
|
70
|
+
kind: "list",
|
|
71
|
+
items: []
|
|
72
|
+
};
|
|
73
|
+
if (Array.isArray(value) && value.every((entry) => isWellFormedEntry(kind, entry, side))) return {
|
|
74
|
+
kind: "list",
|
|
75
|
+
items: value
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
kind: "malformed",
|
|
79
|
+
secretDerived: holdsSecretDerivedEntry(value),
|
|
80
|
+
onlySecret: Array.isArray(value) && value.every((entry) => isWellFormedEntry(kind, entry, side, true))
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function secretDerivedCause(kinds, read) {
|
|
84
|
+
const secret = kinds.filter((k) => {
|
|
85
|
+
const r = read[k];
|
|
86
|
+
return r.kind === "malformed" && r.onlySecret;
|
|
87
|
+
});
|
|
88
|
+
return secret.length === 0 ? "" : ` (${secret.join(" / ")} holds a dynamic reference or its mask where a name belongs, which names nothing Auto Scaling accepts)`;
|
|
89
|
+
}
|
|
90
|
+
function listWhat(kind) {
|
|
91
|
+
return isAttachmentKind(kind) ? ATTACHMENT_WHAT[kind] : ENTRY_SHAPE[kind].what;
|
|
92
|
+
}
|
|
93
|
+
function readLists(values, side) {
|
|
94
|
+
const out = {};
|
|
95
|
+
for (const kind of LIST_KINDS) out[kind] = readList(kind, values[kind], side);
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function malformedKinds(read) {
|
|
99
|
+
return LIST_KINDS.filter((k) => read[k].kind === "malformed");
|
|
100
|
+
}
|
|
101
|
+
function removableRecorded(kind, items) {
|
|
102
|
+
if (isAttachmentKind(kind)) return items;
|
|
103
|
+
const { identity, stringList } = ENTRY_SHAPE[kind];
|
|
104
|
+
return items.filter((e) => !holdsSecretDerivedEntry(e[identity])).flatMap((e) => {
|
|
105
|
+
if (stringList === void 0) return [e];
|
|
106
|
+
const record = e;
|
|
107
|
+
const list = record[stringList];
|
|
108
|
+
if (!Array.isArray(list) || !list.some((v) => holdsSecretDerivedEntry(v))) return [e];
|
|
109
|
+
const kept = list.filter((v) => !holdsSecretDerivedEntry(v));
|
|
110
|
+
if (kept.length === 0 && kind === "MetricsCollection") return [];
|
|
111
|
+
return [{
|
|
112
|
+
...record,
|
|
113
|
+
[stringList]: kept
|
|
114
|
+
}];
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function itemsOf(read) {
|
|
118
|
+
return read.kind === "list" ? read.items : [];
|
|
119
|
+
}
|
|
120
|
+
function declaresSubnets(value) {
|
|
121
|
+
return Array.isArray(value) ? value.length > 0 : typeof value === "string" && value !== "";
|
|
122
|
+
}
|
|
123
|
+
function zoneMembers(properties, stateBorne, previousProperties) {
|
|
124
|
+
const names = properties["AvailabilityZones"];
|
|
125
|
+
const ids = properties["AvailabilityZoneIds"];
|
|
126
|
+
if (!stateBorne || names === void 0 || ids === void 0) return {
|
|
127
|
+
...names !== void 0 && { AvailabilityZones: names },
|
|
128
|
+
...ids !== void 0 && { AvailabilityZoneIds: ids }
|
|
129
|
+
};
|
|
130
|
+
if (declaresSubnets(properties["VPCZoneIdentifier"])) return {};
|
|
131
|
+
const changed = (key) => previousProperties === void 0 || JSON.stringify(properties[key]) !== JSON.stringify(previousProperties[key]);
|
|
132
|
+
if (Array.isArray(ids) && ids.length > 0 && changed("AvailabilityZoneIds")) return { AvailabilityZoneIds: ids };
|
|
133
|
+
if (changed("AvailabilityZones")) return { AvailabilityZones: names };
|
|
134
|
+
return {};
|
|
135
|
+
}
|
|
136
|
+
function isFoldableMetricsEntry(entry) {
|
|
137
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return false;
|
|
138
|
+
const { Granularity, Metrics } = entry;
|
|
139
|
+
return typeof Granularity === "string" && Granularity.length > 0 && (Metrics == null || Array.isArray(Metrics) && Metrics.every((m) => typeof m === "string"));
|
|
140
|
+
}
|
|
141
|
+
const ALL_GROUP_METRICS = [
|
|
142
|
+
"GroupAndWarmPoolDesiredCapacity",
|
|
143
|
+
"GroupAndWarmPoolTotalCapacity",
|
|
144
|
+
"GroupDesiredCapacity",
|
|
145
|
+
"GroupInServiceCapacity",
|
|
146
|
+
"GroupInServiceInstances",
|
|
147
|
+
"GroupMaxSize",
|
|
148
|
+
"GroupMinSize",
|
|
149
|
+
"GroupPendingCapacity",
|
|
150
|
+
"GroupPendingInstances",
|
|
151
|
+
"GroupStandbyCapacity",
|
|
152
|
+
"GroupStandbyInstances",
|
|
153
|
+
"GroupTerminatingCapacity",
|
|
154
|
+
"GroupTerminatingInstances",
|
|
155
|
+
"GroupTerminatingRetainedCapacity",
|
|
156
|
+
"GroupTerminatingRetainedInstances",
|
|
157
|
+
"GroupTotalCapacity",
|
|
158
|
+
"GroupTotalInstances",
|
|
159
|
+
"WarmPoolDesiredCapacity",
|
|
160
|
+
"WarmPoolMinSize",
|
|
161
|
+
"WarmPoolPendingCapacity",
|
|
162
|
+
"WarmPoolPendingRetainedCapacity",
|
|
163
|
+
"WarmPoolTerminatingCapacity",
|
|
164
|
+
"WarmPoolTerminatingRetainedCapacity",
|
|
165
|
+
"WarmPoolTotalCapacity",
|
|
166
|
+
"WarmPoolWarmedCapacity"
|
|
167
|
+
];
|
|
168
|
+
function holdsAllKnownMetrics(metrics) {
|
|
169
|
+
return ALL_GROUP_METRICS.every((m) => metrics.includes(m));
|
|
170
|
+
}
|
|
171
|
+
function foldMetricsCollection(value) {
|
|
172
|
+
if (!Array.isArray(value)) return [];
|
|
173
|
+
const byGranularity = /* @__PURE__ */ new Map();
|
|
174
|
+
for (const raw of value) {
|
|
175
|
+
if (raw === null || typeof raw !== "object") continue;
|
|
176
|
+
const entry = raw;
|
|
177
|
+
if (typeof entry.Granularity !== "string" || entry.Granularity.length === 0) continue;
|
|
178
|
+
const metrics = Array.isArray(entry.Metrics) ? entry.Metrics.filter((m) => typeof m === "string") : [];
|
|
179
|
+
const current = byGranularity.get(entry.Granularity);
|
|
180
|
+
if (metrics.length === 0 || current === "ALL") {
|
|
181
|
+
byGranularity.set(entry.Granularity, "ALL");
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const set = current ?? /* @__PURE__ */ new Set();
|
|
185
|
+
for (const m of metrics) set.add(m);
|
|
186
|
+
byGranularity.set(entry.Granularity, set);
|
|
187
|
+
}
|
|
188
|
+
return [...byGranularity.keys()].sort().map((granularity) => {
|
|
189
|
+
const metrics = byGranularity.get(granularity);
|
|
190
|
+
return metrics === "ALL" ? { Granularity: granularity } : {
|
|
191
|
+
Granularity: granularity,
|
|
192
|
+
Metrics: [...metrics].sort()
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
function autoScalingGroupProtectionSite(groupName, region, removedLevel) {
|
|
197
|
+
return {
|
|
198
|
+
subject: "AutoScalingGroup",
|
|
199
|
+
guardName: "DeletionProtection",
|
|
200
|
+
noun: "group",
|
|
201
|
+
isNotFound: (error) => {
|
|
202
|
+
if (typeof error !== "object" || error === null) return false;
|
|
203
|
+
const { name, message } = error;
|
|
204
|
+
return name === "ValidationError" && typeof message === "string" && /not found|does not exist/i.test(message);
|
|
205
|
+
},
|
|
206
|
+
notFoundMeaning: "Auto Scaling answered that the group was not found. That most commonly means the group is gone, and it can also mean it is not in this region or account.",
|
|
207
|
+
commands: () => {
|
|
208
|
+
const aws = pasteableAwsCommand();
|
|
209
|
+
const regionArg = region ? aws` --region ${region}` : aws``;
|
|
210
|
+
const level = removedLevel();
|
|
211
|
+
const restore = level === void 0 ? WITHHELD_AWS_COMMAND : aws`aws autoscaling update-auto-scaling-group --auto-scaling-group-name ${groupName}${regionArg} --deletion-protection ${level}`.render();
|
|
212
|
+
return {
|
|
213
|
+
check: aws`aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names ${groupName}${regionArg}`.render(),
|
|
214
|
+
restoreAfterNotFound: restore,
|
|
215
|
+
restoreLive: restore
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
var LaunchedInstanceGoneError = class extends Error {
|
|
221
|
+
name = "LaunchedInstanceGone";
|
|
222
|
+
constructor(instanceId, state) {
|
|
223
|
+
super(`instance ${instanceId} is ${state}`);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const GONE_INSTANCE_STATES = /* @__PURE__ */ new Set(["shutting-down", "terminated"]);
|
|
227
|
+
function autoScalingGroupInstanceProtectionSite(instanceId, region) {
|
|
228
|
+
const ec2Site = ec2InstanceProtectionSite(instanceId, region);
|
|
229
|
+
return {
|
|
230
|
+
...ec2Site,
|
|
231
|
+
subject: "EC2 Instance launched by AutoScalingGroup",
|
|
232
|
+
isNotFound: (error) => ec2Site.isNotFound(error) || typeof error === "object" && error !== null && error.name === "LaunchedInstanceGone",
|
|
233
|
+
notFoundMeaning: "EC2 answered that the instance was not found, or reports it shutting-down or terminated (the group may have replaced it). That most commonly means the instance is gone, and a not-found answer can also mean it is not in this region or account."
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
const EC2_INSTANCE_TYPE = "AWS::EC2::Instance";
|
|
237
|
+
var ASGProvider = class ASGProvider {
|
|
238
|
+
asgClient;
|
|
239
|
+
createClient;
|
|
240
|
+
ec2Client;
|
|
241
|
+
providerRegion = ambientRegion();
|
|
242
|
+
logger = getLogger().child("ASGProvider");
|
|
243
|
+
protectionFlips = new ProtectionFlipRegistry();
|
|
244
|
+
removedProtection = /* @__PURE__ */ new WeakMap();
|
|
245
|
+
instanceProtectionFlips = new ProtectionFlipRegistry();
|
|
246
|
+
groupInstanceFlips = /* @__PURE__ */ new WeakMap();
|
|
247
|
+
removalDefaults = /* @__PURE__ */ new Map([["AWS::AutoScaling::AutoScalingGroup", /* @__PURE__ */ new Map([
|
|
248
|
+
["InstanceMaintenancePolicy", {
|
|
249
|
+
MinHealthyPercentage: -1,
|
|
250
|
+
MaxHealthyPercentage: -1
|
|
251
|
+
}],
|
|
252
|
+
["CapacityReservationSpecification", { CapacityReservationPreference: "default" }],
|
|
253
|
+
["AvailabilityZoneDistribution", { CapacityDistributionStrategy: "balanced-best-effort" }]
|
|
254
|
+
])]]);
|
|
255
|
+
handledProperties = /* @__PURE__ */ new Map([["AWS::AutoScaling::AutoScalingGroup", /* @__PURE__ */ new Set([
|
|
256
|
+
"AutoScalingGroupName",
|
|
257
|
+
"LaunchTemplate",
|
|
258
|
+
"MinSize",
|
|
259
|
+
"MaxSize",
|
|
260
|
+
"DesiredCapacity",
|
|
261
|
+
"VPCZoneIdentifier",
|
|
262
|
+
"AvailabilityZones",
|
|
263
|
+
"AvailabilityZoneIds",
|
|
264
|
+
"PlacementGroup",
|
|
265
|
+
"InstanceLifecyclePolicy",
|
|
266
|
+
"HealthCheckType",
|
|
267
|
+
"HealthCheckGracePeriod",
|
|
268
|
+
"Cooldown",
|
|
269
|
+
"DefaultCooldown",
|
|
270
|
+
"Tags",
|
|
271
|
+
"TerminationPolicies",
|
|
272
|
+
"NewInstancesProtectedFromScaleIn",
|
|
273
|
+
"CapacityRebalance",
|
|
274
|
+
"ServiceLinkedRoleARN",
|
|
275
|
+
"MaxInstanceLifetime",
|
|
276
|
+
"LoadBalancerNames",
|
|
277
|
+
"TargetGroupARNs",
|
|
278
|
+
"MetricsCollection",
|
|
279
|
+
"LifecycleHookSpecificationList",
|
|
280
|
+
"MixedInstancesPolicy",
|
|
281
|
+
"Context",
|
|
282
|
+
"DesiredCapacityType",
|
|
283
|
+
"DefaultInstanceWarmup",
|
|
284
|
+
"TrafficSources",
|
|
285
|
+
"NotificationConfigurations",
|
|
286
|
+
"AvailabilityZoneDistribution",
|
|
287
|
+
"AvailabilityZoneImpairmentPolicy",
|
|
288
|
+
"SkipZonalShiftValidation",
|
|
289
|
+
"CapacityReservationSpecification",
|
|
290
|
+
"InstanceMaintenancePolicy",
|
|
291
|
+
"DeletionProtection"
|
|
292
|
+
])]]);
|
|
293
|
+
unhandledByDesign = /* @__PURE__ */ new Map([["AWS::AutoScaling::AutoScalingGroup", /* @__PURE__ */ new Map([
|
|
294
|
+
["LaunchConfigurationName", "AWS Launch Configurations end-of-life 2024-10; use LaunchTemplate instead"],
|
|
295
|
+
["NotificationConfiguration", "Legacy singular form; use NotificationConfigurations (plural) which cdkd already wires"],
|
|
296
|
+
["InstanceId", "CreateAutoScalingGroup derives a new launch configuration from the instance, and AWS Launch Configurations are end-of-life 2024-10 like LaunchConfigurationName; use LaunchTemplate instead"]
|
|
297
|
+
])]]);
|
|
298
|
+
canonicalizeDriftProperties(resourceType, properties) {
|
|
299
|
+
if (resourceType !== "AWS::AutoScaling::AutoScalingGroup") return properties;
|
|
300
|
+
const key = "MetricsCollection";
|
|
301
|
+
const metrics = properties[key];
|
|
302
|
+
if (!Array.isArray(metrics) || !metrics.every(isFoldableMetricsEntry)) return properties;
|
|
303
|
+
const folded = foldMetricsCollection(metrics);
|
|
304
|
+
if (JSON.stringify(folded) === JSON.stringify(metrics)) return properties;
|
|
305
|
+
return {
|
|
306
|
+
...properties,
|
|
307
|
+
[key]: folded
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
async canonicalizeDriftPair(resourceType, baseline, aws) {
|
|
311
|
+
const key = "MetricsCollection";
|
|
312
|
+
const unchanged = {
|
|
313
|
+
baseline,
|
|
314
|
+
aws
|
|
315
|
+
};
|
|
316
|
+
if (resourceType !== "AWS::AutoScaling::AutoScalingGroup") return unchanged;
|
|
317
|
+
const recorded = baseline[key];
|
|
318
|
+
if (!Array.isArray(recorded) || !recorded.every(isFoldableMetricsEntry)) return unchanged;
|
|
319
|
+
const folded = foldMetricsCollection(recorded);
|
|
320
|
+
if (folded.every((e) => e.Metrics !== void 0)) return unchanged;
|
|
321
|
+
const live = aws[key];
|
|
322
|
+
const liveBy = /* @__PURE__ */ new Map();
|
|
323
|
+
if (Array.isArray(live) && live.every(isFoldableMetricsEntry)) for (const e of foldMetricsCollection(live)) liveBy.set(e.Granularity, e.Metrics ?? []);
|
|
324
|
+
const expanded = folded.map((e) => e.Metrics !== void 0 ? e : {
|
|
325
|
+
Granularity: e.Granularity,
|
|
326
|
+
Metrics: [.../* @__PURE__ */ new Set([...ALL_GROUP_METRICS, ...liveBy.get(e.Granularity) ?? []])].sort()
|
|
327
|
+
});
|
|
328
|
+
return {
|
|
329
|
+
baseline: {
|
|
330
|
+
...baseline,
|
|
331
|
+
[key]: expanded
|
|
332
|
+
},
|
|
333
|
+
aws
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
getClient() {
|
|
337
|
+
if (!this.asgClient) {
|
|
338
|
+
this.asgClient = new AutoScalingClient({
|
|
339
|
+
...ambientClientDefaults(),
|
|
340
|
+
...this.providerRegion ? { region: this.providerRegion } : {}
|
|
341
|
+
});
|
|
342
|
+
this.createClient = withoutServerErrorRetries(new AutoScalingClient({
|
|
343
|
+
...ambientClientDefaults(),
|
|
344
|
+
...this.providerRegion ? { region: this.providerRegion } : {}
|
|
345
|
+
}));
|
|
346
|
+
}
|
|
347
|
+
return this.asgClient;
|
|
348
|
+
}
|
|
349
|
+
getCreateClient() {
|
|
350
|
+
this.getClient();
|
|
351
|
+
return this.createClient;
|
|
352
|
+
}
|
|
353
|
+
getEc2Client() {
|
|
354
|
+
if (!this.ec2Client) this.ec2Client = new EC2Client({
|
|
355
|
+
...ambientClientDefaults(),
|
|
356
|
+
...this.providerRegion ? { region: this.providerRegion } : {}
|
|
357
|
+
});
|
|
358
|
+
return this.ec2Client;
|
|
359
|
+
}
|
|
360
|
+
async create(logicalId, resourceType, properties, context) {
|
|
361
|
+
if (resourceType !== "AWS::AutoScaling::AutoScalingGroup") throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
|
|
362
|
+
const groupName = properties["AutoScalingGroupName"] || generatedNameFor("AWS::AutoScaling::AutoScalingGroup", logicalId);
|
|
363
|
+
const maskSecrets = context?.maskSecrets ?? ((text) => text);
|
|
364
|
+
const debug = (message) => this.logger.debug(maskSecrets(message));
|
|
365
|
+
debug(`Creating AutoScalingGroup ${logicalId}: ${maskSecrets(groupName)}`);
|
|
366
|
+
const lists = readLists({
|
|
367
|
+
Tags: properties["Tags"],
|
|
368
|
+
LoadBalancerNames: properties["LoadBalancerNames"],
|
|
369
|
+
TargetGroupARNs: properties["TargetGroupARNs"],
|
|
370
|
+
MetricsCollection: properties["MetricsCollection"],
|
|
371
|
+
LifecycleHookSpecificationList: properties["LifecycleHookSpecificationList"],
|
|
372
|
+
TrafficSources: properties["TrafficSources"],
|
|
373
|
+
NotificationConfigurations: properties["NotificationConfigurations"]
|
|
374
|
+
}, "desired");
|
|
375
|
+
const malformedOnCreate = malformedKinds(lists);
|
|
376
|
+
if (malformedOnCreate.length > 0) throw markNonRetryable(new ProvisioningError(`${malformedOnCreate.join(" / ")} of AutoScalingGroup ${logicalId} is not a list of ${malformedOnCreate.map(listWhat).join(" / ")}${secretDerivedCause(malformedOnCreate, lists)} — the group was not created`, resourceType, logicalId));
|
|
377
|
+
let survivorNote;
|
|
378
|
+
try {
|
|
379
|
+
const launchTemplate = this.buildLaunchTemplate(properties);
|
|
380
|
+
const tags = this.buildTags(groupName, properties);
|
|
381
|
+
const vpcZoneIdentifier = this.joinVpcZoneIdentifier(properties["VPCZoneIdentifier"]);
|
|
382
|
+
const createZones = zoneMembers(properties, context?.replayingState === true);
|
|
383
|
+
const minSize = properties["MinSize"] != null ? Number(properties["MinSize"]) : 0;
|
|
384
|
+
const maxSize = properties["MaxSize"] != null ? Number(properties["MaxSize"]) : minSize;
|
|
385
|
+
await this.getCreateClient().send(new CreateAutoScalingGroupCommand({
|
|
386
|
+
AutoScalingGroupName: groupName,
|
|
387
|
+
MinSize: minSize,
|
|
388
|
+
MaxSize: maxSize,
|
|
389
|
+
...properties["DesiredCapacity"] != null && { DesiredCapacity: Number(properties["DesiredCapacity"]) },
|
|
390
|
+
...launchTemplate && { LaunchTemplate: launchTemplate },
|
|
391
|
+
...properties["MixedInstancesPolicy"] !== void 0 && { MixedInstancesPolicy: properties["MixedInstancesPolicy"] },
|
|
392
|
+
...vpcZoneIdentifier !== void 0 && { VPCZoneIdentifier: vpcZoneIdentifier },
|
|
393
|
+
...createZones.AvailabilityZones !== void 0 && { AvailabilityZones: createZones.AvailabilityZones },
|
|
394
|
+
...createZones.AvailabilityZoneIds !== void 0 && { AvailabilityZoneIds: createZones.AvailabilityZoneIds },
|
|
395
|
+
...properties["PlacementGroup"] !== void 0 && { PlacementGroup: properties["PlacementGroup"] },
|
|
396
|
+
...properties["InstanceLifecyclePolicy"] !== void 0 && { InstanceLifecyclePolicy: properties["InstanceLifecyclePolicy"] },
|
|
397
|
+
...properties["HealthCheckType"] !== void 0 && { HealthCheckType: properties["HealthCheckType"] },
|
|
398
|
+
...properties["HealthCheckGracePeriod"] != null && { HealthCheckGracePeriod: Number(properties["HealthCheckGracePeriod"]) },
|
|
399
|
+
...properties["Cooldown"] != null && { DefaultCooldown: Number(properties["Cooldown"]) },
|
|
400
|
+
...properties["DefaultCooldown"] != null && { DefaultCooldown: Number(properties["DefaultCooldown"]) },
|
|
401
|
+
...properties["TerminationPolicies"] !== void 0 && { TerminationPolicies: properties["TerminationPolicies"] },
|
|
402
|
+
...properties["NewInstancesProtectedFromScaleIn"] !== void 0 && { NewInstancesProtectedFromScaleIn: properties["NewInstancesProtectedFromScaleIn"] },
|
|
403
|
+
...properties["CapacityRebalance"] !== void 0 && { CapacityRebalance: properties["CapacityRebalance"] },
|
|
404
|
+
...properties["ServiceLinkedRoleARN"] !== void 0 && { ServiceLinkedRoleARN: properties["ServiceLinkedRoleARN"] },
|
|
405
|
+
...properties["MaxInstanceLifetime"] != null && { MaxInstanceLifetime: Number(properties["MaxInstanceLifetime"]) },
|
|
406
|
+
...properties["LoadBalancerNames"] != null && { LoadBalancerNames: itemsOf(lists.LoadBalancerNames) },
|
|
407
|
+
...properties["TargetGroupARNs"] != null && { TargetGroupARNs: itemsOf(lists.TargetGroupARNs) },
|
|
408
|
+
...properties["Context"] !== void 0 && { Context: properties["Context"] },
|
|
409
|
+
...properties["DesiredCapacityType"] !== void 0 && { DesiredCapacityType: properties["DesiredCapacityType"] },
|
|
410
|
+
...properties["DefaultInstanceWarmup"] != null && { DefaultInstanceWarmup: Number(properties["DefaultInstanceWarmup"]) },
|
|
411
|
+
...properties["LifecycleHookSpecificationList"] != null && { LifecycleHookSpecificationList: properties["LifecycleHookSpecificationList"] },
|
|
412
|
+
...properties["TrafficSources"] != null && { TrafficSources: properties["TrafficSources"] },
|
|
413
|
+
...properties["AvailabilityZoneDistribution"] !== void 0 && { AvailabilityZoneDistribution: properties["AvailabilityZoneDistribution"] },
|
|
414
|
+
...properties["AvailabilityZoneImpairmentPolicy"] !== void 0 && { AvailabilityZoneImpairmentPolicy: properties["AvailabilityZoneImpairmentPolicy"] },
|
|
415
|
+
...properties["SkipZonalShiftValidation"] !== void 0 && { SkipZonalShiftValidation: properties["SkipZonalShiftValidation"] },
|
|
416
|
+
...properties["CapacityReservationSpecification"] !== void 0 && { CapacityReservationSpecification: properties["CapacityReservationSpecification"] },
|
|
417
|
+
...properties["InstanceMaintenancePolicy"] !== void 0 && { InstanceMaintenancePolicy: properties["InstanceMaintenancePolicy"] },
|
|
418
|
+
...properties["DeletionProtection"] !== void 0 && { DeletionProtection: properties["DeletionProtection"] },
|
|
419
|
+
...tags.length > 0 && { Tags: tags }
|
|
420
|
+
}));
|
|
421
|
+
reportCreatedResource(logicalId, resourceType, groupName);
|
|
422
|
+
try {
|
|
423
|
+
for (const entry of itemsOf(lists.MetricsCollection)) await this.getClient().send(new EnableMetricsCollectionCommand({
|
|
424
|
+
AutoScalingGroupName: groupName,
|
|
425
|
+
Granularity: entry.Granularity,
|
|
426
|
+
...entry.Metrics && entry.Metrics.length > 0 && !holdsAllKnownMetrics(entry.Metrics) ? { Metrics: entry.Metrics } : {}
|
|
427
|
+
}));
|
|
428
|
+
for (const entry of itemsOf(lists.NotificationConfigurations)) await this.getClient().send(new PutNotificationConfigurationCommand({
|
|
429
|
+
AutoScalingGroupName: groupName,
|
|
430
|
+
TopicARN: entry.TopicARN,
|
|
431
|
+
NotificationTypes: entry.NotificationTypes ?? []
|
|
432
|
+
}));
|
|
433
|
+
} catch (wiringError) {
|
|
434
|
+
survivorNote = await this.retireFailedCreate(groupName, logicalId, properties, maskSecrets);
|
|
435
|
+
if (survivorNote === void 0) retractCreatedResource(logicalId, resourceType, groupName);
|
|
436
|
+
throw markAuxiliaryFailure(wiringError, logicalId);
|
|
437
|
+
}
|
|
438
|
+
debug(`Successfully created AutoScalingGroup ${logicalId}: ${maskSecrets(groupName)}`);
|
|
439
|
+
const arn = await this.fetchArn(groupName, maskSecrets);
|
|
440
|
+
const attributes = {};
|
|
441
|
+
if (arn) attributes["Arn"] = arn;
|
|
442
|
+
if (launchTemplate?.LaunchTemplateId) attributes["LaunchTemplateID"] = launchTemplate.LaunchTemplateId;
|
|
443
|
+
return {
|
|
444
|
+
physicalId: groupName,
|
|
445
|
+
attributes
|
|
446
|
+
};
|
|
447
|
+
} catch (error) {
|
|
448
|
+
const cause = error instanceof Error ? error : void 0;
|
|
449
|
+
const failure = this.wrapCaughtError(maskSecrets, error, (text) => new ProvisioningError(`Failed to create AutoScalingGroup ${logicalId}: ${text}` + (survivorNote === void 0 ? "" : ` ${survivorNote}`), resourceType, logicalId, groupName, cause));
|
|
450
|
+
if (survivorNote === void 0) throw failure;
|
|
451
|
+
markCreatedBeforeFailure(markNonRetryable(failure), logicalId, resourceType, groupName);
|
|
452
|
+
throw failure;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
async retireFailedCreate(groupName, logicalId, properties, maskSecrets) {
|
|
456
|
+
const debug = (message) => this.logger.debug(maskSecrets(message));
|
|
457
|
+
let deleteAccepted = false;
|
|
458
|
+
try {
|
|
459
|
+
const protection = properties["DeletionProtection"];
|
|
460
|
+
try {
|
|
461
|
+
await this.getClient().send(new UpdateAutoScalingGroupCommand({
|
|
462
|
+
AutoScalingGroupName: groupName,
|
|
463
|
+
MinSize: 0,
|
|
464
|
+
MaxSize: 0,
|
|
465
|
+
DesiredCapacity: 0,
|
|
466
|
+
...protection != null && protection !== "none" && { DeletionProtection: "none" }
|
|
467
|
+
}));
|
|
468
|
+
} catch (scaleError) {
|
|
469
|
+
debug(`Could not scale AutoScalingGroup ${logicalId} to zero before retiring it: ` + describeAwsFailure(scaleError).detail);
|
|
470
|
+
if (protection != null && protection !== "none") try {
|
|
471
|
+
await this.getClient().send(new UpdateAutoScalingGroupCommand({
|
|
472
|
+
AutoScalingGroupName: groupName,
|
|
473
|
+
DeletionProtection: "none"
|
|
474
|
+
}));
|
|
475
|
+
} catch (liftError) {
|
|
476
|
+
debug(`Could not clear DeletionProtection on AutoScalingGroup ${logicalId} before retiring it: ${describeAwsFailure(liftError).detail}`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const flipped = /* @__PURE__ */ new Set();
|
|
480
|
+
await this.removeInstanceTerminationProtection(groupName, logicalId, maskSecrets, flipped);
|
|
481
|
+
await this.removeInstanceTerminationProtection(groupName, logicalId, maskSecrets, flipped);
|
|
482
|
+
await this.getClient().send(new DeleteAutoScalingGroupCommand({
|
|
483
|
+
AutoScalingGroupName: groupName,
|
|
484
|
+
ForceDelete: true
|
|
485
|
+
}));
|
|
486
|
+
deleteAccepted = true;
|
|
487
|
+
await this.waitForGroupDeleted(groupName);
|
|
488
|
+
debug(`Retired AutoScalingGroup ${logicalId} after its create-time wiring failed`);
|
|
489
|
+
return;
|
|
490
|
+
} catch (cleanupError) {
|
|
491
|
+
const outcome = deleteAccepted ? `cdkd started deleting it but could not confirm it is gone` : `cdkd could not delete it`;
|
|
492
|
+
const cleanupFailure = describeAwsFailure(cleanupError);
|
|
493
|
+
if (cleanupFailure.redacted) debug(`Retiring AutoScalingGroup ${logicalId} after its create-time wiring failed did not finish: ${cleanupFailure.detail}`);
|
|
494
|
+
return maskSecrets(`The group was created, and ${outcome} (${cleanupFailure.summary}); outside a rollback's re-create, the failed deploy's rollback journal records it for \`cdkd rollback --revert-failed\`; otherwise delete it yourself before the next deploy: ` + pasteableAwsCommand(maskSecrets)`aws autoscaling delete-auto-scaling-group --auto-scaling-group-name ${groupName} --force-delete`.render());
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
async update(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
498
|
+
properties = withRemovalDefaults(this.removalDefaults, resourceType, properties, previousProperties, context);
|
|
499
|
+
if (resourceType !== "AWS::AutoScaling::AutoScalingGroup") throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId, physicalId);
|
|
500
|
+
const { mask } = withDerivedNameMasks(this.logger, createMaskedLogSinks(this.logger, context?.maskSecrets), [[previousProperties["AutoScalingGroupName"], physicalId]]);
|
|
501
|
+
this.logger.debug(`Updating AutoScalingGroup ${logicalId}: ${mask(physicalId)}`);
|
|
502
|
+
const stringEq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
503
|
+
if (!stringEq(properties["AutoScalingGroupName"], previousProperties["AutoScalingGroupName"]) && !await unchangedBehindSecretReference({
|
|
504
|
+
resourceType,
|
|
505
|
+
key: "AutoScalingGroupName",
|
|
506
|
+
desired: properties["AutoScalingGroupName"],
|
|
507
|
+
previous: previousProperties["AutoScalingGroupName"],
|
|
508
|
+
physicalName: physicalId
|
|
509
|
+
})) {
|
|
510
|
+
const remedy = previousProperties["DeletionProtection"] === "prevent-all-deletion" ? protectedReplacementAdvice({
|
|
511
|
+
evidence: "cdkd's recorded properties for this group carry DeletionProtection: prevent-all-deletion",
|
|
512
|
+
replaceFlags: "cdkd deploy --replace",
|
|
513
|
+
disable: {
|
|
514
|
+
before: "aws autoscaling update-auto-scaling-group --auto-scaling-group-name",
|
|
515
|
+
identifier: physicalId,
|
|
516
|
+
after: "--deletion-protection none",
|
|
517
|
+
maskSecrets: mask
|
|
518
|
+
}
|
|
519
|
+
}) : "Use cdkd deploy --replace to replace the group.";
|
|
520
|
+
throw new ResourceUpdateNotSupportedError(resourceType, logicalId, "AutoScalingGroupName is immutable on AWS — UpdateAutoScalingGroup does not accept a new name; the name is fixed at creation. " + remedy);
|
|
521
|
+
}
|
|
522
|
+
const { next, prev, retained } = await this.readUpdateLists(logicalId, physicalId, resourceType, {
|
|
523
|
+
Tags: properties["Tags"],
|
|
524
|
+
LoadBalancerNames: properties["LoadBalancerNames"],
|
|
525
|
+
TargetGroupARNs: properties["TargetGroupARNs"],
|
|
526
|
+
MetricsCollection: properties["MetricsCollection"],
|
|
527
|
+
LifecycleHookSpecificationList: properties["LifecycleHookSpecificationList"],
|
|
528
|
+
TrafficSources: properties["TrafficSources"],
|
|
529
|
+
NotificationConfigurations: properties["NotificationConfigurations"]
|
|
530
|
+
}, {
|
|
531
|
+
Tags: previousProperties["Tags"],
|
|
532
|
+
LoadBalancerNames: previousProperties["LoadBalancerNames"],
|
|
533
|
+
TargetGroupARNs: previousProperties["TargetGroupARNs"],
|
|
534
|
+
MetricsCollection: previousProperties["MetricsCollection"],
|
|
535
|
+
LifecycleHookSpecificationList: previousProperties["LifecycleHookSpecificationList"],
|
|
536
|
+
TrafficSources: previousProperties["TrafficSources"],
|
|
537
|
+
NotificationConfigurations: previousProperties["NotificationConfigurations"]
|
|
538
|
+
});
|
|
539
|
+
try {
|
|
540
|
+
await this.applyTagsDiff(physicalId, next.Tags, prev.Tags);
|
|
541
|
+
await this.applyLoadBalancerNamesDiff(physicalId, next.LoadBalancerNames, prev.LoadBalancerNames);
|
|
542
|
+
await this.applyTargetGroupArnsDiff(physicalId, next.TargetGroupARNs, prev.TargetGroupARNs, mask, retained.TargetGroupARNs);
|
|
543
|
+
await this.applyMetricsCollectionDiff(physicalId, next.MetricsCollection, prev.MetricsCollection);
|
|
544
|
+
await this.applyLifecycleHooksDiff(physicalId, next.LifecycleHookSpecificationList, prev.LifecycleHookSpecificationList);
|
|
545
|
+
await this.applyTrafficSourcesDiff(physicalId, next.TrafficSources, prev.TrafficSources);
|
|
546
|
+
await this.applyNotificationConfigurationsDiff(physicalId, next.NotificationConfigurations, prev.NotificationConfigurations);
|
|
547
|
+
const launchTemplate = this.buildLaunchTemplate(properties);
|
|
548
|
+
const vpcZoneIdentifier = this.joinVpcZoneIdentifier(properties["VPCZoneIdentifier"]);
|
|
549
|
+
const healthCheckTypeInput = properties["HealthCheckType"];
|
|
550
|
+
const healthCheckGracePeriodInput = properties["HealthCheckGracePeriod"] != null ? Number(properties["HealthCheckGracePeriod"]) : void 0;
|
|
551
|
+
const cooldownRaw = properties["Cooldown"] ?? properties["DefaultCooldown"];
|
|
552
|
+
const defaultCooldownInput = cooldownRaw != null ? Number(cooldownRaw) : void 0;
|
|
553
|
+
const terminationPoliciesInput = properties["TerminationPolicies"];
|
|
554
|
+
const newInstancesProtectedInput = properties["NewInstancesProtectedFromScaleIn"];
|
|
555
|
+
const capacityRebalanceInput = properties["CapacityRebalance"];
|
|
556
|
+
const maxInstanceLifetimeInput = clearOnUpdateRemoval(properties["MaxInstanceLifetime"] != null ? Number(properties["MaxInstanceLifetime"]) : void 0, previousProperties["MaxInstanceLifetime"] != null ? Number(previousProperties["MaxInstanceLifetime"]) : void 0, 0);
|
|
557
|
+
const desiredCapacityTypeInput = properties["DesiredCapacityType"];
|
|
558
|
+
const defaultInstanceWarmupInput = properties["DefaultInstanceWarmup"] != null ? Number(properties["DefaultInstanceWarmup"]) : void 0;
|
|
559
|
+
const instanceMaintenancePolicyInput = properties["InstanceMaintenancePolicy"];
|
|
560
|
+
const capacityReservationSpecInput = properties["CapacityReservationSpecification"];
|
|
561
|
+
const availabilityZoneDistributionInput = properties["AvailabilityZoneDistribution"];
|
|
562
|
+
const deletionProtectionInput = properties["DeletionProtection"];
|
|
563
|
+
const placementGroupInput = clearOnUpdateRemoval(properties["PlacementGroup"], previousProperties["PlacementGroup"], "");
|
|
564
|
+
const instanceLifecyclePolicyInput = properties["InstanceLifecyclePolicy"];
|
|
565
|
+
const zones = zoneMembers(properties, context?.desiredFromAwsReadback === true || context?.replayingState === true, previousProperties);
|
|
566
|
+
const previousImpairmentPolicy = previousProperties["AvailabilityZoneImpairmentPolicy"];
|
|
567
|
+
const previousImpairedBehavior = typeof previousImpairmentPolicy === "object" && previousImpairmentPolicy !== null ? previousImpairmentPolicy["ImpairedZoneHealthCheckBehavior"] : void 0;
|
|
568
|
+
const availabilityZoneImpairmentPolicyInput = clearOnUpdateRemoval(properties["AvailabilityZoneImpairmentPolicy"], previousImpairmentPolicy, {
|
|
569
|
+
ZonalShiftEnabled: false,
|
|
570
|
+
...typeof previousImpairedBehavior === "string" && { ImpairedZoneHealthCheckBehavior: previousImpairedBehavior }
|
|
571
|
+
});
|
|
572
|
+
await this.getClient().send(new UpdateAutoScalingGroupCommand({
|
|
573
|
+
AutoScalingGroupName: physicalId,
|
|
574
|
+
...properties["MinSize"] != null && { MinSize: Number(properties["MinSize"]) },
|
|
575
|
+
...properties["MaxSize"] != null && { MaxSize: Number(properties["MaxSize"]) },
|
|
576
|
+
...properties["DesiredCapacity"] != null && { DesiredCapacity: Number(properties["DesiredCapacity"]) },
|
|
577
|
+
...launchTemplate && { LaunchTemplate: launchTemplate },
|
|
578
|
+
...properties["MixedInstancesPolicy"] !== void 0 && { MixedInstancesPolicy: properties["MixedInstancesPolicy"] },
|
|
579
|
+
...vpcZoneIdentifier !== void 0 && { VPCZoneIdentifier: vpcZoneIdentifier },
|
|
580
|
+
...zones.AvailabilityZones !== void 0 && { AvailabilityZones: zones.AvailabilityZones },
|
|
581
|
+
...zones.AvailabilityZoneIds !== void 0 && { AvailabilityZoneIds: zones.AvailabilityZoneIds },
|
|
582
|
+
...placementGroupInput !== void 0 && { PlacementGroup: placementGroupInput },
|
|
583
|
+
...instanceLifecyclePolicyInput !== void 0 && { InstanceLifecyclePolicy: instanceLifecyclePolicyInput },
|
|
584
|
+
...healthCheckTypeInput !== void 0 && { HealthCheckType: healthCheckTypeInput },
|
|
585
|
+
...healthCheckGracePeriodInput !== void 0 && { HealthCheckGracePeriod: healthCheckGracePeriodInput },
|
|
586
|
+
...defaultCooldownInput !== void 0 && { DefaultCooldown: defaultCooldownInput },
|
|
587
|
+
...terminationPoliciesInput !== void 0 && { TerminationPolicies: terminationPoliciesInput },
|
|
588
|
+
...newInstancesProtectedInput !== void 0 && { NewInstancesProtectedFromScaleIn: newInstancesProtectedInput },
|
|
589
|
+
...capacityRebalanceInput !== void 0 && { CapacityRebalance: capacityRebalanceInput },
|
|
590
|
+
...properties["ServiceLinkedRoleARN"] !== void 0 && { ServiceLinkedRoleARN: properties["ServiceLinkedRoleARN"] },
|
|
591
|
+
...maxInstanceLifetimeInput !== void 0 && { MaxInstanceLifetime: maxInstanceLifetimeInput },
|
|
592
|
+
...properties["Context"] !== void 0 && { Context: properties["Context"] },
|
|
593
|
+
...desiredCapacityTypeInput !== void 0 && { DesiredCapacityType: desiredCapacityTypeInput },
|
|
594
|
+
...defaultInstanceWarmupInput !== void 0 && { DefaultInstanceWarmup: defaultInstanceWarmupInput },
|
|
595
|
+
...availabilityZoneDistributionInput !== void 0 && { AvailabilityZoneDistribution: availabilityZoneDistributionInput },
|
|
596
|
+
...availabilityZoneImpairmentPolicyInput !== void 0 && { AvailabilityZoneImpairmentPolicy: availabilityZoneImpairmentPolicyInput },
|
|
597
|
+
...properties["SkipZonalShiftValidation"] !== void 0 && { SkipZonalShiftValidation: properties["SkipZonalShiftValidation"] },
|
|
598
|
+
...capacityReservationSpecInput !== void 0 && { CapacityReservationSpecification: capacityReservationSpecInput },
|
|
599
|
+
...instanceMaintenancePolicyInput !== void 0 && { InstanceMaintenancePolicy: instanceMaintenancePolicyInput },
|
|
600
|
+
...deletionProtectionInput !== void 0 && { DeletionProtection: deletionProtectionInput }
|
|
601
|
+
}));
|
|
602
|
+
this.logger.debug(`Successfully updated AutoScalingGroup ${logicalId}`);
|
|
603
|
+
const arn = await this.fetchArn(physicalId, mask);
|
|
604
|
+
const attributes = {};
|
|
605
|
+
if (arn) attributes["Arn"] = arn;
|
|
606
|
+
if (launchTemplate?.LaunchTemplateId) attributes["LaunchTemplateID"] = launchTemplate.LaunchTemplateId;
|
|
607
|
+
return {
|
|
608
|
+
physicalId,
|
|
609
|
+
wasReplaced: false,
|
|
610
|
+
attributes
|
|
611
|
+
};
|
|
612
|
+
} catch (error) {
|
|
613
|
+
if (error instanceof ResourceUpdateNotSupportedError) throw error;
|
|
614
|
+
throw this.wrapUpdateError(error, resourceType, logicalId, physicalId, mask);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
wrapCaughtError(mask, error, build) {
|
|
618
|
+
const failure = describeAwsFailure(error);
|
|
619
|
+
if (!failure.redacted) return wrapMaskedAwsError(mask, error, build);
|
|
620
|
+
this.logger.debug(mask(build(mask(failure.detail)).message));
|
|
621
|
+
const relay = isAuxiliaryFailure(error) ? "" : relayedNameCollision(failure.detail);
|
|
622
|
+
const wrapped = build(`${mask(failure.summary)}${relay}`);
|
|
623
|
+
return error instanceof Error ? markRedactedCause(wrapped) : wrapped;
|
|
624
|
+
}
|
|
625
|
+
wrapUpdateError(error, resourceType, logicalId, physicalId, maskSecrets) {
|
|
626
|
+
const cause = error instanceof Error ? error : void 0;
|
|
627
|
+
return this.wrapCaughtError(maskerOrIdentity(maskSecrets), error, (text) => new ProvisioningError(`Failed to update AutoScalingGroup ${logicalId}: ${text}`, resourceType, logicalId, physicalId, cause));
|
|
628
|
+
}
|
|
629
|
+
async delete(logicalId, physicalId, resourceType, _properties, context) {
|
|
630
|
+
let current;
|
|
631
|
+
const removedValue = () => current ? this.removedProtection.get(current) : void 0;
|
|
632
|
+
try {
|
|
633
|
+
await this.deleteGroupUnderCompensation(logicalId, physicalId, resourceType, context, removedValue, (flip) => {
|
|
634
|
+
current = flip;
|
|
635
|
+
});
|
|
636
|
+
} catch (error) {
|
|
637
|
+
if (current) await this.settleInstanceFlips(logicalId, current, error, context?.expectedRegion, context?.maskSecrets);
|
|
638
|
+
throw error;
|
|
639
|
+
}
|
|
640
|
+
if (current) this.releaseInstanceFlips(current, context?.expectedRegion);
|
|
641
|
+
}
|
|
642
|
+
async settleInstanceFlips(logicalId, groupFlip, error, region, mask) {
|
|
643
|
+
const instances = this.groupInstanceFlips.get(groupFlip);
|
|
644
|
+
if (!instances) return;
|
|
645
|
+
for (const [instanceId, flip] of instances) {
|
|
646
|
+
if (groupFlip.deleteAccepted) flip.deleteAccepted = true;
|
|
647
|
+
if (await settleProtectionFlip({
|
|
648
|
+
registry: this.instanceProtectionFlips,
|
|
649
|
+
key: protectionFlipKey(EC2_INSTANCE_TYPE, instanceId, region),
|
|
650
|
+
flip,
|
|
651
|
+
error,
|
|
652
|
+
compensation: {
|
|
653
|
+
logicalId,
|
|
654
|
+
physicalId: instanceId,
|
|
655
|
+
logger: this.logger,
|
|
656
|
+
mask,
|
|
657
|
+
site: autoScalingGroupInstanceProtectionSite(instanceId, region),
|
|
658
|
+
reEnable: () => this.reEnableLaunchedInstance(instanceId)
|
|
659
|
+
}
|
|
660
|
+
})) instances.delete(instanceId);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
async reEnableLaunchedInstance(instanceId) {
|
|
664
|
+
try {
|
|
665
|
+
await reEnableInstanceApiTermination(this.getEc2Client(), instanceId);
|
|
666
|
+
} catch (writeError) {
|
|
667
|
+
let state;
|
|
668
|
+
try {
|
|
669
|
+
state = (await this.getEc2Client().send(new DescribeInstancesCommand({ InstanceIds: [instanceId] }))).Reservations?.[0]?.Instances?.[0]?.State?.Name;
|
|
670
|
+
} catch {}
|
|
671
|
+
if (state !== void 0 && GONE_INSTANCE_STATES.has(state)) throw new LaunchedInstanceGoneError(instanceId, state);
|
|
672
|
+
throw writeError;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
releaseInstanceFlips(groupFlip, region) {
|
|
676
|
+
for (const instanceId of this.groupInstanceFlips.get(groupFlip)?.keys() ?? []) this.instanceProtectionFlips.release(protectionFlipKey(EC2_INSTANCE_TYPE, instanceId, region));
|
|
677
|
+
this.groupInstanceFlips.delete(groupFlip);
|
|
678
|
+
}
|
|
679
|
+
async deleteGroupUnderCompensation(logicalId, physicalId, resourceType, context, removedValue, onFlip) {
|
|
680
|
+
await deleteWithProtectionCompensation({
|
|
681
|
+
registry: this.protectionFlips,
|
|
682
|
+
key: protectionFlipKey(resourceType, physicalId, context?.expectedRegion),
|
|
683
|
+
run: (flip) => {
|
|
684
|
+
onFlip(flip);
|
|
685
|
+
return this.deleteOnce(logicalId, physicalId, resourceType, context, flip);
|
|
686
|
+
},
|
|
687
|
+
compensation: {
|
|
688
|
+
logicalId,
|
|
689
|
+
physicalId,
|
|
690
|
+
logger: this.logger,
|
|
691
|
+
mask: context?.maskSecrets,
|
|
692
|
+
site: autoScalingGroupProtectionSite(physicalId, context?.expectedRegion, removedValue),
|
|
693
|
+
reEnable: async () => {
|
|
694
|
+
const value = removedValue();
|
|
695
|
+
if (value === void 0) throw new Error("the DeletionProtection value removed by this run was not recorded");
|
|
696
|
+
await this.getClient().send(new UpdateAutoScalingGroupCommand({
|
|
697
|
+
AutoScalingGroupName: physicalId,
|
|
698
|
+
DeletionProtection: value
|
|
699
|
+
}));
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
async deleteOnce(logicalId, physicalId, resourceType, context, flip) {
|
|
705
|
+
this.logger.debug(`Deleting AutoScalingGroup ${logicalId}: ${physicalId}`);
|
|
706
|
+
if (context?.removeProtection === true) {
|
|
707
|
+
try {
|
|
708
|
+
let observed;
|
|
709
|
+
await observeThenDisableProtection({
|
|
710
|
+
flip,
|
|
711
|
+
logger: this.logger,
|
|
712
|
+
physicalId,
|
|
713
|
+
mask: context?.maskSecrets,
|
|
714
|
+
guardName: "DeletionProtection",
|
|
715
|
+
observe: async () => {
|
|
716
|
+
const value = (await this.describeGroup(physicalId))?.DeletionProtection;
|
|
717
|
+
observed = typeof value === "string" && value !== "none" ? value : void 0;
|
|
718
|
+
return observed !== void 0;
|
|
719
|
+
},
|
|
720
|
+
disable: async () => {
|
|
721
|
+
await this.getClient().send(new UpdateAutoScalingGroupCommand({
|
|
722
|
+
AutoScalingGroupName: physicalId,
|
|
723
|
+
DeletionProtection: "none"
|
|
724
|
+
}));
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
if (observed !== void 0) this.removedProtection.set(flip, observed);
|
|
728
|
+
this.logger.debug(`Disabled DeletionProtection on AutoScalingGroup ${logicalId} before delete`);
|
|
729
|
+
} catch (flipError) {
|
|
730
|
+
this.logger.debug(maskerOrIdentity(context?.maskSecrets)(`Could not disable DeletionProtection on ${physicalId}: ${describeAwsFailure(flipError).detail}`));
|
|
731
|
+
}
|
|
732
|
+
await this.removeInstanceTerminationProtectionForDelete(physicalId, logicalId, flip, context.expectedRegion);
|
|
733
|
+
}
|
|
734
|
+
try {
|
|
735
|
+
await this.getClient().send(new DeleteAutoScalingGroupCommand({
|
|
736
|
+
AutoScalingGroupName: physicalId,
|
|
737
|
+
ForceDelete: context?.removeProtection === true
|
|
738
|
+
}));
|
|
739
|
+
flip.deleteAccepted = true;
|
|
740
|
+
this.logger.debug(`Successfully initiated deletion of AutoScalingGroup ${logicalId}`);
|
|
741
|
+
await this.waitForGroupDeleted(physicalId);
|
|
742
|
+
} catch (error) {
|
|
743
|
+
if (this.isNotFoundError(error)) {
|
|
744
|
+
const clientRegion = await this.getClient().config.region();
|
|
745
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
746
|
+
this.logger.debug(`AutoScalingGroup ${physicalId} does not exist, skipping deletion`);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const cause = error instanceof Error ? error : void 0;
|
|
750
|
+
const thrown = this.wrapCaughtError(maskerOrIdentity(context?.maskSecrets), error, (text) => new ProvisioningError(`Failed to delete AutoScalingGroup ${logicalId}: ${text}`, resourceType, logicalId, physicalId, cause));
|
|
751
|
+
throw error instanceof Error && error.name.includes("NotFound") ? markNotGone(thrown) : thrown;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
async getAttribute(physicalId, _resourceType, attributeName, logicalId) {
|
|
755
|
+
const group = await this.describeGroup(physicalId);
|
|
756
|
+
if (!group) throw new ProvisioningError(`AutoScalingGroup ${physicalId} not found while resolving attribute ${attributeName}`, "AWS::AutoScaling::AutoScalingGroup", logicalId, physicalId);
|
|
757
|
+
switch (attributeName) {
|
|
758
|
+
case "Arn":
|
|
759
|
+
case "AutoScalingGroupARN": return group.AutoScalingGroupARN ?? "";
|
|
760
|
+
case "LaunchConfigurationName": return group.LaunchConfigurationName ?? "";
|
|
761
|
+
case "LaunchTemplateID":
|
|
762
|
+
case "LaunchTemplateId": return group.LaunchTemplate?.LaunchTemplateId ?? "";
|
|
763
|
+
default: return "";
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
async readCurrentState(physicalId, _logicalId, _resourceType) {
|
|
767
|
+
const groupPromise = (async () => {
|
|
768
|
+
try {
|
|
769
|
+
return await this.describeGroup(physicalId);
|
|
770
|
+
} catch (err) {
|
|
771
|
+
if (this.isNotFoundError(err)) return null;
|
|
772
|
+
throw err;
|
|
773
|
+
}
|
|
774
|
+
})();
|
|
775
|
+
const lifecycleHooksPromise = this.getClient().send(new DescribeLifecycleHooksCommand({ AutoScalingGroupName: physicalId })).then((r) => r.LifecycleHooks ?? []).catch((err) => {
|
|
776
|
+
this.logger.debug(`DescribeLifecycleHooks(${physicalId}) failed: ${describeAwsFailure(err).detail}`);
|
|
777
|
+
return [];
|
|
778
|
+
});
|
|
779
|
+
const trafficSourcesPromise = this.getClient().send(new DescribeTrafficSourcesCommand({ AutoScalingGroupName: physicalId })).then((r) => r.TrafficSources ?? []).catch((err) => {
|
|
780
|
+
this.logger.debug(`DescribeTrafficSources(${physicalId}) failed: ${describeAwsFailure(err).detail}`);
|
|
781
|
+
return [];
|
|
782
|
+
});
|
|
783
|
+
const notificationsPromise = this.getClient().send(new DescribeNotificationConfigurationsCommand({ AutoScalingGroupNames: [physicalId] })).then((r) => r.NotificationConfigurations ?? []).catch((err) => {
|
|
784
|
+
this.logger.debug(`DescribeNotificationConfigurations(${physicalId}) failed: ${describeAwsFailure(err).detail}`);
|
|
785
|
+
return [];
|
|
786
|
+
});
|
|
787
|
+
const [group, lifecycleHooks, trafficSources, notifications] = await Promise.all([
|
|
788
|
+
groupPromise,
|
|
789
|
+
lifecycleHooksPromise,
|
|
790
|
+
trafficSourcesPromise,
|
|
791
|
+
notificationsPromise
|
|
792
|
+
]);
|
|
793
|
+
if (group === null) return void 0;
|
|
794
|
+
if (!group) return RESOURCE_NOT_FOUND;
|
|
795
|
+
const result = {};
|
|
796
|
+
if (group.AutoScalingGroupName !== void 0) result["AutoScalingGroupName"] = group.AutoScalingGroupName;
|
|
797
|
+
if (group.LaunchTemplate) {
|
|
798
|
+
const lt = {};
|
|
799
|
+
if (group.LaunchTemplate.LaunchTemplateId !== void 0) lt["LaunchTemplateId"] = group.LaunchTemplate.LaunchTemplateId;
|
|
800
|
+
if (group.LaunchTemplate.LaunchTemplateName !== void 0) lt["LaunchTemplateName"] = group.LaunchTemplate.LaunchTemplateName;
|
|
801
|
+
if (group.LaunchTemplate.Version !== void 0) lt["Version"] = group.LaunchTemplate.Version;
|
|
802
|
+
result["LaunchTemplate"] = lt;
|
|
803
|
+
}
|
|
804
|
+
result["MinSize"] = group.MinSize ?? 0;
|
|
805
|
+
result["MaxSize"] = group.MaxSize ?? 0;
|
|
806
|
+
if (group.DesiredCapacity !== void 0) result["DesiredCapacity"] = group.DesiredCapacity;
|
|
807
|
+
if (group.VPCZoneIdentifier !== void 0 && group.VPCZoneIdentifier !== "") result["VPCZoneIdentifier"] = group.VPCZoneIdentifier.split(",").map((s) => s.trim());
|
|
808
|
+
else result["VPCZoneIdentifier"] = [];
|
|
809
|
+
result["AvailabilityZones"] = group.AvailabilityZones ?? [];
|
|
810
|
+
result["AvailabilityZoneIds"] = group.AvailabilityZoneIds ?? [];
|
|
811
|
+
if (group.PlacementGroup !== void 0 && group.PlacementGroup !== "") result["PlacementGroup"] = group.PlacementGroup;
|
|
812
|
+
if (group.InstanceLifecyclePolicy !== void 0) result["InstanceLifecyclePolicy"] = group.InstanceLifecyclePolicy;
|
|
813
|
+
if (group.HealthCheckType !== void 0) result["HealthCheckType"] = group.HealthCheckType;
|
|
814
|
+
if (group.HealthCheckGracePeriod !== void 0) result["HealthCheckGracePeriod"] = group.HealthCheckGracePeriod;
|
|
815
|
+
if (group.DefaultCooldown !== void 0) result["Cooldown"] = group.DefaultCooldown;
|
|
816
|
+
result["NewInstancesProtectedFromScaleIn"] = group.NewInstancesProtectedFromScaleIn ?? false;
|
|
817
|
+
result["TerminationPolicies"] = group.TerminationPolicies ?? [];
|
|
818
|
+
result["CapacityRebalance"] = group.CapacityRebalance ?? false;
|
|
819
|
+
if (group.ServiceLinkedRoleARN !== void 0) result["ServiceLinkedRoleARN"] = group.ServiceLinkedRoleARN;
|
|
820
|
+
if (group.MaxInstanceLifetime !== void 0) result["MaxInstanceLifetime"] = group.MaxInstanceLifetime;
|
|
821
|
+
result["LoadBalancerNames"] = group.LoadBalancerNames ?? [];
|
|
822
|
+
result["TargetGroupARNs"] = group.TargetGroupARNs ?? [];
|
|
823
|
+
if (group.Context !== void 0) result["Context"] = group.Context;
|
|
824
|
+
if (group.DesiredCapacityType !== void 0) result["DesiredCapacityType"] = group.DesiredCapacityType;
|
|
825
|
+
if (group.DefaultInstanceWarmup !== void 0) result["DefaultInstanceWarmup"] = group.DefaultInstanceWarmup;
|
|
826
|
+
if (group.MixedInstancesPolicy !== void 0) result["MixedInstancesPolicy"] = group.MixedInstancesPolicy;
|
|
827
|
+
if (group.AvailabilityZoneDistribution !== void 0) result["AvailabilityZoneDistribution"] = group.AvailabilityZoneDistribution;
|
|
828
|
+
if (group.AvailabilityZoneImpairmentPolicy !== void 0) result["AvailabilityZoneImpairmentPolicy"] = group.AvailabilityZoneImpairmentPolicy;
|
|
829
|
+
if (group.CapacityReservationSpecification !== void 0) result["CapacityReservationSpecification"] = group.CapacityReservationSpecification;
|
|
830
|
+
if (group.InstanceMaintenancePolicy !== void 0) result["InstanceMaintenancePolicy"] = group.InstanceMaintenancePolicy;
|
|
831
|
+
if (group.DeletionProtection !== void 0) result["DeletionProtection"] = group.DeletionProtection;
|
|
832
|
+
else result["DeletionProtection"] = "none";
|
|
833
|
+
result["Tags"] = normalizeAwsTagsToCfn(group.Tags);
|
|
834
|
+
result["MetricsCollection"] = mapEnabledMetricsToCfn(group.EnabledMetrics);
|
|
835
|
+
result["LifecycleHookSpecificationList"] = mapLifecycleHooksToCfn(lifecycleHooks);
|
|
836
|
+
result["TrafficSources"] = mapTrafficSourcesToCfn(trafficSources.filter((t) => {
|
|
837
|
+
if (t.Identifier === void 0) return false;
|
|
838
|
+
if (t.Type === "elbv2" || t.Type === "elb") return false;
|
|
839
|
+
return true;
|
|
840
|
+
}));
|
|
841
|
+
result["NotificationConfigurations"] = mapNotificationsToCfn(notifications);
|
|
842
|
+
return result;
|
|
843
|
+
}
|
|
844
|
+
buildLaunchTemplate(properties) {
|
|
845
|
+
const lt = properties["LaunchTemplate"];
|
|
846
|
+
if (!lt) return void 0;
|
|
847
|
+
const out = {};
|
|
848
|
+
if (lt.LaunchTemplateId !== void 0) {
|
|
849
|
+
out.LaunchTemplateId = lt.LaunchTemplateId;
|
|
850
|
+
if (lt.LaunchTemplateName !== void 0) this.logger.debug(`buildLaunchTemplate: both LaunchTemplateId (${lt.LaunchTemplateId}) and LaunchTemplateName (${lt.LaunchTemplateName}) templated; dropping Name`);
|
|
851
|
+
} else if (lt.LaunchTemplateName !== void 0) out.LaunchTemplateName = lt.LaunchTemplateName;
|
|
852
|
+
if (lt.Version !== void 0) out.Version = String(lt.Version);
|
|
853
|
+
if (out.LaunchTemplateId === void 0 && out.LaunchTemplateName === void 0) return;
|
|
854
|
+
return out;
|
|
855
|
+
}
|
|
856
|
+
buildTags(groupName, properties) {
|
|
857
|
+
const raw = properties["Tags"];
|
|
858
|
+
if (!raw) return [];
|
|
859
|
+
return raw.filter((t) => t.Key !== void 0).map((t) => ({
|
|
860
|
+
ResourceId: groupName,
|
|
861
|
+
ResourceType: "auto-scaling-group",
|
|
862
|
+
Key: t.Key,
|
|
863
|
+
Value: t.Value ?? "",
|
|
864
|
+
PropagateAtLaunch: t.PropagateAtLaunch ?? false
|
|
865
|
+
}));
|
|
866
|
+
}
|
|
867
|
+
joinVpcZoneIdentifier(value) {
|
|
868
|
+
if (value === void 0 || value === null) return void 0;
|
|
869
|
+
if (Array.isArray(value)) {
|
|
870
|
+
const cleaned = value.map((v) => String(v).trim()).filter((v) => v.length > 0);
|
|
871
|
+
if (cleaned.length === 0) return void 0;
|
|
872
|
+
return cleaned.join(",");
|
|
873
|
+
}
|
|
874
|
+
if (typeof value === "string") return value;
|
|
875
|
+
}
|
|
876
|
+
async describeGroup(groupName) {
|
|
877
|
+
return (await this.getClient().send(new DescribeAutoScalingGroupsCommand({ AutoScalingGroupNames: [groupName] }))).AutoScalingGroups?.[0];
|
|
878
|
+
}
|
|
879
|
+
async removeInstanceTerminationProtection(groupName, logicalId, maskSecrets, alreadyFlipped) {
|
|
880
|
+
const instanceIds = (await this.listGroupInstanceIds(groupName, logicalId, maskSecrets))?.filter((id) => !alreadyFlipped.has(id));
|
|
881
|
+
if (!instanceIds || instanceIds.length === 0) return;
|
|
882
|
+
this.logger.debug(`Disabling EC2 termination protection on ${instanceIds.length} instance(s) of AutoScalingGroup ${logicalId} before force delete`);
|
|
883
|
+
for (const instanceId of instanceIds) if (await disableInstanceApiTermination(this.getEc2Client(), instanceId, this.logger)) alreadyFlipped.add(instanceId);
|
|
884
|
+
}
|
|
885
|
+
async removeInstanceTerminationProtectionForDelete(groupName, logicalId, groupFlip, region) {
|
|
886
|
+
const instanceIds = await this.listGroupInstanceIds(groupName, logicalId, (text) => text);
|
|
887
|
+
if (!instanceIds || instanceIds.length === 0) return;
|
|
888
|
+
let flips = this.groupInstanceFlips.get(groupFlip);
|
|
889
|
+
if (!flips) {
|
|
890
|
+
flips = /* @__PURE__ */ new Map();
|
|
891
|
+
this.groupInstanceFlips.set(groupFlip, flips);
|
|
892
|
+
}
|
|
893
|
+
this.logger.debug(`Disabling EC2 termination protection on ${instanceIds.length} instance(s) of AutoScalingGroup ${logicalId} before force delete`);
|
|
894
|
+
for (const instanceId of instanceIds) {
|
|
895
|
+
const flip = this.instanceProtectionFlips.acquire(protectionFlipKey(EC2_INSTANCE_TYPE, instanceId, region), PROTECTION_FLIP_REUSE_WINDOW_MS);
|
|
896
|
+
flips.set(instanceId, flip);
|
|
897
|
+
await observeThenDisableInstanceApiTermination(this.getEc2Client(), instanceId, flip, this.logger);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
async listGroupInstanceIds(groupName, logicalId, maskSecrets) {
|
|
901
|
+
try {
|
|
902
|
+
return ((await this.describeGroup(groupName))?.Instances ?? []).map((i) => i.InstanceId).filter((id) => typeof id === "string" && id.length > 0);
|
|
903
|
+
} catch (describeError) {
|
|
904
|
+
this.logger.debug(maskSecrets(`Could not enumerate instances of AutoScalingGroup ${logicalId} for termination-protection removal: ${describeAwsFailure(describeError).detail}`));
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
async fetchArn(groupName, maskSecrets = (text) => text) {
|
|
909
|
+
try {
|
|
910
|
+
return (await this.describeGroup(groupName))?.AutoScalingGroupARN;
|
|
911
|
+
} catch (err) {
|
|
912
|
+
this.logger.debug(maskSecrets(`DescribeAutoScalingGroups(${groupName}) failed: ${describeAwsFailure(err).detail}`));
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
isNotFoundError(error) {
|
|
917
|
+
if (!(error instanceof Error)) return false;
|
|
918
|
+
const name = error.name ?? "";
|
|
919
|
+
const message = error.message.toLowerCase();
|
|
920
|
+
return name === "ValidationError" && (message.includes("autoscalinggroup name not found") || message.includes("not found") || message.includes("does not exist"));
|
|
921
|
+
}
|
|
922
|
+
async waitForGroupDeleted(groupName, maxWaitMs = 9e5) {
|
|
923
|
+
const startTime = Date.now();
|
|
924
|
+
let delay = 5e3;
|
|
925
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
926
|
+
try {
|
|
927
|
+
if (!await this.describeGroup(groupName)) return;
|
|
928
|
+
} catch (error) {
|
|
929
|
+
if (this.isNotFoundError(error)) return;
|
|
930
|
+
throw error;
|
|
931
|
+
}
|
|
932
|
+
await this.sleep(delay);
|
|
933
|
+
delay = Math.min(delay * 2, 1e4);
|
|
934
|
+
}
|
|
935
|
+
throw new Error(`Timed out waiting for AutoScalingGroup ${groupName} to be deleted (15 minute cap)`);
|
|
936
|
+
}
|
|
937
|
+
sleep(ms) {
|
|
938
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
939
|
+
}
|
|
940
|
+
async readUpdateLists(logicalId, physicalId, resourceType, desired, recorded) {
|
|
941
|
+
const nextRead = readLists(desired, "desired");
|
|
942
|
+
const prevRead = readLists(recorded, "recorded");
|
|
943
|
+
const badNext = malformedKinds(nextRead);
|
|
944
|
+
let badPrev = malformedKinds(prevRead);
|
|
945
|
+
const retained = {
|
|
946
|
+
LoadBalancerNames: [],
|
|
947
|
+
TargetGroupARNs: []
|
|
948
|
+
};
|
|
949
|
+
const readLive = (k) => {
|
|
950
|
+
const read = prevRead[k];
|
|
951
|
+
return isAttachmentKind(k) && read.kind === "malformed";
|
|
952
|
+
};
|
|
953
|
+
if (badNext.length === 0 && badPrev.length > 0 && badPrev.every(readLive)) {
|
|
954
|
+
const kinds = badPrev.join(" / ");
|
|
955
|
+
let group;
|
|
956
|
+
try {
|
|
957
|
+
group = await this.describeGroup(physicalId);
|
|
958
|
+
} catch (error) {
|
|
959
|
+
throw new ProvisioningError(`the recorded ${kinds} of AutoScalingGroup ${logicalId} is not a list cdkd can read, and the live group could not be read from Auto Scaling instead — nothing was attached or detached`, resourceType, logicalId, physicalId, error instanceof Error ? error : void 0);
|
|
960
|
+
}
|
|
961
|
+
for (const kind of badPrev) {
|
|
962
|
+
const live = (group?.[kind] ?? []).filter((v) => typeof v === "string");
|
|
963
|
+
const wanted = itemsOf(nextRead[kind]);
|
|
964
|
+
prevRead[kind] = {
|
|
965
|
+
kind: "list",
|
|
966
|
+
items: live.filter((v) => wanted.includes(v))
|
|
967
|
+
};
|
|
968
|
+
retained[kind] = live.filter((v) => !wanted.includes(v));
|
|
969
|
+
if (retained[kind].length > 0) this.logger.warn(safeMsg`The recorded ${kind} of AutoScalingGroup ${logicalId} is not a list cdkd can read, so cdkd read it from Auto Scaling; the group holds ${kind} entries the desired list does not name, and cdkd left them attached. Detach them yourself if they are no longer wanted.`);
|
|
970
|
+
}
|
|
971
|
+
badPrev = [];
|
|
972
|
+
}
|
|
973
|
+
if (badNext.length > 0 || badPrev.length > 0) {
|
|
974
|
+
const which = [...badNext.map((k) => `desired ${k}`), ...badPrev.map((k) => `recorded ${k}`)];
|
|
975
|
+
const what = [...new Set([...badNext, ...badPrev].map(listWhat))];
|
|
976
|
+
throw markNonRetryable(new ProvisioningError(`${which.join(" / ")} of AutoScalingGroup ${logicalId} is not a list of ${what.join(" / ")}${secretDerivedCause(badNext, nextRead)} — nothing was attached, detached or removed` + (badPrev.length > 0 ? `: ${this.recordedListRepair(badPrev, prevRead)}` : ""), resourceType, logicalId, physicalId));
|
|
977
|
+
}
|
|
978
|
+
const next = {};
|
|
979
|
+
const prev = {};
|
|
980
|
+
for (const kind of LIST_KINDS) {
|
|
981
|
+
next[kind] = itemsOf(nextRead[kind]);
|
|
982
|
+
prev[kind] = removableRecorded(kind, itemsOf(prevRead[kind]));
|
|
983
|
+
}
|
|
984
|
+
return {
|
|
985
|
+
next,
|
|
986
|
+
prev,
|
|
987
|
+
retained
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
recordedListRepair(kinds, read) {
|
|
991
|
+
const live = kinds.filter(isAttachmentKind);
|
|
992
|
+
const entry = kinds.filter((k) => !isAttachmentKind(k));
|
|
993
|
+
const secret = entry.filter((k) => {
|
|
994
|
+
const r = read[k];
|
|
995
|
+
return r.kind === "malformed" && r.secretDerived;
|
|
996
|
+
});
|
|
997
|
+
const plain = entry.filter((k) => !secret.includes(k));
|
|
998
|
+
const parts = [];
|
|
999
|
+
if (plain.length > 0) parts.push(`repair the recorded ${plain.join(" / ")} in state.json to a list of ${[...new Set(plain.map(listWhat))].join(" / ")} and re-run`);
|
|
1000
|
+
if (secret.length > 0) parts.push(`the recorded ${secret.join(" / ")} is secret-derived (cdkd keeps the dynamic reference or its mask in state), so do not write the value into state.json; set it to [] in state.json and re-run, which removes nothing`);
|
|
1001
|
+
if (live.length > 0) parts.push(`the recorded ${live.join(" / ")} needs no repair: cdkd reads it from Auto Scaling instead once every desired list and every other recorded list is well-formed`);
|
|
1002
|
+
return parts.join("; ");
|
|
1003
|
+
}
|
|
1004
|
+
async applyTagsDiff(physicalId, next, prev) {
|
|
1005
|
+
if (JSON.stringify(next) === JSON.stringify(prev)) return;
|
|
1006
|
+
const nextEntries = next;
|
|
1007
|
+
const prevEntries = prev;
|
|
1008
|
+
const nextByKey = /* @__PURE__ */ new Map();
|
|
1009
|
+
for (const t of nextEntries) if (t.Key) nextByKey.set(t.Key, t);
|
|
1010
|
+
const prevByKey = /* @__PURE__ */ new Map();
|
|
1011
|
+
for (const t of prevEntries) if (t.Key) prevByKey.set(t.Key, t);
|
|
1012
|
+
const toDelete = [];
|
|
1013
|
+
for (const [key, tag] of prevByKey) if (!nextByKey.has(key)) toDelete.push(tag);
|
|
1014
|
+
if (toDelete.length > 0) await this.getClient().send(new DeleteTagsCommand$1({ Tags: toDelete.map((t) => ({
|
|
1015
|
+
ResourceId: physicalId,
|
|
1016
|
+
ResourceType: "auto-scaling-group",
|
|
1017
|
+
Key: t.Key
|
|
1018
|
+
})) }));
|
|
1019
|
+
const toUpsert = [];
|
|
1020
|
+
for (const [key, tag] of nextByKey) {
|
|
1021
|
+
const before = prevByKey.get(key);
|
|
1022
|
+
if (JSON.stringify(before) === JSON.stringify(tag)) continue;
|
|
1023
|
+
toUpsert.push(tag);
|
|
1024
|
+
}
|
|
1025
|
+
if (toUpsert.length > 0) await this.getClient().send(new CreateOrUpdateTagsCommand({ Tags: toUpsert.map((t) => ({
|
|
1026
|
+
ResourceId: physicalId,
|
|
1027
|
+
ResourceType: "auto-scaling-group",
|
|
1028
|
+
Key: t.Key,
|
|
1029
|
+
...t.Value !== void 0 && { Value: t.Value },
|
|
1030
|
+
...t.PropagateAtLaunch !== void 0 && { PropagateAtLaunch: t.PropagateAtLaunch }
|
|
1031
|
+
})) }));
|
|
1032
|
+
}
|
|
1033
|
+
async applyLoadBalancerNamesDiff(physicalId, nextNames, prevNames) {
|
|
1034
|
+
if (JSON.stringify(nextNames) === JSON.stringify(prevNames)) return;
|
|
1035
|
+
const nextSet = new Set(nextNames);
|
|
1036
|
+
const prevSet = new Set(prevNames);
|
|
1037
|
+
const toAttach = nextNames.filter((n) => !prevSet.has(n));
|
|
1038
|
+
const toDetach = prevNames.filter((n) => !nextSet.has(n));
|
|
1039
|
+
if (toDetach.length > 0) await this.getClient().send(new DetachLoadBalancersCommand({
|
|
1040
|
+
AutoScalingGroupName: physicalId,
|
|
1041
|
+
LoadBalancerNames: toDetach
|
|
1042
|
+
}));
|
|
1043
|
+
if (toAttach.length > 0) await this.getClient().send(new AttachLoadBalancersCommand({
|
|
1044
|
+
AutoScalingGroupName: physicalId,
|
|
1045
|
+
LoadBalancerNames: toAttach
|
|
1046
|
+
}));
|
|
1047
|
+
}
|
|
1048
|
+
async applyTargetGroupArnsDiff(physicalId, nextArns, prevArns, maskSecrets, retainedArns = []) {
|
|
1049
|
+
if (JSON.stringify(nextArns) === JSON.stringify(prevArns)) return;
|
|
1050
|
+
const nextSet = new Set(nextArns);
|
|
1051
|
+
const prevSet = new Set(prevArns);
|
|
1052
|
+
const toAttach = nextArns.filter((a) => !prevSet.has(a));
|
|
1053
|
+
const toDetach = prevArns.filter((a) => !nextSet.has(a));
|
|
1054
|
+
if (toDetach.length > 0) await this.getClient().send(new DetachLoadBalancerTargetGroupsCommand({
|
|
1055
|
+
AutoScalingGroupName: physicalId,
|
|
1056
|
+
TargetGroupARNs: toDetach
|
|
1057
|
+
}));
|
|
1058
|
+
if (toAttach.length > 0) await this.getClient().send(new AttachLoadBalancerTargetGroupsCommand({
|
|
1059
|
+
AutoScalingGroupName: physicalId,
|
|
1060
|
+
TargetGroupARNs: toAttach
|
|
1061
|
+
}));
|
|
1062
|
+
if (toDetach.length > 0 || toAttach.length > 0) await this.waitForTargetGroupArnsConvergence(physicalId, /* @__PURE__ */ new Set([...nextArns, ...retainedArns]), maskSecrets);
|
|
1063
|
+
}
|
|
1064
|
+
static TG_CONVERGENCE_TIMEOUT_MS = 3e4;
|
|
1065
|
+
static TG_CONVERGENCE_POLL_INTERVAL_MS = 1e3;
|
|
1066
|
+
async waitForTargetGroupArnsConvergence(physicalId, expected, maskSecrets = (text) => text) {
|
|
1067
|
+
const warn = (message) => this.logger.warn(maskSecrets(message));
|
|
1068
|
+
const debug = (message) => this.logger.debug(maskSecrets(message));
|
|
1069
|
+
const deadlineMs = Date.now() + ASGProvider.TG_CONVERGENCE_TIMEOUT_MS;
|
|
1070
|
+
let lastObserved = /* @__PURE__ */ new Set();
|
|
1071
|
+
while (Date.now() < deadlineMs) {
|
|
1072
|
+
let resp;
|
|
1073
|
+
try {
|
|
1074
|
+
resp = await this.getClient().send(new DescribeAutoScalingGroupsCommand({ AutoScalingGroupNames: [physicalId] }));
|
|
1075
|
+
} catch (err) {
|
|
1076
|
+
debug(`applyTargetGroupArnsDiff convergence poll: transient error, retrying — ${describeAwsFailure(err).detail}`);
|
|
1077
|
+
await new Promise((r) => setTimeout(r, ASGProvider.TG_CONVERGENCE_POLL_INTERVAL_MS));
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
lastObserved = new Set(resp.AutoScalingGroups?.[0]?.TargetGroupARNs ?? []);
|
|
1081
|
+
if (lastObserved.size === expected.size && [...expected].every((a) => lastObserved.has(a))) return;
|
|
1082
|
+
await new Promise((r) => setTimeout(r, ASGProvider.TG_CONVERGENCE_POLL_INTERVAL_MS));
|
|
1083
|
+
}
|
|
1084
|
+
const expectedSorted = [...expected].sort().map((a) => maskSecrets(a));
|
|
1085
|
+
const observedSorted = [...lastObserved].sort().map((a) => maskSecrets(a));
|
|
1086
|
+
warn(`applyTargetGroupArnsDiff: TG set did not converge within ${ASGProvider.TG_CONVERGENCE_TIMEOUT_MS}ms for ASG ${physicalId}. expected=${JSON.stringify(expectedSorted)} observed=${JSON.stringify(observedSorted)}`);
|
|
1087
|
+
}
|
|
1088
|
+
async applyMetricsCollectionDiff(physicalId, next, prev) {
|
|
1089
|
+
const nextFold = foldMetricsCollection(next);
|
|
1090
|
+
const prevFold = foldMetricsCollection(prev);
|
|
1091
|
+
if (JSON.stringify(nextFold) === JSON.stringify(prevFold)) return;
|
|
1092
|
+
const toMap = (fold) => new Map(fold.map((e) => [e.Granularity, e.Metrics]));
|
|
1093
|
+
const prevBy = toMap(prevFold);
|
|
1094
|
+
const nextBy = toMap(nextFold);
|
|
1095
|
+
const disable = async (metrics) => {
|
|
1096
|
+
await this.getClient().send(new DisableMetricsCollectionCommand({
|
|
1097
|
+
AutoScalingGroupName: physicalId,
|
|
1098
|
+
...metrics !== void 0 ? { Metrics: metrics } : {}
|
|
1099
|
+
}));
|
|
1100
|
+
};
|
|
1101
|
+
const enable = async (granularity, metrics) => {
|
|
1102
|
+
const asAll = metrics === void 0 || disables.length === 0 && holdsAllKnownMetrics(metrics);
|
|
1103
|
+
await this.getClient().send(new EnableMetricsCollectionCommand({
|
|
1104
|
+
AutoScalingGroupName: physicalId,
|
|
1105
|
+
Granularity: granularity,
|
|
1106
|
+
...asAll ? {} : { Metrics: metrics }
|
|
1107
|
+
}));
|
|
1108
|
+
};
|
|
1109
|
+
const disables = [];
|
|
1110
|
+
const changed = /* @__PURE__ */ new Set();
|
|
1111
|
+
for (const [granularity, metrics] of prevBy) if (!nextBy.has(granularity)) disables.push(metrics);
|
|
1112
|
+
for (const [granularity, metrics] of nextBy) {
|
|
1113
|
+
const had = prevBy.has(granularity);
|
|
1114
|
+
const before = prevBy.get(granularity);
|
|
1115
|
+
if (had && JSON.stringify(before ?? null) === JSON.stringify(metrics ?? null)) continue;
|
|
1116
|
+
changed.add(granularity);
|
|
1117
|
+
if (metrics !== void 0 && had) {
|
|
1118
|
+
if (before === void 0) disables.push(void 0);
|
|
1119
|
+
else {
|
|
1120
|
+
const removed = before.filter((m) => !metrics.includes(m));
|
|
1121
|
+
if (removed.length > 0) disables.push(removed);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
for (const metrics of disables) await disable(metrics);
|
|
1126
|
+
for (const [granularity, metrics] of nextBy) if (disables.length > 0 || changed.has(granularity)) await enable(granularity, metrics);
|
|
1127
|
+
}
|
|
1128
|
+
async applyLifecycleHooksDiff(physicalId, next, prev) {
|
|
1129
|
+
if (JSON.stringify(next) === JSON.stringify(prev)) return;
|
|
1130
|
+
const nextEntries = next;
|
|
1131
|
+
const prevEntries = prev;
|
|
1132
|
+
const nextNames = new Set(nextEntries.map((e) => e.LifecycleHookName).filter((n) => !!n));
|
|
1133
|
+
for (const e of prevEntries) if (e.LifecycleHookName && !nextNames.has(e.LifecycleHookName)) await this.getClient().send(new DeleteLifecycleHookCommand({
|
|
1134
|
+
AutoScalingGroupName: physicalId,
|
|
1135
|
+
LifecycleHookName: e.LifecycleHookName
|
|
1136
|
+
}));
|
|
1137
|
+
const prevByName = /* @__PURE__ */ new Map();
|
|
1138
|
+
for (const e of prevEntries) if (e.LifecycleHookName) prevByName.set(e.LifecycleHookName, e);
|
|
1139
|
+
for (const e of nextEntries) {
|
|
1140
|
+
if (!e.LifecycleHookName) continue;
|
|
1141
|
+
const prevHook = prevByName.get(e.LifecycleHookName);
|
|
1142
|
+
if (JSON.stringify(prevHook) === JSON.stringify(e)) continue;
|
|
1143
|
+
await this.getClient().send(new PutLifecycleHookCommand({
|
|
1144
|
+
AutoScalingGroupName: physicalId,
|
|
1145
|
+
LifecycleHookName: e.LifecycleHookName,
|
|
1146
|
+
...e.LifecycleTransition !== void 0 && { LifecycleTransition: e.LifecycleTransition },
|
|
1147
|
+
...e.RoleARN !== void 0 && { RoleARN: e.RoleARN },
|
|
1148
|
+
...e.NotificationTargetARN !== void 0 && { NotificationTargetARN: e.NotificationTargetARN },
|
|
1149
|
+
...e.NotificationMetadata !== void 0 && { NotificationMetadata: e.NotificationMetadata },
|
|
1150
|
+
...e.HeartbeatTimeout !== void 0 && { HeartbeatTimeout: e.HeartbeatTimeout },
|
|
1151
|
+
...e.DefaultResult !== void 0 && { DefaultResult: e.DefaultResult }
|
|
1152
|
+
}));
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
async applyTrafficSourcesDiff(physicalId, next, prev) {
|
|
1156
|
+
if (JSON.stringify(next) === JSON.stringify(prev)) return;
|
|
1157
|
+
const nextEntries = next;
|
|
1158
|
+
const prevEntries = prev;
|
|
1159
|
+
const nextIds = new Set(nextEntries.map((e) => e.Identifier).filter((i) => !!i));
|
|
1160
|
+
const prevIds = new Set(prevEntries.map((e) => e.Identifier).filter((i) => !!i));
|
|
1161
|
+
const toDetach = prevEntries.filter((e) => e.Identifier && !nextIds.has(e.Identifier));
|
|
1162
|
+
const toAttach = nextEntries.filter((e) => e.Identifier && !prevIds.has(e.Identifier));
|
|
1163
|
+
if (toDetach.length > 0) await this.getClient().send(new DetachTrafficSourcesCommand({
|
|
1164
|
+
AutoScalingGroupName: physicalId,
|
|
1165
|
+
TrafficSources: toDetach.map((e) => ({
|
|
1166
|
+
Identifier: e.Identifier,
|
|
1167
|
+
...e.Type !== void 0 && { Type: e.Type }
|
|
1168
|
+
}))
|
|
1169
|
+
}));
|
|
1170
|
+
if (toAttach.length > 0) await this.getClient().send(new AttachTrafficSourcesCommand({
|
|
1171
|
+
AutoScalingGroupName: physicalId,
|
|
1172
|
+
TrafficSources: toAttach.map((e) => ({
|
|
1173
|
+
Identifier: e.Identifier,
|
|
1174
|
+
...e.Type !== void 0 && { Type: e.Type }
|
|
1175
|
+
}))
|
|
1176
|
+
}));
|
|
1177
|
+
}
|
|
1178
|
+
async applyNotificationConfigurationsDiff(physicalId, next, prev) {
|
|
1179
|
+
if (JSON.stringify(next) === JSON.stringify(prev)) return;
|
|
1180
|
+
const nextEntries = next;
|
|
1181
|
+
const prevEntries = prev;
|
|
1182
|
+
const nextByTopic = /* @__PURE__ */ new Map();
|
|
1183
|
+
for (const e of nextEntries) if (e.TopicARN) nextByTopic.set(e.TopicARN, e.NotificationTypes);
|
|
1184
|
+
const prevByTopic = /* @__PURE__ */ new Map();
|
|
1185
|
+
for (const e of prevEntries) if (e.TopicARN) prevByTopic.set(e.TopicARN, e.NotificationTypes);
|
|
1186
|
+
for (const topic of prevByTopic.keys()) if (!nextByTopic.has(topic)) await this.getClient().send(new DeleteNotificationConfigurationCommand({
|
|
1187
|
+
AutoScalingGroupName: physicalId,
|
|
1188
|
+
TopicARN: topic
|
|
1189
|
+
}));
|
|
1190
|
+
for (const [topic, types] of nextByTopic) {
|
|
1191
|
+
const before = prevByTopic.get(topic);
|
|
1192
|
+
if (JSON.stringify(before ?? null) === JSON.stringify(types ?? null)) continue;
|
|
1193
|
+
await this.getClient().send(new PutNotificationConfigurationCommand({
|
|
1194
|
+
AutoScalingGroupName: physicalId,
|
|
1195
|
+
TopicARN: topic,
|
|
1196
|
+
NotificationTypes: types ?? []
|
|
1197
|
+
}));
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
function mapEnabledMetricsToCfn(enabledMetrics) {
|
|
1202
|
+
if (!enabledMetrics || enabledMetrics.length === 0) return [];
|
|
1203
|
+
const byGranularity = /* @__PURE__ */ new Map();
|
|
1204
|
+
for (const e of enabledMetrics) {
|
|
1205
|
+
const g = e.Granularity;
|
|
1206
|
+
if (!g) continue;
|
|
1207
|
+
let set = byGranularity.get(g);
|
|
1208
|
+
if (!set) {
|
|
1209
|
+
set = /* @__PURE__ */ new Set();
|
|
1210
|
+
byGranularity.set(g, set);
|
|
1211
|
+
}
|
|
1212
|
+
if (e.Metric) set.add(e.Metric);
|
|
1213
|
+
}
|
|
1214
|
+
const result = [];
|
|
1215
|
+
for (const granularity of Array.from(byGranularity.keys()).sort()) {
|
|
1216
|
+
const metrics = Array.from(byGranularity.get(granularity) ?? []).sort();
|
|
1217
|
+
result.push(metrics.length > 0 ? {
|
|
1218
|
+
Granularity: granularity,
|
|
1219
|
+
Metrics: metrics
|
|
1220
|
+
} : { Granularity: granularity });
|
|
1221
|
+
}
|
|
1222
|
+
return result;
|
|
1223
|
+
}
|
|
1224
|
+
function mapLifecycleHooksToCfn(hooks) {
|
|
1225
|
+
if (!hooks || hooks.length === 0) return [];
|
|
1226
|
+
const result = [];
|
|
1227
|
+
for (const h of hooks) {
|
|
1228
|
+
if (!h.LifecycleHookName) continue;
|
|
1229
|
+
const entry = { LifecycleHookName: h.LifecycleHookName };
|
|
1230
|
+
if (h.LifecycleTransition !== void 0) entry["LifecycleTransition"] = h.LifecycleTransition;
|
|
1231
|
+
if (h.RoleARN !== void 0) entry["RoleARN"] = h.RoleARN;
|
|
1232
|
+
if (h.NotificationTargetARN !== void 0) entry["NotificationTargetARN"] = h.NotificationTargetARN;
|
|
1233
|
+
if (h.NotificationMetadata !== void 0) entry["NotificationMetadata"] = h.NotificationMetadata;
|
|
1234
|
+
if (h.HeartbeatTimeout !== void 0) entry["HeartbeatTimeout"] = h.HeartbeatTimeout;
|
|
1235
|
+
if (h.DefaultResult !== void 0) entry["DefaultResult"] = h.DefaultResult;
|
|
1236
|
+
result.push(entry);
|
|
1237
|
+
}
|
|
1238
|
+
result.sort((a, b) => String(a["LifecycleHookName"]).localeCompare(String(b["LifecycleHookName"])));
|
|
1239
|
+
return result;
|
|
1240
|
+
}
|
|
1241
|
+
function mapTrafficSourcesToCfn(trafficSources) {
|
|
1242
|
+
if (!trafficSources || trafficSources.length === 0) return [];
|
|
1243
|
+
const result = [];
|
|
1244
|
+
for (const t of trafficSources) {
|
|
1245
|
+
if (!t.Identifier) continue;
|
|
1246
|
+
const entry = { Identifier: t.Identifier };
|
|
1247
|
+
if (t.Type !== void 0) entry["Type"] = t.Type;
|
|
1248
|
+
result.push(entry);
|
|
1249
|
+
}
|
|
1250
|
+
result.sort((a, b) => String(a["Identifier"]).localeCompare(String(b["Identifier"])));
|
|
1251
|
+
return result;
|
|
1252
|
+
}
|
|
1253
|
+
function mapNotificationsToCfn(configurations) {
|
|
1254
|
+
if (!configurations || configurations.length === 0) return [];
|
|
1255
|
+
const byTopic = /* @__PURE__ */ new Map();
|
|
1256
|
+
for (const c of configurations) {
|
|
1257
|
+
if (!c.TopicARN) continue;
|
|
1258
|
+
let set = byTopic.get(c.TopicARN);
|
|
1259
|
+
if (!set) {
|
|
1260
|
+
set = /* @__PURE__ */ new Set();
|
|
1261
|
+
byTopic.set(c.TopicARN, set);
|
|
1262
|
+
}
|
|
1263
|
+
if (c.NotificationType) set.add(c.NotificationType);
|
|
1264
|
+
}
|
|
1265
|
+
const result = [];
|
|
1266
|
+
for (const topic of Array.from(byTopic.keys()).sort()) {
|
|
1267
|
+
const types = Array.from(byTopic.get(topic) ?? []).sort();
|
|
1268
|
+
result.push({
|
|
1269
|
+
TopicARN: topic,
|
|
1270
|
+
NotificationTypes: types
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
return result;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
export { ASGProvider };
|
|
1277
|
+
//# sourceMappingURL=asg-provider-4rNFSmz9.js.map
|