@go-to-k/cdkd 0.284.9 → 0.284.11
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-B09ceB1r.js → asg-provider-C9OlqHee.js} +2 -2
- package/dist/{asg-provider-B09ceB1r.js.map → asg-provider-C9OlqHee.js.map} +1 -1
- package/dist/cli.js +241 -10
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-CY2fx4K1.js → deploy-engine-K5kHzsB-.js} +500 -41
- package/dist/deploy-engine-K5kHzsB-.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-CY2fx4K1.js.map +0 -1
|
@@ -10234,6 +10234,14 @@ function clearRecordedSecretExpressions() {
|
|
|
10234
10234
|
* value is still masked at the exact leaf where it was the WHOLE value (handled
|
|
10235
10235
|
* by the caller), but is not scanned for as a substring. Real secrets are far
|
|
10236
10236
|
* longer than this, so the bound only excludes degenerate cases.
|
|
10237
|
+
*
|
|
10238
|
+
* EXPORTED because a caller assembling its own secrets bag may need the same
|
|
10239
|
+
* bound on the WHOLE-VALUE arm, which this module deliberately does not apply
|
|
10240
|
+
* (the no-source arm below matches a whole value at ANY length, which is right
|
|
10241
|
+
* for a POSITION-SCOPED bag). `cdkd scrub`'s cross-resource union has no
|
|
10242
|
+
* position source at all, so it filters itself here before scanning — see
|
|
10243
|
+
* `allRecordedSecrets` in `src/cli/commands/scrub.ts`. Read-only: no behavior
|
|
10244
|
+
* in this module changes with the export.
|
|
10237
10245
|
*/
|
|
10238
10246
|
const MIN_NEEDLE_LENGTH = 4;
|
|
10239
10247
|
function escapeRegExp(value) {
|
|
@@ -10245,7 +10253,7 @@ function escapeRegExp(value) {
|
|
|
10245
10253
|
* `undefined` when there is nothing worth scanning for.
|
|
10246
10254
|
*/
|
|
10247
10255
|
function buildNeedleRegex(values) {
|
|
10248
|
-
const needles = Array.from(new Set(values)).filter((v) => v.length >=
|
|
10256
|
+
const needles = Array.from(new Set(values)).filter((v) => v.length >= 4).sort((a, b) => b.length - a.length);
|
|
10249
10257
|
if (needles.length === 0) return void 0;
|
|
10250
10258
|
return new RegExp(needles.map(escapeRegExp).join("|"), "g");
|
|
10251
10259
|
}
|
|
@@ -16045,7 +16053,7 @@ var CloudControlProvider = class {
|
|
|
16045
16053
|
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);
|
|
16046
16054
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
16047
16055
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
16048
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
16056
|
+
const { ASGProvider } = await import("./asg-provider-C9OlqHee.js").then((n) => n.n);
|
|
16049
16057
|
return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
16050
16058
|
}
|
|
16051
16059
|
const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
|
|
@@ -16733,6 +16741,33 @@ const CR_NO_PROPERTIES_SKIP_REASON = "no properties in state — Delete handler
|
|
|
16733
16741
|
*/
|
|
16734
16742
|
const CR_NO_SERVICE_TOKEN_SKIP_REASON = "no ServiceToken in state — Delete handler not invoked";
|
|
16735
16743
|
/**
|
|
16744
|
+
* Third sibling of the two above, for the arm where cdkd HAD everything it
|
|
16745
|
+
* needed and the Delete request still could not be completed — a permanent
|
|
16746
|
+
* `lambda:InvokeFunction` denial, an exhausted readiness waiter, a response
|
|
16747
|
+
* that never arrived.
|
|
16748
|
+
*
|
|
16749
|
+
* That arm used to swallow the error and return `undefined`, which
|
|
16750
|
+
* `deleteSkipReason` reads as DELETED: `cdkd destroy` printed `✓ … deleted`,
|
|
16751
|
+
* dropped the state record and exited 0 over a handler that never received a
|
|
16752
|
+
* `Delete` — silently orphaning everything that handler manages. It is the
|
|
16753
|
+
* same silent-orphan class issue
|
|
16754
|
+
* [#1752](https://github.com/go-to-k/cdkd/issues/1752) removed from the two
|
|
16755
|
+
* arms above, reached through the catch rather than through a guard.
|
|
16756
|
+
*
|
|
16757
|
+
* **Fixed wording, no interpolation.** The underlying AWS message goes out on
|
|
16758
|
+
* the `logger.warn` beside it and NOT into the reason, because a `reason` is
|
|
16759
|
+
* rendered into the `Error` the deploy-side replacement sites throw, whose
|
|
16760
|
+
* catch classifies an already-deleted resource by SUBSTRING — an AWS message
|
|
16761
|
+
* carrying `does not exist` / `not found` would make a skip read as "already
|
|
16762
|
+
* gone" and drop the record again, one layer further out. Same rule the
|
|
16763
|
+
* `sns-subscription` abort follows.
|
|
16764
|
+
*
|
|
16765
|
+
* The premise is "the resource was NOT destroyed", not "no AWS call was
|
|
16766
|
+
* issued": the handler may have run and failed, or run and had its response
|
|
16767
|
+
* lost. Both leave the resource unproven, which is what a skip asserts.
|
|
16768
|
+
*/
|
|
16769
|
+
const CR_DELETE_INVOKE_FAILED_SKIP_REASON = "Delete request to the handler did not complete — resource unproven";
|
|
16770
|
+
/**
|
|
16736
16771
|
* The deploy-side caveat both skip warnings in this file carry (issue
|
|
16737
16772
|
* [#1762](https://github.com/go-to-k/cdkd/issues/1762)).
|
|
16738
16773
|
*
|
|
@@ -16787,12 +16822,115 @@ function decodeInvokeLogTail(logResult) {
|
|
|
16787
16822
|
}
|
|
16788
16823
|
}
|
|
16789
16824
|
/**
|
|
16825
|
+
* Recover the backing function's own STATUS fields from an
|
|
16826
|
+
* `@smithy/util-waiter` failure message, or `undefined` when they are not
|
|
16827
|
+
* there (issue #2033).
|
|
16828
|
+
*
|
|
16829
|
+
* The message is `JSON.stringify(result)` and `result.reason` is, for both
|
|
16830
|
+
* Lambda readiness waiters, the ENTIRE `GetFunction` response. Only these
|
|
16831
|
+
* AWS-authored status fields are lifted out of it — never the whole payload,
|
|
16832
|
+
* which carries `Configuration.Environment.Variables` into a durable store.
|
|
16833
|
+
*
|
|
16834
|
+
* Best-effort by construction: a non-JSON message, a different waiter shape, or
|
|
16835
|
+
* a payload with no `Configuration` all yield `undefined`, and the caller falls
|
|
16836
|
+
* back to a fixed sentence.
|
|
16837
|
+
*/
|
|
16838
|
+
function extractWaiterFunctionStatus(message) {
|
|
16839
|
+
let parsed;
|
|
16840
|
+
try {
|
|
16841
|
+
parsed = JSON.parse(message);
|
|
16842
|
+
} catch {
|
|
16843
|
+
return;
|
|
16844
|
+
}
|
|
16845
|
+
const config = (parsed?.reason)?.Configuration;
|
|
16846
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) return void 0;
|
|
16847
|
+
const fields = config;
|
|
16848
|
+
const parts = [];
|
|
16849
|
+
for (const key of [
|
|
16850
|
+
"State",
|
|
16851
|
+
"StateReasonCode",
|
|
16852
|
+
"StateReason",
|
|
16853
|
+
"LastUpdateStatus",
|
|
16854
|
+
"LastUpdateStatusReasonCode",
|
|
16855
|
+
"LastUpdateStatusReason"
|
|
16856
|
+
]) {
|
|
16857
|
+
const value = fields[key];
|
|
16858
|
+
if (typeof value === "string" && value !== "") parts.push(`${key}=${value}`);
|
|
16859
|
+
}
|
|
16860
|
+
return parts.length > 0 ? parts.join(", ") : void 0;
|
|
16861
|
+
}
|
|
16862
|
+
/**
|
|
16863
|
+
* Render a Lambda readiness-waiter failure for a message that is persisted
|
|
16864
|
+
* (issue #2033) — see `waitForBackingLambdaReady` for the whole argument.
|
|
16865
|
+
*
|
|
16866
|
+
* TIMEOUT / ABORT keep the waiter's own message: its `observedResponses` keys
|
|
16867
|
+
* are status lines `@smithy/util-waiter` builds itself (`403: <AWS message>`),
|
|
16868
|
+
* which is exactly the diagnostic a stalled waiter needs and carries no
|
|
16869
|
+
* response body. Every other state serialized the full `GetFunction` response,
|
|
16870
|
+
* so that arm reports the error NAME plus the function's own status fields.
|
|
16871
|
+
*/
|
|
16872
|
+
function describeWaiterFailure(error) {
|
|
16873
|
+
const name = error instanceof Error ? error.name : "Error";
|
|
16874
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16875
|
+
if (name === "TimeoutError" || name === "AbortError") return message;
|
|
16876
|
+
return `${name} (${extractWaiterFunctionStatus(message) ?? "no function status reported"}). The waiter's raw payload is withheld because it embeds the whole GetFunction response, environment variables included; run \`aws lambda get-function\` for the detail.`;
|
|
16877
|
+
}
|
|
16878
|
+
/**
|
|
16790
16879
|
* IAM-authorization-propagation signals in a custom resource FAILED reason that
|
|
16791
16880
|
* indicate the backing Lambda's freshly-attached execution-role policy has not
|
|
16792
16881
|
* yet taken effect for its assumed-role session (so a recycle + retry will
|
|
16793
16882
|
* succeed once IAM settles). Lowercase substrings. Intentionally narrow — these
|
|
16794
16883
|
* are the IAM-permission-not-yet-effective phrases only, NOT generic transient
|
|
16795
16884
|
* errors (throttling / timeouts), which must not trigger a CR re-invoke.
|
|
16885
|
+
*
|
|
16886
|
+
* **This set is deliberately NARROWER than
|
|
16887
|
+
* `IAM_PROPAGATION_ERROR_MESSAGE_PATTERNS` (`src/deployment/retryable-errors.ts`)
|
|
16888
|
+
* and stays that way** (issue
|
|
16889
|
+
* [#2033](https://github.com/go-to-k/cdkd/issues/2033), which asked whether the
|
|
16890
|
+
* narrowing was still intended or a list that had stopped tracking its
|
|
16891
|
+
* counterpart).
|
|
16892
|
+
*
|
|
16893
|
+
* "Narrower", not "a subset" — an earlier revision of this comment said SUBSET
|
|
16894
|
+
* and that was FALSE of the list beside it. Three of these six entries appear
|
|
16895
|
+
* in no form in the shared list (`no identity-based policy allows`, both
|
|
16896
|
+
* `not in the state functionActive` spellings), and a fourth appears there only
|
|
16897
|
+
* ANCHORED: the shared list carries `Firehose is unable to assume role` /
|
|
16898
|
+
* `is unable to assume provided role` / `is unable to assume the role` and
|
|
16899
|
+
* deliberately refuses the bare `is unable to assume` this list uses, so that a
|
|
16900
|
+
* permanent `... is unable to assume role X because of an explicit deny` cannot
|
|
16901
|
+
* burn a retry budget. The bare spelling is right HERE — the text is the
|
|
16902
|
+
* handler's own reason about the race cdkd created — and wrong for AWS-authored
|
|
16903
|
+
* text about a call cdkd made, which is why
|
|
16904
|
+
* {@link CR_THROWN_AUTHZ_EXTRA_SIGNALS} does not re-export it.
|
|
16905
|
+
*
|
|
16906
|
+
* The narrowing is intended, because the two lists are consumed under different
|
|
16907
|
+
* COSTS and classify text with different AUTHORS:
|
|
16908
|
+
*
|
|
16909
|
+
* - This set is matched against the HANDLER's own FAILED `Reason` (and, since
|
|
16910
|
+
* #1674, against arbitrary handler stdout in the log tail). A match here buys
|
|
16911
|
+
* a re-INVOKE, which re-runs the user's `Create` — a non-idempotent handler
|
|
16912
|
+
* repeats partial work, and a Provider-framework `onEvent` can create a
|
|
16913
|
+
* SECOND physical resource and orphan the first (the accepted cost stated on
|
|
16914
|
+
* {@link CR_TRANSIENT_AUTHZ_LOG_SIGNALS}). So the phrases must name the race
|
|
16915
|
+
* cdkd ITSELF created — the backing function's freshly-attached execution
|
|
16916
|
+
* role — and nothing else. Most of the superset's entries describe a
|
|
16917
|
+
* DOWNSTREAM call the handler made (`Invalid principal in policy`,
|
|
16918
|
+
* `Cannot access stream`, `KMS key is invalid for CreateGrant`,
|
|
16919
|
+
* `Invalid InstanceProfile`, …); a re-invoke is not the remedy for those, so
|
|
16920
|
+
* each one would buy a recycle plus an identical re-failure. The three the
|
|
16921
|
+
* issue named specifically — `role defined for the function`, `trust policy`,
|
|
16922
|
+
* `Invalid principal in policy` — are exactly that shape when they arrive in
|
|
16923
|
+
* a handler-authored reason, and the first two are ALREADY covered here in
|
|
16924
|
+
* the spelling that matters (`cannot be assumed` / `is unable to assume` are
|
|
16925
|
+
* what Lambda emits for an unassumable execution role).
|
|
16926
|
+
* - The shared list is matched against text AWS wrote about a call CDKD made.
|
|
16927
|
+
* It is used, in full, by
|
|
16928
|
+
* {@link CustomResourceProvider.isTransientAuthzThrow} for a THROWN error
|
|
16929
|
+
* from one of the provider's OWN SDK calls — see that method for why the
|
|
16930
|
+
* wider list is correct there and costs nothing extra.
|
|
16931
|
+
*
|
|
16932
|
+
* So the answer to "should these converge" is no; what was genuinely missing was
|
|
16933
|
+
* the second consumer, not a wider first one.
|
|
16796
16934
|
*/
|
|
16797
16935
|
const CR_TRANSIENT_AUTHZ_SIGNALS = [
|
|
16798
16936
|
"not authorized to perform",
|
|
@@ -16803,6 +16941,27 @@ const CR_TRANSIENT_AUTHZ_SIGNALS = [
|
|
|
16803
16941
|
"is unable to assume"
|
|
16804
16942
|
];
|
|
16805
16943
|
/**
|
|
16944
|
+
* The CR-specific spellings {@link CustomResourceProvider.isTransientAuthzThrow}
|
|
16945
|
+
* adds ON TOP of `IAM_PROPAGATION_ERROR_MESSAGE_PATTERNS` — i.e. exactly the
|
|
16946
|
+
* phrases the shared list does not carry in any form (issue #2033).
|
|
16947
|
+
*
|
|
16948
|
+
* Deliberately NOT `CR_TRANSIENT_AUTHZ_SIGNALS` itself, which is what the first
|
|
16949
|
+
* cut of the fix used. Three of that list's entries are already covered by the
|
|
16950
|
+
* shared list (`not authorized to perform` / `cannot be assumed`, plus the three
|
|
16951
|
+
* ANCHORED `unable to assume` spellings), so re-uniting the whole thing bought
|
|
16952
|
+
* nothing except the bare, UN-anchored `is unable to assume` — which the shared
|
|
16953
|
+
* list refuses on purpose so a permanent explicit-deny cannot spend a 47.75s
|
|
16954
|
+
* budget before failing. This list is the difference, and only the difference.
|
|
16955
|
+
*
|
|
16956
|
+
* Lower-cased substrings, matched against a lower-cased message (the shared list
|
|
16957
|
+
* is mixed-case and matched verbatim by `isIamPropagationError`).
|
|
16958
|
+
*
|
|
16959
|
+
* `is not in the state functionactive` from the sibling list is omitted as a
|
|
16960
|
+
* pure superstring of the entry below it: any message matching it matches this
|
|
16961
|
+
* one too.
|
|
16962
|
+
*/
|
|
16963
|
+
const CR_THROWN_AUTHZ_EXTRA_SIGNALS = ["no identity-based policy allows", "not in the state functionactive"];
|
|
16964
|
+
/**
|
|
16806
16965
|
* The same IAM-authorization-propagation signals, matched against the backing
|
|
16807
16966
|
* function's INVOCATION LOG TAIL rather than the FAILED reason (issue #1674).
|
|
16808
16967
|
*
|
|
@@ -16863,6 +17022,37 @@ const CR_TRANSIENT_AUTHZ_LOG_SIGNALS = [
|
|
|
16863
17022
|
* at 4 KB regardless, so this only trims the extreme case.
|
|
16864
17023
|
*/
|
|
16865
17024
|
const CR_LOG_TAIL_WARN_MAX_CHARS = 2e3;
|
|
17025
|
+
/** Default for `CDKD_CR_AUTHZ_MAX_RETRIES` — see `transientAuthzMaxRetries`. */
|
|
17026
|
+
const CR_AUTHZ_MAX_RETRIES_DEFAULT = 2;
|
|
17027
|
+
/**
|
|
17028
|
+
* Hard ceiling for `CDKD_CR_AUTHZ_MAX_RETRIES`.
|
|
17029
|
+
*
|
|
17030
|
+
* The knob's units are RE-INVOCATIONS OF THE USER'S HANDLER, each one also
|
|
17031
|
+
* paying a `recycleBackingFunctionExecEnv` (an `UpdateFunctionConfiguration`
|
|
17032
|
+
* plus a 120s waiter). Ten of those is already far past the point where an
|
|
17033
|
+
* IAM-propagation race would have settled, so anything above it is a typo or a
|
|
17034
|
+
* misunderstanding rather than a preference — and left unclamped a `1e9`
|
|
17035
|
+
* passes the finite / `>= 0` gate and re-invokes until the deploy engine's
|
|
17036
|
+
* per-resource deadline fires an hour later.
|
|
17037
|
+
*/
|
|
17038
|
+
const CR_AUTHZ_MAX_RETRIES_CEILING = 10;
|
|
17039
|
+
/**
|
|
17040
|
+
* Bound for the `.cause` walks in this file, matching the depth
|
|
17041
|
+
* `isMarkedNonRetryable` / `isThrottlingError` use in
|
|
17042
|
+
* `src/deployment/retryable-errors.ts`. Bounded rather than unbounded so a
|
|
17043
|
+
* cyclic chain cannot hang the classifier.
|
|
17044
|
+
*/
|
|
17045
|
+
const CR_ERROR_CAUSE_MAX_DEPTH = 5;
|
|
17046
|
+
/**
|
|
17047
|
+
* Sleep seam for this provider's hand-rolled waits (the pre-delivery retry
|
|
17048
|
+
* backoff and the S3 response poll).
|
|
17049
|
+
*
|
|
17050
|
+
* Mutable module state ONLY so tests can run a 47.75s retry schedule without
|
|
17051
|
+
* spending 47.75s; production never reassigns it. Mirrors the
|
|
17052
|
+
* `deleteTableRetryDelays.sleep` seam the DynamoDB providers use, and the
|
|
17053
|
+
* `sleep` option `withRetry` already exposes for the same reason.
|
|
17054
|
+
*/
|
|
17055
|
+
const customResourceRetryDelays = { sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)) };
|
|
16866
17056
|
/**
|
|
16867
17057
|
* Lines Lambda emits for EVERY invocation regardless of what the handler logged.
|
|
16868
17058
|
* A tail consisting only of these carries no diagnostic value, and it is the
|
|
@@ -17002,6 +17192,24 @@ var CustomResourceProvider = class CustomResourceProvider {
|
|
|
17002
17192
|
* exponential backoff for async patterns (CDK Provider framework with
|
|
17003
17193
|
* isCompleteHandler), so an outer retry adds nothing but the multi-
|
|
17004
17194
|
* key bug.
|
|
17195
|
+
*
|
|
17196
|
+
* **Opting out of the outer loop is a promise to retry HERE, and that
|
|
17197
|
+
* promise is kept PER CALL — not per attempt.** Issue
|
|
17198
|
+
* [#2033](https://github.com/go-to-k/cdkd/issues/2033) found the claim
|
|
17199
|
+
* backed for exactly one error SHAPE: the internal loop keyed only on the
|
|
17200
|
+
* handler's RETURNED `cfnResponse.Status === 'FAILED'`, and its body had no
|
|
17201
|
+
* `try` / `catch` at any point, so a THROWN error from any AWS SDK call the
|
|
17202
|
+
* provider itself makes left `create()` directly and was single-shot — while
|
|
17203
|
+
* every other resource type got 26 retries over 47.75s for the identical
|
|
17204
|
+
* wording. The per-call decision, in one place:
|
|
17205
|
+
*
|
|
17206
|
+
* | call | retried on a throw? | why |
|
|
17207
|
+
* |---|---|---|
|
|
17208
|
+
* | S3 `PutObject` (response-key placeholder) | YES, own `withRetry` (the standard dense propagation schedule, 47.75s) — and its exhausted throw is `markNonRetryable`d so the loop below cannot spend a SECOND budget on it | idempotent PUT of an empty object at a key cdkd just minted; touches no response-URL lifecycle, so a replay is free |
|
|
17209
|
+
* | Lambda `Invoke` / SNS `Publish` | YES, but only PRE-DELIVERY, on {@link CustomResourceProvider.preDeliveryAuthzMaxRetries} — its OWN budget, the same dense 47.75s schedule every other resource type gets | a replay re-delivers the request, which is the hazard this flag exists for, so the PRE-delivery fence is what makes the budget affordable: nothing has been delivered, so a replay re-invokes NOTHING — see {@link CustomResourceProvider.isTransientAuthzThrow} |
|
|
17210
|
+
* | `waitUntilFunctionActiveV2` / `waitUntilFunctionUpdatedV2` | ALREADY, by the SDK waiter — and their wrapped failure is `markNonRetryable`d, so the loop below does not replay it either | measured against `@aws-sdk/client-lambda`: the generated `checkState` catches EVERY exception and returns `RETRY`, so a mid-propagation 403 on `lambda:GetFunction` is polled out to `maxWaitTime` (600s). Wrapping them again would only stack a second budget on top — and `@smithy/util-waiter` serializes its `observedResponses` into the TIMEOUT message, whose keys read `403: User: … is not authorized to perform: lambda:GetFunction …`, so a classifier reading that message would have replayed a PERMANENT denial for 3 x 600s |
|
|
17211
|
+
* | `GetFunction` (delete-path backing-Lambda probe) | NO, deliberately | it already fails OPEN — anything but a definitive `ResourceNotFoundException` falls through to the normal invoke path, whose waiters cover the same propagation window one call later. Retrying would only delay that fall-through by up to 47.75s on a genuine permission denial |
|
|
17212
|
+
* | anything AFTER delivery (`pollS3Response`, the `FunctionError` throw, `cleanupResponseObject`) | NO, deliberately | the handler is running and will PUT to the URL of THIS attempt; a replay strands it at a key nobody polls, which is precisely the bug `disableOuterRetry` prevents |
|
|
17005
17213
|
*/
|
|
17006
17214
|
disableOuterRetry = true;
|
|
17007
17215
|
/** Max time to wait for synchronous S3 response after Lambda invocation (30 seconds) */
|
|
@@ -17015,8 +17223,13 @@ var CustomResourceProvider = class CustomResourceProvider {
|
|
|
17015
17223
|
/** Max poll interval for async polling with exponential backoff (30 seconds) */
|
|
17016
17224
|
MAX_POLL_INTERVAL_MS = 3e4;
|
|
17017
17225
|
/**
|
|
17018
|
-
* How many extra times to
|
|
17019
|
-
* FAILED with a *transient IAM-authorization* reason
|
|
17226
|
+
* How many extra times to RE-INVOKE a custom resource whose handler returned
|
|
17227
|
+
* FAILED with a *transient IAM-authorization* reason.
|
|
17228
|
+
*
|
|
17229
|
+
* TWO error shapes, TWO budgets, deliberately (issue #2033) — this one and
|
|
17230
|
+
* {@link CustomResourceProvider.preDeliveryAuthzMaxRetries}. This budget
|
|
17231
|
+
* governs the shape where the handler ALREADY RAN: it returned FAILED with a
|
|
17232
|
+
* transient-authz reason (e.g. the CDK Provider
|
|
17020
17233
|
* framework's `lambda:GetFunction` / "not in the state functionActive" 403
|
|
17021
17234
|
* when the framework role's freshly-attached inline policy has not yet
|
|
17022
17235
|
* propagated to the assumed-role session). cdkd's fast SDK path invokes the
|
|
@@ -17028,16 +17241,67 @@ var CustomResourceProvider = class CustomResourceProvider {
|
|
|
17028
17241
|
* `disableOuterRetry` to avoid stranding a pre-signed response URL — so we
|
|
17029
17242
|
* retry HERE instead, deriving a fresh response URL + RequestId per attempt
|
|
17030
17243
|
* and recycling the backing function's execution environment between tries).
|
|
17031
|
-
*
|
|
17244
|
+
*
|
|
17245
|
+
* **It is SMALL (2) because every retry it authorises RE-RUNS THE USER'S
|
|
17246
|
+
* HANDLER**, and that is the whole reason the second shape does not share it:
|
|
17247
|
+
* a pre-delivery throw invokes the handler ZERO times, so the argument that
|
|
17248
|
+
* bounds this number does not apply there at all. Sharing it was measured
|
|
17249
|
+
* wrong — 2 retries on the dense schedule is 250ms + 500ms of coverage, i.e.
|
|
17250
|
+
* 0.75s against an IAM-propagation window this repo has measured at 7-12s, so
|
|
17251
|
+
* issue #2033's own scenario still failed with the fix in place.
|
|
17252
|
+
*
|
|
17253
|
+
* Override via `CDKD_CR_AUTHZ_MAX_RETRIES` (clamped to
|
|
17254
|
+
* {@link CR_AUTHZ_MAX_RETRIES_CEILING}). `0` disables the RE-INVOKE only —
|
|
17032
17255
|
* the issue-#1674 log-tail scan and the reason annotation it produces still
|
|
17033
17256
|
* run, because they describe the failure rather than react to it.
|
|
17257
|
+
*
|
|
17258
|
+
* It does NOT disable either of the two retries that cannot reach the
|
|
17259
|
+
* handler: the response-placeholder `PutObject` retry in
|
|
17260
|
+
* `generateResponseURL`, and the pre-delivery arm above. Neither is what this
|
|
17261
|
+
* knob exists to bound — it bounds how many times a user's handler may be
|
|
17262
|
+
* re-run — and a user turning off re-invokes should not thereby lose the
|
|
17263
|
+
* propagation coverage every other resource type gets for free.
|
|
17034
17264
|
*/
|
|
17035
17265
|
transientAuthzMaxRetries = (() => {
|
|
17036
17266
|
const raw = process.env["CDKD_CR_AUTHZ_MAX_RETRIES"];
|
|
17037
|
-
if (raw === void 0 || raw === "") return
|
|
17267
|
+
if (raw === void 0 || raw === "") return CR_AUTHZ_MAX_RETRIES_DEFAULT;
|
|
17038
17268
|
const n = Number(raw);
|
|
17039
|
-
|
|
17269
|
+
if (!Number.isFinite(n) || n < 0) return CR_AUTHZ_MAX_RETRIES_DEFAULT;
|
|
17270
|
+
const clamped = Math.min(Math.floor(n), CR_AUTHZ_MAX_RETRIES_CEILING);
|
|
17271
|
+
if (clamped !== n) this.logger.warn(`CDKD_CR_AUTHZ_MAX_RETRIES=${raw} is out of range; using ${clamped} (whole numbers, at most ${CR_AUTHZ_MAX_RETRIES_CEILING} — each retry re-invokes the custom resource handler).`);
|
|
17272
|
+
return clamped;
|
|
17040
17273
|
})();
|
|
17274
|
+
/**
|
|
17275
|
+
* Budget for the PRE-DELIVERY thrown arm — a transient IAM-authorization
|
|
17276
|
+
* error thrown by the `Invoke` / `Publish` itself BEFORE the request reached
|
|
17277
|
+
* the handler (issue #2033). The reported shape is an `AccessDeniedException`
|
|
17278
|
+
* on `lambda:InvokeFunction` while the DEPLOYING principal's own
|
|
17279
|
+
* freshly-attached policy is still propagating.
|
|
17280
|
+
*
|
|
17281
|
+
* It is {@link IAM_PROPAGATION_MAX_RETRIES} — the same 26 retries over 47.75s
|
|
17282
|
+
* that `withRetry` gives every other resource type for the identical wording,
|
|
17283
|
+
* on the same dense schedule ({@link IAM_PROPAGATION_INITIAL_DELAY_MS}
|
|
17284
|
+
* doubling to {@link IAM_PROPAGATION_MAX_DELAY_MS}). The measured window this
|
|
17285
|
+
* has to cover is 7-12s; the FAILED-response budget's 0.75s does not.
|
|
17286
|
+
*
|
|
17287
|
+
* **Why it can afford that while its sibling cannot**: it fires only when
|
|
17288
|
+
* `delivered === false`, so the handler has been invoked ZERO times and a
|
|
17289
|
+
* replay re-runs NOTHING — no partial work repeated, no second physical
|
|
17290
|
+
* resource from a Provider-framework `onEvent`, no stranded response URL. The
|
|
17291
|
+
* only cost of a retry here is one `PutObject` + one presign, and the
|
|
17292
|
+
* abandoned placeholder is swept before the next attempt. So the constraint
|
|
17293
|
+
* that keeps `transientAuthzMaxRetries` at 2 is simply absent, and matching
|
|
17294
|
+
* every other resource type is the correct answer instead.
|
|
17295
|
+
*
|
|
17296
|
+
* A thrown retry also skips the exec-env recycle: the denial is on CDKD's own
|
|
17297
|
+
* principal, not on the backing function's role, so there is no warm
|
|
17298
|
+
* container holding stale credentials to invalidate.
|
|
17299
|
+
*
|
|
17300
|
+
* Deliberately NOT overridable by an env var. It bounds no handler
|
|
17301
|
+
* invocation, so there is nothing for a user to trade off — the same reason
|
|
17302
|
+
* the placeholder `PutObject`'s `withRetry` takes no knob either.
|
|
17303
|
+
*/
|
|
17304
|
+
preDeliveryAuthzMaxRetries = 26;
|
|
17041
17305
|
constructor(config) {
|
|
17042
17306
|
const awsClients = getAwsClients();
|
|
17043
17307
|
this.lambdaClient = awsClients.lambda;
|
|
@@ -17244,7 +17508,11 @@ var CustomResourceProvider = class CustomResourceProvider {
|
|
|
17244
17508
|
if (cfnResponse.Status === "FAILED") this.logger.warn(`Custom resource delete handler returned FAILED for ${logicalId}: ${cfnResponse.Reason || "Unknown reason"}`);
|
|
17245
17509
|
else this.logger.debug(`Successfully deleted custom resource ${logicalId}`);
|
|
17246
17510
|
} catch (error) {
|
|
17247
|
-
this.logger.warn(`Failed to delete custom resource ${logicalId}, but continuing: ${error instanceof Error ? error.message : String(error)}`);
|
|
17511
|
+
this.logger.warn(`Failed to delete custom resource ${logicalId}, but continuing: ${error instanceof Error ? error.message : String(error)}. The Delete handler did not complete, so anything this custom resource manages may still be LIVE — cdkd is KEEPING the state record so a re-run can retry it. ${DEPLOY_SKIP_CAVEAT}`);
|
|
17512
|
+
return {
|
|
17513
|
+
outcome: "skipped",
|
|
17514
|
+
reason: CR_DELETE_INVOKE_FAILED_SKIP_REASON
|
|
17515
|
+
};
|
|
17248
17516
|
}
|
|
17249
17517
|
}
|
|
17250
17518
|
/**
|
|
@@ -17301,31 +17569,71 @@ var CustomResourceProvider = class CustomResourceProvider {
|
|
|
17301
17569
|
* `prepareInvocation()`) and recycling the backing function's execution
|
|
17302
17570
|
* environment between tries so its next cold start re-assumes the role.
|
|
17303
17571
|
*
|
|
17572
|
+
* **The loop covers TWO error shapes** (issue #2033). The original one is the
|
|
17573
|
+
* handler's RETURNED `Status: 'FAILED'`. The second is an error THROWN by one
|
|
17574
|
+
* of the provider's OWN SDK calls before the request reached the handler —
|
|
17575
|
+
* which used to leave `create()` directly, because the loop body had no
|
|
17576
|
+
* `try` / `catch` at any point, making every such call single-shot while every
|
|
17577
|
+
* other resource type got the outer `withRetry`'s 26 attempts for the identical
|
|
17578
|
+
* wording. The `delivered` flag below is what keeps the second arm honest: it
|
|
17579
|
+
* flips the moment `Invoke` / `Publish` RETURNS, and a throw after that point
|
|
17580
|
+
* is rethrown untouched no matter how transient it reads, because the handler
|
|
17581
|
+
* is running and will PUT to THIS attempt's response URL. See
|
|
17582
|
+
* {@link CustomResourceProvider.disableOuterRetry} for the whole per-call
|
|
17583
|
+
* table, and {@link CustomResourceProvider.isTransientAuthzThrow} for why a
|
|
17584
|
+
* PRE-delivery throw is safe to replay at all.
|
|
17585
|
+
*
|
|
17304
17586
|
* `buildRequest` is called once per attempt with the fresh invocation so the
|
|
17305
17587
|
* CFn request body always carries the matching ResponseURL / RequestId.
|
|
17306
17588
|
* Returns the final response; the caller decides what a terminal FAILED means
|
|
17307
17589
|
* (create/update throw, delete warns-and-continues).
|
|
17308
17590
|
*/
|
|
17309
17591
|
async invokeCustomResourceWithRetry(serviceToken, logicalId, operation, buildRequest) {
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
-
|
|
17318
|
-
|
|
17319
|
-
|
|
17320
|
-
|
|
17321
|
-
|
|
17592
|
+
const watch = this.startInterruptWatch(logicalId);
|
|
17593
|
+
try {
|
|
17594
|
+
let preDeliveryRetries = 0;
|
|
17595
|
+
let failedResponseRetries = 0;
|
|
17596
|
+
for (let attempt = 0;; attempt++) {
|
|
17597
|
+
let delivered = false;
|
|
17598
|
+
let cfnResponse;
|
|
17599
|
+
let logResult;
|
|
17600
|
+
let invocation;
|
|
17601
|
+
try {
|
|
17602
|
+
invocation = await this.prepareInvocation(logicalId, watch);
|
|
17603
|
+
const request = buildRequest(invocation);
|
|
17604
|
+
this.logger.debug(`Sending custom resource ${operation.toLowerCase()} request: ${serviceToken}`);
|
|
17605
|
+
const sent = await this.sendRequest(serviceToken, request, invocation.responseKey, logicalId, operation, () => {
|
|
17606
|
+
delivered = true;
|
|
17607
|
+
});
|
|
17608
|
+
cfnResponse = sent.response;
|
|
17609
|
+
logResult = sent.logResult;
|
|
17610
|
+
} catch (error) {
|
|
17611
|
+
if (delivered || preDeliveryRetries >= this.preDeliveryAuthzMaxRetries || !this.isTransientAuthzThrow(error)) throw error;
|
|
17612
|
+
const delayMs = Math.min(250 * Math.pow(2, preDeliveryRetries), IAM_PROPAGATION_MAX_DELAY_MS);
|
|
17613
|
+
this.logger.warn(`Custom resource ${operation} for ${logicalId} hit a transient IAM-authorization error before the request was delivered (attempt ${attempt + 1}/${this.preDeliveryAuthzMaxRetries + 1}): ${this.truncateReason(error instanceof Error ? error.message : String(error))}. Retrying in ${delayMs / 1e3}s with a fresh response URL and RequestId.`);
|
|
17614
|
+
if (invocation !== void 0) await this.cleanupResponseObject(invocation.responseKey);
|
|
17615
|
+
preDeliveryRetries += 1;
|
|
17616
|
+
await this.sleepInterruptibly(delayMs, watch);
|
|
17617
|
+
continue;
|
|
17618
|
+
}
|
|
17619
|
+
const reasonIsAuthz = cfnResponse.Status === "FAILED" && this.isTransientAuthzFailure(cfnResponse.Reason);
|
|
17620
|
+
const logTail = cfnResponse.Status === "FAILED" && !reasonIsAuthz ? decodeInvokeLogTail(logResult) : void 0;
|
|
17621
|
+
const logAuthzMatch = logTail === void 0 ? void 0 : this.findTransientAuthzLogLine(logTail);
|
|
17622
|
+
if (cfnResponse.Status === "FAILED" && failedResponseRetries < this.transientAuthzMaxRetries && (reasonIsAuthz || logAuthzMatch !== void 0)) {
|
|
17623
|
+
failedResponseRetries += 1;
|
|
17624
|
+
this.logger.warn(`Custom resource ${operation} for ${logicalId} returned a transient IAM-authorization FAILED (attempt ${attempt + 1}/${this.transientAuthzMaxRetries + 1}): ${this.truncateReason(cfnResponse.Reason)}. ` + (logAuthzMatch === void 0 ? "" : `The handler's reason carried no authorization wording; the denial was found in the backing function's log: ${this.truncateReason(logAuthzMatch.line)}. `) + `Recycling the backing function's execution environment and retrying so its next cold start picks up the propagated policy.`);
|
|
17625
|
+
await this.recycleBackingFunctionExecEnv(serviceToken, logicalId);
|
|
17626
|
+
continue;
|
|
17627
|
+
}
|
|
17628
|
+
if (cfnResponse.Status === "FAILED" && logAuthzMatch !== void 0) return {
|
|
17629
|
+
...cfnResponse,
|
|
17630
|
+
Reason: `${cfnResponse.Reason ?? "Unknown reason"} [cdkd: the reason carried no authorization wording, but the backing function's invocation log matched the IAM-authorization signal "${logAuthzMatch.signal}" — see the cdkd warning for the log line, or the function's CloudWatch log group]`
|
|
17631
|
+
};
|
|
17632
|
+
if (logTail !== void 0 && hasHandlerLogOutput(logTail)) this.logger.warn(`Custom resource ${operation} for ${logicalId} failed and cdkd could not classify the reason. Log tail from the DISPATCH invocation (for the CDK Provider framework's async pattern the failure may have occurred in a later execution, whose log this is not):\n` + this.truncateReason(logTail, CR_LOG_TAIL_WARN_MAX_CHARS));
|
|
17633
|
+
return cfnResponse;
|
|
17322
17634
|
}
|
|
17323
|
-
|
|
17324
|
-
|
|
17325
|
-
Reason: `${cfnResponse.Reason ?? "Unknown reason"} [cdkd: the reason carried no authorization wording, but the backing function's invocation log matched the IAM-authorization signal "${logAuthzMatch.signal}" — see the cdkd warning for the log line, or the function's CloudWatch log group]`
|
|
17326
|
-
};
|
|
17327
|
-
if (logTail !== void 0 && hasHandlerLogOutput(logTail)) this.logger.warn(`Custom resource ${operation} for ${logicalId} failed and cdkd could not classify the reason. Log tail from the DISPATCH invocation (for the CDK Provider framework's async pattern the failure may have occurred in a later execution, whose log this is not):\n` + this.truncateReason(logTail, CR_LOG_TAIL_WARN_MAX_CHARS));
|
|
17328
|
-
return cfnResponse;
|
|
17635
|
+
} finally {
|
|
17636
|
+
watch.dispose();
|
|
17329
17637
|
}
|
|
17330
17638
|
}
|
|
17331
17639
|
/**
|
|
@@ -17347,6 +17655,63 @@ var CustomResourceProvider = class CustomResourceProvider {
|
|
|
17347
17655
|
return CR_TRANSIENT_AUTHZ_SIGNALS.some((p) => lower.includes(p));
|
|
17348
17656
|
}
|
|
17349
17657
|
/**
|
|
17658
|
+
* Classify an error THROWN by one of the provider's own SDK calls as a
|
|
17659
|
+
* transient IAM-authorization race worth replaying (issue #2033).
|
|
17660
|
+
*
|
|
17661
|
+
* The list is cdkd's shared `IAM_PROPAGATION_ERROR_MESSAGE_PATTERNS` — the
|
|
17662
|
+
* same one every other resource type is classified with, via `withRetry` —
|
|
17663
|
+
* plus {@link CR_THROWN_AUTHZ_EXTRA_SIGNALS}, the two CR-specific spellings
|
|
17664
|
+
* the shared list does not carry in any form. That is deliberately WIDER than
|
|
17665
|
+
* the FAILED-reason classifier, and the asymmetry is the whole point: this
|
|
17666
|
+
* text was written by AWS about a call CDKD made, whereas a FAILED reason was
|
|
17667
|
+
* written by the user's handler about a call IT made. See
|
|
17668
|
+
* {@link CR_TRANSIENT_AUTHZ_SIGNALS} for the full argument, including why the
|
|
17669
|
+
* bare `is unable to assume` spelling stays on that side of the line.
|
|
17670
|
+
*
|
|
17671
|
+
* **Two REFUSALS come before any pattern**, and each closes a way for a
|
|
17672
|
+
* single call to be given two retry budgets:
|
|
17673
|
+
*
|
|
17674
|
+
* - `isMarkedNonRetryable` — cdkd's own declaration that this raising cannot
|
|
17675
|
+
* succeed on a replay. Both of the provider's already-retried calls stamp
|
|
17676
|
+
* it: the placeholder `PutObject` after its `withRetry` is exhausted, and
|
|
17677
|
+
* `waitForBackingLambdaReady` after the SDK waiter has polled for its full
|
|
17678
|
+
* 600s. The marker rather than the wording is what makes the second one
|
|
17679
|
+
* sound: `@smithy/util-waiter` serializes `observedResponses` into its
|
|
17680
|
+
* TIMEOUT message and those keys read
|
|
17681
|
+
* `403: User: … is not authorized to perform: lambda:GetFunction …`, so a
|
|
17682
|
+
* PERMANENT denial matched every pattern here and a message-shaped fence
|
|
17683
|
+
* would be one AWS wording change from failing open. It also refuses a
|
|
17684
|
+
* cdkd-authored REFUSAL whose text happens to carry an authz phrase.
|
|
17685
|
+
* - the `.cause` chain is WALKED (bounded), matching `isThrottlingError` /
|
|
17686
|
+
* `isMarkedNonRetryable`. cdkd wraps SDK errors routinely, and issue #2040
|
|
17687
|
+
* documents the drop-`cause` class in this very directory; a top-level-only
|
|
17688
|
+
* read would silently un-retry a wrapped propagation denial.
|
|
17689
|
+
*
|
|
17690
|
+
* **Every pattern in that union is an AUTHORIZATION or REQUEST-VALIDATION
|
|
17691
|
+
* rejection, and that is what makes replaying an `Invoke` safe here.** Such a
|
|
17692
|
+
* rejection is decided at the API front door, before any execution environment
|
|
17693
|
+
* is engaged, so the handler provably did not run and a replay cannot
|
|
17694
|
+
* re-deliver work — the hazard `disableOuterRetry` exists for. It is also why
|
|
17695
|
+
* this classifier deliberately does NOT reach for the broader
|
|
17696
|
+
* `isRetryableTransientError`: a throttle, an HTTP 5xx or a socket timeout can
|
|
17697
|
+
* each arrive AFTER the request was accepted, so replaying one could invoke a
|
|
17698
|
+
* non-idempotent handler twice. Those classes stay single-shot on this path by
|
|
17699
|
+
* design, and the caller's `delivered` flag is the second, independent fence.
|
|
17700
|
+
*/
|
|
17701
|
+
isTransientAuthzThrow(error) {
|
|
17702
|
+
if (isMarkedNonRetryable(error)) return false;
|
|
17703
|
+
let current = error;
|
|
17704
|
+
for (let depth = 0; depth < CR_ERROR_CAUSE_MAX_DEPTH && current != null; depth++) {
|
|
17705
|
+
const message = current instanceof Error ? current.message : typeof current === "string" ? current : "";
|
|
17706
|
+
if (message !== "") {
|
|
17707
|
+
const lower = message.toLowerCase();
|
|
17708
|
+
if (isIamPropagationError(message) || CR_THROWN_AUTHZ_EXTRA_SIGNALS.some((p) => lower.includes(p))) return true;
|
|
17709
|
+
}
|
|
17710
|
+
current = current.cause;
|
|
17711
|
+
}
|
|
17712
|
+
return false;
|
|
17713
|
+
}
|
|
17714
|
+
/**
|
|
17350
17715
|
* Find the IAM-authorization denial inside a backing function's invocation
|
|
17351
17716
|
* log tail, for the case the FAILED reason itself carries none (issue #1674).
|
|
17352
17717
|
*
|
|
@@ -17432,15 +17797,25 @@ var CustomResourceProvider = class CustomResourceProvider {
|
|
|
17432
17797
|
* erased from the FAILED reason (issue #1674). Returned UNDECODED so the
|
|
17433
17798
|
* happy path pays nothing — only a FAILED whose reason missed decodes it.
|
|
17434
17799
|
* Absent on the SNS path: there is no Lambda invoke to attach a log to.
|
|
17800
|
+
*
|
|
17801
|
+
* `onDelivered` is invoked exactly once, the moment the `Invoke` / `Publish`
|
|
17802
|
+
* RETURNS — i.e. at the point after which a replay would re-deliver the
|
|
17803
|
+
* request to the handler and strand this attempt's pre-signed response URL.
|
|
17804
|
+
* The caller's retry-on-throw arm is fenced on it (issue #2033). Deliberately
|
|
17805
|
+
* called AFTER the send rather than before: a rejection at the API front door
|
|
17806
|
+
* (the IAM-propagation class this arm exists for) means the handler never ran,
|
|
17807
|
+
* and treating that as delivered would leave the reported failure single-shot.
|
|
17435
17808
|
*/
|
|
17436
|
-
async sendRequest(serviceToken, request, responseKey, logicalId, operation) {
|
|
17809
|
+
async sendRequest(serviceToken, request, responseKey, logicalId, operation, onDelivered) {
|
|
17437
17810
|
if (this.isSnsServiceToken(serviceToken)) {
|
|
17438
17811
|
this.logger.debug(`ServiceToken is SNS topic, publishing to: ${serviceToken}`);
|
|
17439
17812
|
await this.publishToSns(serviceToken, request);
|
|
17813
|
+
onDelivered();
|
|
17440
17814
|
return { response: await this.pollS3Response(responseKey, logicalId, operation) };
|
|
17441
17815
|
}
|
|
17442
17816
|
await this.waitForBackingLambdaReady(serviceToken, logicalId);
|
|
17443
17817
|
const invokeResponse = await this.invokeLambda(serviceToken, request);
|
|
17818
|
+
onDelivered();
|
|
17444
17819
|
return {
|
|
17445
17820
|
response: await this.getCustomResourceResponse(invokeResponse, responseKey, logicalId, operation),
|
|
17446
17821
|
...invokeResponse.LogResult === void 0 ? {} : { logResult: invokeResponse.LogResult }
|
|
@@ -17473,6 +17848,37 @@ var CustomResourceProvider = class CustomResourceProvider {
|
|
|
17473
17848
|
* deploy engine's per-resource `--resource-timeout` (default 30 min)
|
|
17474
17849
|
* still bounds the outer Custom Resource provisioning attempt, so
|
|
17475
17850
|
* this waiter cap is layered defense, not the only timeout.
|
|
17851
|
+
*
|
|
17852
|
+
* **The wrapped failure is `markNonRetryable`d, and its message is NOT the
|
|
17853
|
+
* waiter's** (issue #2033). Both halves are about `@smithy/util-waiter`'s
|
|
17854
|
+
* `checkExceptions`, which serializes its whole result into the `Error`
|
|
17855
|
+
* message:
|
|
17856
|
+
*
|
|
17857
|
+
* - On TIMEOUT that includes `observedResponses`, whose keys read
|
|
17858
|
+
* `403: User: … is not authorized to perform: lambda:GetFunction on
|
|
17859
|
+
* resource: …`. The waiter's generated `checkState` catches EVERY
|
|
17860
|
+
* exception and returns `RETRY`, so reaching here means a 600s budget was
|
|
17861
|
+
* already spent — yet the message matched
|
|
17862
|
+
* {@link CustomResourceProvider.isTransientAuthzThrow} exactly, so a
|
|
17863
|
+
* PERMANENT `lambda:GetFunction` denial was replayed for 3 x 600s instead
|
|
17864
|
+
* of 10 minutes, blowing any `--resource-timeout
|
|
17865
|
+
* AWS::CloudFormation::CustomResource=15m`. The marker is a property of
|
|
17866
|
+
* the error object, so unlike a wording test it cannot be defeated by AWS
|
|
17867
|
+
* rephrasing the denial.
|
|
17868
|
+
* - On the non-TIMEOUT arm (`State: Failed`, i.e. an ENI / VPC failure) the
|
|
17869
|
+
* serialized result carries `reason` and `final`, which for this waiter
|
|
17870
|
+
* are the ENTIRE `GetFunction` response — `Configuration.Environment.
|
|
17871
|
+
* Variables` included. That message reached `ProvisioningError.message`
|
|
17872
|
+
* and `extractDeploymentEventError` persisted it to
|
|
17873
|
+
* `deployments/{runId}.jsonl`, a durable store that outlives
|
|
17874
|
+
* `cdkd destroy` and is contractually "error + metadata only, never
|
|
17875
|
+
* resource properties, because they may contain secrets"
|
|
17876
|
+
* (`docs/deployment-events.md`). So this arm interpolates the error NAME
|
|
17877
|
+
* plus a fixed sentence, and recovers only the function's own
|
|
17878
|
+
* `State` / `StateReason` / `StateReasonCode` — AWS-authored status
|
|
17879
|
+
* fields — from the serialized payload. The TIMEOUT / ABORT arms keep
|
|
17880
|
+
* their message: `observedResponses` keys are status lines built by
|
|
17881
|
+
* `createMessageFromResponse`, never a response body.
|
|
17476
17882
|
*/
|
|
17477
17883
|
async waitForBackingLambdaReady(serviceToken, logicalId) {
|
|
17478
17884
|
try {
|
|
@@ -17489,7 +17895,7 @@ var CustomResourceProvider = class CustomResourceProvider {
|
|
|
17489
17895
|
maxDelay: 5
|
|
17490
17896
|
}, { FunctionName: serviceToken });
|
|
17491
17897
|
} catch (error) {
|
|
17492
|
-
throw new Error(`Lambda backing custom resource ${logicalId} (${serviceToken}) did not reach a ready state for Invoke: ${error
|
|
17898
|
+
throw markNonRetryable(new Error(`Lambda backing custom resource ${logicalId} (${serviceToken}) did not reach a ready state for Invoke: ${describeWaiterFailure(error)}`, { cause: error }));
|
|
17493
17899
|
}
|
|
17494
17900
|
}
|
|
17495
17901
|
/**
|
|
@@ -17585,28 +17991,38 @@ var CustomResourceProvider = class CustomResourceProvider {
|
|
|
17585
17991
|
* Centralising this in one helper makes that invariant impossible to
|
|
17586
17992
|
* violate at the call sites.
|
|
17587
17993
|
*/
|
|
17588
|
-
async prepareInvocation() {
|
|
17994
|
+
async prepareInvocation(logicalId, watch) {
|
|
17589
17995
|
const requestId = `cdkd-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
17590
17996
|
const responseKey = this.getResponseKey(requestId);
|
|
17591
17997
|
return {
|
|
17592
17998
|
requestId,
|
|
17593
17999
|
responseKey,
|
|
17594
|
-
responseURL: await this.generateResponseURL(responseKey)
|
|
18000
|
+
responseURL: await this.generateResponseURL(responseKey, logicalId, watch)
|
|
17595
18001
|
};
|
|
17596
18002
|
}
|
|
17597
18003
|
/**
|
|
17598
18004
|
* Generate a pre-signed S3 PUT URL for Lambda to send its response
|
|
17599
18005
|
*/
|
|
17600
|
-
async generateResponseURL(responseKey) {
|
|
18006
|
+
async generateResponseURL(responseKey, logicalId, watch) {
|
|
17601
18007
|
if (!this.responseBucket) return "https://localhost/cfn-response-not-configured";
|
|
17602
18008
|
await this.ensureResponseClient();
|
|
17603
|
-
|
|
17604
|
-
|
|
17605
|
-
|
|
17606
|
-
|
|
17607
|
-
|
|
17608
|
-
|
|
17609
|
-
|
|
18009
|
+
const bucket = this.responseBucket;
|
|
18010
|
+
try {
|
|
18011
|
+
await withRetry(() => this.s3Client.send(new PutObjectCommand({
|
|
18012
|
+
Bucket: bucket,
|
|
18013
|
+
Key: responseKey,
|
|
18014
|
+
Body: "",
|
|
18015
|
+
ContentLength: 0,
|
|
18016
|
+
ContentType: "application/json"
|
|
18017
|
+
})), `${logicalId} (custom-resource response placeholder)`, {
|
|
18018
|
+
logger: this.logger,
|
|
18019
|
+
isInterrupted: watch.isInterrupted,
|
|
18020
|
+
onInterrupted: watch.onInterrupted,
|
|
18021
|
+
sleep: customResourceRetryDelays.sleep
|
|
18022
|
+
});
|
|
18023
|
+
} catch (error) {
|
|
18024
|
+
throw markNonRetryable(error instanceof Error ? error : new Error(String(error)));
|
|
18025
|
+
}
|
|
17610
18026
|
const command = new PutObjectCommand({
|
|
17611
18027
|
Bucket: this.responseBucket,
|
|
17612
18028
|
Key: responseKey
|
|
@@ -17717,7 +18133,50 @@ var CustomResourceProvider = class CustomResourceProvider {
|
|
|
17717
18133
|
return result;
|
|
17718
18134
|
}
|
|
17719
18135
|
sleep(ms) {
|
|
17720
|
-
return
|
|
18136
|
+
return customResourceRetryDelays.sleep(ms);
|
|
18137
|
+
}
|
|
18138
|
+
/**
|
|
18139
|
+
* Install a SIGINT watch for one custom-resource invocation (issue #2033).
|
|
18140
|
+
*
|
|
18141
|
+
* `docs/provider-development.md` requires a new `withRetry` to thread
|
|
18142
|
+
* `isInterrupted` / `onInterrupted`, and a hand-rolled backoff to be
|
|
18143
|
+
* interruptible for the same reason: without it Ctrl-C is dead for the whole
|
|
18144
|
+
* 47.75s schedule. The provider already had the shape — `pollS3Response`
|
|
18145
|
+
* installs its own handler — so this is that pattern, lifted so the two new
|
|
18146
|
+
* wait sites share one flag and one disposal.
|
|
18147
|
+
*
|
|
18148
|
+
* The caller MUST `dispose()` in a `finally`; a leaked listener would
|
|
18149
|
+
* accumulate one per resource across a deploy.
|
|
18150
|
+
*/
|
|
18151
|
+
startInterruptWatch(logicalId) {
|
|
18152
|
+
let interrupted = false;
|
|
18153
|
+
const handler = () => {
|
|
18154
|
+
interrupted = true;
|
|
18155
|
+
};
|
|
18156
|
+
process.on("SIGINT", handler);
|
|
18157
|
+
return {
|
|
18158
|
+
isInterrupted: () => interrupted,
|
|
18159
|
+
onInterrupted: () => /* @__PURE__ */ new Error(`Custom resource ${logicalId} interrupted by user`),
|
|
18160
|
+
dispose: () => {
|
|
18161
|
+
process.removeListener("SIGINT", handler);
|
|
18162
|
+
}
|
|
18163
|
+
};
|
|
18164
|
+
}
|
|
18165
|
+
/**
|
|
18166
|
+
* `sleep` that checks the interrupt watch at most a second apart, mirroring
|
|
18167
|
+
* `withRetry`'s own once-per-second probe. Throws the watch's error when the
|
|
18168
|
+
* user has hit Ctrl-C, so the retry loop unwinds instead of sitting out the
|
|
18169
|
+
* remaining backoff.
|
|
18170
|
+
*/
|
|
18171
|
+
async sleepInterruptibly(ms, watch) {
|
|
18172
|
+
let remaining = ms;
|
|
18173
|
+
while (remaining > 0) {
|
|
18174
|
+
if (watch.isInterrupted()) throw watch.onInterrupted();
|
|
18175
|
+
const chunk = Math.min(1e3, remaining);
|
|
18176
|
+
await this.sleep(chunk);
|
|
18177
|
+
remaining -= chunk;
|
|
18178
|
+
}
|
|
18179
|
+
if (watch.isInterrupted()) throw watch.onInterrupted();
|
|
17721
18180
|
}
|
|
17722
18181
|
/**
|
|
17723
18182
|
* Adopt an existing custom resource into cdkd state.
|
|
@@ -23623,7 +24082,7 @@ const FLUSH_INTERVAL_MS = 2e3;
|
|
|
23623
24082
|
const FLUSH_EVENT_THRESHOLD = 50;
|
|
23624
24083
|
/** Build-time cdkd version, with a dev fallback for non-built contexts. */
|
|
23625
24084
|
function getCdkdVersion() {
|
|
23626
|
-
return "0.284.
|
|
24085
|
+
return "0.284.11";
|
|
23627
24086
|
}
|
|
23628
24087
|
/**
|
|
23629
24088
|
* Generate a time-sortable unique run id, e.g.
|
|
@@ -26092,4 +26551,4 @@ var DeployEngine = class {
|
|
|
26092
26551
|
|
|
26093
26552
|
//#endregion
|
|
26094
26553
|
export { IntrinsicFunctionResolver as $, StackHasActiveImportsError as $n, validateContainerRepoName as $t, formatResourceLine as A, AssemblyReader as An, describeTypeWithThrottleRetry as At, isExportAliasCollision as B, ConfigError as Bn, WorkGraph as Bt, refusesFinalSnapshot as C, MIGRATE_TMP_PREFIX as Cn, s3BucketDomainName as Ct, MULTI_REGION_RECREATE_BLOCKED_TYPES as D, PARTITION_TABLE as Dn, applyRoleArnIfSet as Dt, extractDeploymentEventError as E, expectedOwnerParam as En, s3BucketWebsiteUrl as Et, red as F, getAwsClients as Fn, S3StateBackend as Ft, clearOnUpdateRemoval as G, LocalStartServiceError as Gn, escapeRegExp$1 as Gt, stateKeySecretExposure as H, DeployCancelledError as Hn, createAssetRedirectResolver as Ht, yellow as I, resetAwsClients as In, rebuildClientForBucketRegion as It, findSilentDropProperties as J, NestedStackChildDirectDestroyError as Jn, BOOTSTRAP_MARKER_PREFIX as Jt, ProviderRegistry as K, LockError as Kn, stripControlChars as Kt, collectDeclaredOutputNames as L, setAwsClients as Ln, shouldRetainResource as Lt, cyan as M, clearBucketRegionCache as Mn, DagBuilder as Mt, gray as N, resolveBucketRegion as Nn, TemplateParser as Nt, isStatefulRecreateTargetSync as O, canonicalizeRegion as On, DiffCalculator as Ot, green as P, AwsClients as Pn, LockManager as Pt, isTerminationProtectionPropagationError as Q, ResourceUpdateNotSupportedError as Qn, validateAssetBucketName as Qt, collectPublishedOutputNames as R, AssetError as Rn, AssetPublisher as Rt, isFinalSnapshotError as S, CFN_TEMPLATE_URL_LIMIT as Sn, s3BucketArn as St, makeCanonicalizePropertiesFn as T, uploadCfnTemplate as Tn, s3BucketRegionalDomainName as Tt, IAMRoleProvider as U, LocalInvokeBuildError as Un, loadPublishableAssetManifest as Ut, secretBearingStateKeyWarning as V, DependencyError as Vn, buildAssetRedirectMap as Vt, collectInlinePolicyNamesManagedBySiblings as W, LocalMigrateError as Wn, rewriteTemplateAssetReferences as Wt, slowCcOperationTimeoutMs as X, ProvisioningError as Xn, getBootstrapMarkerKey as Xt, CloudControlProvider as Y, PartialFailureError as Yn, ensureAssetStorage as Yt, disableInstanceApiTermination as Z, ResourceTimeoutError as Zn, parseBootstrapMarker as Zt, ATOMIC_FINAL_SNAPSHOT_TYPES as _, resolveStateBucketWithDefaultAndSource as _n, TEMPLATE_SOURCED_RULES as _t, DeploymentEventsStore as a, runDockerStreaming as an, normalizeAwsError as ar, resolveExplicitPhysicalId as at, ccRoutedFinalSnapshotError as b, warnDeprecatedNoPrefixCliFlag as bn, redactSecretsForState as bt, replayFailedOperations as c, Synthesizer as cn, isRetryableTransientError as cr, configBooleanRefusal as ct, updatePartialReason as d, getLegacyStateBucketName as dn, __exportAll as dr, replayWarn as dt, buildDenyExternalAccessPolicy as en, StackTerminationProtectionError as er, cfnRefValueFromPhysicalId as et, UNSPECIFIED_SKIP_REASON as f, resolveApp as fn, requireConfigArray as ft, computeImplicitDeleteEdges as g, resolveStateBucketWithDefault as gn, STATE_SOURCED_READBACK_RULES as gt, IMPLICIT_DELETE_DEPENDENCIES as h, resolveSkipPrefix as hn, STATE_SOURCED_CROSS_GENERATION_RULES as ht, DeploymentEventsReader as i, runDockerForeground as in, isCdkdError as ir, normalizeAwsTagsToCfn as it, bold as j, processStackMessages as jn, withRetry as jt, renderStatefulReason as k, derivePartitionAndUrlSuffix as kn, INTRINSIC_KEYS as kt, replayRollback as l, synthesisStatusMessage as ln, isThrottlingError as lr, configStringRefusal as lt, withResourceDeadline as m, resolveCaptureObservedState as mn, requireConfigString as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, formatDockerLoginError as nn, SynthesisError as nr, refStateLookupFromResource as nt, planFailedOps as o, AssetManifestLoader as on, withErrorHandling as or, assertRegionMatch as ot, deleteSkipReason as p, resolveAutoAssetStorage as pn, requireConfigObject as pt, findActionableSilentDrops as q, MissingCdkCliError as qn, AssetModeResolver as qt, DeployEngine as r, getDockerCmd as rn, formatError as rr, WAFv2WebACLProvider as rt, planRollback as s, getDockerImageBySourceHash as sn, isMarkedNonRetryable as sr, coerceCfnBoolean as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, buildDockerImage as tn, StateError as tr, getAccountInfo as tt, updatePartialMessage as u, getDefaultStateBucketName as un, markNonRetryable as ur, readConfigString as ut, PRE_DELETE_SNAPSHOT_TYPES as v, resolveUseCdkBootstrapAssets as vn, createSecretMasker as vt, unsupportedFinalSnapshotError as w, findLargeInlineResources as wn, s3BucketDualStackDomainName as wt, createPreDeleteFinalSnapshot as x, CFN_TEMPLATE_BODY_LIMIT as xn, scrubResourceRecord as xt, buildFinalSnapshotIdentifier as y, stateBucketExistenceConfirmed as yn, maskSecretsInText as yt, exportAliasCollisionScrubWarning as z, CdkdError as zn, stringifyValue as zt };
|
|
26095
|
-
//# sourceMappingURL=deploy-engine-
|
|
26554
|
+
//# sourceMappingURL=deploy-engine-K5kHzsB-.js.map
|