@go-to-k/cdkd 0.289.3 → 0.289.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{asg-provider-rQ1BW281.js → asg-provider-CpxZ9sVH.js} +3 -3
- package/dist/{asg-provider-rQ1BW281.js.map → asg-provider-CpxZ9sVH.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-DAykhfpV.js → deploy-engine-BEpYvI14.js} +267 -121
- package/dist/deploy-engine-BEpYvI14.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{logger-D2Md1kTq.js → logger-CecsQ1UT.js} +53 -3
- package/dist/logger-CecsQ1UT.js.map +1 -0
- package/dist/{program-DLd8dIyf.js → program-CyIOIvtX.js} +139 -32
- package/dist/{program-DLd8dIyf.js.map → program-CyIOIvtX.js.map} +1 -1
- package/dist/{version-DPykgdNN.js → version-C3wRsCF2.js} +2 -2
- package/dist/{version-DPykgdNN.js.map → version-C3wRsCF2.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-DAykhfpV.js.map +0 -1
- package/dist/logger-D2Md1kTq.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { 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 } from "./logger-
|
|
1
|
+
import { 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, y as displaySafe } from "./logger-CecsQ1UT.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-C3wRsCF2.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";
|
|
@@ -1554,7 +1554,10 @@ function isCdkdError(error) {
|
|
|
1554
1554
|
function formatError(error) {
|
|
1555
1555
|
if (isCdkdError(error)) {
|
|
1556
1556
|
let message = `${error.name}: ${error.message}`;
|
|
1557
|
-
if (error.cause)
|
|
1557
|
+
if (error.cause) {
|
|
1558
|
+
const cause = displaySafe(error.cause.message);
|
|
1559
|
+
if (cause) message += `\nCaused by: ${cause}`;
|
|
1560
|
+
}
|
|
1558
1561
|
return message;
|
|
1559
1562
|
}
|
|
1560
1563
|
if (error instanceof Error) return `${error.name}: ${error.message}`;
|
|
@@ -3987,56 +3990,6 @@ async function expectedOwnerParam(client) {
|
|
|
3987
3990
|
return owner ? { ExpectedBucketOwner: owner } : {};
|
|
3988
3991
|
}
|
|
3989
3992
|
|
|
3990
|
-
//#endregion
|
|
3991
|
-
//#region src/utils/display-safe.ts
|
|
3992
|
-
/**
|
|
3993
|
-
* Make an untrusted value safe to render in a terminal or persist into a log.
|
|
3994
|
-
*
|
|
3995
|
-
* A LEAF module with no imports, and deliberately in `src/utils/` rather than
|
|
3996
|
-
* beside its first caller: issue [#2170](https://github.com/go-to-k/cdkd/issues/2170)'s
|
|
3997
|
-
* review found the same rule being widened BY HAND one module at a time and
|
|
3998
|
-
* missing an instance every round — the change sanitized 1 of 5 readers of
|
|
3999
|
-
* `LockInfo.owner`. One shared definition, imported by everything that renders
|
|
4000
|
-
* such a value, is what stops the next reader from inheriting nothing.
|
|
4001
|
-
*
|
|
4002
|
-
* The stripped class is wider than C0 + DEL, which a first cut used and which
|
|
4003
|
-
* misses every mechanism that actually forges a line:
|
|
4004
|
-
*
|
|
4005
|
-
* - `U+0085` (NEL) and the C1 range — xterm reads `U+009B` as CSI in UTF-8;
|
|
4006
|
-
* - `U+2028` / `U+2029` — this text is PERSISTED and re-rendered by JSON and
|
|
4007
|
-
* web log viewers, where both are line terminators;
|
|
4008
|
-
* - `U+202A`-`U+202E` / `U+2066`-`U+2069` — the Trojan-Source bidi overrides
|
|
4009
|
-
* and isolates, which visually REORDER the command being pasted.
|
|
4010
|
-
*
|
|
4011
|
-
* Known residual, recorded rather than implied away: the invisible formatters
|
|
4012
|
-
* (`U+200B`-`U+200D`, `U+FEFF`) and the bidi MARKS (`U+200E` / `U+200F` /
|
|
4013
|
-
* `U+061C`) survive, as do bare RTL letters, which no denylist can reach. All
|
|
4014
|
-
* of them can only make a rendered name differ visually from its bytes — the
|
|
4015
|
-
* command a user pastes still acts on exactly what is shown, and the blast
|
|
4016
|
-
* radius stays the attacker's own stack name.
|
|
4017
|
-
*
|
|
4018
|
-
* A caller whose value has a KNOWN ASCII charset (a stack name, an AWS region)
|
|
4019
|
-
* should pass `asciiOnly`, which is a positive allowlist and therefore has no
|
|
4020
|
-
* such residual at all.
|
|
4021
|
-
*
|
|
4022
|
-
* ONE CALLER DELIBERATELY GOES WIDER, and it is recorded here so an editor of
|
|
4023
|
-
* the residual note above knows a second module now disagrees with it.
|
|
4024
|
-
* `src/deployment/outputs-export-alias.ts` deletes a class derived from
|
|
4025
|
-
* `\p{Cc}` / `\p{Cf}` / `\p{Zl}` / `\p{Zp}` /
|
|
4026
|
-
* `\p{Default_Ignorable_Code_Point}`, because on THAT path the subject is a possibly
|
|
4027
|
-
* secret-bearing name in an operator's log: a plaintext split by a zero-width
|
|
4028
|
-
* character is READ as if it were contiguous, so it is disclosed without any
|
|
4029
|
-
* paste, and the command-forgery reasoning above does not transfer (issue
|
|
4030
|
-
* [#2874](https://github.com/go-to-k/cdkd/issues/2874)). Nothing here changes
|
|
4031
|
-
* -- widening this helper would alter every caller that merely wants a
|
|
4032
|
-
* terminal-safe string.
|
|
4033
|
-
*/
|
|
4034
|
-
function displaySafe(value, opts) {
|
|
4035
|
-
if (value === void 0 || value === null) return "";
|
|
4036
|
-
const text = String(value);
|
|
4037
|
-
return (opts?.asciiOnly ? text.replace(/[^ -~]/g, " ") : text.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029\u202a-\u202e\u2066-\u2069]/g, " ")).trim();
|
|
4038
|
-
}
|
|
4039
|
-
|
|
4040
3993
|
//#endregion
|
|
4041
3994
|
//#region src/state/s3-replication-purge-gap.ts
|
|
4042
3995
|
/**
|
|
@@ -9656,18 +9609,20 @@ var S3StateBackend = class {
|
|
|
9656
9609
|
async getState(stackName, region) {
|
|
9657
9610
|
await this.ensureClientForBucket();
|
|
9658
9611
|
const newKey = this.getStateKey(stackName, region);
|
|
9612
|
+
const shownStackName = this.displayName(stackName);
|
|
9613
|
+
const shownRegionName = displaySafe(region, { asciiOnly: true }) || "<unrenderable>";
|
|
9659
9614
|
try {
|
|
9660
|
-
this.logger.debug(`Getting state for stack: ${
|
|
9615
|
+
this.logger.debug(`Getting state for stack: ${shownStackName} (${shownRegionName})`);
|
|
9661
9616
|
const response = await this.s3Client.send(new GetObjectCommand({
|
|
9662
9617
|
Bucket: this.config.bucket,
|
|
9663
9618
|
...await this.ownerParam(),
|
|
9664
9619
|
Key: newKey
|
|
9665
9620
|
}));
|
|
9666
|
-
if (!response.Body) throw new StateError(`State file for stack '${
|
|
9667
|
-
if (!response.ETag) throw new StateError(`State file for stack '${
|
|
9621
|
+
if (!response.Body) throw new StateError(`State file for stack '${shownStackName}' (${shownRegionName}) has no body`);
|
|
9622
|
+
if (!response.ETag) throw new StateError(`State file for stack '${shownStackName}' (${shownRegionName}) has no ETag`);
|
|
9668
9623
|
const bodyString = await response.Body.transformToString();
|
|
9669
9624
|
const state = this.parseStateBody(bodyString, stackName);
|
|
9670
|
-
this.logger.debug(`Retrieved state: ${
|
|
9625
|
+
this.logger.debug(`Retrieved state: ${shownStackName} (${shownRegionName}), ETag: ${response.ETag}`);
|
|
9671
9626
|
return {
|
|
9672
9627
|
state,
|
|
9673
9628
|
etag: response.ETag
|
|
@@ -9675,13 +9630,15 @@ var S3StateBackend = class {
|
|
|
9675
9630
|
} catch (error) {
|
|
9676
9631
|
if (!isNoSuchKey(error)) {
|
|
9677
9632
|
if (error instanceof StateError) throw error;
|
|
9678
|
-
|
|
9633
|
+
const detail = displaySafe(error instanceof Error ? error.message : String(error), { asciiOnly: true }) || "<unrenderable>";
|
|
9634
|
+
throw new StateError(`Failed to get state for stack '${shownStackName}' (${shownRegionName}): ${detail}`, error instanceof Error ? error : void 0);
|
|
9679
9635
|
}
|
|
9680
|
-
this.logger.debug(`No state at new key for stack: ${
|
|
9636
|
+
this.logger.debug(`No state at new key for stack: ${shownStackName} (${shownRegionName})`);
|
|
9681
9637
|
}
|
|
9682
9638
|
const legacy = await this.tryGetLegacy(stackName, region);
|
|
9683
9639
|
if (legacy) {
|
|
9684
|
-
|
|
9640
|
+
const shownKey = displaySafe(this.getLegacyStateKey(stackName), { asciiOnly: true }) || "<unrenderable>";
|
|
9641
|
+
this.logger.warn(`Loaded legacy state for stack '${shownStackName}' from '${shownKey}'. It will be migrated to the region-scoped layout on next save.`);
|
|
9685
9642
|
return {
|
|
9686
9643
|
...legacy,
|
|
9687
9644
|
migrationPending: true
|
|
@@ -10309,7 +10266,7 @@ var S3StateBackend = class {
|
|
|
10309
10266
|
const bodyString = await response.Body.transformToString();
|
|
10310
10267
|
const state = this.parseStateBody(bodyString, stackName);
|
|
10311
10268
|
if (state.region && state.region !== region) {
|
|
10312
|
-
this.logger.debug(`Legacy state for stack '${stackName}' has region '${state.region}', not '${region}' — skipping legacy fallback.`);
|
|
10269
|
+
this.logger.debug(`Legacy state for stack '${this.displayName(stackName)}' has region '${displaySafe(state.region, { asciiOnly: true }) || "<unrenderable>"}', not '${displaySafe(region, { asciiOnly: true }) || "<unrenderable>"}' — skipping legacy fallback.`);
|
|
10313
10270
|
return null;
|
|
10314
10271
|
}
|
|
10315
10272
|
return {
|
|
@@ -10318,7 +10275,8 @@ var S3StateBackend = class {
|
|
|
10318
10275
|
};
|
|
10319
10276
|
} catch (error) {
|
|
10320
10277
|
if (isNoSuchKey(error)) return null;
|
|
10321
|
-
|
|
10278
|
+
const detail = displaySafe(error instanceof Error ? error.message : String(error), { asciiOnly: true }) || "<unrenderable>";
|
|
10279
|
+
throw new StateError(`Failed to get legacy state for stack '${this.displayName(stackName)}': ${detail}`, error instanceof Error ? error : void 0);
|
|
10322
10280
|
}
|
|
10323
10281
|
}
|
|
10324
10282
|
/**
|
|
@@ -10332,10 +10290,15 @@ var S3StateBackend = class {
|
|
|
10332
10290
|
try {
|
|
10333
10291
|
parsed = JSON.parse(bodyString);
|
|
10334
10292
|
} catch (error) {
|
|
10335
|
-
|
|
10293
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
10294
|
+
const detail = displaySafe(raw, { asciiOnly: true }) || "<unrenderable>";
|
|
10295
|
+
throw new StateError(`State file for stack '${this.displayName(stackName)}' is not valid JSON: ${detail}`, error instanceof Error ? error : void 0);
|
|
10336
10296
|
}
|
|
10337
10297
|
const v = parsed.version;
|
|
10338
|
-
if (v !== void 0 && !STATE_SCHEMA_VERSIONS_READABLE.includes(v))
|
|
10298
|
+
if (v !== void 0 && !STATE_SCHEMA_VERSIONS_READABLE.includes(v)) {
|
|
10299
|
+
const shown = displaySafe(String(v), { asciiOnly: true }) || "<unrenderable>";
|
|
10300
|
+
throw new StateError(`Unsupported state schema version ${shown} for stack '${this.displayName(stackName)}'. This cdkd binary supports versions ${STATE_SCHEMA_VERSIONS_READABLE.join(", ")}. Upgrade cdkd to a version that supports schema ${shown}.`);
|
|
10301
|
+
}
|
|
10339
10302
|
return parsed;
|
|
10340
10303
|
}
|
|
10341
10304
|
};
|
|
@@ -10381,6 +10344,20 @@ const MIN_RENEWAL_INTERVAL_MS = 1e3;
|
|
|
10381
10344
|
*/
|
|
10382
10345
|
const LOCK_OBJECT_DESCRIPTION = "a stack lock heartbeat, which records the lock owner, its acquisition timestamp, its deadline and the operation name — no secret, but one row per renewal";
|
|
10383
10346
|
/**
|
|
10347
|
+
* One spelling of "this value came from an S3 key or a lock record, and is
|
|
10348
|
+
* about to be interpolated into a message a terminal will render". A stack
|
|
10349
|
+
* name and an AWS region both have a known charset, so the ASCII allowlist is
|
|
10350
|
+
* a no-op on every legitimate input while an S3 key admits any UTF-8.
|
|
10351
|
+
*
|
|
10352
|
+
* Call it for a stack name or a region. Not every value in this file goes
|
|
10353
|
+
* through it, and this comment does not say which do: three revisions of that
|
|
10354
|
+
* sentence carried a count or a scope claim and each was wrong.
|
|
10355
|
+
* `grep safeSegment` answers it exactly.
|
|
10356
|
+
*/
|
|
10357
|
+
function safeSegment(value) {
|
|
10358
|
+
return displaySafe(value, { asciiOnly: true }) || "<unrenderable>";
|
|
10359
|
+
}
|
|
10360
|
+
/**
|
|
10384
10361
|
* S3-based lock manager using conditional writes (If-None-Match)
|
|
10385
10362
|
*
|
|
10386
10363
|
* Implements distributed locking using S3's If-None-Match: "*" condition
|
|
@@ -10585,7 +10562,7 @@ var LockManager = class {
|
|
|
10585
10562
|
}
|
|
10586
10563
|
return false;
|
|
10587
10564
|
}
|
|
10588
|
-
throw new LockError(`Failed to acquire lock for stack '${
|
|
10565
|
+
throw new LockError(`Failed to acquire lock for stack '${safeSegment(stackName)}' (${safeSegment(region)}): ${displaySafe(error instanceof Error ? error.message : String(error), { asciiOnly: true }) || "<unrenderable>"}`, error instanceof Error ? error : void 0);
|
|
10589
10566
|
}
|
|
10590
10567
|
}
|
|
10591
10568
|
/**
|
|
@@ -10628,18 +10605,19 @@ var LockManager = class {
|
|
|
10628
10605
|
async getLockRecord(stackName, region) {
|
|
10629
10606
|
await this.ensureClientForBucket();
|
|
10630
10607
|
const key = this.getLockKey(stackName, region);
|
|
10608
|
+
const shownStack = safeSegment(stackName);
|
|
10631
10609
|
try {
|
|
10632
|
-
this.logger.debug(`Getting lock info for stack: ${
|
|
10610
|
+
this.logger.debug(`Getting lock info for stack: ${shownStack}`);
|
|
10633
10611
|
const response = await this.s3Client.send(new GetObjectCommand({
|
|
10634
10612
|
Bucket: this.config.bucket,
|
|
10635
10613
|
...await this.ownerParam(),
|
|
10636
10614
|
Key: key
|
|
10637
10615
|
}));
|
|
10638
|
-
if (!response.Body) throw new LockError(`Lock file for stack '${
|
|
10616
|
+
if (!response.Body) throw new LockError(`Lock file for stack '${shownStack}' has no body`);
|
|
10639
10617
|
const bodyString = await response.Body.transformToString();
|
|
10640
10618
|
const raw = JSON.parse(bodyString);
|
|
10641
10619
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
10642
|
-
this.logger.debug(`Lock file for stack ${
|
|
10620
|
+
this.logger.debug(`Lock file for stack ${shownStack} is not an object; treating as absent`);
|
|
10643
10621
|
return null;
|
|
10644
10622
|
}
|
|
10645
10623
|
const parsed = raw;
|
|
@@ -10648,18 +10626,19 @@ var LockManager = class {
|
|
|
10648
10626
|
owner: displaySafe(parsed.owner),
|
|
10649
10627
|
...parsed.operation !== void 0 && { operation: displaySafe(parsed.operation) }
|
|
10650
10628
|
};
|
|
10651
|
-
this.logger.debug(`Lock info for stack: ${
|
|
10629
|
+
this.logger.debug(`Lock info for stack: ${shownStack}:`, lockInfo);
|
|
10652
10630
|
return {
|
|
10653
10631
|
info: lockInfo,
|
|
10654
10632
|
etag: response.ETag
|
|
10655
10633
|
};
|
|
10656
10634
|
} catch (error) {
|
|
10657
10635
|
if (error instanceof NoSuchKey) {
|
|
10658
|
-
this.logger.debug(`No lock exists for stack: ${
|
|
10636
|
+
this.logger.debug(`No lock exists for stack: ${shownStack}`);
|
|
10659
10637
|
return null;
|
|
10660
10638
|
}
|
|
10661
10639
|
if (error instanceof LockError) throw error;
|
|
10662
|
-
|
|
10640
|
+
const detail = displaySafe(error instanceof Error ? error.message : String(error), { asciiOnly: true }) || "<unrenderable>";
|
|
10641
|
+
throw new LockError(`Failed to get lock info for stack '${shownStack}': ${detail}`, error instanceof Error ? error : void 0);
|
|
10663
10642
|
}
|
|
10664
10643
|
}
|
|
10665
10644
|
/**
|
|
@@ -11156,7 +11135,7 @@ var LockManager = class {
|
|
|
11156
11135
|
if (lockInfo) {
|
|
11157
11136
|
const remainingMs = lockInfo.expiresAt - Date.now();
|
|
11158
11137
|
if (attempt < maxRetries) {
|
|
11159
|
-
this.logger.info(`Stack '${
|
|
11138
|
+
this.logger.info(`Stack '${safeSegment(stackName)}' (${safeSegment(region)}) is locked by ${lockInfo.owner}${lockInfo.operation ? ` (operation: ${lockInfo.operation})` : ""}. Lock expires in ${this.formatDuration(remainingMs)}. Retrying in ${this.formatDuration(retryDelay)}... (attempt ${attempt + 1}/${maxRetries})`);
|
|
11160
11139
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
11161
11140
|
continue;
|
|
11162
11141
|
}
|
|
@@ -11166,8 +11145,8 @@ var LockManager = class {
|
|
|
11166
11145
|
const expiresIn = lockInfo ? this.formatDuration(lockInfo.expiresAt - Date.now()) : "unknown";
|
|
11167
11146
|
const forceUnlockCommand = buildForceUnlockCommand(stackName, region);
|
|
11168
11147
|
const recovery = forceUnlockCommand ? `If you are certain no other process is active, run: ${forceUnlockCommand}` : `If you are certain no other process is active, ${UNREPRODUCIBLE_LOCK_CLAUSE.charAt(0).toLowerCase()}${UNREPRODUCIBLE_LOCK_CLAUSE.slice(1)}`;
|
|
11169
|
-
const safeStack =
|
|
11170
|
-
const safeRegion =
|
|
11148
|
+
const safeStack = safeSegment(stackName);
|
|
11149
|
+
const safeRegion = safeSegment(region);
|
|
11171
11150
|
throw new LockError(`Failed to acquire lock for stack '${safeStack}' (${safeRegion}) after ${maxRetries + 1} attempts. ` + (lockInfo ? `Locked by: ${lockInfo.owner}${lockInfo.operation ? `, operation: ${lockInfo.operation}` : ""}, expires in: ${expiresIn}. ` + recovery : `Lock exists but could not read lock info. ${recovery}`));
|
|
11172
11151
|
}
|
|
11173
11152
|
};
|
|
@@ -20533,11 +20512,15 @@ const drainDeadlines = new AsyncLocalStorage();
|
|
|
20533
20512
|
*
|
|
20534
20513
|
* THE TRADE, stated because it is real: iterations that actually WAIT spend
|
|
20535
20514
|
* the shared budget, so a later one can find it exhausted and its drain get
|
|
20536
|
-
* no grace. That is the
|
|
20515
|
+
* no grace. That is the exposure issue
|
|
20537
20516
|
* [#2814](https://github.com/go-to-k/cdkd/issues/2814) records for nesting,
|
|
20538
|
-
*
|
|
20539
|
-
*
|
|
20540
|
-
*
|
|
20517
|
+
* reachable across a loop as well: a part such a drain stops waiting for
|
|
20518
|
+
* records LATE. Late, not lost -- the engine's `Export.Name` block writes each
|
|
20519
|
+
* recording through to the pass map (it used to copy a per-iteration local,
|
|
20520
|
+
* which DROPPED a late one), and the outputs readers after the pass redact
|
|
20521
|
+
* against the pass map at the moment they read -- but a reader that already
|
|
20522
|
+
* took its copy does not see it, and the resolver warns when a drain stops
|
|
20523
|
+
* waiting. What bounds it is that only real
|
|
20541
20524
|
* waiting spends the budget: reaching zero takes a full cap of drain wait
|
|
20542
20525
|
* inside one pass, not merely a slow pass. It is taken deliberately -- an
|
|
20543
20526
|
* unbounded hold on a deploy's state save costs a first deploy every
|
|
@@ -21373,9 +21356,12 @@ const NAMEABLE_INTRINSIC_KEYS = /* @__PURE__ */ new Set([
|
|
|
21373
21356
|
* now throw on an intrinsic — `route53-provider.ts` (`HostedZoneConfig`),
|
|
21374
21357
|
* `cloudfront-oai-provider.ts` (`CloudFrontOriginAccessIdentityConfig`),
|
|
21375
21358
|
* `kinesis-provider.ts` (`StreamModeDetails`), `ecs-provider.ts`
|
|
21376
|
-
* (`DeploymentController`)
|
|
21377
|
-
*
|
|
21378
|
-
*
|
|
21359
|
+
* (`DeploymentController`) and `codebuild-provider.ts` (reached from `update()`
|
|
21360
|
+
* through `mapProperties`). `secretsmanager-secret-provider.ts` was on this
|
|
21361
|
+
* list until issue #3048 moved its update path to a SKIP (the container is
|
|
21362
|
+
* refused by `requireConfigObject` before `generateSecretString` reads it;
|
|
21363
|
+
* `create()` still throws, and so does the MEMBER-level `ExcludeCharacters`
|
|
21364
|
+
* read inside a well-formed container -- issue #3056). Not a count, for the same
|
|
21379
21365
|
* reason as the near-copy list below: two successive reviews each found
|
|
21380
21366
|
* another. Re-derive with
|
|
21381
21367
|
* `grep -rn 'readConfigString(' src/provisioning/providers/`.
|
|
@@ -23033,6 +23019,13 @@ const CAP_EXPIRED = Symbol("drain-cap-expired");
|
|
|
23033
23019
|
*/
|
|
23034
23020
|
const concurrentDrainCap = {};
|
|
23035
23021
|
/**
|
|
23022
|
+
* The budgets whose drain has already reported abandoned inputs (issue
|
|
23023
|
+
* [#2814](https://github.com/go-to-k/cdkd/issues/2814)). A `WeakSet` so a
|
|
23024
|
+
* budget's entry dies with the budget, as the budget itself does with the
|
|
23025
|
+
* async context that opened it.
|
|
23026
|
+
*/
|
|
23027
|
+
const abandonReported = /* @__PURE__ */ new WeakSet();
|
|
23028
|
+
/**
|
|
23036
23029
|
* `Promise.all`'s RESULT and its choice of error, with `Promise.allSettled`'s
|
|
23037
23030
|
* TIMING: every promise started here has settled before this returns, and
|
|
23038
23031
|
* before it throws unless the cap below expires first (issue
|
|
@@ -23110,8 +23103,25 @@ const concurrentDrainCap = {};
|
|
|
23110
23103
|
* require the "healthy work slower than 60 s" shape the sizing paragraph
|
|
23111
23104
|
* describes; that shape is the cheapest way to reach it with no nesting and
|
|
23112
23105
|
* no wrapped loop, not the floor.
|
|
23113
|
-
*
|
|
23114
|
-
*
|
|
23106
|
+
*
|
|
23107
|
+
* WHAT HAPPENS TO A PART THE CAP STOPS WAITING FOR (issue
|
|
23108
|
+
* [#2814](https://github.com/go-to-k/cdkd/issues/2814)). It keeps running, and
|
|
23109
|
+
* its recording still lands in the context's map whenever it arrives -- the
|
|
23110
|
+
* cap costs it ORDER, not the write. So the answer sits with the READERS: one
|
|
23111
|
+
* that runs after the recording arrives must see it, which `DeployEngine`'s
|
|
23112
|
+
* outputs pass arranges for its `Export.Name` block (a map that writes each
|
|
23113
|
+
* recording through to the pass map at once, where a local copied in a
|
|
23114
|
+
* `finally` used to drop it) and for the persisted outputs, the exports index
|
|
23115
|
+
* and the deploy summary (each redacting against the pass map as it stands
|
|
23116
|
+
* at the moment it is written). A reader that took its
|
|
23117
|
+
* copy BEFORE the recording arrived -- a failure message already printed, a
|
|
23118
|
+
* state save already sent -- cannot be helped without waiting, and the wait
|
|
23119
|
+
* is what the cap bounds. Cancelling the
|
|
23120
|
+
* part would not close that either: a cancelled part never records, so its
|
|
23121
|
+
* plaintext would be missing for EVERY reader rather than for the early ones.
|
|
23122
|
+
* What this helper adds is the report: releasing a rejection with inputs
|
|
23123
|
+
* still running calls `onAbandoned` with how many, at most once per budget,
|
|
23124
|
+
* and the resolver turns that into a warning.
|
|
23115
23125
|
*
|
|
23116
23126
|
* ONE REMAINING CONSEQUENCE, deliberate and pinned by a case rather than only
|
|
23117
23127
|
* described: the earliest-in-time rule holds PER INVOCATION, not across
|
|
@@ -23129,11 +23139,16 @@ const concurrentDrainCap = {};
|
|
|
23129
23139
|
* failure where the old race was arbitrary. Residual: issue
|
|
23130
23140
|
* [#2805](https://github.com/go-to-k/cdkd/issues/2805).
|
|
23131
23141
|
*/
|
|
23132
|
-
async function allSettledKeepingFirstRejection(promises) {
|
|
23142
|
+
async function allSettledKeepingFirstRejection(promises, onAbandoned) {
|
|
23133
23143
|
let rejection;
|
|
23134
23144
|
const shared = drainDeadlines.getStore();
|
|
23135
23145
|
let armCap;
|
|
23136
|
-
|
|
23146
|
+
let settled = 0;
|
|
23147
|
+
const guarded = promises.map((promise) => promise.then((value) => {
|
|
23148
|
+
settled += 1;
|
|
23149
|
+
return value;
|
|
23150
|
+
}, (error) => {
|
|
23151
|
+
settled += 1;
|
|
23137
23152
|
if (rejection === void 0) {
|
|
23138
23153
|
rejection = { error };
|
|
23139
23154
|
armCap?.();
|
|
@@ -23158,7 +23173,17 @@ async function allSettledKeepingFirstRejection(promises) {
|
|
|
23158
23173
|
});
|
|
23159
23174
|
try {
|
|
23160
23175
|
const outcome = await Promise.race([Promise.all(guarded), capped]);
|
|
23161
|
-
if (rejection !== void 0)
|
|
23176
|
+
if (rejection !== void 0) {
|
|
23177
|
+
const pending = promises.length - settled;
|
|
23178
|
+
if (pending > 0) {
|
|
23179
|
+
if (shared === void 0) onAbandoned(pending);
|
|
23180
|
+
else if (!abandonReported.has(shared)) {
|
|
23181
|
+
abandonReported.add(shared);
|
|
23182
|
+
onAbandoned(pending);
|
|
23183
|
+
}
|
|
23184
|
+
}
|
|
23185
|
+
throw rejection.error;
|
|
23186
|
+
}
|
|
23162
23187
|
if (outcome === CAP_EXPIRED) throw markNonRetryable(/* @__PURE__ */ new Error("drain cap expired with no rejection recorded"));
|
|
23163
23188
|
return outcome;
|
|
23164
23189
|
} finally {
|
|
@@ -24126,6 +24151,17 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
24126
24151
|
return conditions;
|
|
24127
24152
|
}
|
|
24128
24153
|
/**
|
|
24154
|
+
* Warn that a drain stopped waiting for `pending` inputs of a failed
|
|
24155
|
+
* resolution (issue [#2814](https://github.com/go-to-k/cdkd/issues/2814)).
|
|
24156
|
+
* The helper calls this at most once per budget. The text carries a count
|
|
24157
|
+
* and the cap, nothing the template or AWS supplied, so it needs no masking.
|
|
24158
|
+
*/
|
|
24159
|
+
warnAbandonedParts(pending) {
|
|
24160
|
+
const capSeconds = (concurrentDrainCap.ms ?? DRAIN_AFTER_REJECTION_MS) / 1e3;
|
|
24161
|
+
const parts = pending === 1 ? "1 concurrent part was" : `${pending} concurrent parts were`;
|
|
24162
|
+
this.logger.warn(`A resolution failed while ${parts} still running, and cdkd stopped waiting because the wait after a failure (capped at ${capSeconds}s) was used up. A secret such a part resolves from now on is still recorded, but only what runs after it arrives can mask it: output already printed or saved is not revisited.`);
|
|
24163
|
+
}
|
|
24164
|
+
/**
|
|
24129
24165
|
* Recursively resolve a value
|
|
24130
24166
|
*/
|
|
24131
24167
|
async resolveValue(value, context) {
|
|
@@ -24133,7 +24169,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
24133
24169
|
if (typeof value === "string" && value.includes("{{resolve:")) return await this.resolveDynamicReferences(value, context);
|
|
24134
24170
|
return value;
|
|
24135
24171
|
}
|
|
24136
|
-
if (Array.isArray(value)) return (await allSettledKeepingFirstRejection(value.map((v) => this.resolveValue(v, context)))).filter((v) => v !== AWS_NO_VALUE);
|
|
24172
|
+
if (Array.isArray(value)) return (await allSettledKeepingFirstRejection(value.map((v) => this.resolveValue(v, context)), (pending) => this.warnAbandonedParts(pending))).filter((v) => v !== AWS_NO_VALUE);
|
|
24137
24173
|
const obj = value;
|
|
24138
24174
|
if ("Ref" in obj) return await this.resolveRef(obj["Ref"], context);
|
|
24139
24175
|
if ("Fn::GetAtt" in obj) return await this.resolveGetAtt(obj["Fn::GetAtt"], context);
|
|
@@ -25040,7 +25076,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
25040
25076
|
let result = (await allSettledKeepingFirstRejection(values.map(async (v) => {
|
|
25041
25077
|
const resolved = await this.resolveValue(v, context);
|
|
25042
25078
|
return String(resolved);
|
|
25043
|
-
}))).join(delimiter);
|
|
25079
|
+
}), (pending) => this.warnAbandonedParts(pending))).join(delimiter);
|
|
25044
25080
|
if (result.includes("{{resolve:")) result = await this.resolveDynamicReferences(result, context);
|
|
25045
25081
|
this.logger.debug(`Resolved Fn::Join: ${this.maskSecretsForLog(result, context)}`);
|
|
25046
25082
|
return result;
|
|
@@ -28005,7 +28041,7 @@ var CloudControlProvider = class {
|
|
|
28005
28041
|
const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
|
|
28006
28042
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
28007
28043
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
28008
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
28044
|
+
const { ASGProvider } = await import("./asg-provider-CpxZ9sVH.js").then((n) => n.n);
|
|
28009
28045
|
const asgProvider = new ASGProvider();
|
|
28010
28046
|
return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
|
|
28011
28047
|
}
|
|
@@ -37316,6 +37352,47 @@ var DeploymentEventsReader = class {
|
|
|
37316
37352
|
*/
|
|
37317
37353
|
const EMPTY_SECRETS = /* @__PURE__ */ new Map();
|
|
37318
37354
|
/**
|
|
37355
|
+
* A secrets map that keeps its OWN entries and also writes each one through
|
|
37356
|
+
* to `target` the moment it is recorded (issue
|
|
37357
|
+
* [#2814](https://github.com/go-to-k/cdkd/issues/2814)).
|
|
37358
|
+
*
|
|
37359
|
+
* The outputs pass resolves each intrinsic `Export.Name` through one of these.
|
|
37360
|
+
* Its own entries are what `exportNameSecretExposure` reads as "substituted
|
|
37361
|
+
* into THIS name", which must stay exact -- a view of the whole pass map
|
|
37362
|
+
* (`cdkd scrub`'s `SharedEntriesSecrets`) would report every secret the pass
|
|
37363
|
+
* has recorded as one the name holds, and refuse every alias. The write-through
|
|
37364
|
+
* is what a copy could not give: a part the drain cap stopped waiting for
|
|
37365
|
+
* records AFTER the name's block has ended, and when this was a local copied
|
|
37366
|
+
* into the pass map in a `finally`, such a record reached nothing.
|
|
37367
|
+
*
|
|
37368
|
+
* Only `set` writes through: it is the one operation the resolver performs on
|
|
37369
|
+
* a recording map (grepped -- no resolver path calls `delete` or `clear` on
|
|
37370
|
+
* one). `cdkd scrub`'s `SharedEntriesSecrets` overrides those too, and the
|
|
37371
|
+
* asymmetry is deliberate: that one is a VIEW holding no entries of its own,
|
|
37372
|
+
* while a `delete` here would leave the pass map carrying an entry this map
|
|
37373
|
+
* dropped -- one needle too many, which over-redacts rather than leaks.
|
|
37374
|
+
* The ENTRIES only, never the resolved pairs beside them
|
|
37375
|
+
* (issue #2485): those are keyed by map INSTANCE (`recordResolvedPair`), so
|
|
37376
|
+
* they stay on this one. A name never positions a leaf, and a value re-using
|
|
37377
|
+
* the same token records its own pair at the seam, so carrying them could add
|
|
37378
|
+
* nothing -- what it COULD do is mark a pair conflicting (an `ssm` reference
|
|
37379
|
+
* whose `Type` came back unclassifiable is never cached, so a name resolving
|
|
37380
|
+
* it re-asks AWS and can see a value that moved since the value pass) and
|
|
37381
|
+
* destroy the positioning the value pass had earned.
|
|
37382
|
+
*/
|
|
37383
|
+
var ForwardingSecrets = class extends Map {
|
|
37384
|
+
target;
|
|
37385
|
+
constructor(target) {
|
|
37386
|
+
super();
|
|
37387
|
+
this.target = target;
|
|
37388
|
+
}
|
|
37389
|
+
set(plaintext, expression) {
|
|
37390
|
+
super.set(plaintext, expression);
|
|
37391
|
+
this.target?.set(plaintext, expression);
|
|
37392
|
+
return this;
|
|
37393
|
+
}
|
|
37394
|
+
};
|
|
37395
|
+
/**
|
|
37319
37396
|
* Default per-resource warn threshold: warn the user when a single
|
|
37320
37397
|
* resource has been in flight for 5 minutes. Most CC API resources
|
|
37321
37398
|
* complete in under a minute; 5m is the agreed elbow.
|
|
@@ -37590,6 +37667,16 @@ var DeployEngine = class DeployEngine {
|
|
|
37590
37667
|
*/
|
|
37591
37668
|
outputSecrets = /* @__PURE__ */ new Map();
|
|
37592
37669
|
/**
|
|
37670
|
+
* The outputs pass's own recording map(s), one per `resolveOutputs` call
|
|
37671
|
+
* this deploy, so every redaction of the outputs bag re-reads them rather
|
|
37672
|
+
* than trusting the copy taken when the pass ended (issue
|
|
37673
|
+
* [#2814](https://github.com/go-to-k/cdkd/issues/2814)): a part the drain
|
|
37674
|
+
* cap stopped waiting for records into its pass map LATE, and the final
|
|
37675
|
+
* save, the exports index and the deploy summary all run after that copy.
|
|
37676
|
+
* Reset per `deploy()`.
|
|
37677
|
+
*/
|
|
37678
|
+
outputsPassSecretMaps = [];
|
|
37679
|
+
/**
|
|
37593
37680
|
* UNRESOLVED template `Outputs` values, keyed by output name (issue #1910) —
|
|
37594
37681
|
* the outputs' POSITION source, the sibling of `perResourceTemplateProps` for
|
|
37595
37682
|
* the bag `resolveOutputs` produces. Without it two outputs resolving one
|
|
@@ -37718,6 +37805,7 @@ var DeployEngine = class DeployEngine {
|
|
|
37718
37805
|
this.perResourceResolvedType = /* @__PURE__ */ new Map();
|
|
37719
37806
|
this.attemptedResolvedProps = /* @__PURE__ */ new Map();
|
|
37720
37807
|
this.outputSecrets = /* @__PURE__ */ new Map();
|
|
37808
|
+
this.outputsPassSecretMaps = [];
|
|
37721
37809
|
this.outputsTemplateSource = Object.create(null);
|
|
37722
37810
|
this.outputsSourceUsable = true;
|
|
37723
37811
|
this.skippedOutputs = void 0;
|
|
@@ -37789,12 +37877,16 @@ var DeployEngine = class DeployEngine {
|
|
|
37789
37877
|
* Redact the resolved stack OUTPUTS bag, positioned by the unresolved
|
|
37790
37878
|
* template `Outputs` values (issue #1910).
|
|
37791
37879
|
*
|
|
37792
|
-
* A single entry point because
|
|
37793
|
-
* state-persist choke point, the no-change re-check,
|
|
37794
|
-
*
|
|
37795
|
-
*
|
|
37796
|
-
*
|
|
37797
|
-
*
|
|
37880
|
+
* A single entry point because SEVEN call sites redact this same bag — the
|
|
37881
|
+
* state-persist choke point, the no-change re-check, that path's exports
|
|
37882
|
+
* index and deploy summary, the changes path's index and summary, and the
|
|
37883
|
+
* outputs pass itself. THREE of those are the ones issue #1910 unified —
|
|
37884
|
+
* the persist choke point, the re-check, and the outputs pass, which the
|
|
37885
|
+
* list above ends on rather than opens with: before it they each spelled
|
|
37886
|
+
* the value-only redaction separately, and two outputs resolving one secret
|
|
37887
|
+
* collapsed onto whichever expression was recorded last at all three. The
|
|
37888
|
+
* other FOUR are issue #2814's, both paths' index and summary: each read
|
|
37889
|
+
* the bag unredacted until then, and now redacts at the moment it reads.
|
|
37798
37890
|
*/
|
|
37799
37891
|
/**
|
|
37800
37892
|
* Record the plaintext behind every output {@link redactOutputs} just masked,
|
|
@@ -37807,9 +37899,18 @@ var DeployEngine = class DeployEngine {
|
|
|
37807
37899
|
* value read back out of a previous run's state — is skipped, because there
|
|
37808
37900
|
* is no plaintext behind it to remember.
|
|
37809
37901
|
*
|
|
37810
|
-
* Called only from the REAL-DEPLOY outputs pass.
|
|
37811
|
-
* callers hand it a bag from a previous
|
|
37812
|
-
*
|
|
37902
|
+
* Called only from the REAL-DEPLOY outputs pass. Of the six other
|
|
37903
|
+
* `redactOutputs` callers, three CAN hand it a bag from a previous
|
|
37904
|
+
* generation — the persist walk always may, and the no-change path's
|
|
37905
|
+
* exports index and summary do on the arms where that path keeps the
|
|
37906
|
+
* previous bag — and there a mask is already unrecoverable, so pretending
|
|
37907
|
+
* otherwise would
|
|
37908
|
+
* serve a stale value. The other three are not this pass either: the
|
|
37909
|
+
* no-change path performs the FIRST redaction of its own resolution there,
|
|
37910
|
+
* and the changes path's index and summary re-redact the bag this pass
|
|
37911
|
+
* already produced. (The "other two" this replaces counted the base's three
|
|
37912
|
+
* callers correctly; what was wrong was calling both of the others
|
|
37913
|
+
* previous-generation, when only the persist walk was one.)
|
|
37813
37914
|
*/
|
|
37814
37915
|
rememberRecoverableMaskedOutputs(stackName, resolved, redacted) {
|
|
37815
37916
|
if (resolved === redacted) return;
|
|
@@ -37820,7 +37921,51 @@ var DeployEngine = class DeployEngine {
|
|
|
37820
37921
|
recordRecoverableMaskedOutput(stackName, this.stackRegion, key, plaintext);
|
|
37821
37922
|
}
|
|
37822
37923
|
}
|
|
37924
|
+
/**
|
|
37925
|
+
* Fold every outputs-pass recording map into `outputSecrets` — the entries,
|
|
37926
|
+
* and the uncollapsed evidence beside them (issue #2485: without it a
|
|
37927
|
+
* literal Output embedding one of two same-plaintext references would lose
|
|
37928
|
+
* its span positioning and persist the sibling's expression). Only what the
|
|
37929
|
+
* outputs pass itself resolved, so the outputs redaction (GHSA fix) uses
|
|
37930
|
+
* only outputs-substituted references: a literal output equal to a secret
|
|
37931
|
+
* nothing resolved is not recorded, and is not touched.
|
|
37932
|
+
*
|
|
37933
|
+
* Run by every {@link redactOutputs} (issue
|
|
37934
|
+
* [#2814](https://github.com/go-to-k/cdkd/issues/2814)), because a part the
|
|
37935
|
+
* drain cap stopped waiting for records into its pass map LATE: a secret
|
|
37936
|
+
* that arrives between the pass and the final save is then still a needle
|
|
37937
|
+
* for the save, the exports index and the deploy summary. Repeating it is
|
|
37938
|
+
* safe for the ENTRIES — re-setting one the bag holds changes nothing, and
|
|
37939
|
+
* `Map.set` on an existing key keeps its insertion order, so last-wins
|
|
37940
|
+
* cannot be reordered by a refold.
|
|
37941
|
+
*
|
|
37942
|
+
* The PAIRS are where repeating is not merely a no-op, stated because the
|
|
37943
|
+
* end-of-pass copy could not see it: a part the cap stopped waiting for can
|
|
37944
|
+
* record a pair LATE, and a second value for one expression marks it
|
|
37945
|
+
* `CONFLICTING_PLAINTEXT` (a non-cacheable `{{resolve:ssm:X}}` re-resolved
|
|
37946
|
+
* to a moved value). A later refold then carries that conflict into
|
|
37947
|
+
* `outputSecrets`, the leaf loses its positioning and falls to the value
|
|
37948
|
+
* scan, which can persist a sibling's expression (the issue #2485 class).
|
|
37949
|
+
* The plaintext stays MASKED either way, so the cost is which expression
|
|
37950
|
+
* is stored, not a disclosure — and the alternative, dropping the late
|
|
37951
|
+
* pair, would keep a positioning the pass itself no longer vouches for.
|
|
37952
|
+
*/
|
|
37953
|
+
absorbOutputsPassSecrets() {
|
|
37954
|
+
for (const passSecrets of this.outputsPassSecretMaps) {
|
|
37955
|
+
for (const [value, expr] of passSecrets) this.outputSecrets.set(value, expr);
|
|
37956
|
+
mergeResolvedPairs(passSecrets, this.outputSecrets);
|
|
37957
|
+
}
|
|
37958
|
+
}
|
|
37959
|
+
/**
|
|
37960
|
+
* Redact the outputs bag. NOT a pure transform: it folds the outputs pass
|
|
37961
|
+
* map into `outputSecrets` first (issue #2814), so every call can grow that
|
|
37962
|
+
* bag. Its KEY set only grows, and the bag has no other reader, which is
|
|
37963
|
+
* what makes calling this on every save, index write and summary safe. The
|
|
37964
|
+
* resolved PAIRS beside those keys are not equally free to refold — see
|
|
37965
|
+
* {@link absorbOutputsPassSecrets}, which states what a late one costs.
|
|
37966
|
+
*/
|
|
37823
37967
|
redactOutputs(outputs) {
|
|
37968
|
+
this.absorbOutputsPassSecrets();
|
|
37824
37969
|
if (this.outputSecrets.size === 0) return outputs;
|
|
37825
37970
|
return redactSecretsForState(outputs, this.outputSecrets, this.outputsSourceUsable ? this.outputsTemplateSource : void 0, TEMPLATE_SOURCED_RULES);
|
|
37826
37971
|
}
|
|
@@ -38453,7 +38598,10 @@ var DeployEngine = class DeployEngine {
|
|
|
38453
38598
|
persistedOutputs = refreshedState.outputs;
|
|
38454
38599
|
if (outputsChanged) this.logger.info("Persisted Outputs-only change (no resource diff).");
|
|
38455
38600
|
else this.logger.debug("Persisted export-set change (no outputs-value diff, no-change path, #2193)");
|
|
38456
|
-
if (this.exportIndexStore) await this.exportIndexStore.updateForStack(stackName, this.stackRegion, importableOutputs(
|
|
38601
|
+
if (this.exportIndexStore) await this.exportIndexStore.updateForStack(stackName, this.stackRegion, importableOutputs({
|
|
38602
|
+
...refreshedState,
|
|
38603
|
+
outputs: this.redactOutputs(refreshedState.outputs)
|
|
38604
|
+
}));
|
|
38457
38605
|
} else if (observedRefresh) this.logger.debug("Persisted refreshed observedProperties (no-change path)");
|
|
38458
38606
|
else this.logger.debug("Persisted skipped-outputs record (no outputs-value diff, no-change path, #2740)");
|
|
38459
38607
|
} catch (saveError) {
|
|
@@ -38470,7 +38618,7 @@ var DeployEngine = class DeployEngine {
|
|
|
38470
38618
|
updatePartial: 0,
|
|
38471
38619
|
unchanged: Object.keys(currentState.resources).length,
|
|
38472
38620
|
durationMs: Date.now() - startTime,
|
|
38473
|
-
outputs: this.buildDisplayOutputs(template, persistedOutputs),
|
|
38621
|
+
outputs: this.buildDisplayOutputs(template, this.redactOutputs(persistedOutputs)),
|
|
38474
38622
|
attributeFallbackCount: this.resolver.getPhysicalIdFallbackCount()
|
|
38475
38623
|
};
|
|
38476
38624
|
}
|
|
@@ -38501,7 +38649,10 @@ var DeployEngine = class DeployEngine {
|
|
|
38501
38649
|
await this.drainObservedCaptures(newState.resources);
|
|
38502
38650
|
const newEtag = await this.stateBackend.saveState(stackName, this.stackRegion, this.withParentInfo(newState));
|
|
38503
38651
|
this.logger.debug(`State saved (ETag: ${newEtag})`);
|
|
38504
|
-
await Promise.all([this.deleteRollbackJournalBestEffort(stackName), this.exportIndexStore ? this.exportIndexStore.updateForStack(stackName, this.stackRegion, importableOutputs(
|
|
38652
|
+
await Promise.all([this.deleteRollbackJournalBestEffort(stackName), this.exportIndexStore ? this.exportIndexStore.updateForStack(stackName, this.stackRegion, importableOutputs({
|
|
38653
|
+
...newState,
|
|
38654
|
+
outputs: this.redactOutputs(newState.outputs)
|
|
38655
|
+
})) : Promise.resolve()]);
|
|
38505
38656
|
const durationMs = Date.now() - startTime;
|
|
38506
38657
|
const unchangedCount = this.diffCalculator.filterByType(changes, "NO_CHANGE").length + actualCounts.skipped;
|
|
38507
38658
|
return {
|
|
@@ -38513,7 +38664,7 @@ var DeployEngine = class DeployEngine {
|
|
|
38513
38664
|
updatePartial: actualCounts.updatePartial,
|
|
38514
38665
|
unchanged: unchangedCount,
|
|
38515
38666
|
durationMs,
|
|
38516
|
-
outputs: this.buildDisplayOutputs(template, newState.outputs ?? {}),
|
|
38667
|
+
outputs: this.buildDisplayOutputs(template, this.redactOutputs(newState.outputs ?? {})),
|
|
38517
38668
|
attributeFallbackCount: this.resolver.getPhysicalIdFallbackCount()
|
|
38518
38669
|
};
|
|
38519
38670
|
} finally {
|
|
@@ -40125,8 +40276,10 @@ var DeployEngine = class DeployEngine {
|
|
|
40125
40276
|
* defense in depth here — but two masking sites in one flow must not argue
|
|
40126
40277
|
* opposite sides of the same question. `secrets` is the outputs pass's own
|
|
40127
40278
|
* map: everything recorded before this handler runs, an `Export.Name`
|
|
40128
|
-
* resolution's entries included (its
|
|
40129
|
-
*
|
|
40279
|
+
* resolution's entries included (its map writes each one through to the
|
|
40280
|
+
* pass map as the resolver records it, so they are here before the `catch`
|
|
40281
|
+
* is; issue #2814 replaced the `finally` that copied them at the end of
|
|
40282
|
+
* the block). Since issue #2563 a still-pending concurrent part
|
|
40130
40283
|
* is in the PASS bag before this handler runs: the resolver drains every
|
|
40131
40284
|
* part it started before a rejection reaches a caller. Not
|
|
40132
40285
|
* unconditionally, and the weaker claim is the true one -- the drain is
|
|
@@ -40261,6 +40414,7 @@ var DeployEngine = class DeployEngine {
|
|
|
40261
40414
|
};
|
|
40262
40415
|
const outputsPassSecrets = context.recordedSecretValues ?? EMPTY_SECRETS;
|
|
40263
40416
|
const outputsPassInherited = context.inheritedSecrets ?? EMPTY_SECRETS;
|
|
40417
|
+
if (context.recordedSecretValues) this.outputsPassSecretMaps.push(context.recordedSecretValues);
|
|
40264
40418
|
const publishedOutputNames = collectPublishedOutputNames(template.Outputs, conditions);
|
|
40265
40419
|
let outputsPassCompleted = false;
|
|
40266
40420
|
try {
|
|
@@ -40286,20 +40440,16 @@ var DeployEngine = class DeployEngine {
|
|
|
40286
40440
|
if (!output.Export?.Name) continue;
|
|
40287
40441
|
const value = outputs[outputKey];
|
|
40288
40442
|
if (value === void 0) continue;
|
|
40289
|
-
const nameSecrets =
|
|
40443
|
+
const nameSecrets = new ForwardingSecrets(context.recordedSecretValues);
|
|
40290
40444
|
const nameReads = [];
|
|
40291
40445
|
let exportName;
|
|
40292
40446
|
try {
|
|
40293
|
-
|
|
40294
|
-
|
|
40295
|
-
|
|
40296
|
-
|
|
40297
|
-
|
|
40298
|
-
|
|
40299
|
-
refuseMaskedOutputReads(outputKey, nameReads);
|
|
40300
|
-
} finally {
|
|
40301
|
-
for (const [plaintext, expression] of nameSecrets) context.recordedSecretValues?.set(plaintext, expression);
|
|
40302
|
-
}
|
|
40447
|
+
exportName = typeof output.Export.Name === "string" ? output.Export.Name : await this.resolver.resolve(output.Export.Name, {
|
|
40448
|
+
...context,
|
|
40449
|
+
recordedSecretValues: nameSecrets,
|
|
40450
|
+
redactedAttributeReads: nameReads
|
|
40451
|
+
});
|
|
40452
|
+
refuseMaskedOutputReads(outputKey, nameReads);
|
|
40303
40453
|
} catch (error) {
|
|
40304
40454
|
this.handleOutputResolutionFailure(error, outputKey, outputs, outputsPassSecrets, outputsPassInherited);
|
|
40305
40455
|
continue;
|
|
@@ -40316,10 +40466,6 @@ var DeployEngine = class DeployEngine {
|
|
|
40316
40466
|
}
|
|
40317
40467
|
outputsPassCompleted = true;
|
|
40318
40468
|
} finally {
|
|
40319
|
-
if (context.recordedSecretValues) {
|
|
40320
|
-
for (const [value, expr] of context.recordedSecretValues) this.outputSecrets.set(value, expr);
|
|
40321
|
-
mergeResolvedPairs(context.recordedSecretValues, this.outputSecrets);
|
|
40322
|
-
}
|
|
40323
40469
|
if (!outputsPassCompleted) this.outputsSourceUsable = false;
|
|
40324
40470
|
}
|
|
40325
40471
|
for (const [outputKey, output] of Object.entries(template.Outputs)) {
|
|
@@ -40341,5 +40487,5 @@ var DeployEngine = class DeployEngine {
|
|
|
40341
40487
|
};
|
|
40342
40488
|
|
|
40343
40489
|
//#endregion
|
|
40344
|
-
export { collectInlinePolicyNamesManagedBySiblings as $, buildDockerImage as $n,
|
|
40345
|
-
//# sourceMappingURL=deploy-engine-
|
|
40490
|
+
export { collectInlinePolicyNamesManagedBySiblings as $, buildDockerImage as $n, LocalInvokeBuildError as $r, DagBuilder as $t, isStatefulRecreateTargetForReplace as A, importableOutputs as An, findLargeInlineResources as Ar, coerceCfnBoolean as At, gray as B, rewriteTemplateAssetReferences as Bn, resolveBucketRegion as Br, withSharedDrainBudget as Bt, refusesFinalSnapshot as C, buildLockContentionMessage as Cn, resolveStateBucketWithDefaultAndSource as Cr, isUnboundTemplateParameter as Ct, makeSiblingClaimReader as D, DEFAULT_STATE_PREFIX as Dn, CFN_TEMPLATE_BODY_LIMIT as Dr, normalizeAwsTagsToCfn as Dt, extractDeploymentEventError as E, CUSTOM_RESOURCE_RESPONSE_PREFIX as En, warnDeprecatedNoPrefixCliFlag as Er, WAFv2WebACLProvider as Et, isWarmThroughputDecrease as F, stringifyValue as Fn, canonicalizeRegion as Fr, requireConfigArray as Ft, collectPublishedOutputNames as G, getBootstrapMarkerKey as Gn, AssetError as Gr, s3BucketWebsiteUrl as Gt, red as H, BOOTSTRAP_MARKER_PREFIX as Hn, getAwsClients as Hr, s3BucketDomainName as Ht, toFiniteNumber as I, WorkGraph as In, derivePartitionAndUrlSuffix as Ir, requireConfigObject as It, secretBearing as J, readBootstrapMarkerBody as Jn, CrossAccountSecretRefusalError as Jr, INTRINSIC_KEYS as Jt, exportAliasCollisionScrubWarning as K, isCrossRegionRedirect as Kn, CdkdError as Kr, applyRoleArnIfSet as Kt, formatResourceLine as L, buildAssetRedirectMap as Ln, AssemblyReader as Lr, requireConfigString as Lt, renderStatefulReason as M, orphansCarriedFrom as Mn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as Mr, configStringRefusal as Mt, WARM_THROUGHPUT_MEMBERS as N, shouldRetainResource as Nn, expectedOwnerParam as Nr, readConfigString as Nt, planOrphanAdoption as O, exportNamesCarriedFrom as On, CFN_TEMPLATE_URL_LIMIT as Or, resolveExplicitPhysicalId as Ot, coerceWarmThroughput as P, AssetPublisher as Pn, PARTITION_TABLE as Pr, replayWarn as Pt, IAMRoleProvider as Q, describeAwsFailure as Qn, IntrinsicResolutionRefusalError as Qr, withRetry as Qt, bold as R, createAssetRedirectResolver as Rn, processStackMessages as Rr, classifyReplaySecretRegion as Rt, isFinalSnapshotError as S, __exportAll as Si, buildForceUnlockCommand as Sn, resolveStateBucketWithDefault as Sr, getAccountInfo as St, makeCanonicalizePropertiesFn as T, shellQuote as Tn, stateBucketExistenceConfirmed as Tr, refStateLookupFromResource as Tt, yellow as U, assertAssetBucketRegion as Un, resetAwsClients as Ur, s3BucketDualStackDomainName as Ut, green as V, AssetModeResolver as Vn, AwsClients as Vr, s3BucketArn as Vt, collectDeclaredOutputNames as W, ensureAssetStorage as Wn, setAwsClients as Wr, s3BucketRegionalDomainName as Wt, secretSafeKeyDisplay as X, validateContainerRepoName as Xn, DeployCancelledError as Xr, findSilentDropProperties as Xt, secretBearingStateKeyWarning as Y, validateAssetBucketName as Yn, DependencyError as Yr, findActionableSilentDrops as Yt, getCurrentResourceSecrets as Z, buildDenyExternalAccessPolicy as Zn, DynamicReferenceRegionAmbiguousError as Zr, describeTypeWithThrottleRetry as Zt, ATOMIC_FINAL_SNAPSHOT_TYPES as _, isThrottlingError as _i, scrubResourceRecord as _n, getLegacyStateBucketName as _r, isTerminationProtectionPropagationError as _t, DeploymentEventsStore as a, ResourceTimeoutError as ai, carriesSecretMask as an, getDockerCmd as ar, maskerOrIdentity as at, ccRoutedFinalSnapshotError as b, markRedactedCause as bi, rebuildClientForBucketRegion as bn, resolveCaptureObservedState as br, cfnRefValueFromPhysicalId as bt, replayFailedOperations as c, StackTerminationProtectionError as ci, errorCauseChain as cn, runDockerForeground as cr, interruptWatchListenerCount as ct, updatePartialReason as d, formatError as di, markSameGenerationBag as dn, stripControlChars as dr, CloudControlProvider as dt, LocalStartServiceError as ei, TemplateParser as en, describeDockerCapturedOutput as er, clearOnUpdateRemoval as et, withResourceDeadline as f, isCdkdError as fi, maskSecretsInError as fn, AssetManifestLoader as fr, slowCcOperationTimeoutMs as ft, bindingSkippedOutputs as g, isRetryableTransientError as gi, redactSecretsForState as gn, getDefaultStateBucketName as gr, disableInstanceApiTermination as gt, computeImplicitDeleteEdges as h, isMarkedNonRetryable as hi, recoverMaskedOutput as hn, synthesisStatusMessage as hr, deleteSkipReason as ht, DeploymentEventsReader as i, ProvisioningError as ii, TEMPLATE_SOURCED_RULES as in, formatDockerLoginError as ir, maskDeep as it, isStatefulRecreateTargetSync as j, orphansAfterRollback as jn, uploadCfnTemplate as jr, configBooleanRefusal as jt, MULTI_REGION_RECREATE_BLOCKED_TYPES as k, importableOutputKeys as kn, MIGRATE_TMP_PREFIX as kr, assertRegionMatch as kt, replayRollback as l, StateError as li, identityKeyFor as ln, runDockerStreaming as lr, isInterruptedWaitError as lt, IMPLICIT_DELETE_DEPENDENCIES as m, withErrorHandling as mi, recordMaskOnlyValue as mn, Synthesizer as mr, deleteIndeterminateGuards as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, NestedStackChildDirectDestroyError as ni, STATE_SOURCED_CROSS_GENERATION_RULES as nn, describeDockerFailure as nr, wouldReturnToSdkProvider as nt, planFailedOps as o, ResourceUpdateNotSupportedError as oi, createSecretMasker as on, partitionSensitiveEnv as or, beginCommandInterruptScope as ot, maskingRetryLogger as p, normalizeAwsError as pi, maskSecretsInText as pn, getDockerImageBySourceHash as pr, UNSPECIFIED_SKIP_REASON as pt, isExportAliasCollision as q, parseBootstrapMarker as qn, ConfigError as qr, DiffCalculator as qt, DeployEngine as r, PartialFailureError as ri, STATE_SOURCED_READBACK_RULES as rn, dockerSpawnEnvWithSensitive as rr, createMaskedRetryLogger as rt, planRollback as s, StackHasActiveImportsError as si, dynamicReferenceTokens as sn, redactDockerArgvValues as sr, endCommandInterruptScope as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, LockError as ti, STATE_SOURCED_BASELINE_RULES as tn, describeDockerExecFailure as tr, ProviderRegistry as tt, updatePartialMessage as u, SynthesisError as ui, isSingleDynamicReferenceToken as un, escapeRegExp$1 as ur, startInterruptWatch as ut, PRE_DELETE_SNAPSHOT_TYPES as v, isTransientServerError as vi, LockManager as vn, resolveApp as vr, IntrinsicFunctionResolver as vt, unsupportedFinalSnapshotError as w, forceQuitRecoveryClause as wn, resolveUseCdkBootstrapAssets as wr, parameterTypeMayLoseSecretIdentity as wt, createPreDeleteFinalSnapshot as x, retryClassificationText as xi, UNRENDERABLE as xn, resolveSkipPrefix as xr, coerceParameterTypedValue as xt, buildFinalSnapshotIdentifier as y, markNonRetryable as yi, S3StateBackend as yn, resolveAutoAssetStorage as yr, carriesDynamicReference as yt, cyan as z, loadPublishableAssetManifest as zn, clearBucketRegionCache as zr, producerRegionsFromState as zt };
|
|
40491
|
+
//# sourceMappingURL=deploy-engine-BEpYvI14.js.map
|