@go-to-k/cdkd 0.60.1 → 0.60.2

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/dist/cli.js CHANGED
@@ -22162,8 +22162,9 @@ var EC2Provider = class {
22162
22162
  async deleteInternetGateway(logicalId, physicalId, resourceType, context) {
22163
22163
  this.logger.debug(`Deleting InternetGateway ${logicalId}: ${physicalId}`);
22164
22164
  try {
22165
- await this.ec2Client.send(
22166
- new DeleteInternetGatewayCommand({ InternetGatewayId: physicalId })
22165
+ await this.withDependencyViolationRetry(
22166
+ () => this.ec2Client.send(new DeleteInternetGatewayCommand({ InternetGatewayId: physicalId })),
22167
+ { description: `DeleteInternetGateway ${logicalId} (${physicalId})` }
22167
22168
  );
22168
22169
  this.logger.debug(`Successfully deleted InternetGateway ${logicalId}`);
22169
22170
  } catch (error) {
@@ -22247,11 +22248,14 @@ var EC2Provider = class {
22247
22248
  }
22248
22249
  const [internetGatewayId, vpcId] = parts;
22249
22250
  try {
22250
- await this.ec2Client.send(
22251
- new DetachInternetGatewayCommand({
22252
- InternetGatewayId: internetGatewayId,
22253
- VpcId: vpcId
22254
- })
22251
+ await this.withDependencyViolationRetry(
22252
+ () => this.ec2Client.send(
22253
+ new DetachInternetGatewayCommand({
22254
+ InternetGatewayId: internetGatewayId,
22255
+ VpcId: vpcId
22256
+ })
22257
+ ),
22258
+ { description: `DetachInternetGateway ${logicalId} (${internetGatewayId} from ${vpcId})` }
22255
22259
  );
22256
22260
  this.logger.debug(`Successfully deleted VPCGatewayAttachment ${logicalId}`);
22257
22261
  } catch (error) {
@@ -23653,6 +23657,99 @@ var EC2Provider = class {
23653
23657
  }
23654
23658
  }
23655
23659
  }
23660
+ /**
23661
+ * Retry an operation that AWS may reject with `DependencyViolation`
23662
+ * for an extended window — specifically `DeleteInternetGateway` and
23663
+ * `DetachInternetGateway` after a sibling EC2 Instance with an
23664
+ * auto-assigned public IP was terminated. AWS releases the public-IP
23665
+ * → IGW mapping asynchronously (5–10 min lag observed in practice);
23666
+ * during that window AWS rejects IGW-detach / -delete with
23667
+ * `DependencyViolation: Network has some mapped public address(es).
23668
+ * Please unmap those public address(es) before detaching the gateway.`
23669
+ * (or similar `has dependencies and cannot be deleted`).
23670
+ *
23671
+ * cdkd's deploy-engine `withRetry` wrapper caps at ~1 min total
23672
+ * (1s/2s/4s/8s × 10 attempts capped at 8s), and the destroy-runner's
23673
+ * inner 3-attempt loop adds ~35s on top — neither is enough for
23674
+ * AWS's 5–10 min release window. This helper extends the budget to
23675
+ * 10 min for the IGW-specific case so `cdkd destroy
23676
+ * --remove-protection` is self-healing on the public-IP release lag
23677
+ * without operator intervention.
23678
+ *
23679
+ * Modeled on the Lambda hyperplane ENI cleanup pattern (~30 min
23680
+ * budget) and the EC2 Subnet/SG side-channel ENI retry — both wait
23681
+ * on AWS-side eventual-consistency that the standard `withRetry`
23682
+ * budget cannot cover.
23683
+ *
23684
+ * Only `DependencyViolation` errors are retried; other errors
23685
+ * (NotFound, AccessDenied, throttle, etc.) propagate immediately so
23686
+ * the caller's existing error handling is unchanged.
23687
+ *
23688
+ * Note on retry layering: `DependencyViolation` is also in
23689
+ * `RETRYABLE_ERROR_MESSAGE_PATTERNS` (consumed by `withRetry`) and
23690
+ * the destroy-runner's inner 3-attempt loop also matches it. After
23691
+ * this helper's budget exhausts and re-throws, those outer retry
23692
+ * loops will see the error and try a few more times — adding at
23693
+ * most ~1 min on top of the 10 min budget, still bounded by the
23694
+ * per-resource `--resource-timeout` deadline (default 30 min).
23695
+ * That extra is harmless (worst case slightly later failure surface)
23696
+ * and avoids threading a per-error-class "already exhausted" flag
23697
+ * through every retry layer.
23698
+ */
23699
+ async withDependencyViolationRetry(operation, opts) {
23700
+ const totalBudgetMs = opts.totalBudgetMs ?? 6e5;
23701
+ const initialDelayMs = 5e3;
23702
+ const maxDelayMs = 6e4;
23703
+ const startedAt = Date.now();
23704
+ let attempt = 0;
23705
+ let delay = initialDelayMs;
23706
+ while (true) {
23707
+ try {
23708
+ return await operation();
23709
+ } catch (error) {
23710
+ if (!this.isDependencyViolationError(error)) {
23711
+ throw error;
23712
+ }
23713
+ const elapsed = Date.now() - startedAt;
23714
+ if (elapsed >= totalBudgetMs) {
23715
+ throw error;
23716
+ }
23717
+ attempt += 1;
23718
+ const sleepMs = Math.min(delay, totalBudgetMs - elapsed);
23719
+ const message = error instanceof Error ? error.message : String(error);
23720
+ this.logger.debug(
23721
+ `${opts.description}: dependency still mapped (attempt ${attempt}, retrying in ${sleepMs}ms): ${message}`
23722
+ );
23723
+ await this.sleep(sleepMs);
23724
+ delay = Math.min(delay * 2, maxDelayMs);
23725
+ }
23726
+ }
23727
+ }
23728
+ /**
23729
+ * Match an AWS `DependencyViolation` error by error code or message.
23730
+ * AWS surfaces this as `Code: 'DependencyViolation'` in the parsed SDK
23731
+ * error, with a human message like `The internetGateway 'igw-xxx' has
23732
+ * dependencies and cannot be deleted.` or `Network has some mapped
23733
+ * public address(es). Please unmap those public address(es)`.
23734
+ */
23735
+ isDependencyViolationError(error) {
23736
+ if (!(error instanceof Error))
23737
+ return false;
23738
+ const code = error.Code ?? "";
23739
+ const name = error.name ?? "";
23740
+ if (code === "DependencyViolation" || name === "DependencyViolation")
23741
+ return true;
23742
+ const message = error.message ?? "";
23743
+ return message.includes("DependencyViolation") || message.includes("has dependencies and cannot be deleted") || message.includes("Network has some mapped public address");
23744
+ }
23745
+ /**
23746
+ * Indirect sleep so unit tests can swap in a fake-timer-aware
23747
+ * implementation via `vi.useFakeTimers()` without monkey-patching
23748
+ * `setTimeout` globally.
23749
+ */
23750
+ sleep(ms) {
23751
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
23752
+ }
23656
23753
  /**
23657
23754
  * Check if an error indicates the resource was not found
23658
23755
  */
@@ -49246,7 +49343,7 @@ function reorderArgs(argv) {
49246
49343
  }
49247
49344
  async function main() {
49248
49345
  const program = new Command14();
49249
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.60.1");
49346
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.60.2");
49250
49347
  program.addCommand(createBootstrapCommand());
49251
49348
  program.addCommand(createSynthCommand());
49252
49349
  program.addCommand(createListCommand());