@cdkd/cli 0.0.1 → 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 +139 -3
|
@@ -0,0 +1,1749 @@
|
|
|
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, It as withDerivedNameMasks, J as deleteAfterProtectionFlipWithFinalSnapshot, K as ProtectionFlipRegistry, Kt as markCreatedBeforeFailure, M as RESOURCE_NOT_FOUND, Mr as markRedactedCause, Nn as ProvisioningError, Nr as wrapMaskedAwsError, P as markNotGone, Q as isTerminalDbWaitStatus, W as resolveExplicitPhysicalId, Y as deleteWithProtectionCompensation, 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, nt as protectionFlipKey, o as retractCreatedResource, rt as rdsFamilyProtectionSite, tt as observeThenDisableProtection } from "./created-resource-sink-CmrPwGge.js";
|
|
3
|
+
import { c as ambientRegion } from "./aws-client-defaults-BLXFsfMa.js";
|
|
4
|
+
import { $i as stringifyIfAssigned, Dt as enclosingDeadlineRemainingMs, Qi as definedAttributes, Tc as dbInstanceEngineChangeIsInPlace, Wi as markDeleteStarted, Yi as refuseMalformedDesiredTags, Zi as tagPlanWarning, gl as dynamicReferenceTokens, ha as slowCcOperationTimeoutMs, qi as planTagDiff } from "./deploy-engine-dYZ0v6Kz.js";
|
|
5
|
+
import { n as modifyDbInstanceWhenAvailable } from "./db-instance-modify-retry-DCLA_p9I.js";
|
|
6
|
+
import { DescribeSecurityGroupsCommand, EC2Client } from "@aws-sdk/client-ec2";
|
|
7
|
+
import { isDeepStrictEqual } from "node:util";
|
|
8
|
+
import { AddTagsToResourceCommand, CreateDBClusterCommand, CreateDBInstanceCommand, CreateDBSubnetGroupCommand, DeleteDBClusterCommand, DeleteDBInstanceCommand, DeleteDBSubnetGroupCommand, DescribeDBClustersCommand, DescribeDBInstancesCommand, DescribeDBSubnetGroupsCommand, DescribeGlobalClustersCommand, DisableHttpEndpointCommand, EnableHttpEndpointCommand, ListTagsForResourceCommand, ModifyDBClusterCommand, ModifyDBInstanceCommand, ModifyDBSubnetGroupCommand, RDSClient, RebootDBInstanceCommand, RemoveFromGlobalClusterCommand, RemoveTagsFromResourceCommand } from "@aws-sdk/client-rds";
|
|
9
|
+
|
|
10
|
+
// src/provisioning/providers/rds-provider.ts
|
|
11
|
+
function propertyChangedForUpdate(key, desired, recorded, context) {
|
|
12
|
+
const changedProperties = context?.changedProperties;
|
|
13
|
+
if (changedProperties !== void 0) return changedProperties.has(key);
|
|
14
|
+
return holdsRecordedStandIn(recorded) || !isDeepStrictEqual(desired, recorded);
|
|
15
|
+
}
|
|
16
|
+
function holdsRecordedStandIn(value) {
|
|
17
|
+
if (typeof value === "string") return value === "***" || dynamicReferenceTokens(value).length > 0;
|
|
18
|
+
if (Array.isArray(value)) return value.some(holdsRecordedStandIn);
|
|
19
|
+
if (value !== null && typeof value === "object") return Object.values(value).some(holdsRecordedStandIn);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
function masterUserPasswordToSend(properties, previousProperties, context) {
|
|
23
|
+
const desired = properties["MasterUserPassword"];
|
|
24
|
+
if (desired === void 0 || desired === null) return void 0;
|
|
25
|
+
return propertyChangedForUpdate("MasterUserPassword", desired, previousProperties["MasterUserPassword"], context) ? desired : void 0;
|
|
26
|
+
}
|
|
27
|
+
function masterCredentialSettingsChanged(properties, previousProperties, context) {
|
|
28
|
+
return propertyChangedForUpdate("MasterUserPassword", properties["MasterUserPassword"], previousProperties["MasterUserPassword"], context) || propertyChangedForUpdate("ManageMasterUserPassword", properties["ManageMasterUserPassword"], previousProperties["ManageMasterUserPassword"], context) || propertyChangedForUpdate("MasterUserSecret", properties["MasterUserSecret"], previousProperties["MasterUserSecret"], context);
|
|
29
|
+
}
|
|
30
|
+
function managedPasswordOnRemoval(properties, previousProperties) {
|
|
31
|
+
const previous = previousProperties["ManageMasterUserPassword"];
|
|
32
|
+
return properties["ManageMasterUserPassword"] === void 0 && (previous === true || previous === "true");
|
|
33
|
+
}
|
|
34
|
+
const CLUSTER_REMOVED_PORT = /* @__PURE__ */ new Map([
|
|
35
|
+
["aurora-postgresql", 5432],
|
|
36
|
+
["aurora-mysql", 3306],
|
|
37
|
+
["mysql", 3306],
|
|
38
|
+
["postgres", 3306]
|
|
39
|
+
]);
|
|
40
|
+
function numberOrUndefined(value) {
|
|
41
|
+
return value == null ? void 0 : Number(value);
|
|
42
|
+
}
|
|
43
|
+
const STORAGE_MEMBERS = /* @__PURE__ */ new Set([
|
|
44
|
+
"AllocatedStorage",
|
|
45
|
+
"Iops",
|
|
46
|
+
"StorageType",
|
|
47
|
+
"StorageThroughput"
|
|
48
|
+
]);
|
|
49
|
+
function instanceValueChanged(properties, previousProperties, key, context) {
|
|
50
|
+
const desired = properties[key];
|
|
51
|
+
if (desired == null) return false;
|
|
52
|
+
const previous = previousProperties[key];
|
|
53
|
+
if (previous == null) return true;
|
|
54
|
+
const differs = typeof desired === "object" || typeof previous === "object" ? !isDeepStrictEqual(desired, previous) : String(desired) !== String(previous);
|
|
55
|
+
if (STORAGE_MEMBERS.has(key) && !differs) return false;
|
|
56
|
+
if (context?.changedProperties !== void 0) return context.changedProperties.has(key);
|
|
57
|
+
return differs;
|
|
58
|
+
}
|
|
59
|
+
function sizeOrZero(value) {
|
|
60
|
+
const size = Number(value);
|
|
61
|
+
return value != null && Number.isFinite(size) ? size : 0;
|
|
62
|
+
}
|
|
63
|
+
function isProvisionedIoStorage(properties) {
|
|
64
|
+
const storageType = properties["StorageType"];
|
|
65
|
+
if (typeof storageType === "string" && storageType !== "") return [
|
|
66
|
+
"io1",
|
|
67
|
+
"io2",
|
|
68
|
+
"gp3"
|
|
69
|
+
].includes(storageType.toLowerCase());
|
|
70
|
+
return properties["Iops"] != null;
|
|
71
|
+
}
|
|
72
|
+
async function instanceModifyBackfillMembers(properties, previousProperties, liveAllocatedStorage, logicalId, context) {
|
|
73
|
+
const changed = (key) => instanceValueChanged(properties, previousProperties, key, context);
|
|
74
|
+
const desiredAllocated = numberOrUndefined(properties["AllocatedStorage"]);
|
|
75
|
+
const members = {};
|
|
76
|
+
if (changed("BackupRetentionPeriod")) members.BackupRetentionPeriod = Number(properties["BackupRetentionPeriod"]);
|
|
77
|
+
for (const key of [
|
|
78
|
+
"PreferredBackupWindow",
|
|
79
|
+
"PreferredMaintenanceWindow",
|
|
80
|
+
"StorageType",
|
|
81
|
+
"OptionGroupName",
|
|
82
|
+
"CACertificateIdentifier"
|
|
83
|
+
]) if (changed(key)) members[key] = properties[key];
|
|
84
|
+
for (const key of ["NetworkType", "LicenseModel"]) if (changed(key)) members[key] = properties[key];
|
|
85
|
+
if (changed("DatabaseInsightsMode")) members.DatabaseInsightsMode = properties["DatabaseInsightsMode"];
|
|
86
|
+
for (const key of [
|
|
87
|
+
"MultiAZ",
|
|
88
|
+
"AutoMinorVersionUpgrade",
|
|
89
|
+
"CopyTagsToSnapshot",
|
|
90
|
+
"DedicatedLogVolume"
|
|
91
|
+
]) if (changed(key)) members[key] = properties[key];
|
|
92
|
+
if (changed("StorageThroughput")) members.StorageThroughput = Number(properties["StorageThroughput"]);
|
|
93
|
+
if (changed("PromotionTier")) members.PromotionTier = Number(properties["PromotionTier"]);
|
|
94
|
+
if (changed("CACertificateIdentifier") && properties["CertificateRotationRestart"] != null) members.CertificateRotationRestart = properties["CertificateRotationRestart"];
|
|
95
|
+
if (changed("MaxAllocatedStorage")) members.MaxAllocatedStorage = Number(properties["MaxAllocatedStorage"]);
|
|
96
|
+
else if (properties["MaxAllocatedStorage"] == null && previousProperties["MaxAllocatedStorage"] != null) {
|
|
97
|
+
const live = await liveAllocatedStorage();
|
|
98
|
+
if (live === void 0) throw new Error(safeMsg`DBInstance ${logicalId}: MaxAllocatedStorage was removed from the template, which turns storage autoscaling off by setting it to the allocated storage, but DescribeDBInstances did not report the allocated storage`);
|
|
99
|
+
members.MaxAllocatedStorage = Math.max(live, sizeOrZero(desiredAllocated));
|
|
100
|
+
}
|
|
101
|
+
const rollback = context?.replayingState === true;
|
|
102
|
+
if (changed("EngineVersion") && !rollback) members.EngineVersion = String(properties["EngineVersion"]);
|
|
103
|
+
if (changed("DBParameterGroupName") && !(rollback && changed("EngineVersion"))) members.DBParameterGroupName = properties["DBParameterGroupName"];
|
|
104
|
+
if (changed("AllowMajorVersionUpgrade") || members.EngineVersion !== void 0 && properties["AllowMajorVersionUpgrade"] != null) members.AllowMajorVersionUpgrade = properties["AllowMajorVersionUpgrade"];
|
|
105
|
+
if (changed("AllocatedStorage") && !rollback) members.AllocatedStorage = desiredAllocated;
|
|
106
|
+
if (isProvisionedIoStorage(properties)) {
|
|
107
|
+
if (changed("AllocatedStorage") || changed("Iops") || changed("StorageThroughput") || changed("StorageType")) {
|
|
108
|
+
if (members.AllocatedStorage === void 0) {
|
|
109
|
+
const live = await liveAllocatedStorage();
|
|
110
|
+
const larger = Math.max(sizeOrZero(live), sizeOrZero(desiredAllocated), rollback ? sizeOrZero(previousProperties["AllocatedStorage"]) : 0);
|
|
111
|
+
if (larger > 0) members.AllocatedStorage = larger;
|
|
112
|
+
}
|
|
113
|
+
if (properties["Iops"] != null) members.Iops = Number(properties["Iops"]);
|
|
114
|
+
}
|
|
115
|
+
} else if (changed("Iops") && !rollback) members.Iops = Number(properties["Iops"]);
|
|
116
|
+
const enableChanged = changed("EnablePerformanceInsights");
|
|
117
|
+
const keyChanged = changed("PerformanceInsightsKMSKeyId");
|
|
118
|
+
const enableDesired = properties["EnablePerformanceInsights"];
|
|
119
|
+
const keyDesired = properties["PerformanceInsightsKMSKeyId"];
|
|
120
|
+
if (enableChanged || keyChanged) {
|
|
121
|
+
members.EnablePerformanceInsights = enableDesired;
|
|
122
|
+
if (keyChanged || String(enableDesired) === "true") members.PerformanceInsightsKMSKeyId = keyDesired;
|
|
123
|
+
}
|
|
124
|
+
if (changed("PerformanceInsightsRetentionPeriod")) {
|
|
125
|
+
members.PerformanceInsightsRetentionPeriod = Number(properties["PerformanceInsightsRetentionPeriod"]);
|
|
126
|
+
members.EnablePerformanceInsights = enableDesired;
|
|
127
|
+
members.PerformanceInsightsKMSKeyId = keyDesired;
|
|
128
|
+
}
|
|
129
|
+
return Object.fromEntries(Object.entries(members).filter(([, value]) => value !== void 0));
|
|
130
|
+
}
|
|
131
|
+
function instanceParameterGroupPendingReboot(instance) {
|
|
132
|
+
return (instance?.DBParameterGroups ?? []).some((group) => group.ParameterApplyStatus === "pending-reboot");
|
|
133
|
+
}
|
|
134
|
+
function instanceParameterGroupApplying(instance) {
|
|
135
|
+
return (instance?.DBParameterGroups ?? []).some((group) => group.ParameterApplyStatus === "applying");
|
|
136
|
+
}
|
|
137
|
+
function instanceHasPendingChanges(instance, caRotationRestarts) {
|
|
138
|
+
const pending = instance.PendingModifiedValues;
|
|
139
|
+
if (pending === void 0) return false;
|
|
140
|
+
return pending.DBInstanceClass !== void 0 || pending.AllocatedStorage !== void 0 || pending.AutomationMode !== void 0 || pending.BackupRetentionPeriod !== void 0 || pending.DBInstanceIdentifier !== void 0 || pending.DBSubnetGroupName !== void 0 || pending.Engine !== void 0 || pending.EngineVersion !== void 0 || pending.IAMDatabaseAuthenticationEnabled !== void 0 || pending.Iops !== void 0 || pending.LicenseModel !== void 0 || pending.MasterUserPassword !== void 0 || pending.MultiAZ !== void 0 || pending.PendingCloudwatchLogsExports !== void 0 || pending.Port !== void 0 || (pending.ProcessorFeatures ?? []).length > 0 || pending.ResumeFullAutomationModeTime !== void 0 || pending.StorageThroughput !== void 0 || pending.StorageType !== void 0 || caRotationRestarts && pending.CACertificateIdentifier !== void 0;
|
|
141
|
+
}
|
|
142
|
+
function instanceParameterGroupSettled(instance, sentGroup, caRotationRestarts) {
|
|
143
|
+
if (instance.DBInstanceStatus !== "available") return false;
|
|
144
|
+
if (instanceHasPendingChanges(instance, caRotationRestarts)) return false;
|
|
145
|
+
const groups = instance.DBParameterGroups ?? [];
|
|
146
|
+
if (groups.some((group) => group.ParameterApplyStatus === "applying")) return false;
|
|
147
|
+
if (sentGroup === void 0 || groups.length === 0) return true;
|
|
148
|
+
return groups.some((group) => group.DBParameterGroupName?.toLowerCase() === sentGroup.toLowerCase());
|
|
149
|
+
}
|
|
150
|
+
function instanceRebootApplied(instance) {
|
|
151
|
+
return instance.DBInstanceStatus === "available" && (instance.DBParameterGroups ?? []).every((group) => group.ParameterApplyStatus === "in-sync") && (instance.OptionGroupMemberships ?? []).every((group) => group.Status === "in-sync");
|
|
152
|
+
}
|
|
153
|
+
function logExportsList(value) {
|
|
154
|
+
if (value == null) return void 0;
|
|
155
|
+
if (!Array.isArray(value) || !value.every((type) => typeof type === "string")) return "malformed";
|
|
156
|
+
return [...new Set(value)];
|
|
157
|
+
}
|
|
158
|
+
function desiredLogExports(properties, resourceType, logicalId, physicalId) {
|
|
159
|
+
const desired = logExportsList(properties["EnableCloudwatchLogsExports"]);
|
|
160
|
+
if (desired !== "malformed") return desired;
|
|
161
|
+
throw markNonRetryable(new ProvisioningError(`${resourceType} ${logicalId}: EnableCloudwatchLogsExports must be a list of log type names`, resourceType, logicalId, physicalId));
|
|
162
|
+
}
|
|
163
|
+
function logExportsDelta(desired, previousValue) {
|
|
164
|
+
const previous = logExportsList(previousValue);
|
|
165
|
+
const had = previous === void 0 || previous === "malformed" ? [] : previous;
|
|
166
|
+
const want = desired ?? [];
|
|
167
|
+
const enable = want.filter((type) => !had.includes(type));
|
|
168
|
+
const disable = had.filter((type) => !want.includes(type));
|
|
169
|
+
if (enable.length === 0 && disable.length === 0) return void 0;
|
|
170
|
+
return {
|
|
171
|
+
...enable.length > 0 && { EnableLogTypes: enable },
|
|
172
|
+
...disable.length > 0 && { DisableLogTypes: disable }
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function liveLogExports(enabled, pending) {
|
|
176
|
+
const disabled = pending?.LogTypesToDisable ?? [];
|
|
177
|
+
const out = (enabled ?? []).filter((type) => !disabled.includes(type));
|
|
178
|
+
for (const type of pending?.LogTypesToEnable ?? []) if (!out.includes(type)) out.push(type);
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
function logExportsApplied(config, enabled, pending) {
|
|
182
|
+
if ((pending?.LogTypesToEnable?.length ?? 0) > 0) return false;
|
|
183
|
+
if ((pending?.LogTypesToDisable?.length ?? 0) > 0) return false;
|
|
184
|
+
const live = enabled ?? [];
|
|
185
|
+
return (config.EnableLogTypes ?? []).every((type) => live.includes(type)) && !(config.DisableLogTypes ?? []).some((type) => live.includes(type));
|
|
186
|
+
}
|
|
187
|
+
function instanceAvailabilityZoneKept(properties, previousProperties) {
|
|
188
|
+
const desired = properties["AvailabilityZone"];
|
|
189
|
+
if (desired === previousProperties["AvailabilityZone"]) return true;
|
|
190
|
+
return (desired == null || desired === "") && String(properties["MultiAZ"]) === "true";
|
|
191
|
+
}
|
|
192
|
+
function withPreviousAvailabilityZone(properties, previousProperties) {
|
|
193
|
+
const { AvailabilityZone: _desired, ...rest } = properties;
|
|
194
|
+
const previous = previousProperties["AvailabilityZone"];
|
|
195
|
+
return previous === void 0 ? rest : {
|
|
196
|
+
...rest,
|
|
197
|
+
AvailabilityZone: previous
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function serverlessV2ScalingToSend(config) {
|
|
201
|
+
const seconds = numberOrUndefined(config.SecondsUntilAutoPause);
|
|
202
|
+
return {
|
|
203
|
+
MinCapacity: config.MinCapacity,
|
|
204
|
+
MaxCapacity: config.MaxCapacity,
|
|
205
|
+
...seconds !== void 0 && { SecondsUntilAutoPause: seconds }
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const CLUSTER_BACKFILL_STRING_MEMBERS = [
|
|
209
|
+
"DBClusterParameterGroupName",
|
|
210
|
+
"PreferredBackupWindow",
|
|
211
|
+
"PreferredMaintenanceWindow",
|
|
212
|
+
"StorageType"
|
|
213
|
+
];
|
|
214
|
+
function clusterCreateBackfillMembers(properties) {
|
|
215
|
+
const members = {};
|
|
216
|
+
for (const key of CLUSTER_BACKFILL_STRING_MEMBERS) if (properties[key] != null) members[key] = properties[key];
|
|
217
|
+
if (properties["CopyTagsToSnapshot"] != null) members.CopyTagsToSnapshot = properties["CopyTagsToSnapshot"];
|
|
218
|
+
return members;
|
|
219
|
+
}
|
|
220
|
+
function clusterModifyBackfillMembers(properties, previousProperties, context) {
|
|
221
|
+
const changed = (key) => instanceValueChanged(properties, previousProperties, key, context);
|
|
222
|
+
const members = {};
|
|
223
|
+
for (const key of CLUSTER_BACKFILL_STRING_MEMBERS) if (changed(key)) members[key] = properties[key];
|
|
224
|
+
if (changed("CopyTagsToSnapshot")) members.CopyTagsToSnapshot = properties["CopyTagsToSnapshot"];
|
|
225
|
+
return members;
|
|
226
|
+
}
|
|
227
|
+
const CLUSTER_PERFORMANCE_INSIGHTS_MEMBERS = [
|
|
228
|
+
["PerformanceInsightsEnabled", "EnablePerformanceInsights"],
|
|
229
|
+
["PerformanceInsightsKmsKeyId", "PerformanceInsightsKMSKeyId"],
|
|
230
|
+
["PerformanceInsightsRetentionPeriod", "PerformanceInsightsRetentionPeriod"]
|
|
231
|
+
];
|
|
232
|
+
function clusterPerformanceInsightsMembers(properties) {
|
|
233
|
+
const members = {};
|
|
234
|
+
if (properties["PerformanceInsightsEnabled"] != null) members.EnablePerformanceInsights = properties["PerformanceInsightsEnabled"];
|
|
235
|
+
if (properties["PerformanceInsightsKmsKeyId"] != null) members.PerformanceInsightsKMSKeyId = properties["PerformanceInsightsKmsKeyId"];
|
|
236
|
+
const retention = retentionPeriodValue(properties["PerformanceInsightsRetentionPeriod"]);
|
|
237
|
+
if (typeof retention === "number") members.PerformanceInsightsRetentionPeriod = retention;
|
|
238
|
+
return members;
|
|
239
|
+
}
|
|
240
|
+
function clusterCreateScalarMembers(properties) {
|
|
241
|
+
return {
|
|
242
|
+
...properties["AutoMinorVersionUpgrade"] != null && { AutoMinorVersionUpgrade: properties["AutoMinorVersionUpgrade"] },
|
|
243
|
+
...properties["NetworkType"] != null && { NetworkType: properties["NetworkType"] },
|
|
244
|
+
...clusterPerformanceInsightsMembers(properties)
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function clusterModifyScalarMembers(properties, previousProperties, context) {
|
|
248
|
+
const changed = (key) => instanceValueChanged(properties, previousProperties, key, context);
|
|
249
|
+
const members = {};
|
|
250
|
+
if (changed("AutoMinorVersionUpgrade")) members.AutoMinorVersionUpgrade = properties["AutoMinorVersionUpgrade"];
|
|
251
|
+
if (changed("NetworkType")) members.NetworkType = properties["NetworkType"];
|
|
252
|
+
if (CLUSTER_PERFORMANCE_INSIGHTS_MEMBERS.some(([key]) => changed(key))) Object.assign(members, clusterPerformanceInsightsMembers(properties));
|
|
253
|
+
return members;
|
|
254
|
+
}
|
|
255
|
+
function httpEndpointFlag(value) {
|
|
256
|
+
if (value == null) return void 0;
|
|
257
|
+
if (value === true || value === "true") return true;
|
|
258
|
+
if (value === false || value === "false") return false;
|
|
259
|
+
return "malformed";
|
|
260
|
+
}
|
|
261
|
+
const CLUSTER_BOOLEAN_SCALARS = [
|
|
262
|
+
"AutoMinorVersionUpgrade",
|
|
263
|
+
"PerformanceInsightsEnabled",
|
|
264
|
+
"EnableHttpEndpoint"
|
|
265
|
+
];
|
|
266
|
+
function retentionPeriodValue(value) {
|
|
267
|
+
if (value == null) return void 0;
|
|
268
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" && /^\d+$/.test(value.trim()) ? Number(value) : NaN;
|
|
269
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : "malformed";
|
|
270
|
+
}
|
|
271
|
+
function clusterHttpEndpointAction(properties, previousProperties, resourceType, logicalId, physicalId, context) {
|
|
272
|
+
const desired = httpEndpointFlag(properties["EnableHttpEndpoint"]);
|
|
273
|
+
if (desired === "malformed") {
|
|
274
|
+
if (context?.replayingState === true || context?.desiredFromAwsReadback === true) return "skip";
|
|
275
|
+
throw markNonRetryable(new ProvisioningError(`${resourceType} ${logicalId}: EnableHttpEndpoint must be true or false`, resourceType, logicalId, physicalId));
|
|
276
|
+
}
|
|
277
|
+
const engine = properties["Engine"];
|
|
278
|
+
if (typeof engine !== "string") return void 0;
|
|
279
|
+
if (!["aurora-mysql", "aurora-postgresql"].includes(engine.toLowerCase())) return void 0;
|
|
280
|
+
const recorded = httpEndpointFlag(previousProperties["EnableHttpEndpoint"]);
|
|
281
|
+
if (desired === (recorded === "malformed" ? void 0 : recorded)) return void 0;
|
|
282
|
+
if (desired !== true) return "disable";
|
|
283
|
+
return context?.desiredFromAwsReadback === true ? "withheld" : "enable";
|
|
284
|
+
}
|
|
285
|
+
function withPreviousValues(properties, previousProperties, skipped) {
|
|
286
|
+
const out = { ...properties };
|
|
287
|
+
for (const [key, usable] of skipped) {
|
|
288
|
+
delete out[key];
|
|
289
|
+
const previous = previousProperties[key];
|
|
290
|
+
if (previous != null && usable(previous)) out[key] = previous;
|
|
291
|
+
}
|
|
292
|
+
return out;
|
|
293
|
+
}
|
|
294
|
+
const usableHttpEndpoint = (value) => typeof httpEndpointFlag(value) === "boolean";
|
|
295
|
+
const usableRetentionPeriod = (value) => typeof retentionPeriodValue(value) === "number";
|
|
296
|
+
function clusterEngineVersionChanged(properties, previousProperties, context) {
|
|
297
|
+
if (context?.replayingState === true) return false;
|
|
298
|
+
return instanceValueChanged(properties, previousProperties, "EngineVersion", context);
|
|
299
|
+
}
|
|
300
|
+
const SECURITY_GROUP_CHANGE_MEMBER_STATUSES = /* @__PURE__ */ new Set(["modifying"]);
|
|
301
|
+
const PORT_CHANGE_MEMBER_STATUSES = /* @__PURE__ */ new Set(["modifying", "rebooting"]);
|
|
302
|
+
function clusterPortToSend(properties, previousProperties) {
|
|
303
|
+
if (properties["Port"] != null) return Number(properties["Port"]);
|
|
304
|
+
if (previousProperties["Port"] == null) return void 0;
|
|
305
|
+
const engine = properties["Engine"] ?? previousProperties["Engine"];
|
|
306
|
+
const reset = typeof engine === "string" ? CLUSTER_REMOVED_PORT.get(engine.toLowerCase()) : void 0;
|
|
307
|
+
return reset === Number(previousProperties["Port"]) ? void 0 : reset;
|
|
308
|
+
}
|
|
309
|
+
const RDS_DELETE_WAIT_MS = Math.max(18e5, slowCcOperationTimeoutMs("AWS::RDS::DBCluster", "DELETE"), slowCcOperationTimeoutMs("AWS::RDS::DBInstance", "DELETE"));
|
|
310
|
+
function deleteAutomatedBackupsInput(properties) {
|
|
311
|
+
const value = properties?.["DeleteAutomatedBackups"];
|
|
312
|
+
if (value === true || value === "true") return { DeleteAutomatedBackups: true };
|
|
313
|
+
if (value === false || value === "false") return { DeleteAutomatedBackups: false };
|
|
314
|
+
return {};
|
|
315
|
+
}
|
|
316
|
+
function recordedGlobalClusterIdentifier(properties) {
|
|
317
|
+
const value = properties?.["GlobalClusterIdentifier"];
|
|
318
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
319
|
+
}
|
|
320
|
+
const HTTP_ENDPOINT_BUSY_FAULTS = /* @__PURE__ */ new Set(["InvalidResourceStateFault", "InvalidDBClusterStateFault"]);
|
|
321
|
+
const HTTP_ENDPOINT_MAX_ATTEMPTS = 8;
|
|
322
|
+
const TRANSIENT_DETACH_FAULTS = /* @__PURE__ */ new Set([
|
|
323
|
+
"InvalidDBClusterStateFault",
|
|
324
|
+
"InvalidGlobalClusterStateFault",
|
|
325
|
+
"ThrottlingException",
|
|
326
|
+
"Throttling",
|
|
327
|
+
"RequestLimitExceeded"
|
|
328
|
+
]);
|
|
329
|
+
function isDbIdentifier(id) {
|
|
330
|
+
return id.length <= 63 && /^[A-Za-z](?:-?[A-Za-z0-9])*$/.test(id);
|
|
331
|
+
}
|
|
332
|
+
function awsErrorName(error) {
|
|
333
|
+
return error?.name ?? "";
|
|
334
|
+
}
|
|
335
|
+
var GlobalClusterDetachError = class extends Error {
|
|
336
|
+
constructor(message) {
|
|
337
|
+
super(message);
|
|
338
|
+
this.name = "GlobalClusterDetachError";
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
var RDSProvider = class {
|
|
342
|
+
rdsClient;
|
|
343
|
+
createClient;
|
|
344
|
+
ec2Client;
|
|
345
|
+
providerRegion;
|
|
346
|
+
logger = getLogger().child("RDSProvider");
|
|
347
|
+
protectionFlips = new ProtectionFlipRegistry();
|
|
348
|
+
removalDefaults = /* @__PURE__ */ new Map();
|
|
349
|
+
handledProperties = /* @__PURE__ */ new Map([
|
|
350
|
+
["AWS::RDS::DBSubnetGroup", /* @__PURE__ */ new Set([
|
|
351
|
+
"DBSubnetGroupName",
|
|
352
|
+
"DBSubnetGroupDescription",
|
|
353
|
+
"SubnetIds",
|
|
354
|
+
"Tags"
|
|
355
|
+
])],
|
|
356
|
+
["AWS::RDS::DBCluster", /* @__PURE__ */ new Set([
|
|
357
|
+
"DBClusterIdentifier",
|
|
358
|
+
"Engine",
|
|
359
|
+
"EngineVersion",
|
|
360
|
+
"MasterUsername",
|
|
361
|
+
"MasterUserPassword",
|
|
362
|
+
"DatabaseName",
|
|
363
|
+
"Port",
|
|
364
|
+
"VpcSecurityGroupIds",
|
|
365
|
+
"DBSubnetGroupName",
|
|
366
|
+
"StorageEncrypted",
|
|
367
|
+
"KmsKeyId",
|
|
368
|
+
"BackupRetentionPeriod",
|
|
369
|
+
"DeletionProtection",
|
|
370
|
+
"ServerlessV2ScalingConfiguration",
|
|
371
|
+
"Tags",
|
|
372
|
+
"ManageMasterUserPassword",
|
|
373
|
+
"MasterUserSecret",
|
|
374
|
+
"MonitoringRoleArn",
|
|
375
|
+
"MonitoringInterval",
|
|
376
|
+
"EnableIAMDatabaseAuthentication",
|
|
377
|
+
"PubliclyAccessible",
|
|
378
|
+
"DBClusterParameterGroupName",
|
|
379
|
+
"PreferredBackupWindow",
|
|
380
|
+
"PreferredMaintenanceWindow",
|
|
381
|
+
"CopyTagsToSnapshot",
|
|
382
|
+
"StorageType",
|
|
383
|
+
"EnableCloudwatchLogsExports",
|
|
384
|
+
"AutoMinorVersionUpgrade",
|
|
385
|
+
"NetworkType",
|
|
386
|
+
"PerformanceInsightsEnabled",
|
|
387
|
+
"PerformanceInsightsKmsKeyId",
|
|
388
|
+
"PerformanceInsightsRetentionPeriod",
|
|
389
|
+
"EnableHttpEndpoint"
|
|
390
|
+
])],
|
|
391
|
+
["AWS::RDS::DBInstance", /* @__PURE__ */ new Set([
|
|
392
|
+
"DBInstanceIdentifier",
|
|
393
|
+
"DBInstanceClass",
|
|
394
|
+
"Engine",
|
|
395
|
+
"DBClusterIdentifier",
|
|
396
|
+
"DBSubnetGroupName",
|
|
397
|
+
"PubliclyAccessible",
|
|
398
|
+
"Tags",
|
|
399
|
+
"AllocatedStorage",
|
|
400
|
+
"DeletionProtection",
|
|
401
|
+
"EngineVersion",
|
|
402
|
+
"MasterUsername",
|
|
403
|
+
"MasterUserPassword",
|
|
404
|
+
"Port",
|
|
405
|
+
"StorageEncrypted",
|
|
406
|
+
"VPCSecurityGroups",
|
|
407
|
+
"KmsKeyId",
|
|
408
|
+
"MasterUserSecret",
|
|
409
|
+
"ManageMasterUserPassword",
|
|
410
|
+
"MonitoringRoleArn",
|
|
411
|
+
"MonitoringInterval",
|
|
412
|
+
"EnableIAMDatabaseAuthentication",
|
|
413
|
+
"BackupRetentionPeriod",
|
|
414
|
+
"PreferredBackupWindow",
|
|
415
|
+
"PreferredMaintenanceWindow",
|
|
416
|
+
"StorageType",
|
|
417
|
+
"MultiAZ",
|
|
418
|
+
"MaxAllocatedStorage",
|
|
419
|
+
"AutoMinorVersionUpgrade",
|
|
420
|
+
"CopyTagsToSnapshot",
|
|
421
|
+
"EnablePerformanceInsights",
|
|
422
|
+
"PerformanceInsightsKMSKeyId",
|
|
423
|
+
"PerformanceInsightsRetentionPeriod",
|
|
424
|
+
"Iops",
|
|
425
|
+
"StorageThroughput",
|
|
426
|
+
"OptionGroupName",
|
|
427
|
+
"CACertificateIdentifier",
|
|
428
|
+
"NetworkType",
|
|
429
|
+
"LicenseModel",
|
|
430
|
+
"PromotionTier",
|
|
431
|
+
"DatabaseInsightsMode",
|
|
432
|
+
"DedicatedLogVolume",
|
|
433
|
+
"AllowMajorVersionUpgrade",
|
|
434
|
+
"CertificateRotationRestart",
|
|
435
|
+
"DBParameterGroupName",
|
|
436
|
+
"EnableCloudwatchLogsExports",
|
|
437
|
+
"DBName",
|
|
438
|
+
"AvailabilityZone"
|
|
439
|
+
])]
|
|
440
|
+
]);
|
|
441
|
+
unhandledByDesign = /* @__PURE__ */ new Map([["AWS::RDS::DBCluster", /* @__PURE__ */ new Map([["DeleteAutomatedBackups", "delete-time flag, sent on the delete when the record carries it; kept unhandled so a template declaring it keeps its Cloud Control route. Final snapshots are governed by DeletionPolicy: Snapshot, which this flag does not control"]])], ["AWS::RDS::DBInstance", /* @__PURE__ */ new Map([
|
|
442
|
+
["DBSecurityGroups", "EC2-Classic-only feature retired by AWS (2022-08-15); new accounts cannot use this — use VPCSecurityGroups instead"],
|
|
443
|
+
["ApplyImmediately", "cdkd always applies modifications immediately (rds:ModifyDBInstance.ApplyImmediately=true is hardcoded); users wanting maintenance-window deferral should run aws rds modify-db-instance directly"],
|
|
444
|
+
["DeleteAutomatedBackups", "delete-time flag, sent on the delete when the record carries it; kept unhandled so a template declaring it keeps its Cloud Control route. Final snapshots are governed by DeletionPolicy: Snapshot, which this flag does not control"]
|
|
445
|
+
])]]);
|
|
446
|
+
constructor(options) {
|
|
447
|
+
this.providerRegion = options?.region ?? ambientRegion();
|
|
448
|
+
}
|
|
449
|
+
getClient() {
|
|
450
|
+
if (!this.rdsClient) {
|
|
451
|
+
this.rdsClient = new RDSClient({
|
|
452
|
+
...ambientClientDefaults(),
|
|
453
|
+
...this.providerRegion ? { region: this.providerRegion } : {}
|
|
454
|
+
});
|
|
455
|
+
this.createClient = withoutServerErrorRetries(new RDSClient({
|
|
456
|
+
...ambientClientDefaults(),
|
|
457
|
+
...this.providerRegion ? { region: this.providerRegion } : {}
|
|
458
|
+
}));
|
|
459
|
+
}
|
|
460
|
+
return this.rdsClient;
|
|
461
|
+
}
|
|
462
|
+
getEc2Client() {
|
|
463
|
+
if (!this.ec2Client) this.ec2Client = new EC2Client({
|
|
464
|
+
...ambientClientDefaults(),
|
|
465
|
+
...this.providerRegion ? { region: this.providerRegion } : {}
|
|
466
|
+
});
|
|
467
|
+
return this.ec2Client;
|
|
468
|
+
}
|
|
469
|
+
getCreateClient() {
|
|
470
|
+
this.getClient();
|
|
471
|
+
return this.createClient;
|
|
472
|
+
}
|
|
473
|
+
async create(logicalId, resourceType, properties, context) {
|
|
474
|
+
const mask = context?.maskSecrets;
|
|
475
|
+
switch (resourceType) {
|
|
476
|
+
case "AWS::RDS::DBSubnetGroup": return this.createDBSubnetGroup(logicalId, resourceType, properties, mask);
|
|
477
|
+
case "AWS::RDS::DBCluster": return this.createDBCluster(logicalId, resourceType, properties, mask, context?.replayingState === true);
|
|
478
|
+
case "AWS::RDS::DBInstance": return this.createDBInstance(logicalId, resourceType, properties, mask);
|
|
479
|
+
default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
async update(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
483
|
+
properties = withRemovalDefaults(this.removalDefaults, resourceType, properties, previousProperties, context);
|
|
484
|
+
switch (resourceType) {
|
|
485
|
+
case "AWS::RDS::DBSubnetGroup": return this.updateDBSubnetGroup(logicalId, physicalId, resourceType, properties, previousProperties, context?.maskSecrets);
|
|
486
|
+
case "AWS::RDS::DBCluster": return this.updateDBCluster(logicalId, physicalId, resourceType, properties, previousProperties, context);
|
|
487
|
+
case "AWS::RDS::DBInstance": return this.updateDBInstance(logicalId, physicalId, resourceType, properties, previousProperties, context);
|
|
488
|
+
default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId, physicalId);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
async delete(logicalId, physicalId, resourceType, properties, context) {
|
|
492
|
+
switch (resourceType) {
|
|
493
|
+
case "AWS::RDS::DBSubnetGroup": return this.deleteDBSubnetGroup(logicalId, physicalId, resourceType, context);
|
|
494
|
+
case "AWS::RDS::DBCluster": return this.deleteDBCluster(logicalId, physicalId, resourceType, properties, context);
|
|
495
|
+
case "AWS::RDS::DBInstance": return this.deleteDBInstance(logicalId, physicalId, resourceType, properties, context);
|
|
496
|
+
default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId, physicalId);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async createDBSubnetGroup(logicalId, resourceType, properties, mask) {
|
|
500
|
+
this.logger.debug(`Creating DBSubnetGroup ${logicalId}`);
|
|
501
|
+
const tags = refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId);
|
|
502
|
+
const dbSubnetGroupName = properties["DBSubnetGroupName"] || generatedNameFor("AWS::RDS::DBSubnetGroup", logicalId);
|
|
503
|
+
try {
|
|
504
|
+
const created = await this.getCreateClient().send(new CreateDBSubnetGroupCommand({
|
|
505
|
+
DBSubnetGroupName: dbSubnetGroupName,
|
|
506
|
+
DBSubnetGroupDescription: properties["DBSubnetGroupDescription"] || `Subnet group for ${logicalId}`,
|
|
507
|
+
SubnetIds: properties["SubnetIds"],
|
|
508
|
+
...tags.length > 0 && { Tags: tags }
|
|
509
|
+
}));
|
|
510
|
+
this.logger.debug(`Successfully created DBSubnetGroup ${logicalId}: ${dbSubnetGroupName}`);
|
|
511
|
+
const dbSubnetGroupArn = created.DBSubnetGroup?.DBSubnetGroupArn;
|
|
512
|
+
return {
|
|
513
|
+
physicalId: dbSubnetGroupName,
|
|
514
|
+
attributes: {
|
|
515
|
+
DBSubnetGroupName: dbSubnetGroupName,
|
|
516
|
+
...dbSubnetGroupArn !== void 0 && { DBSubnetGroupArn: dbSubnetGroupArn }
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
} catch (error) {
|
|
520
|
+
const cause = error instanceof Error ? error : void 0;
|
|
521
|
+
throw this.wrapCaughtError(mask, error, (text) => new ProvisioningError(`Failed to create DBSubnetGroup ${logicalId}: ${text}`, resourceType, logicalId, dbSubnetGroupName, cause));
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
async updateDBSubnetGroup(logicalId, physicalId, resourceType, properties, previousProperties, mask) {
|
|
525
|
+
this.logger.debug(`Updating DBSubnetGroup ${logicalId}: ${physicalId}`);
|
|
526
|
+
refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId, physicalId);
|
|
527
|
+
try {
|
|
528
|
+
const subnetIds = properties["SubnetIds"];
|
|
529
|
+
const sendSubnetIds = subnetIds !== void 0 && subnetIds.length > 0;
|
|
530
|
+
const modifyInput = {
|
|
531
|
+
DBSubnetGroupName: physicalId,
|
|
532
|
+
DBSubnetGroupDescription: properties["DBSubnetGroupDescription"],
|
|
533
|
+
...sendSubnetIds && { SubnetIds: subnetIds }
|
|
534
|
+
};
|
|
535
|
+
await this.getClient().send(new ModifyDBSubnetGroupCommand(modifyInput));
|
|
536
|
+
const arn = (await this.getClient().send(new DescribeDBSubnetGroupsCommand({ DBSubnetGroupName: physicalId }))).DBSubnetGroups?.[0]?.DBSubnetGroupArn;
|
|
537
|
+
if (arn) await this.applyTagDiff(arn, resourceType, logicalId, previousProperties["Tags"], properties["Tags"]);
|
|
538
|
+
this.logger.debug(`Successfully updated DBSubnetGroup ${logicalId}`);
|
|
539
|
+
return {
|
|
540
|
+
physicalId,
|
|
541
|
+
wasReplaced: false,
|
|
542
|
+
attributes: {
|
|
543
|
+
DBSubnetGroupName: physicalId,
|
|
544
|
+
...arn !== void 0 && { DBSubnetGroupArn: arn }
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
} catch (error) {
|
|
548
|
+
const cause = error instanceof Error ? error : void 0;
|
|
549
|
+
throw this.wrapCaughtError(mask, error, (text) => new ProvisioningError(`Failed to update DBSubnetGroup ${logicalId}: ${text}`, resourceType, logicalId, physicalId, cause));
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
async deleteDBSubnetGroup(logicalId, physicalId, resourceType, context) {
|
|
553
|
+
this.logger.debug(`Deleting DBSubnetGroup ${logicalId}: ${physicalId}`);
|
|
554
|
+
try {
|
|
555
|
+
await this.getClient().send(new DeleteDBSubnetGroupCommand({ DBSubnetGroupName: physicalId }));
|
|
556
|
+
this.logger.debug(`Successfully deleted DBSubnetGroup ${logicalId}`);
|
|
557
|
+
} catch (error) {
|
|
558
|
+
if (this.isNotFoundError(error, "DBSubnetGroupNotFoundFault")) {
|
|
559
|
+
const clientRegion = await this.getClient().config.region();
|
|
560
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
561
|
+
this.logger.debug(`DBSubnetGroup ${physicalId} does not exist, skipping deletion`);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
const cause = error instanceof Error ? error : void 0;
|
|
565
|
+
throw this.wrapDeleteError(context?.maskSecrets, error, (text) => new ProvisioningError(`Failed to delete DBSubnetGroup ${logicalId}: ${text}`, resourceType, logicalId, physicalId, cause));
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
async createDBCluster(logicalId, resourceType, properties, mask, replaying = false) {
|
|
569
|
+
this.logger.debug(`Creating DBCluster ${logicalId}`);
|
|
570
|
+
const tags = refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId);
|
|
571
|
+
let httpEndpoint = httpEndpointFlag(properties["EnableHttpEndpoint"]);
|
|
572
|
+
if (httpEndpoint === "malformed") {
|
|
573
|
+
if (!replaying) throw markNonRetryable(new ProvisioningError(`${resourceType} ${logicalId}: EnableHttpEndpoint must be true or false`, resourceType, logicalId));
|
|
574
|
+
this.logger.warn(safeMsg`DBCluster ${logicalId}: the replayed EnableHttpEndpoint is not true or false; the cluster is created with the Data API off`);
|
|
575
|
+
httpEndpoint = void 0;
|
|
576
|
+
}
|
|
577
|
+
const retentionSkipped = retentionPeriodValue(properties["PerformanceInsightsRetentionPeriod"]) === "malformed";
|
|
578
|
+
if (retentionSkipped) {
|
|
579
|
+
if (!replaying) throw markNonRetryable(new ProvisioningError(`${resourceType} ${logicalId}: PerformanceInsightsRetentionPeriod must be a whole number of days`, resourceType, logicalId));
|
|
580
|
+
this.logger.warn(safeMsg`DBCluster ${logicalId}: the replayed PerformanceInsightsRetentionPeriod is not a whole number of days; the cluster is created with AWS's default retention`);
|
|
581
|
+
}
|
|
582
|
+
const skippedKeys = [...properties["EnableHttpEndpoint"] != null && httpEndpoint === void 0 ? ["EnableHttpEndpoint"] : [], ...retentionSkipped ? ["PerformanceInsightsRetentionPeriod"] : []];
|
|
583
|
+
const effectiveProperties = skippedKeys.length > 0 ? Object.fromEntries(Object.entries(properties).filter(([key]) => !skippedKeys.includes(key))) : void 0;
|
|
584
|
+
const logExports = desiredLogExports(properties, resourceType, logicalId);
|
|
585
|
+
const dbClusterIdentifier = properties["DBClusterIdentifier"] || generatedNameFor("AWS::RDS::DBCluster", logicalId);
|
|
586
|
+
let clusterLeftBehind = false;
|
|
587
|
+
let createdResourceId;
|
|
588
|
+
try {
|
|
589
|
+
const serverlessV2Config = properties["ServerlessV2ScalingConfiguration"];
|
|
590
|
+
const masterUserSecret = properties["MasterUserSecret"];
|
|
591
|
+
const response = await this.getCreateClient().send(new CreateDBClusterCommand({
|
|
592
|
+
DBClusterIdentifier: dbClusterIdentifier,
|
|
593
|
+
Engine: properties["Engine"],
|
|
594
|
+
EngineVersion: properties["EngineVersion"],
|
|
595
|
+
MasterUsername: properties["MasterUsername"],
|
|
596
|
+
MasterUserPassword: properties["MasterUserPassword"],
|
|
597
|
+
DatabaseName: properties["DatabaseName"],
|
|
598
|
+
Port: properties["Port"] != null ? Number(properties["Port"]) : void 0,
|
|
599
|
+
VpcSecurityGroupIds: properties["VpcSecurityGroupIds"],
|
|
600
|
+
DBSubnetGroupName: properties["DBSubnetGroupName"],
|
|
601
|
+
StorageEncrypted: properties["StorageEncrypted"],
|
|
602
|
+
KmsKeyId: properties["KmsKeyId"],
|
|
603
|
+
BackupRetentionPeriod: properties["BackupRetentionPeriod"] != null ? Number(properties["BackupRetentionPeriod"]) : void 0,
|
|
604
|
+
DeletionProtection: properties["DeletionProtection"],
|
|
605
|
+
...properties["ManageMasterUserPassword"] !== void 0 && { ManageMasterUserPassword: properties["ManageMasterUserPassword"] },
|
|
606
|
+
...masterUserSecret?.KmsKeyId !== void 0 && { MasterUserSecretKmsKeyId: masterUserSecret.KmsKeyId },
|
|
607
|
+
...properties["MonitoringRoleArn"] !== void 0 && { MonitoringRoleArn: properties["MonitoringRoleArn"] },
|
|
608
|
+
...properties["MonitoringInterval"] !== void 0 && { MonitoringInterval: Number(properties["MonitoringInterval"]) },
|
|
609
|
+
...properties["EnableIAMDatabaseAuthentication"] !== void 0 && { EnableIAMDatabaseAuthentication: properties["EnableIAMDatabaseAuthentication"] },
|
|
610
|
+
...properties["PubliclyAccessible"] !== void 0 && { PubliclyAccessible: properties["PubliclyAccessible"] },
|
|
611
|
+
...serverlessV2Config && { ServerlessV2ScalingConfiguration: serverlessV2ScalingToSend(serverlessV2Config) },
|
|
612
|
+
...clusterCreateBackfillMembers(properties),
|
|
613
|
+
...logExports !== void 0 && logExports.length > 0 && { EnableCloudwatchLogsExports: logExports },
|
|
614
|
+
...clusterCreateScalarMembers(properties),
|
|
615
|
+
...httpEndpoint !== void 0 && { EnableHttpEndpoint: httpEndpoint },
|
|
616
|
+
...tags.length > 0 && { Tags: tags }
|
|
617
|
+
}));
|
|
618
|
+
clusterLeftBehind = true;
|
|
619
|
+
createdResourceId = response.DBCluster?.DbClusterResourceId;
|
|
620
|
+
reportCreatedResource(logicalId, resourceType, dbClusterIdentifier, createdResourceId);
|
|
621
|
+
if (!response.DBCluster) throw new Error("CreateDBCluster did not return DBCluster");
|
|
622
|
+
this.logger.debug(`Successfully created DBCluster ${logicalId}: ${dbClusterIdentifier}`);
|
|
623
|
+
const wantsDeletionProtection = properties["DeletionProtection"] === true;
|
|
624
|
+
try {
|
|
625
|
+
if (process.env["CDKD_NO_WAIT"] !== "true") await this.waitForClusterAvailable(logicalId, dbClusterIdentifier);
|
|
626
|
+
const described = await this.describeDBCluster(dbClusterIdentifier);
|
|
627
|
+
return {
|
|
628
|
+
physicalId: dbClusterIdentifier,
|
|
629
|
+
attributes: definedAttributes({
|
|
630
|
+
"Endpoint.Address": described?.Endpoint,
|
|
631
|
+
"Endpoint.Port": stringifyIfAssigned(described?.Port),
|
|
632
|
+
"ReadEndpoint.Address": described?.ReaderEndpoint,
|
|
633
|
+
Arn: described?.DBClusterArn,
|
|
634
|
+
DBClusterResourceId: described?.DbClusterResourceId
|
|
635
|
+
}),
|
|
636
|
+
...effectiveProperties !== void 0 && { effectiveProperties }
|
|
637
|
+
};
|
|
638
|
+
} catch (innerError) {
|
|
639
|
+
try {
|
|
640
|
+
if (wantsDeletionProtection) try {
|
|
641
|
+
await this.getClient().send(new ModifyDBClusterCommand({
|
|
642
|
+
DBClusterIdentifier: dbClusterIdentifier,
|
|
643
|
+
DeletionProtection: false,
|
|
644
|
+
ApplyImmediately: true
|
|
645
|
+
}));
|
|
646
|
+
} catch (disableError) {
|
|
647
|
+
this.logger.debug(maskerOrIdentity(mask)(`Could not disable DeletionProtection on partially-created DBCluster ${dbClusterIdentifier}: ${describeAwsFailure(disableError).detail} (proceeding with DeleteDBCluster anyway)`));
|
|
648
|
+
}
|
|
649
|
+
await this.getClient().send(new DeleteDBClusterCommand({
|
|
650
|
+
DBClusterIdentifier: dbClusterIdentifier,
|
|
651
|
+
SkipFinalSnapshot: true
|
|
652
|
+
}));
|
|
653
|
+
clusterLeftBehind = false;
|
|
654
|
+
retractCreatedResource(logicalId, resourceType, dbClusterIdentifier);
|
|
655
|
+
this.logger.debug(`Delete requested for partially-created DBCluster ${logicalId} (${dbClusterIdentifier}) after wiring failure (not waiting for deleted state)`);
|
|
656
|
+
} catch (cleanupError) {
|
|
657
|
+
const retiring = await this.partialClusterRetiring(dbClusterIdentifier);
|
|
658
|
+
if (retiring !== void 0) {
|
|
659
|
+
this.logger.debug(maskerOrIdentity(mask)(`Cleanup delete of partially-created DBCluster ${logicalId} refused: ${maskerOrIdentity(mask)(describeAwsFailure(cleanupError).detail)}`));
|
|
660
|
+
this.logger.warn(maskerOrIdentity(mask)(`Partially-created DBCluster ${logicalId} (${dbClusterIdentifier}) is ${retiring === "gone" ? "read as already deleted" : "already in status 'deleting'"}; cdkd did not delete it again. Outside a rollback's re-create, the failed deploy's rollback journal still records it, so \`cdkd rollback --revert-failed\` confirms it is gone`));
|
|
661
|
+
throw innerError;
|
|
662
|
+
}
|
|
663
|
+
const aws = pasteableAwsCommand();
|
|
664
|
+
const unprotect = wantsDeletionProtection ? aws`aws rds modify-db-cluster --db-cluster-identifier ${dbClusterIdentifier} --no-deletion-protection --apply-immediately; ` : aws``;
|
|
665
|
+
const m = maskerOrIdentity(mask);
|
|
666
|
+
const cleanupFailure = describeAwsFailure(cleanupError);
|
|
667
|
+
const cleanupHead = `Failed to delete partially-created DBCluster ${logicalId} (${dbClusterIdentifier}): `;
|
|
668
|
+
if (cleanupFailure.redacted) this.logger.debug(m(`${cleanupHead}${m(cleanupFailure.detail)}`));
|
|
669
|
+
this.logger.warn(`${cleanupHead}${cleanupFailure.redacted ? `${m(cleanupFailure.summary)} ` : `${m(cleanupFailure.detail)}. `}THE CLUSTER IS STILL RUNNING AND BILLING. Outside a rollback's re-create, the failed deploy's rollback journal records it for \`cdkd rollback --revert-failed\`${wantsDeletionProtection ? ", which cannot delete it while its DeletionProtection is on" : ""}; otherwise delete it yourself: ${aws`${unprotect}aws rds delete-db-cluster --db-cluster-identifier ${dbClusterIdentifier} --skip-final-snapshot`.render()}`);
|
|
670
|
+
}
|
|
671
|
+
throw innerError;
|
|
672
|
+
}
|
|
673
|
+
} catch (error) {
|
|
674
|
+
const thrown = error instanceof ProvisioningError ? error : this.wrapCaughtError(mask, error, (text) => new ProvisioningError(`Failed to create DBCluster ${logicalId}: ${text}`, resourceType, logicalId, dbClusterIdentifier, error instanceof Error ? error : void 0));
|
|
675
|
+
if (clusterLeftBehind) markCreatedBeforeFailure(thrown, logicalId, resourceType, dbClusterIdentifier, createdResourceId);
|
|
676
|
+
throw thrown;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
async updateDBCluster(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
680
|
+
this.logger.debug(`Updating DBCluster ${logicalId}: ${physicalId}`);
|
|
681
|
+
refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId, physicalId);
|
|
682
|
+
const logExportConfig = logExportsDelta(desiredLogExports(properties, resourceType, logicalId, physicalId), previousProperties["EnableCloudwatchLogsExports"]);
|
|
683
|
+
const httpEndpointAction = clusterHttpEndpointAction(properties, previousProperties, resourceType, logicalId, physicalId, context);
|
|
684
|
+
if (httpEndpointAction === "skip") this.logger.warn(safeMsg`DBCluster ${logicalId}: the replayed EnableHttpEndpoint is not true or false; the Data API setting is left as it is`);
|
|
685
|
+
if (httpEndpointAction === "withheld") this.logger.warn(safeMsg`DBCluster ${logicalId}: drift --revert does not turn the Data API (EnableHttpEndpoint) back on; if the template declares it, turn it on with aws rds enable-http-endpoint`);
|
|
686
|
+
const skippedKeys = httpEndpointAction === "skip" || httpEndpointAction === "withheld" ? [["EnableHttpEndpoint", usableHttpEndpoint]] : [];
|
|
687
|
+
if (retentionPeriodValue(properties["PerformanceInsightsRetentionPeriod"]) === "malformed") {
|
|
688
|
+
if (context?.replayingState !== true && context?.desiredFromAwsReadback !== true) throw markNonRetryable(new ProvisioningError(`${resourceType} ${logicalId}: PerformanceInsightsRetentionPeriod must be a whole number of days`, resourceType, logicalId, physicalId));
|
|
689
|
+
this.logger.warn(safeMsg`DBCluster ${logicalId}: the replayed PerformanceInsightsRetentionPeriod is not a whole number of days; the retention period is left as it is`);
|
|
690
|
+
skippedKeys.push(["PerformanceInsightsRetentionPeriod", usableRetentionPeriod]);
|
|
691
|
+
}
|
|
692
|
+
try {
|
|
693
|
+
const vpcSgIds = await this.securityGroupsToSend("DBCluster", logicalId, physicalId, resourceType, properties, previousProperties, context);
|
|
694
|
+
const serverlessV2Config = properties["ServerlessV2ScalingConfiguration"];
|
|
695
|
+
const hasServerlessV2 = serverlessV2Config !== void 0 && (serverlessV2Config.MinCapacity !== void 0 || serverlessV2Config.MaxCapacity !== void 0 || serverlessV2Config.SecondsUntilAutoPause !== void 0);
|
|
696
|
+
const masterUserSecret = properties["MasterUserSecret"];
|
|
697
|
+
const sendCredentialSettings = masterCredentialSettingsChanged(properties, previousProperties, context);
|
|
698
|
+
const manageMasterUserPasswordOff = managedPasswordOnRemoval(properties, previousProperties);
|
|
699
|
+
const portToSend = clusterPortToSend(properties, previousProperties);
|
|
700
|
+
const engineVersionChanged = clusterEngineVersionChanged(properties, previousProperties, context);
|
|
701
|
+
await this.getClient().send(new ModifyDBClusterCommand({
|
|
702
|
+
DBClusterIdentifier: physicalId,
|
|
703
|
+
...engineVersionChanged && {
|
|
704
|
+
EngineVersion: properties["EngineVersion"],
|
|
705
|
+
AllowMajorVersionUpgrade: true
|
|
706
|
+
},
|
|
707
|
+
DeletionProtection: properties["DeletionProtection"],
|
|
708
|
+
BackupRetentionPeriod: clearOnUpdateRemoval(properties["BackupRetentionPeriod"] != null ? Number(properties["BackupRetentionPeriod"]) : void 0, previousProperties["BackupRetentionPeriod"] != null ? Number(previousProperties["BackupRetentionPeriod"]) : void 0, 1),
|
|
709
|
+
...vpcSgIds !== void 0 && { VpcSecurityGroupIds: vpcSgIds },
|
|
710
|
+
MasterUserPassword: masterUserPasswordToSend(properties, previousProperties, context),
|
|
711
|
+
Port: portToSend,
|
|
712
|
+
...sendCredentialSettings && properties["ManageMasterUserPassword"] !== void 0 && { ManageMasterUserPassword: properties["ManageMasterUserPassword"] },
|
|
713
|
+
...manageMasterUserPasswordOff && { ManageMasterUserPassword: false },
|
|
714
|
+
...sendCredentialSettings && masterUserSecret?.KmsKeyId !== void 0 && { MasterUserSecretKmsKeyId: masterUserSecret.KmsKeyId },
|
|
715
|
+
...properties["MonitoringRoleArn"] !== void 0 && { MonitoringRoleArn: properties["MonitoringRoleArn"] },
|
|
716
|
+
...properties["MonitoringInterval"] != null && { MonitoringInterval: Number(properties["MonitoringInterval"]) },
|
|
717
|
+
EnableIAMDatabaseAuthentication: properties["EnableIAMDatabaseAuthentication"],
|
|
718
|
+
...hasServerlessV2 && { ServerlessV2ScalingConfiguration: serverlessV2ScalingToSend(serverlessV2Config) },
|
|
719
|
+
...clusterModifyBackfillMembers(properties, previousProperties, context),
|
|
720
|
+
...clusterModifyScalarMembers(properties, previousProperties, context),
|
|
721
|
+
...logExportConfig !== void 0 && { CloudwatchLogsExportConfiguration: logExportConfig },
|
|
722
|
+
ApplyImmediately: true
|
|
723
|
+
}));
|
|
724
|
+
this.logger.debug(`Successfully updated DBCluster ${logicalId}`);
|
|
725
|
+
const noWait = process.env["CDKD_NO_WAIT"] === "true";
|
|
726
|
+
const portChanged = portToSend !== void 0 && portToSend !== numberOrUndefined(previousProperties["Port"]);
|
|
727
|
+
if (!noWait && portChanged) await this.waitForClusterPortApplied(physicalId, logicalId, portToSend);
|
|
728
|
+
if (!noWait && logExportConfig !== void 0) await this.waitForClusterState(logicalId, physicalId, (cluster) => logExportsApplied(logExportConfig, cluster.EnabledCloudwatchLogsExports, cluster.PendingModifiedValues?.PendingCloudwatchLogsExports), "CloudWatch Logs exports");
|
|
729
|
+
const described = await this.describeDBCluster(physicalId);
|
|
730
|
+
if (httpEndpointAction === "enable" || httpEndpointAction === "disable") {
|
|
731
|
+
const arn = described?.DBClusterArn;
|
|
732
|
+
if (!arn) throw new Error(`DescribeDBClusters did not report the cluster ARN, which ${httpEndpointAction === "enable" ? "EnableHttpEndpoint" : "DisableHttpEndpoint"} needs`);
|
|
733
|
+
const enable = httpEndpointAction === "enable";
|
|
734
|
+
await this.sendHttpEndpointChange(logicalId, physicalId, arn, enable);
|
|
735
|
+
if (!noWait) await this.waitForClusterState(logicalId, physicalId, (cluster) => (cluster.HttpEndpointEnabled ?? false) === enable, "HTTP endpoint (Data API) setting");
|
|
736
|
+
}
|
|
737
|
+
if (!noWait && portChanged) {
|
|
738
|
+
for (const member of described?.DBClusterMembers ?? []) if (member.DBInstanceIdentifier) await this.waitForMemberAfterClusterChange(member.DBInstanceIdentifier, logicalId, PORT_CHANGE_MEMBER_STATUSES, "port");
|
|
739
|
+
}
|
|
740
|
+
if (!noWait && securityGroupListChanged(vpcSgIds, previousProperties["VpcSecurityGroupIds"])) {
|
|
741
|
+
await this.waitForSecurityGroupsApplied(physicalId, logicalId, vpcSgIds ?? []);
|
|
742
|
+
for (const member of described?.DBClusterMembers ?? []) if (member.DBInstanceIdentifier) await this.waitForMemberAfterClusterChange(member.DBInstanceIdentifier, logicalId, SECURITY_GROUP_CHANGE_MEMBER_STATUSES, "VPC security groups");
|
|
743
|
+
}
|
|
744
|
+
if (described?.DBClusterArn) await this.applyTagDiff(described.DBClusterArn, resourceType, logicalId, previousProperties["Tags"], properties["Tags"]);
|
|
745
|
+
return {
|
|
746
|
+
physicalId,
|
|
747
|
+
wasReplaced: false,
|
|
748
|
+
attributes: definedAttributes({
|
|
749
|
+
"Endpoint.Address": described?.Endpoint,
|
|
750
|
+
"Endpoint.Port": stringifyIfAssigned(noWait && portChanged && described !== void 0 ? portToSend : described?.Port),
|
|
751
|
+
"ReadEndpoint.Address": described?.ReaderEndpoint,
|
|
752
|
+
Arn: described?.DBClusterArn,
|
|
753
|
+
DBClusterResourceId: described?.DbClusterResourceId
|
|
754
|
+
}),
|
|
755
|
+
...skippedKeys.length > 0 && { effectiveProperties: withPreviousValues(properties, previousProperties, skippedKeys) }
|
|
756
|
+
};
|
|
757
|
+
} catch (error) {
|
|
758
|
+
if (error instanceof ProvisioningError) throw error;
|
|
759
|
+
const cause = error instanceof Error ? error : void 0;
|
|
760
|
+
throw this.wrapCaughtError(context?.maskSecrets, error, (text) => new ProvisioningError(`Failed to update DBCluster ${logicalId}: ${text}`, resourceType, logicalId, physicalId, cause));
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
async deleteDBCluster(logicalId, physicalId, resourceType, properties, context) {
|
|
764
|
+
await deleteWithProtectionCompensation({
|
|
765
|
+
registry: this.protectionFlips,
|
|
766
|
+
key: protectionFlipKey(resourceType, physicalId, context?.expectedRegion),
|
|
767
|
+
run: (flip) => this.deleteDBClusterOnce(logicalId, physicalId, resourceType, properties, context, flip),
|
|
768
|
+
compensation: {
|
|
769
|
+
logicalId,
|
|
770
|
+
physicalId,
|
|
771
|
+
logger: this.logger,
|
|
772
|
+
site: rdsFamilyProtectionSite({
|
|
773
|
+
cliService: "rds",
|
|
774
|
+
serviceLabel: "RDS",
|
|
775
|
+
kind: "cluster",
|
|
776
|
+
physicalId,
|
|
777
|
+
region: context?.expectedRegion,
|
|
778
|
+
notFoundFault: "DBClusterNotFoundFault",
|
|
779
|
+
isNotFound: (error) => this.isNotFoundError(error, "DBClusterNotFoundFault")
|
|
780
|
+
}),
|
|
781
|
+
reEnable: async () => {
|
|
782
|
+
await this.getClient().send(new ModifyDBClusterCommand({
|
|
783
|
+
DBClusterIdentifier: physicalId,
|
|
784
|
+
DeletionProtection: true,
|
|
785
|
+
ApplyImmediately: true
|
|
786
|
+
}));
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
async deleteDBClusterOnce(logicalId, physicalId, resourceType, properties, context, flip) {
|
|
792
|
+
this.logger.debug(`Deleting DBCluster ${logicalId}`);
|
|
793
|
+
let detachedFrom;
|
|
794
|
+
const progress = { stripped: false };
|
|
795
|
+
try {
|
|
796
|
+
if (context?.removeProtection === true) try {
|
|
797
|
+
await observeThenDisableProtection({
|
|
798
|
+
flip,
|
|
799
|
+
logger: this.logger,
|
|
800
|
+
physicalId,
|
|
801
|
+
mask: context?.maskSecrets,
|
|
802
|
+
guardName: "DeletionProtection",
|
|
803
|
+
observe: async () => (await this.describeDBCluster(physicalId))?.DeletionProtection === true,
|
|
804
|
+
disable: async () => {
|
|
805
|
+
await this.getClient().send(new ModifyDBClusterCommand({
|
|
806
|
+
DBClusterIdentifier: physicalId,
|
|
807
|
+
DeletionProtection: false,
|
|
808
|
+
ApplyImmediately: true
|
|
809
|
+
}));
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
this.logger.debug(`Disabled DeletionProtection on DBCluster ${logicalId} before delete`);
|
|
813
|
+
} catch (disableError) {
|
|
814
|
+
if (!this.isNotFoundError(disableError, "DBClusterNotFoundFault")) this.logger.debug(maskerOrIdentity(context?.maskSecrets)(`Could not disable deletion protection for DBCluster ${logicalId}: ${describeAwsFailure(disableError).detail}`));
|
|
815
|
+
}
|
|
816
|
+
const globalClusterId = recordedGlobalClusterIdentifier(properties);
|
|
817
|
+
if (globalClusterId !== void 0) detachedFrom = await this.detachFromGlobalCluster(logicalId, physicalId, globalClusterId, context?.removeProtection === true, progress, maskerOrIdentity(context?.maskSecrets)) ? globalClusterId : void 0;
|
|
818
|
+
const finalSnapshotId = context?.finalSnapshotIdentifier;
|
|
819
|
+
const sendDelete = () => this.getClient().send(new DeleteDBClusterCommand({
|
|
820
|
+
DBClusterIdentifier: physicalId,
|
|
821
|
+
...finalSnapshotId ? {
|
|
822
|
+
SkipFinalSnapshot: false,
|
|
823
|
+
FinalDBSnapshotIdentifier: finalSnapshotId
|
|
824
|
+
} : { SkipFinalSnapshot: true },
|
|
825
|
+
...deleteAutomatedBackupsInput(properties)
|
|
826
|
+
}));
|
|
827
|
+
if (finalSnapshotId && flip.flippedOffByThisRun && !flip.deleteAccepted) await this.deleteAfterProtectionFlip(logicalId, physicalId, sendDelete);
|
|
828
|
+
else await sendDelete();
|
|
829
|
+
flip.deleteAccepted = true;
|
|
830
|
+
if (finalSnapshotId) this.logger.info(`Deleting DBCluster ${logicalId} with a final snapshot (DeletionPolicy: Snapshot)`);
|
|
831
|
+
this.logger.debug(`Successfully initiated deletion of DBCluster ${logicalId}`);
|
|
832
|
+
await this.waitForClusterDeleted(logicalId, physicalId, RDS_DELETE_WAIT_MS);
|
|
833
|
+
} catch (error) {
|
|
834
|
+
if (error instanceof GlobalClusterDetachError) {
|
|
835
|
+
const detachFailure = markNotGone(markNonRetryable(new ProvisioningError(`Failed to delete DBCluster ${logicalId}: ${error.message}`, resourceType, logicalId, physicalId, error)));
|
|
836
|
+
throw progress.stripped ? markDeleteStarted(detachFailure) : detachFailure;
|
|
837
|
+
}
|
|
838
|
+
if (this.isNotFoundError(error, "DBClusterNotFoundFault")) {
|
|
839
|
+
const clientRegion = await this.getClient().config.region();
|
|
840
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
841
|
+
if (context?.failedCreateOrphan === true) this.logger.info(safeMsg` RDS DB cluster ${physicalId} (${logicalId}), which a failed deploy created, is already gone; nothing to delete`);
|
|
842
|
+
else this.logger.debug(`DBCluster ${logicalId} does not exist, skipping deletion`);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
const cause = error instanceof Error ? error : void 0;
|
|
846
|
+
const detachedNote = detachedFrom !== void 0 ? " (it was already detached from its global cluster, and stays a standalone cluster)" : "";
|
|
847
|
+
const failure = this.wrapDeleteError(context?.maskSecrets, error, (text) => new ProvisioningError(`Failed to delete DBCluster ${logicalId}: ${text}${detachedNote}`, resourceType, logicalId, physicalId, cause));
|
|
848
|
+
throw progress.stripped ? markDeleteStarted(failure) : failure;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
async createDBInstance(logicalId, resourceType, properties, mask) {
|
|
852
|
+
this.logger.debug(`Creating DBInstance ${logicalId}`);
|
|
853
|
+
const tags = refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId);
|
|
854
|
+
const logExports = desiredLogExports(properties, resourceType, logicalId);
|
|
855
|
+
const dbInstanceIdentifier = properties["DBInstanceIdentifier"] || generatedNameFor("AWS::RDS::DBInstance", logicalId);
|
|
856
|
+
let instanceCreated = false;
|
|
857
|
+
let createdResourceId;
|
|
858
|
+
try {
|
|
859
|
+
const masterUserSecret = properties["MasterUserSecret"];
|
|
860
|
+
const response = await this.getCreateClient().send(new CreateDBInstanceCommand({
|
|
861
|
+
DBInstanceIdentifier: dbInstanceIdentifier,
|
|
862
|
+
DBInstanceClass: properties["DBInstanceClass"],
|
|
863
|
+
Engine: properties["Engine"],
|
|
864
|
+
DBClusterIdentifier: properties["DBClusterIdentifier"],
|
|
865
|
+
DBSubnetGroupName: properties["DBSubnetGroupName"],
|
|
866
|
+
PubliclyAccessible: properties["PubliclyAccessible"],
|
|
867
|
+
...properties["AllocatedStorage"] !== void 0 && { AllocatedStorage: Number(properties["AllocatedStorage"]) },
|
|
868
|
+
...properties["MasterUsername"] !== void 0 && { MasterUsername: properties["MasterUsername"] },
|
|
869
|
+
...properties["DeletionProtection"] !== void 0 && { DeletionProtection: properties["DeletionProtection"] },
|
|
870
|
+
...properties["EngineVersion"] !== void 0 && { EngineVersion: properties["EngineVersion"] },
|
|
871
|
+
...properties["Port"] !== void 0 && { Port: Number(properties["Port"]) },
|
|
872
|
+
...properties["MasterUserPassword"] !== void 0 && { MasterUserPassword: properties["MasterUserPassword"] },
|
|
873
|
+
...properties["StorageEncrypted"] !== void 0 && { StorageEncrypted: properties["StorageEncrypted"] },
|
|
874
|
+
...properties["VPCSecurityGroups"] !== void 0 && { VpcSecurityGroupIds: properties["VPCSecurityGroups"] },
|
|
875
|
+
...properties["KmsKeyId"] !== void 0 && { KmsKeyId: properties["KmsKeyId"] },
|
|
876
|
+
...masterUserSecret?.KmsKeyId !== void 0 && { MasterUserSecretKmsKeyId: masterUserSecret.KmsKeyId },
|
|
877
|
+
...properties["ManageMasterUserPassword"] !== void 0 && { ManageMasterUserPassword: properties["ManageMasterUserPassword"] },
|
|
878
|
+
...properties["MonitoringRoleArn"] !== void 0 && { MonitoringRoleArn: properties["MonitoringRoleArn"] },
|
|
879
|
+
...properties["MonitoringInterval"] !== void 0 && { MonitoringInterval: Number(properties["MonitoringInterval"]) },
|
|
880
|
+
...properties["EnableIAMDatabaseAuthentication"] !== void 0 && { EnableIAMDatabaseAuthentication: properties["EnableIAMDatabaseAuthentication"] },
|
|
881
|
+
...properties["BackupRetentionPeriod"] != null && { BackupRetentionPeriod: Number(properties["BackupRetentionPeriod"]) },
|
|
882
|
+
...properties["PreferredBackupWindow"] != null && { PreferredBackupWindow: properties["PreferredBackupWindow"] },
|
|
883
|
+
...properties["PreferredMaintenanceWindow"] != null && { PreferredMaintenanceWindow: properties["PreferredMaintenanceWindow"] },
|
|
884
|
+
...properties["StorageType"] != null && { StorageType: properties["StorageType"] },
|
|
885
|
+
...properties["Iops"] != null && { Iops: Number(properties["Iops"]) },
|
|
886
|
+
...properties["StorageThroughput"] != null && { StorageThroughput: Number(properties["StorageThroughput"]) },
|
|
887
|
+
...properties["MaxAllocatedStorage"] != null && { MaxAllocatedStorage: Number(properties["MaxAllocatedStorage"]) },
|
|
888
|
+
...properties["MultiAZ"] != null && { MultiAZ: properties["MultiAZ"] },
|
|
889
|
+
...properties["AutoMinorVersionUpgrade"] != null && { AutoMinorVersionUpgrade: properties["AutoMinorVersionUpgrade"] },
|
|
890
|
+
...properties["CopyTagsToSnapshot"] != null && { CopyTagsToSnapshot: properties["CopyTagsToSnapshot"] },
|
|
891
|
+
...properties["OptionGroupName"] != null && { OptionGroupName: properties["OptionGroupName"] },
|
|
892
|
+
...properties["CACertificateIdentifier"] != null && { CACertificateIdentifier: properties["CACertificateIdentifier"] },
|
|
893
|
+
...String(properties["EnablePerformanceInsights"]) === "true" && {
|
|
894
|
+
EnablePerformanceInsights: true,
|
|
895
|
+
...properties["PerformanceInsightsKMSKeyId"] != null && { PerformanceInsightsKMSKeyId: properties["PerformanceInsightsKMSKeyId"] }
|
|
896
|
+
},
|
|
897
|
+
...properties["PerformanceInsightsRetentionPeriod"] != null && { PerformanceInsightsRetentionPeriod: Number(properties["PerformanceInsightsRetentionPeriod"]) },
|
|
898
|
+
...properties["NetworkType"] != null && { NetworkType: properties["NetworkType"] },
|
|
899
|
+
...properties["LicenseModel"] != null && { LicenseModel: properties["LicenseModel"] },
|
|
900
|
+
...properties["PromotionTier"] != null && { PromotionTier: Number(properties["PromotionTier"]) },
|
|
901
|
+
...properties["DatabaseInsightsMode"] != null && { DatabaseInsightsMode: properties["DatabaseInsightsMode"] },
|
|
902
|
+
...properties["DedicatedLogVolume"] != null && { DedicatedLogVolume: properties["DedicatedLogVolume"] },
|
|
903
|
+
...properties["DBParameterGroupName"] != null && { DBParameterGroupName: properties["DBParameterGroupName"] },
|
|
904
|
+
...logExports !== void 0 && logExports.length > 0 && { EnableCloudwatchLogsExports: logExports },
|
|
905
|
+
...properties["DBName"] != null && { DBName: properties["DBName"] },
|
|
906
|
+
...properties["AvailabilityZone"] != null && { AvailabilityZone: properties["AvailabilityZone"] },
|
|
907
|
+
...tags.length > 0 && { Tags: tags }
|
|
908
|
+
}));
|
|
909
|
+
instanceCreated = true;
|
|
910
|
+
createdResourceId = response.DBInstance?.DbiResourceId;
|
|
911
|
+
reportCreatedResource(logicalId, resourceType, dbInstanceIdentifier, createdResourceId);
|
|
912
|
+
if (!response.DBInstance) throw new Error("CreateDBInstance did not return DBInstance");
|
|
913
|
+
this.logger.debug(`Successfully created DBInstance ${logicalId}: ${dbInstanceIdentifier}`);
|
|
914
|
+
if (process.env["CDKD_NO_WAIT"] !== "true") await this.waitForInstanceAvailable(logicalId, dbInstanceIdentifier);
|
|
915
|
+
const described = await this.describeDBInstance(dbInstanceIdentifier);
|
|
916
|
+
return {
|
|
917
|
+
physicalId: dbInstanceIdentifier,
|
|
918
|
+
attributes: definedAttributes({
|
|
919
|
+
"Endpoint.Address": described?.Endpoint?.Address,
|
|
920
|
+
"Endpoint.Port": stringifyIfAssigned(described?.Endpoint?.Port),
|
|
921
|
+
Arn: described?.DBInstanceArn
|
|
922
|
+
})
|
|
923
|
+
};
|
|
924
|
+
} catch (error) {
|
|
925
|
+
const thrown = error instanceof ProvisioningError ? error : this.wrapCaughtError(mask, error, (text) => new ProvisioningError(`Failed to create DBInstance ${logicalId}: ${text}`, resourceType, logicalId, dbInstanceIdentifier, error instanceof Error ? error : void 0));
|
|
926
|
+
if (instanceCreated) markCreatedBeforeFailure(thrown, logicalId, resourceType, dbInstanceIdentifier, createdResourceId);
|
|
927
|
+
throw thrown;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
async updateDBInstance(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
931
|
+
this.logger.debug(`Updating DBInstance ${logicalId}: ${physicalId}`);
|
|
932
|
+
refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId, physicalId);
|
|
933
|
+
const logExportConfig = logExportsDelta(desiredLogExports(properties, resourceType, logicalId, physicalId), previousProperties["EnableCloudwatchLogsExports"]);
|
|
934
|
+
const vpcSgIds = await this.securityGroupsToSend("DBInstance", logicalId, physicalId, resourceType, properties, previousProperties, context);
|
|
935
|
+
try {
|
|
936
|
+
const { mask: zoneMask } = withDerivedNameMasks(this.logger, createMaskedLogSinks(this.logger, context?.maskSecrets), [[previousProperties["DBInstanceIdentifier"], physicalId]]);
|
|
937
|
+
const desiredZone = properties["AvailabilityZone"];
|
|
938
|
+
let zoneKept = context?.desiredFromAwsReadback === true || context?.replayingState === true && (desiredZone == null || desiredZone === "") || instanceAvailabilityZoneKept(properties, previousProperties);
|
|
939
|
+
let liveZoneRead = "read";
|
|
940
|
+
if (!zoneKept && typeof desiredZone === "string" && desiredZone !== "") zoneKept = await this.describeDBInstance(physicalId).then((instance) => {
|
|
941
|
+
if (instance === void 0) liveZoneRead = "not-found";
|
|
942
|
+
return instance?.AvailabilityZone;
|
|
943
|
+
}, (err) => {
|
|
944
|
+
liveZoneRead = err?.name === "DBInstanceNotFoundFault" ? "not-found" : "unreadable";
|
|
945
|
+
this.logger.debug(zoneMask(safeMsg`DBInstance ${logicalId}: could not read the live zone: ${describeAwsFailure(err).summary}`));
|
|
946
|
+
}) === desiredZone;
|
|
947
|
+
if (!zoneKept && context?.replayingState !== true) throw new ResourceUpdateNotSupportedError(resourceType, logicalId, zoneMask(`AvailabilityZone cannot change on an existing DB instance — ModifyDBInstance has no such member, and CloudFormation refuses the same update ("Resource is immutable"). Nothing was applied to DB instance ${physicalId}. ` + (liveZoneRead === "not-found" ? "RDS reports no such DB instance: it was deleted outside cdkd, so the state record no longer matches AWS. " : liveZoneRead === "unreadable" ? "cdkd could not read the instance's live zone (an AWS read failed), so it could not tell whether the instance already sits in the declared one; a re-run may succeed. " : "") + "To keep the instance where it is, declare the zone the previous deploy declared (or none, if it declared none), or drop the zone and declare MultiAZ: true. The replace flags below apply to the WHOLE deploy: every other stateful resource in the same run whose in-place update is refused or that needs a replacement is recreated too, so run them with nothing else pending. To move it, " + (String(previousProperties["DeletionProtection"]) === "true" ? protectedReplacementAdvice({
|
|
948
|
+
evidence: "cdkd's properties for this DB instance carry DeletionProtection: true",
|
|
949
|
+
replaceFlags: "cdkd deploy --replace --force-stateful-recreation",
|
|
950
|
+
disable: {
|
|
951
|
+
before: "aws rds modify-db-instance --db-instance-identifier",
|
|
952
|
+
identifier: physicalId,
|
|
953
|
+
after: "--no-deletion-protection --apply-immediately",
|
|
954
|
+
maskSecrets: zoneMask
|
|
955
|
+
}
|
|
956
|
+
}) : "note AWS::RDS::DBInstance is a stateful type: re-run with cdkd deploy --replace --force-stateful-recreation to recreate it in the new zone (this deletes the instance and its data)")));
|
|
957
|
+
if (!zoneKept) this.logger.warn(zoneMask(safeMsg`DBInstance ${logicalId} (${physicalId}): AvailabilityZone cannot change in place ` + safeMsg`(ModifyDBInstance has no such member), so the instance keeps its current zone ` + safeMsg`and cdkd records that zone.`));
|
|
958
|
+
const effectiveProperties = zoneKept ? void 0 : withPreviousAvailabilityZone(properties, previousProperties);
|
|
959
|
+
const masterUserSecret = properties["MasterUserSecret"];
|
|
960
|
+
const sendCredentialSettings = masterCredentialSettingsChanged(properties, previousProperties, context);
|
|
961
|
+
const instancePassword = masterUserPasswordToSend(properties, previousProperties, context);
|
|
962
|
+
const manageMasterUserPasswordOff = managedPasswordOnRemoval(properties, previousProperties);
|
|
963
|
+
let liveInstance;
|
|
964
|
+
const backfillMembers = await instanceModifyBackfillMembers(properties, previousProperties, () => liveInstance ??= this.describeDBInstance(physicalId).then((instance) => instance?.AllocatedStorage), logicalId, context);
|
|
965
|
+
await modifyDbInstanceWhenAvailable({
|
|
966
|
+
send: () => this.getClient().send(new ModifyDBInstanceCommand({
|
|
967
|
+
DBInstanceIdentifier: physicalId,
|
|
968
|
+
DBInstanceClass: properties["DBInstanceClass"],
|
|
969
|
+
PubliclyAccessible: properties["PubliclyAccessible"],
|
|
970
|
+
ApplyImmediately: true,
|
|
971
|
+
...backfillMembers,
|
|
972
|
+
DeletionProtection: properties["DeletionProtection"],
|
|
973
|
+
...previousProperties["Engine"] !== properties["Engine"] && dbInstanceEngineChangeIsInPlace(previousProperties["Engine"], properties["Engine"]) && { Engine: properties["Engine"] },
|
|
974
|
+
...properties["Port"] !== void 0 && { DBPortNumber: Number(properties["Port"]) },
|
|
975
|
+
...instancePassword !== void 0 && { MasterUserPassword: instancePassword },
|
|
976
|
+
...vpcSgIds !== void 0 && { VpcSecurityGroupIds: vpcSgIds },
|
|
977
|
+
...sendCredentialSettings && masterUserSecret?.KmsKeyId !== void 0 && { MasterUserSecretKmsKeyId: masterUserSecret.KmsKeyId },
|
|
978
|
+
...sendCredentialSettings && properties["ManageMasterUserPassword"] !== void 0 && { ManageMasterUserPassword: properties["ManageMasterUserPassword"] },
|
|
979
|
+
...manageMasterUserPasswordOff && { ManageMasterUserPassword: false },
|
|
980
|
+
...properties["MonitoringRoleArn"] !== void 0 && { MonitoringRoleArn: properties["MonitoringRoleArn"] },
|
|
981
|
+
...properties["MonitoringInterval"] != null && { MonitoringInterval: Number(properties["MonitoringInterval"]) },
|
|
982
|
+
EnableIAMDatabaseAuthentication: properties["EnableIAMDatabaseAuthentication"],
|
|
983
|
+
...logExportConfig !== void 0 && { CloudwatchLogsExportConfiguration: logExportConfig }
|
|
984
|
+
})),
|
|
985
|
+
readStatus: async () => (await this.describeDBInstance(physicalId))?.DBInstanceStatus,
|
|
986
|
+
sleep: (ms) => this.sleep(ms),
|
|
987
|
+
logger: this.logger,
|
|
988
|
+
label: "RDS DBInstance",
|
|
989
|
+
logicalId,
|
|
990
|
+
remainingMs: enclosingDeadlineRemainingMs()
|
|
991
|
+
});
|
|
992
|
+
this.logger.debug(`Successfully updated DBInstance ${logicalId}`);
|
|
993
|
+
if (process.env["CDKD_NO_WAIT"] !== "true" && logExportConfig !== void 0) await this.waitForInstanceState(logicalId, physicalId, (instance) => logExportsApplied(logExportConfig, instance.EnabledCloudwatchLogsExports, instance.PendingModifiedValues?.PendingCloudwatchLogsExports), "CloudWatch Logs exports change");
|
|
994
|
+
let described = await this.describeDBInstance(physicalId);
|
|
995
|
+
const sentGroup = backfillMembers.DBParameterGroupName;
|
|
996
|
+
if (sentGroup !== void 0 || instanceParameterGroupPendingReboot(described) || instanceParameterGroupApplying(described)) described = await this.rebootForPendingParameterGroup(logicalId, physicalId, sentGroup, String(properties["CertificateRotationRestart"]) === "true");
|
|
997
|
+
if (described?.DBInstanceArn) await this.applyTagDiff(described.DBInstanceArn, resourceType, logicalId, previousProperties["Tags"], properties["Tags"]);
|
|
998
|
+
return {
|
|
999
|
+
physicalId,
|
|
1000
|
+
wasReplaced: false,
|
|
1001
|
+
attributes: definedAttributes({
|
|
1002
|
+
"Endpoint.Address": described?.Endpoint?.Address,
|
|
1003
|
+
"Endpoint.Port": stringifyIfAssigned(described?.Endpoint?.Port),
|
|
1004
|
+
Arn: described?.DBInstanceArn
|
|
1005
|
+
}),
|
|
1006
|
+
...effectiveProperties !== void 0 && { effectiveProperties }
|
|
1007
|
+
};
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
if (error instanceof ResourceUpdateNotSupportedError) throw error;
|
|
1010
|
+
const cause = error instanceof Error ? error : void 0;
|
|
1011
|
+
throw this.wrapCaughtError(context?.maskSecrets, error, (text) => new ProvisioningError(`Failed to update DBInstance ${logicalId}: ${text}`, resourceType, logicalId, physicalId, cause));
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
async deleteDBInstance(logicalId, physicalId, resourceType, properties, context) {
|
|
1015
|
+
await deleteWithProtectionCompensation({
|
|
1016
|
+
registry: this.protectionFlips,
|
|
1017
|
+
key: protectionFlipKey(resourceType, physicalId, context?.expectedRegion),
|
|
1018
|
+
run: (flip) => this.deleteDBInstanceOnce(logicalId, physicalId, resourceType, properties, context, flip),
|
|
1019
|
+
compensation: {
|
|
1020
|
+
logicalId,
|
|
1021
|
+
physicalId,
|
|
1022
|
+
logger: this.logger,
|
|
1023
|
+
site: rdsFamilyProtectionSite({
|
|
1024
|
+
cliService: "rds",
|
|
1025
|
+
serviceLabel: "RDS",
|
|
1026
|
+
kind: "instance",
|
|
1027
|
+
physicalId,
|
|
1028
|
+
region: context?.expectedRegion,
|
|
1029
|
+
notFoundFault: "DBInstanceNotFoundFault",
|
|
1030
|
+
isNotFound: (error) => this.isNotFoundError(error, "DBInstanceNotFoundFault")
|
|
1031
|
+
}),
|
|
1032
|
+
reEnable: async () => {
|
|
1033
|
+
await this.getClient().send(new ModifyDBInstanceCommand({
|
|
1034
|
+
DBInstanceIdentifier: physicalId,
|
|
1035
|
+
DeletionProtection: true,
|
|
1036
|
+
ApplyImmediately: true
|
|
1037
|
+
}));
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
async deleteDBInstanceOnce(logicalId, physicalId, resourceType, properties, context, flip) {
|
|
1043
|
+
this.logger.debug(`Deleting DBInstance ${logicalId}`);
|
|
1044
|
+
try {
|
|
1045
|
+
if (context?.removeProtection === true) try {
|
|
1046
|
+
await observeThenDisableProtection({
|
|
1047
|
+
flip,
|
|
1048
|
+
logger: this.logger,
|
|
1049
|
+
physicalId,
|
|
1050
|
+
mask: context?.maskSecrets,
|
|
1051
|
+
guardName: "DeletionProtection",
|
|
1052
|
+
observe: async () => (await this.describeDBInstance(physicalId))?.DeletionProtection === true,
|
|
1053
|
+
disable: async () => {
|
|
1054
|
+
await this.getClient().send(new ModifyDBInstanceCommand({
|
|
1055
|
+
DBInstanceIdentifier: physicalId,
|
|
1056
|
+
DeletionProtection: false,
|
|
1057
|
+
ApplyImmediately: true
|
|
1058
|
+
}));
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
this.logger.debug(`Disabled DeletionProtection on DBInstance ${logicalId} before delete`);
|
|
1062
|
+
} catch (disableError) {
|
|
1063
|
+
if (!this.isNotFoundError(disableError, "DBInstanceNotFoundFault")) this.logger.debug(maskerOrIdentity(context?.maskSecrets)(`Could not disable deletion protection for DBInstance ${logicalId}: ${describeAwsFailure(disableError).detail}`));
|
|
1064
|
+
}
|
|
1065
|
+
const finalSnapshotId = properties?.["DBClusterIdentifier"] === void 0 ? context?.finalSnapshotIdentifier : void 0;
|
|
1066
|
+
const sendDelete = () => this.getClient().send(new DeleteDBInstanceCommand({
|
|
1067
|
+
DBInstanceIdentifier: physicalId,
|
|
1068
|
+
...finalSnapshotId ? {
|
|
1069
|
+
SkipFinalSnapshot: false,
|
|
1070
|
+
FinalDBSnapshotIdentifier: finalSnapshotId
|
|
1071
|
+
} : { SkipFinalSnapshot: true },
|
|
1072
|
+
...deleteAutomatedBackupsInput(properties)
|
|
1073
|
+
}));
|
|
1074
|
+
if (finalSnapshotId && flip.flippedOffByThisRun && !flip.deleteAccepted) await this.deleteInstanceAfterProtectionFlip(logicalId, physicalId, sendDelete);
|
|
1075
|
+
else await sendDelete();
|
|
1076
|
+
flip.deleteAccepted = true;
|
|
1077
|
+
if (finalSnapshotId) this.logger.info(`Deleting DBInstance ${logicalId} with a final snapshot (DeletionPolicy: Snapshot)`);
|
|
1078
|
+
this.logger.debug(`Successfully initiated deletion of DBInstance ${logicalId}`);
|
|
1079
|
+
await this.waitForInstanceDeleted(logicalId, physicalId, RDS_DELETE_WAIT_MS);
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
if (this.isNotFoundError(error, "DBInstanceNotFoundFault")) {
|
|
1082
|
+
const clientRegion = await this.getClient().config.region();
|
|
1083
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
1084
|
+
if (context?.failedCreateOrphan === true) this.logger.info(safeMsg` RDS DB instance ${physicalId} (${logicalId}), which a failed deploy created, is already gone; nothing to delete`);
|
|
1085
|
+
else this.logger.debug(`DBInstance ${logicalId} does not exist, skipping deletion`);
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
const cause = error instanceof Error ? error : void 0;
|
|
1089
|
+
throw this.wrapDeleteError(context?.maskSecrets, error, (text) => new ProvisioningError(`Failed to delete DBInstance ${logicalId}: ${text}`, resourceType, logicalId, physicalId, cause));
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
async applyTagDiff(arn, resourceType, logicalId, oldTagsRaw, newTagsRaw) {
|
|
1093
|
+
const plan = planTagDiff(oldTagsRaw, newTagsRaw);
|
|
1094
|
+
const tagWarning = tagPlanWarning(plan, resourceType, logicalId);
|
|
1095
|
+
if (tagWarning !== void 0) this.logger.warn(tagWarning);
|
|
1096
|
+
const tagsToAdd = [...plan.set].map(([Key, Value]) => ({
|
|
1097
|
+
Key,
|
|
1098
|
+
Value
|
|
1099
|
+
}));
|
|
1100
|
+
const tagsToRemove = plan.remove;
|
|
1101
|
+
if (tagsToRemove.length > 0) {
|
|
1102
|
+
await this.getClient().send(new RemoveTagsFromResourceCommand({
|
|
1103
|
+
ResourceName: arn,
|
|
1104
|
+
TagKeys: tagsToRemove
|
|
1105
|
+
}));
|
|
1106
|
+
this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from RDS resource ${arn}`);
|
|
1107
|
+
}
|
|
1108
|
+
if (tagsToAdd.length > 0) {
|
|
1109
|
+
await this.getClient().send(new AddTagsToResourceCommand({
|
|
1110
|
+
ResourceName: arn,
|
|
1111
|
+
Tags: tagsToAdd
|
|
1112
|
+
}));
|
|
1113
|
+
this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on RDS resource ${arn}`);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
async partialClusterRetiring(dbClusterIdentifier) {
|
|
1117
|
+
try {
|
|
1118
|
+
return (await this.describeDBCluster(dbClusterIdentifier))?.Status === "deleting" ? "deleting" : void 0;
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
return error instanceof Error && error.name === "DBClusterNotFoundFault" ? "gone" : void 0;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
isNotFoundError(error, faultName) {
|
|
1124
|
+
if (!(error instanceof Error)) return false;
|
|
1125
|
+
const name = error.name ?? "";
|
|
1126
|
+
const message = error.message.toLowerCase();
|
|
1127
|
+
return name === faultName || message.includes("not found") || message.includes("does not exist");
|
|
1128
|
+
}
|
|
1129
|
+
wrapCaughtError(mask, error, build) {
|
|
1130
|
+
const m = maskerOrIdentity(mask);
|
|
1131
|
+
const failure = describeAwsFailure(error);
|
|
1132
|
+
if (!failure.redacted) return wrapMaskedAwsError(m, error, build);
|
|
1133
|
+
this.logger.debug(m(build(m(failure.detail)).message));
|
|
1134
|
+
const wrapped = build(`${m(failure.summary)}${relayedNameCollision(failure.detail)}`);
|
|
1135
|
+
return error instanceof Error ? markRedactedCause(wrapped) : wrapped;
|
|
1136
|
+
}
|
|
1137
|
+
wrapDeleteError(mask, error, build) {
|
|
1138
|
+
const thrown = this.wrapCaughtError(mask, error, build);
|
|
1139
|
+
return error instanceof Error && error.name.includes("NotFound") ? markNotGone(thrown) : thrown;
|
|
1140
|
+
}
|
|
1141
|
+
wrapGlobalClusterDetachError(error, mask, build) {
|
|
1142
|
+
const failure = describeAwsFailure(error);
|
|
1143
|
+
if (failure.redacted) this.logger.debug(mask(build(mask(failure.detail))));
|
|
1144
|
+
return new GlobalClusterDetachError(build(mask(failure.summary)));
|
|
1145
|
+
}
|
|
1146
|
+
async detachFromGlobalCluster(logicalId, physicalId, globalClusterId, removeProtection, progress, mask) {
|
|
1147
|
+
let cluster = await this.describeDBCluster(physicalId);
|
|
1148
|
+
for (let i = 0; removeProtection && cluster?.DeletionProtection === true && i < 6; i++) {
|
|
1149
|
+
await this.sleep(5e3);
|
|
1150
|
+
cluster = await this.describeDBCluster(physicalId);
|
|
1151
|
+
}
|
|
1152
|
+
if (cluster === void 0) return false;
|
|
1153
|
+
if (cluster.DeletionProtection === true) throw new GlobalClusterDetachError(safeMsg`${physicalId} has deletion protection on, so cdkd did not detach it from global cluster ${globalClusterId}; turn it off (cdkd destroy --remove-protection does) and re-run`);
|
|
1154
|
+
const clusterArn = cluster.DBClusterArn;
|
|
1155
|
+
if (typeof clusterArn !== "string" || clusterArn === "") throw new GlobalClusterDetachError(safeMsg`could not read the ARN of ${physicalId} to detach it from global cluster ${globalClusterId}`);
|
|
1156
|
+
const startTime = Date.now();
|
|
1157
|
+
let delay = 5e3;
|
|
1158
|
+
const backOff = async () => {
|
|
1159
|
+
if (Date.now() - startTime >= RDS_DELETE_WAIT_MS) throw new GlobalClusterDetachError(safeMsg`timed out waiting for ${physicalId} to leave global cluster ${globalClusterId}`);
|
|
1160
|
+
await this.sleep(delay);
|
|
1161
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1162
|
+
};
|
|
1163
|
+
for (;;) try {
|
|
1164
|
+
await this.getClient().send(new RemoveFromGlobalClusterCommand({
|
|
1165
|
+
GlobalClusterIdentifier: globalClusterId,
|
|
1166
|
+
DbClusterIdentifier: clusterArn
|
|
1167
|
+
}));
|
|
1168
|
+
progress.stripped = true;
|
|
1169
|
+
this.logger.info(safeMsg`Detaching DBCluster ${logicalId} from global cluster ${globalClusterId} before the delete`);
|
|
1170
|
+
break;
|
|
1171
|
+
} catch (error) {
|
|
1172
|
+
const name = awsErrorName(error);
|
|
1173
|
+
if (name === "GlobalClusterNotFoundFault") return false;
|
|
1174
|
+
if (name === "DBClusterNotFoundFault") break;
|
|
1175
|
+
if (!TRANSIENT_DETACH_FAULTS.has(name)) throw this.wrapGlobalClusterDetachError(error, mask, (text) => safeMsg`detaching ${physicalId} from global cluster ${globalClusterId} failed (${text})`);
|
|
1176
|
+
await backOff();
|
|
1177
|
+
}
|
|
1178
|
+
for (;;) {
|
|
1179
|
+
let stillMember;
|
|
1180
|
+
try {
|
|
1181
|
+
stillMember = ((await this.getClient().send(new DescribeGlobalClustersCommand({ GlobalClusterIdentifier: globalClusterId }))).GlobalClusters?.[0]?.GlobalClusterMembers ?? []).some((member) => member.DBClusterArn === clusterArn);
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
const name = awsErrorName(error);
|
|
1184
|
+
if (name === "GlobalClusterNotFoundFault") stillMember = false;
|
|
1185
|
+
else if (!TRANSIENT_DETACH_FAULTS.has(name)) throw this.wrapGlobalClusterDetachError(error, mask, (text) => safeMsg`could not confirm ${physicalId} left global cluster ${globalClusterId} (${text})`);
|
|
1186
|
+
}
|
|
1187
|
+
if (stillMember === false) {
|
|
1188
|
+
const after = await this.describeDBCluster(physicalId);
|
|
1189
|
+
if (after === void 0) return true;
|
|
1190
|
+
this.logger.debug(safeMsg`DBCluster ${physicalId} status after the detach: ${after.Status}`);
|
|
1191
|
+
if (after.Status === "available") return true;
|
|
1192
|
+
}
|
|
1193
|
+
await backOff();
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
async describeDBCluster(dbClusterIdentifier) {
|
|
1197
|
+
return (await this.getClient().send(new DescribeDBClustersCommand({ DBClusterIdentifier: dbClusterIdentifier }))).DBClusters?.[0];
|
|
1198
|
+
}
|
|
1199
|
+
async securityGroupsToSend(target, logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
1200
|
+
const key = target === "DBCluster" ? "VpcSecurityGroupIds" : "VPCSecurityGroups";
|
|
1201
|
+
const noun = target === "DBCluster" ? "cluster" : "instance";
|
|
1202
|
+
const desired = properties[key];
|
|
1203
|
+
if (desired !== void 0) return desired.length > 0 ? desired : void 0;
|
|
1204
|
+
const previous = previousProperties[key];
|
|
1205
|
+
if (context?.desiredFromAwsReadback === true) return void 0;
|
|
1206
|
+
if (!Array.isArray(previous) || previous.length === 0) return void 0;
|
|
1207
|
+
let groupId;
|
|
1208
|
+
let missing;
|
|
1209
|
+
let awsError;
|
|
1210
|
+
try {
|
|
1211
|
+
let subnetGroupName;
|
|
1212
|
+
let vpcId;
|
|
1213
|
+
if (target === "DBInstance") {
|
|
1214
|
+
const subnetGroup = (await this.describeDBInstance(physicalId))?.DBSubnetGroup;
|
|
1215
|
+
subnetGroupName = subnetGroup?.DBSubnetGroupName;
|
|
1216
|
+
vpcId = subnetGroupName ? subnetGroup?.VpcId : void 0;
|
|
1217
|
+
} else {
|
|
1218
|
+
subnetGroupName = (await this.describeDBCluster(physicalId))?.DBSubnetGroup;
|
|
1219
|
+
vpcId = subnetGroupName ? (await this.getClient().send(new DescribeDBSubnetGroupsCommand({ DBSubnetGroupName: subnetGroupName }))).DBSubnetGroups?.[0]?.VpcId : void 0;
|
|
1220
|
+
}
|
|
1221
|
+
if (!subnetGroupName) missing = `the ${noun} reports no DB subnet group`;
|
|
1222
|
+
else if (!vpcId) missing = `DB subnet group ${subnetGroupName} reports no VPC`;
|
|
1223
|
+
else {
|
|
1224
|
+
groupId = (await this.getEc2Client().send(new DescribeSecurityGroupsCommand({ Filters: [{
|
|
1225
|
+
Name: "vpc-id",
|
|
1226
|
+
Values: [vpcId]
|
|
1227
|
+
}, {
|
|
1228
|
+
Name: "group-name",
|
|
1229
|
+
Values: ["default"]
|
|
1230
|
+
}] }))).SecurityGroups?.[0]?.GroupId;
|
|
1231
|
+
if (!groupId) missing = `VPC ${vpcId} has no security group named default`;
|
|
1232
|
+
}
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
awsError = error;
|
|
1235
|
+
}
|
|
1236
|
+
if (groupId === void 0) {
|
|
1237
|
+
const mask = context?.maskSecrets ?? ((text) => text);
|
|
1238
|
+
const failure = missing === void 0 ? describeAwsFailure(awsError) : void 0;
|
|
1239
|
+
if (failure?.redacted === true) this.logger.debug(safeMsg`${logicalId}: the VPC default security group lookup failed: ${mask(failure.detail)}`);
|
|
1240
|
+
const shown = mask(failure?.summary ?? "");
|
|
1241
|
+
const refusal = new ProvisioningError(`Failed to update ${target} ${logicalId}: ${key} was removed from the template, so the ${noun} moves to its VPC's default security group (as CloudFormation does), but cdkd could not find that group: ${missing ?? shown}. Declare ${key} in the template to choose the ${noun}'s security groups.`, resourceType, logicalId, physicalId, awsError instanceof Error ? awsError : void 0);
|
|
1242
|
+
if (missing !== void 0) throw markNonRetryable(refusal);
|
|
1243
|
+
throw awsError instanceof Error && (failure?.redacted === true || shown !== (failure?.summary ?? "")) ? markRedactedCause(refusal) : refusal;
|
|
1244
|
+
}
|
|
1245
|
+
this.logger.warn(safeMsg`${logicalId}: ${key} was removed from the template; attaching the VPC's default security group ${groupId}, as CloudFormation does.`);
|
|
1246
|
+
return [groupId];
|
|
1247
|
+
}
|
|
1248
|
+
async describeDBInstance(dbInstanceIdentifier) {
|
|
1249
|
+
return (await this.getClient().send(new DescribeDBInstancesCommand({ DBInstanceIdentifier: dbInstanceIdentifier }))).DBInstances?.[0];
|
|
1250
|
+
}
|
|
1251
|
+
async waitForMemberAfterClusterChange(dbInstanceIdentifier, clusterLogicalId, transitional, change, maxWaitMs = 18e5) {
|
|
1252
|
+
const startTime = Date.now();
|
|
1253
|
+
let delay = 5e3;
|
|
1254
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1255
|
+
let status;
|
|
1256
|
+
try {
|
|
1257
|
+
status = (await this.describeDBInstance(dbInstanceIdentifier))?.DBInstanceStatus;
|
|
1258
|
+
} catch (error) {
|
|
1259
|
+
if (this.isNotFoundError(error, "DBInstanceNotFoundFault")) return;
|
|
1260
|
+
throw error;
|
|
1261
|
+
}
|
|
1262
|
+
if (status === void 0 || !transitional.has(status)) return;
|
|
1263
|
+
await this.sleep(delay);
|
|
1264
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1265
|
+
}
|
|
1266
|
+
throw new Error(safeMsg`Timed out waiting for a member instance of DBCluster ${clusterLogicalId} to finish applying its new ${change}`);
|
|
1267
|
+
}
|
|
1268
|
+
requireClusterWhileWaiting(cluster, logicalId, change) {
|
|
1269
|
+
if (cluster === void 0) throw new Error(safeMsg`DescribeDBClusters returned no cluster for DBCluster ${logicalId} while waiting for its new ${change}`);
|
|
1270
|
+
if (isTerminalDbWaitStatus(cluster.Status)) throw new Error(safeMsg`DBCluster ${logicalId} entered status ${cluster.Status ?? ""} while applying its new ${change}`);
|
|
1271
|
+
return cluster;
|
|
1272
|
+
}
|
|
1273
|
+
async waitForClusterPortApplied(dbClusterIdentifier, logicalId, port, maxWaitMs = 18e5) {
|
|
1274
|
+
const startTime = Date.now();
|
|
1275
|
+
let delay = 5e3;
|
|
1276
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1277
|
+
const cluster = this.requireClusterWhileWaiting(await this.describeDBCluster(dbClusterIdentifier), logicalId, "port");
|
|
1278
|
+
if (cluster.Status !== "modifying" && cluster.Port === port) return;
|
|
1279
|
+
this.logger.debug(safeMsg`DBCluster ${logicalId} port not applied yet (status ${cluster.Status ?? "unknown"})`);
|
|
1280
|
+
await this.sleep(delay);
|
|
1281
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1282
|
+
}
|
|
1283
|
+
throw new Error(`Timed out waiting for DBCluster ${logicalId} to apply its new port`);
|
|
1284
|
+
}
|
|
1285
|
+
async waitForSecurityGroupsApplied(dbClusterIdentifier, logicalId, expected, maxWaitMs = 18e5) {
|
|
1286
|
+
const want = [...new Set(expected)].sort().join(" ");
|
|
1287
|
+
const startTime = Date.now();
|
|
1288
|
+
let delay = 5e3;
|
|
1289
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1290
|
+
const cluster = this.requireClusterWhileWaiting(await this.describeDBCluster(dbClusterIdentifier), logicalId, "VPC security groups");
|
|
1291
|
+
const groups = cluster.VpcSecurityGroups ?? [];
|
|
1292
|
+
const ids = groups.map((group) => group.VpcSecurityGroupId ?? "").sort().join(" ");
|
|
1293
|
+
if (cluster.Status !== "modifying" && groups.every((group) => group.Status === "active") && ids === want) return;
|
|
1294
|
+
this.logger.debug(safeMsg`DBCluster ${logicalId} security groups not settled yet (status ${cluster.Status ?? "unknown"})`);
|
|
1295
|
+
await this.sleep(delay);
|
|
1296
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1297
|
+
}
|
|
1298
|
+
throw new Error(`Timed out waiting for DBCluster ${logicalId} to apply its new VPC security groups`);
|
|
1299
|
+
}
|
|
1300
|
+
async waitForClusterState(logicalId, dbClusterIdentifier, done, change) {
|
|
1301
|
+
const remaining = enclosingDeadlineRemainingMs();
|
|
1302
|
+
const maxWaitMs = Number.isFinite(remaining) ? remaining : 18e5;
|
|
1303
|
+
const startTime = Date.now();
|
|
1304
|
+
let delay = 5e3;
|
|
1305
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1306
|
+
const cluster = this.requireClusterWhileWaiting(await this.describeDBCluster(dbClusterIdentifier), logicalId, change);
|
|
1307
|
+
if (done(cluster)) return;
|
|
1308
|
+
this.logger.debug(safeMsg`DBCluster ${logicalId} waiting for its ${change} (status ${cluster.Status ?? "unknown"})`);
|
|
1309
|
+
await this.sleep(delay);
|
|
1310
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1311
|
+
}
|
|
1312
|
+
throw new Error(safeMsg`Timed out waiting for DBCluster ${logicalId}'s ${change}`);
|
|
1313
|
+
}
|
|
1314
|
+
async sendHttpEndpointChange(logicalId, physicalId, arn, enable) {
|
|
1315
|
+
let delay = 5e3;
|
|
1316
|
+
for (let attempt = 1;; attempt += 1) {
|
|
1317
|
+
await this.waitForClusterState(logicalId, physicalId, (cluster) => cluster.Status === "available", "pending changes to settle before the Data API call");
|
|
1318
|
+
try {
|
|
1319
|
+
await this.getClient().send(enable ? new EnableHttpEndpointCommand({ ResourceArn: arn }) : new DisableHttpEndpointCommand({ ResourceArn: arn }));
|
|
1320
|
+
return;
|
|
1321
|
+
} catch (error) {
|
|
1322
|
+
const name = awsErrorName(error);
|
|
1323
|
+
if (!HTTP_ENDPOINT_BUSY_FAULTS.has(name) || attempt >= HTTP_ENDPOINT_MAX_ATTEMPTS) throw error;
|
|
1324
|
+
this.logger.debug(safeMsg`DBCluster ${logicalId}: the Data API call was refused as busy (${name}); retrying`);
|
|
1325
|
+
await this.sleep(delay);
|
|
1326
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
async deleteAfterProtectionFlip(logicalId, physicalId, send) {
|
|
1331
|
+
await deleteAfterProtectionFlipWithFinalSnapshot({
|
|
1332
|
+
stateFault: "InvalidDBClusterStateFault",
|
|
1333
|
+
send,
|
|
1334
|
+
noun: "cluster",
|
|
1335
|
+
label: "DBCluster",
|
|
1336
|
+
logicalId,
|
|
1337
|
+
read: async () => {
|
|
1338
|
+
const current = await this.describeDBCluster(physicalId);
|
|
1339
|
+
return current === void 0 ? void 0 : {
|
|
1340
|
+
deletionProtection: current.DeletionProtection,
|
|
1341
|
+
status: current.Status
|
|
1342
|
+
};
|
|
1343
|
+
},
|
|
1344
|
+
sleep: (ms) => this.sleep(ms),
|
|
1345
|
+
logger: this.logger,
|
|
1346
|
+
remainingMs: enclosingDeadlineRemainingMs()
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
async deleteInstanceAfterProtectionFlip(logicalId, physicalId, send) {
|
|
1350
|
+
await deleteAfterProtectionFlipWithFinalSnapshot({
|
|
1351
|
+
stateFault: "InvalidDBInstanceStateFault",
|
|
1352
|
+
send,
|
|
1353
|
+
noun: "instance",
|
|
1354
|
+
label: "DBInstance",
|
|
1355
|
+
logicalId,
|
|
1356
|
+
read: async () => {
|
|
1357
|
+
const current = await this.describeDBInstance(physicalId);
|
|
1358
|
+
return current === void 0 ? void 0 : {
|
|
1359
|
+
deletionProtection: current.DeletionProtection,
|
|
1360
|
+
status: current.DBInstanceStatus
|
|
1361
|
+
};
|
|
1362
|
+
},
|
|
1363
|
+
sleep: (ms) => this.sleep(ms),
|
|
1364
|
+
logger: this.logger,
|
|
1365
|
+
remainingMs: enclosingDeadlineRemainingMs()
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
async waitForClusterAvailable(logicalId, dbClusterIdentifier, maxWaitMs = 18e5) {
|
|
1369
|
+
const startTime = Date.now();
|
|
1370
|
+
let delay = 5e3;
|
|
1371
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1372
|
+
const status = (await this.describeDBCluster(dbClusterIdentifier))?.Status;
|
|
1373
|
+
this.logger.debug(`DBCluster ${dbClusterIdentifier} status: ${status}`);
|
|
1374
|
+
if (status === "available") return;
|
|
1375
|
+
if (isTerminalDbWaitStatus(status)) throw new Error(safeMsg`DBCluster ${logicalId} entered status ${status ?? ""} while being created, which it does not leave on its own`);
|
|
1376
|
+
await this.sleep(delay);
|
|
1377
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1378
|
+
}
|
|
1379
|
+
throw new Error(`Timed out waiting for DBCluster ${dbClusterIdentifier} to become available`);
|
|
1380
|
+
}
|
|
1381
|
+
async waitForClusterDeleted(logicalId, dbClusterIdentifier, maxWaitMs = 18e5) {
|
|
1382
|
+
const startTime = Date.now();
|
|
1383
|
+
let delay = 5e3;
|
|
1384
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1385
|
+
try {
|
|
1386
|
+
const cluster = await this.describeDBCluster(dbClusterIdentifier);
|
|
1387
|
+
const status = cluster?.Status;
|
|
1388
|
+
this.logger.debug(`DBCluster ${logicalId} status: ${status}`);
|
|
1389
|
+
if (!cluster) return;
|
|
1390
|
+
} catch (error) {
|
|
1391
|
+
if (this.isNotFoundError(error, "DBClusterNotFoundFault")) return;
|
|
1392
|
+
throw error;
|
|
1393
|
+
}
|
|
1394
|
+
await this.sleep(delay);
|
|
1395
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1396
|
+
}
|
|
1397
|
+
throw new Error(`Timed out waiting for DBCluster ${logicalId} to be deleted`);
|
|
1398
|
+
}
|
|
1399
|
+
async rebootForPendingParameterGroup(logicalId, physicalId, sentGroup, caRotationRestarts) {
|
|
1400
|
+
const settled = await this.waitForInstanceState(logicalId, physicalId, (instance) => instanceParameterGroupSettled(instance, sentGroup, caRotationRestarts), "parameter group change to settle");
|
|
1401
|
+
if (!instanceParameterGroupPendingReboot(settled)) return settled;
|
|
1402
|
+
this.logger.info(safeMsg`Rebooting DBInstance ${logicalId} to apply its parameter group (pending-reboot), as CloudFormation does after an update`);
|
|
1403
|
+
await this.getClient().send(new RebootDBInstanceCommand({ DBInstanceIdentifier: physicalId }));
|
|
1404
|
+
if (process.env["CDKD_NO_WAIT"] === "true") return settled;
|
|
1405
|
+
return this.waitForInstanceState(logicalId, physicalId, instanceRebootApplied, "reboot to apply its parameter group");
|
|
1406
|
+
}
|
|
1407
|
+
async waitForInstanceState(logicalId, dbInstanceIdentifier, done, change) {
|
|
1408
|
+
const remaining = enclosingDeadlineRemainingMs();
|
|
1409
|
+
const maxWaitMs = Number.isFinite(remaining) ? remaining : 18e5;
|
|
1410
|
+
const startTime = Date.now();
|
|
1411
|
+
let delay = 5e3;
|
|
1412
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1413
|
+
const instance = await this.describeDBInstance(dbInstanceIdentifier);
|
|
1414
|
+
if (instance === void 0) throw new Error(safeMsg`DescribeDBInstances returned no instance for DBInstance ${logicalId} while waiting for its ${change}`);
|
|
1415
|
+
if (isTerminalDbWaitStatus(instance.DBInstanceStatus)) throw new Error(safeMsg`DBInstance ${logicalId} entered status ${instance.DBInstanceStatus ?? ""} while waiting for its ${change}`);
|
|
1416
|
+
if (done(instance)) return instance;
|
|
1417
|
+
this.logger.debug(safeMsg`DBInstance ${logicalId} waiting for its ${change} (status ${instance.DBInstanceStatus ?? "unknown"})`);
|
|
1418
|
+
await this.sleep(delay);
|
|
1419
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1420
|
+
}
|
|
1421
|
+
throw new Error(safeMsg`Timed out waiting for DBInstance ${logicalId}'s ${change}`);
|
|
1422
|
+
}
|
|
1423
|
+
async waitForInstanceAvailable(logicalId, dbInstanceIdentifier, maxWaitMs = 18e5) {
|
|
1424
|
+
const startTime = Date.now();
|
|
1425
|
+
let delay = 1e4;
|
|
1426
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1427
|
+
const status = (await this.describeDBInstance(dbInstanceIdentifier))?.DBInstanceStatus;
|
|
1428
|
+
this.logger.debug(`DBInstance ${dbInstanceIdentifier} status: ${status}`);
|
|
1429
|
+
if (status === "available") return;
|
|
1430
|
+
if (isTerminalDbWaitStatus(status)) throw new Error(safeMsg`DBInstance ${logicalId} entered status ${status ?? ""} while being created, which it does not leave on its own`);
|
|
1431
|
+
await this.sleep(delay);
|
|
1432
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1433
|
+
}
|
|
1434
|
+
throw new Error(`Timed out waiting for DBInstance ${dbInstanceIdentifier} to become available`);
|
|
1435
|
+
}
|
|
1436
|
+
async waitForInstanceDeleted(logicalId, dbInstanceIdentifier, maxWaitMs = 18e5) {
|
|
1437
|
+
const startTime = Date.now();
|
|
1438
|
+
let delay = 1e4;
|
|
1439
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1440
|
+
try {
|
|
1441
|
+
const instance = await this.describeDBInstance(dbInstanceIdentifier);
|
|
1442
|
+
const status = instance?.DBInstanceStatus;
|
|
1443
|
+
this.logger.debug(`DBInstance ${logicalId} status: ${status}`);
|
|
1444
|
+
if (!instance) return;
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
if (this.isNotFoundError(error, "DBInstanceNotFoundFault")) return;
|
|
1447
|
+
throw error;
|
|
1448
|
+
}
|
|
1449
|
+
await this.sleep(delay);
|
|
1450
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1451
|
+
}
|
|
1452
|
+
throw new Error(`Timed out waiting for DBInstance ${logicalId} to be deleted`);
|
|
1453
|
+
}
|
|
1454
|
+
sleep(ms) {
|
|
1455
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1456
|
+
}
|
|
1457
|
+
async import(input) {
|
|
1458
|
+
switch (input.resourceType) {
|
|
1459
|
+
case "AWS::RDS::DBInstance": return this.importDBInstance(input);
|
|
1460
|
+
case "AWS::RDS::DBCluster": return this.importDBCluster(input);
|
|
1461
|
+
case "AWS::RDS::DBSubnetGroup": return this.importDBSubnetGroup(input);
|
|
1462
|
+
default: return null;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
getDriftUnorderedPaths(resourceType) {
|
|
1466
|
+
return resourceType === "AWS::RDS::DBCluster" || resourceType === "AWS::RDS::DBInstance" ? ["EnableCloudwatchLogsExports"] : [];
|
|
1467
|
+
}
|
|
1468
|
+
getDriftUnknownPaths(resourceType, properties) {
|
|
1469
|
+
if (resourceType === "AWS::RDS::DBInstance") return ["AllowMajorVersionUpgrade", "CertificateRotationRestart"];
|
|
1470
|
+
if (resourceType !== "AWS::RDS::DBCluster") return [];
|
|
1471
|
+
if (properties === void 0 || Object.keys(properties).length === 0) return [];
|
|
1472
|
+
return (properties?.["ServerlessV2ScalingConfiguration"])?.SecondsUntilAutoPause === void 0 ? ["ServerlessV2ScalingConfiguration.SecondsUntilAutoPause"] : [];
|
|
1473
|
+
}
|
|
1474
|
+
canonicalizeDriftProperties(resourceType, properties) {
|
|
1475
|
+
if (resourceType !== "AWS::RDS::DBCluster") return properties;
|
|
1476
|
+
let out;
|
|
1477
|
+
const set = (key, value) => {
|
|
1478
|
+
out ??= { ...properties };
|
|
1479
|
+
out[key] = value;
|
|
1480
|
+
};
|
|
1481
|
+
for (const key of CLUSTER_BOOLEAN_SCALARS) {
|
|
1482
|
+
const value = properties[key];
|
|
1483
|
+
if (value === "true" || value === "false") set(key, value === "true");
|
|
1484
|
+
}
|
|
1485
|
+
const retention = properties["PerformanceInsightsRetentionPeriod"];
|
|
1486
|
+
if (typeof retention === "string") {
|
|
1487
|
+
const parsed = retentionPeriodValue(retention);
|
|
1488
|
+
if (typeof parsed === "number") set("PerformanceInsightsRetentionPeriod", parsed);
|
|
1489
|
+
}
|
|
1490
|
+
return out ?? properties;
|
|
1491
|
+
}
|
|
1492
|
+
async readCurrentState(physicalId, _logicalId, resourceType) {
|
|
1493
|
+
switch (resourceType) {
|
|
1494
|
+
case "AWS::RDS::DBInstance": return this.readCurrentStateDBInstance(physicalId);
|
|
1495
|
+
case "AWS::RDS::DBCluster": return this.readCurrentStateDBCluster(physicalId);
|
|
1496
|
+
case "AWS::RDS::DBSubnetGroup": return this.readCurrentStateDBSubnetGroup(physicalId);
|
|
1497
|
+
default: return;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
async isSameResource(journaledPhysicalId, record, resourceType, context) {
|
|
1501
|
+
if (resourceType !== "AWS::RDS::DBCluster" && resourceType !== "AWS::RDS::DBInstance") return "unknown";
|
|
1502
|
+
if (!isDbIdentifier(journaledPhysicalId) || !isDbIdentifier(record.physicalId)) return "unknown";
|
|
1503
|
+
if (journaledPhysicalId.toLowerCase() === record.physicalId.toLowerCase()) return "same";
|
|
1504
|
+
if (await this.getClient().config.region() !== context.expectedRegion) return "unknown";
|
|
1505
|
+
const recordResourceId = await this.readDbResourceIdIfExists(resourceType, record.physicalId);
|
|
1506
|
+
if (recordResourceId === void 0) return "unknown";
|
|
1507
|
+
const journaledResourceId = await this.readDbResourceIdIfExists(resourceType, journaledPhysicalId);
|
|
1508
|
+
if (journaledResourceId === void 0) return "different";
|
|
1509
|
+
return journaledResourceId === recordResourceId ? "same" : "different";
|
|
1510
|
+
}
|
|
1511
|
+
async resourceIdentity(physicalId, resourceType, context) {
|
|
1512
|
+
if (resourceType !== "AWS::RDS::DBCluster" && resourceType !== "AWS::RDS::DBInstance") return;
|
|
1513
|
+
if (!isDbIdentifier(physicalId)) return void 0;
|
|
1514
|
+
if (await this.getClient().config.region() !== context.expectedRegion) return void 0;
|
|
1515
|
+
const live = await this.readDbResourceIdIfExists(resourceType, physicalId);
|
|
1516
|
+
return live === void 0 ? RESOURCE_NOT_FOUND : live;
|
|
1517
|
+
}
|
|
1518
|
+
async readDbResourceIdIfExists(resourceType, identifier) {
|
|
1519
|
+
const isCluster = resourceType === "AWS::RDS::DBCluster";
|
|
1520
|
+
let found;
|
|
1521
|
+
try {
|
|
1522
|
+
if (isCluster) {
|
|
1523
|
+
const cluster = await this.describeDBCluster(identifier);
|
|
1524
|
+
found = cluster && {
|
|
1525
|
+
identifier: cluster.DBClusterIdentifier,
|
|
1526
|
+
resourceId: cluster.DbClusterResourceId
|
|
1527
|
+
};
|
|
1528
|
+
} else {
|
|
1529
|
+
const instance = await this.describeDBInstance(identifier);
|
|
1530
|
+
found = instance && {
|
|
1531
|
+
identifier: instance.DBInstanceIdentifier,
|
|
1532
|
+
resourceId: instance.DbiResourceId
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
} catch (error) {
|
|
1536
|
+
const notFound = isCluster ? "DBClusterNotFoundFault" : "DBInstanceNotFoundFault";
|
|
1537
|
+
if (error?.name === notFound) return void 0;
|
|
1538
|
+
throw error;
|
|
1539
|
+
}
|
|
1540
|
+
if (found === void 0) return void 0;
|
|
1541
|
+
const api = isCluster ? "DescribeDBClusters" : "DescribeDBInstances";
|
|
1542
|
+
if (found.identifier?.toLowerCase() !== identifier.toLowerCase()) throw new Error(`${api} answered for another identifier`);
|
|
1543
|
+
if (typeof found.resourceId !== "string" || found.resourceId === "") throw new Error(`${api} returned no resource id`);
|
|
1544
|
+
return found.resourceId;
|
|
1545
|
+
}
|
|
1546
|
+
async readCurrentStateDBInstance(physicalId) {
|
|
1547
|
+
let inst;
|
|
1548
|
+
try {
|
|
1549
|
+
inst = await this.describeDBInstance(physicalId);
|
|
1550
|
+
} catch (err) {
|
|
1551
|
+
if (err?.name === "DBInstanceNotFoundFault") return RESOURCE_NOT_FOUND;
|
|
1552
|
+
if (this.isNotFoundError(err, "DBInstanceNotFoundFault")) return void 0;
|
|
1553
|
+
throw err;
|
|
1554
|
+
}
|
|
1555
|
+
if (!inst) return RESOURCE_NOT_FOUND;
|
|
1556
|
+
const result = {};
|
|
1557
|
+
if (inst.DBInstanceIdentifier !== void 0) result["DBInstanceIdentifier"] = inst.DBInstanceIdentifier;
|
|
1558
|
+
if (inst.DBInstanceClass !== void 0) result["DBInstanceClass"] = inst.DBInstanceClass;
|
|
1559
|
+
if (inst.Engine !== void 0) result["Engine"] = inst.Engine;
|
|
1560
|
+
if (inst.DBClusterIdentifier !== void 0) result["DBClusterIdentifier"] = inst.DBClusterIdentifier;
|
|
1561
|
+
if (inst.DBSubnetGroup?.DBSubnetGroupName !== void 0) result["DBSubnetGroupName"] = inst.DBSubnetGroup.DBSubnetGroupName;
|
|
1562
|
+
if (inst.PubliclyAccessible !== void 0) result["PubliclyAccessible"] = inst.PubliclyAccessible;
|
|
1563
|
+
if (inst.AllocatedStorage !== void 0) result["AllocatedStorage"] = inst.AllocatedStorage;
|
|
1564
|
+
if (inst.MasterUsername !== void 0) result["MasterUsername"] = inst.MasterUsername;
|
|
1565
|
+
if (inst.DeletionProtection !== void 0) result["DeletionProtection"] = inst.DeletionProtection;
|
|
1566
|
+
if (inst.EngineVersion !== void 0) result["EngineVersion"] = inst.EngineVersion;
|
|
1567
|
+
if (inst.Endpoint?.Port !== void 0) result["Port"] = inst.Endpoint.Port;
|
|
1568
|
+
if (inst.StorageEncrypted !== void 0) result["StorageEncrypted"] = inst.StorageEncrypted;
|
|
1569
|
+
const sgIds = (inst.VpcSecurityGroups ?? []).map((sg) => sg.VpcSecurityGroupId).filter((id) => !!id);
|
|
1570
|
+
if (sgIds.length > 0) result["VPCSecurityGroups"] = sgIds;
|
|
1571
|
+
if (inst.KmsKeyId !== void 0) result["KmsKeyId"] = inst.KmsKeyId;
|
|
1572
|
+
if (inst.MonitoringRoleArn !== void 0) result["MonitoringRoleArn"] = inst.MonitoringRoleArn;
|
|
1573
|
+
if (inst.MonitoringInterval !== void 0) result["MonitoringInterval"] = inst.MonitoringInterval;
|
|
1574
|
+
if (inst.IAMDatabaseAuthenticationEnabled !== void 0) result["EnableIAMDatabaseAuthentication"] = inst.IAMDatabaseAuthenticationEnabled;
|
|
1575
|
+
if (inst.MasterUserSecret?.KmsKeyId !== void 0) result["MasterUserSecret"] = { KmsKeyId: inst.MasterUserSecret.KmsKeyId };
|
|
1576
|
+
for (const key of [
|
|
1577
|
+
"BackupRetentionPeriod",
|
|
1578
|
+
"PreferredBackupWindow",
|
|
1579
|
+
"PreferredMaintenanceWindow",
|
|
1580
|
+
"StorageType",
|
|
1581
|
+
"MultiAZ",
|
|
1582
|
+
"MaxAllocatedStorage",
|
|
1583
|
+
"AutoMinorVersionUpgrade",
|
|
1584
|
+
"CopyTagsToSnapshot",
|
|
1585
|
+
"PerformanceInsightsKMSKeyId",
|
|
1586
|
+
"PerformanceInsightsRetentionPeriod",
|
|
1587
|
+
"Iops",
|
|
1588
|
+
"StorageThroughput",
|
|
1589
|
+
"CACertificateIdentifier",
|
|
1590
|
+
"NetworkType",
|
|
1591
|
+
"LicenseModel",
|
|
1592
|
+
"PromotionTier",
|
|
1593
|
+
"DatabaseInsightsMode",
|
|
1594
|
+
"DedicatedLogVolume"
|
|
1595
|
+
]) if (inst[key] !== void 0) result[key] = inst[key];
|
|
1596
|
+
if (inst.PerformanceInsightsEnabled !== void 0) result["EnablePerformanceInsights"] = inst.PerformanceInsightsEnabled;
|
|
1597
|
+
const memberships = inst.OptionGroupMemberships ?? [];
|
|
1598
|
+
const optionGroupName = (memberships.find((m) => m.Status === "in-sync" || m.Status === "pending-apply") ?? memberships[0])?.OptionGroupName;
|
|
1599
|
+
if (optionGroupName !== void 0) result["OptionGroupName"] = optionGroupName;
|
|
1600
|
+
const parameterGroupName = inst.DBParameterGroups?.[0]?.DBParameterGroupName;
|
|
1601
|
+
if (parameterGroupName !== void 0) result["DBParameterGroupName"] = parameterGroupName;
|
|
1602
|
+
if (inst.DBClusterIdentifier === void 0) {
|
|
1603
|
+
const instanceLogs = liveLogExports(inst.EnabledCloudwatchLogsExports, inst.PendingModifiedValues?.PendingCloudwatchLogsExports);
|
|
1604
|
+
if (instanceLogs.length > 0) result["EnableCloudwatchLogsExports"] = instanceLogs;
|
|
1605
|
+
}
|
|
1606
|
+
if (inst.DBName !== void 0) result["DBName"] = inst.DBName;
|
|
1607
|
+
if (inst.AvailabilityZone !== void 0 && inst.MultiAZ !== true) result["AvailabilityZone"] = inst.AvailabilityZone;
|
|
1608
|
+
if (inst.DBInstanceArn) await this.attachTags(result, inst.DBInstanceArn);
|
|
1609
|
+
return result;
|
|
1610
|
+
}
|
|
1611
|
+
async readCurrentStateDBCluster(physicalId) {
|
|
1612
|
+
let cluster;
|
|
1613
|
+
try {
|
|
1614
|
+
cluster = await this.describeDBCluster(physicalId);
|
|
1615
|
+
} catch (err) {
|
|
1616
|
+
if (err?.name === "DBClusterNotFoundFault") return RESOURCE_NOT_FOUND;
|
|
1617
|
+
if (this.isNotFoundError(err, "DBClusterNotFoundFault")) return void 0;
|
|
1618
|
+
throw err;
|
|
1619
|
+
}
|
|
1620
|
+
if (!cluster) return RESOURCE_NOT_FOUND;
|
|
1621
|
+
const result = {};
|
|
1622
|
+
if (cluster.DBClusterIdentifier !== void 0) result["DBClusterIdentifier"] = cluster.DBClusterIdentifier;
|
|
1623
|
+
if (cluster.Engine !== void 0) result["Engine"] = cluster.Engine;
|
|
1624
|
+
if (cluster.EngineVersion !== void 0) result["EngineVersion"] = cluster.EngineVersion;
|
|
1625
|
+
if (cluster.MasterUsername !== void 0) result["MasterUsername"] = cluster.MasterUsername;
|
|
1626
|
+
if (cluster.DatabaseName !== void 0) result["DatabaseName"] = cluster.DatabaseName;
|
|
1627
|
+
if (cluster.Port !== void 0) result["Port"] = cluster.Port;
|
|
1628
|
+
result["VpcSecurityGroupIds"] = (cluster.VpcSecurityGroups ?? []).map((sg) => sg.VpcSecurityGroupId).filter((id) => !!id);
|
|
1629
|
+
if (cluster.DBSubnetGroup !== void 0) result["DBSubnetGroupName"] = cluster.DBSubnetGroup;
|
|
1630
|
+
if (cluster.StorageEncrypted !== void 0) result["StorageEncrypted"] = cluster.StorageEncrypted;
|
|
1631
|
+
if (cluster.KmsKeyId !== void 0) result["KmsKeyId"] = cluster.KmsKeyId;
|
|
1632
|
+
if (cluster.BackupRetentionPeriod !== void 0) result["BackupRetentionPeriod"] = cluster.BackupRetentionPeriod;
|
|
1633
|
+
if (cluster.DeletionProtection !== void 0) result["DeletionProtection"] = cluster.DeletionProtection;
|
|
1634
|
+
if (cluster.MonitoringRoleArn !== void 0) result["MonitoringRoleArn"] = cluster.MonitoringRoleArn;
|
|
1635
|
+
if (cluster.MonitoringInterval !== void 0) result["MonitoringInterval"] = cluster.MonitoringInterval;
|
|
1636
|
+
if (cluster.IAMDatabaseAuthenticationEnabled !== void 0) result["EnableIAMDatabaseAuthentication"] = cluster.IAMDatabaseAuthenticationEnabled;
|
|
1637
|
+
if (cluster.PubliclyAccessible !== void 0) result["PubliclyAccessible"] = cluster.PubliclyAccessible;
|
|
1638
|
+
if (cluster.MasterUserSecret?.KmsKeyId !== void 0) result["MasterUserSecret"] = { KmsKeyId: cluster.MasterUserSecret.KmsKeyId };
|
|
1639
|
+
if (cluster.ServerlessV2ScalingConfiguration?.MinCapacity !== void 0 || cluster.ServerlessV2ScalingConfiguration?.MaxCapacity !== void 0) {
|
|
1640
|
+
const sc = {};
|
|
1641
|
+
if (cluster.ServerlessV2ScalingConfiguration?.MinCapacity !== void 0) sc["MinCapacity"] = cluster.ServerlessV2ScalingConfiguration.MinCapacity;
|
|
1642
|
+
if (cluster.ServerlessV2ScalingConfiguration?.MaxCapacity !== void 0) sc["MaxCapacity"] = cluster.ServerlessV2ScalingConfiguration.MaxCapacity;
|
|
1643
|
+
if (cluster.ServerlessV2ScalingConfiguration?.SecondsUntilAutoPause !== void 0) sc["SecondsUntilAutoPause"] = cluster.ServerlessV2ScalingConfiguration.SecondsUntilAutoPause;
|
|
1644
|
+
result["ServerlessV2ScalingConfiguration"] = sc;
|
|
1645
|
+
}
|
|
1646
|
+
if (cluster.DBClusterParameterGroup !== void 0) result["DBClusterParameterGroupName"] = cluster.DBClusterParameterGroup;
|
|
1647
|
+
if (cluster.PreferredBackupWindow !== void 0) result["PreferredBackupWindow"] = cluster.PreferredBackupWindow;
|
|
1648
|
+
if (cluster.PreferredMaintenanceWindow !== void 0) result["PreferredMaintenanceWindow"] = cluster.PreferredMaintenanceWindow;
|
|
1649
|
+
if (cluster.CopyTagsToSnapshot !== void 0) result["CopyTagsToSnapshot"] = cluster.CopyTagsToSnapshot;
|
|
1650
|
+
const storageType = cluster.PendingModifiedValues?.StorageType ?? cluster.StorageType ?? (cluster.Engine?.toLowerCase().startsWith("aurora") ? "aurora" : void 0);
|
|
1651
|
+
if (storageType !== void 0) result["StorageType"] = storageType;
|
|
1652
|
+
const clusterLogs = liveLogExports(cluster.EnabledCloudwatchLogsExports, cluster.PendingModifiedValues?.PendingCloudwatchLogsExports);
|
|
1653
|
+
if (clusterLogs.length > 0) result["EnableCloudwatchLogsExports"] = clusterLogs;
|
|
1654
|
+
if (cluster.AutoMinorVersionUpgrade !== void 0) result["AutoMinorVersionUpgrade"] = cluster.AutoMinorVersionUpgrade;
|
|
1655
|
+
if (cluster.NetworkType !== void 0) result["NetworkType"] = cluster.NetworkType;
|
|
1656
|
+
if (cluster.PerformanceInsightsEnabled !== void 0) result["PerformanceInsightsEnabled"] = cluster.PerformanceInsightsEnabled;
|
|
1657
|
+
if (cluster.PerformanceInsightsKMSKeyId !== void 0) result["PerformanceInsightsKmsKeyId"] = cluster.PerformanceInsightsKMSKeyId;
|
|
1658
|
+
if (cluster.PerformanceInsightsRetentionPeriod !== void 0) result["PerformanceInsightsRetentionPeriod"] = cluster.PerformanceInsightsRetentionPeriod;
|
|
1659
|
+
if (cluster.HttpEndpointEnabled !== void 0) result["EnableHttpEndpoint"] = cluster.HttpEndpointEnabled;
|
|
1660
|
+
if (cluster.DBClusterArn) await this.attachTags(result, cluster.DBClusterArn);
|
|
1661
|
+
return result;
|
|
1662
|
+
}
|
|
1663
|
+
async readCurrentStateDBSubnetGroup(physicalId) {
|
|
1664
|
+
let resp;
|
|
1665
|
+
try {
|
|
1666
|
+
resp = await this.getClient().send(new DescribeDBSubnetGroupsCommand({ DBSubnetGroupName: physicalId }));
|
|
1667
|
+
} catch (err) {
|
|
1668
|
+
if (err?.name === "DBSubnetGroupNotFoundFault") return RESOURCE_NOT_FOUND;
|
|
1669
|
+
if (this.isNotFoundError(err, "DBSubnetGroupNotFoundFault")) return void 0;
|
|
1670
|
+
throw err;
|
|
1671
|
+
}
|
|
1672
|
+
const sg = resp.DBSubnetGroups?.[0];
|
|
1673
|
+
if (!sg) return RESOURCE_NOT_FOUND;
|
|
1674
|
+
const result = {};
|
|
1675
|
+
if (sg.DBSubnetGroupName !== void 0) result["DBSubnetGroupName"] = sg.DBSubnetGroupName;
|
|
1676
|
+
if (sg.DBSubnetGroupDescription !== void 0) result["DBSubnetGroupDescription"] = sg.DBSubnetGroupDescription;
|
|
1677
|
+
result["SubnetIds"] = (sg.Subnets ?? []).map((s) => s.SubnetIdentifier).filter((id) => !!id);
|
|
1678
|
+
if (sg.DBSubnetGroupArn) await this.attachTags(result, sg.DBSubnetGroupArn);
|
|
1679
|
+
return result;
|
|
1680
|
+
}
|
|
1681
|
+
async attachTags(result, arn) {
|
|
1682
|
+
try {
|
|
1683
|
+
const tagsResp = await this.getClient().send(new ListTagsForResourceCommand({ ResourceName: arn }));
|
|
1684
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.TagList);
|
|
1685
|
+
} catch (err) {
|
|
1686
|
+
this.logger.debug(`RDS ListTagsForResource(${arn}) failed: ${describeAwsFailure(err).detail}`);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
async importDBInstance(input) {
|
|
1690
|
+
const explicit = resolveExplicitPhysicalId(input, "DBInstanceIdentifier");
|
|
1691
|
+
if (explicit) try {
|
|
1692
|
+
const described = (await this.getClient().send(new DescribeDBInstancesCommand({ DBInstanceIdentifier: explicit }))).DBInstances?.[0];
|
|
1693
|
+
return {
|
|
1694
|
+
physicalId: explicit,
|
|
1695
|
+
attributes: definedAttributes({
|
|
1696
|
+
"Endpoint.Address": described?.Endpoint?.Address,
|
|
1697
|
+
"Endpoint.Port": stringifyIfAssigned(described?.Endpoint?.Port),
|
|
1698
|
+
Arn: described?.DBInstanceArn
|
|
1699
|
+
})
|
|
1700
|
+
};
|
|
1701
|
+
} catch (err) {
|
|
1702
|
+
if (err.name === "DBInstanceNotFoundFault") return null;
|
|
1703
|
+
throw err;
|
|
1704
|
+
}
|
|
1705
|
+
return null;
|
|
1706
|
+
}
|
|
1707
|
+
async importDBCluster(input) {
|
|
1708
|
+
const explicit = resolveExplicitPhysicalId(input, "DBClusterIdentifier");
|
|
1709
|
+
if (explicit) try {
|
|
1710
|
+
const described = (await this.getClient().send(new DescribeDBClustersCommand({ DBClusterIdentifier: explicit }))).DBClusters?.[0];
|
|
1711
|
+
return {
|
|
1712
|
+
physicalId: explicit,
|
|
1713
|
+
attributes: definedAttributes({
|
|
1714
|
+
"Endpoint.Address": described?.Endpoint,
|
|
1715
|
+
"Endpoint.Port": stringifyIfAssigned(described?.Port),
|
|
1716
|
+
"ReadEndpoint.Address": described?.ReaderEndpoint,
|
|
1717
|
+
Arn: described?.DBClusterArn,
|
|
1718
|
+
DBClusterResourceId: described?.DbClusterResourceId
|
|
1719
|
+
})
|
|
1720
|
+
};
|
|
1721
|
+
} catch (err) {
|
|
1722
|
+
if (err.name === "DBClusterNotFoundFault") return null;
|
|
1723
|
+
throw err;
|
|
1724
|
+
}
|
|
1725
|
+
return null;
|
|
1726
|
+
}
|
|
1727
|
+
async importDBSubnetGroup(input) {
|
|
1728
|
+
const explicit = resolveExplicitPhysicalId(input, "DBSubnetGroupName");
|
|
1729
|
+
if (explicit) try {
|
|
1730
|
+
const arn = (await this.getClient().send(new DescribeDBSubnetGroupsCommand({ DBSubnetGroupName: explicit }))).DBSubnetGroups?.[0]?.DBSubnetGroupArn;
|
|
1731
|
+
return {
|
|
1732
|
+
physicalId: explicit,
|
|
1733
|
+
attributes: { ...arn !== void 0 && { DBSubnetGroupArn: arn } }
|
|
1734
|
+
};
|
|
1735
|
+
} catch (err) {
|
|
1736
|
+
if (err.name === "DBSubnetGroupNotFoundFault") return null;
|
|
1737
|
+
throw err;
|
|
1738
|
+
}
|
|
1739
|
+
return null;
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
function securityGroupListChanged(sent, previous) {
|
|
1743
|
+
if (sent === void 0) return false;
|
|
1744
|
+
const before = Array.isArray(previous) ? previous.filter((id) => typeof id === "string") : [];
|
|
1745
|
+
return [...sent].sort().join(" ") !== [...before].sort().join(" ");
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
export { RDSProvider };
|
|
1749
|
+
//# sourceMappingURL=rds-provider-C8enyD9d.js.map
|