@go-to-k/cdkd 0.314.0 → 0.315.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.
Files changed (28) hide show
  1. package/dist/{asg-provider-_nun2Jda.js → asg-provider-DpNkEibf.js} +2 -2
  2. package/dist/{asg-provider-_nun2Jda.js.map → asg-provider-DpNkEibf.js.map} +1 -1
  3. package/dist/cli.js +2 -2
  4. package/dist/{db-instance-modify-retry-By0_BFNO.js → db-instance-modify-retry-B1NRKBSQ.js} +12 -6
  5. package/dist/db-instance-modify-retry-B1NRKBSQ.js.map +1 -0
  6. package/dist/{deploy-engine-ClIUqDQL.js → deploy-engine--bCIMkkk.js} +41 -52
  7. package/dist/deploy-engine--bCIMkkk.js.map +1 -0
  8. package/dist/{iam-role-provider-DJep3uns.js → iam-role-provider-s5--ZmBZ.js} +2 -2
  9. package/dist/{iam-role-provider-DJep3uns.js.map → iam-role-provider-s5--ZmBZ.js.map} +1 -1
  10. package/dist/index.d.ts +2 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +2 -2
  13. package/dist/{neptune-provider-CGZ_G4Vz.js → neptune-provider-BNxotnbp.js} +374 -19
  14. package/dist/neptune-provider-BNxotnbp.js.map +1 -0
  15. package/dist/{program-Du6jOw1-.js → program-CMtAmRyw.js} +4 -4
  16. package/dist/{program-Du6jOw1-.js.map → program-CMtAmRyw.js.map} +1 -1
  17. package/dist/{provider-classes-ZqxIefLl.js → provider-classes-B8kkAZqJ.js} +375 -29
  18. package/dist/provider-classes-B8kkAZqJ.js.map +1 -0
  19. package/dist/{rds-provider-C2fHUNSE.js → rds-provider-Cm3dsmFW.js} +45 -5
  20. package/dist/rds-provider-Cm3dsmFW.js.map +1 -0
  21. package/dist/{version-B2PvJ12r.js → version-Caty7J3x.js} +2 -2
  22. package/dist/{version-B2PvJ12r.js.map → version-Caty7J3x.js.map} +1 -1
  23. package/package.json +1 -1
  24. package/dist/db-instance-modify-retry-By0_BFNO.js.map +0 -1
  25. package/dist/deploy-engine-ClIUqDQL.js.map +0 -1
  26. package/dist/neptune-provider-CGZ_G4Vz.js.map +0 -1
  27. package/dist/provider-classes-ZqxIefLl.js.map +0 -1
  28. package/dist/rds-provider-C2fHUNSE.js.map +0 -1
@@ -1,10 +1,11 @@
1
1
  import { $ as safeMsg, E as generatedNameFor, n as describeAwsFailure, s as getLogger } from "./aws-failure-text-agWtoyga.js";
2
- import { A as withoutServerErrorRetries, Ar as markNonRetryable, G as ProtectionFlipRegistry, Gt as markCreatedBeforeFailure, J as deleteWithProtectionCompensation, Mn as ProvisioningError, Mr as wrapMaskedAwsError, N as markNotGone, P as assertRegionMatch, Pt as maskerOrIdentity, U as resolveExplicitPhysicalId, Z as isTerminalDbWaitStatus, dn as ambientClientDefaults, et as observeThenDisableProtection, h as clearOnUpdateRemoval, i as reportCreatedResource, j as RESOURCE_NOT_FOUND, jr as markRedactedCause, mr as relayedNameCollision, nt as rdsFamilyProtectionSite, q as deleteAfterProtectionFlipWithFinalSnapshot, tt as protectionFlipKey, v as removedTemplateKeys, z as normalizeAwsTagsToCfn } from "./created-resource-sink-QECX8C-Z.js";
2
+ import { A as withoutServerErrorRetries, Ar as markNonRetryable, G as ProtectionFlipRegistry, Gt as markCreatedBeforeFailure, J as deleteWithProtectionCompensation, Mn as ProvisioningError, Mr as wrapMaskedAwsError, N as markNotGone, P as assertRegionMatch, Pt as maskerOrIdentity, U as resolveExplicitPhysicalId, Wt as markAuxiliaryFailure, X as isProceedDbStatus, Z as isTerminalDbWaitStatus, dn as ambientClientDefaults, et as observeThenDisableProtection, h as clearOnUpdateRemoval, i as reportCreatedResource, j as RESOURCE_NOT_FOUND, jr as markRedactedCause, mr as relayedNameCollision, nt as rdsFamilyProtectionSite, q as deleteAfterProtectionFlipWithFinalSnapshot, tt as protectionFlipKey, v as removedTemplateKeys, z as normalizeAwsTagsToCfn } from "./created-resource-sink-QECX8C-Z.js";
3
3
  import { c as ambientRegion } from "./aws-client-defaults-BX9Y_fKp.js";
