@go-to-k/cdkd 0.267.2 → 0.267.4
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/{asg-provider-zMwVyLi6.js → asg-provider-C8yQ0Br2.js} +2 -2
- package/dist/{asg-provider-zMwVyLi6.js.map → asg-provider-C8yQ0Br2.js.map} +1 -1
- package/dist/cli.js +9 -5
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-DTNj0ag6.js → deploy-engine-DYrhAO-0.js} +266 -220
- package/dist/deploy-engine-DYrhAO-0.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-DTNj0ag6.js.map +0 -1
|
@@ -7486,6 +7486,266 @@ var ReplacementRulesRegistry = class {
|
|
|
7486
7486
|
}
|
|
7487
7487
|
};
|
|
7488
7488
|
|
|
7489
|
+
//#endregion
|
|
7490
|
+
//#region src/deployment/retryable-errors.ts
|
|
7491
|
+
/**
|
|
7492
|
+
* Patterns that mark an AWS error as a transient/retryable failure.
|
|
7493
|
+
* Each entry is a substring match against the error message; all of these
|
|
7494
|
+
* are situations where the same call typically succeeds after a short delay
|
|
7495
|
+
* because of eventual consistency or just-created-dependency propagation.
|
|
7496
|
+
*/
|
|
7497
|
+
const RETRYABLE_ERROR_MESSAGE_PATTERNS = [
|
|
7498
|
+
"cannot be assumed",
|
|
7499
|
+
"Firehose is unable to assume role",
|
|
7500
|
+
"is unable to assume provided role",
|
|
7501
|
+
"role defined for the function",
|
|
7502
|
+
"not authorized to perform",
|
|
7503
|
+
"execution role",
|
|
7504
|
+
"trust policy",
|
|
7505
|
+
"Role validation failed",
|
|
7506
|
+
"does not have required permissions",
|
|
7507
|
+
"Trusted Entity",
|
|
7508
|
+
"currently in the following state: Pending",
|
|
7509
|
+
"has dependencies and cannot be deleted",
|
|
7510
|
+
"can't be deleted since it has",
|
|
7511
|
+
"DependencyViolation",
|
|
7512
|
+
"does not exist",
|
|
7513
|
+
"Schema is currently being altered",
|
|
7514
|
+
"Invalid principal in policy",
|
|
7515
|
+
"Policy Error: PrincipalNotFound",
|
|
7516
|
+
"Invalid value for the parameter Policy",
|
|
7517
|
+
"required permissions for: ENHANCED_MONITORING",
|
|
7518
|
+
"Caught ServiceAccessDeniedException",
|
|
7519
|
+
"permissions required to assume the role",
|
|
7520
|
+
"authorized to assume the provided role",
|
|
7521
|
+
"conflicting conditional operation",
|
|
7522
|
+
"scheduled for deletion",
|
|
7523
|
+
"Cannot access stream",
|
|
7524
|
+
"Please ensure the role can perform",
|
|
7525
|
+
"KMS key is invalid for CreateGrant",
|
|
7526
|
+
"Policy contains a statement with one or more invalid principals",
|
|
7527
|
+
"Invalid IAM Instance Profile",
|
|
7528
|
+
"Invalid InstanceProfile",
|
|
7529
|
+
"Failed to authorize instance profile",
|
|
7530
|
+
"Could not deliver test message",
|
|
7531
|
+
"wait 60 seconds",
|
|
7532
|
+
"concurrent update operation",
|
|
7533
|
+
"because it is in use",
|
|
7534
|
+
"Rate exceeded"
|
|
7535
|
+
];
|
|
7536
|
+
/**
|
|
7537
|
+
* HTTP status codes that always indicate a transient failure worth retrying.
|
|
7538
|
+
* 429 = Too Many Requests (throttle), 503 = Service Unavailable.
|
|
7539
|
+
*/
|
|
7540
|
+
const RETRYABLE_HTTP_STATUS_CODES = /* @__PURE__ */ new Set([429, 503]);
|
|
7541
|
+
/**
|
|
7542
|
+
* AWS SDK v3 canonical throttling error names. Mirrors
|
|
7543
|
+
* `@aws-sdk/service-error-classification`'s `THROTTLING_ERROR_CODES` — any
|
|
7544
|
+
* error (or wrapped cause) whose `name` is one of these is a transient rate-
|
|
7545
|
+
* limit rejection worth retrying with backoff. Detecting by NAME is more
|
|
7546
|
+
* robust than by HTTP status because most AWS throttles surface as HTTP 400
|
|
7547
|
+
* (not 429) with the throttling signal carried only in the error code / name
|
|
7548
|
+
* (e.g. SSM `ThrottlingException` for the `Rate exceeded` message).
|
|
7549
|
+
*/
|
|
7550
|
+
const THROTTLING_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
7551
|
+
"BandwidthLimitExceeded",
|
|
7552
|
+
"EC2ThrottledException",
|
|
7553
|
+
"LimitExceededException",
|
|
7554
|
+
"PriorRequestNotComplete",
|
|
7555
|
+
"ProvisionedThroughputExceededException",
|
|
7556
|
+
"RequestLimitExceeded",
|
|
7557
|
+
"RequestThrottled",
|
|
7558
|
+
"RequestThrottledException",
|
|
7559
|
+
"SlowDown",
|
|
7560
|
+
"ThrottledException",
|
|
7561
|
+
"Throttling",
|
|
7562
|
+
"ThrottlingException",
|
|
7563
|
+
"TooManyRequestsException",
|
|
7564
|
+
"TransactionInProgressException"
|
|
7565
|
+
]);
|
|
7566
|
+
/**
|
|
7567
|
+
* Walk the error + its `.cause` chain (bounded) looking for a rate-limit
|
|
7568
|
+
* signal — either an AWS SDK v3 throttling error `name`
|
|
7569
|
+
* ({@link THROTTLING_ERROR_NAMES}) or a retryable HTTP status
|
|
7570
|
+
* ({@link RETRYABLE_HTTP_STATUS_CODES}) on `$metadata`.
|
|
7571
|
+
*
|
|
7572
|
+
* cdkd wraps the original AWS error in a `ProvisioningError`, so the signal is
|
|
7573
|
+
* typically one cause-link deep; the bounded walk also tolerates SDK errors
|
|
7574
|
+
* that nest a `$response`/cause without exploding on a cyclic chain.
|
|
7575
|
+
*
|
|
7576
|
+
* BOTH signals are checked at EVERY depth. An earlier version checked the name
|
|
7577
|
+
* to depth 5 but the HTTP status only at depths 0 and 1, so a 429 nested two
|
|
7578
|
+
* links deep was missed.
|
|
7579
|
+
*/
|
|
7580
|
+
function isThrottlingError(error) {
|
|
7581
|
+
let current = error;
|
|
7582
|
+
for (let depth = 0; depth < 5 && current != null; depth++) {
|
|
7583
|
+
const name = current.name;
|
|
7584
|
+
if (typeof name === "string" && THROTTLING_ERROR_NAMES.has(name)) return true;
|
|
7585
|
+
const status = current.$metadata?.httpStatusCode;
|
|
7586
|
+
if (status !== void 0 && RETRYABLE_HTTP_STATUS_CODES.has(status)) return true;
|
|
7587
|
+
current = current.cause;
|
|
7588
|
+
}
|
|
7589
|
+
return false;
|
|
7590
|
+
}
|
|
7591
|
+
/**
|
|
7592
|
+
* Determine whether an AWS error should be retried.
|
|
7593
|
+
*
|
|
7594
|
+
* Checks (in order):
|
|
7595
|
+
* 1. Rate-limit signal on the error or any wrapped cause — throttling error
|
|
7596
|
+
* `name` or retryable HTTP status (most AWS throttles are HTTP 400, not
|
|
7597
|
+
* 429, so the name check carries most of the weight). See
|
|
7598
|
+
* {@link isThrottlingError}.
|
|
7599
|
+
* 2. Substring match against {@link RETRYABLE_ERROR_MESSAGE_PATTERNS}
|
|
7600
|
+
*/
|
|
7601
|
+
function isRetryableTransientError(error, message) {
|
|
7602
|
+
if (isThrottlingError(error)) return true;
|
|
7603
|
+
return RETRYABLE_ERROR_MESSAGE_PATTERNS.some((p) => message.includes(p));
|
|
7604
|
+
}
|
|
7605
|
+
/**
|
|
7606
|
+
* Match the "already exists" name-collision signature raised when a create
|
|
7607
|
+
* targets a physical name still held by another resource (or by the same
|
|
7608
|
+
* name's not-yet-released tombstone after an async delete).
|
|
7609
|
+
*
|
|
7610
|
+
* Deliberately NOT part of {@link RETRYABLE_ERROR_MESSAGE_PATTERNS}: a name
|
|
7611
|
+
* collision is only worth retrying at the specific re-create sites that just
|
|
7612
|
+
* deleted the old holder (the deploy engine's --replace delete-first fallback
|
|
7613
|
+
* and the rollback executor's reverse-replacement) — everywhere else it is a
|
|
7614
|
+
* genuine conflict that must fail fast. Shared by those sites' collision
|
|
7615
|
+
* detection + retry filters so a signature extension lands in one place.
|
|
7616
|
+
*/
|
|
7617
|
+
function isNameCollisionError(message) {
|
|
7618
|
+
return /already exists/i.test(message) || message.includes("AlreadyExists");
|
|
7619
|
+
}
|
|
7620
|
+
/**
|
|
7621
|
+
* Match the SQS same-name re-creation cooldown: after `DeleteQueue`, creating
|
|
7622
|
+
* a queue with the SAME name inside ~60s fails with
|
|
7623
|
+
* `AWS.SimpleQueueService.QueueDeletedRecently` ("You must wait 60 seconds
|
|
7624
|
+
* after deleting a queue before you can create another with the same name").
|
|
7625
|
+
*
|
|
7626
|
+
* The generic transient table above already carries 'wait 60 seconds' for
|
|
7627
|
+
* plain CREATEs (rapid destroy → redeploy loops), but the delete-then-re-create
|
|
7628
|
+
* sites (the deploy engine's --replace delete-first fallback and the rollback
|
|
7629
|
+
* executor's reverse-replacement) override the retry filter with
|
|
7630
|
+
* {@link isNameCollisionError}, which this signature does NOT match — so a
|
|
7631
|
+
* replacement revert used to fail fast mid-flight with the resource absent
|
|
7632
|
+
* from both AWS and state (issue #1206). Those sites now OR this matcher into
|
|
7633
|
+
* their retry filter, with a schedule long enough to cover the 60s window.
|
|
7634
|
+
*
|
|
7635
|
+
* Kept separate from {@link isNameCollisionError} on purpose: a cooldown at a
|
|
7636
|
+
* create-first site must NOT be treated as a collision (deleting the new
|
|
7637
|
+
* resource would not release the cooldown on the old name).
|
|
7638
|
+
*/
|
|
7639
|
+
function isNameCooldownError(message) {
|
|
7640
|
+
return message.includes("QueueDeletedRecently") || message.includes("wait 60 seconds");
|
|
7641
|
+
}
|
|
7642
|
+
/**
|
|
7643
|
+
* Retry filter for the delete-then-re-create sites: the old name holder was
|
|
7644
|
+
* just deleted, so both the late name release ("already exists" from an async
|
|
7645
|
+
* delete) and the SQS 60s name cooldown are worth waiting out. Pair with a
|
|
7646
|
+
* schedule that covers the full cooldown window (maxRetries 8, delays
|
|
7647
|
+
* 2s/4s/8s then capped at 10s ≈ 64s total sleep).
|
|
7648
|
+
*/
|
|
7649
|
+
function isRecreateRetryableError(message) {
|
|
7650
|
+
return isNameCollisionError(message) || isNameCooldownError(message);
|
|
7651
|
+
}
|
|
7652
|
+
|
|
7653
|
+
//#endregion
|
|
7654
|
+
//#region src/deployment/retry.ts
|
|
7655
|
+
/**
|
|
7656
|
+
* Retry helper for resource provisioning operations that hit transient
|
|
7657
|
+
* AWS eventual-consistency errors (IAM propagation, Lambda Pending state,
|
|
7658
|
+
* dependency violations, etc.).
|
|
7659
|
+
*
|
|
7660
|
+
* Extracted from DeployEngine so the backoff schedule can be unit-tested
|
|
7661
|
+
* in isolation. The retryable-error classifier itself lives in
|
|
7662
|
+
* `./retryable-errors.ts`.
|
|
7663
|
+
*/
|
|
7664
|
+
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
7665
|
+
/**
|
|
7666
|
+
* Run `operation`, retrying transient failures with exponential backoff
|
|
7667
|
+
* capped at `maxDelayMs`.
|
|
7668
|
+
*
|
|
7669
|
+
* Backoff at the defaults (initialDelayMs=1_000, maxDelayMs=8_000, maxRetries=8):
|
|
7670
|
+
* 1s -> 2s -> 4s -> 8s -> 8s -> 8s -> 8s -> 8s (cumulative 47s)
|
|
7671
|
+
*
|
|
7672
|
+
* Non-retryable errors are rethrown immediately. The transient-error
|
|
7673
|
+
* classifier is `isRetryableTransientError` from ./retryable-errors.ts.
|
|
7674
|
+
*/
|
|
7675
|
+
async function withRetry(operation, logicalId, opts = {}) {
|
|
7676
|
+
const maxRetries = opts.maxRetries ?? 8;
|
|
7677
|
+
const initialDelayMs = opts.initialDelayMs ?? 1e3;
|
|
7678
|
+
const maxDelayMs = opts.maxDelayMs ?? 8e3;
|
|
7679
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
7680
|
+
let lastError;
|
|
7681
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
7682
|
+
return await operation();
|
|
7683
|
+
} catch (error) {
|
|
7684
|
+
lastError = error;
|
|
7685
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7686
|
+
if (!(opts.isRetryable ? opts.isRetryable(message, error) : isRetryableTransientError(error, message)) || attempt >= maxRetries) throw error;
|
|
7687
|
+
const delay = Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs);
|
|
7688
|
+
opts.logger?.debug(` ⏳ Retrying ${logicalId} in ${delay / 1e3}s (attempt ${attempt + 1}/${maxRetries}) - ${message}`);
|
|
7689
|
+
for (let waited = 0; waited < delay; waited += 1e3) {
|
|
7690
|
+
if (opts.isInterrupted?.()) throw opts.onInterrupted ? opts.onInterrupted() : /* @__PURE__ */ new Error("Interrupted");
|
|
7691
|
+
await sleep(Math.min(1e3, delay - waited));
|
|
7692
|
+
}
|
|
7693
|
+
}
|
|
7694
|
+
throw lastError;
|
|
7695
|
+
}
|
|
7696
|
+
|
|
7697
|
+
//#endregion
|
|
7698
|
+
//#region src/provisioning/describe-type.ts
|
|
7699
|
+
/**
|
|
7700
|
+
* Shared `cloudformation:DescribeType` invocation with throttle-only retry
|
|
7701
|
+
* (issue #1236).
|
|
7702
|
+
*
|
|
7703
|
+
* DescribeType is throttled per-account, and a deploy can issue a burst of
|
|
7704
|
+
* them (the #1182 create-only schema prefetch describes every type in the
|
|
7705
|
+
* template at deploy start). A lookup issued moments later — the write-only
|
|
7706
|
+
* property resolution during a CC-routed UPDATE is the critical case — can
|
|
7707
|
+
* then be throttled past the SDK's own short retry, and the caller's graceful
|
|
7708
|
+
* fallback turns the transient throttle into a real failure: the minimal
|
|
7709
|
+
* update patch drops a load-bearing write-only property and the update
|
|
7710
|
+
* hard-fails (`AWS::ECS::Service.VolumeConfigurations`), or a replacement is
|
|
7711
|
+
* mis-classified from a missing create-only schema.
|
|
7712
|
+
*
|
|
7713
|
+
* The fix is to retry ONLY throttle-shaped failures ({@link isThrottlingError})
|
|
7714
|
+
* with the standard backoff before surfacing the error. Non-throttle failures
|
|
7715
|
+
* (a missing `cloudformation:DescribeType` permission being the important one)
|
|
7716
|
+
* are rethrown immediately so the callers' warn-and-fall-back path stays as
|
|
7717
|
+
* fast as before — a caller permanently without the permission must not pay a
|
|
7718
|
+
* retry sleep on every lookup.
|
|
7719
|
+
*/
|
|
7720
|
+
/**
|
|
7721
|
+
* Test seam: overriding `sleep` lets unit tests drive the backoff schedule
|
|
7722
|
+
* without real waits (mirrors macro-expander's `retryDelays`).
|
|
7723
|
+
*/
|
|
7724
|
+
const describeTypeRetryDelays = {};
|
|
7725
|
+
/**
|
|
7726
|
+
* Retries after the first attempt, throttle-shaped failures only. At the
|
|
7727
|
+
* default backoff (1s -> 2s -> 4s -> 8s) this adds at most ~15s of sleep —
|
|
7728
|
+
* enough to ride out a prefetch-burst throttle window, small next to the
|
|
7729
|
+
* failed-update + rollback cycle it prevents.
|
|
7730
|
+
*/
|
|
7731
|
+
const MAX_THROTTLE_RETRIES = 4;
|
|
7732
|
+
/**
|
|
7733
|
+
* Issue `DescribeType` for a resource type, retrying throttle-shaped failures
|
|
7734
|
+
* with exponential backoff. Any other failure (or a throttle persisting past
|
|
7735
|
+
* the retry budget) is thrown to the caller unchanged.
|
|
7736
|
+
*/
|
|
7737
|
+
function describeTypeWithThrottleRetry(resourceType) {
|
|
7738
|
+
return withRetry(() => getAwsClients().cloudFormation.send(new DescribeTypeCommand({
|
|
7739
|
+
Type: "RESOURCE",
|
|
7740
|
+
TypeName: resourceType
|
|
7741
|
+
})), resourceType, {
|
|
7742
|
+
maxRetries: MAX_THROTTLE_RETRIES,
|
|
7743
|
+
isRetryable: (_message, error) => isThrottlingError(error),
|
|
7744
|
+
logger: getLogger().child("DescribeType"),
|
|
7745
|
+
...describeTypeRetryDelays.sleep ? { sleep: describeTypeRetryDelays.sleep } : {}
|
|
7746
|
+
});
|
|
7747
|
+
}
|
|
7748
|
+
|
|
7489
7749
|
//#endregion
|
|
7490
7750
|
//#region src/provisioning/create-only-properties.ts
|
|
7491
7751
|
/**
|
|
@@ -7642,10 +7902,7 @@ function isIntrinsicShaped(value) {
|
|
|
7642
7902
|
*/
|
|
7643
7903
|
async function fetchCreateOnlyPropertyPaths(resourceType) {
|
|
7644
7904
|
const logger = getLogger().child("CreateOnlyProperties");
|
|
7645
|
-
const response = await
|
|
7646
|
-
Type: "RESOURCE",
|
|
7647
|
-
TypeName: resourceType
|
|
7648
|
-
}));
|
|
7905
|
+
const response = await describeTypeWithThrottleRetry(resourceType);
|
|
7649
7906
|
const result = [];
|
|
7650
7907
|
if (response.Schema) {
|
|
7651
7908
|
const createOnly = JSON.parse(response.Schema).createOnlyProperties;
|
|
@@ -10826,10 +11083,7 @@ function getTopLevelWriteOnlyProperties(resourceType) {
|
|
|
10826
11083
|
*/
|
|
10827
11084
|
async function fetchTopLevelWriteOnlyProperties(resourceType) {
|
|
10828
11085
|
const logger = getLogger().child("WriteOnlyProperties");
|
|
10829
|
-
const response = await
|
|
10830
|
-
Type: "RESOURCE",
|
|
10831
|
-
TypeName: resourceType
|
|
10832
|
-
}));
|
|
11086
|
+
const response = await describeTypeWithThrottleRetry(resourceType);
|
|
10833
11087
|
const result = /* @__PURE__ */ new Set();
|
|
10834
11088
|
if (response.Schema) {
|
|
10835
11089
|
const writeOnly = JSON.parse(response.Schema).writeOnlyProperties;
|
|
@@ -11354,7 +11608,7 @@ var CloudControlProvider = class {
|
|
|
11354
11608
|
this.logger.debug(`Deleting resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`);
|
|
11355
11609
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
11356
11610
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
11357
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
11611
|
+
const { ASGProvider } = await import("./asg-provider-C8yQ0Br2.js").then((n) => n.n);
|
|
11358
11612
|
await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
11359
11613
|
return;
|
|
11360
11614
|
}
|
|
@@ -16401,214 +16655,6 @@ function computeImplicitDeleteEdges(resources) {
|
|
|
16401
16655
|
return edges;
|
|
16402
16656
|
}
|
|
16403
16657
|
|
|
16404
|
-
//#endregion
|
|
16405
|
-
//#region src/deployment/retryable-errors.ts
|
|
16406
|
-
/**
|
|
16407
|
-
* Patterns that mark an AWS error as a transient/retryable failure.
|
|
16408
|
-
* Each entry is a substring match against the error message; all of these
|
|
16409
|
-
* are situations where the same call typically succeeds after a short delay
|
|
16410
|
-
* because of eventual consistency or just-created-dependency propagation.
|
|
16411
|
-
*/
|
|
16412
|
-
const RETRYABLE_ERROR_MESSAGE_PATTERNS = [
|
|
16413
|
-
"cannot be assumed",
|
|
16414
|
-
"Firehose is unable to assume role",
|
|
16415
|
-
"is unable to assume provided role",
|
|
16416
|
-
"role defined for the function",
|
|
16417
|
-
"not authorized to perform",
|
|
16418
|
-
"execution role",
|
|
16419
|
-
"trust policy",
|
|
16420
|
-
"Role validation failed",
|
|
16421
|
-
"does not have required permissions",
|
|
16422
|
-
"Trusted Entity",
|
|
16423
|
-
"currently in the following state: Pending",
|
|
16424
|
-
"has dependencies and cannot be deleted",
|
|
16425
|
-
"can't be deleted since it has",
|
|
16426
|
-
"DependencyViolation",
|
|
16427
|
-
"does not exist",
|
|
16428
|
-
"Schema is currently being altered",
|
|
16429
|
-
"Invalid principal in policy",
|
|
16430
|
-
"Policy Error: PrincipalNotFound",
|
|
16431
|
-
"Invalid value for the parameter Policy",
|
|
16432
|
-
"required permissions for: ENHANCED_MONITORING",
|
|
16433
|
-
"Caught ServiceAccessDeniedException",
|
|
16434
|
-
"permissions required to assume the role",
|
|
16435
|
-
"authorized to assume the provided role",
|
|
16436
|
-
"conflicting conditional operation",
|
|
16437
|
-
"scheduled for deletion",
|
|
16438
|
-
"Cannot access stream",
|
|
16439
|
-
"Please ensure the role can perform",
|
|
16440
|
-
"KMS key is invalid for CreateGrant",
|
|
16441
|
-
"Policy contains a statement with one or more invalid principals",
|
|
16442
|
-
"Invalid IAM Instance Profile",
|
|
16443
|
-
"Invalid InstanceProfile",
|
|
16444
|
-
"Failed to authorize instance profile",
|
|
16445
|
-
"Could not deliver test message",
|
|
16446
|
-
"wait 60 seconds",
|
|
16447
|
-
"concurrent update operation",
|
|
16448
|
-
"because it is in use",
|
|
16449
|
-
"Rate exceeded"
|
|
16450
|
-
];
|
|
16451
|
-
/**
|
|
16452
|
-
* HTTP status codes that always indicate a transient failure worth retrying.
|
|
16453
|
-
* 429 = Too Many Requests (throttle), 503 = Service Unavailable.
|
|
16454
|
-
*/
|
|
16455
|
-
const RETRYABLE_HTTP_STATUS_CODES = /* @__PURE__ */ new Set([429, 503]);
|
|
16456
|
-
/**
|
|
16457
|
-
* AWS SDK v3 canonical throttling error names. Mirrors
|
|
16458
|
-
* `@aws-sdk/service-error-classification`'s `THROTTLING_ERROR_CODES` — any
|
|
16459
|
-
* error (or wrapped cause) whose `name` is one of these is a transient rate-
|
|
16460
|
-
* limit rejection worth retrying with backoff. Detecting by NAME is more
|
|
16461
|
-
* robust than by HTTP status because most AWS throttles surface as HTTP 400
|
|
16462
|
-
* (not 429) with the throttling signal carried only in the error code / name
|
|
16463
|
-
* (e.g. SSM `ThrottlingException` for the `Rate exceeded` message).
|
|
16464
|
-
*/
|
|
16465
|
-
const THROTTLING_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
16466
|
-
"BandwidthLimitExceeded",
|
|
16467
|
-
"EC2ThrottledException",
|
|
16468
|
-
"LimitExceededException",
|
|
16469
|
-
"PriorRequestNotComplete",
|
|
16470
|
-
"ProvisionedThroughputExceededException",
|
|
16471
|
-
"RequestLimitExceeded",
|
|
16472
|
-
"RequestThrottled",
|
|
16473
|
-
"RequestThrottledException",
|
|
16474
|
-
"SlowDown",
|
|
16475
|
-
"ThrottledException",
|
|
16476
|
-
"Throttling",
|
|
16477
|
-
"ThrottlingException",
|
|
16478
|
-
"TooManyRequestsException",
|
|
16479
|
-
"TransactionInProgressException"
|
|
16480
|
-
]);
|
|
16481
|
-
/**
|
|
16482
|
-
* Walk the error + its `.cause` chain (bounded) looking for a rate-limit
|
|
16483
|
-
* signal — either an AWS SDK v3 throttling error `name`
|
|
16484
|
-
* ({@link THROTTLING_ERROR_NAMES}) or a retryable HTTP status
|
|
16485
|
-
* ({@link RETRYABLE_HTTP_STATUS_CODES}) on `$metadata`.
|
|
16486
|
-
*
|
|
16487
|
-
* cdkd wraps the original AWS error in a `ProvisioningError`, so the signal is
|
|
16488
|
-
* typically one cause-link deep; the bounded walk also tolerates SDK errors
|
|
16489
|
-
* that nest a `$response`/cause without exploding on a cyclic chain.
|
|
16490
|
-
*
|
|
16491
|
-
* BOTH signals are checked at EVERY depth. An earlier version checked the name
|
|
16492
|
-
* to depth 5 but the HTTP status only at depths 0 and 1, so a 429 nested two
|
|
16493
|
-
* links deep was missed.
|
|
16494
|
-
*/
|
|
16495
|
-
function isThrottlingError(error) {
|
|
16496
|
-
let current = error;
|
|
16497
|
-
for (let depth = 0; depth < 5 && current != null; depth++) {
|
|
16498
|
-
const name = current.name;
|
|
16499
|
-
if (typeof name === "string" && THROTTLING_ERROR_NAMES.has(name)) return true;
|
|
16500
|
-
const status = current.$metadata?.httpStatusCode;
|
|
16501
|
-
if (status !== void 0 && RETRYABLE_HTTP_STATUS_CODES.has(status)) return true;
|
|
16502
|
-
current = current.cause;
|
|
16503
|
-
}
|
|
16504
|
-
return false;
|
|
16505
|
-
}
|
|
16506
|
-
/**
|
|
16507
|
-
* Determine whether an AWS error should be retried.
|
|
16508
|
-
*
|
|
16509
|
-
* Checks (in order):
|
|
16510
|
-
* 1. Rate-limit signal on the error or any wrapped cause — throttling error
|
|
16511
|
-
* `name` or retryable HTTP status (most AWS throttles are HTTP 400, not
|
|
16512
|
-
* 429, so the name check carries most of the weight). See
|
|
16513
|
-
* {@link isThrottlingError}.
|
|
16514
|
-
* 2. Substring match against {@link RETRYABLE_ERROR_MESSAGE_PATTERNS}
|
|
16515
|
-
*/
|
|
16516
|
-
function isRetryableTransientError(error, message) {
|
|
16517
|
-
if (isThrottlingError(error)) return true;
|
|
16518
|
-
return RETRYABLE_ERROR_MESSAGE_PATTERNS.some((p) => message.includes(p));
|
|
16519
|
-
}
|
|
16520
|
-
/**
|
|
16521
|
-
* Match the "already exists" name-collision signature raised when a create
|
|
16522
|
-
* targets a physical name still held by another resource (or by the same
|
|
16523
|
-
* name's not-yet-released tombstone after an async delete).
|
|
16524
|
-
*
|
|
16525
|
-
* Deliberately NOT part of {@link RETRYABLE_ERROR_MESSAGE_PATTERNS}: a name
|
|
16526
|
-
* collision is only worth retrying at the specific re-create sites that just
|
|
16527
|
-
* deleted the old holder (the deploy engine's --replace delete-first fallback
|
|
16528
|
-
* and the rollback executor's reverse-replacement) — everywhere else it is a
|
|
16529
|
-
* genuine conflict that must fail fast. Shared by those sites' collision
|
|
16530
|
-
* detection + retry filters so a signature extension lands in one place.
|
|
16531
|
-
*/
|
|
16532
|
-
function isNameCollisionError(message) {
|
|
16533
|
-
return /already exists/i.test(message) || message.includes("AlreadyExists");
|
|
16534
|
-
}
|
|
16535
|
-
/**
|
|
16536
|
-
* Match the SQS same-name re-creation cooldown: after `DeleteQueue`, creating
|
|
16537
|
-
* a queue with the SAME name inside ~60s fails with
|
|
16538
|
-
* `AWS.SimpleQueueService.QueueDeletedRecently` ("You must wait 60 seconds
|
|
16539
|
-
* after deleting a queue before you can create another with the same name").
|
|
16540
|
-
*
|
|
16541
|
-
* The generic transient table above already carries 'wait 60 seconds' for
|
|
16542
|
-
* plain CREATEs (rapid destroy → redeploy loops), but the delete-then-re-create
|
|
16543
|
-
* sites (the deploy engine's --replace delete-first fallback and the rollback
|
|
16544
|
-
* executor's reverse-replacement) override the retry filter with
|
|
16545
|
-
* {@link isNameCollisionError}, which this signature does NOT match — so a
|
|
16546
|
-
* replacement revert used to fail fast mid-flight with the resource absent
|
|
16547
|
-
* from both AWS and state (issue #1206). Those sites now OR this matcher into
|
|
16548
|
-
* their retry filter, with a schedule long enough to cover the 60s window.
|
|
16549
|
-
*
|
|
16550
|
-
* Kept separate from {@link isNameCollisionError} on purpose: a cooldown at a
|
|
16551
|
-
* create-first site must NOT be treated as a collision (deleting the new
|
|
16552
|
-
* resource would not release the cooldown on the old name).
|
|
16553
|
-
*/
|
|
16554
|
-
function isNameCooldownError(message) {
|
|
16555
|
-
return message.includes("QueueDeletedRecently") || message.includes("wait 60 seconds");
|
|
16556
|
-
}
|
|
16557
|
-
/**
|
|
16558
|
-
* Retry filter for the delete-then-re-create sites: the old name holder was
|
|
16559
|
-
* just deleted, so both the late name release ("already exists" from an async
|
|
16560
|
-
* delete) and the SQS 60s name cooldown are worth waiting out. Pair with a
|
|
16561
|
-
* schedule that covers the full cooldown window (maxRetries 8, delays
|
|
16562
|
-
* 2s/4s/8s then capped at 10s ≈ 64s total sleep).
|
|
16563
|
-
*/
|
|
16564
|
-
function isRecreateRetryableError(message) {
|
|
16565
|
-
return isNameCollisionError(message) || isNameCooldownError(message);
|
|
16566
|
-
}
|
|
16567
|
-
|
|
16568
|
-
//#endregion
|
|
16569
|
-
//#region src/deployment/retry.ts
|
|
16570
|
-
/**
|
|
16571
|
-
* Retry helper for resource provisioning operations that hit transient
|
|
16572
|
-
* AWS eventual-consistency errors (IAM propagation, Lambda Pending state,
|
|
16573
|
-
* dependency violations, etc.).
|
|
16574
|
-
*
|
|
16575
|
-
* Extracted from DeployEngine so the backoff schedule can be unit-tested
|
|
16576
|
-
* in isolation. The retryable-error classifier itself lives in
|
|
16577
|
-
* `./retryable-errors.ts`.
|
|
16578
|
-
*/
|
|
16579
|
-
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
16580
|
-
/**
|
|
16581
|
-
* Run `operation`, retrying transient failures with exponential backoff
|
|
16582
|
-
* capped at `maxDelayMs`.
|
|
16583
|
-
*
|
|
16584
|
-
* Backoff at the defaults (initialDelayMs=1_000, maxDelayMs=8_000, maxRetries=8):
|
|
16585
|
-
* 1s -> 2s -> 4s -> 8s -> 8s -> 8s -> 8s -> 8s (cumulative 47s)
|
|
16586
|
-
*
|
|
16587
|
-
* Non-retryable errors are rethrown immediately. The transient-error
|
|
16588
|
-
* classifier is `isRetryableTransientError` from ./retryable-errors.ts.
|
|
16589
|
-
*/
|
|
16590
|
-
async function withRetry(operation, logicalId, opts = {}) {
|
|
16591
|
-
const maxRetries = opts.maxRetries ?? 8;
|
|
16592
|
-
const initialDelayMs = opts.initialDelayMs ?? 1e3;
|
|
16593
|
-
const maxDelayMs = opts.maxDelayMs ?? 8e3;
|
|
16594
|
-
const sleep = opts.sleep ?? defaultSleep;
|
|
16595
|
-
let lastError;
|
|
16596
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
16597
|
-
return await operation();
|
|
16598
|
-
} catch (error) {
|
|
16599
|
-
lastError = error;
|
|
16600
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
16601
|
-
if (!(opts.isRetryable ? opts.isRetryable(message, error) : isRetryableTransientError(error, message)) || attempt >= maxRetries) throw error;
|
|
16602
|
-
const delay = Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs);
|
|
16603
|
-
opts.logger?.debug(` ⏳ Retrying ${logicalId} in ${delay / 1e3}s (attempt ${attempt + 1}/${maxRetries}) - ${message}`);
|
|
16604
|
-
for (let waited = 0; waited < delay; waited += 1e3) {
|
|
16605
|
-
if (opts.isInterrupted?.()) throw opts.onInterrupted ? opts.onInterrupted() : /* @__PURE__ */ new Error("Interrupted");
|
|
16606
|
-
await sleep(Math.min(1e3, delay - waited));
|
|
16607
|
-
}
|
|
16608
|
-
}
|
|
16609
|
-
throw lastError;
|
|
16610
|
-
}
|
|
16611
|
-
|
|
16612
16658
|
//#endregion
|
|
16613
16659
|
//#region src/deployment/resource-deadline.ts
|
|
16614
16660
|
/**
|
|
@@ -17265,7 +17311,7 @@ const FLUSH_INTERVAL_MS = 2e3;
|
|
|
17265
17311
|
const FLUSH_EVENT_THRESHOLD = 50;
|
|
17266
17312
|
/** Build-time cdkd version, with a dev fallback for non-built contexts. */
|
|
17267
17313
|
function getCdkdVersion() {
|
|
17268
|
-
return "0.267.
|
|
17314
|
+
return "0.267.4";
|
|
17269
17315
|
}
|
|
17270
17316
|
/**
|
|
17271
17317
|
* Generate a time-sortable unique run id, e.g.
|
|
@@ -19252,5 +19298,5 @@ var DeployEngine = class {
|
|
|
19252
19298
|
};
|
|
19253
19299
|
|
|
19254
19300
|
//#endregion
|
|
19255
|
-
export { buildAssetRedirectMap as $, PartialFailureError as $t,
|
|
19256
|
-
//# sourceMappingURL=deploy-engine-
|
|
19301
|
+
export { buildAssetRedirectMap as $, PartialFailureError as $t, slowCcOperationTimeoutMs as A, CFN_TEMPLATE_URL_LIMIT as At, applyRoleArnIfSet as B, getAwsClients as Bt, yellow as C, resolveCaptureObservedState as Ct, ProviderRegistry as D, resolveUseCdkBootstrapAssets as Dt, clearOnUpdateRemoval as E, resolveStateBucketWithDefaultAndSource as Et, refStateLookupFromResource as F, AssemblyReader as Ft, TemplateParser as G, ConfigError as Gt, withRetry as H, setAwsClients as Ht, WAFv2WebACLProvider as I, processStackMessages as It, rebuildClientForBucketRegion as J, LocalMigrateError as Jt, LockManager as K, DependencyError as Kt, normalizeAwsTagsToCfn as L, clearBucketRegionCache as Lt, isTerminationProtectionPropagationError as M, findLargeInlineResources as Mt, IntrinsicFunctionResolver as N, uploadCfnTemplate as Nt, findActionableSilentDrops as O, warnDeprecatedNoPrefixCliFlag as Ot, cfnRefValueFromPhysicalId as P, expectedOwnerParam as Pt, WorkGraph as Q, NestedStackChildDirectDestroyError as Qt, resolveExplicitPhysicalId as R, resolveBucketRegion as Rt, red as S, resolveAutoAssetStorage as St, collectInlinePolicyNamesManagedBySiblings as T, resolveStateBucketWithDefault as Tt, isRetryableTransientError as U, AssetError as Ut, DiffCalculator as V, resetAwsClients as Vt, DagBuilder as W, CdkdError as Wt, AssetPublisher as X, LockError as Xt, shouldRetainResource as Y, LocalStartServiceError as Yt, stringifyValue as Z, MissingCdkCliError as Zt, formatResourceLine as _, Synthesizer as _t, DeploymentEventsStore as a, StateError as an, ensureAssetStorage as at, gray as b, getLegacyStateBucketName as bt, replayFailedOperations as c, isCdkdError as cn, validateAssetBucketName as ct, IMPLICIT_DELETE_DEPENDENCIES as d, __exportAll as dn, formatDockerLoginError as dt, ProvisioningError as en, createAssetRedirectResolver as et, computeImplicitDeleteEdges as f, getDockerCmd as ft, renderStatefulReason as g, getDockerImageBySourceHash as gt, isStatefulRecreateTargetSync as h, AssetManifestLoader as ht, DeploymentEventsReader as i, StackTerminationProtectionError as in, BOOTSTRAP_MARKER_PREFIX as it, disableInstanceApiTermination as j, MIGRATE_TMP_PREFIX as jt, CloudControlProvider as k, CFN_TEMPLATE_BODY_LIMIT as kt, replayRollback as l, normalizeAwsError as ln, validateContainerRepoName as lt, MULTI_REGION_RECREATE_BLOCKED_TYPES as m, runDockerStreaming as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, ResourceUpdateNotSupportedError as nn, rewriteTemplateAssetReferences as nt, planFailedOps as o, SynthesisError as on, getBootstrapMarkerKey as ot, extractDeploymentEventError as p, runDockerForeground as pt, S3StateBackend as q, LocalInvokeBuildError as qt, DeployEngine as r, StackHasActiveImportsError as rn, AssetModeResolver as rt, planRollback as s, formatError as sn, parseBootstrapMarker as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, ResourceTimeoutError as tn, loadPublishableAssetManifest as tt, withResourceDeadline as u, withErrorHandling as un, buildDockerImage as ut, bold as v, synthesisStatusMessage as vt, IAMRoleProvider as w, resolveSkipPrefix as wt, green as x, resolveApp as xt, cyan as y, getDefaultStateBucketName as yt, assertRegionMatch as z, AwsClients as zt };
|
|
19302
|
+
//# sourceMappingURL=deploy-engine-DYrhAO-0.js.map
|