@go-to-k/cdkd 0.284.52 → 0.284.54
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-DYnIz-Gy.js → asg-provider-BaNTIjU4.js} +2 -2
- package/dist/{asg-provider-DYnIz-Gy.js.map → asg-provider-BaNTIjU4.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-DxboGDr4.js → deploy-engine-B9SrF2-R.js} +291 -35
- package/dist/deploy-engine-B9SrF2-R.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-BJ8R04vD.js → program-Bpz-tAA5.js} +149 -47
- package/dist/{program-BJ8R04vD.js.map → program-Bpz-tAA5.js.map} +1 -1
- package/dist/{version-BXmUXkY2.js → version-DLRgymoe.js} +2 -2
- package/dist/{version-BXmUXkY2.js.map → version-DLRgymoe.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-DxboGDr4.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as getLiveRenderer, d as generateResourceNameWithFallback, f as getCurrentStackName, h as withStackName, l as applyDefaultNameForFallback, n as getLogger, p as looksLikeCdkdGeneratedName, u as generateResourceName } from "./logger-zRrlbaQt.js";
|
|
2
|
-
import { t as getCdkdVersion } from "./version-
|
|
2
|
+
import { t as getCdkdVersion } from "./version-DLRgymoe.js";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
5
5
|
import { CloudControlClient, CreateResourceCommand, DeleteResourceCommand, GetResourceCommand, GetResourceRequestStatusCommand, ListResourcesCommand, UpdateResourceCommand } from "@aws-sdk/client-cloudcontrol";
|
|
@@ -110,11 +110,17 @@ const IAM_PROPAGATION_ERROR_MESSAGE_PATTERNS = [
|
|
|
110
110
|
"is not a valid role to allow SNS"
|
|
111
111
|
];
|
|
112
112
|
/**
|
|
113
|
-
* The NON-IAM-propagation
|
|
114
|
-
* transient failures whose recovery
|
|
115
|
-
*
|
|
116
|
-
* load-related (throttling), where hammering AWS with dense retries
|
|
117
|
-
* and exponential backoff is the correct shape.
|
|
113
|
+
* The NON-IAM-propagation, NON-name-cooldown third of
|
|
114
|
+
* {@link RETRYABLE_ERROR_MESSAGE_PATTERNS}: transient failures whose recovery
|
|
115
|
+
* window is either long (a resource still leaving a Pending/Creating state) or
|
|
116
|
+
* genuinely load-related (throttling), where hammering AWS with dense retries
|
|
117
|
+
* is harmful and exponential backoff is the correct shape.
|
|
118
|
+
*
|
|
119
|
+
* The name-cooldown spellings used to live here too (`wait 60 seconds`, S3's
|
|
120
|
+
* `conflicting conditional operation`); they moved to
|
|
121
|
+
* {@link NAME_COOLDOWN_ERROR_MESSAGE_PATTERNS} so the ordinary-create path and
|
|
122
|
+
* the delete-then-re-create sites read ONE list instead of two that drifted
|
|
123
|
+
* apart (issue [#2116](https://github.com/go-to-k/cdkd/issues/2116)).
|
|
118
124
|
*/
|
|
119
125
|
const OTHER_TRANSIENT_ERROR_MESSAGE_PATTERNS = [
|
|
120
126
|
"currently in the following state: Pending",
|
|
@@ -123,10 +129,8 @@ const OTHER_TRANSIENT_ERROR_MESSAGE_PATTERNS = [
|
|
|
123
129
|
"DependencyViolation",
|
|
124
130
|
"does not exist",
|
|
125
131
|
"Schema is currently being altered",
|
|
126
|
-
"conflicting conditional operation",
|
|
127
132
|
"scheduled for deletion",
|
|
128
133
|
"Could not deliver test message",
|
|
129
|
-
"wait 60 seconds",
|
|
130
134
|
"concurrent update operation",
|
|
131
135
|
"because it is in use",
|
|
132
136
|
"Rate exceeded",
|
|
@@ -135,15 +139,101 @@ const OTHER_TRANSIENT_ERROR_MESSAGE_PATTERNS = [
|
|
|
135
139
|
"Unable to complete operation due to concurrent modification"
|
|
136
140
|
];
|
|
137
141
|
/**
|
|
142
|
+
* The **name-cooldown** third of {@link RETRYABLE_ERROR_MESSAGE_PATTERNS}:
|
|
143
|
+
* an AWS service that holds a resource's NAME (or other unique identifier)
|
|
144
|
+
* while an ASYNCHRONOUS delete of the previous holder is still in flight, so a
|
|
145
|
+
* create of the same name inside that window is refused with a
|
|
146
|
+
* service-specific message. The window always clears on its own — the delete
|
|
147
|
+
* that opened it is already running — which is what makes every entry here
|
|
148
|
+
* retryable rather than terminal, whichever call site hits it.
|
|
149
|
+
*
|
|
150
|
+
* Read by BOTH consumers, which is the whole point of the list existing
|
|
151
|
+
* (issue [#2116](https://github.com/go-to-k/cdkd/issues/2116)):
|
|
152
|
+
*
|
|
153
|
+
* - {@link isNameCooldownError}, and through it
|
|
154
|
+
* {@link isRecreateRetryableError}, the retry filter at the
|
|
155
|
+
* delete-then-re-create sites (the deploy engine's `--replace` delete-first
|
|
156
|
+
* fallback, the recreate-via-* path, the rollback executor's
|
|
157
|
+
* delete-new-first) — the sites where cdkd itself just deleted the name
|
|
158
|
+
* holder;
|
|
159
|
+
* - {@link RETRYABLE_ERROR_MESSAGE_PATTERNS}, i.e. the ORDINARY create path,
|
|
160
|
+
* where a fresh `cdkd deploy` process has no idea a prior `cdkd destroy`
|
|
161
|
+
* deleted anything.
|
|
162
|
+
*
|
|
163
|
+
* The second consumer is the reachable one and was the measured failure in
|
|
164
|
+
* #2116: destroy-then-redeploy is a routine dev loop, CloudFormation absorbs
|
|
165
|
+
* the window and converges, and cdkd claims template compatibility with
|
|
166
|
+
* CloudFormation — so failing the whole deploy (26 resources created and
|
|
167
|
+
* rolled back, on the run that filed the issue) over a condition that clears
|
|
168
|
+
* in seconds is a parity defect. Before this list, the two consumers held
|
|
169
|
+
* DIFFERENT spellings of the same SQS error: the wire message
|
|
170
|
+
* (`wait 60 seconds`) was in the generic table so an ordinary create retried
|
|
171
|
+
* it, while the error CODE (`QueueDeletedRecently`) was not — so whether the
|
|
172
|
+
* identical AWS condition was survivable depended on which spelling the SDK
|
|
173
|
+
* happened to surface.
|
|
174
|
+
*
|
|
175
|
+
* Bounded, not unbounded: since #2116 the ordinary-create path rides
|
|
176
|
+
* `withRetry`'s NAME-COOLDOWN grid (8 retries, 2s/4s/8s then capped at 10s ≈
|
|
177
|
+
* 64s of sleep — `NAME_COOLDOWN_INITIAL_DELAY_MS` in `./retry.ts`), which is
|
|
178
|
+
* the same budget the re-create sites already carried. It is deliberately NOT
|
|
179
|
+
* the generic 47s schedule this path used to inherit: SQS's own sentence names
|
|
180
|
+
* a 60-second window, so 47s would not converge, it would merely fail 47s
|
|
181
|
+
* later. A name that is NOT in fact being
|
|
182
|
+
* released — a genuine, permanent collision — is a different signature
|
|
183
|
+
* ({@link isNameCollisionError}) and is deliberately NOT here, so it still
|
|
184
|
+
* fails fast into the actionable `--replace` refusal instead of burning a
|
|
185
|
+
* budget it cannot survive.
|
|
186
|
+
*
|
|
187
|
+
* **What must NOT go in this list.** Every entry below is specific to a delete
|
|
188
|
+
* that is ALREADY in flight and clears within a budget. Three candidates the
|
|
189
|
+
* sibling sweep turned up are recorded here as deliberate EXCLUSIONS rather
|
|
190
|
+
* than left unmentioned, because "absent" and "considered and rejected" are
|
|
191
|
+
* indistinguishable to the next person doing this sweep:
|
|
192
|
+
*
|
|
193
|
+
* - **ELBv2 `DuplicateLoadBalancerName`** and **DynamoDB's create-side
|
|
194
|
+
* `Table already exists: <name>`** — AWS raises both for a resource that
|
|
195
|
+
* merely EXISTS, just as readily as for a deleting one, so neither is
|
|
196
|
+
* distinguishable from a terminal collision. Promoting either would convert
|
|
197
|
+
* a fast, actionable `--replace` refusal into a full retry budget ending in
|
|
198
|
+
* the same failure. (DynamoDB's `Table is being deleted` IS distinguishable,
|
|
199
|
+
* but promoting THAT one re-multiplies the destroy-runner budget arithmetic
|
|
200
|
+
* `src/provisioning/dynamodb-index-busy-delete.ts` derives against the
|
|
201
|
+
* per-resource deadline — a different reason, so it is stated separately
|
|
202
|
+
* rather than folded in with the two above.)
|
|
203
|
+
* - **Secrets Manager `scheduled for deletion`** — the same one-sided shape as
|
|
204
|
+
* S3's entry (generic table only, invisible to
|
|
205
|
+
* {@link isRecreateRetryableError}), and the provider does delete with
|
|
206
|
+
* `ForceDeleteWithoutRecovery: true`, so it LOOKS like it belongs. It is
|
|
207
|
+
* excluded because that single message covers TWO conditions with wildly
|
|
208
|
+
* different windows: a force-deleted secret's name releasing in
|
|
209
|
+
* seconds-to-minutes, and a secret scheduled for deletion with a
|
|
210
|
+
* `RecoveryWindowInDays` of 7-30 DAYS, which no bounded budget can ride out
|
|
211
|
+
* and which a user reaches by deleting a secret outside cdkd. A budget that
|
|
212
|
+
* cannot converge on half its population is worse than failing fast, so the
|
|
213
|
+
* entry stays where it already was — in the generic table, retryable on an
|
|
214
|
+
* ordinary create and terminal at the re-create sites, unchanged by #2116.
|
|
215
|
+
*/
|
|
216
|
+
const NAME_COOLDOWN_ERROR_MESSAGE_PATTERNS = [
|
|
217
|
+
"QueueDeletedRecently",
|
|
218
|
+
"wait 60 seconds",
|
|
219
|
+
"StateMachineDeleting",
|
|
220
|
+
"State Machine is being deleted",
|
|
221
|
+
"conflicting conditional operation"
|
|
222
|
+
];
|
|
223
|
+
/**
|
|
138
224
|
* Patterns that mark an AWS error as a transient/retryable failure.
|
|
139
225
|
* Each entry is a substring match against the error message; all of these
|
|
140
226
|
* are situations where the same call typically succeeds after a short delay
|
|
141
227
|
* because of eventual consistency or just-created-dependency propagation.
|
|
142
228
|
*
|
|
143
|
-
* Composed from the
|
|
229
|
+
* Composed from the three halves above so retryability has ONE source of truth
|
|
144
230
|
* while `withRetry` can still pick a per-class backoff cadence.
|
|
145
231
|
*/
|
|
146
|
-
const RETRYABLE_ERROR_MESSAGE_PATTERNS = [
|
|
232
|
+
const RETRYABLE_ERROR_MESSAGE_PATTERNS = [
|
|
233
|
+
...IAM_PROPAGATION_ERROR_MESSAGE_PATTERNS,
|
|
234
|
+
...OTHER_TRANSIENT_ERROR_MESSAGE_PATTERNS,
|
|
235
|
+
...NAME_COOLDOWN_ERROR_MESSAGE_PATTERNS
|
|
236
|
+
];
|
|
147
237
|
/**
|
|
148
238
|
* HTTP status codes that always indicate a transient failure worth retrying.
|
|
149
239
|
* 429 = Too Many Requests (throttle), 503 = Service Unavailable.
|
|
@@ -575,26 +665,31 @@ function isNameCollisionError(message) {
|
|
|
575
665
|
return /(?<!\b(?:must|not|should|may|cannot)\s)already exists?\b/i.test(message) || message.includes("AlreadyExists");
|
|
576
666
|
}
|
|
577
667
|
/**
|
|
578
|
-
* Match
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
*
|
|
586
|
-
*
|
|
587
|
-
*
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
*
|
|
668
|
+
* Match a same-name re-creation cooldown — an AWS service still holding a
|
|
669
|
+
* resource's name while its asynchronous delete finishes. Every recognised
|
|
670
|
+
* spelling lives in {@link NAME_COOLDOWN_ERROR_MESSAGE_PATTERNS}; add new ones
|
|
671
|
+
* THERE rather than here, so the ordinary-create path
|
|
672
|
+
* ({@link RETRYABLE_ERROR_MESSAGE_PATTERNS}, which composes that list) cannot
|
|
673
|
+
* drift out of step with the delete-then-re-create sites again. That drift is
|
|
674
|
+
* exactly what issue [#2116](https://github.com/go-to-k/cdkd/issues/2116)
|
|
675
|
+
* found: SQS's wire message was retryable on an ordinary create while its
|
|
676
|
+
* error code was not, and the Step Functions spelling was recognised by
|
|
677
|
+
* neither.
|
|
678
|
+
*
|
|
679
|
+
* The delete-then-re-create sites (the deploy engine's `--replace`
|
|
680
|
+
* delete-first fallback and the rollback executor's reverse-replacement)
|
|
681
|
+
* override the retry filter with {@link isNameCollisionError}, which these
|
|
682
|
+
* signatures do NOT match — so a replacement revert used to fail fast
|
|
683
|
+
* mid-flight with the resource absent from both AWS and state (issue #1206).
|
|
684
|
+
* Those sites OR this matcher into their retry filter, with a schedule long
|
|
685
|
+
* enough to cover the 60s SQS window.
|
|
591
686
|
*
|
|
592
687
|
* Kept separate from {@link isNameCollisionError} on purpose: a cooldown at a
|
|
593
688
|
* create-first site must NOT be treated as a collision (deleting the new
|
|
594
689
|
* resource would not release the cooldown on the old name).
|
|
595
690
|
*/
|
|
596
691
|
function isNameCooldownError(message) {
|
|
597
|
-
return
|
|
692
|
+
return NAME_COOLDOWN_ERROR_MESSAGE_PATTERNS.some((p) => message.includes(p));
|
|
598
693
|
}
|
|
599
694
|
/**
|
|
600
695
|
* Retry filter for the delete-then-re-create sites: the old name holder was
|
|
@@ -989,8 +1084,10 @@ var PartialFailureError = class PartialFailureError extends CdkdError {
|
|
|
989
1084
|
* is the whole point: the message interpolates the resource's LOGICAL ID,
|
|
990
1085
|
* and the retry classifiers match by SUBSTRING, so a perfectly ordinary
|
|
991
1086
|
* composite CDK id like `MyDependencyViolationSub` made
|
|
992
|
-
* `isRetryableTransientError` return true (`DependencyViolation`
|
|
993
|
-
* whitespace-free entry in `RETRYABLE_ERROR_MESSAGE_PATTERNS
|
|
1087
|
+
* `isRetryableTransientError` return true (`DependencyViolation` was then the
|
|
1088
|
+
* only whitespace-free entry in `RETRYABLE_ERROR_MESSAGE_PATTERNS`; the
|
|
1089
|
+
* name-cooldown error CODES `QueueDeletedRecently` / `StateMachineDeleting`
|
|
1090
|
+
* joined it under issue #2116, so the hazard is wider, not narrower) and burned the
|
|
994
1091
|
* full generic schedule — 8 retries, ~47s of pure sleep — on a path that was
|
|
995
1092
|
* never going to succeed, before the fallback the error exists to trigger was
|
|
996
1093
|
* even reached.
|
|
@@ -7575,6 +7672,45 @@ var S3StateBackend = class {
|
|
|
7575
7672
|
return keys;
|
|
7576
7673
|
}
|
|
7577
7674
|
/**
|
|
7675
|
+
* Raw sidecar-object listing WITH metadata under the state bucket.
|
|
7676
|
+
*
|
|
7677
|
+
* {@link listRawKeys}'s twin, and it exists because an age-guarded sweep
|
|
7678
|
+
* needs `LastModified` and the reclaim plan needs `Size` — neither of which a
|
|
7679
|
+
* key list carries. Used by `cdkd gc`'s custom-resource-response sweep
|
|
7680
|
+
* (issue #2052), where the age is the only thing separating an abandoned
|
|
7681
|
+
* placeholder from one a concurrent run is about to write to.
|
|
7682
|
+
*
|
|
7683
|
+
* `LastModified` / `Size` are omitted from the response only for a key S3
|
|
7684
|
+
* did not return metadata for, which does not happen for `ListObjectsV2`
|
|
7685
|
+
* `Contents` entries; an entry missing either is DROPPED rather than
|
|
7686
|
+
* defaulted, because defaulting the date would either exempt an object from
|
|
7687
|
+
* the age guard forever or expose it immediately, and both are wrong in a
|
|
7688
|
+
* direction the caller cannot see.
|
|
7689
|
+
*/
|
|
7690
|
+
async listRawObjects(keyPrefix) {
|
|
7691
|
+
await this.ensureClientForBucket();
|
|
7692
|
+
const objects = [];
|
|
7693
|
+
let continuationToken;
|
|
7694
|
+
do {
|
|
7695
|
+
const response = await this.s3Client.send(new ListObjectsV2Command({
|
|
7696
|
+
Bucket: this.config.bucket,
|
|
7697
|
+
...await this.ownerParam(),
|
|
7698
|
+
Prefix: keyPrefix,
|
|
7699
|
+
...continuationToken && { ContinuationToken: continuationToken }
|
|
7700
|
+
}));
|
|
7701
|
+
for (const obj of response.Contents ?? []) {
|
|
7702
|
+
if (obj.Key === void 0 || obj.LastModified === void 0 || obj.Size === void 0) continue;
|
|
7703
|
+
objects.push({
|
|
7704
|
+
key: obj.Key,
|
|
7705
|
+
lastModified: obj.LastModified,
|
|
7706
|
+
size: obj.Size
|
|
7707
|
+
});
|
|
7708
|
+
}
|
|
7709
|
+
continuationToken = response.IsTruncated ? response.NextContinuationToken : void 0;
|
|
7710
|
+
} while (continuationToken);
|
|
7711
|
+
return objects;
|
|
7712
|
+
}
|
|
7713
|
+
/**
|
|
7578
7714
|
* Raw sidecar-object batch delete under the state bucket. Used by the
|
|
7579
7715
|
* deployment-events pruner (issue #885) to drop superseded `{runId}.jsonl`
|
|
7580
7716
|
* streams + their index. Chunked to the 1,000-key `DeleteObjects` ceiling.
|
|
@@ -9760,6 +9896,68 @@ const IAM_PROPAGATION_MAX_DELAY_MS = 2e3;
|
|
|
9760
9896
|
* generic one by more than 0.75s in the early band.
|
|
9761
9897
|
*/
|
|
9762
9898
|
const IAM_PROPAGATION_MAX_RETRIES = 26;
|
|
9899
|
+
/**
|
|
9900
|
+
* Backoff for the NAME-COOLDOWN class on the DEFAULT schedule — an AWS service
|
|
9901
|
+
* still holding a resource's name while an asynchronous delete of the previous
|
|
9902
|
+
* holder finishes (`NAME_COOLDOWN_ERROR_MESSAGE_PATTERNS` in
|
|
9903
|
+
* ./retryable-errors.ts). Issue
|
|
9904
|
+
* [#2116](https://github.com/go-to-k/cdkd/issues/2116).
|
|
9905
|
+
*
|
|
9906
|
+
* **Why this class does not inherit the generic schedule.** The generic one
|
|
9907
|
+
* sleeps 1+2+4+8+8+8+8+8 = 47s, and the longest window in this class NAMES its
|
|
9908
|
+
* own duration: SQS's own sentence is "You must wait 60 seconds after deleting
|
|
9909
|
+
* a queue before you can create another with the same name." A 47s budget
|
|
9910
|
+
* against a 60s window does not converge — it just fails 47s later, which is
|
|
9911
|
+
* strictly worse than failing at once. That mismatch was live for as long as
|
|
9912
|
+
* `wait 60 seconds` has been in the generic table.
|
|
9913
|
+
*
|
|
9914
|
+
* **Where the numbers come from — precedent, not a fresh guess.** They are the
|
|
9915
|
+
* delete-then-re-create sites' existing budget, chosen for this exact window
|
|
9916
|
+
* (`deploy-engine.ts`'s `--replace` fallback and `rollback-executor.ts`'s
|
|
9917
|
+
* reverse-replacement both pass `maxRetries: 8, initialDelayMs: 2_000,
|
|
9918
|
+
* maxDelayMs: 10_000`). Adopting it here makes the ordinary create path and
|
|
9919
|
+
* the re-create sites ride the SAME window with the SAME budget, which is the
|
|
9920
|
+
* property #2116 is about: whether a cooldown is survivable must not depend on
|
|
9921
|
+
* which call site met it.
|
|
9922
|
+
*
|
|
9923
|
+
* 2 + 4 + 8 + 10 x 5 = 64s over 8 retries
|
|
9924
|
+
*
|
|
9925
|
+
* Probe grid (seconds after the first failure): 2, 6, 14, 24, 34, 44, 54, 64 —
|
|
9926
|
+
* covering SQS's 60s and, with room to spare, the ~23s of `status: DELETING`
|
|
9927
|
+
* measured on an idle Step Functions state machine
|
|
9928
|
+
* (`tests/integration/custom-resource-provider`).
|
|
9929
|
+
*
|
|
9930
|
+
* **What it does NOT claim.** A window LONGER than 64s still fails, with the
|
|
9931
|
+
* same AWS message as before. This class converts "always fails" into "fails
|
|
9932
|
+
* only past a budget derived from the longest window AWS itself documents" —
|
|
9933
|
+
* and, unlike the generic schedule, a run that exhausts it now says so (see
|
|
9934
|
+
* the give-up summary in {@link withRetry}), so a too-short budget is
|
|
9935
|
+
* reportable rather than indistinguishable from having no retry at all.
|
|
9936
|
+
*
|
|
9937
|
+
* The retry COUNT is deliberately the generic 8 rather than a new ceiling, so
|
|
9938
|
+
* nothing about the loop's own exit condition moves. Same reason the dense IAM
|
|
9939
|
+
* grid needed its own count and this does not.
|
|
9940
|
+
*
|
|
9941
|
+
* **But "only the delay grid changes" understates the effect at the NESTED
|
|
9942
|
+
* sites, so state the compounding rather than implying there is none.** Three
|
|
9943
|
+
* call sites wrap a DEFAULT-schedule `withRetry` inside their own outer loop
|
|
9944
|
+
* (`deploy-engine.ts`'s two `--replace` / recreate sites and
|
|
9945
|
+
* `rollback-executor.ts`'s reverse-replacement). The inner loop is the one
|
|
9946
|
+
* this grid changes, and the outer one re-enters it per attempt, so the
|
|
9947
|
+
* product grows with it: total sleep on a cooldown at those sites measures
|
|
9948
|
+
* **487s -> 640s (8.1 -> 10.7 min)**. That is accepted, not overlooked -- it
|
|
9949
|
+
* stays inside the 30-minute per-resource `withResourceDeadline`, and the
|
|
9950
|
+
* alternative is an inner loop that cannot ride out the window it is nested
|
|
9951
|
+
* for. A reader auditing those budgets needs the number, which is why it is
|
|
9952
|
+
* here and not left to be re-derived.
|
|
9953
|
+
*
|
|
9954
|
+
* `dynamodb-index-busy-delete.ts` is unaffected and that was checked rather
|
|
9955
|
+
* than assumed: it passes all four knobs, so `defaultSchedule` is false and
|
|
9956
|
+
* this grid never applies to it -- its own deadline arithmetic stands.
|
|
9957
|
+
*/
|
|
9958
|
+
const NAME_COOLDOWN_INITIAL_DELAY_MS = 2e3;
|
|
9959
|
+
/** Cap for the name-cooldown backoff. See {@link NAME_COOLDOWN_INITIAL_DELAY_MS}. */
|
|
9960
|
+
const NAME_COOLDOWN_MAX_DELAY_MS = 1e4;
|
|
9763
9961
|
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
9764
9962
|
/**
|
|
9765
9963
|
* Run `operation`, retrying transient failures with exponential backoff
|
|
@@ -9779,6 +9977,13 @@ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
9779
9977
|
* re-create sites' ~64s budget covering SQS's 60s name cooldown) and gets it
|
|
9780
9978
|
* verbatim.
|
|
9781
9979
|
*
|
|
9980
|
+
* A THIRD class rides its own grid on the default schedule since issue
|
|
9981
|
+
* [#2116](https://github.com/go-to-k/cdkd/issues/2116): a NAME COOLDOWN (a
|
|
9982
|
+
* service still holding a name while an async delete finishes) gets
|
|
9983
|
+
* 2s/4s/8s then 10s to a 64s total, because the longest window in that class
|
|
9984
|
+
* NAMES its duration -- SQS says "wait 60 seconds" -- and the generic 47s
|
|
9985
|
+
* cannot ride out a 60s wait. See {@link NAME_COOLDOWN_INITIAL_DELAY_MS}.
|
|
9986
|
+
*
|
|
9782
9987
|
* The class is re-evaluated per attempt, so a propagation retry that runs into
|
|
9783
9988
|
* a throttle backs OFF exponentially for that attempt instead of hammering.
|
|
9784
9989
|
*
|
|
@@ -9809,6 +10014,8 @@ async function withRetry(operation, logicalId, opts = {}) {
|
|
|
9809
10014
|
let sawPropagation = false;
|
|
9810
10015
|
let propagationRetries = 0;
|
|
9811
10016
|
let propagationSleptMs = 0;
|
|
10017
|
+
let nameCooldownRetries = 0;
|
|
10018
|
+
let nameCooldownSleptMs = 0;
|
|
9812
10019
|
let serverErrorRetries = 0;
|
|
9813
10020
|
for (let attempt = 0; attempt <= attemptCeiling; attempt++) try {
|
|
9814
10021
|
return await operation();
|
|
@@ -9819,12 +10026,15 @@ async function withRetry(operation, logicalId, opts = {}) {
|
|
|
9819
10026
|
const retryable = opts.isRetryable ? opts.isRetryable(message, error) : isRetryableTransientError(error, message);
|
|
9820
10027
|
const propagation = defaultSchedule && isIamPropagationError(message);
|
|
9821
10028
|
if (propagation) sawPropagation = true;
|
|
10029
|
+
const nameCooldown = defaultSchedule && !propagation && isNameCooldownError(message);
|
|
9822
10030
|
const attemptLimit = sawPropagation ? 26 : maxRetries;
|
|
9823
10031
|
if (!retryable || attempt >= attemptLimit) {
|
|
9824
|
-
if (propagationRetries > 0 || serverErrorRetries > 0) {
|
|
10032
|
+
if (propagationRetries > 0 || serverErrorRetries > 0 || nameCooldownRetries > 0) {
|
|
9825
10033
|
const budgetExhausted = sawPropagation && attempt >= attemptLimit;
|
|
10034
|
+
const nameCooldownBudgetExhausted = nameCooldownRetries >= maxRetries && attempt >= attemptLimit;
|
|
9826
10035
|
const spent = [];
|
|
9827
10036
|
if (propagationRetries > 0) spent.push(`${propagationRetries} IAM-propagation ${propagationRetries === 1 ? "retry" : "retries"} over ${(propagationSleptMs / 1e3).toFixed(2)}s of propagation backoff${budgetExhausted ? " (the full propagation budget)" : ""}`);
|
|
10037
|
+
if (nameCooldownRetries > 0) spent.push(`${nameCooldownRetries} name-cooldown ${nameCooldownRetries === 1 ? "retry" : "retries"} over ${(nameCooldownSleptMs / 1e3).toFixed(2)}s waiting for the name to be released${nameCooldownBudgetExhausted ? " (the full name-cooldown budget)" : ""}`);
|
|
9828
10038
|
if (serverErrorRetries > 0) spent.push(`${serverErrorRetries} transient server-error ${serverErrorRetries === 1 ? "retry" : "retries"} (HTTP 5xx)`);
|
|
9829
10039
|
const summary = () => `${logicalId}: gave up after ${spent.join(" and ")} - ${message}` + formatRetryClassificationSignals(error);
|
|
9830
10040
|
try {
|
|
@@ -9833,7 +10043,7 @@ async function withRetry(operation, logicalId, opts = {}) {
|
|
|
9833
10043
|
}
|
|
9834
10044
|
throw error;
|
|
9835
10045
|
}
|
|
9836
|
-
const delay = propagation ? Math.min(250 * Math.pow(2, attempt), IAM_PROPAGATION_MAX_DELAY_MS) : Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs);
|
|
10046
|
+
const delay = propagation ? Math.min(250 * Math.pow(2, attempt), IAM_PROPAGATION_MAX_DELAY_MS) : nameCooldown ? Math.min(NAME_COOLDOWN_INITIAL_DELAY_MS * Math.pow(2, attempt), NAME_COOLDOWN_MAX_DELAY_MS) : Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs);
|
|
9837
10047
|
const backoffThroughThisAttemptMs = propagation ? propagationSleptMs + delay : propagationSleptMs;
|
|
9838
10048
|
opts.logger?.debug(` ⏳ Retrying ${logicalId} in ${delay / 1e3}s (attempt ${attempt + 1}/${attemptLimit}${propagation ? `, ${(backoffThroughThisAttemptMs / 1e3).toFixed(2)}s backoff through this attempt` : ""}) - ${message}`);
|
|
9839
10049
|
for (let waited = 0; waited < delay; waited += 1e3) {
|
|
@@ -9843,6 +10053,9 @@ async function withRetry(operation, logicalId, opts = {}) {
|
|
|
9843
10053
|
if (propagation) {
|
|
9844
10054
|
propagationRetries++;
|
|
9845
10055
|
propagationSleptMs = backoffThroughThisAttemptMs;
|
|
10056
|
+
} else if (nameCooldown) {
|
|
10057
|
+
nameCooldownRetries++;
|
|
10058
|
+
nameCooldownSleptMs += delay;
|
|
9846
10059
|
} else if (opts.isRetryable === void 0 && isTransientServerError(error)) serverErrorRetries++;
|
|
9847
10060
|
}
|
|
9848
10061
|
throw lastError;
|
|
@@ -16261,7 +16474,8 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
16261
16474
|
* criterion `retryable-errors.ts` documents as insufficient: the classifiers
|
|
16262
16475
|
* match by SUBSTRING, and `sourceClause` interpolates template-controlled
|
|
16263
16476
|
* text, so a logical id like `MyDependencyViolationHandler` puts
|
|
16264
|
-
* `DependencyViolation` (
|
|
16477
|
+
* `DependencyViolation` (a whitespace-free entry in the table — the only
|
|
16478
|
+
* one until issue #2116 added the name-cooldown error codes) into the
|
|
16265
16479
|
* message. Reachability is real even though resolution runs outside
|
|
16266
16480
|
* `withRetry` on the flat path: `NestedStackProvider.create` runs a child
|
|
16267
16481
|
* `DeployEngine.deploy()` and re-throws, and the parent wraps `create()` in
|
|
@@ -18276,7 +18490,7 @@ var CloudControlProvider = class {
|
|
|
18276
18490
|
if (context?.finalSnapshotIdentifier !== void 0) throw new ProvisioningError(`${logicalId} (${resourceType}) requires a final snapshot (DeletionPolicy: Snapshot), but the Cloud Control API delete route has no final-snapshot parameter. Re-run with --skip-final-snapshot after snapshotting manually, or retain the resource.`, resourceType, logicalId, physicalId);
|
|
18277
18491
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
18278
18492
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
18279
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
18493
|
+
const { ASGProvider } = await import("./asg-provider-BaNTIjU4.js").then((n) => n.n);
|
|
18280
18494
|
return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
18281
18495
|
}
|
|
18282
18496
|
const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
|
|
@@ -19221,6 +19435,42 @@ function endCommandInterruptScope() {
|
|
|
19221
19435
|
resetInterruptWatchLatch();
|
|
19222
19436
|
}
|
|
19223
19437
|
|
|
19438
|
+
//#endregion
|
|
19439
|
+
//#region src/state/state-prefix.ts
|
|
19440
|
+
/**
|
|
19441
|
+
* The default S3 key prefix for cdkd state.
|
|
19442
|
+
*
|
|
19443
|
+
* Homed in the STATE layer rather than in `src/cli/commands/state-file-keys.ts`
|
|
19444
|
+
* (which re-exports it, so its four existing importers are unchanged) because
|
|
19445
|
+
* `src/state/lock-contention-message.ts` needs it to decide whether a recovery
|
|
19446
|
+
* hint should spell `--state-prefix` at all, and a `src/state/**` module
|
|
19447
|
+
* importing from `src/cli/commands/**` inverts the layering — the CLI sits
|
|
19448
|
+
* ABOVE the state layer in the 7-layer architecture, not below it.
|
|
19449
|
+
*
|
|
19450
|
+
* Note this is only the DEFAULT. Other commands accept `--state-prefix`, so
|
|
19451
|
+
* whole-bucket listings deliberately do not scope to it.
|
|
19452
|
+
*/
|
|
19453
|
+
const DEFAULT_STATE_PREFIX = "cdkd";
|
|
19454
|
+
/**
|
|
19455
|
+
* The state-bucket prefix `CustomResourceProvider` PUTs its response
|
|
19456
|
+
* placeholders under, one object per invocation
|
|
19457
|
+
* (`custom-resource-responses/{requestId}.json`).
|
|
19458
|
+
*
|
|
19459
|
+
* Homed here for the same layering reason as {@link DEFAULT_STATE_PREFIX}: the
|
|
19460
|
+
* PRODUCER is `src/provisioning/providers/custom-resource-provider.ts` and the
|
|
19461
|
+
* COLLECTOR is `src/cli/commands/gc.ts`, so a copy in either would be a copy
|
|
19462
|
+
* the other could drift from — and the two spellings would then disagree about
|
|
19463
|
+
* which objects exist, which is the only way a sweeper can miss the family it
|
|
19464
|
+
* was written for (issue #2052). `src/cli/commands/state-file-keys.ts`
|
|
19465
|
+
* re-exports it so gc reads it alongside the other state-key constants.
|
|
19466
|
+
*
|
|
19467
|
+
* Note this is only the DEFAULT: `ProviderRegistry` can be configured with a
|
|
19468
|
+
* different `responsePrefix`, so a sweep scoped to this value is a sweep of the
|
|
19469
|
+
* default layout. gc has no access to a non-default one — nothing persists it —
|
|
19470
|
+
* which is stated at the sweep's own call site rather than implied here.
|
|
19471
|
+
*/
|
|
19472
|
+
const CUSTOM_RESOURCE_RESPONSE_PREFIX = "custom-resource-responses";
|
|
19473
|
+
|
|
19224
19474
|
//#endregion
|
|
19225
19475
|
//#region src/provisioning/providers/custom-resource-provider.ts
|
|
19226
19476
|
/**
|
|
@@ -26075,7 +26325,8 @@ async function replayRollback(operations, stateResources, stackName, ctx, option
|
|
|
26075
26325
|
* hang the flag exists to prevent. Those providers retry internally.
|
|
26076
26326
|
* - **Interrupt.** `replayRollback` polls interrupts only BETWEEN ops, so an
|
|
26077
26327
|
* un-threaded `isInterrupted` leaves Ctrl-C dead for the length of the
|
|
26078
|
-
* backoff schedule
|
|
26328
|
+
* backoff schedule per op — ~47s on the generic grid, or ~64s if the op
|
|
26329
|
+
* hits a name cooldown, which rides its own longer grid since issue #2116.
|
|
26079
26330
|
*
|
|
26080
26331
|
* Returns the provider's result so the caller can honour
|
|
26081
26332
|
* `effectiveProperties` (issue #1644) — both revert arms used to write the
|
|
@@ -26381,8 +26632,13 @@ async function updateWithRollbackRetry(provider, args, logicalId, logger, isInte
|
|
|
26381
26632
|
* on attempt 0 and it reaches the caller's catch on the FIRST outer attempt,
|
|
26382
26633
|
* exactly as before. The SQS cooldown IS matched by the inner classifier (the
|
|
26383
26634
|
* generic table carries `wait 60 seconds`), which is the same division of
|
|
26384
|
-
* labour the deploy engine's named-replacement site documents
|
|
26385
|
-
*
|
|
26635
|
+
* labour the deploy engine's named-replacement site documents. Since issue
|
|
26636
|
+
* #2116 the inner retry rides the name-cooldown grid (≈64s) rather than the
|
|
26637
|
+
* generic ~47s one, so it covers the whole 60s window on its own instead of
|
|
26638
|
+
* absorbing most of it and leaving a tail for the outer loop; the outer loop
|
|
26639
|
+
* now earns its place by ALSO covering the late name release that the inner
|
|
26640
|
+
* default classifier rejects. The two compound — measured at 640s of total
|
|
26641
|
+
* sleep on a cooldown, inside the 30-minute per-resource deadline.
|
|
26386
26642
|
*/
|
|
26387
26643
|
async function createWithRollbackRetry(provider, create, logicalId, logger, isInterrupted, secrets, outer) {
|
|
26388
26644
|
if (provider.disableOuterRetry) return await create();
|
|
@@ -29615,5 +29871,5 @@ var DeployEngine = class {
|
|
|
29615
29871
|
};
|
|
29616
29872
|
|
|
29617
29873
|
//#endregion
|
|
29618
|
-
export {
|
|
29619
|
-
//# sourceMappingURL=deploy-engine-
|
|
29874
|
+
export { endCommandInterruptScope as $, clearBucketRegionCache as $n, importableOutputKeys as $t, renderStatefulReason as A, synthesisStatusMessage as An, isMarkedNonRetryable as Ar, maskSecretsInError as At, exportAliasCollisionScrubWarning as B, stateBucketExistenceConfirmed as Bn, s3BucketWebsiteUrl as Bt, isFinalSnapshotError as C, getDockerCmd as Cn, StackTerminationProtectionError as Cr, STATE_SOURCED_CROSS_GENERATION_RULES as Ct, extractDeploymentEventError as D, AssetManifestLoader as Dn, isCdkdError as Dr, dynamicReferenceTokens as Dt, makeCanonicalizePropertiesFn as E, runDockerStreaming as En, formatError as Er, createSecretMasker as Et, green as F, resolveCaptureObservedState as Fn, producerRegionsFromState as Ft, collectInlinePolicyNamesManagedBySiblings as G, findLargeInlineResources as Gn, withRetry as Gt, secretBearingStateKeyWarning as H, CFN_TEMPLATE_BODY_LIMIT as Hn, DiffCalculator as Ht, red as I, resolveSkipPrefix as In, s3BucketArn as It, findActionableSilentDrops as J, PARTITION_TABLE as Jn, LockManager as Jt, clearOnUpdateRemoval as K, uploadCfnTemplate as Kn, DagBuilder as Kt, yellow as L, resolveStateBucketWithDefault as Ln, s3BucketDomainName as Lt, bold as M, getLegacyStateBucketName as Mn, isThrottlingError as Mr, redactSecretsForState as Mt, cyan as N, resolveApp as Nn, markNonRetryable as Nr, scrubResourceRecord as Nt, MULTI_REGION_RECREATE_BLOCKED_TYPES as O, getDockerImageBySourceHash as On, normalizeAwsError as Or, errorCauseChain as Ot, gray as P, resolveAutoAssetStorage as Pn, __exportAll as Pr, classifyReplaySecretRegion as Pt, beginCommandInterruptScope as Q, processStackMessages as Qn, exportNamesCarriedFrom as Qt, collectDeclaredOutputNames as R, resolveStateBucketWithDefaultAndSource as Rn, s3BucketDualStackDomainName as Rt, createPreDeleteFinalSnapshot as S, formatDockerLoginError as Sn, StackHasActiveImportsError as Sr, requireConfigString as St, unsupportedFinalSnapshotError as T, runDockerForeground as Tn, SynthesisError as Tr, TEMPLATE_SOURCED_RULES as Tt, stateKeySecretExposure as U, CFN_TEMPLATE_URL_LIMIT as Un, INTRINSIC_KEYS as Ut, isExportAliasCollision as V, warnDeprecatedNoPrefixCliFlag as Vn, applyRoleArnIfSet as Vt, IAMRoleProvider as W, MIGRATE_TMP_PREFIX as Wn, describeTypeWithThrottleRetry as Wt, CUSTOM_RESOURCE_RESPONSE_PREFIX as X, derivePartitionAndUrlSuffix as Xn, S3StateBackend as Xt, findSilentDropProperties as Y, canonicalizeRegion as Yn, displaySafe as Yt, DEFAULT_STATE_PREFIX as Z, AssemblyReader as Zn, rebuildClientForBucketRegion as Zt, computeImplicitDeleteEdges as _, validateAssetBucketName as _n, NestedStackChildDirectDestroyError as _r, configStringRefusal as _t, DeploymentEventsStore as a, buildAssetRedirectMap as an, AssetError as ar, isTerminationProtectionPropagationError as at, buildFinalSnapshotIdentifier as b, buildDockerImage as bn, ResourceTimeoutError as br, requireConfigArray as bt, replayFailedOperations as c, rewriteTemplateAssetReferences as cn, CrossAccountSecretRefusalError as cr, cfnRefValueFromPhysicalId as ct, updatePartialReason as d, AssetModeResolver as dn, DynamicReferenceRegionAmbiguousError as dr, WAFv2WebACLProvider as dt, importableOutputs as en, resolveBucketRegion as er, isInterruptedWaitError as et, UNSPECIFIED_SKIP_REASON as f, BOOTSTRAP_MARKER_PREFIX as fn, LocalInvokeBuildError as fr, normalizeAwsTagsToCfn as ft, IMPLICIT_DELETE_DEPENDENCIES as g, readBootstrapMarkerBody as gn, MissingCdkCliError as gr, configBooleanRefusal as gt, maskingRetryLogger as h, parseBootstrapMarker as hn, LockError as hr, coerceCfnBoolean as ht, DeploymentEventsReader as i, WorkGraph as in, setAwsClients as ir, disableInstanceApiTermination as it, formatResourceLine as j, getDefaultStateBucketName as jn, isRetryableTransientError as jr, maskSecretsInText as jt, isStatefulRecreateTargetSync as k, Synthesizer as kn, withErrorHandling as kr, isSingleDynamicReferenceToken as kt, replayRollback as l, escapeRegExp$1 as ln, DependencyError as lr, getAccountInfo as lt, withResourceDeadline as m, getBootstrapMarkerKey as mn, LocalStartServiceError as mr, assertRegionMatch as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, AssetPublisher as nn, getAwsClients as nr, CloudControlProvider as nt, planFailedOps as o, createAssetRedirectResolver as on, CdkdError as or, IntrinsicFunctionResolver as ot, deleteSkipReason as p, ensureAssetStorage as pn, LocalMigrateError as pr, resolveExplicitPhysicalId as pt, ProviderRegistry as q, expectedOwnerParam as qn, TemplateParser as qt, DeployEngine as r, stringifyValue as rn, resetAwsClients as rr, slowCcOperationTimeoutMs as rt, planRollback as s, loadPublishableAssetManifest as sn, ConfigError as sr, carriesDynamicReference as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, shouldRetainResource as tn, AwsClients as tr, startInterruptWatch as tt, updatePartialMessage as u, stripControlChars as un, DeployCancelledError as ur, refStateLookupFromResource as ut, ATOMIC_FINAL_SNAPSHOT_TYPES as v, validateContainerRepoName as vn, PartialFailureError as vr, readConfigString as vt, refusesFinalSnapshot as w, partitionSensitiveEnv as wn, StateError as wr, STATE_SOURCED_READBACK_RULES as wt, ccRoutedFinalSnapshotError as x, dockerSpawnEnvWithSensitive as xn, ResourceUpdateNotSupportedError as xr, requireConfigObject as xt, PRE_DELETE_SNAPSHOT_TYPES as y, buildDenyExternalAccessPolicy as yn, ProvisioningError as yr, replayWarn as yt, collectPublishedOutputNames as z, resolveUseCdkBootstrapAssets as zn, s3BucketRegionalDomainName as zt };
|
|
29875
|
+
//# sourceMappingURL=deploy-engine-B9SrF2-R.js.map
|