4
- import { Ct as enclosingDeadlineRemainingMs, Hi as planTagDiff, Ji as stringifyIfAssigned, Ki as tagPlanWarning, Wi as refuseMalformedDesiredTags, qi as definedAttributes } from "./deploy-engine-ClIUqDQL.js";
5
- import { t as modifyDbInstanceWhenAvailable } from "./db-instance-modify-retry-By0_BFNO.js";
4
+ import { Aa as isUsableAttemptedName, Gi as refuseMalformedDesiredTags, Ji as definedAttributes, Ui as planTagDiff, Yi as stringifyIfAssigned, lc as configBooleanRefusal, qi as tagPlanWarning, sc as coerceCfnBoolean, wt as enclosingDeadlineRemainingMs } from "./deploy-engine--bCIMkkk.js";
5
+ import { n as modifyDbInstanceWhenAvailable, t as isModifyNotAvailableRefusal } from "./db-instance-modify-retry-B1NRKBSQ.js";
6
6
  import { DescribeSecurityGroupsCommand, EC2Client } from "@aws-sdk/client-ec2";
7
- import { AddTagsToResourceCommand, CreateDBClusterCommand, CreateDBInstanceCommand, CreateDBSubnetGroupCommand, DeleteDBClusterCommand, DeleteDBInstanceCommand, DeleteDBSubnetGroupCommand, DescribeDBClustersCommand, DescribeDBInstancesCommand, DescribeDBSubnetGroupsCommand, ListTagsForResourceCommand, ModifyDBClusterCommand, ModifyDBInstanceCommand, ModifyDBSubnetGroupCommand, NeptuneClient, RemoveTagsFromResourceCommand } from "@aws-sdk/client-neptune";
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 } from "@aws-sdk/client-neptune";
8
9
 
9
10
  //#region src/provisioning/providers/neptune-provider.ts
10
11
  function clusterAttributes(cluster) {
@@ -39,13 +40,113 @@ const CLUSTER_KEPT_ON_REMOVAL = [
39
40
  "IamAuthEnabled",
40
41
  "BackupRetentionPeriod",
41
42
  "DBPort",
42
- "Port"
43
+ "Port",
44
+ "CopyTagsToSnapshot"
43
45
  ];
44
46
  const INSTANCE_KEPT_ON_REMOVAL = [
45
47
  "DBParameterGroupName",
46
48
  "PreferredMaintenanceWindow",
47
49
  "AutoMinorVersionUpgrade"
48
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 DEADLINE_MARGIN_MS = 3e4;
122
+ const CLUSTER_MODIFY_KEYS = [
123
+ "EngineVersion",
124
+ "DeletionProtection",
125
+ "BackupRetentionPeriod",
126
+ "PreferredBackupWindow",
127
+ "PreferredMaintenanceWindow",
128
+ "DBClusterParameterGroupName",
129
+ "IamAuthEnabled",
130
+ "VpcSecurityGroupIds",
131
+ "DBPort",
132
+ "Port",
133
+ "CopyTagsToSnapshot",
134
+ "NetworkType",
135
+ "ServerlessScalingConfiguration",
136
+ "EnableCloudwatchLogsExports"
137
+ ];
138
+ const INSTANCE_MODIFY_KEYS = [
139
+ "DBInstanceClass",
140
+ "DBParameterGroupName",
141
+ "PreferredMaintenanceWindow",
142
+ "AutoMinorVersionUpgrade",
143
+ "DeletionProtection",
144
+ "PubliclyAccessible"
145
+ ];
146
+ function markChangedUnsettled(keys, properties, previousProperties, context) {
147
+ for (const key of keys) if (!isDeepStrictEqual(properties[key], previousProperties[key])) context?.markUnsettled?.(key);
148
+ }
149
+ const CLUSTER_UNMEASURED_ON_REMOVAL = ["NetworkType", "ServerlessScalingConfiguration"];
49
150
  var NeptuneProvider = class {
50
151
  neptuneClient;
51
152
  createClient;
@@ -75,7 +176,14 @@ var NeptuneProvider = class {
75
176
  "DBClusterParameterGroupName",
76
177
  "IamAuthEnabled",
77
178
  "DeletionProtection",
78
- "Tags"
179
+ "Tags",
180
+ "AssociatedRoles",
181
+ "AvailabilityZones",
182
+ "CopyTagsToSnapshot",
183
+ "DBInstanceParameterGroupName",
184
+ "EnableCloudwatchLogsExports",
185
+ "NetworkType",
186
+ "ServerlessScalingConfiguration"
79
187
  ])],
80
188
  ["AWS::Neptune::DBInstance", /* @__PURE__ */ new Set([
81
189
  "DBInstanceIdentifier",
@@ -87,17 +195,25 @@ var NeptuneProvider = class {
87
195
  "PreferredMaintenanceWindow",
88
196
  "AutoMinorVersionUpgrade",
89
197
  "DeletionProtection",
90
- "Tags"
198
+ "Tags",
199
+ "AllowMajorVersionUpgrade",
200
+ "PubliclyAccessible"
91
201
  ])]
92
202
  ]);
