@cdkd/cli 0.0.0-stage → 0.323.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +179 -2
- package/dist/asg-provider-4rNFSmz9.js +1277 -0
- package/dist/asg-provider-4rNFSmz9.js.map +1 -0
- package/dist/aws-client-defaults-BLXFsfMa.js +275 -0
- package/dist/aws-client-defaults-BLXFsfMa.js.map +1 -0
- package/dist/aws-clients-DWpHZk2l.js +440 -0
- package/dist/aws-clients-DWpHZk2l.js.map +1 -0
- package/dist/aws-failure-text-DiHodi76.js +1158 -0
- package/dist/aws-failure-text-DiHodi76.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +135 -0
- package/dist/cli.js.map +1 -0
- package/dist/created-resource-sink-CmrPwGge.js +2343 -0
- package/dist/created-resource-sink-CmrPwGge.js.map +1 -0
- package/dist/db-instance-modify-retry-DCLA_p9I.js +43 -0
- package/dist/db-instance-modify-retry-DCLA_p9I.js.map +1 -0
- package/dist/deploy-engine-dYZ0v6Kz.js +55863 -0
- package/dist/deploy-engine-dYZ0v6Kz.js.map +1 -0
- package/dist/iam-role-provider-Bp4osNrj.js +698 -0
- package/dist/iam-role-provider-Bp4osNrj.js.map +1 -0
- package/dist/index.d.ts +1629 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/neptune-provider-BqtKFZaE.js +1470 -0
- package/dist/neptune-provider-BqtKFZaE.js.map +1 -0
- package/dist/program-BYT8z2ca.js +31547 -0
- package/dist/program-BYT8z2ca.js.map +1 -0
- package/dist/provider-classes-KPYbVTtY.js +55302 -0
- package/dist/provider-classes-KPYbVTtY.js.map +1 -0
- package/dist/rds-provider-C8enyD9d.js +1749 -0
- package/dist/rds-provider-C8enyD9d.js.map +1 -0
- package/dist/version-kAoaNHQI.js +11 -0
- package/dist/version-kAoaNHQI.js.map +1 -0
- package/package.json +145 -4
|
@@ -0,0 +1,1470 @@
|
|
|
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, Ft as maskerOrIdentity, Gt as markAuxiliaryFailure, 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, Z as isProceedDbStatus, fn as ambientClientDefaults, g as clearOnUpdateRemoval, hr as relayedNameCollision, i as reportCreatedResource, j as withoutServerErrorRetries, jr as markNonRetryable, nt as protectionFlipKey, rt as rdsFamilyProtectionSite, tt as observeThenDisableProtection, y as removedTemplateKeys } 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, Na as isUsableAttemptedName, Qi as definedAttributes, Vr as refuseRestoreFromItself, Yi as refuseMalformedDesiredTags, Zi as tagPlanWarning, fc as configBooleanRefusal, qi as planTagDiff, uc as coerceCfnBoolean } from "./deploy-engine-dYZ0v6Kz.js";
|
|
5
|
+
import { n as modifyDbInstanceWhenAvailable, t as isModifyNotAvailableRefusal } 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 { AddRoleToDBClusterCommand, AddTagsToResourceCommand, CreateDBClusterCommand, CreateDBInstanceCommand, CreateDBSubnetGroupCommand, DeleteDBClusterCommand, DeleteDBInstanceCommand, DeleteDBSubnetGroupCommand, DescribeDBClustersCommand, DescribeDBInstancesCommand, DescribeDBSubnetGroupsCommand, ListTagsForResourceCommand, ModifyDBClusterCommand, ModifyDBInstanceCommand, ModifyDBSubnetGroupCommand, NeptuneClient, RemoveRoleFromDBClusterCommand, RemoveTagsFromResourceCommand, RestoreDBClusterFromSnapshotCommand, RestoreDBClusterToPointInTimeCommand } from "@aws-sdk/client-neptune";
|
|
9
|
+
|
|
10
|
+
// src/provisioning/providers/neptune-provider.ts
|
|
11
|
+
function clusterAttributes(cluster) {
|
|
12
|
+
return definedAttributes({
|
|
13
|
+
Endpoint: cluster?.Endpoint,
|
|
14
|
+
Port: stringifyIfAssigned(cluster?.Port),
|
|
15
|
+
ReadEndpoint: cluster?.ReaderEndpoint,
|
|
16
|
+
"Endpoint.Address": cluster?.Endpoint,
|
|
17
|
+
"Endpoint.Port": stringifyIfAssigned(cluster?.Port),
|
|
18
|
+
"ReadEndpoint.Address": cluster?.ReaderEndpoint,
|
|
19
|
+
ClusterResourceId: cluster?.DbClusterResourceId
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function instanceAttributes(instance) {
|
|
23
|
+
return definedAttributes({
|
|
24
|
+
Endpoint: instance?.Endpoint?.Address,
|
|
25
|
+
Port: stringifyIfAssigned(instance?.Endpoint?.Port),
|
|
26
|
+
"Endpoint.Address": instance?.Endpoint?.Address,
|
|
27
|
+
"Endpoint.Port": stringifyIfAssigned(instance?.Endpoint?.Port),
|
|
28
|
+
Arn: instance?.DBInstanceArn
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function isDbIdentifier(id) {
|
|
32
|
+
return id.length <= 63 && /^[A-Za-z](?:-?[A-Za-z0-9])*$/.test(id);
|
|
33
|
+
}
|
|
34
|
+
const CLUSTER_KEPT_ON_REMOVAL = [
|
|
35
|
+
"EngineVersion",
|
|
36
|
+
"PreferredBackupWindow",
|
|
37
|
+
"PreferredMaintenanceWindow",
|
|
38
|
+
"DBClusterParameterGroupName",
|
|
39
|
+
"DeletionProtection",
|
|
40
|
+
"IamAuthEnabled",
|
|
41
|
+
"BackupRetentionPeriod",
|
|
42
|
+
"DBPort",
|
|
43
|
+
"Port",
|
|
44
|
+
"CopyTagsToSnapshot"
|
|
45
|
+
];
|
|
46
|
+
const INSTANCE_KEPT_ON_REMOVAL = [
|
|
47
|
+
"DBParameterGroupName",
|
|
48
|
+
"PreferredMaintenanceWindow",
|
|
49
|
+
"AutoMinorVersionUpgrade"
|
|
50
|
+
];
|
|
51
|
+
function isPlainObject(value) {
|
|
52
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
53
|
+
}
|
|
54
|
+
function clusterRoleKey(role) {
|
|
55
|
+
return `${role.RoleArn ?? ""}\n${role.FeatureName ?? ""}`;
|
|
56
|
+
}
|
|
57
|
+
function clusterRoleList(value) {
|
|
58
|
+
if (value == null) return [];
|
|
59
|
+
if (!Array.isArray(value)) return void 0;
|
|
60
|
+
const roles = /* @__PURE__ */ new Map();
|
|
61
|
+
for (const entry of value) {
|
|
62
|
+
if (!isPlainObject(entry)) return void 0;
|
|
63
|
+
const { RoleArn: roleArn, FeatureName: featureName } = entry;
|
|
64
|
+
if (typeof roleArn !== "string" || roleArn === "") return void 0;
|
|
65
|
+
if (featureName != null && typeof featureName !== "string") return void 0;
|
|
66
|
+
const role = {
|
|
67
|
+
RoleArn: roleArn,
|
|
68
|
+
...featureName ? { FeatureName: featureName } : {}
|
|
69
|
+
};
|
|
70
|
+
roles.set(clusterRoleKey(role), role);
|
|
71
|
+
}
|
|
72
|
+
return [...roles.values()];
|
|
73
|
+
}
|
|
74
|
+
function stringList(value) {
|
|
75
|
+
if (value == null) return [];
|
|
76
|
+
if (!Array.isArray(value)) return void 0;
|
|
77
|
+
return value.every((item) => typeof item === "string") ? value : void 0;
|
|
78
|
+
}
|
|
79
|
+
function logExportChange(desired, previous) {
|
|
80
|
+
const enable = desired.filter((type) => !previous.includes(type));
|
|
81
|
+
const disable = previous.filter((type) => !desired.includes(type));
|
|
82
|
+
if (enable.length === 0 && disable.length === 0) return void 0;
|
|
83
|
+
return {
|
|
84
|
+
...enable.length > 0 && { EnableLogTypes: enable },
|
|
85
|
+
...disable.length > 0 && { DisableLogTypes: disable }
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function serverlessScalingToSend(value) {
|
|
89
|
+
if (value == null) return void 0;
|
|
90
|
+
if (!isPlainObject(value)) return null;
|
|
91
|
+
const min = cfnCapacity(value["MinCapacity"]);
|
|
92
|
+
const max = cfnCapacity(value["MaxCapacity"]);
|
|
93
|
+
if (min === void 0 || max === void 0) return null;
|
|
94
|
+
return {
|
|
95
|
+
MinCapacity: min,
|
|
96
|
+
MaxCapacity: max
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function cfnCapacity(value) {
|
|
100
|
+
const capacity = typeof value === "number" || typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
|
|
101
|
+
return Number.isFinite(capacity) ? capacity : void 0;
|
|
102
|
+
}
|
|
103
|
+
function isMajorEngineChange(previous, desired) {
|
|
104
|
+
if (typeof previous !== "string" || typeof desired !== "string") return false;
|
|
105
|
+
const major = (version) => version.split(".").slice(0, 2).join(".");
|
|
106
|
+
return major(previous) !== major(desired);
|
|
107
|
+
}
|
|
108
|
+
function holdsPendingValue(value) {
|
|
109
|
+
if (value === void 0 || value === null) return false;
|
|
110
|
+
if (typeof value === "object") return Object.values(value).some(holdsPendingValue);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
function isUnmovingDbStatus(status) {
|
|
114
|
+
return isProceedDbStatus(status) || status === "backing-up";
|
|
115
|
+
}
|
|
116
|
+
function isRoleCallNotAvailableRefusal(error) {
|
|
117
|
+
return error instanceof Error && error.name === "InvalidDBClusterStateFault" && /is not available for IAM role association, disassociation, or updates/i.test(error.message);
|
|
118
|
+
}
|
|
119
|
+
const SETTLE_MAX_WAIT_MS = 3e5;
|
|
120
|
+
const ROLE_AVAILABLE_MAX_WAIT_MS = 18e5;
|
|
121
|
+
const CLUSTER_AVAILABLE_MAX_WAIT_MS = 18e5;
|
|
122
|
+
function isRestoringClusterRefusal(error) {
|
|
123
|
+
return error instanceof Error && error.name === "InvalidDBClusterStateFault" && /(?:isn't|is not|isn’t) available for modification with status/i.test(error.message);
|
|
124
|
+
}
|
|
125
|
+
const DEADLINE_MARGIN_MS = 3e4;
|
|
126
|
+
const DEFAULT_CLUSTER_PORT = 8182;
|
|
127
|
+
const DEFAULT_STORAGE_KEY = "alias/aws/rds";
|
|
128
|
+
const DEFAULT_RESTORED_BACKUP_RETENTION = 1;
|
|
129
|
+
function restoreWaitMs() {
|
|
130
|
+
const remaining = enclosingDeadlineRemainingMs() - DEADLINE_MARGIN_MS;
|
|
131
|
+
return Number.isFinite(remaining) ? Math.max(CLUSTER_AVAILABLE_MAX_WAIT_MS, remaining) : CLUSTER_AVAILABLE_MAX_WAIT_MS;
|
|
132
|
+
}
|
|
133
|
+
const RESTORE_VALUE_REFUSALS = /* @__PURE__ */ new Set(["InvalidParameterValue", "InvalidParameterCombination"]);
|
|
134
|
+
function readClusterRestore(properties, resourceType, logicalId) {
|
|
135
|
+
const refuse = (key, expected) => {
|
|
136
|
+
throw markNonRetryable(new ProvisioningError(safeMsg`Neptune DBCluster ${logicalId}: ${key} must be ${expected}; nothing was changed.`, resourceType, logicalId));
|
|
137
|
+
};
|
|
138
|
+
const identifier = (key) => {
|
|
139
|
+
const value = properties[key];
|
|
140
|
+
if (value == null) return void 0;
|
|
141
|
+
return typeof value === "string" ? value : refuse(key, "a string");
|
|
142
|
+
};
|
|
143
|
+
const snapshotIdentifier = identifier("SnapshotIdentifier") || void 0;
|
|
144
|
+
const sourceDBClusterIdentifier = identifier("SourceDBClusterIdentifier") || void 0;
|
|
145
|
+
if (sourceDBClusterIdentifier !== void 0) {
|
|
146
|
+
const declared = properties["RestoreToTime"];
|
|
147
|
+
const restoreToTime = declared == null ? void 0 : new Date(typeof declared !== "string" ? NaN : /(?:z|[+-]\d{2}:?\d{2})$/i.test(declared) ? declared : `${declared}Z`);
|
|
148
|
+
if (restoreToTime !== void 0 && Number.isNaN(restoreToTime.getTime())) return refuse("RestoreToTime", "a timestamp");
|
|
149
|
+
return {
|
|
150
|
+
kind: "point-in-time",
|
|
151
|
+
sourceDBClusterIdentifier,
|
|
152
|
+
restoreToTime
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return snapshotIdentifier === void 0 ? void 0 : {
|
|
156
|
+
kind: "snapshot",
|
|
157
|
+
snapshotIdentifier
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function ignoredRestoreKeys(restore) {
|
|
161
|
+
if (restore?.kind === "point-in-time") return ["SnapshotIdentifier", "AvailabilityZones"];
|
|
162
|
+
const pointInTimeKeys = [
|
|
163
|
+
"RestoreToTime",
|
|
164
|
+
"RestoreType",
|
|
165
|
+
"UseLatestRestorableTime"
|
|
166
|
+
];
|
|
167
|
+
return restore === void 0 ? [
|
|
168
|
+
"SnapshotIdentifier",
|
|
169
|
+
"SourceDBClusterIdentifier",
|
|
170
|
+
...pointInTimeKeys
|
|
171
|
+
] : ["SourceDBClusterIdentifier", ...pointInTimeKeys];
|
|
172
|
+
}
|
|
173
|
+
function declaresSnapshot(properties) {
|
|
174
|
+
return properties["SnapshotIdentifier"] != null && properties["SnapshotIdentifier"] !== "";
|
|
175
|
+
}
|
|
176
|
+
function isRestoredRecord(previous) {
|
|
177
|
+
return declaresSnapshot(previous) || previous["SourceDBClusterIdentifier"] != null && previous["SourceDBClusterIdentifier"] !== "";
|
|
178
|
+
}
|
|
179
|
+
function sentOnUpdate(properties, previous) {
|
|
180
|
+
return (key) => isRestoredRecord(previous) && isDeepStrictEqual(previous[key], properties[key]) ? void 0 : properties[key];
|
|
181
|
+
}
|
|
182
|
+
function restoreFollowUp(properties, restore, restoredEngineVersion) {
|
|
183
|
+
const nonEmpty = (members) => Object.keys(members).length > 0 ? members : void 0;
|
|
184
|
+
const declaredRetention = properties["BackupRetentionPeriod"];
|
|
185
|
+
const retention = declaredRetention != null ? Number(declaredRetention) : void 0;
|
|
186
|
+
const snapshotDeclared = declaresSnapshot(properties);
|
|
187
|
+
const pointInTime = restore.kind === "point-in-time";
|
|
188
|
+
const engineVersion = properties["EngineVersion"];
|
|
189
|
+
const engineChange = pointInTime && engineVersion != null && engineVersion !== restoredEngineVersion;
|
|
190
|
+
const majorUpgrade = engineChange && isMajorEngineChange(restoredEngineVersion, engineVersion);
|
|
191
|
+
return {
|
|
192
|
+
parity: nonEmpty({ ...snapshotDeclared && { BackupRetentionPeriod: retention ?? DEFAULT_RESTORED_BACKUP_RETENTION } }),
|
|
193
|
+
declared: [
|
|
194
|
+
nonEmpty({ ...!snapshotDeclared && retention !== void 0 && { BackupRetentionPeriod: retention } }),
|
|
195
|
+
nonEmpty({
|
|
196
|
+
...properties["PreferredBackupWindow"] != null && { PreferredBackupWindow: properties["PreferredBackupWindow"] },
|
|
197
|
+
...properties["PreferredMaintenanceWindow"] != null && { PreferredMaintenanceWindow: properties["PreferredMaintenanceWindow"] }
|
|
198
|
+
}),
|
|
199
|
+
nonEmpty({ ...pointInTime && properties["CopyTagsToSnapshot"] != null && { CopyTagsToSnapshot: properties["CopyTagsToSnapshot"] } }),
|
|
200
|
+
nonEmpty({
|
|
201
|
+
...pointInTime && properties["DBClusterParameterGroupName"] != null && { DBClusterParameterGroupName: properties["DBClusterParameterGroupName"] },
|
|
202
|
+
...engineChange && { EngineVersion: engineVersion },
|
|
203
|
+
...majorUpgrade && { AllowMajorVersionUpgrade: true },
|
|
204
|
+
...majorUpgrade && properties["DBInstanceParameterGroupName"] != null && { DBInstanceParameterGroupName: properties["DBInstanceParameterGroupName"] }
|
|
205
|
+
})
|
|
206
|
+
].filter((group) => group !== void 0)
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const CLUSTER_MODIFY_KEYS = [
|
|
210
|
+
"EngineVersion",
|
|
211
|
+
"DeletionProtection",
|
|
212
|
+
"BackupRetentionPeriod",
|
|
213
|
+
"PreferredBackupWindow",
|
|
214
|
+
"PreferredMaintenanceWindow",
|
|
215
|
+
"DBClusterParameterGroupName",
|
|
216
|
+
"IamAuthEnabled",
|
|
217
|
+
"VpcSecurityGroupIds",
|
|
218
|
+
"DBPort",
|
|
219
|
+
"Port",
|
|
220
|
+
"CopyTagsToSnapshot",
|
|
221
|
+
"NetworkType",
|
|
222
|
+
"ServerlessScalingConfiguration",
|
|
223
|
+
"EnableCloudwatchLogsExports"
|
|
224
|
+
];
|
|
225
|
+
const INSTANCE_MODIFY_KEYS = [
|
|
226
|
+
"DBInstanceClass",
|
|
227
|
+
"DBParameterGroupName",
|
|
228
|
+
"PreferredMaintenanceWindow",
|
|
229
|
+
"AutoMinorVersionUpgrade",
|
|
230
|
+
"DeletionProtection",
|
|
231
|
+
"PubliclyAccessible"
|
|
232
|
+
];
|
|
233
|
+
function markChangedUnsettled(keys, properties, previousProperties, context) {
|
|
234
|
+
for (const key of keys) if (!isDeepStrictEqual(properties[key], previousProperties[key])) context?.markUnsettled?.(key);
|
|
235
|
+
}
|
|
236
|
+
const CLUSTER_UNMEASURED_ON_REMOVAL = ["NetworkType", "ServerlessScalingConfiguration"];
|
|
237
|
+
var NeptuneProvider = class {
|
|
238
|
+
neptuneClient;
|
|
239
|
+
createClient;
|
|
240
|
+
ec2Client;
|
|
241
|
+
providerRegion;
|
|
242
|
+
logger = getLogger().child("NeptuneProvider");
|
|
243
|
+
protectionFlips = new ProtectionFlipRegistry();
|
|
244
|
+
handledProperties = /* @__PURE__ */ new Map([
|
|
245
|
+
["AWS::Neptune::DBSubnetGroup", /* @__PURE__ */ new Set([
|
|
246
|
+
"DBSubnetGroupName",
|
|
247
|
+
"DBSubnetGroupDescription",
|
|
248
|
+
"SubnetIds",
|
|
249
|
+
"Tags"
|
|
250
|
+
])],
|
|
251
|
+
["AWS::Neptune::DBCluster", /* @__PURE__ */ new Set([
|
|
252
|
+
"DBClusterIdentifier",
|
|
253
|
+
"EngineVersion",
|
|
254
|
+
"DBPort",
|
|
255
|
+
"Port",
|
|
256
|
+
"VpcSecurityGroupIds",
|
|
257
|
+
"DBSubnetGroupName",
|
|
258
|
+
"StorageEncrypted",
|
|
259
|
+
"KmsKeyId",
|
|
260
|
+
"BackupRetentionPeriod",
|
|
261
|
+
"PreferredBackupWindow",
|
|
262
|
+
"PreferredMaintenanceWindow",
|
|
263
|
+
"DBClusterParameterGroupName",
|
|
264
|
+
"IamAuthEnabled",
|
|
265
|
+
"DeletionProtection",
|
|
266
|
+
"Tags",
|
|
267
|
+
"AssociatedRoles",
|
|
268
|
+
"AvailabilityZones",
|
|
269
|
+
"CopyTagsToSnapshot",
|
|
270
|
+
"DBInstanceParameterGroupName",
|
|
271
|
+
"EnableCloudwatchLogsExports",
|
|
272
|
+
"NetworkType",
|
|
273
|
+
"ServerlessScalingConfiguration",
|
|
274
|
+
"RestoreToTime",
|
|
275
|
+
"RestoreType",
|
|
276
|
+
"SnapshotIdentifier",
|
|
277
|
+
"SourceDBClusterIdentifier",
|
|
278
|
+
"UseLatestRestorableTime"
|
|
279
|
+
])],
|
|
280
|
+
["AWS::Neptune::DBInstance", /* @__PURE__ */ new Set([
|
|
281
|
+
"DBInstanceIdentifier",
|
|
282
|
+
"DBInstanceClass",
|
|
283
|
+
"DBClusterIdentifier",
|
|
284
|
+
"DBSubnetGroupName",
|
|
285
|
+
"DBParameterGroupName",
|
|
286
|
+
"AvailabilityZone",
|
|
287
|
+
"PreferredMaintenanceWindow",
|
|
288
|
+
"AutoMinorVersionUpgrade",
|
|
289
|
+
"DeletionProtection",
|
|
290
|
+
"Tags",
|
|
291
|
+
"AllowMajorVersionUpgrade",
|
|
292
|
+
"PubliclyAccessible"
|
|
293
|
+
])]
|
|
294
|
+
]);
|
|
295
|
+
unhandledByDesign = /* @__PURE__ */ new Map([["AWS::Neptune::DBCluster", /* @__PURE__ */ new Map([["GlobalClusterIdentifier", "Cloud Control owns a global-cluster member: NeptuneProvider has no detach, and a member cannot be deleted while attached (deletesThroughSdkProvider keeps its delete there too)"]])], ["AWS::Neptune::DBInstance", /* @__PURE__ */ new Map([["DBSnapshotIdentifier", "The CloudFormation schema documents it as not supported, and CreateDBInstance has no such member: a Neptune instance is not restored from a snapshot, its cluster is"]])]]);
|
|
296
|
+
removalHandledInUpdate = /* @__PURE__ */ new Map([["AWS::Neptune::DBCluster", /* @__PURE__ */ new Set([
|
|
297
|
+
"DBClusterIdentifier",
|
|
298
|
+
"DBSubnetGroupName",
|
|
299
|
+
"StorageEncrypted",
|
|
300
|
+
"KmsKeyId",
|
|
301
|
+
"AvailabilityZones",
|
|
302
|
+
"VpcSecurityGroupIds",
|
|
303
|
+
"Tags",
|
|
304
|
+
"AssociatedRoles",
|
|
305
|
+
"EnableCloudwatchLogsExports",
|
|
306
|
+
"DBInstanceParameterGroupName",
|
|
307
|
+
"RestoreToTime",
|
|
308
|
+
"RestoreType",
|
|
309
|
+
"SnapshotIdentifier",
|
|
310
|
+
"SourceDBClusterIdentifier",
|
|
311
|
+
"UseLatestRestorableTime",
|
|
312
|
+
...CLUSTER_KEPT_ON_REMOVAL,
|
|
313
|
+
...CLUSTER_UNMEASURED_ON_REMOVAL
|
|
314
|
+
])], ["AWS::Neptune::DBInstance", /* @__PURE__ */ new Set([
|
|
315
|
+
"DBInstanceIdentifier",
|
|
316
|
+
"DBClusterIdentifier",
|
|
317
|
+
"DBSubnetGroupName",
|
|
318
|
+
"AvailabilityZone",
|
|
319
|
+
"DBInstanceClass",
|
|
320
|
+
"DeletionProtection",
|
|
321
|
+
"Tags",
|
|
322
|
+
"AllowMajorVersionUpgrade",
|
|
323
|
+
"PubliclyAccessible",
|
|
324
|
+
...INSTANCE_KEPT_ON_REMOVAL
|
|
325
|
+
])]]);
|
|
326
|
+
constructor(options) {
|
|
327
|
+
this.providerRegion = options?.region ?? ambientRegion();
|
|
328
|
+
}
|
|
329
|
+
getClient() {
|
|
330
|
+
if (!this.neptuneClient) {
|
|
331
|
+
this.neptuneClient = new NeptuneClient({
|
|
332
|
+
...ambientClientDefaults(),
|
|
333
|
+
...this.providerRegion ? { region: this.providerRegion } : {}
|
|
334
|
+
});
|
|
335
|
+
this.createClient = withoutServerErrorRetries(new NeptuneClient({
|
|
336
|
+
...ambientClientDefaults(),
|
|
337
|
+
...this.providerRegion ? { region: this.providerRegion } : {}
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
return this.neptuneClient;
|
|
341
|
+
}
|
|
342
|
+
getEc2Client() {
|
|
343
|
+
if (!this.ec2Client) this.ec2Client = new EC2Client({
|
|
344
|
+
...ambientClientDefaults(),
|
|
345
|
+
...this.providerRegion ? { region: this.providerRegion } : {}
|
|
346
|
+
});
|
|
347
|
+
return this.ec2Client;
|
|
348
|
+
}
|
|
349
|
+
getCreateClient() {
|
|
350
|
+
this.getClient();
|
|
351
|
+
return this.createClient;
|
|
352
|
+
}
|
|
353
|
+
async create(logicalId, resourceType, properties, context) {
|
|
354
|
+
const mask = context?.maskSecrets;
|
|
355
|
+
switch (resourceType) {
|
|
356
|
+
case "AWS::Neptune::DBSubnetGroup": return this.createDBSubnetGroup(logicalId, resourceType, properties, mask);
|
|
357
|
+
case "AWS::Neptune::DBCluster": return this.createDBCluster(logicalId, resourceType, properties, mask, context?.replayingState === true);
|
|
358
|
+
case "AWS::Neptune::DBInstance": return this.createDBInstance(logicalId, resourceType, properties, mask, context?.replayingState === true);
|
|
359
|
+
default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async update(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
363
|
+
switch (resourceType) {
|
|
364
|
+
case "AWS::Neptune::DBSubnetGroup": return this.updateDBSubnetGroup(logicalId, physicalId, resourceType, properties, previousProperties, context?.maskSecrets);
|
|
365
|
+
case "AWS::Neptune::DBCluster": return this.updateDBCluster(logicalId, physicalId, resourceType, properties, previousProperties, context);
|
|
366
|
+
case "AWS::Neptune::DBInstance": return this.updateDBInstance(logicalId, physicalId, resourceType, properties, previousProperties, context);
|
|
367
|
+
default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId, physicalId);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
async delete(logicalId, physicalId, resourceType, _properties, context) {
|
|
371
|
+
switch (resourceType) {
|
|
372
|
+
case "AWS::Neptune::DBSubnetGroup": return this.deleteDBSubnetGroup(logicalId, physicalId, resourceType, context);
|
|
373
|
+
case "AWS::Neptune::DBCluster": return this.deleteDBCluster(logicalId, physicalId, resourceType, context);
|
|
374
|
+
case "AWS::Neptune::DBInstance": return this.deleteDBInstance(logicalId, physicalId, resourceType, context);
|
|
375
|
+
default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId, physicalId);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async createDBSubnetGroup(logicalId, resourceType, properties, mask) {
|
|
379
|
+
this.logger.debug(`Creating Neptune DBSubnetGroup ${logicalId}`);
|
|
380
|
+
const tags = refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId);
|
|
381
|
+
const dbSubnetGroupName = properties["DBSubnetGroupName"] || generatedNameFor("AWS::Neptune::DBSubnetGroup", logicalId);
|
|
382
|
+
try {
|
|
383
|
+
await this.getCreateClient().send(new CreateDBSubnetGroupCommand({
|
|
384
|
+
DBSubnetGroupName: dbSubnetGroupName,
|
|
385
|
+
DBSubnetGroupDescription: properties["DBSubnetGroupDescription"] || `Subnet group for ${logicalId}`,
|
|
386
|
+
SubnetIds: properties["SubnetIds"],
|
|
387
|
+
...tags.length > 0 && { Tags: tags }
|
|
388
|
+
}));
|
|
389
|
+
this.logger.debug(`Successfully created Neptune DBSubnetGroup ${logicalId}: ${dbSubnetGroupName}`);
|
|
390
|
+
return {
|
|
391
|
+
physicalId: dbSubnetGroupName,
|
|
392
|
+
attributes: { DBSubnetGroupName: dbSubnetGroupName }
|
|
393
|
+
};
|
|
394
|
+
} catch (error) {
|
|
395
|
+
const cause = error instanceof Error ? error : void 0;
|
|
396
|
+
throw this.wrapCaughtError(mask, error, (text) => new ProvisioningError(`Failed to create Neptune DBSubnetGroup ${logicalId}: ${text}`, resourceType, logicalId, dbSubnetGroupName, cause));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
async updateDBSubnetGroup(logicalId, physicalId, resourceType, properties, previousProperties, mask) {
|
|
400
|
+
this.logger.debug(`Updating Neptune DBSubnetGroup ${logicalId}: ${physicalId}`);
|
|
401
|
+
refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId, physicalId);
|
|
402
|
+
try {
|
|
403
|
+
const subnetIds = properties["SubnetIds"];
|
|
404
|
+
const sendSubnetIds = subnetIds !== void 0 && subnetIds.length > 0;
|
|
405
|
+
const modifyInput = {
|
|
406
|
+
DBSubnetGroupName: physicalId,
|
|
407
|
+
DBSubnetGroupDescription: clearOnUpdateRemoval(properties["DBSubnetGroupDescription"], previousProperties["DBSubnetGroupDescription"], `Subnet group for ${logicalId}`),
|
|
408
|
+
...sendSubnetIds && { SubnetIds: subnetIds }
|
|
409
|
+
};
|
|
410
|
+
await this.getClient().send(new ModifyDBSubnetGroupCommand(modifyInput));
|
|
411
|
+
const arn = (await this.getClient().send(new DescribeDBSubnetGroupsCommand({ DBSubnetGroupName: physicalId }))).DBSubnetGroups?.[0]?.DBSubnetGroupArn;
|
|
412
|
+
if (arn) await this.applyTagDiff(arn, resourceType, logicalId, previousProperties["Tags"], properties["Tags"]);
|
|
413
|
+
this.logger.debug(`Successfully updated Neptune DBSubnetGroup ${logicalId}`);
|
|
414
|
+
return {
|
|
415
|
+
physicalId,
|
|
416
|
+
wasReplaced: false,
|
|
417
|
+
attributes: { DBSubnetGroupName: physicalId }
|
|
418
|
+
};
|
|
419
|
+
} catch (error) {
|
|
420
|
+
const cause = error instanceof Error ? error : void 0;
|
|
421
|
+
throw this.wrapCaughtError(mask, error, (text) => new ProvisioningError(`Failed to update Neptune DBSubnetGroup ${logicalId}: ${text}`, resourceType, logicalId, physicalId, cause));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async deleteDBSubnetGroup(logicalId, physicalId, resourceType, context) {
|
|
425
|
+
this.logger.debug(`Deleting Neptune DBSubnetGroup ${logicalId}: ${physicalId}`);
|
|
426
|
+
try {
|
|
427
|
+
await this.getClient().send(new DeleteDBSubnetGroupCommand({ DBSubnetGroupName: physicalId }));
|
|
428
|
+
this.logger.debug(`Successfully deleted Neptune DBSubnetGroup ${logicalId}`);
|
|
429
|
+
} catch (error) {
|
|
430
|
+
if (this.isNotFoundError(error, "DBSubnetGroupNotFoundFault")) {
|
|
431
|
+
const clientRegion = await this.getClient().config.region();
|
|
432
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
433
|
+
this.logger.debug(`Neptune DBSubnetGroup ${physicalId} does not exist, skipping deletion`);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const cause = error instanceof Error ? error : void 0;
|
|
437
|
+
throw this.wrapDeleteError(context?.maskSecrets, error, (text) => new ProvisioningError(`Failed to delete Neptune DBSubnetGroup ${logicalId}: ${text}`, resourceType, logicalId, physicalId, cause));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
async createDBCluster(logicalId, resourceType, properties, mask, replayingState) {
|
|
441
|
+
this.logger.debug(`Creating Neptune DBCluster ${logicalId}`);
|
|
442
|
+
const tags = refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId);
|
|
443
|
+
const { roles, logTypes, scaling } = this.readClusterBlocks(properties, resourceType, logicalId, {
|
|
444
|
+
creating: true,
|
|
445
|
+
stateBorne: replayingState
|
|
446
|
+
});
|
|
447
|
+
const restore = readClusterRestore(properties, resourceType, logicalId);
|
|
448
|
+
this.warnIgnoredRestoreProperties(logicalId, resourceType, properties, restore);
|
|
449
|
+
const dbClusterIdentifier = properties["DBClusterIdentifier"] || generatedNameFor("AWS::Neptune::DBCluster", logicalId);
|
|
450
|
+
if (!String(properties["SourceDBClusterIdentifier"]).startsWith("arn:")) refuseRestoreFromItself(resourceType, properties, dbClusterIdentifier, logicalId, { region: this.providerRegion });
|
|
451
|
+
const port = properties["DBPort"] != null ? Number(properties["DBPort"]) : properties["Port"] != null ? Number(properties["Port"]) : void 0;
|
|
452
|
+
const kmsKeyId = properties["KmsKeyId"];
|
|
453
|
+
const sharedInput = {
|
|
454
|
+
DBClusterIdentifier: dbClusterIdentifier,
|
|
455
|
+
VpcSecurityGroupIds: properties["VpcSecurityGroupIds"],
|
|
456
|
+
DBSubnetGroupName: properties["DBSubnetGroupName"],
|
|
457
|
+
DBClusterParameterGroupName: properties["DBClusterParameterGroupName"],
|
|
458
|
+
EnableIAMDatabaseAuthentication: properties["IamAuthEnabled"],
|
|
459
|
+
DeletionProtection: properties["DeletionProtection"],
|
|
460
|
+
NetworkType: properties["NetworkType"],
|
|
461
|
+
...logTypes.length > 0 && { EnableCloudwatchLogsExports: logTypes },
|
|
462
|
+
...scaling != null && { ServerlessV2ScalingConfiguration: scaling },
|
|
463
|
+
...tags.length > 0 && { Tags: tags }
|
|
464
|
+
};
|
|
465
|
+
const restoreInput = {
|
|
466
|
+
...sharedInput,
|
|
467
|
+
Port: port ?? DEFAULT_CLUSTER_PORT,
|
|
468
|
+
KmsKeyId: kmsKeyId ?? (String(properties["StorageEncrypted"]).toLowerCase() === "true" ? DEFAULT_STORAGE_KEY : void 0)
|
|
469
|
+
};
|
|
470
|
+
let clusterCreated = false;
|
|
471
|
+
let createdResourceId;
|
|
472
|
+
try {
|
|
473
|
+
const response = await (restore === void 0 ? this.getCreateClient().send(new CreateDBClusterCommand({
|
|
474
|
+
...sharedInput,
|
|
475
|
+
Engine: "neptune",
|
|
476
|
+
EngineVersion: properties["EngineVersion"],
|
|
477
|
+
Port: port,
|
|
478
|
+
StorageEncrypted: properties["StorageEncrypted"],
|
|
479
|
+
KmsKeyId: kmsKeyId,
|
|
480
|
+
BackupRetentionPeriod: properties["BackupRetentionPeriod"] != null ? Number(properties["BackupRetentionPeriod"]) : void 0,
|
|
481
|
+
PreferredBackupWindow: properties["PreferredBackupWindow"],
|
|
482
|
+
PreferredMaintenanceWindow: properties["PreferredMaintenanceWindow"],
|
|
483
|
+
AvailabilityZones: properties["AvailabilityZones"],
|
|
484
|
+
CopyTagsToSnapshot: properties["CopyTagsToSnapshot"]
|
|
485
|
+
})) : restore.kind === "snapshot" ? this.getCreateClient().send(new RestoreDBClusterFromSnapshotCommand({
|
|
486
|
+
...restoreInput,
|
|
487
|
+
SnapshotIdentifier: restore.snapshotIdentifier,
|
|
488
|
+
Engine: "neptune",
|
|
489
|
+
EngineVersion: properties["EngineVersion"],
|
|
490
|
+
AvailabilityZones: properties["AvailabilityZones"],
|
|
491
|
+
CopyTagsToSnapshot: properties["CopyTagsToSnapshot"]
|
|
492
|
+
})) : this.getCreateClient().send(new RestoreDBClusterToPointInTimeCommand({
|
|
493
|
+
...restoreInput,
|
|
494
|
+
DBClusterParameterGroupName: void 0,
|
|
495
|
+
SourceDBClusterIdentifier: restore.sourceDBClusterIdentifier,
|
|
496
|
+
RestoreType: properties["RestoreType"],
|
|
497
|
+
...restore.restoreToTime !== void 0 && { RestoreToTime: restore.restoreToTime },
|
|
498
|
+
UseLatestRestorableTime: properties["UseLatestRestorableTime"]
|
|
499
|
+
})));
|
|
500
|
+
clusterCreated = true;
|
|
501
|
+
createdResourceId = response.DBCluster?.DbClusterResourceId;
|
|
502
|
+
reportCreatedResource(logicalId, resourceType, dbClusterIdentifier, createdResourceId);
|
|
503
|
+
const cluster = response.DBCluster;
|
|
504
|
+
if (!cluster) throw new Error("The create call did not return DBCluster");
|
|
505
|
+
this.logger.debug(`Successfully created Neptune DBCluster ${logicalId}: ${dbClusterIdentifier}`);
|
|
506
|
+
const followUp = restore === void 0 ? void 0 : restoreFollowUp(properties, restore, cluster.EngineVersion);
|
|
507
|
+
const followingUp = followUp !== void 0 && (followUp.parity !== void 0 || followUp.declared.length > 0);
|
|
508
|
+
if (followingUp || process.env["CDKD_NO_WAIT"] !== "true") await this.waitForClusterAvailable(logicalId, dbClusterIdentifier, void 0, restore === void 0 ? void 0 : restoreWaitMs());
|
|
509
|
+
try {
|
|
510
|
+
if (followUp !== void 0 && followingUp) await this.sendRestoreFollowUp(logicalId, resourceType, dbClusterIdentifier, followUp, mask);
|
|
511
|
+
await this.applyClusterRoles(logicalId, dbClusterIdentifier, roles, [], [], mask, void 0);
|
|
512
|
+
} catch (error) {
|
|
513
|
+
markAuxiliaryFailure(error, logicalId);
|
|
514
|
+
throw error;
|
|
515
|
+
}
|
|
516
|
+
return {
|
|
517
|
+
physicalId: dbClusterIdentifier,
|
|
518
|
+
attributes: clusterAttributes(await this.describeDBCluster(dbClusterIdentifier))
|
|
519
|
+
};
|
|
520
|
+
} catch (error) {
|
|
521
|
+
const thrown = error instanceof ProvisioningError ? error : this.wrapCaughtError(mask, error, (text) => new ProvisioningError(`Failed to create Neptune DBCluster ${logicalId}: ${text}`, resourceType, logicalId, dbClusterIdentifier, error instanceof Error ? error : void 0));
|
|
522
|
+
if (clusterCreated) markCreatedBeforeFailure(thrown, logicalId, resourceType, dbClusterIdentifier, createdResourceId);
|
|
523
|
+
throw thrown;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
async sendRestoreFollowUp(logicalId, resourceType, physicalId, followUp, mask) {
|
|
527
|
+
const send = (members) => modifyDbInstanceWhenAvailable({
|
|
528
|
+
send: () => this.getClient().send(new ModifyDBClusterCommand({
|
|
529
|
+
DBClusterIdentifier: physicalId,
|
|
530
|
+
...members,
|
|
531
|
+
ApplyImmediately: true
|
|
532
|
+
})),
|
|
533
|
+
readStatus: async () => (await this.describeDBCluster(physicalId))?.Status,
|
|
534
|
+
sleep: (ms) => this.sleep(ms),
|
|
535
|
+
logger: this.logger,
|
|
536
|
+
label: "Neptune DBCluster",
|
|
537
|
+
subject: "cluster",
|
|
538
|
+
isRefusal: (error) => isModifyNotAvailableRefusal(error) || isRestoringClusterRefusal(error),
|
|
539
|
+
logicalId,
|
|
540
|
+
remainingMs: enclosingDeadlineRemainingMs()
|
|
541
|
+
});
|
|
542
|
+
if (followUp.parity !== void 0) await send(followUp.parity);
|
|
543
|
+
const refused = async (members) => {
|
|
544
|
+
try {
|
|
545
|
+
await send(members);
|
|
546
|
+
return false;
|
|
547
|
+
} catch (error) {
|
|
548
|
+
if (error instanceof Error && RESTORE_VALUE_REFUSALS.has(error.name)) {
|
|
549
|
+
this.logger.debug(safeMsg`${error.name} after restoring ${logicalId}: ${(mask ?? String)(error.message)}`);
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
552
|
+
throw error;
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
const { declared } = followUp;
|
|
556
|
+
if (declared.length > 0 && await refused(Object.assign({}, ...declared))) {
|
|
557
|
+
const keys = [];
|
|
558
|
+
for (const group of declared) if (declared.length === 1 || await refused(group)) keys.push(...Object.keys(group));
|
|
559
|
+
const refusedKeys = keys.filter((key) => key !== "AllowMajorVersionUpgrade");
|
|
560
|
+
if (refusedKeys.length > 0) this.logger.warn(safeMsg`${logicalId} (${resourceType}): AWS refused ${refusedKeys.join(", ")} after the restore, so the cluster keeps its restored values. The record holds the declared ones, so drift does not report the difference. An update leaves an unchanged window or EngineVersion out, as CloudFormation does; a refused BackupRetentionPeriod, CopyTagsToSnapshot or DBClusterParameterGroupName is sent again, so every update of this cluster fails until the template changes it.`);
|
|
561
|
+
}
|
|
562
|
+
if (process.env["CDKD_NO_WAIT"] !== "true") await this.waitForClusterAvailable(logicalId, physicalId, "being configured after a restore", restoreWaitMs());
|
|
563
|
+
}
|
|
564
|
+
warnIgnoredRestoreProperties(logicalId, resourceType, properties, restore) {
|
|
565
|
+
const ignored = ignoredRestoreKeys(restore).filter((key) => properties[key] != null && properties[key] !== false);
|
|
566
|
+
if (ignored.length === 0) return;
|
|
567
|
+
const call = restore === void 0 ? "create" : restore.kind === "snapshot" ? "snapshot restore" : "point-in-time restore";
|
|
568
|
+
const retention = restore?.kind === "point-in-time" && declaresSnapshot(properties) ? " A declared SnapshotIdentifier still sets BackupRetentionPeriod, 1 when none is declared." : "";
|
|
569
|
+
this.logger.warn(safeMsg`${logicalId} (${resourceType}): this ${call} does not use ${ignored.join(", ")}, as in CloudFormation.${retention}`);
|
|
570
|
+
}
|
|
571
|
+
async updateDBCluster(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
572
|
+
this.logger.debug(`Updating Neptune DBCluster ${logicalId}: ${physicalId}`);
|
|
573
|
+
refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId, physicalId);
|
|
574
|
+
const { roles, logTypes, scaling } = this.readClusterBlocks(properties, resourceType, logicalId, {
|
|
575
|
+
creating: false,
|
|
576
|
+
stateBorne: context?.replayingState === true || context?.desiredFromAwsReadback === true,
|
|
577
|
+
physicalId
|
|
578
|
+
});
|
|
579
|
+
const logExportConfig = logExportChange(logTypes, stringList(previousProperties["EnableCloudwatchLogsExports"]) ?? []);
|
|
580
|
+
try {
|
|
581
|
+
const unrecorded = (key) => properties[key] != null && previousProperties[key] == null;
|
|
582
|
+
const live = unrecorded("EngineVersion") || unrecorded("NetworkType") || logExportConfig?.EnableLogTypes !== void 0 ? await this.describeDBCluster(physicalId) : void 0;
|
|
583
|
+
const majorUpgrade = isMajorEngineChange(previousProperties["EngineVersion"] ?? live?.EngineVersion, properties["EngineVersion"]);
|
|
584
|
+
const networkType = properties["NetworkType"];
|
|
585
|
+
const networkChange = networkType != null && networkType !== (previousProperties["NetworkType"] ?? live?.NetworkType);
|
|
586
|
+
markChangedUnsettled(CLUSTER_MODIFY_KEYS, properties, previousProperties, context);
|
|
587
|
+
const send = sentOnUpdate(properties, previousProperties);
|
|
588
|
+
const vpcSgIds = await this.dbClusterSecurityGroupsToSend(logicalId, physicalId, resourceType, properties, previousProperties, context);
|
|
589
|
+
await this.getClient().send(new ModifyDBClusterCommand({
|
|
590
|
+
DBClusterIdentifier: physicalId,
|
|
591
|
+
EngineVersion: send("EngineVersion"),
|
|
592
|
+
DeletionProtection: properties["DeletionProtection"],
|
|
593
|
+
BackupRetentionPeriod: properties["BackupRetentionPeriod"] != null ? Number(properties["BackupRetentionPeriod"]) : void 0,
|
|
594
|
+
PreferredBackupWindow: send("PreferredBackupWindow"),
|
|
595
|
+
PreferredMaintenanceWindow: send("PreferredMaintenanceWindow"),
|
|
596
|
+
DBClusterParameterGroupName: properties["DBClusterParameterGroupName"],
|
|
597
|
+
EnableIAMDatabaseAuthentication: properties["IamAuthEnabled"],
|
|
598
|
+
...vpcSgIds !== void 0 && { VpcSecurityGroupIds: vpcSgIds },
|
|
599
|
+
Port: properties["DBPort"] != null ? Number(properties["DBPort"]) : properties["Port"] != null ? Number(properties["Port"]) : void 0,
|
|
600
|
+
CopyTagsToSnapshot: properties["CopyTagsToSnapshot"],
|
|
601
|
+
...networkChange && { NetworkType: networkType },
|
|
602
|
+
...scaling != null && { ServerlessV2ScalingConfiguration: scaling },
|
|
603
|
+
...logExportConfig !== void 0 && { CloudwatchLogsExportConfiguration: logExportConfig },
|
|
604
|
+
...majorUpgrade && { AllowMajorVersionUpgrade: true },
|
|
605
|
+
...majorUpgrade && properties["DBInstanceParameterGroupName"] != null && { DBInstanceParameterGroupName: properties["DBInstanceParameterGroupName"] },
|
|
606
|
+
ApplyImmediately: true
|
|
607
|
+
}));
|
|
608
|
+
const addedLogTypes = (logExportConfig?.EnableLogTypes ?? []).filter((type) => !live?.EnabledCloudwatchLogsExports?.includes(type));
|
|
609
|
+
if (addedLogTypes.length > 0) context?.markAttempted?.("EnableCloudwatchLogsExports", addedLogTypes);
|
|
610
|
+
this.logger.debug(`Successfully updated Neptune DBCluster ${logicalId}`);
|
|
611
|
+
const described = await this.describeDBCluster(physicalId);
|
|
612
|
+
if (logExportConfig !== void 0 && process.env["CDKD_NO_WAIT"] !== "true") {
|
|
613
|
+
const { EnableLogTypes: enabled = [], DisableLogTypes: disabled = [] } = logExportConfig;
|
|
614
|
+
await this.waitForApplied(logicalId, "EnableCloudwatchLogsExports", async () => {
|
|
615
|
+
const live = (await this.describeDBCluster(physicalId))?.EnabledCloudwatchLogsExports;
|
|
616
|
+
return enabled.every((type) => live?.includes(type)) && !disabled.some((type) => live?.includes(type));
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
if (networkChange && process.env["CDKD_NO_WAIT"] !== "true") await this.waitForApplied(logicalId, "NetworkType", async () => (await this.describeDBCluster(physicalId))?.NetworkType === networkType);
|
|
620
|
+
await this.applyClusterRoles(logicalId, physicalId, roles, clusterRoleList(previousProperties["AssociatedRoles"]) ?? [], described?.AssociatedRoles ?? [], context?.maskSecrets, context);
|
|
621
|
+
if (process.env["CDKD_NO_WAIT"] !== "true" && securityGroupListChanged(vpcSgIds, previousProperties["VpcSecurityGroupIds"])) {
|
|
622
|
+
await this.waitForSecurityGroupsApplied(physicalId, logicalId, vpcSgIds ?? []);
|
|
623
|
+
for (const member of described?.DBClusterMembers ?? []) if (member.DBInstanceIdentifier) await this.waitForMemberAfterSecurityGroupChange(member.DBInstanceIdentifier, logicalId);
|
|
624
|
+
}
|
|
625
|
+
if (described?.DBClusterArn) {
|
|
626
|
+
markChangedUnsettled(["Tags"], properties, previousProperties, context);
|
|
627
|
+
await this.applyTagDiff(described.DBClusterArn, resourceType, logicalId, previousProperties["Tags"], properties["Tags"]);
|
|
628
|
+
}
|
|
629
|
+
this.warnKeptRemovals(logicalId, resourceType, properties, previousProperties, CLUSTER_KEPT_ON_REMOVAL, context);
|
|
630
|
+
this.warnKeptRemovals(logicalId, resourceType, properties, previousProperties, CLUSTER_UNMEASURED_ON_REMOVAL, context, false);
|
|
631
|
+
return {
|
|
632
|
+
physicalId,
|
|
633
|
+
wasReplaced: false,
|
|
634
|
+
attributes: clusterAttributes(described)
|
|
635
|
+
};
|
|
636
|
+
} catch (error) {
|
|
637
|
+
if (error instanceof ProvisioningError) throw error;
|
|
638
|
+
const cause = error instanceof Error ? error : void 0;
|
|
639
|
+
throw this.wrapCaughtError(context?.maskSecrets, error, (text) => new ProvisioningError(`Failed to update Neptune DBCluster ${logicalId}: ${text}`, resourceType, logicalId, physicalId, cause));
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
async deleteDBCluster(logicalId, physicalId, resourceType, context) {
|
|
643
|
+
await deleteWithProtectionCompensation({
|
|
644
|
+
registry: this.protectionFlips,
|
|
645
|
+
key: protectionFlipKey(resourceType, physicalId, context?.expectedRegion),
|
|
646
|
+
run: (flip) => this.deleteDBClusterOnce(logicalId, physicalId, resourceType, context, flip),
|
|
647
|
+
compensation: {
|
|
648
|
+
logicalId,
|
|
649
|
+
physicalId,
|
|
650
|
+
logger: this.logger,
|
|
651
|
+
site: rdsFamilyProtectionSite({
|
|
652
|
+
cliService: "neptune",
|
|
653
|
+
serviceLabel: "Neptune",
|
|
654
|
+
kind: "cluster",
|
|
655
|
+
physicalId,
|
|
656
|
+
region: context?.expectedRegion,
|
|
657
|
+
notFoundFault: "DBClusterNotFoundFault",
|
|
658
|
+
isNotFound: (error) => this.isNotFoundError(error, "DBClusterNotFoundFault")
|
|
659
|
+
}),
|
|
660
|
+
reEnable: async () => {
|
|
661
|
+
await this.getClient().send(new ModifyDBClusterCommand({
|
|
662
|
+
DBClusterIdentifier: physicalId,
|
|
663
|
+
DeletionProtection: true,
|
|
664
|
+
ApplyImmediately: true
|
|
665
|
+
}));
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
async deleteDBClusterOnce(logicalId, physicalId, resourceType, context, flip) {
|
|
671
|
+
this.logger.debug(`Deleting Neptune DBCluster ${logicalId}`);
|
|
672
|
+
try {
|
|
673
|
+
if (context?.removeProtection === true) try {
|
|
674
|
+
await observeThenDisableProtection({
|
|
675
|
+
flip,
|
|
676
|
+
logger: this.logger,
|
|
677
|
+
physicalId,
|
|
678
|
+
mask: context?.maskSecrets,
|
|
679
|
+
guardName: "DeletionProtection",
|
|
680
|
+
observe: async () => (await this.describeDBCluster(physicalId))?.DeletionProtection === true,
|
|
681
|
+
disable: async () => {
|
|
682
|
+
await this.getClient().send(new ModifyDBClusterCommand({
|
|
683
|
+
DBClusterIdentifier: physicalId,
|
|
684
|
+
DeletionProtection: false,
|
|
685
|
+
ApplyImmediately: true
|
|
686
|
+
}));
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
this.logger.debug(`Disabled DeletionProtection on Neptune DBCluster ${logicalId} before delete`);
|
|
690
|
+
} catch (disableError) {
|
|
691
|
+
if (!this.isNotFoundError(disableError, "DBClusterNotFoundFault")) this.logger.debug(maskerOrIdentity(context?.maskSecrets)(`Could not disable deletion protection for Neptune DBCluster ${logicalId}: ${describeAwsFailure(disableError).detail}`));
|
|
692
|
+
}
|
|
693
|
+
const finalSnapshotId = context?.finalSnapshotIdentifier;
|
|
694
|
+
const sendDelete = () => this.getClient().send(new DeleteDBClusterCommand({
|
|
695
|
+
DBClusterIdentifier: physicalId,
|
|
696
|
+
...finalSnapshotId ? {
|
|
697
|
+
SkipFinalSnapshot: false,
|
|
698
|
+
FinalDBSnapshotIdentifier: finalSnapshotId
|
|
699
|
+
} : { SkipFinalSnapshot: true }
|
|
700
|
+
}));
|
|
701
|
+
if (finalSnapshotId && flip.flippedOffByThisRun && !flip.deleteAccepted) await this.deleteAfterProtectionFlip(logicalId, physicalId, sendDelete);
|
|
702
|
+
else await sendDelete();
|
|
703
|
+
flip.deleteAccepted = true;
|
|
704
|
+
if (finalSnapshotId) this.logger.info(`Deleting Neptune DBCluster ${logicalId} with a final snapshot (DeletionPolicy: Snapshot)`);
|
|
705
|
+
this.logger.debug(`Successfully initiated deletion of Neptune DBCluster ${logicalId}`);
|
|
706
|
+
await this.waitForClusterDeleted(logicalId, physicalId);
|
|
707
|
+
} catch (error) {
|
|
708
|
+
if (this.isNotFoundError(error, "DBClusterNotFoundFault")) {
|
|
709
|
+
const clientRegion = await this.getClient().config.region();
|
|
710
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
711
|
+
if (context?.failedCreateOrphan === true) this.logger.info(safeMsg` Neptune DB cluster ${physicalId} (${logicalId}), which a failed deploy created, is already gone; nothing to delete`);
|
|
712
|
+
else this.logger.debug(`Neptune DBCluster ${logicalId} does not exist, skipping deletion`);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
const cause = error instanceof Error ? error : void 0;
|
|
716
|
+
throw this.wrapDeleteError(context?.maskSecrets, error, (text) => new ProvisioningError(`Failed to delete Neptune DBCluster ${logicalId}: ${text}`, resourceType, logicalId, physicalId, cause));
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
async createDBInstance(logicalId, resourceType, properties, mask, replayingState) {
|
|
720
|
+
this.logger.debug(`Creating Neptune DBInstance ${logicalId}`);
|
|
721
|
+
const tags = refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId);
|
|
722
|
+
const publiclyAccessible = this.readPublicAccess(properties, resourceType, logicalId, void 0, replayingState);
|
|
723
|
+
const dbInstanceIdentifier = properties["DBInstanceIdentifier"] || generatedNameFor("AWS::Neptune::DBInstance", logicalId);
|
|
724
|
+
let instanceCreated = false;
|
|
725
|
+
let createdResourceId;
|
|
726
|
+
try {
|
|
727
|
+
const response = await this.getCreateClient().send(new CreateDBInstanceCommand({
|
|
728
|
+
DBInstanceIdentifier: dbInstanceIdentifier,
|
|
729
|
+
DBInstanceClass: properties["DBInstanceClass"],
|
|
730
|
+
Engine: "neptune",
|
|
731
|
+
DBClusterIdentifier: properties["DBClusterIdentifier"],
|
|
732
|
+
DBSubnetGroupName: properties["DBSubnetGroupName"],
|
|
733
|
+
DBParameterGroupName: properties["DBParameterGroupName"],
|
|
734
|
+
AvailabilityZone: properties["AvailabilityZone"],
|
|
735
|
+
PreferredMaintenanceWindow: properties["PreferredMaintenanceWindow"],
|
|
736
|
+
AutoMinorVersionUpgrade: properties["AutoMinorVersionUpgrade"],
|
|
737
|
+
DeletionProtection: properties["DeletionProtection"],
|
|
738
|
+
PubliclyAccessible: publiclyAccessible,
|
|
739
|
+
...tags.length > 0 && { Tags: tags }
|
|
740
|
+
}));
|
|
741
|
+
instanceCreated = true;
|
|
742
|
+
createdResourceId = response.DBInstance?.DbiResourceId;
|
|
743
|
+
reportCreatedResource(logicalId, resourceType, dbInstanceIdentifier, createdResourceId);
|
|
744
|
+
if (!response.DBInstance) throw new Error("CreateDBInstance did not return DBInstance");
|
|
745
|
+
this.logger.debug(`Successfully created Neptune DBInstance ${logicalId}: ${dbInstanceIdentifier}`);
|
|
746
|
+
if (process.env["CDKD_NO_WAIT"] !== "true") await this.waitForInstanceAvailable(logicalId, dbInstanceIdentifier);
|
|
747
|
+
return {
|
|
748
|
+
physicalId: dbInstanceIdentifier,
|
|
749
|
+
attributes: instanceAttributes(await this.describeDBInstance(dbInstanceIdentifier))
|
|
750
|
+
};
|
|
751
|
+
} catch (error) {
|
|
752
|
+
const thrown = error instanceof ProvisioningError ? error : this.wrapCaughtError(mask, error, (text) => new ProvisioningError(`Failed to create Neptune DBInstance ${logicalId}: ${text}`, resourceType, logicalId, dbInstanceIdentifier, error instanceof Error ? error : void 0));
|
|
753
|
+
if (instanceCreated) markCreatedBeforeFailure(thrown, logicalId, resourceType, dbInstanceIdentifier, createdResourceId);
|
|
754
|
+
throw thrown;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
async updateDBInstance(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
758
|
+
const mask = context?.maskSecrets;
|
|
759
|
+
this.logger.debug(`Updating Neptune DBInstance ${logicalId}: ${physicalId}`);
|
|
760
|
+
refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId, physicalId);
|
|
761
|
+
const publiclyAccessible = this.readPublicAccess(properties, resourceType, logicalId, physicalId, context?.replayingState === true || context?.desiredFromAwsReadback === true);
|
|
762
|
+
try {
|
|
763
|
+
markChangedUnsettled(INSTANCE_MODIFY_KEYS, properties, previousProperties, context);
|
|
764
|
+
await modifyDbInstanceWhenAvailable({
|
|
765
|
+
send: () => this.getClient().send(new ModifyDBInstanceCommand({
|
|
766
|
+
DBInstanceIdentifier: physicalId,
|
|
767
|
+
DBInstanceClass: properties["DBInstanceClass"],
|
|
768
|
+
DBParameterGroupName: properties["DBParameterGroupName"],
|
|
769
|
+
PreferredMaintenanceWindow: properties["PreferredMaintenanceWindow"],
|
|
770
|
+
AutoMinorVersionUpgrade: properties["AutoMinorVersionUpgrade"],
|
|
771
|
+
DeletionProtection: properties["DeletionProtection"],
|
|
772
|
+
PubliclyAccessible: publiclyAccessible,
|
|
773
|
+
AllowMajorVersionUpgrade: properties["AllowMajorVersionUpgrade"],
|
|
774
|
+
ApplyImmediately: true
|
|
775
|
+
})),
|
|
776
|
+
readStatus: async () => (await this.describeDBInstance(physicalId))?.DBInstanceStatus,
|
|
777
|
+
sleep: (ms) => this.sleep(ms),
|
|
778
|
+
logger: this.logger,
|
|
779
|
+
label: "Neptune DBInstance",
|
|
780
|
+
logicalId,
|
|
781
|
+
remainingMs: enclosingDeadlineRemainingMs()
|
|
782
|
+
});
|
|
783
|
+
this.logger.debug(`Successfully updated Neptune DBInstance ${logicalId}`);
|
|
784
|
+
if (publiclyAccessible !== void 0 && publiclyAccessible !== coerceCfnBoolean(previousProperties["PubliclyAccessible"]) && process.env["CDKD_NO_WAIT"] !== "true") await this.waitForApplied(logicalId, "PubliclyAccessible", async () => (await this.describeDBInstance(physicalId))?.PubliclyAccessible === publiclyAccessible);
|
|
785
|
+
const described = await this.describeDBInstance(physicalId);
|
|
786
|
+
if (described?.DBInstanceArn) {
|
|
787
|
+
markChangedUnsettled(["Tags"], properties, previousProperties, context);
|
|
788
|
+
await this.applyTagDiff(described.DBInstanceArn, resourceType, logicalId, previousProperties["Tags"], properties["Tags"]);
|
|
789
|
+
}
|
|
790
|
+
this.warnKeptRemovals(logicalId, resourceType, properties, previousProperties, INSTANCE_KEPT_ON_REMOVAL, context);
|
|
791
|
+
this.warnPublicAccessKept(logicalId, resourceType, properties, previousProperties, context);
|
|
792
|
+
return {
|
|
793
|
+
physicalId,
|
|
794
|
+
wasReplaced: false,
|
|
795
|
+
attributes: instanceAttributes(described)
|
|
796
|
+
};
|
|
797
|
+
} catch (error) {
|
|
798
|
+
const cause = error instanceof Error ? error : void 0;
|
|
799
|
+
throw this.wrapCaughtError(mask, error, (text) => new ProvisioningError(`Failed to update Neptune DBInstance ${logicalId}: ${text}`, resourceType, logicalId, physicalId, cause));
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
async deleteDBInstance(logicalId, physicalId, resourceType, context) {
|
|
803
|
+
await deleteWithProtectionCompensation({
|
|
804
|
+
registry: this.protectionFlips,
|
|
805
|
+
key: protectionFlipKey(resourceType, physicalId, context?.expectedRegion),
|
|
806
|
+
run: (flip) => this.deleteDBInstanceOnce(logicalId, physicalId, resourceType, context, flip),
|
|
807
|
+
compensation: {
|
|
808
|
+
logicalId,
|
|
809
|
+
physicalId,
|
|
810
|
+
logger: this.logger,
|
|
811
|
+
site: rdsFamilyProtectionSite({
|
|
812
|
+
cliService: "neptune",
|
|
813
|
+
serviceLabel: "Neptune",
|
|
814
|
+
kind: "instance",
|
|
815
|
+
physicalId,
|
|
816
|
+
region: context?.expectedRegion,
|
|
817
|
+
notFoundFault: "DBInstanceNotFoundFault",
|
|
818
|
+
isNotFound: (error) => this.isNotFoundError(error, "DBInstanceNotFoundFault")
|
|
819
|
+
}),
|
|
820
|
+
reEnable: async () => {
|
|
821
|
+
await this.getClient().send(new ModifyDBInstanceCommand({
|
|
822
|
+
DBInstanceIdentifier: physicalId,
|
|
823
|
+
DeletionProtection: true,
|
|
824
|
+
ApplyImmediately: true
|
|
825
|
+
}));
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
async deleteDBInstanceOnce(logicalId, physicalId, resourceType, context, flip) {
|
|
831
|
+
this.logger.debug(`Deleting Neptune DBInstance ${logicalId}: ${physicalId}`);
|
|
832
|
+
try {
|
|
833
|
+
if (context?.removeProtection === true) try {
|
|
834
|
+
await observeThenDisableProtection({
|
|
835
|
+
flip,
|
|
836
|
+
logger: this.logger,
|
|
837
|
+
physicalId,
|
|
838
|
+
mask: context?.maskSecrets,
|
|
839
|
+
guardName: "DeletionProtection",
|
|
840
|
+
observe: async () => (await this.describeDBInstance(physicalId))?.DeletionProtection === true,
|
|
841
|
+
disable: async () => {
|
|
842
|
+
await this.getClient().send(new ModifyDBInstanceCommand({
|
|
843
|
+
DBInstanceIdentifier: physicalId,
|
|
844
|
+
DeletionProtection: false,
|
|
845
|
+
ApplyImmediately: true
|
|
846
|
+
}));
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
this.logger.debug(`Disabled DeletionProtection on Neptune DBInstance ${logicalId} before delete`);
|
|
850
|
+
} catch (disableError) {
|
|
851
|
+
if (!this.isNotFoundError(disableError, "DBInstanceNotFoundFault")) this.logger.debug(maskerOrIdentity(context?.maskSecrets)(`Could not disable deletion protection for ${physicalId}: ${describeAwsFailure(disableError).detail}`));
|
|
852
|
+
}
|
|
853
|
+
await this.getClient().send(new DeleteDBInstanceCommand({
|
|
854
|
+
DBInstanceIdentifier: physicalId,
|
|
855
|
+
SkipFinalSnapshot: true
|
|
856
|
+
}));
|
|
857
|
+
flip.deleteAccepted = true;
|
|
858
|
+
this.logger.debug(`Successfully initiated deletion of Neptune DBInstance ${logicalId}`);
|
|
859
|
+
await this.waitForInstanceDeleted(physicalId);
|
|
860
|
+
} catch (error) {
|
|
861
|
+
if (this.isNotFoundError(error, "DBInstanceNotFoundFault")) {
|
|
862
|
+
const clientRegion = await this.getClient().config.region();
|
|
863
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
864
|
+
if (context?.failedCreateOrphan === true) this.logger.info(safeMsg` Neptune DB instance ${physicalId} (${logicalId}), which a failed deploy created, is already gone; nothing to delete`);
|
|
865
|
+
else this.logger.debug(`Neptune DBInstance ${physicalId} does not exist, skipping deletion`);
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
const cause = error instanceof Error ? error : void 0;
|
|
869
|
+
throw this.wrapDeleteError(context?.maskSecrets, error, (text) => new ProvisioningError(`Failed to delete Neptune DBInstance ${logicalId}: ${text}`, resourceType, logicalId, physicalId, cause));
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
wrapCaughtError(mask, error, build) {
|
|
873
|
+
const m = maskerOrIdentity(mask);
|
|
874
|
+
const failure = describeAwsFailure(error);
|
|
875
|
+
if (!failure.redacted) return wrapMaskedAwsError(m, error, build);
|
|
876
|
+
this.logger.debug(m(build(m(failure.detail)).message));
|
|
877
|
+
const wrapped = build(`${m(failure.summary)}${relayedNameCollision(failure.detail)}`);
|
|
878
|
+
return error instanceof Error ? markRedactedCause(wrapped) : wrapped;
|
|
879
|
+
}
|
|
880
|
+
wrapDeleteError(mask, error, build) {
|
|
881
|
+
const thrown = this.wrapCaughtError(mask, error, build);
|
|
882
|
+
return error instanceof Error && error.name.includes("NotFound") ? markNotGone(thrown) : thrown;
|
|
883
|
+
}
|
|
884
|
+
async applyTagDiff(arn, resourceType, logicalId, oldTagsRaw, newTagsRaw) {
|
|
885
|
+
const plan = planTagDiff(oldTagsRaw, newTagsRaw);
|
|
886
|
+
const tagWarning = tagPlanWarning(plan, resourceType, logicalId);
|
|
887
|
+
if (tagWarning !== void 0) this.logger.warn(tagWarning);
|
|
888
|
+
const tagsToAdd = [...plan.set].map(([Key, Value]) => ({
|
|
889
|
+
Key,
|
|
890
|
+
Value
|
|
891
|
+
}));
|
|
892
|
+
const tagsToRemove = plan.remove;
|
|
893
|
+
if (tagsToRemove.length > 0) {
|
|
894
|
+
await this.getClient().send(new RemoveTagsFromResourceCommand({
|
|
895
|
+
ResourceName: arn,
|
|
896
|
+
TagKeys: tagsToRemove
|
|
897
|
+
}));
|
|
898
|
+
this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from Neptune resource ${arn}`);
|
|
899
|
+
}
|
|
900
|
+
if (tagsToAdd.length > 0) {
|
|
901
|
+
await this.getClient().send(new AddTagsToResourceCommand({
|
|
902
|
+
ResourceName: arn,
|
|
903
|
+
Tags: tagsToAdd
|
|
904
|
+
}));
|
|
905
|
+
this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on Neptune resource ${arn}`);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
isNotFoundError(error, faultName) {
|
|
909
|
+
if (!(error instanceof Error)) return false;
|
|
910
|
+
const name = error.name ?? "";
|
|
911
|
+
const message = error.message.toLowerCase();
|
|
912
|
+
return name === faultName || message.includes("not found") || message.includes("does not exist");
|
|
913
|
+
}
|
|
914
|
+
async isSameResource(journaledPhysicalId, record, resourceType, context) {
|
|
915
|
+
if (resourceType !== "AWS::Neptune::DBCluster" && resourceType !== "AWS::Neptune::DBInstance") return "unknown";
|
|
916
|
+
if (!isDbIdentifier(journaledPhysicalId) || !isDbIdentifier(record.physicalId)) return "unknown";
|
|
917
|
+
if (journaledPhysicalId.toLowerCase() === record.physicalId.toLowerCase()) return "same";
|
|
918
|
+
if (await this.getClient().config.region() !== context.expectedRegion) return "unknown";
|
|
919
|
+
const recordResourceId = await this.readDbResourceIdIfExists(resourceType, record.physicalId);
|
|
920
|
+
if (recordResourceId === void 0) return "unknown";
|
|
921
|
+
const journaledResourceId = await this.readDbResourceIdIfExists(resourceType, journaledPhysicalId);
|
|
922
|
+
if (journaledResourceId === void 0) return "different";
|
|
923
|
+
return journaledResourceId === recordResourceId ? "same" : "different";
|
|
924
|
+
}
|
|
925
|
+
async resourceIdentity(physicalId, resourceType, context) {
|
|
926
|
+
if (resourceType !== "AWS::Neptune::DBCluster" && resourceType !== "AWS::Neptune::DBInstance") return;
|
|
927
|
+
if (!isDbIdentifier(physicalId)) return void 0;
|
|
928
|
+
if (await this.getClient().config.region() !== context.expectedRegion) return void 0;
|
|
929
|
+
const live = await this.readDbResourceIdIfExists(resourceType, physicalId);
|
|
930
|
+
return live === void 0 ? RESOURCE_NOT_FOUND : live;
|
|
931
|
+
}
|
|
932
|
+
async readDbResourceIdIfExists(resourceType, identifier) {
|
|
933
|
+
const isCluster = resourceType === "AWS::Neptune::DBCluster";
|
|
934
|
+
let found;
|
|
935
|
+
try {
|
|
936
|
+
if (isCluster) {
|
|
937
|
+
const cluster = await this.describeDBCluster(identifier);
|
|
938
|
+
found = cluster && {
|
|
939
|
+
identifier: cluster.DBClusterIdentifier,
|
|
940
|
+
engine: cluster.Engine,
|
|
941
|
+
resourceId: cluster.DbClusterResourceId
|
|
942
|
+
};
|
|
943
|
+
} else {
|
|
944
|
+
const instance = await this.describeDBInstance(identifier);
|
|
945
|
+
found = instance && {
|
|
946
|
+
identifier: instance.DBInstanceIdentifier,
|
|
947
|
+
engine: instance.Engine,
|
|
948
|
+
resourceId: instance.DbiResourceId
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
} catch (error) {
|
|
952
|
+
const notFound = isCluster ? "DBClusterNotFoundFault" : "DBInstanceNotFoundFault";
|
|
953
|
+
if (error?.name === notFound) return void 0;
|
|
954
|
+
throw error;
|
|
955
|
+
}
|
|
956
|
+
if (found === void 0) return void 0;
|
|
957
|
+
const api = isCluster ? "DescribeDBClusters" : "DescribeDBInstances";
|
|
958
|
+
if (found.identifier?.toLowerCase() !== identifier.toLowerCase()) throw new Error(`${api} answered for another identifier`);
|
|
959
|
+
if (found.engine !== "neptune") throw new Error(`${api} answered for a resource of another engine`);
|
|
960
|
+
if (typeof found.resourceId !== "string" || found.resourceId === "") throw new Error(`${api} returned no resource id`);
|
|
961
|
+
return found.resourceId;
|
|
962
|
+
}
|
|
963
|
+
readClusterBlocks(properties, resourceType, logicalId, origin) {
|
|
964
|
+
const refuse = (key, expected) => {
|
|
965
|
+
throw markNonRetryable(new ProvisioningError(safeMsg`Neptune DBCluster ${logicalId}: ${key} must be ${expected}; nothing was changed.`, resourceType, logicalId, origin.physicalId));
|
|
966
|
+
};
|
|
967
|
+
const leaveOut = (key) => {
|
|
968
|
+
this.logger.warn(safeMsg`${logicalId} (${resourceType}): the recorded ${key} is unusable, so it is not sent.`);
|
|
969
|
+
};
|
|
970
|
+
const unusableList = (key, expected) => {
|
|
971
|
+
if (!(origin.creating && origin.stateBorne)) return refuse(key, expected);
|
|
972
|
+
leaveOut(key);
|
|
973
|
+
return [];
|
|
974
|
+
};
|
|
975
|
+
const roles = clusterRoleList(properties["AssociatedRoles"]) ?? unusableList("AssociatedRoles", "a list of objects with a RoleArn");
|
|
976
|
+
const logTypes = stringList(properties["EnableCloudwatchLogsExports"]) ?? unusableList("EnableCloudwatchLogsExports", "a list of strings");
|
|
977
|
+
const scaling = serverlessScalingToSend(properties["ServerlessScalingConfiguration"]);
|
|
978
|
+
if (scaling === null) {
|
|
979
|
+
if (!origin.stateBorne) return refuse("ServerlessScalingConfiguration", "an object with both a numeric MinCapacity and a numeric MaxCapacity");
|
|
980
|
+
leaveOut("ServerlessScalingConfiguration");
|
|
981
|
+
}
|
|
982
|
+
return {
|
|
983
|
+
roles,
|
|
984
|
+
logTypes,
|
|
985
|
+
scaling
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
async applyClusterRoles(logicalId, physicalId, desired, previous, live, mask, marks) {
|
|
989
|
+
const liveKeys = new Set(live.map(clusterRoleKey));
|
|
990
|
+
const desiredKeys = new Set(desired.map(clusterRoleKey));
|
|
991
|
+
const toAdd = desired.filter((role) => !liveKeys.has(clusterRoleKey(role)));
|
|
992
|
+
const toRemove = previous.filter((role) => {
|
|
993
|
+
const key = clusterRoleKey(role);
|
|
994
|
+
return !desiredKeys.has(key) && liveKeys.has(key);
|
|
995
|
+
});
|
|
996
|
+
if (toAdd.length === 0 && toRemove.length === 0) return;
|
|
997
|
+
const waitBudgetMs = Math.min(ROLE_AVAILABLE_MAX_WAIT_MS, enclosingDeadlineRemainingMs() - DEADLINE_MARGIN_MS);
|
|
998
|
+
if (waitBudgetMs <= 0) throw new Error("the resource timeout leaves no more time to change the roles of the cluster; re-run the deploy, or with a longer --resource-timeout");
|
|
999
|
+
await this.waitForClusterAvailable(logicalId, physicalId, "changing its roles", waitBudgetMs);
|
|
1000
|
+
marks?.markUnsettled?.("AssociatedRoles");
|
|
1001
|
+
const whenAvailable = (send) => modifyDbInstanceWhenAvailable({
|
|
1002
|
+
send,
|
|
1003
|
+
readStatus: async () => (await this.describeDBCluster(physicalId))?.Status,
|
|
1004
|
+
sleep: (ms) => this.sleep(ms),
|
|
1005
|
+
logger: this.logger,
|
|
1006
|
+
label: "Neptune DBCluster",
|
|
1007
|
+
subject: "cluster",
|
|
1008
|
+
isRefusal: (error) => isRoleCallNotAvailableRefusal(error) || isModifyNotAvailableRefusal(error),
|
|
1009
|
+
logicalId,
|
|
1010
|
+
remainingMs: enclosingDeadlineRemainingMs()
|
|
1011
|
+
});
|
|
1012
|
+
for (const role of toRemove) await whenAvailable(() => this.getClient().send(new RemoveRoleFromDBClusterCommand({
|
|
1013
|
+
DBClusterIdentifier: physicalId,
|
|
1014
|
+
...role
|
|
1015
|
+
})));
|
|
1016
|
+
for (const role of toAdd) {
|
|
1017
|
+
await whenAvailable(() => this.getClient().send(new AddRoleToDBClusterCommand({
|
|
1018
|
+
DBClusterIdentifier: physicalId,
|
|
1019
|
+
...role
|
|
1020
|
+
})));
|
|
1021
|
+
marks?.markAttempted?.("AssociatedRoles", [clusterRoleKey(role)]);
|
|
1022
|
+
}
|
|
1023
|
+
if (process.env["CDKD_NO_WAIT"] !== "true") await this.waitForClusterRolesSettled(logicalId, physicalId, new Set(toAdd.map(clusterRoleKey)), new Set(toRemove.map(clusterRoleKey)), mask);
|
|
1024
|
+
}
|
|
1025
|
+
async waitForClusterRolesSettled(logicalId, physicalId, attached, detached, mask) {
|
|
1026
|
+
const shown = (keys) => maskerOrIdentity(mask)(keys.map((key) => key.split("\n")[0] ?? "").join(", "));
|
|
1027
|
+
let unsettled = [];
|
|
1028
|
+
if (!await this.pollUntilSettled(async () => {
|
|
1029
|
+
const roles = (await this.describeDBCluster(physicalId))?.AssociatedRoles ?? [];
|
|
1030
|
+
const byKey = new Map(roles.map((role) => [clusterRoleKey(role), role]));
|
|
1031
|
+
unsettled = [...[...attached].filter((key) => (byKey.get(key)?.Status ?? "PENDING") === "PENDING"), ...[...detached].filter((key) => byKey.has(key))];
|
|
1032
|
+
if (unsettled.length > 0) return false;
|
|
1033
|
+
const invalid = [...attached].filter((key) => byKey.get(key)?.Status === "INVALID");
|
|
1034
|
+
if (invalid.length > 0) this.logger.warn(safeMsg`${logicalId}: Neptune reports ${shown(invalid)} as INVALID: the role is attached, but the cluster cannot assume it. Check the role's trust policy.`);
|
|
1035
|
+
return true;
|
|
1036
|
+
})) this.logger.warn(safeMsg`${logicalId}: the IAM roles of the Neptune cluster had not settled when cdkd stopped waiting (${shown(unsettled)}).`);
|
|
1037
|
+
}
|
|
1038
|
+
async waitForApplied(logicalId, what, applied) {
|
|
1039
|
+
if (await this.pollUntilSettled(applied)) return;
|
|
1040
|
+
this.logger.warn(safeMsg`${logicalId}: the new ${what} did not read back when cdkd stopped waiting; AWS accepted it, so cdkd drift may report the old value as the baseline. Once it applies, run cdkd drift --accept.`);
|
|
1041
|
+
}
|
|
1042
|
+
async pollUntilSettled(settled) {
|
|
1043
|
+
const capMs = Math.min(SETTLE_MAX_WAIT_MS, enclosingDeadlineRemainingMs() - DEADLINE_MARGIN_MS);
|
|
1044
|
+
const startTime = Date.now();
|
|
1045
|
+
let delay = 2e3;
|
|
1046
|
+
for (;;) {
|
|
1047
|
+
if (await settled()) return true;
|
|
1048
|
+
if (Date.now() - startTime + delay >= capMs) return false;
|
|
1049
|
+
await this.sleep(delay);
|
|
1050
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
async describeDBCluster(dbClusterIdentifier) {
|
|
1054
|
+
return (await this.getClient().send(new DescribeDBClustersCommand({ DBClusterIdentifier: dbClusterIdentifier }))).DBClusters?.[0];
|
|
1055
|
+
}
|
|
1056
|
+
warnKeptRemovals(logicalId, resourceType, properties, previousProperties, kept, context, measured = true) {
|
|
1057
|
+
if (context?.desiredFromAwsReadback === true) return;
|
|
1058
|
+
const removed = context?.removedProperties ?? new Set(removedTemplateKeys(previousProperties, properties));
|
|
1059
|
+
const portStays = properties["DBPort"] != null || properties["Port"] != null;
|
|
1060
|
+
const names = kept.filter((key) => removed.has(key) && !((key === "DBPort" || key === "Port") && portStays));
|
|
1061
|
+
if (names.length === 0) return;
|
|
1062
|
+
const one = names.length === 1;
|
|
1063
|
+
const modify = resourceType === "AWS::Neptune::DBInstance" ? "ModifyDBInstance" : "ModifyDBCluster";
|
|
1064
|
+
this.logger.warn(context?.replayingState === true ? safeMsg`${logicalId} (${resourceType}): ${one ? "property" : "properties"} ${names.join(", ")} ${one ? "is" : "are"} absent from the state being restored; ${modify} keeps a setting it is not sent, so the rollback leaves any value the failed deploy applied in place.` : safeMsg`${logicalId} (${resourceType}): ${one ? "property" : "properties"} ${names.join(", ")} ${one ? "was" : "were"} removed from the template; cdkd leaves the current AWS ${one ? "value" : "values"} in place${measured ? ", as CloudFormation does" : ""}. Declare ${one ? "it" : "them"} to change ${one ? "it" : "them"}.`);
|
|
1065
|
+
}
|
|
1066
|
+
readPublicAccess(properties, resourceType, logicalId, physicalId, stateBorne) {
|
|
1067
|
+
const refusal = configBooleanRefusal(properties, "PubliclyAccessible", logicalId);
|
|
1068
|
+
if (refusal === void 0) return coerceCfnBoolean(properties["PubliclyAccessible"]);
|
|
1069
|
+
if (!stateBorne) throw markNonRetryable(new ProvisioningError(safeMsg`Neptune DBInstance ${refusal}; nothing was changed.`, resourceType, logicalId, physicalId));
|
|
1070
|
+
this.logger.warn(safeMsg`${logicalId} (${resourceType}): the recorded PubliclyAccessible is unusable, so it is not sent.`);
|
|
1071
|
+
}
|
|
1072
|
+
warnPublicAccessKept(logicalId, resourceType, properties, previousProperties, context) {
|
|
1073
|
+
if (context?.desiredFromAwsReadback === true) return;
|
|
1074
|
+
if (!(context?.removedProperties ?? new Set(removedTemplateKeys(previousProperties, properties))).has("PubliclyAccessible")) return;
|
|
1075
|
+
if (coerceCfnBoolean(previousProperties["PubliclyAccessible"]) !== true) return;
|
|
1076
|
+
this.logger.warn(context?.replayingState === true ? safeMsg`${logicalId} (${resourceType}): PubliclyAccessible is absent from the state being restored, and ModifyDBInstance keeps a setting it is not sent, so the instance stays publicly accessible after this rollback, as CloudFormation leaves it. Set PubliclyAccessible: false to close it.` : safeMsg`${logicalId} (${resourceType}): PubliclyAccessible was removed from the template; the instance stays publicly accessible, as CloudFormation leaves it. Set PubliclyAccessible: false to close it.`);
|
|
1077
|
+
}
|
|
1078
|
+
async dbClusterSecurityGroupsToSend(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
1079
|
+
const desired = properties["VpcSecurityGroupIds"];
|
|
1080
|
+
if (desired !== void 0) return desired.length > 0 ? desired : void 0;
|
|
1081
|
+
const previous = previousProperties["VpcSecurityGroupIds"];
|
|
1082
|
+
if (context?.desiredFromAwsReadback === true) return void 0;
|
|
1083
|
+
if (!Array.isArray(previous) || previous.length === 0) return void 0;
|
|
1084
|
+
let groupId;
|
|
1085
|
+
let missing;
|
|
1086
|
+
let awsError;
|
|
1087
|
+
try {
|
|
1088
|
+
const subnetGroupName = (await this.describeDBCluster(physicalId))?.DBSubnetGroup;
|
|
1089
|
+
const vpcId = subnetGroupName ? (await this.getClient().send(new DescribeDBSubnetGroupsCommand({ DBSubnetGroupName: subnetGroupName }))).DBSubnetGroups?.[0]?.VpcId : void 0;
|
|
1090
|
+
if (!subnetGroupName) missing = "the cluster reports no DB subnet group";
|
|
1091
|
+
else if (!vpcId) missing = `DB subnet group ${subnetGroupName} reports no VPC`;
|
|
1092
|
+
else {
|
|
1093
|
+
groupId = (await this.getEc2Client().send(new DescribeSecurityGroupsCommand({ Filters: [{
|
|
1094
|
+
Name: "vpc-id",
|
|
1095
|
+
Values: [vpcId]
|
|
1096
|
+
}, {
|
|
1097
|
+
Name: "group-name",
|
|
1098
|
+
Values: ["default"]
|
|
1099
|
+
}] }))).SecurityGroups?.[0]?.GroupId;
|
|
1100
|
+
if (!groupId) missing = `VPC ${vpcId} has no security group named default`;
|
|
1101
|
+
}
|
|
1102
|
+
} catch (error) {
|
|
1103
|
+
awsError = error;
|
|
1104
|
+
}
|
|
1105
|
+
if (groupId === void 0) {
|
|
1106
|
+
const mask = context?.maskSecrets ?? ((text) => text);
|
|
1107
|
+
const failure = missing === void 0 ? describeAwsFailure(awsError) : void 0;
|
|
1108
|
+
if (failure?.redacted === true) this.logger.debug(safeMsg`${logicalId}: the VPC default security group lookup failed: ${mask(failure.detail)}`);
|
|
1109
|
+
const shown = mask(failure?.summary ?? "");
|
|
1110
|
+
const refusal = new ProvisioningError(`Failed to update Neptune DBCluster ${logicalId}: VpcSecurityGroupIds was removed from the template, so the cluster moves to its VPC's default security group (as CloudFormation does), but cdkd could not find that group: ${missing ?? shown}. Declare VpcSecurityGroupIds in the template to choose the cluster's security groups.`, resourceType, logicalId, physicalId, awsError instanceof Error ? awsError : void 0);
|
|
1111
|
+
if (missing !== void 0) throw markNonRetryable(refusal);
|
|
1112
|
+
throw awsError instanceof Error && (failure?.redacted === true || shown !== (failure?.summary ?? "")) ? markRedactedCause(refusal) : refusal;
|
|
1113
|
+
}
|
|
1114
|
+
this.logger.warn(safeMsg`${logicalId}: VpcSecurityGroupIds was removed from the template; attaching the VPC's default security group ${groupId}, as CloudFormation does.`);
|
|
1115
|
+
return [groupId];
|
|
1116
|
+
}
|
|
1117
|
+
async describeDBInstance(dbInstanceIdentifier) {
|
|
1118
|
+
return (await this.getClient().send(new DescribeDBInstancesCommand({ DBInstanceIdentifier: dbInstanceIdentifier }))).DBInstances?.[0];
|
|
1119
|
+
}
|
|
1120
|
+
async waitForMemberAfterSecurityGroupChange(dbInstanceIdentifier, clusterLogicalId, maxWaitMs = 18e5) {
|
|
1121
|
+
const startTime = Date.now();
|
|
1122
|
+
let delay = 5e3;
|
|
1123
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1124
|
+
let status;
|
|
1125
|
+
try {
|
|
1126
|
+
status = (await this.describeDBInstance(dbInstanceIdentifier))?.DBInstanceStatus;
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
if (this.isNotFoundError(error, "DBInstanceNotFoundFault")) return;
|
|
1129
|
+
throw error;
|
|
1130
|
+
}
|
|
1131
|
+
if (status !== "modifying") return;
|
|
1132
|
+
await this.sleep(delay);
|
|
1133
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1134
|
+
}
|
|
1135
|
+
throw new Error(safeMsg`Timed out waiting for a member instance of Neptune DBCluster ${clusterLogicalId} to finish applying its new VPC security groups`);
|
|
1136
|
+
}
|
|
1137
|
+
requireClusterWhileWaiting(cluster, logicalId) {
|
|
1138
|
+
if (cluster === void 0) throw new Error(safeMsg`DescribeDBClusters returned no cluster for Neptune DBCluster ${logicalId} while waiting for its new VPC security groups`);
|
|
1139
|
+
if (isTerminalDbWaitStatus(cluster.Status)) throw new Error(safeMsg`Neptune DBCluster ${logicalId} entered status ${cluster.Status ?? ""} while applying its new VPC security groups`);
|
|
1140
|
+
return cluster;
|
|
1141
|
+
}
|
|
1142
|
+
async waitForSecurityGroupsApplied(dbClusterIdentifier, logicalId, expected, maxWaitMs = 18e5) {
|
|
1143
|
+
const want = [...new Set(expected)].sort().join(" ");
|
|
1144
|
+
const startTime = Date.now();
|
|
1145
|
+
let delay = 5e3;
|
|
1146
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1147
|
+
const cluster = this.requireClusterWhileWaiting(await this.describeDBCluster(dbClusterIdentifier), logicalId);
|
|
1148
|
+
const groups = cluster.VpcSecurityGroups ?? [];
|
|
1149
|
+
const ids = groups.map((group) => group.VpcSecurityGroupId ?? "").sort().join(" ");
|
|
1150
|
+
if (cluster.Status !== "modifying" && groups.every((group) => group.Status === "active") && ids === want) return;
|
|
1151
|
+
this.logger.debug(safeMsg`Neptune DBCluster ${logicalId} security groups not settled yet (status ${cluster.Status ?? "unknown"})`);
|
|
1152
|
+
await this.sleep(delay);
|
|
1153
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1154
|
+
}
|
|
1155
|
+
throw new Error(`Timed out waiting for Neptune DBCluster ${logicalId} to apply its new VPC security groups`);
|
|
1156
|
+
}
|
|
1157
|
+
async deleteAfterProtectionFlip(logicalId, physicalId, send) {
|
|
1158
|
+
await deleteAfterProtectionFlipWithFinalSnapshot({
|
|
1159
|
+
stateFault: "InvalidDBClusterStateFault",
|
|
1160
|
+
send,
|
|
1161
|
+
noun: "cluster",
|
|
1162
|
+
label: "Neptune DBCluster",
|
|
1163
|
+
logicalId,
|
|
1164
|
+
read: async () => {
|
|
1165
|
+
const current = await this.describeDBCluster(physicalId);
|
|
1166
|
+
return current === void 0 ? void 0 : {
|
|
1167
|
+
deletionProtection: current.DeletionProtection,
|
|
1168
|
+
status: current.Status
|
|
1169
|
+
};
|
|
1170
|
+
},
|
|
1171
|
+
sleep: (ms) => this.sleep(ms),
|
|
1172
|
+
logger: this.logger,
|
|
1173
|
+
remainingMs: enclosingDeadlineRemainingMs()
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
async waitForClusterAvailable(logicalId, dbClusterIdentifier, during = "being created", maxWaitMs = CLUSTER_AVAILABLE_MAX_WAIT_MS) {
|
|
1177
|
+
const startTime = Date.now();
|
|
1178
|
+
let delay = 5e3;
|
|
1179
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1180
|
+
const status = (await this.describeDBCluster(dbClusterIdentifier))?.Status;
|
|
1181
|
+
this.logger.debug(`Neptune DBCluster ${dbClusterIdentifier} status: ${status}`);
|
|
1182
|
+
if (status === "available") return;
|
|
1183
|
+
if (isTerminalDbWaitStatus(status)) throw new Error(safeMsg`Neptune DBCluster ${logicalId} entered status ${status ?? ""} while ${during}, which it does not leave on its own`);
|
|
1184
|
+
await this.sleep(delay);
|
|
1185
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1186
|
+
}
|
|
1187
|
+
throw new Error(`Timed out waiting for Neptune DBCluster ${dbClusterIdentifier} to become available`);
|
|
1188
|
+
}
|
|
1189
|
+
async waitForClusterDeleted(logicalId, dbClusterIdentifier, maxWaitMs = 18e5) {
|
|
1190
|
+
const startTime = Date.now();
|
|
1191
|
+
let delay = 5e3;
|
|
1192
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1193
|
+
try {
|
|
1194
|
+
const cluster = await this.describeDBCluster(dbClusterIdentifier);
|
|
1195
|
+
const status = cluster?.Status;
|
|
1196
|
+
this.logger.debug(`Neptune DBCluster ${logicalId} status: ${status}`);
|
|
1197
|
+
if (!cluster) return;
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
if (this.isNotFoundError(error, "DBClusterNotFoundFault")) return;
|
|
1200
|
+
throw error;
|
|
1201
|
+
}
|
|
1202
|
+
await this.sleep(delay);
|
|
1203
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1204
|
+
}
|
|
1205
|
+
throw new Error(`Timed out waiting for Neptune DBCluster ${logicalId} to be deleted`);
|
|
1206
|
+
}
|
|
1207
|
+
async waitForInstanceAvailable(logicalId, dbInstanceIdentifier, maxWaitMs = 18e5) {
|
|
1208
|
+
const startTime = Date.now();
|
|
1209
|
+
let delay = 1e4;
|
|
1210
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1211
|
+
const status = (await this.describeDBInstance(dbInstanceIdentifier))?.DBInstanceStatus;
|
|
1212
|
+
this.logger.debug(`Neptune DBInstance ${dbInstanceIdentifier} status: ${status}`);
|
|
1213
|
+
if (status === "available") return;
|
|
1214
|
+
if (isTerminalDbWaitStatus(status)) throw new Error(safeMsg`Neptune DBInstance ${logicalId} entered status ${status ?? ""} while being created, which it does not leave on its own`);
|
|
1215
|
+
await this.sleep(delay);
|
|
1216
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1217
|
+
}
|
|
1218
|
+
throw new Error(`Timed out waiting for Neptune DBInstance ${dbInstanceIdentifier} to become available`);
|
|
1219
|
+
}
|
|
1220
|
+
async waitForInstanceDeleted(dbInstanceIdentifier, maxWaitMs = 18e5) {
|
|
1221
|
+
const startTime = Date.now();
|
|
1222
|
+
let delay = 1e4;
|
|
1223
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1224
|
+
try {
|
|
1225
|
+
const instance = await this.describeDBInstance(dbInstanceIdentifier);
|
|
1226
|
+
const status = instance?.DBInstanceStatus;
|
|
1227
|
+
this.logger.debug(`Neptune DBInstance ${dbInstanceIdentifier} status: ${status}`);
|
|
1228
|
+
if (!instance) return;
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
if (this.isNotFoundError(error, "DBInstanceNotFoundFault")) return;
|
|
1231
|
+
throw error;
|
|
1232
|
+
}
|
|
1233
|
+
await this.sleep(delay);
|
|
1234
|
+
delay = Math.min(delay * 2, 1e4);
|
|
1235
|
+
}
|
|
1236
|
+
throw new Error(`Timed out waiting for Neptune DBInstance ${dbInstanceIdentifier} to be deleted`);
|
|
1237
|
+
}
|
|
1238
|
+
sleep(ms) {
|
|
1239
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1240
|
+
}
|
|
1241
|
+
getDriftUnknownPaths(resourceType) {
|
|
1242
|
+
if (resourceType === "AWS::Neptune::DBCluster") return [
|
|
1243
|
+
"AvailabilityZones",
|
|
1244
|
+
"DBInstanceParameterGroupName",
|
|
1245
|
+
"RestoreToTime",
|
|
1246
|
+
"RestoreType",
|
|
1247
|
+
"SnapshotIdentifier",
|
|
1248
|
+
"SourceDBClusterIdentifier",
|
|
1249
|
+
"UseLatestRestorableTime"
|
|
1250
|
+
];
|
|
1251
|
+
if (resourceType === "AWS::Neptune::DBInstance") return ["AllowMajorVersionUpgrade"];
|
|
1252
|
+
return [];
|
|
1253
|
+
}
|
|
1254
|
+
getDriftUnorderedPaths(resourceType) {
|
|
1255
|
+
return resourceType === "AWS::Neptune::DBCluster" ? [
|
|
1256
|
+
"AssociatedRoles",
|
|
1257
|
+
"EnableCloudwatchLogsExports",
|
|
1258
|
+
"VpcSecurityGroupIds"
|
|
1259
|
+
] : [];
|
|
1260
|
+
}
|
|
1261
|
+
canonicalizeDriftProperties(resourceType, properties) {
|
|
1262
|
+
if (resourceType !== "AWS::Neptune::DBCluster") return properties;
|
|
1263
|
+
const out = { ...properties };
|
|
1264
|
+
if (Array.isArray(out["AssociatedRoles"])) out["AssociatedRoles"] = clusterRoleList(out["AssociatedRoles"]) ?? out["AssociatedRoles"];
|
|
1265
|
+
const logs = out["EnableCloudwatchLogsExports"];
|
|
1266
|
+
if (Array.isArray(logs) && stringList(logs) !== void 0) out["EnableCloudwatchLogsExports"] = [...new Set(logs)];
|
|
1267
|
+
return out;
|
|
1268
|
+
}
|
|
1269
|
+
canonicalizeUnsettledProperties(resourceType, properties) {
|
|
1270
|
+
return this.canonicalizeDriftProperties(resourceType, properties);
|
|
1271
|
+
}
|
|
1272
|
+
async readUnsettled(physicalId, logicalId, resourceType, names, recordedProperties, desiredProperties, attemptedItems) {
|
|
1273
|
+
const live = await this.readCurrentState(physicalId, logicalId, resourceType);
|
|
1274
|
+
if (resourceType !== "AWS::Neptune::DBCluster") return live;
|
|
1275
|
+
if (live === void 0 || live === RESOURCE_NOT_FOUND) return live;
|
|
1276
|
+
const attempted = (key) => [...attemptedItems?.get(key) ?? []].filter(isUsableAttemptedName);
|
|
1277
|
+
const out = { ...live };
|
|
1278
|
+
if (names.has("AssociatedRoles")) {
|
|
1279
|
+
const named = /* @__PURE__ */ new Set([
|
|
1280
|
+
...(clusterRoleList(recordedProperties?.["AssociatedRoles"]) ?? []).map(clusterRoleKey),
|
|
1281
|
+
...(clusterRoleList(desiredProperties?.["AssociatedRoles"]) ?? []).map(clusterRoleKey),
|
|
1282
|
+
...attempted("AssociatedRoles")
|
|
1283
|
+
]);
|
|
1284
|
+
out["AssociatedRoles"] = (live["AssociatedRoles"] ?? []).filter((role) => named.has(clusterRoleKey(role)));
|
|
1285
|
+
}
|
|
1286
|
+
if (names.has("EnableCloudwatchLogsExports")) {
|
|
1287
|
+
const named = /* @__PURE__ */ new Set([
|
|
1288
|
+
...stringList(recordedProperties?.["EnableCloudwatchLogsExports"]) ?? [],
|
|
1289
|
+
...stringList(desiredProperties?.["EnableCloudwatchLogsExports"]) ?? [],
|
|
1290
|
+
...attempted("EnableCloudwatchLogsExports")
|
|
1291
|
+
]);
|
|
1292
|
+
out["EnableCloudwatchLogsExports"] = (live["EnableCloudwatchLogsExports"] ?? []).filter((type) => named.has(type));
|
|
1293
|
+
}
|
|
1294
|
+
return out;
|
|
1295
|
+
}
|
|
1296
|
+
async unsettledInTransition(physicalId, _logicalId, resourceType, names, _recordedProperties, desiredProperties) {
|
|
1297
|
+
if (desiredProperties !== void 0) return /* @__PURE__ */ new Set();
|
|
1298
|
+
try {
|
|
1299
|
+
if (resourceType === "AWS::Neptune::DBCluster") {
|
|
1300
|
+
const cluster = await this.describeDBCluster(physicalId);
|
|
1301
|
+
return isUnmovingDbStatus(cluster?.Status) && !holdsPendingValue(cluster?.PendingModifiedValues) ? /* @__PURE__ */ new Set() : names;
|
|
1302
|
+
}
|
|
1303
|
+
if (resourceType === "AWS::Neptune::DBInstance") {
|
|
1304
|
+
const instance = await this.describeDBInstance(physicalId);
|
|
1305
|
+
return isUnmovingDbStatus(instance?.DBInstanceStatus) && !holdsPendingValue(instance?.PendingModifiedValues) ? /* @__PURE__ */ new Set() : names;
|
|
1306
|
+
}
|
|
1307
|
+
return /* @__PURE__ */ new Set();
|
|
1308
|
+
} catch {
|
|
1309
|
+
return names;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
async import(input) {
|
|
1313
|
+
switch (input.resourceType) {
|
|
1314
|
+
case "AWS::Neptune::DBInstance": return this.importDBInstance(input);
|
|
1315
|
+
case "AWS::Neptune::DBCluster": return this.importDBCluster(input);
|
|
1316
|
+
case "AWS::Neptune::DBSubnetGroup": return this.importDBSubnetGroup(input);
|
|
1317
|
+
default: return null;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
async readCurrentState(physicalId, _logicalId, resourceType) {
|
|
1321
|
+
switch (resourceType) {
|
|
1322
|
+
case "AWS::Neptune::DBInstance": return this.readCurrentStateDBInstance(physicalId);
|
|
1323
|
+
case "AWS::Neptune::DBCluster": return this.readCurrentStateDBCluster(physicalId);
|
|
1324
|
+
case "AWS::Neptune::DBSubnetGroup": return this.readCurrentStateDBSubnetGroup(physicalId);
|
|
1325
|
+
default: return;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
async readCurrentStateDBInstance(physicalId) {
|
|
1329
|
+
let inst;
|
|
1330
|
+
try {
|
|
1331
|
+
inst = await this.describeDBInstance(physicalId);
|
|
1332
|
+
} catch (err) {
|
|
1333
|
+
if (err?.name === "DBInstanceNotFoundFault") return RESOURCE_NOT_FOUND;
|
|
1334
|
+
if (this.isNotFoundError(err, "DBInstanceNotFoundFault")) return void 0;
|
|
1335
|
+
throw err;
|
|
1336
|
+
}
|
|
1337
|
+
if (!inst) return RESOURCE_NOT_FOUND;
|
|
1338
|
+
const result = {};
|
|
1339
|
+
if (inst.DBInstanceIdentifier !== void 0) result["DBInstanceIdentifier"] = inst.DBInstanceIdentifier;
|
|
1340
|
+
if (inst.DBInstanceClass !== void 0) result["DBInstanceClass"] = inst.DBInstanceClass;
|
|
1341
|
+
if (inst.DBClusterIdentifier !== void 0) result["DBClusterIdentifier"] = inst.DBClusterIdentifier;
|
|
1342
|
+
if (inst.DBSubnetGroup?.DBSubnetGroupName !== void 0) result["DBSubnetGroupName"] = inst.DBSubnetGroup.DBSubnetGroupName;
|
|
1343
|
+
if (inst.AvailabilityZone !== void 0) result["AvailabilityZone"] = inst.AvailabilityZone;
|
|
1344
|
+
if (inst.PreferredMaintenanceWindow !== void 0) result["PreferredMaintenanceWindow"] = inst.PreferredMaintenanceWindow;
|
|
1345
|
+
if (inst.AutoMinorVersionUpgrade !== void 0) result["AutoMinorVersionUpgrade"] = inst.AutoMinorVersionUpgrade;
|
|
1346
|
+
if (inst.DeletionProtection !== void 0) result["DeletionProtection"] = inst.DeletionProtection;
|
|
1347
|
+
if (inst.PubliclyAccessible !== void 0) result["PubliclyAccessible"] = inst.PubliclyAccessible;
|
|
1348
|
+
const pg = inst.DBParameterGroups?.[0]?.DBParameterGroupName;
|
|
1349
|
+
if (pg !== void 0) result["DBParameterGroupName"] = pg;
|
|
1350
|
+
if (inst.DBInstanceArn) await this.attachTags(result, inst.DBInstanceArn);
|
|
1351
|
+
return result;
|
|
1352
|
+
}
|
|
1353
|
+
async readCurrentStateDBCluster(physicalId) {
|
|
1354
|
+
let cluster;
|
|
1355
|
+
try {
|
|
1356
|
+
cluster = await this.describeDBCluster(physicalId);
|
|
1357
|
+
} catch (err) {
|
|
1358
|
+
if (err?.name === "DBClusterNotFoundFault") return RESOURCE_NOT_FOUND;
|
|
1359
|
+
if (this.isNotFoundError(err, "DBClusterNotFoundFault")) return void 0;
|
|
1360
|
+
throw err;
|
|
1361
|
+
}
|
|
1362
|
+
if (!cluster) return RESOURCE_NOT_FOUND;
|
|
1363
|
+
const result = {};
|
|
1364
|
+
if (cluster.DBClusterIdentifier !== void 0) result["DBClusterIdentifier"] = cluster.DBClusterIdentifier;
|
|
1365
|
+
if (cluster.EngineVersion !== void 0) result["EngineVersion"] = cluster.EngineVersion;
|
|
1366
|
+
if (cluster.Port !== void 0) {
|
|
1367
|
+
result["Port"] = cluster.Port;
|
|
1368
|
+
result["DBPort"] = cluster.Port;
|
|
1369
|
+
}
|
|
1370
|
+
result["VpcSecurityGroupIds"] = (cluster.VpcSecurityGroups ?? []).map((sg) => sg.VpcSecurityGroupId).filter((id) => !!id);
|
|
1371
|
+
if (cluster.DBSubnetGroup !== void 0) result["DBSubnetGroupName"] = cluster.DBSubnetGroup;
|
|
1372
|
+
if (cluster.StorageEncrypted !== void 0) result["StorageEncrypted"] = cluster.StorageEncrypted;
|
|
1373
|
+
if (cluster.KmsKeyId !== void 0) result["KmsKeyId"] = cluster.KmsKeyId;
|
|
1374
|
+
if (cluster.BackupRetentionPeriod !== void 0) result["BackupRetentionPeriod"] = cluster.BackupRetentionPeriod;
|
|
1375
|
+
if (cluster.PreferredBackupWindow !== void 0) result["PreferredBackupWindow"] = cluster.PreferredBackupWindow;
|
|
1376
|
+
if (cluster.PreferredMaintenanceWindow !== void 0) result["PreferredMaintenanceWindow"] = cluster.PreferredMaintenanceWindow;
|
|
1377
|
+
if (cluster.DBClusterParameterGroup !== void 0) result["DBClusterParameterGroupName"] = cluster.DBClusterParameterGroup;
|
|
1378
|
+
if (cluster.IAMDatabaseAuthenticationEnabled !== void 0) result["IamAuthEnabled"] = cluster.IAMDatabaseAuthenticationEnabled;
|
|
1379
|
+
if (cluster.DeletionProtection !== void 0) result["DeletionProtection"] = cluster.DeletionProtection;
|
|
1380
|
+
if (cluster.CopyTagsToSnapshot !== void 0) result["CopyTagsToSnapshot"] = cluster.CopyTagsToSnapshot;
|
|
1381
|
+
if (cluster.NetworkType !== void 0) result["NetworkType"] = cluster.NetworkType;
|
|
1382
|
+
const { MinCapacity: min, MaxCapacity: max } = cluster.ServerlessV2ScalingConfiguration ?? {};
|
|
1383
|
+
if (min !== void 0 || max !== void 0) result["ServerlessScalingConfiguration"] = {
|
|
1384
|
+
...min !== void 0 && { MinCapacity: min },
|
|
1385
|
+
...max !== void 0 && { MaxCapacity: max }
|
|
1386
|
+
};
|
|
1387
|
+
result["EnableCloudwatchLogsExports"] = cluster.EnabledCloudwatchLogsExports ?? [];
|
|
1388
|
+
result["AssociatedRoles"] = (cluster.AssociatedRoles ?? []).map((role) => ({
|
|
1389
|
+
RoleArn: role.RoleArn,
|
|
1390
|
+
...role.FeatureName ? { FeatureName: role.FeatureName } : {}
|
|
1391
|
+
}));
|
|
1392
|
+
if (cluster.DBClusterArn) await this.attachTags(result, cluster.DBClusterArn);
|
|
1393
|
+
return result;
|
|
1394
|
+
}
|
|
1395
|
+
async readCurrentStateDBSubnetGroup(physicalId) {
|
|
1396
|
+
let resp;
|
|
1397
|
+
try {
|
|
1398
|
+
resp = await this.getClient().send(new DescribeDBSubnetGroupsCommand({ DBSubnetGroupName: physicalId }));
|
|
1399
|
+
} catch (err) {
|
|
1400
|
+
if (err?.name === "DBSubnetGroupNotFoundFault") return RESOURCE_NOT_FOUND;
|
|
1401
|
+
if (this.isNotFoundError(err, "DBSubnetGroupNotFoundFault")) return void 0;
|
|
1402
|
+
throw err;
|
|
1403
|
+
}
|
|
1404
|
+
const sg = resp.DBSubnetGroups?.[0];
|
|
1405
|
+
if (!sg) return RESOURCE_NOT_FOUND;
|
|
1406
|
+
const result = {};
|
|
1407
|
+
if (sg.DBSubnetGroupName !== void 0) result["DBSubnetGroupName"] = sg.DBSubnetGroupName;
|
|
1408
|
+
if (sg.DBSubnetGroupDescription !== void 0) result["DBSubnetGroupDescription"] = sg.DBSubnetGroupDescription;
|
|
1409
|
+
result["SubnetIds"] = (sg.Subnets ?? []).map((s) => s.SubnetIdentifier).filter((id) => !!id);
|
|
1410
|
+
if (sg.DBSubnetGroupArn) await this.attachTags(result, sg.DBSubnetGroupArn);
|
|
1411
|
+
return result;
|
|
1412
|
+
}
|
|
1413
|
+
async attachTags(result, arn) {
|
|
1414
|
+
try {
|
|
1415
|
+
const tagsResp = await this.getClient().send(new ListTagsForResourceCommand({ ResourceName: arn }));
|
|
1416
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.TagList);
|
|
1417
|
+
} catch (err) {
|
|
1418
|
+
this.logger.debug(`Neptune ListTagsForResource(${arn}) failed: ${describeAwsFailure(err).detail}`);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
async importDBInstance(input) {
|
|
1422
|
+
const explicit = resolveExplicitPhysicalId(input, "DBInstanceIdentifier");
|
|
1423
|
+
if (explicit) try {
|
|
1424
|
+
const described = (await this.getClient().send(new DescribeDBInstancesCommand({ DBInstanceIdentifier: explicit }))).DBInstances?.[0];
|
|
1425
|
+
return {
|
|
1426
|
+
physicalId: explicit,
|
|
1427
|
+
attributes: instanceAttributes(described)
|
|
1428
|
+
};
|
|
1429
|
+
} catch (err) {
|
|
1430
|
+
if (err.name === "DBInstanceNotFoundFault") return null;
|
|
1431
|
+
throw err;
|
|
1432
|
+
}
|
|
1433
|
+
return null;
|
|
1434
|
+
}
|
|
1435
|
+
async importDBCluster(input) {
|
|
1436
|
+
const explicit = resolveExplicitPhysicalId(input, "DBClusterIdentifier");
|
|
1437
|
+
if (explicit) try {
|
|
1438
|
+
return {
|
|
1439
|
+
physicalId: explicit,
|
|
1440
|
+
attributes: clusterAttributes((await this.getClient().send(new DescribeDBClustersCommand({ DBClusterIdentifier: explicit }))).DBClusters?.[0])
|
|
1441
|
+
};
|
|
1442
|
+
} catch (err) {
|
|
1443
|
+
if (err.name === "DBClusterNotFoundFault") return null;
|
|
1444
|
+
throw err;
|
|
1445
|
+
}
|
|
1446
|
+
return null;
|
|
1447
|
+
}
|
|
1448
|
+
async importDBSubnetGroup(input) {
|
|
1449
|
+
const explicit = resolveExplicitPhysicalId(input, "DBSubnetGroupName");
|
|
1450
|
+
if (explicit) try {
|
|
1451
|
+
await this.getClient().send(new DescribeDBSubnetGroupsCommand({ DBSubnetGroupName: explicit }));
|
|
1452
|
+
return {
|
|
1453
|
+
physicalId: explicit,
|
|
1454
|
+
attributes: {}
|
|
1455
|
+
};
|
|
1456
|
+
} catch (err) {
|
|
1457
|
+
if (err.name === "DBSubnetGroupNotFoundFault") return null;
|
|
1458
|
+
throw err;
|
|
1459
|
+
}
|
|
1460
|
+
return null;
|
|
1461
|
+
}
|
|
1462
|
+
};
|
|
1463
|
+
function securityGroupListChanged(sent, previous) {
|
|
1464
|
+
if (sent === void 0) return false;
|
|
1465
|
+
const before = Array.isArray(previous) ? previous.filter((id) => typeof id === "string") : [];
|
|
1466
|
+
return [...sent].sort().join(" ") !== [...before].sort().join(" ");
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
export { NeptuneProvider };
|
|
1470
|
+
//# sourceMappingURL=neptune-provider-BqtKFZaE.js.map
|