@go-to-k/cdkd 0.284.76 → 0.284.78
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-BjebXXKD.js → asg-provider-R60Irey3.js} +2 -2
- package/dist/{asg-provider-BjebXXKD.js.map → asg-provider-R60Irey3.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-Dm-MdFR-.js → deploy-engine-Cvkqa30x.js} +524 -20
- package/dist/deploy-engine-Cvkqa30x.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-BVT169-W.js → program-DyZp744n.js} +54 -13
- package/dist/{program-BVT169-W.js.map → program-DyZp744n.js.map} +1 -1
- package/dist/{version-BMCefk9p.js → version-GEIr41xq.js} +2 -2
- package/dist/{version-BMCefk9p.js.map → version-GEIr41xq.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-Dm-MdFR-.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { d as generateResourceName, f as generateResourceNameWithFallback, g as withStackName, m as looksLikeCdkdGeneratedName, n as getLogger, o as getLiveRenderer, p as getCurrentStackName, u as applyDefaultNameForFallback } from "./logger-C9-E73FI.js";
|
|
2
|
-
import { t as getCdkdVersion } from "./version-
|
|
2
|
+
import { t as getCdkdVersion } from "./version-GEIr41xq.js";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -288,6 +288,83 @@ const THROTTLING_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
|
288
288
|
*/
|
|
289
289
|
const NON_RETRYABLE_MARKER = Symbol.for("cdkd.nonRetryable");
|
|
290
290
|
/**
|
|
291
|
+
* How far every `.cause` walk in this module reads.
|
|
292
|
+
*
|
|
293
|
+
* ONE constant rather than five literals, because the walks are only sound
|
|
294
|
+
* together: {@link isMarkedNonRetryable} is what stops a deliberate refusal
|
|
295
|
+
* being read as transient, so any walk that reads text FURTHER than the marker
|
|
296
|
+
* walk creates a band where a refusal is legible but its marker is not.
|
|
297
|
+
* {@link retryClassificationText} shipped at 10 against the marker's 5 for
|
|
298
|
+
* exactly that reason, and the chain shape that makes the band reachable is
|
|
299
|
+
* the one the code already cites -- a nested-stack failure grows one
|
|
300
|
+
* `ProvisioningError` per level.
|
|
301
|
+
*/
|
|
302
|
+
const MAX_CAUSE_CHAIN_DEPTH = 5;
|
|
303
|
+
/**
|
|
304
|
+
* Stamped on an error whose own message deliberately WITHHOLDS text its
|
|
305
|
+
* `cause` carries (issue [#2302](https://github.com/go-to-k/cdkd/issues/2302)).
|
|
306
|
+
*
|
|
307
|
+
* Distinct from {@link NON_RETRYABLE_MARKER} and orthogonal to it: this one
|
|
308
|
+
* says nothing about whether the error should be retried, only that the
|
|
309
|
+
* message a classifier would normally read is INCOMPLETE.
|
|
310
|
+
*/
|
|
311
|
+
const REDACTED_CAUSE_MARKER = Symbol.for("cdkd.redactedCause");
|
|
312
|
+
/**
|
|
313
|
+
* Declare that this error's message withholds text its `cause` carries, so the
|
|
314
|
+
* message-based retry classifiers must read the CHAIN instead
|
|
315
|
+
* (issue [#2302](https://github.com/go-to-k/cdkd/issues/2302)).
|
|
316
|
+
*
|
|
317
|
+
* cdkd's retry classifiers match by SUBSTRING over a message. That is sound
|
|
318
|
+
* only while a wrapper copies its cause's text, which every wrapper did until
|
|
319
|
+
* #2302 started reducing an AWS failure to its error CLASS -- S3 words its
|
|
320
|
+
* `AccessDenied` as `User: arn:aws:sts::<account>:assumed-role/<role>/<session>
|
|
321
|
+
* is not authorized to perform: ...`, and a THROWN message is captured into the
|
|
322
|
+
* persisted `deployments/{runId}.jsonl` store. Redacting it also removed the
|
|
323
|
+
* substrings the pattern table matches on: measured on `S3BucketProvider`,
|
|
324
|
+
* `not authorized to perform` (retryable, on the DENSE IAM-propagation cadence)
|
|
325
|
+
* and S3's `conflicting conditional operation` both went NON-retryable.
|
|
326
|
+
*
|
|
327
|
+
* Call it at every site that redacts, and only there. It is deliberately an
|
|
328
|
+
* opt-in stamp rather than an unconditional chain read -- see
|
|
329
|
+
* {@link retryClassificationText} for the measurement that forced that choice.
|
|
330
|
+
*
|
|
331
|
+
* Same non-extensible tolerance as {@link markNonRetryable}, for the same
|
|
332
|
+
* reason: callers use it inline around the error they are about to throw, so a
|
|
333
|
+
* `TypeError` here would replace the refusal with an unrelated crash. Losing
|
|
334
|
+
* the stamp degrades to reading the top-level message, i.e. the pre-#2302
|
|
335
|
+
* classification of a message that is now shorter -- worse, but not a crash.
|
|
336
|
+
*/
|
|
337
|
+
function markRedactedCause(error) {
|
|
338
|
+
if (!Object.isExtensible(error)) return error;
|
|
339
|
+
Object.defineProperty(error, REDACTED_CAUSE_MARKER, {
|
|
340
|
+
value: true,
|
|
341
|
+
enumerable: false,
|
|
342
|
+
configurable: true,
|
|
343
|
+
writable: false
|
|
344
|
+
});
|
|
345
|
+
return error;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* True when the error, or anything in its bounded `.cause` chain, was stamped
|
|
349
|
+
* by {@link markRedactedCause}.
|
|
350
|
+
*
|
|
351
|
+
* Walked rather than read off the top link, because the redacting error is
|
|
352
|
+
* itself wrapped further out: `deploy-engine.ts` re-wraps every provider
|
|
353
|
+
* failure, so by the time a classifier sees it the stamp is one or more links
|
|
354
|
+
* deep. Same {@link MAX_CAUSE_CHAIN_DEPTH} as the marker walk, which is what
|
|
355
|
+
* lets {@link retryClassificationText} claim the two agree.
|
|
356
|
+
*/
|
|
357
|
+
function hasRedactedCause(error) {
|
|
358
|
+
let current = error;
|
|
359
|
+
for (let depth = 0; depth < MAX_CAUSE_CHAIN_DEPTH && current != null; depth++) {
|
|
360
|
+
if (typeof current === "object" || typeof current === "function") {
|
|
361
|
+
if (current[REDACTED_CAUSE_MARKER] === true) return true;
|
|
362
|
+
}
|
|
363
|
+
current = current.cause;
|
|
364
|
+
}
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
291
368
|
* Mark a cdkd-authored refusal as terminal and return it, for
|
|
292
369
|
* `throw markNonRetryable(new ProvisioningError(...))`.
|
|
293
370
|
*
|
|
@@ -354,7 +431,7 @@ function markNonRetryable(error) {
|
|
|
354
431
|
*/
|
|
355
432
|
function isMarkedNonRetryable(error) {
|
|
356
433
|
let current = error;
|
|
357
|
-
for (let depth = 0; depth <
|
|
434
|
+
for (let depth = 0; depth < MAX_CAUSE_CHAIN_DEPTH && current != null; depth++) {
|
|
358
435
|
if (typeof current === "object" || typeof current === "function") {
|
|
359
436
|
if (current[NON_RETRYABLE_MARKER] === true) return true;
|
|
360
437
|
}
|
|
@@ -378,7 +455,7 @@ function isMarkedNonRetryable(error) {
|
|
|
378
455
|
*/
|
|
379
456
|
function isThrottlingError(error) {
|
|
380
457
|
let current = error;
|
|
381
|
-
for (let depth = 0; depth <
|
|
458
|
+
for (let depth = 0; depth < MAX_CAUSE_CHAIN_DEPTH && current != null; depth++) {
|
|
382
459
|
const name = current.name;
|
|
383
460
|
if (typeof name === "string" && THROTTLING_ERROR_NAMES.has(name)) return true;
|
|
384
461
|
const status = current.$metadata?.httpStatusCode;
|
|
@@ -480,7 +557,7 @@ const TRANSIENT_SERVER_ERROR_STATUS_CODES = /* @__PURE__ */ new Set([
|
|
|
480
557
|
*/
|
|
481
558
|
function isTransientServerError(error) {
|
|
482
559
|
let current = error;
|
|
483
|
-
for (let depth = 0; depth <
|
|
560
|
+
for (let depth = 0; depth < MAX_CAUSE_CHAIN_DEPTH && current != null; depth++) {
|
|
484
561
|
const status = current.$metadata?.httpStatusCode;
|
|
485
562
|
if (status !== void 0 && TRANSIENT_SERVER_ERROR_STATUS_CODES.has(status)) return true;
|
|
486
563
|
current = current.cause;
|
|
@@ -531,7 +608,7 @@ function describeRetryClassificationSignals(error) {
|
|
|
531
608
|
let sawMetadata = false;
|
|
532
609
|
let metadataName;
|
|
533
610
|
let metadataRequestId;
|
|
534
|
-
for (let depth = 0; depth <
|
|
611
|
+
for (let depth = 0; depth < MAX_CAUSE_CHAIN_DEPTH && current != null; depth++) {
|
|
535
612
|
const name = current.name;
|
|
536
613
|
if (depth > 0 && typeof name === "string" && name !== "") deepestName = name;
|
|
537
614
|
const metadata = current.$metadata;
|
|
@@ -599,6 +676,62 @@ function isRetryableTransientError(error, message) {
|
|
|
599
676
|
return RETRYABLE_ERROR_MESSAGE_PATTERNS.some((p) => message.includes(p));
|
|
600
677
|
}
|
|
601
678
|
/**
|
|
679
|
+
* The text every MESSAGE-based retry classifier should read: this error's own
|
|
680
|
+
* message plus every message down its `cause` chain.
|
|
681
|
+
*
|
|
682
|
+
* Every classifier in this module that reads a message reads the TOP-LEVEL one,
|
|
683
|
+
* and that was sound only while cdkd's wrappers copied their cause's text
|
|
684
|
+
* verbatim -- which they did, everywhere, until issue
|
|
685
|
+
* [#2302](https://github.com/go-to-k/cdkd/issues/2302). A wrapper on a THROWN
|
|
686
|
+
* path may now deliberately WITHHOLD AWS's wording, because a thrown message is
|
|
687
|
+
* captured into the persisted `deployments/{runId}.jsonl` store and S3's
|
|
688
|
+
* `AccessDenied` names the caller's account, role and session. Measured on
|
|
689
|
+
* `S3BucketProvider.create` before this function existed: the wrap turned
|
|
690
|
+
* `not authorized to perform` (retryable, and on the DENSE IAM-propagation
|
|
691
|
+
* cadence) and `conflicting conditional operation` (retryable) into
|
|
692
|
+
* NON-retryable, i.e. the redaction silently removed a retry the deploy
|
|
693
|
+
* depended on.
|
|
694
|
+
*
|
|
695
|
+
* This is the missing THIRD walk rather than a new idea: {@link
|
|
696
|
+
* isMarkedNonRetryable} and {@link isThrottlingError} already walk the same
|
|
697
|
+
* chain, for the same stated reason (cdkd wraps SDK errors routinely).
|
|
698
|
+
*
|
|
699
|
+
* It cannot resurrect a cdkd refusal: every consumer checks
|
|
700
|
+
* {@link isMarkedNonRetryable} FIRST, that marker is itself chain-walked, and
|
|
701
|
+
* both walks are bounded by the SAME {@link MAX_CAUSE_CHAIN_DEPTH}.
|
|
702
|
+
*
|
|
703
|
+
* It is a NO-OP for every error that has not opted in via
|
|
704
|
+
* {@link markRedactedCause} -- which is every wrapper on `main` outside
|
|
705
|
+
* #2302's redacting sites. An earlier revision justified itself with a wider
|
|
706
|
+
* claim, that every wrapper on `main` COPIES its cause's message; that claim is
|
|
707
|
+
* false (`custom-resource-provider.ts`'s `describeWaiterFailure` already
|
|
708
|
+
* withholds the raw waiter payload, and it is not the only wrapper that does),
|
|
709
|
+
* and it does not need to be true. The opt-in stamp makes the no-op a property
|
|
710
|
+
* of the mechanism rather than of a survey.
|
|
711
|
+
*
|
|
712
|
+
* NEVER use the result as a LOG line or a thrown message: it is the union of
|
|
713
|
+
* exactly the text the redaction exists to withhold. `retry.ts` keeps the
|
|
714
|
+
* top-level message for its `warn` / `debug` output and uses this only to
|
|
715
|
+
* classify.
|
|
716
|
+
*/
|
|
717
|
+
function retryClassificationText(error) {
|
|
718
|
+
const top = error instanceof Error ? error.message : String(error);
|
|
719
|
+
if (!hasRedactedCause(error)) return top;
|
|
720
|
+
const parts = [];
|
|
721
|
+
const seen = /* @__PURE__ */ new Set();
|
|
722
|
+
let current = error;
|
|
723
|
+
for (let depth = 0; depth < MAX_CAUSE_CHAIN_DEPTH && current != null && !seen.has(current); depth++) {
|
|
724
|
+
seen.add(current);
|
|
725
|
+
if (current instanceof Error) {
|
|
726
|
+
if (current.message) parts.push(current.message);
|
|
727
|
+
} else try {
|
|
728
|
+
parts.push(String(current));
|
|
729
|
+
} catch {}
|
|
730
|
+
current = current?.cause;
|
|
731
|
+
}
|
|
732
|
+
return parts.join("\n");
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
602
735
|
* True when the message is a just-created-IAM-entity propagation rejection
|
|
603
736
|
* ({@link IAM_PROPAGATION_ERROR_MESSAGE_PATTERNS}).
|
|
604
737
|
*
|
|
@@ -5756,6 +5889,50 @@ var DockerAssetPublisher = class {
|
|
|
5756
5889
|
}
|
|
5757
5890
|
};
|
|
5758
5891
|
|
|
5892
|
+
//#endregion
|
|
5893
|
+
//#region src/utils/aws-failure-text.ts
|
|
5894
|
+
/** Appended to a redacted summary so the withheld half is still reachable. */
|
|
5895
|
+
const VERBOSE_POINTER = "Re-run with --verbose for AWS's own message.";
|
|
5896
|
+
/**
|
|
5897
|
+
* Whether AWS wrote this failure's message.
|
|
5898
|
+
*
|
|
5899
|
+
* Keyed on the marker fields `@aws-sdk/*` errors carry through
|
|
5900
|
+
* `@smithy/smithy-client`'s `ServiceException` — `$metadata` on every
|
|
5901
|
+
* deserialized error, `$fault` on every modeled one, `$response` where a
|
|
5902
|
+
* middleware attached the raw response. Nothing under `src/` sets any of them,
|
|
5903
|
+
* so a match cannot be a cdkd-authored error.
|
|
5904
|
+
*
|
|
5905
|
+
* A transport-level failure (a socket timeout, a DNS error) can reach a caller
|
|
5906
|
+
* without `$metadata`, and that is the correct answer rather than a gap: those
|
|
5907
|
+
* messages are written by the HTTP layer and name a host, never a caller.
|
|
5908
|
+
*/
|
|
5909
|
+
function isAwsAuthoredFailure(error) {
|
|
5910
|
+
const candidate = error;
|
|
5911
|
+
return candidate.$metadata !== void 0 || candidate.$fault !== void 0 || candidate.$response !== void 0;
|
|
5912
|
+
}
|
|
5913
|
+
/**
|
|
5914
|
+
* Describe a caught failure for a thrown message. See {@link AwsFailureText}.
|
|
5915
|
+
*/
|
|
5916
|
+
function describeAwsFailure(error) {
|
|
5917
|
+
if (error instanceof Error) {
|
|
5918
|
+
if (!isAwsAuthoredFailure(error)) return {
|
|
5919
|
+
summary: error.message,
|
|
5920
|
+
detail: error.message,
|
|
5921
|
+
redacted: false
|
|
5922
|
+
};
|
|
5923
|
+
return {
|
|
5924
|
+
summary: `${error.name || "Error"}. ${VERBOSE_POINTER}`,
|
|
5925
|
+
detail: error.message,
|
|
5926
|
+
redacted: true
|
|
5927
|
+
};
|
|
5928
|
+
}
|
|
5929
|
+
return {
|
|
5930
|
+
summary: `a non-Error value of type ${typeof error}. ${VERBOSE_POINTER}`,
|
|
5931
|
+
detail: String(error),
|
|
5932
|
+
redacted: true
|
|
5933
|
+
};
|
|
5934
|
+
}
|
|
5935
|
+
|
|
5759
5936
|
//#endregion
|
|
5760
5937
|
//#region src/utils/deny-external-access-policy.ts
|
|
5761
5938
|
/**
|
|
@@ -5945,7 +6122,11 @@ async function assertAssetBucketRegion(s3Client, bucketName, expectedRegion, acc
|
|
|
5945
6122
|
} catch (probeError) {
|
|
5946
6123
|
const fromProbe = readBucketRegionHeader(probeError);
|
|
5947
6124
|
if (fromProbe) actual = canonicalizeRegion(fromProbe);
|
|
5948
|
-
else
|
|
6125
|
+
else {
|
|
6126
|
+
const failure = describeAwsFailure(probeError);
|
|
6127
|
+
if (failure.redacted) getLogger().debug(`GetBucketLocation failed for asset bucket '${bucketName}' while confirming it belongs to ${want}: ${failure.detail}`);
|
|
6128
|
+
throw new CdkdError(`Asset bucket '${bucketName}' is claimed by an existing bucket, but cdkd could not determine which region that bucket is in, so it cannot confirm it belongs to ${want}. Refusing to adopt it. (region probe failed: ${failure.summary}) ${remedy}`, "ASSET_STORAGE_FOREIGN_REGION_BUCKET", cause);
|
|
6129
|
+
}
|
|
5949
6130
|
}
|
|
5950
6131
|
if (actual === want) return;
|
|
5951
6132
|
throw new CdkdError(`Asset bucket name '${bucketName}' resolves to a bucket in ${actual}, while this operation targets ${want}. S3 bucket names are globally unique, and both 'BucketAlreadyOwnedByYou' and a cross-region redirect report ACCOUNT ownership rather than the bucket's region, so cdkd cannot treat it as ${want}'s asset bucket. cdkd asset storage is per-region by design: adopting it would publish ${want}'s assets into ${actual} and apply ${want}'s bucket configuration there. ${remedy}`, "ASSET_STORAGE_FOREIGN_REGION_BUCKET", cause);
|
|
@@ -8977,9 +9158,13 @@ function splitGetAttStringForm(getAtt) {
|
|
|
8977
9158
|
* (`(producer X / Y)`), so it is neither derivable from the source leaf nor
|
|
8978
9159
|
* stable.
|
|
8979
9160
|
*
|
|
8980
|
-
*
|
|
8981
|
-
* `Fn::
|
|
8982
|
-
*
|
|
9161
|
+
* FIVE arms answer, and the enumeration is kept current because a stale one
|
|
9162
|
+
* reads as exhaustive: `Fn::ImportValue`, `Fn::GetStackOutput`, the `Fn::GetAtt`
|
|
9163
|
+
* on a nested-stack OUTPUT that issue #2055's read site re-resolves, the
|
|
9164
|
+
* single-placeholder `Fn::Sub` that normalizes ONTO that `Fn::GetAtt` key
|
|
9165
|
+
* (issue #2270 round 3 -- this list said THREE until issue #2291 noticed it had
|
|
9166
|
+
* been four since then), and the `Ref` to a nested-stack CHILD's own PARAMETER
|
|
9167
|
+
* (issue #2291). Every other leaf refuses.
|
|
8983
9168
|
*
|
|
8984
9169
|
* `Region` and `RoleArn` are OPTIONAL slots, and an ABSENT one keys as empty
|
|
8985
9170
|
* while a PRESENT-but-non-literal one refuses. Absent has to be its own key
|
|
@@ -9082,6 +9267,11 @@ function crossStackSourceKey(source) {
|
|
|
9082
9267
|
split.attributeName
|
|
9083
9268
|
].join(CROSS_STACK_KEY_SEPARATOR);
|
|
9084
9269
|
}
|
|
9270
|
+
if (key === "Ref") {
|
|
9271
|
+
const parameterName = literalStringOrUndefined(source[key]);
|
|
9272
|
+
if (parameterName === void 0) return void 0;
|
|
9273
|
+
return ["Ref", parameterName].join(CROSS_STACK_KEY_SEPARATOR);
|
|
9274
|
+
}
|
|
9085
9275
|
}
|
|
9086
9276
|
/**
|
|
9087
9277
|
* Remember, FOR THE PASS THAT OWNS `secrets`, that the cross-stack source leaf
|
|
@@ -9101,6 +9291,34 @@ function recordCrossStackExpression(secrets, key, expression, plaintext) {
|
|
|
9101
9291
|
associations = /* @__PURE__ */ new Map();
|
|
9102
9292
|
crossStackAssociations.set(secrets, associations);
|
|
9103
9293
|
}
|
|
9294
|
+
storeAssociation(associations, key, expression, plaintext);
|
|
9295
|
+
}
|
|
9296
|
+
/**
|
|
9297
|
+
* The store's WRITE rule, shared by {@link recordCrossStackExpression} and
|
|
9298
|
+
* {@link recordNestedStackParameterExpressions} so the two tables cannot drift
|
|
9299
|
+
* apart on what a second sighting means.
|
|
9300
|
+
*
|
|
9301
|
+
* A key recorded against a DIFFERENT (expression, plaintext) pair is POISONED
|
|
9302
|
+
* to {@link CONFLICTING_CROSS_STACK} rather than overwritten; an already
|
|
9303
|
+
* poisoned key stays poisoned, because a third sighting cannot un-contradict
|
|
9304
|
+
* the first two. Every reader refuses a poisoned key, which degrades to the
|
|
9305
|
+
* value scan — today's behaviour — rather than guessing between the two.
|
|
9306
|
+
*
|
|
9307
|
+
* WHAT IS AND IS NOT FENCED, stated because a reader would otherwise assume the
|
|
9308
|
+
* whole thing is (review of issue #2291). The POISON-vs-OVERWRITE decision IS
|
|
9309
|
+
* fenced: a second sighting under a THIRD expression makes the two outcomes
|
|
9310
|
+
* differ (poison refuses and falls back; overwriting would certify the third),
|
|
9311
|
+
* and a case asserts it. Nothing fences the branch from
|
|
9312
|
+
* {@link recordNestedStackParameterExpressions}'s side in PRODUCTION, because
|
|
9313
|
+
* that writer runs once per resource bag and `Object.entries` cannot yield one
|
|
9314
|
+
* name twice — the poison is reachable only through this module's API. It is
|
|
9315
|
+
* kept as an INVARIANT, not claimed as covered behaviour.
|
|
9316
|
+
*
|
|
9317
|
+
* The `isSingleDynamicReferenceToken` shape invariant is deliberately NOT here:
|
|
9318
|
+
* it belongs to each writer, which is where the parameters are named and where
|
|
9319
|
+
* a swap could be introduced.
|
|
9320
|
+
*/
|
|
9321
|
+
function storeAssociation(associations, key, expression, plaintext) {
|
|
9104
9322
|
const seen = associations.get(key);
|
|
9105
9323
|
if (seen === void 0) {
|
|
9106
9324
|
associations.set(key, {
|
|
@@ -9113,6 +9331,218 @@ function recordCrossStackExpression(secrets, key, expression, plaintext) {
|
|
|
9113
9331
|
if (seen.expression !== expression || seen.plaintext !== plaintext) associations.set(key, CONFLICTING_CROSS_STACK);
|
|
9114
9332
|
}
|
|
9115
9333
|
/**
|
|
9334
|
+
* The `AWS::CloudFormation::Stack` type string, named once because the recorder
|
|
9335
|
+
* below gates on it and the tests assert against the same population.
|
|
9336
|
+
*
|
|
9337
|
+
* DUPLICATED, deliberately: `intrinsic-function-resolver.ts` declares the same
|
|
9338
|
+
* literal under the same name (for the `Outputs.<Name>` re-resolution of issue
|
|
9339
|
+
* #2055). This module is a LEAF by design -- see the file header, it imports
|
|
9340
|
+
* nothing, because both the resolver and the deploy engine consume it -- so
|
|
9341
|
+
* importing that spelling would close a cycle, and exporting this one for the
|
|
9342
|
+
* resolver to import would make the leaf a source of values rather than of
|
|
9343
|
+
* pure functions. The two cannot drift into DISAGREEMENT in any way that
|
|
9344
|
+
* matters: an AWS resource type string is fixed by AWS, and a typo in either
|
|
9345
|
+
* copy makes that copy's gate simply never fire (no-op), never fire wrongly.
|
|
9346
|
+
*/
|
|
9347
|
+
const NESTED_STACK_RESOURCE_TYPE$1 = "AWS::CloudFormation::Stack";
|
|
9348
|
+
/**
|
|
9349
|
+
* What a PARENT stack's resolution proved about each `Parameters` entry of an
|
|
9350
|
+
* `AWS::CloudFormation::Stack` row it is about to provision, keyed by the
|
|
9351
|
+
* child's PARAMETER NAME (issue
|
|
9352
|
+
* [#2291](https://github.com/go-to-k/cdkd/issues/2291)).
|
|
9353
|
+
*
|
|
9354
|
+
* WHY A THIRD STORE, when {@link crossStackAssociations} already holds
|
|
9355
|
+
* per-leaf associations. This one is OUTBOUND: it is written against the
|
|
9356
|
+
* PARENT's bag and describes leaves of the CHILD's template, so it has to be
|
|
9357
|
+
* transported across the engine boundary before any reader can use it. The
|
|
9358
|
+
* child's per-resource bag then receives those entries as ORDINARY
|
|
9359
|
+
* {@link crossStackAssociations} rows (see
|
|
9360
|
+
* {@link inheritNestedStackParameterAssociations}), which is what lets the
|
|
9361
|
+
* existing three-condition reader answer for them with no new arm.
|
|
9362
|
+
*
|
|
9363
|
+
* KEEPING THE TWO TABLES SEPARATE IS LOAD-BEARING, not tidiness. A child engine
|
|
9364
|
+
* that itself owns a grandchild `AWS::CloudFormation::Stack` row records the
|
|
9365
|
+
* GRANDCHILD's parameter names against the CHILD's bag — the same bag that
|
|
9366
|
+
* already carries the child's own INBOUND `Ref` associations. Writing both into
|
|
9367
|
+
* one table means a grandchild parameter sharing a NAME with a child parameter
|
|
9368
|
+
* poisons the child's entry (two expressions under one key), so a leaf that was
|
|
9369
|
+
* being certified correctly falls back to the value scan the moment a
|
|
9370
|
+
* same-named parameter appears one level down. Two tables make the collision
|
|
9371
|
+
* impossible rather than unlikely.
|
|
9372
|
+
*
|
|
9373
|
+
* A `WeakMap` keyed by the parent pass's own bag, for the reason
|
|
9374
|
+
* {@link crossStackAssociations} gives: the entries hold PLAINTEXT, and they
|
|
9375
|
+
* must not outlive the pass that fetched them.
|
|
9376
|
+
*/
|
|
9377
|
+
const nestedStackParameterExpressions = /* @__PURE__ */ new WeakMap();
|
|
9378
|
+
/**
|
|
9379
|
+
* Record, for the pass that owns `secrets`, which `{{resolve:...}}` expression
|
|
9380
|
+
* each `Parameters` entry of a nested-stack row was resolved FROM (issue
|
|
9381
|
+
* #2291). No-op for every other resource type.
|
|
9382
|
+
*
|
|
9383
|
+
* THE EXPRESSIONS COME FROM THE POSITION PASS, not from `secrets`, and that is
|
|
9384
|
+
* the whole reason this works at all. `RecordedSecretValues` is keyed by
|
|
9385
|
+
* PLAINTEXT, so two parameters resolving to one value have already collapsed to
|
|
9386
|
+
* a single entry there by the time this runs — asking the map which expression
|
|
9387
|
+
* a given parameter came from returns the SURVIVOR for both. What has not
|
|
9388
|
+
* collapsed is the parent's own template: `Properties.Parameters.<Name>` still
|
|
9389
|
+
* holds each entry's own source leaf. So this walks the parent's resolved
|
|
9390
|
+
* parameter bag against that source with {@link redactSecretsForState}, which
|
|
9391
|
+
* is exactly the machinery the parent's persist path already uses and which
|
|
9392
|
+
* certifies PER LEAF (measured on this issue: the parent's own two properties
|
|
9393
|
+
* come out correct while the child's collapse onto one). Deriving the answer
|
|
9394
|
+
* from the positioner rather than restating its rules is what keeps this from
|
|
9395
|
+
* drifting away from the persist path.
|
|
9396
|
+
*
|
|
9397
|
+
* THE `rules` PARAMETER IS THE CALLER'S GENERATION CLAIM, not a knob. The
|
|
9398
|
+
* DEPLOY path passes {@link TEMPLATE_DERIVED_RULES} (the default): its source is
|
|
9399
|
+
* the parent's TEMPLATE, which can carry a PUBLIC `ssm:` reference that must
|
|
9400
|
+
* stay resolved (issue #1901) and which cannot certify the generation of
|
|
9401
|
+
* anything. The rollback REPLAY passes {@link STATE_DERIVED_RULES}, because its
|
|
9402
|
+
* source is the JOURNAL record — a persisted bag holding no public expressions,
|
|
9403
|
+
* and the same generation the bag was resolved from one statement earlier. That
|
|
9404
|
+
* is the identical pairing `redactRollbackRecord` already makes for the record
|
|
9405
|
+
* it positions, so the two replay walks now agree about what their source is.
|
|
9406
|
+
*
|
|
9407
|
+
* "No public expressions" carries the carve-out {@link PathSourceRules} states
|
|
9408
|
+
* and this note must not restate without it: `cdkd import` warns and persists
|
|
9409
|
+
* the RAW template intrinsic, so a public `ssm:` token CAN sit in a record. The
|
|
9410
|
+
* replay then CERTIFIES one the deploy default would refuse, at a cost bounded
|
|
9411
|
+
* to the issue #1901 class — a spurious UPDATE over a value state should hold
|
|
9412
|
+
* resolved, never a disclosure, since an expression is what gets persisted
|
|
9413
|
+
* either way. See the replay call sites for why gating on
|
|
9414
|
+
* {@link isKnownSecretExpression} is the wrong way to close it.
|
|
9415
|
+
*
|
|
9416
|
+
* FOUR REFUSALS, each degrading to today's behaviour (the child leaf falls to
|
|
9417
|
+
* the plaintext-keyed value scan):
|
|
9418
|
+
*
|
|
9419
|
+
* 1. REFUSAL — the resolved parameter value is not a WHOLE recorded plaintext.
|
|
9420
|
+
* A parameter the parent built with an `Fn::Sub` merely EMBEDS the secret,
|
|
9421
|
+
* so there is no single expression the child's leaf could be persisted as.
|
|
9422
|
+
* It also bounds what this store HOLDS: an entry carries a plaintext, and
|
|
9423
|
+
* remembering one this pass never resolved has no purpose.
|
|
9424
|
+
* 2. REFUSAL — the expression is not a single complete `{{resolve:...}}` token.
|
|
9425
|
+
* The same test {@link recordCrossStackExpression} applies at its own
|
|
9426
|
+
* boundary, spelled here because this writer populates a DIFFERENT table.
|
|
9427
|
+
* This is what rejects an embedded case that survived refusal 1 (a value
|
|
9428
|
+
* scan produced `postgres://u:{{resolve:...}}@host`, which is not a token).
|
|
9429
|
+
* 3. REFUSAL — the expression may never EQUAL the plaintext it is recorded
|
|
9430
|
+
* against. The reader returns the expression to be PERSISTED, so an entry
|
|
9431
|
+
* whose two halves coincide hands a SECRET back as though it were a
|
|
9432
|
+
* reference.
|
|
9433
|
+
*
|
|
9434
|
+
* THIS IS REACHABLE, and an earlier revision of this note called it an
|
|
9435
|
+
* unreachable invariant and told the next reader not to try to fence it.
|
|
9436
|
+
* That was wrong twice over: wrong on the fact, and wrong to assert it,
|
|
9437
|
+
* because {@link plaintextIndexOf}'s own note records the rule that
|
|
9438
|
+
* "asserting something cannot be fenced suppresses the attempt, so it needs
|
|
9439
|
+
* the same evidence a fence does" -- and no such evidence existed. The
|
|
9440
|
+
* reaching shape is the issue
|
|
9441
|
+
* [#1917](https://github.com/go-to-k/cdkd/issues/1917) family: a
|
|
9442
|
+
* SELF-REFERENTIAL secret, whose stored VALUE is byte-identical to its own
|
|
9443
|
+
* `{{resolve:...}}` text, so the pass records `SELF -> SELF`. The route is
|
|
9444
|
+
* NOT the value scan the old note named. It is {@link redactByPath}'s
|
|
9445
|
+
* `!sourceIsSameGeneration && isSingleDynamicReferenceToken(bag)` arm, which
|
|
9446
|
+
* returns `secrets.get(bag) ?? bag` -- and for a self-referential secret
|
|
9447
|
+
* that IS the bag. Under {@link STATE_DERIVED_RULES} the same input arrives
|
|
9448
|
+
* by the other door (`sourceIsSameGeneration` is true, so the arm takes
|
|
9449
|
+
* `return source`, which is the same string again), so both callers reach it.
|
|
9450
|
+
* Fenced by the self-referential case in
|
|
9451
|
+
* `secret-redaction-nested-parameter-source.test.ts`.
|
|
9452
|
+
*/
|
|
9453
|
+
function recordNestedStackParameterExpressions(secrets, resourceType, resolvedProperties, sourceProperties, rules = TEMPLATE_DERIVED_RULES) {
|
|
9454
|
+
if (resourceType !== NESTED_STACK_RESOURCE_TYPE$1) return;
|
|
9455
|
+
if (secrets.size === 0) return;
|
|
9456
|
+
if (!isPlainObject$2(resolvedProperties) || !isPlainObject$2(sourceProperties)) return;
|
|
9457
|
+
if (!Object.hasOwn(resolvedProperties, "Parameters")) return;
|
|
9458
|
+
if (!Object.hasOwn(sourceProperties, "Parameters")) return;
|
|
9459
|
+
const resolvedParameters = resolvedProperties["Parameters"];
|
|
9460
|
+
const sourceParameters = sourceProperties["Parameters"];
|
|
9461
|
+
if (!isPlainObject$2(resolvedParameters) || !isPlainObject$2(sourceParameters)) return;
|
|
9462
|
+
const positioned = redactSecretsForState(resolvedParameters, secrets, sourceParameters, rules);
|
|
9463
|
+
let table = nestedStackParameterExpressions.get(secrets);
|
|
9464
|
+
for (const [name, resolvedValue] of Object.entries(resolvedParameters)) {
|
|
9465
|
+
if (typeof resolvedValue !== "string" || !secrets.has(resolvedValue)) continue;
|
|
9466
|
+
const expression = positioned[name];
|
|
9467
|
+
if (typeof expression !== "string") continue;
|
|
9468
|
+
if (!isSingleDynamicReferenceToken(expression)) continue;
|
|
9469
|
+
const sourceLeaf = sourceParameters[name];
|
|
9470
|
+
if (typeof sourceLeaf === "string" && expression !== sourceLeaf) continue;
|
|
9471
|
+
if (expression === resolvedValue) continue;
|
|
9472
|
+
if (table === void 0) {
|
|
9473
|
+
table = /* @__PURE__ */ new Map();
|
|
9474
|
+
nestedStackParameterExpressions.set(secrets, table);
|
|
9475
|
+
}
|
|
9476
|
+
storeAssociation(table, name, expression, resolvedValue);
|
|
9477
|
+
}
|
|
9478
|
+
}
|
|
9479
|
+
/**
|
|
9480
|
+
* Copy a parent pass's per-PARAMETER associations onto a nested-stack CHILD
|
|
9481
|
+
* resource's own bag, as ordinary {@link crossStackAssociations} rows keyed by
|
|
9482
|
+
* `{Ref: <ParamName>}` (issue #2291).
|
|
9483
|
+
*
|
|
9484
|
+
* Called by the child {@link DeployEngine} once per resolver context, so each
|
|
9485
|
+
* child resource's fresh bag carries them. Pre-seeding every resource this way
|
|
9486
|
+
* does NOT reintroduce issue #2087's over-redaction: an association can only
|
|
9487
|
+
* change an answer through {@link positionByCrossStackSource}'s condition 1,
|
|
9488
|
+
* which requires the bag leaf to be a plaintext THIS resource's bag holds — and
|
|
9489
|
+
* since #2087 that is true only of resources whose own resolution consumed the
|
|
9490
|
+
* parameter. The scoping still comes from the plaintext bag; this table only
|
|
9491
|
+
* decides WHICH expression such a leaf takes.
|
|
9492
|
+
*
|
|
9493
|
+
* Nothing is copied when the parent recorded nothing, which is every non-nested
|
|
9494
|
+
* caller and every nested one whose parameters carry no secret.
|
|
9495
|
+
*/
|
|
9496
|
+
function inheritNestedStackParameterAssociations(childSecrets, parentSecrets) {
|
|
9497
|
+
const table = nestedStackParameterExpressions.get(parentSecrets);
|
|
9498
|
+
if (table === void 0 || table.size === 0) return;
|
|
9499
|
+
let associations = crossStackAssociations.get(childSecrets);
|
|
9500
|
+
for (const [name, association] of table) {
|
|
9501
|
+
const key = crossStackSourceKey({ Ref: name });
|
|
9502
|
+
if (key === void 0) continue;
|
|
9503
|
+
if (associations === void 0) {
|
|
9504
|
+
associations = /* @__PURE__ */ new Map();
|
|
9505
|
+
crossStackAssociations.set(childSecrets, associations);
|
|
9506
|
+
}
|
|
9507
|
+
if (typeof association === "symbol") {
|
|
9508
|
+
associations.set(key, association);
|
|
9509
|
+
continue;
|
|
9510
|
+
}
|
|
9511
|
+
storeAssociation(associations, key, association.expression, association.plaintext);
|
|
9512
|
+
}
|
|
9513
|
+
}
|
|
9514
|
+
/**
|
|
9515
|
+
* The expression a nested-stack child's PARAMETER was resolved from, or
|
|
9516
|
+
* `undefined` when this pass cannot certify one (issue #2291).
|
|
9517
|
+
*
|
|
9518
|
+
* The DIFF-side twin of {@link positionByCrossStackSource}, and it must exist
|
|
9519
|
+
* or the fix trades one bug for another. The child's persisted state now holds
|
|
9520
|
+
* each parameter-fed leaf's OWN expression, so the desired side of the next
|
|
9521
|
+
* diff has to hold it too; the engine's `redactParametersForDiff` rewrites the
|
|
9522
|
+
* parameter bag through the plaintext-keyed map alone, which hands BOTH members
|
|
9523
|
+
* of a coinciding pair the survivor's expression. The two sides would then
|
|
9524
|
+
* never match for the losing parameter: a perpetual UPDATE on every deploy of
|
|
9525
|
+
* such a child, which is issue #2087's user-visible symptom arriving through a
|
|
9526
|
+
* different door.
|
|
9527
|
+
*
|
|
9528
|
+
* `parentSecrets` is the INHERITED bag — the parent's own per-resource map, the
|
|
9529
|
+
* object this table is keyed by — not the child resource's bag.
|
|
9530
|
+
*
|
|
9531
|
+
* The same THREE conditions {@link positionByCrossStackSource} applies, for the
|
|
9532
|
+
* same reasons; see that function's own notes for why condition 3 is not
|
|
9533
|
+
* subsumed by condition 2.
|
|
9534
|
+
*/
|
|
9535
|
+
function inheritedParameterExpression(parentSecrets, parameterName, resolvedValue) {
|
|
9536
|
+
if (typeof resolvedValue !== "string" || resolvedValue === "") return void 0;
|
|
9537
|
+
if (!parentSecrets.has(resolvedValue)) return void 0;
|
|
9538
|
+
const association = nestedStackParameterExpressions.get(parentSecrets)?.get(parameterName);
|
|
9539
|
+
if (association === void 0 || typeof association === "symbol") return void 0;
|
|
9540
|
+
if (association.plaintext !== resolvedValue) return void 0;
|
|
9541
|
+
const recordedPlaintext = plaintextIndexOf(parentSecrets).get(association.expression);
|
|
9542
|
+
if (recordedPlaintext !== void 0 && recordedPlaintext !== resolvedValue) return void 0;
|
|
9543
|
+
return association.expression;
|
|
9544
|
+
}
|
|
9545
|
+
/**
|
|
9116
9546
|
* A resolved secret value shorter than this is NOT used as a redaction needle:
|
|
9117
9547
|
* a 1-2 character plaintext (e.g. a secret whose JSON key holds `"0"`) would
|
|
9118
9548
|
* match incidental characters everywhere and mangle unrelated state. Such a
|
|
@@ -12235,11 +12665,12 @@ async function withRetry(operation, logicalId, opts = {}) {
|
|
|
12235
12665
|
} catch (error) {
|
|
12236
12666
|
lastError = error;
|
|
12237
12667
|
const message = error instanceof Error ? error.message : String(error);
|
|
12668
|
+
const classifyText = retryClassificationText(error);
|
|
12238
12669
|
if (isMarkedNonRetryable(error)) throw error;
|
|
12239
|
-
const retryable = opts.isRetryable ? opts.isRetryable(
|
|
12240
|
-
const propagation = defaultSchedule && isIamPropagationError(
|
|
12670
|
+
const retryable = opts.isRetryable ? opts.isRetryable(classifyText, error) : isRetryableTransientError(error, classifyText);
|
|
12671
|
+
const propagation = defaultSchedule && isIamPropagationError(classifyText);
|
|
12241
12672
|
if (propagation) sawPropagation = true;
|
|
12242
|
-
const nameCooldown = defaultSchedule && !propagation && isNameCooldownError(
|
|
12673
|
+
const nameCooldown = defaultSchedule && !propagation && isNameCooldownError(classifyText);
|
|
12243
12674
|
const attemptLimit = sawPropagation ? 26 : maxRetries;
|
|
12244
12675
|
if (!retryable || attempt >= attemptLimit) {
|
|
12245
12676
|
if (propagationRetries > 0 || serverErrorRetries > 0 || nameCooldownRetries > 0) {
|
|
@@ -16420,15 +16851,79 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
16420
16851
|
* `Fn::Select` / `Fn::FindInMap` all re-enter `resolveValue` and reach the
|
|
16421
16852
|
* parameter through this same `Ref` branch.
|
|
16422
16853
|
*
|
|
16854
|
+
* WHICH EXPRESSION the pair is recorded against is the issue
|
|
16855
|
+
* [#2291](https://github.com/go-to-k/cdkd/issues/2291) round-2 fix, and
|
|
16856
|
+
* skipping it made the persist and diff halves DISAGREE.
|
|
16857
|
+
*
|
|
16858
|
+
* `inherited` is keyed by PLAINTEXT, so two parent parameters resolving to
|
|
16859
|
+
* ONE value collapse to a single entry there and this method used to copy
|
|
16860
|
+
* whichever expression SURVIVED. That is invisible for a leaf spelled exactly
|
|
16861
|
+
* `{Ref: P}` — the persist path positions such a leaf through the parent's
|
|
16862
|
+
* per-parameter association and never consults this bag's value — but every
|
|
16863
|
+
* EMBEDDING shape (`Fn::Sub`, `Fn::Join`, and `{'Fn::Sub': '${P}'}`, which
|
|
16864
|
+
* `crossStackSourceKey` refuses because its `Fn::Sub` arm requires a dotted
|
|
16865
|
+
* attribute) falls to the plaintext-keyed VALUE SCAN, which reads exactly this
|
|
16866
|
+
* bag. Meanwhile `DeployEngine.redactParametersForDiff` answers PER PARAMETER.
|
|
16867
|
+
* So `Fn::Sub "postgres://u:${LoserParam}@host"` — the dominant CDK
|
|
16868
|
+
* connection-string shape — persisted the SURVIVOR's expression while the
|
|
16869
|
+
* desired side computed the LOSER's, and the two never matched again: a
|
|
16870
|
+
* perpetual UPDATE, or a perpetual REPLACEMENT on a create-only property.
|
|
16871
|
+
* That is issue [#2087](https://github.com/go-to-k/cdkd/issues/2087)'s symptom
|
|
16872
|
+
* arriving through a different door, prevented for the bare-`Ref` leaf and
|
|
16873
|
+
* created for the embedded one.
|
|
16874
|
+
*
|
|
16875
|
+
* Recording THIS parameter's own expression makes the value scan agree with
|
|
16876
|
+
* the diff side, so both halves move together.
|
|
16877
|
+
*
|
|
16878
|
+
* THE RESIDUAL, and the earlier version of this note UNDERSTATED IT. It said
|
|
16879
|
+
* the leftover case was "no worse than the behaviour before this fix". That
|
|
16880
|
+
* is true only of the shape it was measured on. MEASURED 2026-08-27 against
|
|
16881
|
+
* `main` (f56c2cf9) and against this branch, one child resource, parameters
|
|
16882
|
+
* `A` and `B` resolving to one plaintext:
|
|
16883
|
+
*
|
|
16884
|
+
* | shape | main | here |
|
|
16885
|
+
* | ---------------------------------------- | ------- | ------- |
|
|
16886
|
+
* | `{Ref: A}` and `{Ref: B}` | agree | agree |
|
|
16887
|
+
* | `Fn::Sub '${A}'` only | agree | agree |
|
|
16888
|
+
* | `Fn::Sub 'x${A}'` **and** `{Ref: B}` | agree | DISAGREE|
|
|
16889
|
+
*
|
|
16890
|
+
* The third row is a NEW disagreement this PR introduces, not a pre-existing
|
|
16891
|
+
* one it fails to fix: `main` had both halves take the collapsed survivor, so
|
|
16892
|
+
* they matched (on the WRONG expression, which is issue #2291, but they
|
|
16893
|
+
* matched). Here the DIFF side is per-parameter while an EMBEDDED leaf can
|
|
16894
|
+
* only be redacted by the plaintext-keyed value scan, and this bag holds ONE
|
|
16895
|
+
* entry — whichever `Ref` resolved LAST. So the embedded leaf takes `B`'s
|
|
16896
|
+
* expression while the desired side computes `A`'s, and the resource reports
|
|
16897
|
+
* an UPDATE on every deploy (a REPLACEMENT, on a create-only property).
|
|
16898
|
+
*
|
|
16899
|
+
* It is ORDER-DEPENDENT, which is why it is narrow: reversing the two
|
|
16900
|
+
* properties makes the embedded parameter the last one resolved and the two
|
|
16901
|
+
* halves agree again (measured). It also needs BOTH leaves in ONE resource —
|
|
16902
|
+
* `perResourceSecrets` is keyed by logical id, so two resources get two bags
|
|
16903
|
+
* and each is right. A resource that consumes and embeds BOTH parameters is
|
|
16904
|
+
* unfixable here for the same reason and is genuinely inherent.
|
|
16905
|
+
*
|
|
16906
|
+
* CLOSING IT NEEDS A PLACEHOLDER-SPAN POSITION ARM — aligning an `Fn::Sub` /
|
|
16907
|
+
* `Fn::Join` source against the resolved string to locate each placeholder's
|
|
16908
|
+
* span and rewrite it from the association. That is a new positioning
|
|
16909
|
+
* CONCEPT rather than an arm beside the existing ones: the persist path would
|
|
16910
|
+
* have to reproduce the resolver's substitution semantics from a module that
|
|
16911
|
+
* holds neither a resolver nor a parameter bag, and any divergence between
|
|
16912
|
+
* the two reproductions is this same perpetual-UPDATE bug. Deferred to issue
|
|
16913
|
+
* [#2320](https://github.com/go-to-k/cdkd/issues/2320) with the measurement.
|
|
16914
|
+
* It shares only the word "span" with issue #2102, which registers live
|
|
16915
|
+
* values for `{{resolve:...}}` TOKEN spans on the drift paths.
|
|
16916
|
+
*
|
|
16423
16917
|
* Substituting is deliberately NOT done here — the resolved value is what
|
|
16424
16918
|
* reaches AWS, and an `Fn::Equals` over a parameter must compare the real
|
|
16425
16919
|
* value or the condition flips.
|
|
16426
16920
|
*/
|
|
16427
|
-
recordInheritedParameterSecrets(value, context) {
|
|
16921
|
+
recordInheritedParameterSecrets(parameterName, value, context) {
|
|
16428
16922
|
const inherited = context.inheritedSecrets;
|
|
16429
16923
|
const recorded = context.recordedSecretValues;
|
|
16430
16924
|
if (!inherited || inherited.size === 0 || !recorded) return;
|
|
16431
|
-
|
|
16925
|
+
const own = inheritedParameterExpression(inherited, parameterName, value);
|
|
16926
|
+
for (const [plaintext, expression] of inheritedSecretsCarriedBy(value, inherited)) recorded.set(plaintext, own !== void 0 && plaintext === value ? own : expression);
|
|
16432
16927
|
}
|
|
16433
16928
|
/**
|
|
16434
16929
|
* Refuse a child parameter whose declared `Type` would COERCE an inherited
|
|
@@ -16493,7 +16988,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
16493
16988
|
const value = context.parameters[logicalId];
|
|
16494
16989
|
const paramDef = context.template.Parameters?.[logicalId];
|
|
16495
16990
|
this.logger.debug(`Resolved Ref to parameter: ${logicalId} -> ${this.maskSecretsForLog(stringifyParameterForLog(paramDef, value), context)}`);
|
|
16496
|
-
this.recordInheritedParameterSecrets(value, context);
|
|
16991
|
+
this.recordInheritedParameterSecrets(logicalId, value, context);
|
|
16497
16992
|
return value;
|
|
16498
16993
|
}
|
|
16499
16994
|
const pseudoValue = await this.resolvePseudoParameter(logicalId, context);
|
|
@@ -19514,7 +20009,7 @@ var CloudControlProvider = class {
|
|
|
19514
20009
|
await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
|
|
19515
20010
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
19516
20011
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
19517
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
20012
|
+
const { ASGProvider } = await import("./asg-provider-R60Irey3.js").then((n) => n.n);
|
|
19518
20013
|
return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
19519
20014
|
}
|
|
19520
20015
|
const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
|
|
@@ -28346,6 +28841,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
28346
28841
|
const current = stateResources[op.logicalId];
|
|
28347
28842
|
const prev = op.previousState;
|
|
28348
28843
|
const resolvedPrevProps = await resolveReplayProps(prev.properties, resolver, secrets, ctx, op.logicalId) ?? {};
|
|
28844
|
+
recordNestedStackParameterExpressions(secrets, op.resourceType, resolvedPrevProps, prev.properties, STATE_DERIVED_RULES);
|
|
28349
28845
|
logger.info(` Rollback: Reversing replacement of ${op.logicalId} (${op.resourceType}) — re-creating the old resource and deleting the new one`);
|
|
28350
28846
|
if (STATEFUL_TYPES.has(op.resourceType)) logger.warn(` ⚠ ${op.logicalId} (${op.resourceType}) is a stateful type — the old physical resource's data was destroyed by the replacement and CANNOT be recovered; the re-created resource starts empty.`);
|
|
28351
28847
|
const { provider: createProvider } = ctx.providerRegistry.getProviderFor({
|
|
@@ -28439,6 +28935,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
28439
28935
|
});
|
|
28440
28936
|
const desiredProps = await resolveReplayProps(previousState.properties, resolver, secrets, ctx, op.logicalId);
|
|
28441
28937
|
const currentProps = await resolveReplayProps(current.properties, resolver, secrets, ctx, op.logicalId);
|
|
28938
|
+
recordNestedStackParameterExpressions(secrets, op.resourceType, desiredProps, previousState.properties, STATE_DERIVED_RULES);
|
|
28442
28939
|
const revertResult = await updateWithRollbackRetry(provider, [
|
|
28443
28940
|
op.logicalId,
|
|
28444
28941
|
current.physicalId,
|
|
@@ -28591,6 +29088,7 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
|
|
|
28591
29088
|
});
|
|
28592
29089
|
const desiredProps = await resolveReplayProps(prev.properties, resolver, secrets, ctx, op.logicalId);
|
|
28593
29090
|
const attemptedProps = await resolveReplayProps(op.attemptedProperties ?? current.properties, resolver, secrets, ctx, op.logicalId);
|
|
29091
|
+
recordNestedStackParameterExpressions(secrets, op.resourceType, desiredProps, prev.properties, STATE_DERIVED_RULES);
|
|
28594
29092
|
const revertFailedResult = await updateWithRollbackRetry(provider, [
|
|
28595
29093
|
op.logicalId,
|
|
28596
29094
|
current.physicalId,
|
|
@@ -29533,6 +30031,8 @@ var DeployEngine = class {
|
|
|
29533
30031
|
* `this.buildResolverContext({...}, stackName)`.
|
|
29534
30032
|
*/
|
|
29535
30033
|
buildResolverContext(base, stackName) {
|
|
30034
|
+
const recordedSecretValues = /* @__PURE__ */ new Map();
|
|
30035
|
+
if (this.options.inheritedSecrets && this.options.inheritedSecrets.size > 0) inheritNestedStackParameterAssociations(recordedSecretValues, this.options.inheritedSecrets);
|
|
29536
30036
|
return {
|
|
29537
30037
|
template: base.template,
|
|
29538
30038
|
resources: base.resources,
|
|
@@ -29544,7 +30044,7 @@ var DeployEngine = class {
|
|
|
29544
30044
|
recordedImports: this.recordedImports,
|
|
29545
30045
|
recordedOutputReads: this.recordedOutputReads,
|
|
29546
30046
|
...this.options.inheritedSecrets && this.options.inheritedSecrets.size > 0 && { inheritedSecrets: this.options.inheritedSecrets },
|
|
29547
|
-
recordedSecretValues
|
|
30047
|
+
recordedSecretValues
|
|
29548
30048
|
};
|
|
29549
30049
|
}
|
|
29550
30050
|
/**
|
|
@@ -29571,7 +30071,9 @@ var DeployEngine = class {
|
|
|
29571
30071
|
redactParametersForDiff(parameterValues) {
|
|
29572
30072
|
const inherited = this.options.inheritedSecrets;
|
|
29573
30073
|
if (!inherited || inherited.size === 0) return parameterValues;
|
|
29574
|
-
|
|
30074
|
+
const out = {};
|
|
30075
|
+
for (const [name, value] of Object.entries(parameterValues)) out[name] = inheritedParameterExpression(inherited, name, value) ?? redactSecretsForState(value, inherited);
|
|
30076
|
+
return out;
|
|
29575
30077
|
}
|
|
29576
30078
|
/**
|
|
29577
30079
|
* Redact resolved secret plaintext out of a bag about to be PERSISTED to
|
|
@@ -30687,6 +31189,7 @@ var DeployEngine = class {
|
|
|
30687
31189
|
const resolvedProps = await this.resolver.resolve(desiredProps, context);
|
|
30688
31190
|
this.perResourceTemplateProps.set(logicalId, desiredProps);
|
|
30689
31191
|
const createSecrets = context.recordedSecretValues ?? /* @__PURE__ */ new Map();
|
|
31192
|
+
recordNestedStackParameterExpressions(createSecrets, resourceType, resolvedProps, desiredProps);
|
|
30690
31193
|
this.auditResolvedAssetReferences(logicalId, resourceType, resolvedProps);
|
|
30691
31194
|
this.attemptedResolvedProps.set(logicalId, resolvedProps);
|
|
30692
31195
|
const createDecision = this.providerRegistry.getProviderFor({
|
|
@@ -30731,6 +31234,7 @@ var DeployEngine = class {
|
|
|
30731
31234
|
this.perResourceSecrets.set(logicalId, updateSecrets);
|
|
30732
31235
|
const resolvedProps = await this.resolver.resolve(desiredProps, context);
|
|
30733
31236
|
this.perResourceTemplateProps.set(logicalId, desiredProps);
|
|
31237
|
+
recordNestedStackParameterExpressions(updateSecrets, resourceType, resolvedProps, desiredProps);
|
|
30734
31238
|
this.auditResolvedAssetReferences(logicalId, resourceType, resolvedProps);
|
|
30735
31239
|
this.attemptedResolvedProps.set(logicalId, resolvedProps);
|
|
30736
31240
|
if (JSON.stringify(redactSecretsForState(resolvedProps, updateSecrets, desiredProps)) === JSON.stringify(currentProps)) {
|
|
@@ -31398,5 +31902,5 @@ var DeployEngine = class {
|
|
|
31398
31902
|
};
|
|
31399
31903
|
|
|
31400
31904
|
//#endregion
|
|
31401
|
-
export { maskerOrIdentity as $,
|
|
31402
|
-
//# sourceMappingURL=deploy-engine-
|
|
31905
|
+
export { maskerOrIdentity as $, CFN_TEMPLATE_URL_LIMIT as $n, redactSecretsForState as $t, renderStatefulReason as A, buildDockerImage as An, ResourceTimeoutError as Ar, classifyReplaySecretRegion as At, exportAliasCollisionScrubWarning as B, synthesisStatusMessage as Bn, isMarkedNonRetryable as Br, describeTypeWithThrottleRetry as Bt, isFinalSnapshotError as C, isCrossRegionRedirect as Cn, LocalMigrateError as Cr, configBooleanRefusal as Ct, extractDeploymentEventError as D, validateContainerRepoName as Dn, NestedStackChildDirectDestroyError as Dr, requireConfigArray as Dt, makeCanonicalizePropertiesFn as E, validateAssetBucketName as En, MissingCdkCliError as Er, replayWarn as Et, green as F, runDockerForeground as Fn, SynthesisError as Fr, s3BucketRegionalDomainName as Ft, IAMRoleProvider as G, resolveCaptureObservedState as Gn, retryClassificationText as Gr, STATE_SOURCED_READBACK_RULES as Gt, secretBearingStateKeyWarning as H, getLegacyStateBucketName as Hn, isThrottlingError as Hr, DagBuilder as Ht, red as I, runDockerStreaming as In, formatError as Ir, s3BucketWebsiteUrl as It, ProviderRegistry as J, resolveStateBucketWithDefaultAndSource as Jn, dynamicReferenceTokens as Jt, collectInlinePolicyNamesManagedBySiblings as K, resolveSkipPrefix as Kn, __exportAll as Kr, TEMPLATE_SOURCED_RULES as Kt, yellow as L, AssetManifestLoader as Ln, isCdkdError as Lr, applyRoleArnIfSet as Lt, bold as M, formatDockerLoginError as Mn, StackHasActiveImportsError as Mr, s3BucketArn as Mt, cyan as N, getDockerCmd as Nn, StackTerminationProtectionError as Nr, s3BucketDomainName as Nt, MULTI_REGION_RECREATE_BLOCKED_TYPES as O, buildDenyExternalAccessPolicy as On, PartialFailureError as Or, requireConfigObject as Ot, gray as P, partitionSensitiveEnv as Pn, StateError as Pr, s3BucketDualStackDomainName as Pt, maskDeep as Q, CFN_TEMPLATE_BODY_LIMIT as Qn, maskSecretsInText as Qt, collectDeclaredOutputNames as R, getDockerImageBySourceHash as Rn, normalizeAwsError as Rr, DiffCalculator as Rt, createPreDeleteFinalSnapshot as S, getBootstrapMarkerKey as Sn, LocalInvokeBuildError as Sr, coerceCfnBoolean as St, unsupportedFinalSnapshotError as T, readBootstrapMarkerBody as Tn, LockError as Tr, readConfigString as Tt, stateKeySecretExposure as U, resolveApp as Un, markNonRetryable as Ur, TemplateParser as Ut, isExportAliasCollision as V, getDefaultStateBucketName as Vn, isRetryableTransientError as Vr, withRetry as Vt, getCurrentResourceSecrets as W, resolveAutoAssetStorage as Wn, markRedactedCause as Wr, STATE_SOURCED_CROSS_GENERATION_RULES as Wt, findSilentDropProperties as X, stateBucketExistenceConfirmed as Xn, isSingleDynamicReferenceToken as Xt, findActionableSilentDrops as Y, resolveUseCdkBootstrapAssets as Yn, errorCauseChain as Yt, createMaskedRetryLogger as Z, warnDeprecatedNoPrefixCliFlag as Zn, maskSecretsInError as Zt, computeImplicitDeleteEdges as _, stripControlChars as _n, ConfigError as _r, refStateLookupFromResource as _t, DeploymentEventsStore as a, exportNamesCarriedFrom as an, canonicalizeRegion as ar, isInterruptedWaitError as at, buildFinalSnapshotIdentifier as b, assertAssetBucketRegion as bn, DeployCancelledError as br, resolveExplicitPhysicalId as bt, replayFailedOperations as c, shouldRetainResource as cn, processStackMessages as cr, slowCcOperationTimeoutMs as ct, updatePartialReason as d, WorkGraph as dn, AwsClients as dr, IntrinsicFunctionResolver as dt, scrubResourceRecord as en, MIGRATE_TMP_PREFIX as er, CUSTOM_RESOURCE_RESPONSE_PREFIX as et, UNSPECIFIED_SKIP_REASON as f, buildAssetRedirectMap as fn, getAwsClients as fr, carriesDynamicReference as ft, IMPLICIT_DELETE_DEPENDENCIES as g, escapeRegExp$1 as gn, CdkdError as gr, parameterTypeMayLoseSecretIdentity as gt, maskingRetryLogger as h, rewriteTemplateAssetReferences as hn, AssetError as hr, isUnboundTemplateParameter as ht, DeploymentEventsReader as i, rebuildClientForBucketRegion as in, PARTITION_TABLE as ir, interruptWatchListenerCount as it, formatResourceLine as j, dockerSpawnEnvWithSensitive as jn, ResourceUpdateNotSupportedError as jr, producerRegionsFromState as jt, isStatefulRecreateTargetSync as k, describeAwsFailure as kn, ProvisioningError as kr, requireConfigString as kt, replayRollback as l, AssetPublisher as ln, clearBucketRegionCache as lr, disableInstanceApiTermination as lt, withResourceDeadline as m, loadPublishableAssetManifest as mn, setAwsClients as mr, getAccountInfo as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, displaySafe as nn, uploadCfnTemplate as nr, beginCommandInterruptScope as nt, planFailedOps as o, importableOutputKeys as on, derivePartitionAndUrlSuffix as or, startInterruptWatch as ot, deleteSkipReason as p, createAssetRedirectResolver as pn, resetAwsClients as pr, cfnRefValueFromPhysicalId as pt, clearOnUpdateRemoval as q, resolveStateBucketWithDefault as qn, createSecretMasker as qt, DeployEngine as r, S3StateBackend as rn, expectedOwnerParam as rr, endCommandInterruptScope as rt, planRollback as s, importableOutputs as sn, AssemblyReader as sr, CloudControlProvider as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, LockManager as tn, findLargeInlineResources as tr, DEFAULT_STATE_PREFIX as tt, updatePartialMessage as u, stringifyValue as un, resolveBucketRegion as ur, isTerminationProtectionPropagationError as ut, ATOMIC_FINAL_SNAPSHOT_TYPES as v, AssetModeResolver as vn, CrossAccountSecretRefusalError as vr, WAFv2WebACLProvider as vt, refusesFinalSnapshot as w, parseBootstrapMarker as wn, LocalStartServiceError as wr, configStringRefusal as wt, ccRoutedFinalSnapshotError as x, ensureAssetStorage as xn, DynamicReferenceRegionAmbiguousError as xr, assertRegionMatch as xt, PRE_DELETE_SNAPSHOT_TYPES as y, BOOTSTRAP_MARKER_PREFIX as yn, DependencyError as yr, normalizeAwsTagsToCfn as yt, collectPublishedOutputNames as z, Synthesizer as zn, withErrorHandling as zr, INTRINSIC_KEYS as zt };
|
|
31906
|
+
//# sourceMappingURL=deploy-engine-Cvkqa30x.js.map
|