203
+ 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"]])]]);
93
204
  removalHandledInUpdate = /* @__PURE__ */ new Map([["AWS::Neptune::DBCluster", /* @__PURE__ */ new Set([
94
205
  "DBClusterIdentifier",
95
206
  "DBSubnetGroupName",
96
207
  "StorageEncrypted",
97
208
  "KmsKeyId",
209
+ "AvailabilityZones",
98
210
  "VpcSecurityGroupIds",
99
211
  "Tags",
100
- ...CLUSTER_KEPT_ON_REMOVAL
212
+ "AssociatedRoles",
213
+ "EnableCloudwatchLogsExports",
214
+ "DBInstanceParameterGroupName",
215
+ ...CLUSTER_KEPT_ON_REMOVAL,
216
+ ...CLUSTER_UNMEASURED_ON_REMOVAL
101
217
  ])], ["AWS::Neptune::DBInstance", /* @__PURE__ */ new Set([
102
218
  "DBInstanceIdentifier",
103
219
  "DBClusterIdentifier",
@@ -106,6 +222,8 @@ var NeptuneProvider = class {
106
222
  "DBInstanceClass",
107
223
  "DeletionProtection",
108
224
  "Tags",
225
+ "AllowMajorVersionUpgrade",
226
+ "PubliclyAccessible",
109
227
  ...INSTANCE_KEPT_ON_REMOVAL
110
228
  ])]]);
111
229
  constructor(options) {
@@ -139,8 +257,8 @@ var NeptuneProvider = class {
139
257
  const mask = context?.maskSecrets;
140
258
  switch (resourceType) {
141
259
  case "AWS::Neptune::DBSubnetGroup": return this.createDBSubnetGroup(logicalId, resourceType, properties, mask);
142
- case "AWS::Neptune::DBCluster": return this.createDBCluster(logicalId, resourceType, properties, mask);
143
- case "AWS::Neptune::DBInstance": return this.createDBInstance(logicalId, resourceType, properties, mask);
260
+ case "AWS::Neptune::DBCluster": return this.createDBCluster(logicalId, resourceType, properties, mask, context?.replayingState === true);
261
+ case "AWS::Neptune::DBInstance": return this.createDBInstance(logicalId, resourceType, properties, mask, context?.replayingState === true);
144
262
  default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
145
263
  }
146
264
  }
@@ -222,9 +340,13 @@ var NeptuneProvider = class {
222
340
  throw this.wrapDeleteError(context?.maskSecrets, error, (text) => new ProvisioningError(`Failed to delete Neptune DBSubnetGroup ${logicalId}: ${text}`, resourceType, logicalId, physicalId, cause));
223
341
  }
224
342
  }
225
- async createDBCluster(logicalId, resourceType, properties, mask) {
343
+ async createDBCluster(logicalId, resourceType, properties, mask, replayingState) {
226
344
  this.logger.debug(`Creating Neptune DBCluster ${logicalId}`);
227
345
  const tags = refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId);
346
+ const { roles, logTypes, scaling } = this.readClusterBlocks(properties, resourceType, logicalId, {
347
+ creating: true,
348
+ stateBorne: replayingState
349
+ });
228
350
  const dbClusterIdentifier = properties["DBClusterIdentifier"] || generatedNameFor("AWS::Neptune::DBCluster", logicalId);
229
351
  let clusterCreated = false;
230
352
  let createdResourceId;
@@ -244,6 +366,11 @@ var NeptuneProvider = class {
244
366
  DBClusterParameterGroupName: properties["DBClusterParameterGroupName"],
245
367
  EnableIAMDatabaseAuthentication: properties["IamAuthEnabled"],
246
368
  DeletionProtection: properties["DeletionProtection"],
369
+ AvailabilityZones: properties["AvailabilityZones"],
370
+ CopyTagsToSnapshot: properties["CopyTagsToSnapshot"],
371
+ NetworkType: properties["NetworkType"],
372
+ ...logTypes.length > 0 && { EnableCloudwatchLogsExports: logTypes },
373
+ ...scaling != null && { ServerlessV2ScalingConfiguration: scaling },
247
374
  ...tags.length > 0 && { Tags: tags }
248
375
  }));
249
376
  clusterCreated = true;
