@go-to-k/cdkd 0.290.12 → 0.290.14
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-Daqr0Ygq.js → asg-provider-BAYjqZuM.js} +2 -2
- package/dist/{asg-provider-Daqr0Ygq.js.map → asg-provider-BAYjqZuM.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-e6ZxOr56.js → deploy-engine-v7h1H_Ac.js} +421 -82
- package/dist/deploy-engine-v7h1H_Ac.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-Cts1UWod.js → program-DD0X9qLM.js} +527 -325
- package/dist/{program-Cts1UWod.js.map → program-DD0X9qLM.js.map} +1 -1
- package/dist/{version-CEPJTaHl.js → version-CqnAWzcc.js} +2 -2
- package/dist/{version-CEPJTaHl.js.map → version-CqnAWzcc.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-e6ZxOr56.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { C as displaySafe, S as displayIdent, d as applyDefaultNameForFallback, f as explicitNamePropertyFor, g as looksLikeCdkdGeneratedName, h as getCurrentStackName, m as generateResourceNameWithFallback, n as getLogger, p as generateResourceName, r as isStdoutReservedForPayload, s as getLiveRenderer, v as withStackName, x as UNRENDERABLE, y as withoutGeneratedFallbackName } from "./logger-C17H06QA.js";
|
|
2
2
|
import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
|
|
3
|
-
import { t as getCdkdVersion } from "./version-
|
|
3
|
+
import { t as getCdkdVersion } from "./version-CqnAWzcc.js";
|
|
4
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
6
|
import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetBucketReplicationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -804,6 +804,96 @@ function isNameCollisionError(message) {
|
|
|
804
804
|
return /(?<!\b(?:must|not|should|may|cannot)\s)already exists?\b/i.test(message) || message.includes("AlreadyExists");
|
|
805
805
|
}
|
|
806
806
|
/**
|
|
807
|
+
* SDK exception NAMES that mean, on their own, "a resource with this NAME
|
|
808
|
+
* already exists" — nothing else (issue
|
|
809
|
+
* [#3208](https://github.com/go-to-k/cdkd/issues/3208)).
|
|
810
|
+
*
|
|
811
|
+
* This list is the NARROW exception to the paragraph above, not a reversal of
|
|
812
|
+
* it. That paragraph refuses name-based classification because a name like
|
|
813
|
+
* `ResourceConflictException` is AMBIGUOUS — Lambda raises it for a function in
|
|
814
|
+
* a PENDING state too, so crediting it would delete a live function under
|
|
815
|
+
* `--replace`. Every name here is the opposite: the service declares it for the
|
|
816
|
+
* duplicate-name condition and for nothing else, so there is no second reading
|
|
817
|
+
* to be wrong about. A name may join this list only on that test.
|
|
818
|
+
*
|
|
819
|
+
* It exists because ELBv2 states the condition in prose that the message
|
|
820
|
+
* matcher above cannot see, and must not be widened to see. MEASURED against
|
|
821
|
+
* the real API (us-east-1, 2026-09-16, `@aws-sdk/client-elastic-load-balancing-v2`
|
|
822
|
+
* 3.1126.0), creating a second target group under a live name:
|
|
823
|
+
*
|
|
824
|
+
* name = 'DuplicateTargetGroupNameException'
|
|
825
|
+
* message = "A target group with the same name 'x' exists, but with
|
|
826
|
+
* different settings"
|
|
827
|
+
* cause = none
|
|
828
|
+
*
|
|
829
|
+
* The message carries no code and never says "already exists", so
|
|
830
|
+
* `isNameCollisionError` misses it — and the consequence is not cosmetic: the
|
|
831
|
+
* `--replace` delete-first fallback and the rollback executor's
|
|
832
|
+
* delete-new-first arm both gate on that predicate, so BOTH recovery paths went
|
|
833
|
+
* inert and a create-only change to a cdkd-named target group could not be
|
|
834
|
+
* deployed at all. Widening the prose matcher to a bare `exists` was rejected:
|
|
835
|
+
* it is substring-matched against every service's text, and the direction of a
|
|
836
|
+
* false positive here is a DELETE.
|
|
837
|
+
*
|
|
838
|
+
* The SET is complete rather than illustrative: `Duplicate*NameException` over
|
|
839
|
+
* every installed `@aws-sdk/client-*` model yields exactly these three, all
|
|
840
|
+
* ELBv2. Two ELBv2 siblings are deliberately EXCLUDED, and the reasons are the
|
|
841
|
+
* membership test in action — `DuplicateListenerException` reports a listener
|
|
842
|
+
* already bound to that PORT, which is not a name, and
|
|
843
|
+
* `DuplicateTagKeysException` reports repeated keys WITHIN one request, which
|
|
844
|
+
* is not an existence condition at all. Crediting either would hand a
|
|
845
|
+
* destructive path a collision that no delete can clear.
|
|
846
|
+
*/
|
|
847
|
+
const NAME_COLLISION_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
848
|
+
"DuplicateTargetGroupNameException",
|
|
849
|
+
"DuplicateLoadBalancerNameException",
|
|
850
|
+
"DuplicateTrustStoreNameException"
|
|
851
|
+
]);
|
|
852
|
+
/**
|
|
853
|
+
* {@link isNameCollisionError}, but reading the ERROR rather than a rendered
|
|
854
|
+
* message — which is the only way to see an exception NAME (issue #3208).
|
|
855
|
+
*
|
|
856
|
+
* Providers wrap an AWS failure as `Failed to create X: ${err.message}`, so the
|
|
857
|
+
* name is dropped before any caller could match it. This walks the bounded
|
|
858
|
+
* `cause` chain (the same {@link MAX_CAUSE_CHAIN_DEPTH} as every other
|
|
859
|
+
* classifier in this file), checking each link's `name` against
|
|
860
|
+
* {@link NAME_COLLISION_ERROR_NAMES} and each link's `message` through
|
|
861
|
+
* {@link isNameCollisionError}. A provider is required to thread the caught
|
|
862
|
+
* value as `cause` (`scripts/check-provider-error-cause.ts` enforces it across
|
|
863
|
+
* `src/provisioning/providers/**`), which is what makes the walk reach the SDK
|
|
864
|
+
* error at all.
|
|
865
|
+
*
|
|
866
|
+
* Prefer this at any site holding the error object. The string form stays for
|
|
867
|
+
* callers that genuinely have only text, and its behaviour is unchanged — this
|
|
868
|
+
* is strictly additive, so nothing that matched before stops matching.
|
|
869
|
+
*/
|
|
870
|
+
/**
|
|
871
|
+
* `String(value)` that cannot itself throw — a thrown `Object.create(null)` has
|
|
872
|
+
* no `toString`. Preserves the call sites' pre-#3208 `String(createError)` arm
|
|
873
|
+
* for a non-`Error` throw, which reading `.message` alone would have dropped.
|
|
874
|
+
*/
|
|
875
|
+
function stringifyForMatch(value) {
|
|
876
|
+
try {
|
|
877
|
+
return String(value);
|
|
878
|
+
} catch {
|
|
879
|
+
return "";
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
function isNameCollisionErrorFrom(error, logicalId) {
|
|
883
|
+
if (typeof error === "string") return isNameCollisionError(error);
|
|
884
|
+
let current = error;
|
|
885
|
+
for (let depth = 0; depth < MAX_CAUSE_CHAIN_DEPTH && current != null; depth++) {
|
|
886
|
+
const link = current;
|
|
887
|
+
if (typeof link.logicalId === "string" && link.logicalId !== logicalId) return false;
|
|
888
|
+
if (typeof link.name === "string" && NAME_COLLISION_ERROR_NAMES.has(link.name)) return true;
|
|
889
|
+
if (depth === 0) {
|
|
890
|
+
if (isNameCollisionError(error instanceof Error && typeof link.message === "string" ? link.message : stringifyForMatch(error))) return true;
|
|
891
|
+
}
|
|
892
|
+
current = link.cause;
|
|
893
|
+
}
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
807
897
|
* Match a same-name re-creation cooldown — an AWS service still holding a
|
|
808
898
|
* resource's name while its asynchronous delete finishes. Every recognised
|
|
809
899
|
* spelling lives in {@link NAME_COOLDOWN_ERROR_MESSAGE_PATTERNS}; add new ones
|
|
@@ -9133,6 +9223,53 @@ function shouldRetainResource(deletionPolicy) {
|
|
|
9133
9223
|
return deletionPolicy === "Retain" || deletionPolicy === "RetainExceptOnCreate";
|
|
9134
9224
|
}
|
|
9135
9225
|
/**
|
|
9226
|
+
* Can this container be read as the record map its type claims it is?
|
|
9227
|
+
*
|
|
9228
|
+
* THE single plain-object test behind every malformed-bag guard in the repo.
|
|
9229
|
+
* `parseStateBody` validates the root object and the schema version and
|
|
9230
|
+
* nothing inside, so a hand-edited or truncated record reaches a consumer with
|
|
9231
|
+
* `resources` / `outputs` / `attributes` / `properties` holding a string, a
|
|
9232
|
+
* list, a number or `null`. `Object.entries` walks a string or a list as
|
|
9233
|
+
* readily as a map, so either FABRICATES entries. `in` and `Object.hasOwn`
|
|
9234
|
+
* behave differently per shape and neither is a guard: `in` THROWS on a
|
|
9235
|
+
* string, a number and `null`, but ANSWERS on a list (`0 in [1,2]` and
|
|
9236
|
+
* `'length' in [1,2]` are both `true`), while `Object.hasOwn` ANSWERS on a
|
|
9237
|
+
* string and a list alike (`Object.hasOwn('abcdef', '0')` is `true`) and
|
|
9238
|
+
* throws ONLY on `null` / `undefined` — the one pair a `?? {}` upstream has
|
|
9239
|
+
* usually already absorbed, which is what makes it look like a guard. Do not
|
|
9240
|
+
* read a nearby `in` as already catching the list case. Widened past
|
|
9241
|
+
* `null` / `undefined` for
|
|
9242
|
+
* exactly that reason: `[]`, `5` and `"ab"` all survive `Object.entries` and
|
|
9243
|
+
* yield a bag that is empty or, for the string, absurd.
|
|
9244
|
+
*
|
|
9245
|
+
* WHY IT LIVES HERE rather than beside the guards that grew out of it. Its
|
|
9246
|
+
* first home was `src/state/malformed-resources-bag.ts`, which still re-exports
|
|
9247
|
+
* it so no importer moved; it came down to this module when
|
|
9248
|
+
* {@link importableOutputKeys} needed it (go-to-k/cdkd#3192). That helper sits
|
|
9249
|
+
* in `src/types/**`, the layer every other one imports and which imports
|
|
9250
|
+
* nothing itself, so the alternative — `src/types/state.ts` importing from
|
|
9251
|
+
* `src/state/**` — inverts the layering AND pulls that module's
|
|
9252
|
+
* `error-handler` / `display-safe` / `lock-contention-message` chain into the
|
|
9253
|
+
* one module the whole codebase depends on. Same move, same reason, as
|
|
9254
|
+
* `DEFAULT_STATE_PREFIX` in `src/state/state-prefix.ts`: home the leaf in the
|
|
9255
|
+
* layer that needs it, re-export from where it was.
|
|
9256
|
+
*
|
|
9257
|
+
* Spelling this test a second time at any call site is what the single export
|
|
9258
|
+
* exists to prevent: the copies could then drift, and the one that drifted
|
|
9259
|
+
* would fabricate again while looking guarded. Enumerate the consumers with
|
|
9260
|
+
* `grep -rn "isReadableBag" src/` rather than from a list here — three
|
|
9261
|
+
* successive enumerations of them, written by reasoning, came out incomplete.
|
|
9262
|
+
*
|
|
9263
|
+
* What it does NOT decide is whether a given container is malformed. That is a
|
|
9264
|
+
* per-container call, because ABSENCE means different things: an absent
|
|
9265
|
+
* `resources` bag is a defect, while an absent `outputs` bag is an ordinary
|
|
9266
|
+
* record `cdkd scrub` round-trips on purpose. Each caller pairs this predicate
|
|
9267
|
+
* with its own absence rule.
|
|
9268
|
+
*/
|
|
9269
|
+
function isReadableBag(container) {
|
|
9270
|
+
return typeof container === "object" && container !== null && !Array.isArray(container);
|
|
9271
|
+
}
|
|
9272
|
+
/**
|
|
9136
9273
|
* The keys of `state.outputs` an `Fn::ImportValue` may bind to (issue
|
|
9137
9274
|
* [#2193](https://github.com/go-to-k/cdkd/issues/2193)) — THE predicate
|
|
9138
9275
|
* behind "what does this stack export". Four readers used to answer that
|
|
@@ -9150,15 +9287,79 @@ function shouldRetainResource(deletionPolicy) {
|
|
|
9150
9287
|
*
|
|
9151
9288
|
* `outputs` is typed required but every consumer treats it as optional (a
|
|
9152
9289
|
* state file may simply have none), so it is read defensively here too.
|
|
9290
|
+
*
|
|
9291
|
+
* ## Two FAIL-CLOSED arms for a malformed record (go-to-k/cdkd#3192)
|
|
9292
|
+
*
|
|
9293
|
+
* Both answer `[]` — "this record exports nothing" — and neither repairs the
|
|
9294
|
+
* record or throws. The reasoning is the same for both and is a property of
|
|
9295
|
+
* WHERE this function sits: it is a pure predicate with no stack name, no
|
|
9296
|
+
* region and no writer of its own, reached from the exports-index rebuild, the
|
|
9297
|
+
* deploy-time resolver's state scan, `cdkd diff`'s no-change merge and the
|
|
9298
|
+
* local-command loader. Throwing here would be the bare `TypeError`
|
|
9299
|
+
* go-to-k/cdkd#3018 set out to remove, renamed; and refusing the whole
|
|
9300
|
+
* operation over ONE foreign record would take every OTHER stack's
|
|
9301
|
+
* `Fn::ImportValue` down with it. Publishing nothing from a record whose
|
|
9302
|
+
* export set cannot be read is the only answer that fabricates nothing and
|
|
9303
|
+
* destroys nothing — the damaged record is left exactly as it is, for the
|
|
9304
|
+
* write-capable commands (`cdkd orphan` / `import` / `scrub`) to REFUSE by
|
|
9305
|
+
* name.
|
|
9306
|
+
*
|
|
9307
|
+
* 1. `outputs` is not a readable bag. `Object.keys('abcdef')` is
|
|
9308
|
+
* `['0'...'5']`, so the pre-fix code published one fabricated export per
|
|
9309
|
+
* CHARACTER into `cdkd/_index/<region>/exports.json` — the shared index
|
|
9310
|
+
* every consumer's `Fn::ImportValue` binds against. {@link isReadableBag}
|
|
9311
|
+
* is the test, so an ABSENT bag keeps answering `[]` exactly as the old
|
|
9312
|
+
* `?? {}` did; no verdict moves on a healthy or an absent record.
|
|
9313
|
+
* 2. `exportNames` is present but not an array. `state.exportNames.filter(...)`
|
|
9314
|
+
* threw `TypeError: state.exportNames.filter is not a function` on a
|
|
9315
|
+
* hand-edited non-array or `null`, from `cdkd diff` among others. Falling
|
|
9316
|
+
* back to the `undefined` arm instead would be fail-OPEN in the one
|
|
9317
|
+
* direction #2193 exists to close — every plain Output name published as an
|
|
9318
|
+
* export — so a corrupt set is read as an EMPTY set, not as an unknown one.
|
|
9319
|
+
* A non-string element is dropped for the same reason: `Object.hasOwn` does
|
|
9320
|
+
* not throw on one, it COERCES, so `exportNames: [0]` would otherwise
|
|
9321
|
+
* publish the bag's `"0"` key. The filter is the identity on every healthy
|
|
9322
|
+
* `string[]`.
|
|
9153
9323
|
*/
|
|
9154
9324
|
function importableOutputKeys(state) {
|
|
9155
|
-
|
|
9325
|
+
if (!hasReadableExportSet(state)) return [];
|
|
9326
|
+
const outputs = state.outputs;
|
|
9156
9327
|
if (state.exportNames === void 0) return Object.keys(outputs);
|
|
9157
|
-
return state.exportNames.filter((name) => Object.hasOwn(outputs, name));
|
|
9328
|
+
return state.exportNames.filter((name) => typeof name === "string" && Object.hasOwn(outputs, name));
|
|
9329
|
+
}
|
|
9330
|
+
/**
|
|
9331
|
+
* Whether {@link importableOutputKeys} can answer from this record at all —
|
|
9332
|
+
* the two fail-closed arms of its own doc, as ONE predicate a caller can ask
|
|
9333
|
+
* (issue go-to-k/cdkd#3192).
|
|
9334
|
+
*
|
|
9335
|
+
* It exists because `[]` is a real answer: a stack that exports nothing and a
|
|
9336
|
+
* record whose export set could not be read produce the identical empty list,
|
|
9337
|
+
* and a caller that must SAY which one it got cannot re-derive the difference
|
|
9338
|
+
* from the list. `ExportIndexStore`'s rebuild is that caller — a silent
|
|
9339
|
+
* fail-closed there would leave the next `Fn::ImportValue` miss naming the
|
|
9340
|
+
* consumer rather than the damaged producer record.
|
|
9341
|
+
*
|
|
9342
|
+
* ONE spelling shared with {@link importableOutputKeys}, which calls this
|
|
9343
|
+
* rather than repeating the two tests: a second copy could drift, and the copy
|
|
9344
|
+
* that drifted would report a record readable while the key list stayed empty,
|
|
9345
|
+
* or the reverse.
|
|
9346
|
+
*
|
|
9347
|
+
* `outputs` absent answers FALSE here, unlike
|
|
9348
|
+
* `malformed-resources-bag.ts`'s `hasReadableOutputs`, and the difference is
|
|
9349
|
+
* the QUESTION rather than a disagreement: that one asks "is this record
|
|
9350
|
+
* damaged" (an absent bag is an ordinary record), this one asks "can an export
|
|
9351
|
+
* set be read off it" (it cannot). A caller that must tell absent from damaged
|
|
9352
|
+
* tests for absence first, as the exports-index rebuild does.
|
|
9353
|
+
*/
|
|
9354
|
+
function hasReadableExportSet(state) {
|
|
9355
|
+
if (!isReadableBag(state.outputs)) return false;
|
|
9356
|
+
if (state.exportNames === void 0) return true;
|
|
9357
|
+
if (!Array.isArray(state.exportNames)) return false;
|
|
9358
|
+
return state.exportNames.length === 0 || state.exportNames.some((n) => typeof n === "string");
|
|
9158
9359
|
}
|
|
9159
9360
|
/** `state.outputs` narrowed to its {@link importableOutputKeys}. */
|
|
9160
9361
|
function importableOutputs(state) {
|
|
9161
|
-
const outputs = state.outputs
|
|
9362
|
+
const outputs = isReadableBag(state.outputs) ? state.outputs : {};
|
|
9162
9363
|
const picked = Object.create(null);
|
|
9163
9364
|
for (const name of importableOutputKeys(state)) picked[name] = outputs[name];
|
|
9164
9365
|
return picked;
|
|
@@ -24506,6 +24707,16 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
24506
24707
|
* one.
|
|
24507
24708
|
*/
|
|
24508
24709
|
producerRegionGuest = false;
|
|
24710
|
+
/**
|
|
24711
|
+
* How a producer-region guest PRINTS its own `explicitRegion` (issue
|
|
24712
|
+
* [#3150](https://github.com/go-to-k/cdkd/issues/3150)): the masked log text
|
|
24713
|
+
* its creator held for that region, which a template can assemble around a
|
|
24714
|
+
* short secret, or `***` once a later spelling of the same region masks
|
|
24715
|
+
* differently. Display only; `undefined` on an ordinary resolver, whose
|
|
24716
|
+
* region its command built (the stack's synthesized or recorded region,
|
|
24717
|
+
* `--region`, or a region read out of a literal token).
|
|
24718
|
+
*/
|
|
24719
|
+
explicitRegionLogText;
|
|
24509
24720
|
strictGetAtt;
|
|
24510
24721
|
cfnFallback;
|
|
24511
24722
|
/**
|
|
@@ -24757,18 +24968,19 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
24757
24968
|
* uppercase spelling would build a second client for the same physical
|
|
24758
24969
|
* region — benign, but wasteful and confusing in a debug log.
|
|
24759
24970
|
*/
|
|
24760
|
-
clientsForRegion(targetRegion) {
|
|
24971
|
+
clientsForRegion(targetRegion, targetLogText) {
|
|
24761
24972
|
const ambient = getAwsClients();
|
|
24762
24973
|
if (!targetRegion) return ambient;
|
|
24763
24974
|
const target = canonicalizeRegion(targetRegion);
|
|
24764
|
-
|
|
24975
|
+
const loggedTarget = targetLogText ?? (targetRegion === this.explicitRegion ? this.explicitRegionLogText : void 0) ?? target;
|
|
24976
|
+
if (!isClientSafeRegion(target)) throw new Error(`Refusing to build AWS clients for the region '${stripControlChars(loggedTarget).slice(0, 64)}': it is not a valid AWS region name, and a region is substituted into the AWS service hostname.`);
|
|
24765
24977
|
if (typeof ambient.withRegion !== "function") return ambient;
|
|
24766
24978
|
const cached = this.regionScopedClients.get(target);
|
|
24767
24979
|
if (cached) return cached;
|
|
24768
24980
|
if (canonicalizeRegion(ambient.configuredRegion) === target) return ambient;
|
|
24769
24981
|
const scoped = ambient.withRegion(target);
|
|
24770
24982
|
this.regionScopedClients.set(target, scoped);
|
|
24771
|
-
this.logger.debug(`Using region-scoped AWS clients for ${
|
|
24983
|
+
this.logger.debug(`Using region-scoped AWS clients for ${loggedTarget}`);
|
|
24772
24984
|
return scoped;
|
|
24773
24985
|
}
|
|
24774
24986
|
/**
|
|
@@ -25358,7 +25570,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
25358
25570
|
return this.noteAttributeSecrecy(logicalId, attributeName, nameServers, context);
|
|
25359
25571
|
}
|
|
25360
25572
|
this.logger.debug(`Resolved Fn::GetAtt from attributes: ${logicalId}.${this.maskSecretsForLog(attributeName, context)} -> ${this.maskSecretsForLog(stringifyAttributeForLog(attributeName, this.maskValueLeaves(flatValue, context)), context)}`);
|
|
25361
|
-
if (resource.resourceType === NESTED_STACK_RESOURCE_TYPE$1 && attributeName.startsWith(NESTED_STACK_OUTPUT_ATTRIBUTE_PREFIX) && carriesDynamicReference(flatValue)) return await this.reresolveCrossStackValue(flatValue, nestedStackChildRegionFromLocalArn(resource.physicalId), context, `nested stack ${logicalId} ${attributeName}`, crossStackSourceKey({ "Fn::GetAtt": getAtt }));
|
|
25573
|
+
if (resource.resourceType === NESTED_STACK_RESOURCE_TYPE$1 && attributeName.startsWith(NESTED_STACK_OUTPUT_ATTRIBUTE_PREFIX) && carriesDynamicReference(flatValue)) return await this.reresolveCrossStackValue(flatValue, nestedStackChildRegionFromLocalArn(resource.physicalId), context, `nested stack ${logicalId} ${this.logTextOfLeaf(attributeName, context)}`, crossStackSourceKey({ "Fn::GetAtt": getAtt }));
|
|
25362
25574
|
return this.noteAttributeSecrecy(logicalId, attributeName, flatValue, context);
|
|
25363
25575
|
}
|
|
25364
25576
|
if (attributeName.includes(".")) {
|
|
@@ -25376,8 +25588,9 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
25376
25588
|
}
|
|
25377
25589
|
}
|
|
25378
25590
|
if (resource.resourceType === NESTED_STACK_RESOURCE_TYPE$1 && attributeName.startsWith(NESTED_STACK_OUTPUT_ATTRIBUTE_PREFIX)) {
|
|
25379
|
-
const declared = Object.keys(resource.attributes ?? {}).filter((k) => k.startsWith(NESTED_STACK_OUTPUT_ATTRIBUTE_PREFIX)).map((k) =>
|
|
25380
|
-
|
|
25591
|
+
const declared = Object.keys(resource.attributes ?? {}).filter((k) => k.startsWith(NESTED_STACK_OUTPUT_ATTRIBUTE_PREFIX)).sort().map((k) => this.maskSecretsForLog(this.outputNameLogText(k, context), context));
|
|
25592
|
+
const declaredText = declared.length > 0 ? declared.join(", ") : "(none)";
|
|
25593
|
+
throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.maskSecretsForLog(attributeName, context)}]: the nested stack '${logicalId}' declares no output named '${this.maskSecretsForLog(this.outputNameLogText(attributeName, context), context)}'. Its outputs are ${this.maskSecretsForLog(declaredText, context)}. Check the output name in the nested stack's template, and deploy the child stack again if you have just added it.`));
|
|
25381
25594
|
}
|
|
25382
25595
|
const value = await this.constructGuardedAttribute(resource, attributeName, context, logicalId);
|
|
25383
25596
|
this.logger.debug(`Resolved Fn::GetAtt: ${logicalId}.${this.maskSecretsForLog(attributeName, context)} -> ${this.maskSecretsForLog(stringifyAttributeForLog(attributeName, this.maskValueLeaves(value, context)), context)}`);
|
|
@@ -26664,7 +26877,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26664
26877
|
* the producer's rather than the caller's — which is exactly what issue #2057
|
|
26665
26878
|
* says the other two copies get wrong.
|
|
26666
26879
|
*/
|
|
26667
|
-
async reresolveCrossStackValue(value, producerRegion, context, origin, sourceKey, producerOutput) {
|
|
26880
|
+
async reresolveCrossStackValue(value, producerRegion, context, origin, sourceKey, producerOutput, producerRegionLogText) {
|
|
26668
26881
|
if (carriesSecretMask(value)) {
|
|
26669
26882
|
const recovered = producerOutput === void 0 || producerOutput.crossAccount === true ? void 0 : recoverMaskedOutput(producerOutput.stackName, producerOutput.region, producerOutput.outputKey);
|
|
26670
26883
|
if (recovered !== void 0) {
|
|
@@ -26680,7 +26893,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26680
26893
|
}
|
|
26681
26894
|
}
|
|
26682
26895
|
if (!carriesDynamicReference(value)) return value;
|
|
26683
|
-
const resolver = this.resolverForProducerRegion(producerRegion);
|
|
26896
|
+
const resolver = this.resolverForProducerRegion(producerRegion, context, producerRegionLogText);
|
|
26684
26897
|
const pinnedContext = producerRegion ? withoutProducerRegions(context) : context;
|
|
26685
26898
|
const walk = async (v) => {
|
|
26686
26899
|
if (typeof v === "string") return v.includes("{{resolve:") ? await resolver.resolveDynamicReferences(v, pinnedContext) : v;
|
|
@@ -26761,19 +26974,25 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26761
26974
|
if (secret) recordedSecretExpressions.add(expression);
|
|
26762
26975
|
else recordedSecretExpressions.delete(expression);
|
|
26763
26976
|
}
|
|
26764
|
-
resolverForProducerRegion(producerRegion) {
|
|
26977
|
+
resolverForProducerRegion(producerRegion, context, producerRegionLogText) {
|
|
26765
26978
|
if (!producerRegion) return this;
|
|
26766
26979
|
const target = canonicalizeRegion(producerRegion);
|
|
26767
26980
|
if (target === canonicalizeRegion(this.explicitRegion)) return this;
|
|
26981
|
+
const regionText = producerRegionLogText !== void 0 ? canonicalizeRegion(producerRegionLogText) : this.regionLogText(producerRegion, context);
|
|
26982
|
+
const guestRegionText = this.maskThenStripThenMask(regionText, context);
|
|
26768
26983
|
const cached = this.producerRegionResolvers.get(target);
|
|
26769
|
-
if (cached)
|
|
26984
|
+
if (cached) {
|
|
26985
|
+
if (cached.explicitRegionLogText !== guestRegionText) cached.explicitRegionLogText = "***";
|
|
26986
|
+
return cached;
|
|
26987
|
+
}
|
|
26770
26988
|
const scoped = new IntrinsicFunctionResolver(target, {
|
|
26771
26989
|
strictGetAtt: this.strictGetAtt,
|
|
26772
26990
|
cfnFallback: this.cfnFallback
|
|
26773
26991
|
});
|
|
26774
26992
|
scoped.producerRegionGuest = true;
|
|
26993
|
+
scoped.explicitRegionLogText = guestRegionText;
|
|
26775
26994
|
this.producerRegionResolvers.set(target, scoped);
|
|
26776
|
-
this.logger.debug(`Using a producer-region resolver for ${
|
|
26995
|
+
this.logger.debug(`Using a producer-region resolver for ${guestRegionText}`);
|
|
26777
26996
|
return scoped;
|
|
26778
26997
|
}
|
|
26779
26998
|
/**
|
|
@@ -26798,8 +27017,8 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26798
27017
|
}
|
|
26799
27018
|
if (entry && (!context.stackName || entry.producerStack !== context.stackName)) {
|
|
26800
27019
|
this.recordImport(context, exportName, entry.producerStack, entry.producerRegion);
|
|
26801
|
-
this.logger.info(`Resolved Fn::ImportValue: ${loggedExportName} (from index: ${this.maskSecretsForLog(entry.producerStack, context)} / ${entry.producerRegion}; ${carriesDynamicReference(entry.value) ? "redacted dynamic reference" : "literal value"})`);
|
|
26802
|
-
return await this.reresolveCrossStackValue(entry.value, entry.producerRegion, context, `Fn::ImportValue '${exportName}' (producer ${entry.producerStack} / ${entry.producerRegion})`, sourceKey, {
|
|
27020
|
+
this.logger.info(`Resolved Fn::ImportValue: ${loggedExportName} (from index: ${this.maskSecretsForLog(entry.producerStack, context)} / ${this.maskSecretsForLog(entry.producerRegion, context)}; ${carriesDynamicReference(entry.value) ? "redacted dynamic reference" : "literal value"})`);
|
|
27021
|
+
return await this.reresolveCrossStackValue(entry.value, entry.producerRegion, context, `Fn::ImportValue '${this.logTextOfLeaf(exportName, context)}' (producer ${this.logTextOfLeaf(entry.producerStack, context)} / ${this.logTextOfLeaf(entry.producerRegion, context)})`, sourceKey, {
|
|
26803
27022
|
stackName: entry.producerStack,
|
|
26804
27023
|
region: entry.producerRegion,
|
|
26805
27024
|
outputKey: exportName
|
|
@@ -26823,13 +27042,13 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26823
27042
|
}
|
|
26824
27043
|
const stateData = await context.stateBackend.getState(refStack, lookupRegion);
|
|
26825
27044
|
if (!stateData) {
|
|
26826
|
-
this.logger.debug(`No state found for stack: ${this.maskSecretsForLog(refStack, context)} (${lookupRegion})`);
|
|
27045
|
+
this.logger.debug(`No state found for stack: ${this.maskSecretsForLog(refStack, context)} (${this.maskSecretsForLog(lookupRegion, context)})`);
|
|
26827
27046
|
continue;
|
|
26828
27047
|
}
|
|
26829
27048
|
const { state } = stateData;
|
|
26830
27049
|
if (importableOutputKeys(state).includes(exportName)) {
|
|
26831
27050
|
const value = state.outputs[exportName];
|
|
26832
|
-
this.logger.info(`Resolved Fn::ImportValue: ${loggedExportName} (from stack: ${this.maskSecretsForLog(refStack, context)} / ${lookupRegion}; ${carriesDynamicReference(value) ? "redacted dynamic reference" : "literal value"})`);
|
|
27051
|
+
this.logger.info(`Resolved Fn::ImportValue: ${loggedExportName} (from stack: ${this.maskSecretsForLog(refStack, context)} / ${this.maskSecretsForLog(lookupRegion, context)}; ${carriesDynamicReference(value) ? "redacted dynamic reference" : "literal value"})`);
|
|
26833
27052
|
if (context.exportIndex) context.exportIndex.patchEntry(exportName, {
|
|
26834
27053
|
value,
|
|
26835
27054
|
producerStack: refStack,
|
|
@@ -26850,7 +27069,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26850
27069
|
continue;
|
|
26851
27070
|
}
|
|
26852
27071
|
}
|
|
26853
|
-
if (found) return await this.reresolveCrossStackValue(found.value, found.lookupRegion, context, `Fn::ImportValue '${exportName}' (producer ${found.refStack} / ${found.lookupRegion})`, sourceKey, {
|
|
27072
|
+
if (found) return await this.reresolveCrossStackValue(found.value, found.lookupRegion, context, `Fn::ImportValue '${this.logTextOfLeaf(exportName, context)}' (producer ${this.logTextOfLeaf(found.refStack, context)} / ${this.logTextOfLeaf(found.lookupRegion, context)})`, sourceKey, {
|
|
26854
27073
|
stackName: found.refStack,
|
|
26855
27074
|
region: found.lookupRegion,
|
|
26856
27075
|
outputKey: exportName
|
|
@@ -26915,9 +27134,11 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26915
27134
|
* `OutputName` is the overwhelmingly common cause, and the list is what makes
|
|
26916
27135
|
* the error actionable.
|
|
26917
27136
|
*
|
|
26918
|
-
*
|
|
26919
|
-
*
|
|
26920
|
-
*
|
|
27137
|
+
* `maskSecretsForLog` reads the consumer pass's log twin first (issue #3150),
|
|
27138
|
+
* so a key equal to a name this pass assembled around a short secret is
|
|
27139
|
+
* masked. Residual, stated rather than hidden: the needles and twins belong to the
|
|
27140
|
+
* CONSUMER's resolution, so a plaintext sitting in a PRODUCER key that this
|
|
27141
|
+
* consumer never resolved is not maskable from here. The cap is what bounds that case;
|
|
26921
27142
|
* `cdkd scrub` reporting the producer's own `secretBearingKeys` is the remedy.
|
|
26922
27143
|
*/
|
|
26923
27144
|
describeAvailableOutputs(keys, context) {
|
|
@@ -26949,7 +27170,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26949
27170
|
* logged for non-not-found failures). Same graceful-degradation
|
|
26950
27171
|
* contract as {@link lookupCfnExport}.
|
|
26951
27172
|
*/
|
|
26952
|
-
async lookupCfnStackOutputs(stackName, region, context) {
|
|
27173
|
+
async lookupCfnStackOutputs(stackName, region, context, loggedRegionText) {
|
|
26953
27174
|
const cacheKey = `${region}\0${stackName}`;
|
|
26954
27175
|
let fetch = this.cfnStackOutputsCache.get(cacheKey);
|
|
26955
27176
|
if (!fetch) {
|
|
@@ -26963,7 +27184,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26963
27184
|
return await fetch;
|
|
26964
27185
|
} catch (error) {
|
|
26965
27186
|
const message = error instanceof Error ? error.message : String(error);
|
|
26966
|
-
this.logger.warn(`Fn::GetStackOutput: CloudFormation DescribeStacks fallback failed for stack '${this.maskSecretsForLog(stackName, context)}' (${this.maskSecretsForLog(
|
|
27187
|
+
this.logger.warn(`Fn::GetStackOutput: CloudFormation DescribeStacks fallback failed for stack '${this.maskSecretsForLog(stackName, context)}' (${this.maskSecretsForLog(loggedRegionText, context)}): ${this.maskSecretsForLog(message, context)}. Grant cloudformation:DescribeStacks to resolve outputs from CloudFormation-managed stacks, or pass --no-cfn-fallback to disable the fallback.`);
|
|
26967
27188
|
return;
|
|
26968
27189
|
}
|
|
26969
27190
|
}
|
|
@@ -27068,12 +27289,14 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27068
27289
|
const outputName = await this.resolveValue(args["OutputName"], context);
|
|
27069
27290
|
if (typeof outputName !== "string" || outputName === "") throw new Error(`Fn::GetStackOutput: OutputName must resolve to a non-empty string, got ${typeof outputName}`);
|
|
27070
27291
|
let region = this.resolverRegion;
|
|
27292
|
+
let loggedRegionText = this.logTextOfLeaf(region, context);
|
|
27071
27293
|
if ("Region" in args && args["Region"] !== void 0 && args["Region"] !== null) {
|
|
27072
27294
|
const resolvedRegion = await this.resolveValue(args["Region"], context);
|
|
27073
27295
|
if (typeof resolvedRegion !== "string" || resolvedRegion === "") throw new Error(`Fn::GetStackOutput: Region must resolve to a non-empty string, got ${typeof resolvedRegion}`);
|
|
27074
27296
|
const requestedRegion = canonicalizeRegion(resolvedRegion);
|
|
27075
|
-
if (!isClientSafeRegion(requestedRegion)) throw new Error(`Fn::GetStackOutput: '${this.maskThenStripThenMask(resolvedRegion, context).slice(0, 64)}' is not a valid AWS region name. The region selects both the AWS endpoint and the state-file key, so cdkd will not use it.`);
|
|
27297
|
+
if (!isClientSafeRegion(requestedRegion)) throw new Error(`Fn::GetStackOutput: '${this.maskThenStripThenMask(this.logTextOfLeaf(resolvedRegion, context) !== resolvedRegion ? "***" : resolvedRegion, context).slice(0, 64)}' is not a valid AWS region name. The region selects both the AWS endpoint and the state-file key, so cdkd will not use it.`);
|
|
27076
27298
|
region = requestedRegion;
|
|
27299
|
+
loggedRegionText = this.regionLogText(resolvedRegion, context);
|
|
27077
27300
|
}
|
|
27078
27301
|
let roleArn;
|
|
27079
27302
|
if ("RoleArn" in args && args["RoleArn"] !== void 0 && args["RoleArn"] !== null) {
|
|
@@ -27081,15 +27304,15 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27081
27304
|
if (typeof raw !== "string" || raw === "") throw new Error(`Fn::GetStackOutput: RoleArn must be a literal string in the template (no Ref / Fn::GetAtt / Fn::Sub allowed for cross-account references). Got ${raw === null ? "null" : Array.isArray(raw) ? "array" : typeof raw}${typeof raw === "object" ? ` (intrinsic shape: ${JSON.stringify(raw).slice(0, 80)})` : ""}.`);
|
|
27082
27305
|
roleArn = raw;
|
|
27083
27306
|
}
|
|
27084
|
-
if (!roleArn && context.stackName && context.stackName === stackName && canonicalizeRegion(region) === canonicalizeRegion(this.resolverRegion)) throw new Error(`Fn::GetStackOutput: cannot reference own stack '${this.maskSecretsForLog(stackName, context)}' in the same region '${this.maskSecretsForLog(
|
|
27307
|
+
if (!roleArn && context.stackName && context.stackName === stackName && canonicalizeRegion(region) === canonicalizeRegion(this.resolverRegion)) throw new Error(`Fn::GetStackOutput: cannot reference own stack '${this.maskSecretsForLog(stackName, context)}' in the same region '${this.maskSecretsForLog(loggedRegionText, context)}'`);
|
|
27085
27308
|
const loggedStackName = this.maskSecretsForLog(stackName, context);
|
|
27086
27309
|
const loggedOutputName = this.maskSecretsForLog(outputName, context);
|
|
27087
|
-
const loggedRegion = this.maskSecretsForLog(
|
|
27310
|
+
const loggedRegion = this.maskSecretsForLog(loggedRegionText, context);
|
|
27088
27311
|
this.logger.debug(`Resolving Fn::GetStackOutput: StackName=${loggedStackName}, Region=${loggedRegion}, OutputName=${loggedOutputName}${roleArn ? `, RoleArn=${roleArn}` : ""}`);
|
|
27089
27312
|
const stateData = roleArn ? await this.getCrossAccountStackState(roleArn, stackName, region, context) : await this.getSameAccountStackState(stackName, region, context);
|
|
27090
27313
|
if (!stateData) {
|
|
27091
27314
|
if (!roleArn && this.cfnFallback) {
|
|
27092
|
-
const cfnOutputs = await this.lookupCfnStackOutputs(stackName, region, context);
|
|
27315
|
+
const cfnOutputs = await this.lookupCfnStackOutputs(stackName, region, context, loggedRegionText);
|
|
27093
27316
|
if (cfnOutputs) {
|
|
27094
27317
|
if (!Object.hasOwn(cfnOutputs, outputName)) {
|
|
27095
27318
|
const available = this.describeAvailableOutputs(Object.keys(cfnOutputs), context);
|
|
@@ -27111,12 +27334,12 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27111
27334
|
this.logger.info(`Resolved Fn::GetStackOutput: StackName=${loggedStackName}, Region=${loggedRegion}, OutputName=${loggedOutputName}${roleArn ? `, RoleArn=${roleArn}` : ""} (${carriesDynamicReference(value) ? "redacted dynamic reference" : "literal value"})`);
|
|
27112
27335
|
if (!roleArn) this.recordOutputRead(context, stackName, region, outputName);
|
|
27113
27336
|
if (roleArn && !context.skipDynamicReferences && carriesDynamicReference(value)) throw markNonRetryable(new CrossAccountSecretRefusalError(`Fn::GetStackOutput: output '${loggedOutputName}' of stack '${loggedStackName}' (${loggedRegion}) is a redacted dynamic reference, and this is a CROSS-ACCOUNT reference (RoleArn ${roleArn}). cdkd will not resolve a producer account's secret with the consumer's credentials — a same-named secret in the consumer account would answer instead. Export a non-secret value (e.g. the secret's ARN) and resolve it in the consumer stack, or reference the producer stack from within its own account.`));
|
|
27114
|
-
return await this.reresolveCrossStackValue(value, region, context, `Fn::GetStackOutput '${outputName}' (producer ${stackName} / ${
|
|
27337
|
+
return await this.reresolveCrossStackValue(value, region, context, `Fn::GetStackOutput '${this.logTextOfLeaf(outputName, context)}' (producer ${this.logTextOfLeaf(stackName, context)} / ${loggedRegionText})`, sourceKey, {
|
|
27115
27338
|
stackName,
|
|
27116
27339
|
region,
|
|
27117
27340
|
outputKey: outputName,
|
|
27118
27341
|
...roleArn ? { crossAccount: true } : {}
|
|
27119
|
-
});
|
|
27342
|
+
}, loggedRegionText);
|
|
27120
27343
|
}
|
|
27121
27344
|
/**
|
|
27122
27345
|
* Push a resolved `Fn::GetStackOutput` into the consumer's
|
|
@@ -27254,7 +27477,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27254
27477
|
if (typeof resolvedValue !== "string") throw new Error(`Fn::Base64: value must resolve to a string, got ${typeof resolvedValue}`);
|
|
27255
27478
|
const result = Buffer.from(resolvedValue).toString("base64");
|
|
27256
27479
|
const inputLogText = this.logTextOfLeaf(resolvedValue, context);
|
|
27257
|
-
if (context.recordedSecretValues && (inputLogText !== resolvedValue || this.
|
|
27480
|
+
if (context.recordedSecretValues && (inputLogText !== resolvedValue || this.maskNeedlesForLog(resolvedValue, context) !== resolvedValue)) recordMaskOnlyValue(context.recordedSecretValues, result);
|
|
27258
27481
|
this.logger.debug(`Resolved Fn::Base64: ${this.maskSecretsForLog(inputLogText, context)} -> ${this.maskSecretsForLog(inputLogText !== resolvedValue ? "***" : result, context)}`);
|
|
27259
27482
|
return result;
|
|
27260
27483
|
}
|
|
@@ -27284,21 +27507,23 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27284
27507
|
* given explicitly (see {@link explicitRegion}).
|
|
27285
27508
|
*/
|
|
27286
27509
|
let clientRegion;
|
|
27510
|
+
let loggedRegionText;
|
|
27287
27511
|
if (typeof resolvedValue === "string" && resolvedValue !== "") {
|
|
27288
27512
|
const requested = canonicalizeRegion(resolvedValue);
|
|
27289
|
-
if (!isClientSafeRegion(requested)) throw new Error(`Fn::GetAZs: '${this.maskThenStripThenMask(resolvedValue, context).slice(0, 64)}' is not a valid AWS region name. A region is substituted into the AWS service hostname, so cdkd will not build a client from it.`);
|
|
27513
|
+
if (!isClientSafeRegion(requested)) throw new Error(`Fn::GetAZs: '${this.maskThenStripThenMask(this.logTextOfLeaf(resolvedValue, context) !== resolvedValue ? "***" : resolvedValue, context).slice(0, 64)}' is not a valid AWS region name. A region is substituted into the AWS service hostname, so cdkd will not build a client from it.`);
|
|
27290
27514
|
region = requested;
|
|
27291
27515
|
clientRegion = requested;
|
|
27516
|
+
loggedRegionText = this.regionLogText(resolvedValue, context);
|
|
27292
27517
|
} else {
|
|
27293
27518
|
region = (await getAccountInfo(this.resolverRegion)).region;
|
|
27294
27519
|
clientRegion = this.explicitRegion;
|
|
27295
27520
|
}
|
|
27296
27521
|
const cached = cachedAvailabilityZones[region];
|
|
27297
27522
|
if (cached) {
|
|
27298
|
-
this.logger.debug(`Resolved Fn::GetAZs from cache: ${this.maskSecretsForLog(region, context)} -> ${JSON.stringify(this.maskValueLeaves(cached, context))}`);
|
|
27523
|
+
this.logger.debug(`Resolved Fn::GetAZs from cache: ${this.maskSecretsForLog(loggedRegionText ?? region, context)} -> ${JSON.stringify(this.maskValueLeaves(cached, context))}`);
|
|
27299
27524
|
return cached;
|
|
27300
27525
|
}
|
|
27301
|
-
const ec2Client = this.clientsForRegion(clientRegion).ec2;
|
|
27526
|
+
const ec2Client = this.clientsForRegion(clientRegion, loggedRegionText === void 0 ? void 0 : this.maskSecretsForLog(loggedRegionText, context)).ec2;
|
|
27302
27527
|
let azNames;
|
|
27303
27528
|
try {
|
|
27304
27529
|
azNames = ((await ec2Client.send(new DescribeAvailabilityZonesCommand({ Filters: [{
|
|
@@ -27309,11 +27534,11 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27309
27534
|
Values: ["available"]
|
|
27310
27535
|
}] }))).AvailabilityZones || []).map((az) => az.ZoneName).filter((name) => name !== void 0).sort();
|
|
27311
27536
|
} catch (error) {
|
|
27312
|
-
throw new Error(`Fn::GetAZs: failed to describe availability zones for region '${this.maskSecretsForLog(region, context)}': ${this.maskSecretsForLog(error instanceof Error ? error.message : String(error), context)}`);
|
|
27537
|
+
throw new Error(`Fn::GetAZs: failed to describe availability zones for region '${this.maskSecretsForLog(loggedRegionText ?? region, context)}': ${this.maskSecretsForLog(error instanceof Error ? error.message : String(error), context)}`);
|
|
27313
27538
|
}
|
|
27314
|
-
if (azNames.length === 0) throw new Error(`Fn::GetAZs: no availability zones returned for region '${this.maskSecretsForLog(region, context)}'. Either the region is not enabled on this account (opt-in regions must be enabled before use), or the request was answered by a different region's endpoint.`);
|
|
27539
|
+
if (azNames.length === 0) throw new Error(`Fn::GetAZs: no availability zones returned for region '${this.maskSecretsForLog(loggedRegionText ?? region, context)}'. Either the region is not enabled on this account (opt-in regions must be enabled before use), or the request was answered by a different region's endpoint.`);
|
|
27315
27540
|
cachedAvailabilityZones[region] = azNames;
|
|
27316
|
-
this.logger.debug(`Resolved Fn::GetAZs: ${this.maskSecretsForLog(region, context)} -> ${JSON.stringify(this.maskValueLeaves(azNames, context))}`);
|
|
27541
|
+
this.logger.debug(`Resolved Fn::GetAZs: ${this.maskSecretsForLog(loggedRegionText ?? region, context)} -> ${JSON.stringify(this.maskValueLeaves(azNames, context))}`);
|
|
27317
27542
|
return azNames;
|
|
27318
27543
|
}
|
|
27319
27544
|
/**
|
|
@@ -27350,8 +27575,37 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27350
27575
|
* Mask any resolved secret value out of a string bound for a log line, using
|
|
27351
27576
|
* the secrets recorded on the resolution pass (GHSA fix). No-op when the pass
|
|
27352
27577
|
* recorded no secrets.
|
|
27578
|
+
*
|
|
27579
|
+
* A value with a registered LOG TWIN prints as its twin first (issue
|
|
27580
|
+
* [#3150](https://github.com/go-to-k/cdkd/issues/3150)). The needle mask
|
|
27581
|
+
* matches a secret under {@link MIN_NEEDLE_LENGTH} only as the whole text,
|
|
27582
|
+
* so a name an `Fn::Sub` assembled around a 1-3 character secret (a map key,
|
|
27583
|
+
* a stack or output name, an attribute name, the secret id of a dynamic
|
|
27584
|
+
* reference) printed in the clear at every site that handed its RAW value
|
|
27585
|
+
* here, although the pass had registered where the secret sits. Looking the
|
|
27586
|
+
* twin up HERE, rather than at each such site, is what makes the class
|
|
27587
|
+
* closed for raw values: every one of those sites already calls this method.
|
|
27588
|
+
* The twin prints as it is: it is the raw text with masked spans, so it
|
|
27589
|
+
* holds no recorded needle the raw text lacks. When the needle mask ALSO
|
|
27590
|
+
* changes the raw text the whole text is masked instead, the rule
|
|
27591
|
+
* `logTwinText` applies to a Join / Sub line, since the two masks' spans
|
|
27592
|
+
* cannot be merged. What this cannot see is a value TRANSFORMED before it
|
|
27593
|
+
* arrives (a lowercased region, a sliced output name, an assembled
|
|
27594
|
+
* sentence); those sites derive their text from the twin themselves.
|
|
27353
27595
|
*/
|
|
27354
27596
|
maskSecretsForLog(text, context) {
|
|
27597
|
+
const registered = this.registeredLogTwin(text, context);
|
|
27598
|
+
if (registered === void 0) return this.maskNeedlesForLog(text, context);
|
|
27599
|
+
return this.maskNeedlesForLog(text, context) !== text ? "***" : registered;
|
|
27600
|
+
}
|
|
27601
|
+
/**
|
|
27602
|
+
* The needle mask alone: {@link maskSecretsForLog} without the log-twin
|
|
27603
|
+
* lookup. For the two DETECTORS that must ask the needle mask apart from the
|
|
27604
|
+
* position mask: `logTwinText`, which asks it of a value that already has a
|
|
27605
|
+
* twin, and `resolveBase64`, whose other operand is the position mask. A
|
|
27606
|
+
* message masks through `maskSecretsForLog`.
|
|
27607
|
+
*/
|
|
27608
|
+
maskNeedlesForLog(text, context) {
|
|
27355
27609
|
let masked = text;
|
|
27356
27610
|
const inherited = context?.inheritedSecrets;
|
|
27357
27611
|
if (inherited && inherited.size > 0) masked = maskSecretsInText(masked, inherited);
|
|
@@ -27381,7 +27635,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27381
27635
|
* resolver's mask-coverage checker sees a masker at the site.
|
|
27382
27636
|
*/
|
|
27383
27637
|
logTwinText(result, twin, context) {
|
|
27384
|
-
return twin !== result && this.
|
|
27638
|
+
return twin !== result && this.maskNeedlesForLog(result, context) !== result ? "***" : twin;
|
|
27385
27639
|
}
|
|
27386
27640
|
/**
|
|
27387
27641
|
* The log twins of `Fn::Split`'s pieces (issue #3100), each registered for
|
|
@@ -27429,16 +27683,15 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27429
27683
|
* the leaf treated as a resolution product — masked whole when it is a
|
|
27430
27684
|
* recorded secret, otherwise the masked twin this pass registered for it —
|
|
27431
27685
|
* and then `logTwinText`'s whole-line guard. The caller still wraps the
|
|
27432
|
-
* result in `maskSecretsForLog`.
|
|
27433
|
-
*
|
|
27434
|
-
*
|
|
27435
|
-
*
|
|
27436
|
-
*
|
|
27437
|
-
*
|
|
27438
|
-
*
|
|
27439
|
-
*
|
|
27440
|
-
*
|
|
27441
|
-
* already takes for a whole secret.
|
|
27686
|
+
* result in `maskSecretsForLog`. Used where the text is needed BEFORE that
|
|
27687
|
+
* masker, or apart from it: the `Fn::Base64` line and its detector, and the
|
|
27688
|
+
* names issue [#3150](https://github.com/go-to-k/cdkd/issues/3150)
|
|
27689
|
+
* transforms or composes before printing (`regionLogText`,
|
|
27690
|
+
* `outputNameLogText`, the invalid-region refusals, the cross-stack `origin`
|
|
27691
|
+
* strings). A leaf printed as it is needs none of it: `maskSecretsForLog`
|
|
27692
|
+
* looks the twin up itself, which is how `maskValueLeaves` gives its lines
|
|
27693
|
+
* and the THROWN messages it masks (the `Fn::Cidr` argument refusals, for
|
|
27694
|
+
* one) the same position mask.
|
|
27442
27695
|
*/
|
|
27443
27696
|
logTextOfLeaf(value, context) {
|
|
27444
27697
|
return this.logTwinText(value, this.logTwinOfProduct({
|
|
@@ -27446,6 +27699,85 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27446
27699
|
twin: value
|
|
27447
27700
|
}, context).twin, context);
|
|
27448
27701
|
}
|
|
27702
|
+
/**
|
|
27703
|
+
* The log text of a nested stack's OUTPUT name, the part of `attributeName`
|
|
27704
|
+
* after `Outputs.` (issue [#3150](https://github.com/go-to-k/cdkd/issues/3150)).
|
|
27705
|
+
* The twin registry is keyed by the exact string, so the suffix has no twin
|
|
27706
|
+
* of its own: it is sliced from the twin of the WHOLE attribute name. A twin
|
|
27707
|
+
* that no longer starts with the literal prefix has a mask over part of it
|
|
27708
|
+
* (a secret was written into the prefix), so the slice offset no longer
|
|
27709
|
+
* lines up and the suffix prints as `***`.
|
|
27710
|
+
*/
|
|
27711
|
+
outputNameLogText(attributeName, context) {
|
|
27712
|
+
const twin = this.logTextOfLeaf(attributeName, context);
|
|
27713
|
+
return twin.startsWith(NESTED_STACK_OUTPUT_ATTRIBUTE_PREFIX) ? twin.slice(8) : "***";
|
|
27714
|
+
}
|
|
27715
|
+
/**
|
|
27716
|
+
* The log text of a template-supplied REGION as the resolver uses it, i.e.
|
|
27717
|
+
* after `canonicalizeRegion` (issue #3150). The twin is taken from the RAW
|
|
27718
|
+
* value, the string a write registered, and lowercased the same way: a
|
|
27719
|
+
* lookup of the canonical form finds no twin once the raw value had an
|
|
27720
|
+
* upper-case part, and lowercasing leaves every `***` span where it was.
|
|
27721
|
+
*/
|
|
27722
|
+
regionLogText(rawRegion, context) {
|
|
27723
|
+
const canonical = canonicalizeRegion(rawRegion);
|
|
27724
|
+
const text = canonicalizeRegion(this.logTextOfLeaf(rawRegion, context));
|
|
27725
|
+
return text !== canonical && this.maskNeedlesForLog(canonical, context) !== canonical ? "***" : text;
|
|
27726
|
+
}
|
|
27727
|
+
/**
|
|
27728
|
+
* The log text of a name or token whose `twin` masks spans of `raw` (issue
|
|
27729
|
+
* [#3150](https://github.com/go-to-k/cdkd/issues/3150)). The twin prints
|
|
27730
|
+
* unless the needle mask ALSO changes `raw`; then the two must agree. When
|
|
27731
|
+
* the needle-masked `raw` IS the twin, both masked the same spans and the
|
|
27732
|
+
* twin is safe (`/probe/***` for a 4+ character secret the name holds
|
|
27733
|
+
* whole). When they differ, a recorded secret straddles a masked span
|
|
27734
|
+
* (`q7ab` across `id-***ab`), no text can honour both, and the name prints
|
|
27735
|
+
* as `***`. Coarser `logTwinText` gives up whenever both masks fire.
|
|
27736
|
+
*/
|
|
27737
|
+
straddleSafeTwin(raw, twin, context) {
|
|
27738
|
+
if (twin === raw) return twin;
|
|
27739
|
+
const needled = this.maskNeedlesForLog(raw, context);
|
|
27740
|
+
if (needled === raw) return twin;
|
|
27741
|
+
return needled === twin ? twin : "***";
|
|
27742
|
+
}
|
|
27743
|
+
/**
|
|
27744
|
+
* The log text of a name parsed out of a dynamic-reference token (issue
|
|
27745
|
+
* [#3150](https://github.com/go-to-k/cdkd/issues/3150)). `resolveSub` /
|
|
27746
|
+
* `resolveJoin` re-enter the dynamic-reference loop with the ASSEMBLED string
|
|
27747
|
+
* and its twin, so a secret id, JSON key, version stage or id, SSM parameter
|
|
27748
|
+
* name, service or ARN region assembled around a short secret has its masked
|
|
27749
|
+
* spelling in `tokenTwin` only. Every such name is a run of the token's
|
|
27750
|
+
* `:`-separated pieces, so its log text is the twin's run over the same
|
|
27751
|
+
* pieces. Nothing is registered: the pass's log-twin registry also decides
|
|
27752
|
+
* what `Fn::Base64` persists, so the mapping lives in the returned function.
|
|
27753
|
+
*
|
|
27754
|
+
* `tokenTwin` is the twin's own `{{resolve:...}}` token paired with this one.
|
|
27755
|
+
* Each run's text goes through `straddleSafeTwin`, so a name whose needle
|
|
27756
|
+
* mask disagrees with its twin prints as `***`. A name whose runs carry different
|
|
27757
|
+
* twins, or a token whose pieces do not pair with the twin's (a mask
|
|
27758
|
+
* covering a `:`), prints as `***` too.
|
|
27759
|
+
*
|
|
27760
|
+
* A name that is no run of the token is a default the caller synthesized
|
|
27761
|
+
* (`AWSCURRENT` for an empty version stage, `''` for an absent version id),
|
|
27762
|
+
* printed as it is. Should the token spell such a non-empty name inside a longer
|
|
27763
|
+
* piece, it prints as `***`: the run rule says nothing about that text.
|
|
27764
|
+
*/
|
|
27765
|
+
dynamicReferenceNameLogText(inner, tokenTwin, context) {
|
|
27766
|
+
const twinPieces = tokenTwin.slice(10, -2).split(":");
|
|
27767
|
+
const pieces = inner.split(":");
|
|
27768
|
+
if (twinPieces.length !== pieces.length) return () => "***";
|
|
27769
|
+
return (name) => {
|
|
27770
|
+
let text;
|
|
27771
|
+
for (let start = 0; start < pieces.length; start++) for (let end = start + 1; end <= pieces.length; end++) {
|
|
27772
|
+
if (pieces.slice(start, end).join(":") !== name) continue;
|
|
27773
|
+
const twin = this.straddleSafeTwin(name, twinPieces.slice(start, end).join(":"), context);
|
|
27774
|
+
if (text !== void 0 && text !== twin) return "***";
|
|
27775
|
+
text = twin;
|
|
27776
|
+
break;
|
|
27777
|
+
}
|
|
27778
|
+
return text ?? (name !== "" && inner.includes(name) ? "***" : name);
|
|
27779
|
+
};
|
|
27780
|
+
}
|
|
27449
27781
|
/** The bag a pass's log twins are keyed by (issue #3100): the recorded one, else the inherited one. */
|
|
27450
27782
|
logTwinBag(context) {
|
|
27451
27783
|
return context?.recordedSecretValues ?? context?.inheritedSecrets;
|
|
@@ -27581,7 +27913,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27581
27913
|
maskValueLeaves(value, context) {
|
|
27582
27914
|
const done = /* @__PURE__ */ new Map();
|
|
27583
27915
|
const walk = (node) => {
|
|
27584
|
-
if (typeof node === "string") return this.maskSecretsForLog(
|
|
27916
|
+
if (typeof node === "string") return this.maskSecretsForLog(node, context);
|
|
27585
27917
|
if (node === null || typeof node !== "object") return node;
|
|
27586
27918
|
const memo = done.get(node);
|
|
27587
27919
|
if (memo !== void 0) return memo;
|
|
@@ -27593,7 +27925,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27593
27925
|
}
|
|
27594
27926
|
const out = Object.create(null);
|
|
27595
27927
|
done.set(node, out);
|
|
27596
|
-
for (const [key, child] of Object.entries(node)) out[this.maskSecretsForLog(
|
|
27928
|
+
for (const [key, child] of Object.entries(node)) out[this.maskSecretsForLog(key, context)] = walk(child);
|
|
27597
27929
|
return out;
|
|
27598
27930
|
};
|
|
27599
27931
|
return walk(value);
|
|
@@ -27620,14 +27952,14 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27620
27952
|
* visible. The FIRST mask is demonstrated by a test: deleting it (masking
|
|
27621
27953
|
* only after the strip) reds the split-needle case, because the recorded
|
|
27622
27954
|
* needle is then the split form and the strip has destroyed it. The SECOND
|
|
27623
|
-
* mask
|
|
27624
|
-
*
|
|
27625
|
-
*
|
|
27626
|
-
*
|
|
27627
|
-
*
|
|
27628
|
-
*
|
|
27629
|
-
*
|
|
27630
|
-
*
|
|
27955
|
+
* mask earns its place when the bag holds a needle that is the STRIPPED form
|
|
27956
|
+
* of the text in hand. A value this resolver resolved cannot be that text
|
|
27957
|
+
* (the split copy is itself recorded, so the first mask catches it), but a
|
|
27958
|
+
* template LITERAL can: issue #3150's region-scoped clients refusal prints
|
|
27959
|
+
* a producer-region guest's region through this helper, and a literal ARN
|
|
27960
|
+
* region spelling a recorded `st-1` as `s` + U+0001 + `t-1` is masked only
|
|
27961
|
+
* by the second pass (`intrinsic-resolver-name-argument-log-twin.test.ts`
|
|
27962
|
+
* pins both halves on that route).
|
|
27631
27963
|
*
|
|
27632
27964
|
* THE BOUND, since this file's job is to state them: this covers a needle
|
|
27633
27965
|
* split by a character `stripControlChars` removes. A needle split by
|
|
@@ -27661,7 +27993,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27661
27993
|
* NEEDLE match that straddles a masked span, which is why the log lines go
|
|
27662
27994
|
* through `logTwinText` rather than masking the twin alone.
|
|
27663
27995
|
*/
|
|
27664
|
-
async resolveDynamicReferencesWithLogTwin(value, logTwin, context) {
|
|
27996
|
+
async resolveDynamicReferencesWithLogTwin(value, logTwin, context, inherited) {
|
|
27665
27997
|
const pattern = /\{\{resolve:([^}]+)\}\}/g;
|
|
27666
27998
|
let result = value;
|
|
27667
27999
|
let twin = logTwin;
|
|
@@ -27671,14 +28003,22 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27671
28003
|
fullMatch: match[0],
|
|
27672
28004
|
inner: match[1]
|
|
27673
28005
|
});
|
|
27674
|
-
|
|
28006
|
+
const twinTokens = dynamicReferenceTokens(logTwin);
|
|
28007
|
+
const tokensAligned = twinTokens.length === matches.length;
|
|
28008
|
+
for (const [index, { fullMatch, inner }] of matches.entries()) {
|
|
27675
28009
|
const service = inner.split(":")[0];
|
|
28010
|
+
const pairedTwin = tokensAligned ? twinTokens[index] : void 0;
|
|
28011
|
+
const tokenLogText = inherited?.tokenLogText ?? (pairedTwin === void 0 ? "***" : this.straddleSafeTwin(fullMatch, pairedTwin, context));
|
|
28012
|
+
const nameLogText = inherited?.nameLogText ?? (pairedTwin === void 0 ? () => "***" : this.dynamicReferenceNameLogText(inner, pairedTwin, context));
|
|
27676
28013
|
const isKnownSecret = service === "secretsmanager" || service === "ssm-secure" || recordedSecretExpressions.has(fullMatch);
|
|
27677
28014
|
if (isKnownSecret && context?.skipDynamicReferences) continue;
|
|
27678
28015
|
const regionVerdict = classifyReplaySecretRegion(fullMatch, this.explicitRegion ?? this.resolverRegion, context?.producerRegions);
|
|
27679
|
-
if (regionVerdict.kind === "ambiguous") throw markNonRetryable(new DynamicReferenceRegionAmbiguousError(`Refusing to resolve the secret reference ${this.maskSecretsForLog(
|
|
28016
|
+
if (regionVerdict.kind === "ambiguous") throw markNonRetryable(new DynamicReferenceRegionAmbiguousError(`Refusing to resolve the secret reference ${this.maskSecretsForLog(tokenLogText, context)}: it names '${this.maskSecretsForLog(nameLogText(regionVerdict.secretName), context)}' without a region, and this stack reads from ${this.maskSecretsForLog(regionVerdict.foreignProducerRegions.map((r) => this.maskSecretsForLog(r, context)).join(", "), context)} as well as its own region. cdkd cannot tell which one must answer, and resolving against the wrong one yields a different secret. Spell the reference as a full ARN to say which region owns it.`));
|
|
27680
28017
|
if (regionVerdict.kind === "named-region") {
|
|
27681
|
-
const foreign = await this.resolverForProducerRegion(regionVerdict.region).resolveDynamicReferencesWithLogTwin(fullMatch, fullMatch, withoutProducerRegions(context)
|
|
28018
|
+
const foreign = await this.resolverForProducerRegion(regionVerdict.region, context, nameLogText(regionVerdict.region)).resolveDynamicReferencesWithLogTwin(fullMatch, fullMatch, withoutProducerRegions(context), {
|
|
28019
|
+
tokenLogText,
|
|
28020
|
+
nameLogText
|
|
28021
|
+
});
|
|
27682
28022
|
result = result.replace(fullMatch, () => foreign.result);
|
|
27683
28023
|
twin = twin.replace(fullMatch, () => foreign.twin);
|
|
27684
28024
|
continue;
|
|
@@ -27709,10 +28049,10 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27709
28049
|
* which caches nothing (issue #1933).
|
|
27710
28050
|
*/
|
|
27711
28051
|
let cacheable = true;
|
|
27712
|
-
if (service === "secretsmanager") resolved = await this.resolveSecretsManagerReference(inner, context);
|
|
28052
|
+
if (service === "secretsmanager") resolved = await this.resolveSecretsManagerReference(inner, context, nameLogText);
|
|
27713
28053
|
else if (service === "ssm") {
|
|
27714
28054
|
const decrypt = context?.skipDynamicReferences !== true;
|
|
27715
|
-
const param = await this.resolveSSMReference(parts, decrypt, "ssm", context);
|
|
28055
|
+
const param = await this.resolveSSMReference(parts, decrypt, "ssm", context, nameLogText);
|
|
27716
28056
|
if (param.type === "SecureString") this.pinSecretVerdict(fullMatch, true);
|
|
27717
28057
|
else if (!param.secure) this.pinSecretVerdict(fullMatch, false);
|
|
27718
28058
|
else cacheable = false;
|
|
@@ -27722,12 +28062,12 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27722
28062
|
}
|
|
27723
28063
|
resolved = param.value;
|
|
27724
28064
|
} else if (service === "ssm-secure") {
|
|
27725
|
-
const param = await this.resolveSSMReference(parts, true, "ssm-secure", context);
|
|
27726
|
-
if (!param.secure) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Refusing to resolve ${this.maskSecretsForLog(
|
|
28065
|
+
const param = await this.resolveSSMReference(parts, true, "ssm-secure", context, nameLogText);
|
|
28066
|
+
if (!param.secure) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Refusing to resolve ${this.maskSecretsForLog(tokenLogText, context)}: the parameter is a ${param.type} parameter, and the ssm-secure spelling is defined for SecureString parameters only. Reference it as {{resolve:ssm:...}} if it is public configuration.`));
|
|
27727
28067
|
isSecret = true;
|
|
27728
28068
|
resolved = param.value;
|
|
27729
28069
|
} else {
|
|
27730
|
-
this.logger.warn(this.maskSecretsForLog(`Unsupported dynamic reference service: ${service}`, context));
|
|
28070
|
+
this.logger.warn(this.maskSecretsForLog(`Unsupported dynamic reference service: ${this.maskSecretsForLog(nameLogText(String(service)), context)}`, context));
|
|
27731
28071
|
continue;
|
|
27732
28072
|
}
|
|
27733
28073
|
if (cacheable) this.cachedDynamicReferences.set(fullMatch, {
|
|
@@ -27763,7 +28103,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27763
28103
|
* runs when no mid-string ":SecretString:" delimiter is present, so the json-key / version
|
|
27764
28104
|
* forms are unaffected.)
|
|
27765
28105
|
*/
|
|
27766
|
-
async resolveSecretsManagerReference(inner, context) {
|
|
28106
|
+
async resolveSecretsManagerReference(inner, context, nameLogText) {
|
|
27767
28107
|
const afterService = inner.substring(15);
|
|
27768
28108
|
let secretId;
|
|
27769
28109
|
let jsonKey = "";
|
|
@@ -27792,9 +28132,9 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27792
28132
|
} else secretId = afterService;
|
|
27793
28133
|
if (!versionStage) versionStage = "AWSCURRENT";
|
|
27794
28134
|
if (!secretId) throw new Error("Dynamic reference: secretsmanager SECRET_ID is required");
|
|
27795
|
-
const loggedSecretId = this.maskSecretsForLog(secretId, context);
|
|
27796
|
-
const loggedJsonKey = this.maskSecretsForLog(jsonKey, context);
|
|
27797
|
-
this.logger.debug(`Resolving dynamic reference: secretsmanager:${loggedSecretId}:SecretString:${loggedJsonKey}:${this.maskSecretsForLog(versionStage, context)}:${this.maskSecretsForLog(versionId, context)}`);
|
|
28135
|
+
const loggedSecretId = this.maskSecretsForLog(nameLogText(secretId), context);
|
|
28136
|
+
const loggedJsonKey = this.maskSecretsForLog(nameLogText(jsonKey), context);
|
|
28137
|
+
this.logger.debug(`Resolving dynamic reference: secretsmanager:${loggedSecretId}:SecretString:${loggedJsonKey}:${this.maskSecretsForLog(nameLogText(versionStage), context)}:${this.maskSecretsForLog(nameLogText(versionId), context)}`);
|
|
27798
28138
|
const client = this.clientsForRegion(this.explicitRegion).secretsManager;
|
|
27799
28139
|
const command = new GetSecretValueCommand({
|
|
27800
28140
|
SecretId: secretId,
|
|
@@ -27955,10 +28295,10 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
27955
28295
|
* discard the value when `secure` is set — it is ciphertext, not the resolved
|
|
27956
28296
|
* reference.
|
|
27957
28297
|
*/
|
|
27958
|
-
async resolveSSMReference(parts, decrypt
|
|
28298
|
+
async resolveSSMReference(parts, decrypt, service, context, nameLogText) {
|
|
27959
28299
|
const parameterName = parts.slice(1).join(":");
|
|
27960
28300
|
if (!parameterName) throw new Error(`Dynamic reference: ${service} PARAMETER_NAME is required`);
|
|
27961
|
-
const loggedParameterName = this.maskSecretsForLog(parameterName, context);
|
|
28301
|
+
const loggedParameterName = this.maskSecretsForLog(nameLogText(parameterName), context);
|
|
27962
28302
|
this.logger.debug(`Resolving dynamic reference: ${service}:${loggedParameterName}`);
|
|
27963
28303
|
const client = this.clientsForRegion(this.explicitRegion).ssm;
|
|
27964
28304
|
const command = new GetParameterCommand({
|
|
@@ -29310,7 +29650,7 @@ var CloudControlProvider = class {
|
|
|
29310
29650
|
const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
|
|
29311
29651
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
29312
29652
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
29313
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
29653
|
+
const { ASGProvider } = await import("./asg-provider-BAYjqZuM.js").then((n) => n.n);
|
|
29314
29654
|
const asgProvider = new ASGProvider();
|
|
29315
29655
|
return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
|
|
29316
29656
|
}
|
|
@@ -38011,7 +38351,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
38011
38351
|
});
|
|
38012
38352
|
} catch (createError) {
|
|
38013
38353
|
const msg = createError instanceof Error ? createError.message : String(createError);
|
|
38014
|
-
if (!
|
|
38354
|
+
if (!isNameCollisionErrorFrom(createError, op.logicalId)) throw createError;
|
|
38015
38355
|
if (rollbackRetainsNewResource(current)) throw markNonRetryable(new CdkdError(maskSecretsInText(`Cannot reverse the replacement of ${safe(op.logicalId)} (${safe(op.resourceType)}): the re-create of the old resource (${safe(prev.physicalId)}) collided with the name still held by the new one (${safe(current.physicalId)}), and UpdateReplacePolicy: Retain pins that new resource in place, so cdkd will not delete it to free the name. Delete the new resource yourself, or remove UpdateReplacePolicy: Retain, then re-run \`cdkd rollback\` — the journal is kept, so the revert resumes from here. To leave THIS resource alone and let the rest of the rollback proceed, re-run with ` + (typeof op.logicalId === "string" && PASTEABLE_LOGICAL_ID.test(op.logicalId) ? `\`cdkd rollback --orphan ${op.logicalId}\`: one op failure stops the ` : "`cdkd rollback --orphan <id>` (the id is withheld: it is not a plain CloudFormation logical id, so a pasted command could be reshaped by the shell or name a different resource -- read it from `cdkd events`): one op failure stops the ") + `segment loop, so a single pinned resource otherwise halts every OLDER segment too. Underlying collision: ${displaySafe(msg)}`, secrets), "NAMED_REPLACEMENT_COLLISION", maskSecretsInError(createError instanceof Error ? createError : void 0, secrets)));
|
|
38016
38356
|
logger.info(` Rollback: re-create collided with the new resource's name — deleting the new resource (${displaySafe(current.physicalId)}) first...`);
|
|
38017
38357
|
{
|
|
@@ -41148,7 +41488,7 @@ var DeployEngine = class DeployEngine {
|
|
|
41148
41488
|
createResult = await this.withRetry(() => withCurrentResourceSecrets(updateSecrets, () => replaceProvider.create(logicalId, resourceType, replaceProps, { maskSecrets: createSecretMasker(updateSecrets) })), logicalId, void 0, void 0, replaceProvider);
|
|
41149
41489
|
} catch (createError) {
|
|
41150
41490
|
const createMsg = createError instanceof Error ? createError.message : String(createError);
|
|
41151
|
-
if (!
|
|
41491
|
+
if (!isNameCollisionErrorFrom(createError, logicalId)) throw createError;
|
|
41152
41492
|
const nameOrigin = this.replacementNameOrigin(logicalId, currentResource.physicalId);
|
|
41153
41493
|
if (updateReplacePolicy === "Retain") throw new CdkdError(`${logicalId} (${resourceType}) requires replacement, but its physical name is still held by the existing resource AND UpdateReplacePolicy: Retain pins that resource in place. ${nameOrigin.descriptor}. ${nameOrigin.remedy} — with Retain, the old resource keeps the name, so a same-name replacement can never proceed.`, "NAMED_REPLACEMENT_COLLISION");
|
|
41154
41494
|
if (this.options.replace !== true) throw new CdkdError(`${logicalId} (${resourceType}) requires replacement, but the create-first attempt collided with the existing resource: ${createMsg}. ${nameOrigin.descriptor}, so the CloudFormation-style safe replacement order (create the new resource before deleting the old) cannot reuse the occupied name — CloudFormation refuses this shape with "cannot update a stack when a custom-named resource requires replacing". ${nameOrigin.remedy}, or re-run with \`cdkd deploy --replace\` to delete the old resource FIRST and recreate it under the same name (the resource is briefly unavailable while it is recreated).`, "NAMED_REPLACEMENT_COLLISION");
|
|
@@ -41284,8 +41624,7 @@ var DeployEngine = class DeployEngine {
|
|
|
41284
41624
|
createResult = await this.withRetry(() => withCurrentResourceSecrets(updateSecrets, () => replProvider.create(logicalId, resourceType, replProps, { maskSecrets: createSecretMasker(updateSecrets) })), logicalId, void 0, void 0, replProvider);
|
|
41285
41625
|
} catch (createError) {
|
|
41286
41626
|
if (!retainOldOnReplace) throw new Error(maskSecretsInText(`Failed to create ${logicalId} after the UPDATE-not-supported replacement: the old resource (${currentResource.physicalId}) is now gone. Cause: ${createError instanceof Error ? createError.message : String(createError)}. Re-run the deploy to create it fresh.`, updateSecrets), { cause: createError instanceof Error ? createError : void 0 });
|
|
41287
|
-
|
|
41288
|
-
if (!isNameCollisionError(createMsg)) throw createError;
|
|
41627
|
+
if (!isNameCollisionErrorFrom(createError, logicalId)) throw createError;
|
|
41289
41628
|
const nameOrigin = this.replacementNameOrigin(logicalId, currentResource.physicalId);
|
|
41290
41629
|
throw markNonRetryable(new CdkdError(`${logicalId} (${resourceType}) requires replacement because the provisioning layer cannot update it in place — but its physical name is still held by the existing resource AND UpdateReplacePolicy: Retain pins that resource in place. ${nameOrigin.descriptor}. ${nameOrigin.remedy} — with Retain, the old resource keeps the name, so a same-name replacement can never proceed. Removing UpdateReplacePolicy: Retain lets cdkd delete the old resource first, which destroys it and any data it holds.`, "NAMED_REPLACEMENT_COLLISION", createError instanceof Error ? createError : void 0));
|
|
41291
41630
|
}
|
|
@@ -41569,7 +41908,7 @@ var DeployEngine = class DeployEngine {
|
|
|
41569
41908
|
if (!(error instanceof ProvisioningError)) return void 0;
|
|
41570
41909
|
const physicalId = error.physicalId;
|
|
41571
41910
|
if (!physicalId) return void 0;
|
|
41572
|
-
if (!
|
|
41911
|
+
if (!isNameCollisionErrorFrom(error, logicalId)) return void 0;
|
|
41573
41912
|
const stackName = getCurrentStackName();
|
|
41574
41913
|
if (!looksLikeCdkdGeneratedName(physicalId, logicalId, stackName)) return void 0;
|
|
41575
41914
|
if (!stackName) return void 0;
|
|
@@ -42039,5 +42378,5 @@ var DeployEngine = class DeployEngine {
|
|
|
42039
42378
|
};
|
|
42040
42379
|
|
|
42041
42380
|
//#endregion
|
|
42042
|
-
export { secretBearing as $,
|
|
42043
|
-
//# sourceMappingURL=deploy-engine-
|
|
42381
|
+
export { secretBearing as $, BOOTSTRAP_MARKER_PREFIX as $n, getAwsClients as $r, s3BucketRegionalDomainName as $t, extractDeploymentEventError as A, markNonRetryable as Ai, buildForceUnlockCommand as An, resolveAutoAssetStorage as Ar, parameterTypeMayLoseSecretIdentity as At, toCfnInteger as B, importableOutputs as Bn, MIGRATE_TMP_PREFIX as Br, configIntegerRefusal as Bt, buildFinalSnapshotIdentifier as C, isCdkdError as Ci, recordMaskOnlyValue as Cn, AssetManifestLoader as Cr, IntrinsicFunctionResolver as Ct, refusesFinalSnapshot as D, isRetryableTransientError as Di, LockManager as Dn, getDefaultStateBucketName as Dr, getAccountInfo as Dt, isFinalSnapshotError as E, isMarkedNonRetryable as Ei, scrubResourceRecord as En, synthesisStatusMessage as Er, coerceParameterTypedValue as Et, isStatefulRecreateTargetSync as F, CUSTOM_RESOURCE_RESPONSE_PREFIX as Fn, resolveUseCdkBootstrapAssets as Fr, resolveExplicitPhysicalId as Ft, gray as G, AssetPublisher as Gn, PARTITION_TABLE as Gr, requireConfigObject as Gt, formatResourceLine as H, orphansAfterRollback as Hn, uploadCfnTemplate as Hr, readConfigString as Ht, renderStatefulReason as I, DEFAULT_STATE_PREFIX as In, stateBucketExistenceConfirmed as Ir, assertRegionMatch as It, yellow as J, buildAssetRedirectMap as Jn, AssemblyReader as Jr, producerRegionsFromState as Jt, green as K, stringifyValue as Kn, canonicalizeRegion as Kr, requireConfigString as Kt, WARM_THROUGHPUT_MEMBERS as L, exportNamesCarriedFrom as Ln, warnDeprecatedNoPrefixCliFlag as Lr, coerceCfnBoolean as Lt, planOrphanAdoption as M, retryClassificationText as Mi, forceQuitRecoveryClause as Mn, resolveSkipPrefix as Mr, isSettledInstanceState as Mt, MULTI_REGION_RECREATE_BLOCKED_TYPES as N, __exportAll as Ni, formatLockExpiry as Nn, resolveStateBucketWithDefault as Nr, WAFv2WebACLProvider as Nt, unsupportedFinalSnapshotError as O, isThrottlingError as Oi, S3StateBackend as On, getLegacyStateBucketName as Or, isImpossibleEmptyStoredAttribute as Ot, isStatefulRecreateTargetForReplace as P, shellQuote as Pn, resolveStateBucketWithDefaultAndSource as Pr, normalizeAwsTagsToCfn as Pt, isExportAliasCollision as Q, AssetModeResolver as Qn, AwsClients as Qr, s3BucketDualStackDomainName as Qt, coerceWarmThroughput as R, hasReadableExportSet as Rn, CFN_TEMPLATE_BODY_LIMIT as Rr, coerceCfnInteger as Rt, PRE_DELETE_SNAPSHOT_TYPES as S, formatError as Si, maskSecretsInText as Sn, stripControlChars as Sr, isTerminationProtectionPropagationError as St, createPreDeleteFinalSnapshot as T, withErrorHandling as Ti, redactSecretsForState as Tn, Synthesizer as Tr, cfnRefValueFromPhysicalId as Tt, bold as U, orphansCarriedFrom as Un, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as Ur, replayWarn as Ut, toFiniteNumber as V, isReadableBag as Vn, findLargeInlineResources as Vr, configStringRefusal as Vt, cyan as W, shouldRetainResource as Wn, expectedOwnerParam as Wr, requireConfigArray as Wt, collectPublishedOutputNames as X, loadPublishableAssetManifest as Xn, clearBucketRegionCache as Xr, s3BucketArn as Xt, collectDeclaredOutputNames as Y, createAssetRedirectResolver as Yn, processStackMessages as Yr, withSharedDrainBudget as Yt, exportAliasCollisionScrubWarning as Z, rewriteTemplateAssetReferences as Zn, resolveBucketRegion as Zr, s3BucketDomainName as Zt, isSecretBearingReferenceString as _, ResourceUpdateNotSupportedError as _i, errorCauseChain as _n, partitionSensitiveEnv as _r, slowCcOperationTimeoutMs as _t, DeploymentEventsStore as a, CrossAccountSecretRefusalError as ai, findSilentDropProperties as an, readBootstrapMarkerBody as ar, clearOnUpdateRemoval as at, bindingSkippedOutputs as b, StateError as bi, markSameGenerationBag as bn, runDockerStreaming as br, deleteSkipReason as bt, replayFailedOperations as c, DynamicReferenceRegionAmbiguousError as ci, DagBuilder as cn, buildDenyExternalAccessPolicy as cr, createMaskedRetryLogger as ct, updatePartialReason as d, LocalStartServiceError as di, STATE_SOURCED_CROSS_GENERATION_RULES as dn, describeDockerCapturedOutput as dr, beginCommandInterruptScope as dt, resetAwsClients as ei, s3BucketWebsiteUrl as en, assertAssetBucketRegion as er, secretBearingStateKeyWarning as et, withResourceDeadline as f, LockError as fi, STATE_SOURCED_READBACK_RULES as fn, describeDockerExecFailure as fr, endCommandInterruptScope as ft, bagHoldsSecretExpression as g, ResourceTimeoutError as gi, dynamicReferenceTokens as gn, getDockerCmd as gr, CloudControlProvider as gt, computeImplicitDeleteEdges as h, ProvisioningError as hi, createSecretMasker as hn, formatDockerLoginError as hr, startInterruptWatch as ht, DeploymentEventsReader as i, ConfigError as ii, findActionableSilentDrops as in, parseBootstrapMarker as ir, collectInlinePolicyNamesManagedBySiblings as it, makeSiblingClaimReader as j, markRedactedCause as ji, buildLockContentionMessage as jn, resolveCaptureObservedState as jr, refStateLookupFromResource as jt, makeCanonicalizePropertiesFn as k, isTransientServerError as ki, rebuildClientForBucketRegion as kn, resolveApp as kr, isUnboundTemplateParameter as kt, replayRollback as l, IntrinsicResolutionRefusalError as li, TemplateParser as ln, describeAwsFailure as lr, maskDeep as lt, IMPLICIT_DELETE_DEPENDENCIES as m, PartialFailureError as mi, carriesSecretMask as mn, dockerSpawnEnvWithSensitive as mr, isInterruptedWaitError as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, AssetError as ni, DiffCalculator as nn, getBootstrapMarkerKey as nr, getCurrentResourceSecrets as nt, planFailedOps as o, DependencyError as oi, describeTypeWithThrottleRetry as on, validateAssetBucketName as or, ProviderRegistry as ot, maskingRetryLogger as p, NestedStackChildDirectDestroyError as pi, TEMPLATE_SOURCED_RULES as pn, describeDockerFailure as pr, interruptWatchListenerCount as pt, red as q, WorkGraph as qn, derivePartitionAndUrlSuffix as qr, classifyReplaySecretRegion as qt, DeployEngine as r, CdkdError as ri, INTRINSIC_KEYS as rn, isCrossRegionRedirect as rr, IAMRoleProvider as rt, planRollback as s, DeployCancelledError as si, withRetry as sn, validateContainerRepoName as sr, wouldReturnToSdkProvider as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, setAwsClients as ti, applyRoleArnIfSet as tn, ensureAssetStorage as tr, secretSafeKeyDisplay as tt, updatePartialMessage as u, LocalInvokeBuildError as ui, STATE_SOURCED_BASELINE_RULES as un, buildDockerImage as ur, maskerOrIdentity as ut, keptWholeReasonText as v, StackHasActiveImportsError as vi, identityKeyFor as vn, redactDockerArgvValues as vr, UNSPECIFIED_SKIP_REASON as vt, ccRoutedFinalSnapshotError as w, normalizeAwsError as wi, recoverMaskedOutput as wn, getDockerImageBySourceHash as wr, carriesDynamicReference as wt, ATOMIC_FINAL_SNAPSHOT_TYPES as x, SynthesisError as xi, maskSecretsInError as xn, escapeRegExp$1 as xr, disableInstanceApiTermination as xt, mergeNoChangeOutputs as y, StackTerminationProtectionError as yi, isSingleDynamicReferenceToken as yn, runDockerForeground as yr, deleteIndeterminateGuards as yt, isWarmThroughputDecrease as z, importableOutputKeys as zn, CFN_TEMPLATE_URL_LIMIT as zr, configBooleanRefusal as zt };
|
|
42382
|
+
//# sourceMappingURL=deploy-engine-v7h1H_Ac.js.map
|