@go-to-k/cdkd 0.26.0 → 0.28.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.
Binary file
package/dist/index.js CHANGED
@@ -921,8 +921,9 @@ var ResourceTimeoutError = class _ResourceTimeoutError extends CdkdError {
921
921
  super(
922
922
  `Resource ${logicalId} (${resourceType}) in ${region} timed out after ${timeoutLabel} during ${operation} (elapsed ${elapsedLabel}).
923
923
  This may indicate a stuck Cloud Control polling loop, hung Custom Resource, or
924
- slow ENI provisioning. Re-run with --resource-timeout 1h if the resource genuinely
925
- needs more time, or --verbose to see the underlying provider activity.`,
924
+ slow ENI provisioning. Re-run with --resource-timeout ${resourceType}=<DURATION>
925
+ to bump the budget for this resource type only, or --verbose to see the
926
+ underlying provider activity.`,
926
927
  "RESOURCE_TIMEOUT"
927
928
  );
928
929
  this.logicalId = logicalId;
@@ -6696,6 +6697,23 @@ var CustomResourceProvider = class _CustomResourceProvider {
6696
6697
  logger = getLogger().child("CustomResourceProvider");
6697
6698
  responseBucket;
6698
6699
  responsePrefix;
6700
+ /**
6701
+ * Opt out of the deploy engine's outer transient-error retry loop.
6702
+ *
6703
+ * The loop re-invokes `provider.create()` from the top on a transient
6704
+ * SDK error (IAM propagation, HTTP 429/503, etc.). Each invocation
6705
+ * generates a brand-new RequestId and a brand-new pre-signed S3
6706
+ * response URL via `prepareInvocation()`. If the underlying Lambda has
6707
+ * already started — e.g. an outer retry fired between the placeholder
6708
+ * `PutObject` and the `Invoke`, or after the `Invoke` returned but a
6709
+ * spurious downstream error fired — the first attempt's Lambda
6710
+ * response lands at an S3 key that nobody polls, hanging the deploy
6711
+ * until the polling timeout. The provider already polls with its own
6712
+ * exponential backoff for async patterns (CDK Provider framework with
6713
+ * isCompleteHandler), so an outer retry adds nothing but the multi-
6714
+ * key bug.
6715
+ */
6716
+ disableOuterRetry = true;
6699
6717
  /** Max time to wait for synchronous S3 response after Lambda invocation (30 seconds) */
6700
6718
  SYNC_RESPONSE_TIMEOUT_MS = 3e4;
6701
6719
  /** Max time to wait for async S3 response (CDK Provider framework with isCompleteHandler) */
@@ -6715,6 +6733,22 @@ var CustomResourceProvider = class _CustomResourceProvider {
6715
6733
  this.responsePrefix = config?.responsePrefix ?? "custom-resource-responses";
6716
6734
  this.asyncResponseTimeoutMs = config?.asyncResponseTimeoutMs ?? _CustomResourceProvider.DEFAULT_ASYNC_RESPONSE_TIMEOUT_MS;
6717
6735
  }
6736
+ /**
6737
+ * Self-reported minimum per-resource timeout.
6738
+ *
6739
+ * Custom Resource async invocations (CDK Provider framework with
6740
+ * `isCompleteHandler`) poll for up to `asyncResponseTimeoutMs`
6741
+ * (default 1 hour, matching CDK's `totalTimeout` default). The deploy
6742
+ * engine's global `--resource-timeout` default is 30 minutes, which
6743
+ * would abort a perfectly healthy CR mid-poll. By self-reporting the
6744
+ * polling cap, the engine lifts the deadline to `max(self-report,
6745
+ * global)` for CR resources only; a user-supplied per-type override
6746
+ * (`--resource-timeout AWS::CloudFormation::CustomResource=5m`) still
6747
+ * wins for explicit escape-hatching.
6748
+ */
6749
+ getMinResourceTimeoutMs() {
6750
+ return this.asyncResponseTimeoutMs;
6751
+ }
6718
6752
  /**
6719
6753
  * Set the S3 bucket for custom resource responses
6720
6754
  * Called by ProviderRegistry when state bucket is configured
@@ -7886,6 +7920,33 @@ var IAMRoleProvider = class {
7886
7920
  this.logger.debug(`Added/updated ${tagsToAdd.length} tags on role ${roleName}`);
7887
7921
  }
7888
7922
  }
7923
+ /**
7924
+ * Resolve a single `Fn::GetAtt` attribute for an existing IAM role.
7925
+ *
7926
+ * CloudFormation's `AWS::IAM::Role` exposes `Arn` and `RoleId`; both are
7927
+ * available from the `GetRole` response. See:
7928
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#aws-resource-iam-role-return-values
7929
+ *
7930
+ * Used by `cdkd orphan` to live-fetch attribute values that need to be
7931
+ * substituted into sibling references.
7932
+ */
7933
+ async getAttribute(physicalId, _resourceType, attributeName) {
7934
+ try {
7935
+ const resp = await this.iamClient.send(new GetRoleCommand({ RoleName: physicalId }));
7936
+ switch (attributeName) {
7937
+ case "Arn":
7938
+ return resp.Role?.Arn;
7939
+ case "RoleId":
7940
+ return resp.Role?.RoleId;
7941
+ default:
7942
+ return void 0;
7943
+ }
7944
+ } catch (err) {
7945
+ if (err instanceof NoSuchEntityException)
7946
+ return void 0;
7947
+ throw err;
7948
+ }
7949
+ }
7889
7950
  /**
7890
7951
  * Adopt an existing IAM role into cdkd state.
7891
7952
  *
@@ -8861,8 +8922,11 @@ var DeployEngine = class {
8861
8922
  const baseLabel = `${verb} ${logicalId} (${resourceType})`;
8862
8923
  renderer.addTask(logicalId, baseLabel);
8863
8924
  const operationKind = change.changeType === "CREATE" ? "CREATE" : change.changeType === "DELETE" ? "DELETE" : "UPDATE";
8925
+ const provider = this.providerRegistry.getProvider(resourceType);
8926
+ const providerMinTimeoutMs = provider.getMinResourceTimeoutMs?.() ?? 0;
8864
8927
  const warnAfterMs = this.options.resourceWarnAfterByType?.[resourceType] ?? this.options.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;
8865
- const timeoutMs = this.options.resourceTimeoutByType?.[resourceType] ?? this.options.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
8928
+ const globalTimeoutMs = this.options.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
8929
+ const timeoutMs = this.options.resourceTimeoutByType?.[resourceType] ?? Math.max(providerMinTimeoutMs, globalTimeoutMs);
8866
8930
  try {
8867
8931
  await withResourceDeadline(
8868
8932
  async () => {
@@ -8941,7 +9005,10 @@ var DeployEngine = class {
8941
9005
  const { provider: createProvider, properties: createProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
8942
9006
  const result = await this.withRetry(
8943
9007
  () => createProvider.create(logicalId, resourceType, createProps),
8944
- logicalId
9008
+ logicalId,
9009
+ void 0,
9010
+ void 0,
9011
+ provider
8945
9012
  );
8946
9013
  const dependencies = this.extractAllDependencies(template, logicalId);
8947
9014
  stateResources[logicalId] = {
@@ -8995,7 +9062,10 @@ var DeployEngine = class {
8995
9062
  const { provider: replaceProvider, properties: replaceProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
8996
9063
  const createResult = await this.withRetry(
8997
9064
  () => replaceProvider.create(logicalId, resourceType, replaceProps),
8998
- logicalId
9065
+ logicalId,
9066
+ void 0,
9067
+ void 0,
9068
+ provider
8999
9069
  );
9000
9070
  const updateReplacePolicy = template?.Resources?.[logicalId]?.UpdateReplacePolicy;
9001
9071
  if (updateReplacePolicy === "Retain") {
@@ -9046,7 +9116,10 @@ var DeployEngine = class {
9046
9116
  updateProps,
9047
9117
  currentProps
9048
9118
  ),
9049
- logicalId
9119
+ logicalId,
9120
+ void 0,
9121
+ void 0,
9122
+ provider
9050
9123
  );
9051
9124
  } catch (updateError) {
9052
9125
  const msg = updateError instanceof Error ? updateError.message : String(updateError);
@@ -9075,7 +9148,10 @@ var DeployEngine = class {
9075
9148
  const { provider: replProvider, properties: replProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
9076
9149
  const createResult = await this.withRetry(
9077
9150
  () => replProvider.create(logicalId, resourceType, replProps),
9078
- logicalId
9151
+ logicalId,
9152
+ void 0,
9153
+ void 0,
9154
+ provider
9079
9155
  );
9080
9156
  result = {
9081
9157
  physicalId: createResult.physicalId,
@@ -9132,7 +9208,8 @@ var DeployEngine = class {
9132
9208
  logicalId,
9133
9209
  3,
9134
9210
  // fewer retries for DELETE
9135
- 5e3
9211
+ 5e3,
9212
+ provider
9136
9213
  );
9137
9214
  } catch (deleteError) {
9138
9215
  const msg = deleteError instanceof Error ? deleteError.message : String(deleteError);
@@ -9309,8 +9386,18 @@ var DeployEngine = class {
9309
9386
  * Thin wrapper over `withRetry` from ./retry.js that injects this engine's
9310
9387
  * SIGINT-aware interrupt check and logger. The actual backoff schedule
9311
9388
  * lives there.
9389
+ *
9390
+ * When the provider opts out via `disableOuterRetry`, the operation is
9391
+ * invoked exactly once and the retry loop is skipped entirely. The
9392
+ * Custom Resource provider uses this to avoid re-running its `create()`
9393
+ * — each invocation derives a fresh pre-signed S3 URL and RequestId,
9394
+ * so an outer retry leaves the previous attempt's Lambda response
9395
+ * stranded at an S3 key nobody polls.
9312
9396
  */
9313
- async withRetry(operation, logicalId, maxRetries, initialDelayMs) {
9397
+ async withRetry(operation, logicalId, maxRetries, initialDelayMs, provider) {
9398
+ if (provider?.disableOuterRetry) {
9399
+ return operation();
9400
+ }
9314
9401
  return withRetry(operation, logicalId, {
9315
9402
  ...maxRetries !== void 0 && { maxRetries },
9316
9403
  ...initialDelayMs !== void 0 && { initialDelayMs },