@@ -252,6 +379,12 @@ var NeptuneProvider = class {
252
379
  if (!response.DBCluster) throw new Error("CreateDBCluster did not return DBCluster");
253
380
  this.logger.debug(`Successfully created Neptune DBCluster ${logicalId}: ${dbClusterIdentifier}`);
254
381
  if (process.env["CDKD_NO_WAIT"] !== "true") await this.waitForClusterAvailable(logicalId, dbClusterIdentifier);
382
+ try {
383
+ await this.applyClusterRoles(logicalId, dbClusterIdentifier, roles, [], [], mask, void 0);
384
+ } catch (error) {
385
+ markAuxiliaryFailure(error, logicalId);
386
+ throw error;
387
+ }
255
388
  return {
256
389
  physicalId: dbClusterIdentifier,
257
390
  attributes: clusterAttributes(await this.describeDBCluster(dbClusterIdentifier))
@@ -265,7 +398,19 @@ var NeptuneProvider = class {
265
398
  async updateDBCluster(logicalId, physicalId, resourceType, properties, previousProperties, context) {
266
399
  this.logger.debug(`Updating Neptune DBCluster ${logicalId}: ${physicalId}`);
267
400
  refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId, physicalId);
401
+ const { roles, logTypes, scaling } = this.readClusterBlocks(properties, resourceType, logicalId, {
402
+ creating: false,
403
+ stateBorne: context?.replayingState === true || context?.desiredFromAwsReadback === true,
404
+ physicalId
405
+ });
406
+ const logExportConfig = logExportChange(logTypes, stringList(previousProperties["EnableCloudwatchLogsExports"]) ?? []);
268
407
  try {
408
+ const unrecorded = (key) => properties[key] != null && previousProperties[key] == null;
409
+ const live = unrecorded("EngineVersion") || unrecorded("NetworkType") || logExportConfig?.EnableLogTypes !== void 0 ? await this.describeDBCluster(physicalId) : void 0;
410
+ const majorUpgrade = isMajorEngineChange(previousProperties["EngineVersion"] ?? live?.EngineVersion, properties["EngineVersion"]);
411
+ const networkType = properties["NetworkType"];
412
+ const networkChange = networkType != null && networkType !== (previousProperties["NetworkType"] ?? live?.NetworkType);
413
+ markChangedUnsettled(CLUSTER_MODIFY_KEYS, properties, previousProperties, context);
269
414
  const vpcSgIds = await this.dbClusterSecurityGroupsToSend(logicalId, physicalId, resourceType, properties, previousProperties, context);
270
415
  await this.getClient().send(new ModifyDBClusterCommand({
271
416
  DBClusterIdentifier: physicalId,
@@ -278,16 +423,37 @@ var NeptuneProvider = class {
278
423
  EnableIAMDatabaseAuthentication: properties["IamAuthEnabled"],
279
424
  ...vpcSgIds !== void 0 && { VpcSecurityGroupIds: vpcSgIds },
280
425
  Port: properties["DBPort"] != null ? Number(properties["DBPort"]) : properties["Port"] != null ? Number(properties["Port"]) : void 0,
426
+ CopyTagsToSnapshot: properties["CopyTagsToSnapshot"],
427
+ ...networkChange && { NetworkType: networkType },
428
+ ...scaling != null && { ServerlessV2ScalingConfiguration: scaling },
429
+ ...logExportConfig !== void 0 && { CloudwatchLogsExportConfiguration: logExportConfig },
430
+ ...majorUpgrade && { AllowMajorVersionUpgrade: true },
431
+ ...majorUpgrade && properties["DBInstanceParameterGroupName"] != null && { DBInstanceParameterGroupName: properties["DBInstanceParameterGroupName"] },
281
432
  ApplyImmediately: true
282
433
  }));
434
+ const addedLogTypes = (logExportConfig?.EnableLogTypes ?? []).filter((type) => !live?.EnabledCloudwatchLogsExports?.includes(type));
435
+ if (addedLogTypes.length > 0) context?.markAttempted?.("EnableCloudwatchLogsExports", addedLogTypes);
283
436
  this.logger.debug(`Successfully updated Neptune DBCluster ${logicalId}`);
284
437
  const described = await this.describeDBCluster(physicalId);
438
+ if (logExportConfig !== void 0 && process.env["CDKD_NO_WAIT"] !== "true") {
439
+ const { EnableLogTypes: enabled = [], DisableLogTypes: disabled = [] } = logExportConfig;
440
+ await this.waitForApplied(logicalId, "EnableCloudwatchLogsExports", async () => {
441
+ const live = (await this.describeDBCluster(physicalId))?.EnabledCloudwatchLogsExports;
442
+ return enabled.every((type) => live?.includes(type)) && !disabled.some((type) => live?.includes(type));
443
+ });
444
+ }
445
+ if (networkChange && process.env["CDKD_NO_WAIT"] !== "true") await this.waitForApplied(logicalId, "NetworkType", async () => (await this.describeDBCluster(physicalId))?.NetworkType === networkType);
446
+ await this.applyClusterRoles(logicalId, physicalId, roles, clusterRoleList(previousProperties["AssociatedRoles"]) ?? [], described?.AssociatedRoles ?? [], context?.maskSecrets, context);
285
447
  if (process.env["CDKD_NO_WAIT"] !== "true" && securityGroupListChanged(vpcSgIds, previousProperties["VpcSecurityGroupIds"])) {
286
448
  await this.waitForSecurityGroupsApplied(physicalId, logicalId, vpcSgIds ?? []);
287
449
  for (const member of described?.DBClusterMembers ?? []) if (member.DBInstanceIdentifier) await this.waitForMemberAfterSecurityGroupChange(member.DBInstanceIdentifier, logicalId);
288
450
  }
289
- if (described?.DBClusterArn) await this.applyTagDiff(described.DBClusterArn, resourceType, logicalId, previousProperties["Tags"], properties["Tags"]);
451
+ if (described?.DBClusterArn) {
452
+ markChangedUnsettled(["Tags"], properties, previousProperties, context);
453
+ await this.applyTagDiff(described.DBClusterArn, resourceType, logicalId, previousProperties["Tags"], properties["Tags"]);
454
+ }
290
455
  this.warnKeptRemovals(logicalId, resourceType, properties, previousProperties, CLUSTER_KEPT_ON_REMOVAL, context);
456
+ this.warnKeptRemovals(logicalId, resourceType, properties, previousProperties, CLUSTER_UNMEASURED_ON_REMOVAL, context, false);
291
457
  return {
292
458
  physicalId,
293
459
  wasReplaced: false,
@@ -376,9 +542,10 @@ var NeptuneProvider = class {
376
542
  throw this.wrapDeleteError(context?.maskSecrets, error, (text) => new ProvisioningError(`Failed to delete Neptune DBCluster ${logicalId}: ${text}`, resourceType, logicalId, physicalId, cause));
377
543
  }
378
544
  }
379
- async createDBInstance(logicalId, resourceType, properties, mask) {
545
+ async createDBInstance(logicalId, resourceType, properties, mask, replayingState) {
380
546
  this.logger.debug(`Creating Neptune DBInstance ${logicalId}`);
381
547
  const tags = refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId);
548
+ const publiclyAccessible = this.readPublicAccess(properties, resourceType, logicalId, void 0, replayingState);
382
549
  const dbInstanceIdentifier = properties["DBInstanceIdentifier"] || generatedNameFor("AWS::Neptune::DBInstance", logicalId);
383
550
  let instanceCreated = false;
384
551
  let createdResourceId;
@@ -394,6 +561,7 @@ var NeptuneProvider = class {
394
561
  PreferredMaintenanceWindow: properties["PreferredMaintenanceWindow"],
395
562
  AutoMinorVersionUpgrade: properties["AutoMinorVersionUpgrade"],
396
563
  DeletionProtection: properties["DeletionProtection"],
564
+ PubliclyAccessible: publiclyAccessible,
397
565
  ...tags.length > 0 && { Tags: tags }
398
566
  }));
399
567
  instanceCreated = true;
@@ -416,7 +584,9 @@ var NeptuneProvider = class {
416
584
  const mask = context?.maskSecrets;
417
585
  this.logger.debug(`Updating Neptune DBInstance ${logicalId}: ${physicalId}`);
418
586
  refuseMalformedDesiredTags(properties["Tags"], resourceType, logicalId, physicalId);
587
+ const publiclyAccessible = this.readPublicAccess(properties, resourceType, logicalId, physicalId, context?.replayingState === true || context?.desiredFromAwsReadback === true);
419
588
  try {
589
+ markChangedUnsettled(INSTANCE_MODIFY_KEYS, properties, previousProperties, context);
420
590
  await modifyDbInstanceWhenAvailable({
421
591
  send: () => this.getClient().send(new ModifyDBInstanceCommand({
422
592
  DBInstanceIdentifier: physicalId,
@@ -425,6 +595,8 @@ var NeptuneProvider = class {
425
595
  PreferredMaintenanceWindow: properties["PreferredMaintenanceWindow"],
426
596
  AutoMinorVersionUpgrade: properties["AutoMinorVersionUpgrade"],
427
597
  DeletionProtection: properties["DeletionProtection"],
598
+ PubliclyAccessible: publiclyAccessible,
599
+ AllowMajorVersionUpgrade: properties["AllowMajorVersionUpgrade"],
428
600
  ApplyImmediately: true
429
601
  })),
430
602
  readStatus: async () => (await this.describeDBInstance(physicalId))?.DBInstanceStatus,
@@ -435,9 +607,14 @@ var NeptuneProvider = class {
435
607
  remainingMs: enclosingDeadlineRemainingMs()
436
608
  });
437
609
  this.logger.debug(`Successfully updated Neptune DBInstance ${logicalId}`);
610
+ 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);
438
611
  const described = await this.describeDBInstance(physicalId);
439
- if (described?.DBInstanceArn) await this.applyTagDiff(described.DBInstanceArn, resourceType, logicalId, previousProperties["Tags"], properties["Tags"]);
612
+ if (described?.DBInstanceArn) {
613
+ markChangedUnsettled(["Tags"], properties, previousProperties, context);
614
+ await this.applyTagDiff(described.DBInstanceArn, resourceType, logicalId, previousProperties["Tags"], properties["Tags"]);
615
+ }
440
616
  this.warnKeptRemovals(logicalId, resourceType, properties, previousProperties, INSTANCE_KEPT_ON_REMOVAL, context);
617
+ this.warnPublicAccessKept(logicalId, resourceType, properties, previousProperties, context);
441
618
  return {
442
619
  physicalId,
443
620
  wasReplaced: false,
@@ -609,10 +786,100 @@ var NeptuneProvider = class {
609
786
  if (typeof found.resourceId !== "string" || found.resourceId === "") throw new Error(`${api} returned no resource id`);
610
787
  return found.resourceId;
611
788
  }
789
+ readClusterBlocks(properties, resourceType, logicalId, origin) {
790
+ const refuse = (key, expected) => {
791
+ throw markNonRetryable(new ProvisioningError(safeMsg`Neptune DBCluster ${logicalId}: ${key} must be ${expected}; nothing was changed.`, resourceType, logicalId, origin.physicalId));
792
+ };
793
+ const leaveOut = (key) => {
794
+ this.logger.warn(safeMsg`${logicalId} (${resourceType}): the recorded ${key} is unusable, so it is not sent.`);
795
+ };
796
+ const unusableList = (key, expected) => {
797
+ if (!(origin.creating && origin.stateBorne)) return refuse(key, expected);
798
+ leaveOut(key);
799
+ return [];
800
+ };
801
+ const roles = clusterRoleList(properties["AssociatedRoles"]) ?? unusableList("AssociatedRoles", "a list of objects with a RoleArn");
802
+ const logTypes = stringList(properties["EnableCloudwatchLogsExports"]) ?? unusableList("EnableCloudwatchLogsExports", "a list of strings");
803
+ const scaling = serverlessScalingToSend(properties["ServerlessScalingConfiguration"]);
804
+ if (scaling === null) {
805
+ if (!origin.stateBorne) return refuse("ServerlessScalingConfiguration", "an object with both a numeric MinCapacity and a numeric MaxCapacity");
806
+ leaveOut("ServerlessScalingConfiguration");
807
+ }
808
+ return {
809
+ roles,
810
+ logTypes,
811
+ scaling
812
+ };
813
+ }
814
+ async applyClusterRoles(logicalId, physicalId, desired, previous, live, mask, marks) {
815
+ const liveKeys = new Set(live.map(clusterRoleKey));
816
+ const desiredKeys = new Set(desired.map(clusterRoleKey));
817
+ const toAdd = desired.filter((role) => !liveKeys.has(clusterRoleKey(role)));
818
+ const toRemove = previous.filter((role) => {
819
+ const key = clusterRoleKey(role);
820
+ return !desiredKeys.has(key) && liveKeys.has(key);
821
+ });
822
+ if (toAdd.length === 0 && toRemove.length === 0) return;
823
+ const waitBudgetMs = Math.min(ROLE_AVAILABLE_MAX_WAIT_MS, enclosingDeadlineRemainingMs() - DEADLINE_MARGIN_MS);
824
+ 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");
825
+ await this.waitForClusterAvailable(logicalId, physicalId, "changing its roles", waitBudgetMs);
826
+ marks?.markUnsettled?.("AssociatedRoles");
827
+ const whenAvailable = (send) => modifyDbInstanceWhenAvailable({
828
+ send,
829
+ readStatus: async () => (await this.describeDBCluster(physicalId))?.Status,
830
+ sleep: (ms) => this.sleep(ms),
831
+ logger: this.logger,
832
+ label: "Neptune DBCluster",
833
+ subject: "cluster",
834
+ isRefusal: (error) => isRoleCallNotAvailableRefusal(error) || isModifyNotAvailableRefusal(error),
835
+ logicalId,
836
+ remainingMs: enclosingDeadlineRemainingMs()
837
+ });
838
+ for (const role of toRemove) await whenAvailable(() => this.getClient().send(new RemoveRoleFromDBClusterCommand({
839
+ DBClusterIdentifier: physicalId,
840
+ ...role
841
+ })));
842
+ for (const role of toAdd) {
843
+ await whenAvailable(() => this.getClient().send(new AddRoleToDBClusterCommand({
844
+ DBClusterIdentifier: physicalId,
845
+ ...role
846
+ })));
847
+ marks?.markAttempted?.("AssociatedRoles", [clusterRoleKey(role)]);
848
+ }
849
+ if (process.env["CDKD_NO_WAIT"] !== "true") await this.waitForClusterRolesSettled(logicalId, physicalId, new Set(toAdd.map(clusterRoleKey)), new Set(toRemove.map(clusterRoleKey)), mask);
850
+ }
851
+ async waitForClusterRolesSettled(logicalId, physicalId, attached, detached, mask) {
852
+ const shown = (keys) => maskerOrIdentity(mask)(keys.map((key) => key.split("\n")[0] ?? "").join(", "));
853
+ let unsettled = [];
854
+ if (!await this.pollUntilSettled(async () => {
855
+ const roles = (await this.describeDBCluster(physicalId))?.AssociatedRoles ?? [];
856
+ const byKey = new Map(roles.map((role) => [clusterRoleKey(role), role]));
857
+ unsettled = [...[...attached].filter((key) => (byKey.get(key)?.Status ?? "PENDING") === "PENDING"), ...[...detached].filter((key) => byKey.has(key))];
858
+ if (unsettled.length > 0) return false;
859
+ const invalid = [...attached].filter((key) => byKey.get(key)?.Status === "INVALID");
860
+ 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.`);
861
+ return true;
862
+ })) this.logger.warn(safeMsg`${logicalId}: the IAM roles of the Neptune cluster had not settled when cdkd stopped waiting (${shown(unsettled)}).`);
863
+ }
864
+ async waitForApplied(logicalId, what, applied) {
865
+ if (await this.pollUntilSettled(applied)) return;
866
+ this.logger.warn(safeMsg`${logicalId}: the new ${what} did not read back when cdkd stopped waiting; AWS accepted it, so a drift check may report it until it applies.`);
867
+ }
868
+ async pollUntilSettled(settled) {
869
+ const capMs = Math.min(SETTLE_MAX_WAIT_MS, enclosingDeadlineRemainingMs() - DEADLINE_MARGIN_MS);
870
+ const startTime = Date.now();
871
+ let delay = 2e3;
872
+ for (;;) {
873
+ if (await settled()) return true;
874
+ if (Date.now() - startTime + delay >= capMs) return false;
875
+ await this.sleep(delay);
876
+ delay = Math.min(delay * 2, 1e4);
877
+ }
878
+ }
612
879
  async describeDBCluster(dbClusterIdentifier) {
613
880
  return (await this.getClient().send(new DescribeDBClustersCommand({ DBClusterIdentifier: dbClusterIdentifier }))).DBClusters?.[0];
614
881
  }
615
- warnKeptRemovals(logicalId, resourceType, properties, previousProperties, kept, context) {
882
+ warnKeptRemovals(logicalId, resourceType, properties, previousProperties, kept, context, measured = true) {
616
883
  if (context?.desiredFromAwsReadback === true) return;
617
884
  const removed = context?.removedProperties ?? new Set(removedTemplateKeys(previousProperties, properties));
618
885
  const portStays = properties["DBPort"] != null || properties["Port"] != null;
@@ -620,7 +887,19 @@ var NeptuneProvider = class {
620
887
  if (names.length === 0) return;
621
888
  const one = names.length === 1;
622
889
  const modify = resourceType === "AWS::Neptune::DBInstance" ? "ModifyDBInstance" : "ModifyDBCluster";
623
- 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, as CloudFormation does. Declare ${one ? "it" : "them"} to change ${one ? "it" : "them"}.`);
890
+ 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"}.`);
891
+ }
892
+ readPublicAccess(properties, resourceType, logicalId, physicalId, stateBorne) {
893
+ const refusal = configBooleanRefusal(properties, "PubliclyAccessible", logicalId);
894
+ if (refusal === void 0) return coerceCfnBoolean(properties["PubliclyAccessible"]);
895
+ if (!stateBorne) throw markNonRetryable(new ProvisioningError(safeMsg`Neptune DBInstance ${refusal}; nothing was changed.`, resourceType, logicalId, physicalId));
896
+ this.logger.warn(safeMsg`${logicalId} (${resourceType}): the recorded PubliclyAccessible is unusable, so it is not sent.`);
897
+ }
898
+ warnPublicAccessKept(logicalId, resourceType, properties, previousProperties, context) {
899
+ if (context?.desiredFromAwsReadback === true) return;
900
+ if (!(context?.removedProperties ?? new Set(removedTemplateKeys(previousProperties, properties))).has("PubliclyAccessible")) return;
901
+ if (coerceCfnBoolean(previousProperties["PubliclyAccessible"]) !== true) return;
902
+ 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.`);
624
903
  }
625
904
  async dbClusterSecurityGroupsToSend(logicalId, physicalId, resourceType, properties, previousProperties, context) {
626
905
  const desired = properties["VpcSecurityGroupIds"];
@@ -720,14 +999,14 @@ var NeptuneProvider = class {
720
999
  remainingMs: enclosingDeadlineRemainingMs()
721
1000
  });
722
1001
  }
723
- async waitForClusterAvailable(logicalId, dbClusterIdentifier, maxWaitMs = 18e5) {
1002
+ async waitForClusterAvailable(logicalId, dbClusterIdentifier, during = "being created", maxWaitMs = 18e5) {
724
1003
  const startTime = Date.now();
725
1004
  let delay = 5e3;
726
1005
  while (Date.now() - startTime < maxWaitMs) {
727
1006
  const status = (await this.describeDBCluster(dbClusterIdentifier))?.Status;
728
1007
  this.logger.debug(`Neptune DBCluster ${dbClusterIdentifier} status: ${status}`);
729
1008
  if (status === "available") return;
730
- if (isTerminalDbWaitStatus(status)) throw new Error(safeMsg`Neptune DBCluster ${logicalId} entered status ${status ?? ""} while being created, which it does not leave on its own`);
1009
+ if (isTerminalDbWaitStatus(status)) throw new Error(safeMsg`Neptune DBCluster ${logicalId} entered status ${status ?? ""} while ${during}, which it does not leave on its own`);
731
1010
  await this.sleep(delay);
732
1011
  delay = Math.min(delay * 2, 1e4);
733
1012
  }
@@ -785,6 +1064,69 @@ var NeptuneProvider = class {
785
1064
  sleep(ms) {
786
1065
  return new Promise((resolve) => setTimeout(resolve, ms));
787
1066
  }
1067
+ getDriftUnknownPaths(resourceType) {
1068
+ if (resourceType === "AWS::Neptune::DBCluster") return ["AvailabilityZones", "DBInstanceParameterGroupName"];
1069
+ if (resourceType === "AWS::Neptune::DBInstance") return ["AllowMajorVersionUpgrade"];
1070
+ return [];
1071
+ }
1072
+ getDriftUnorderedPaths(resourceType) {
1073
+ return resourceType === "AWS::Neptune::DBCluster" ? [
1074
+ "AssociatedRoles",
1075
+ "EnableCloudwatchLogsExports",
1076
+ "VpcSecurityGroupIds"
1077
+ ] : [];
1078
+ }
1079
+ canonicalizeDriftProperties(resourceType, properties) {
1080
+ if (resourceType !== "AWS::Neptune::DBCluster") return properties;
1081
+ const out = { ...properties };
1082
+ if (Array.isArray(out["AssociatedRoles"])) out["AssociatedRoles"] = clusterRoleList(out["AssociatedRoles"]) ?? out["AssociatedRoles"];
1083
+ const logs = out["EnableCloudwatchLogsExports"];
1084
+ if (Array.isArray(logs) && stringList(logs) !== void 0) out["EnableCloudwatchLogsExports"] = [...new Set(logs)];
1085
+ return out;
1086
+ }
1087
+ canonicalizeUnsettledProperties(resourceType, properties) {
1088
+ return this.canonicalizeDriftProperties(resourceType, properties);
1089
+ }
1090
+ async readUnsettled(physicalId, logicalId, resourceType, names, recordedProperties, desiredProperties, attemptedItems) {
1091
+ const live = await this.readCurrentState(physicalId, logicalId, resourceType);
1092
+ if (resourceType !== "AWS::Neptune::DBCluster") return live;
1093
+ if (live === void 0 || live === RESOURCE_NOT_FOUND) return live;
1094
+ const attempted = (key) => [...attemptedItems?.get(key) ?? []].filter(isUsableAttemptedName);
1095
+ const out = { ...live };
1096
+ if (names.has("AssociatedRoles")) {
1097
+ const named = /* @__PURE__ */ new Set([
1098
+ ...(clusterRoleList(recordedProperties?.["AssociatedRoles"]) ?? []).map(clusterRoleKey),
1099
+ ...(clusterRoleList(desiredProperties?.["AssociatedRoles"]) ?? []).map(clusterRoleKey),
1100
+ ...attempted("AssociatedRoles")
1101
+ ]);
1102
+ out["AssociatedRoles"] = (live["AssociatedRoles"] ?? []).filter((role) => named.has(clusterRoleKey(role)));
1103
+ }
1104
+ if (names.has("EnableCloudwatchLogsExports")) {
1105
+ const named = /* @__PURE__ */ new Set([
1106
+ ...stringList(recordedProperties?.["EnableCloudwatchLogsExports"]) ?? [],
1107
+ ...stringList(desiredProperties?.["EnableCloudwatchLogsExports"]) ?? [],
1108
+ ...attempted("EnableCloudwatchLogsExports")
1109
+ ]);
1110
+ out["EnableCloudwatchLogsExports"] = (live["EnableCloudwatchLogsExports"] ?? []).filter((type) => named.has(type));
1111
+ }
1112
+ return out;
1113
+ }
1114
+ async unsettledInTransition(physicalId, _logicalId, resourceType, names, _recordedProperties, desiredProperties) {
1115
+ if (desiredProperties !== void 0) return /* @__PURE__ */ new Set();
1116
+ try {
1117
+ if (resourceType === "AWS::Neptune::DBCluster") {
1118
+ const cluster = await this.describeDBCluster(physicalId);
1119
+ return isUnmovingDbStatus(cluster?.Status) && !holdsPendingValue(cluster?.PendingModifiedValues) ? /* @__PURE__ */ new Set() : names;
1120
+ }
1121
+ if (resourceType === "AWS::Neptune::DBInstance") {
1122
+ const instance = await this.describeDBInstance(physicalId);
1123
+ return isUnmovingDbStatus(instance?.DBInstanceStatus) && !holdsPendingValue(instance?.PendingModifiedValues) ? /* @__PURE__ */ new Set() : names;
1124
+ }
1125
+ return /* @__PURE__ */ new Set();
1126
+ } catch {
1127
+ return names;
1128
+ }
1129
+ }
788
1130
  async import(input) {
789
1131
  switch (input.resourceType) {
790
1132
  case "AWS::Neptune::DBInstance": return this.importDBInstance(input);
@@ -820,6 +1162,7 @@ var NeptuneProvider = class {
820
1162
  if (inst.PreferredMaintenanceWindow !== void 0) result["PreferredMaintenanceWindow"] = inst.PreferredMaintenanceWindow;
821
1163
  if (inst.AutoMinorVersionUpgrade !== void 0) result["AutoMinorVersionUpgrade"] = inst.AutoMinorVersionUpgrade;
822
1164
  if (inst.DeletionProtection !== void 0) result["DeletionProtection"] = inst.DeletionProtection;
1165
+ if (inst.PubliclyAccessible !== void 0) result["PubliclyAccessible"] = inst.PubliclyAccessible;
823
1166
  const pg = inst.DBParameterGroups?.[0]?.DBParameterGroupName;
824
1167
  if (pg !== void 0) result["DBParameterGroupName"] = pg;
825
1168
  if (inst.DBInstanceArn) await this.attachTags(result, inst.DBInstanceArn);
@@ -852,6 +1195,18 @@ var NeptuneProvider = class {
852
1195
  if (cluster.DBClusterParameterGroup !== void 0) result["DBClusterParameterGroupName"] = cluster.DBClusterParameterGroup;
853
1196
  if (cluster.IAMDatabaseAuthenticationEnabled !== void 0) result["IamAuthEnabled"] = cluster.IAMDatabaseAuthenticationEnabled;
854
1197
  if (cluster.DeletionProtection !== void 0) result["DeletionProtection"] = cluster.DeletionProtection;
1198
+ if (cluster.CopyTagsToSnapshot !== void 0) result["CopyTagsToSnapshot"] = cluster.CopyTagsToSnapshot;
1199
+ if (cluster.NetworkType !== void 0) result["NetworkType"] = cluster.NetworkType;
1200
+ const { MinCapacity: min, MaxCapacity: max } = cluster.ServerlessV2ScalingConfiguration ?? {};
1201
+ if (min !== void 0 || max !== void 0) result["ServerlessScalingConfiguration"] = {
1202
+ ...min !== void 0 && { MinCapacity: min },
1203
+ ...max !== void 0 && { MaxCapacity: max }
1204
+ };
1205
+ result["EnableCloudwatchLogsExports"] = cluster.EnabledCloudwatchLogsExports ?? [];
1206
+ result["AssociatedRoles"] = (cluster.AssociatedRoles ?? []).map((role) => ({
1207
+ RoleArn: role.RoleArn,
1208
+ ...role.FeatureName ? { FeatureName: role.FeatureName } : {}
1209
+ }));
855
1210
  if (cluster.DBClusterArn) await this.attachTags(result, cluster.DBClusterArn);
856
1211
  return result;
857
1212
  }
@@ -931,4 +1286,4 @@ function securityGroupListChanged(sent, previous) {
931
1286
 
932
1287
  //#endregion
933
1288
  export { NeptuneProvider };
934
- //# sourceMappingURL=neptune-provider-CGZ_G4Vz.js.map
1289
+ //# sourceMappingURL=neptune-provider-BNxotnbp.js.map