@go-to-k/cdkd 0.285.5 → 0.285.7
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-BBSPZtne.js → asg-provider-B1EEjygi.js} +3 -3
- package/dist/{asg-provider-BBSPZtne.js.map → asg-provider-B1EEjygi.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-CSBoL-Az.js → deploy-engine-DmWPwiyQ.js} +207 -58
- package/dist/deploy-engine-DmWPwiyQ.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{logger-Dz3Le2Pw.js → logger-DyTk5GeO.js} +81 -17
- package/dist/{logger-Dz3Le2Pw.js.map → logger-DyTk5GeO.js.map} +1 -1
- package/dist/{program-Def0C7JX.js → program-CjlLza5f.js} +31 -15
- package/dist/{program-Def0C7JX.js.map → program-CjlLza5f.js.map} +1 -1
- package/dist/{version-neBHYAq9.js → version-DlUEyyGa.js} +2 -2
- package/dist/{version-neBHYAq9.js.map → version-DlUEyyGa.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-CSBoL-Az.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { d as
|
|
1
|
+
import { _ as withStackName, d as applyDefaultNameForFallback, f as generateResourceName, h as looksLikeCdkdGeneratedName, m as getCurrentStackName, n as getLogger, p as generateResourceNameWithFallback, r as isStdoutReservedForPayload, s as getLiveRenderer } from "./logger-DyTk5GeO.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-DlUEyyGa.js";
|
|
4
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -3880,6 +3880,44 @@ async function expectedOwnerParam(client) {
|
|
|
3880
3880
|
return owner ? { ExpectedBucketOwner: owner } : {};
|
|
3881
3881
|
}
|
|
3882
3882
|
|
|
3883
|
+
//#endregion
|
|
3884
|
+
//#region src/utils/display-safe.ts
|
|
3885
|
+
/**
|
|
3886
|
+
* Make an untrusted value safe to render in a terminal or persist into a log.
|
|
3887
|
+
*
|
|
3888
|
+
* A LEAF module with no imports, and deliberately in `src/utils/` rather than
|
|
3889
|
+
* beside its first caller: issue [#2170](https://github.com/go-to-k/cdkd/issues/2170)'s
|
|
3890
|
+
* review found the same rule being widened BY HAND one module at a time and
|
|
3891
|
+
* missing an instance every round — the change sanitized 1 of 5 readers of
|
|
3892
|
+
* `LockInfo.owner`. One shared definition, imported by everything that renders
|
|
3893
|
+
* such a value, is what stops the next reader from inheriting nothing.
|
|
3894
|
+
*
|
|
3895
|
+
* The stripped class is wider than C0 + DEL, which a first cut used and which
|
|
3896
|
+
* misses every mechanism that actually forges a line:
|
|
3897
|
+
*
|
|
3898
|
+
* - `U+0085` (NEL) and the C1 range — xterm reads `U+009B` as CSI in UTF-8;
|
|
3899
|
+
* - `U+2028` / `U+2029` — this text is PERSISTED and re-rendered by JSON and
|
|
3900
|
+
* web log viewers, where both are line terminators;
|
|
3901
|
+
* - `U+202A`-`U+202E` / `U+2066`-`U+2069` — the Trojan-Source bidi overrides
|
|
3902
|
+
* and isolates, which visually REORDER the command being pasted.
|
|
3903
|
+
*
|
|
3904
|
+
* Known residual, recorded rather than implied away: the invisible formatters
|
|
3905
|
+
* (`U+200B`-`U+200D`, `U+FEFF`) and the bidi MARKS (`U+200E` / `U+200F` /
|
|
3906
|
+
* `U+061C`) survive, as do bare RTL letters, which no denylist can reach. All
|
|
3907
|
+
* of them can only make a rendered name differ visually from its bytes — the
|
|
3908
|
+
* command a user pastes still acts on exactly what is shown, and the blast
|
|
3909
|
+
* radius stays the attacker's own stack name.
|
|
3910
|
+
*
|
|
3911
|
+
* A caller whose value has a KNOWN ASCII charset (a stack name, an AWS region)
|
|
3912
|
+
* should pass `asciiOnly`, which is a positive allowlist and therefore has no
|
|
3913
|
+
* such residual at all.
|
|
3914
|
+
*/
|
|
3915
|
+
function displaySafe(value, opts) {
|
|
3916
|
+
if (value === void 0 || value === null) return "";
|
|
3917
|
+
const text = String(value);
|
|
3918
|
+
return (opts?.asciiOnly ? text.replace(/[^ -~]/g, " ") : text.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029\u202a-\u202e\u2066-\u2069]/g, " ")).trim();
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3883
3921
|
//#endregion
|
|
3884
3922
|
//#region src/state/s3-noncurrent-version-purge.ts
|
|
3885
3923
|
/**
|
|
@@ -3971,7 +4009,7 @@ async function purgeNoncurrentKeyVersions(s3Client, bucket, keys, options = {})
|
|
|
3971
4009
|
for (const key of affected) recordFailure(failed, key, describe$1(error));
|
|
3972
4010
|
}
|
|
3973
4011
|
if (failed.size > 0) {
|
|
3974
|
-
const named = [...failed.entries()].slice(0, MAX_NAMED_FAILURES).map(([key, reasons]) => `${key} (${reasons.join("; ")})`);
|
|
4012
|
+
const named = [...failed.entries()].slice(0, MAX_NAMED_FAILURES).map(([key, reasons]) => displaySafe(`${key} (${reasons.join("; ")})`));
|
|
3975
4013
|
const elided = failed.size - named.length;
|
|
3976
4014
|
logger.warn(`Could not purge noncurrent versions of ${failed.size} key(s) in s3://${bucket}. Their previous versions survive and remain readable via GetObject with a VersionId (${options.objectDescription ?? DEFAULT_OBJECT_DESCRIPTION}). Grant s3:ListBucketVersions and s3:DeleteObjectVersion on the state bucket, or purge the key(s) by hand. Failures: ${named.join(", ")}` + (elided > 0 ? ` (and ${elided} more)` : ""));
|
|
3977
4015
|
}
|
|
@@ -4023,6 +4061,7 @@ async function purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields,
|
|
|
4023
4061
|
}
|
|
4024
4062
|
}));
|
|
4025
4063
|
for (const err of deleted.Errors ?? []) {
|
|
4064
|
+
if (err.Code === "NoSuchVersion") continue;
|
|
4026
4065
|
const reason = `version ${err.VersionId ?? "<unknown>"}: ${err.Code ?? "Error"}` + (err.Message ? ` - ${err.Message}` : "");
|
|
4027
4066
|
if (err.Key !== void 0) recordFailure(failed, err.Key, reason);
|
|
4028
4067
|
else {
|
|
@@ -5525,8 +5564,13 @@ var FileAssetPublisher = class {
|
|
|
5525
5564
|
* Output handling: stdout/stderr are collected in memory unconditionally so
|
|
5526
5565
|
* `runDockerStreaming` can return them to the caller for error wrapping.
|
|
5527
5566
|
* When the logger is at debug level (i.e. the user passed `--verbose`),
|
|
5528
|
-
* the chunks are ALSO mirrored
|
|
5529
|
-
*
|
|
5567
|
+
* the chunks are ALSO mirrored live so the user sees build progress —
|
|
5568
|
+
* stderr always to `process.stderr`, and stdout to `process.stdout` EXCEPT
|
|
5569
|
+
* while a command holds a payload reservation
|
|
5570
|
+
* ({@link isStdoutReservedForPayload}), where it joins the logger on stderr
|
|
5571
|
+
* so a child's diagnostics cannot land in the payload
|
|
5572
|
+
* ([#2410](https://github.com/go-to-k/cdkd/issues/2410)). The same
|
|
5573
|
+
* reservation redirects `spawnForeground`'s inherited fd 1 to fd 2.
|
|
5530
5574
|
*/
|
|
5531
5575
|
/**
|
|
5532
5576
|
* Return the docker-compatible CLI binary to invoke. Matches CDK CLI:
|
|
@@ -5573,7 +5617,8 @@ async function spawnStreaming(cmd, args, options = {}) {
|
|
|
5573
5617
|
const stderrChunks = [];
|
|
5574
5618
|
child.stdout.on("data", (chunk) => {
|
|
5575
5619
|
stdoutChunks.push(chunk);
|
|
5576
|
-
if (streamLive) process.
|
|
5620
|
+
if (streamLive) if (isStdoutReservedForPayload()) process.stderr.write(chunk);
|
|
5621
|
+
else process.stdout.write(chunk);
|
|
5577
5622
|
});
|
|
5578
5623
|
child.stderr.on("data", (chunk) => {
|
|
5579
5624
|
stderrChunks.push(chunk);
|
|
@@ -5617,25 +5662,42 @@ async function spawnStreaming(cmd, args, options = {}) {
|
|
|
5617
5662
|
* exit, so the caller can wrap with its own error class.
|
|
5618
5663
|
*
|
|
5619
5664
|
* Differs from {@link runDockerStreaming} in two ways:
|
|
5620
|
-
* 1.
|
|
5621
|
-
* (color, progress bar overwrites) flow through
|
|
5622
|
-
* load-bearing reason for the split:
|
|
5623
|
-
*
|
|
5665
|
+
* 1. The child INHERITS descriptors — output is NOT captured, so terminal
|
|
5666
|
+
* control codes (color, progress bar overwrites) flow through
|
|
5667
|
+
* unchanged. That is the load-bearing reason for the split:
|
|
5668
|
+
* `docker pull`'s progress bars only animate when the child writes to a
|
|
5669
|
+
* real TTY rather than a pipe. Under a payload reservation the child's
|
|
5670
|
+
* fd 1 is redirected to the parent's fd 2 rather than piped, precisely
|
|
5671
|
+
* so it keeps a descriptor and not a pipe — though the animation then
|
|
5672
|
+
* depends on STDERR being a terminal, and degrades to plain lines under
|
|
5673
|
+
* `2> file` (issue
|
|
5674
|
+
* [#2410](https://github.com/go-to-k/cdkd/issues/2410)).
|
|
5624
5675
|
* 2. No `input` / `streamLive` options — inherit-mode has nothing to
|
|
5625
5676
|
* capture and nothing to mirror.
|
|
5626
5677
|
*
|
|
5627
|
-
* Used by the
|
|
5628
|
-
*
|
|
5629
|
-
*
|
|
5678
|
+
* Used by the `docker pull` plumbing in `docker-runner.ts` and
|
|
5679
|
+
* `ecr-puller.ts`. Those two callers differ, and the difference matters
|
|
5680
|
+
* enough to state: `docker-runner.ts` reaches this only under `--verbose`,
|
|
5681
|
+
* while `ecr-puller.ts` runs it UNCONDITIONALLY, which is what made the
|
|
5682
|
+
* pre-#2410 stdout leak reachable with no flag at all. Non-verbose pulls in
|
|
5683
|
+
* `docker-runner.ts` go through {@link runDockerStreaming} instead, so
|
|
5684
|
+
* stderr can be folded into the error message.
|
|
5630
5685
|
*/
|
|
5631
5686
|
async function runDockerForeground(args, options = {}) {
|
|
5632
5687
|
return spawnForeground(getDockerCmd(), args, options);
|
|
5633
5688
|
}
|
|
5634
5689
|
/**
|
|
5635
|
-
* Foreground (
|
|
5690
|
+
* Foreground (descriptor-inheriting) spawn — the inherit-mode counterpart to
|
|
5636
5691
|
* {@link spawnStreaming}. Used by {@link runDockerForeground} for docker-CLI
|
|
5637
5692
|
* subprocesses.
|
|
5638
5693
|
*
|
|
5694
|
+
* "inherit" is not unqualified, and THIS is the function that qualifies it:
|
|
5695
|
+
* while a command holds a payload reservation
|
|
5696
|
+
* ({@link isStdoutReservedForPayload}) the child's fd 1 is redirected to the
|
|
5697
|
+
* parent's fd 2, so its output cannot land in the payload
|
|
5698
|
+
* ([#2410](https://github.com/go-to-k/cdkd/issues/2410)). stdin and stderr
|
|
5699
|
+
* are inherited either way. See the inline note at the `spawn` call.
|
|
5700
|
+
*
|
|
5639
5701
|
* The ENOENT branch crafts a docker-specific install hint ("Install Docker
|
|
5640
5702
|
* (or set CDK_DOCKER ...)"), so non-docker callers reusing this helper
|
|
5641
5703
|
* would see a misleading error on missing-binary failures. Keep the binary
|
|
@@ -5648,7 +5710,11 @@ async function spawnForeground(cmd, args, options = {}) {
|
|
|
5648
5710
|
const child = spawn(cmd, args, {
|
|
5649
5711
|
cwd: options.cwd,
|
|
5650
5712
|
env,
|
|
5651
|
-
stdio:
|
|
5713
|
+
stdio: isStdoutReservedForPayload() ? [
|
|
5714
|
+
"inherit",
|
|
5715
|
+
2,
|
|
5716
|
+
"inherit"
|
|
5717
|
+
] : "inherit"
|
|
5652
5718
|
});
|
|
5653
5719
|
child.once("error", (err) => {
|
|
5654
5720
|
if (err.code === "ENOENT") {
|
|
@@ -8665,44 +8731,6 @@ function isNoSuchKey(error) {
|
|
|
8665
8731
|
return error?.name === "NoSuchKey";
|
|
8666
8732
|
}
|
|
8667
8733
|
|
|
8668
|
-
//#endregion
|
|
8669
|
-
//#region src/utils/display-safe.ts
|
|
8670
|
-
/**
|
|
8671
|
-
* Make an untrusted value safe to render in a terminal or persist into a log.
|
|
8672
|
-
*
|
|
8673
|
-
* A LEAF module with no imports, and deliberately in `src/utils/` rather than
|
|
8674
|
-
* beside its first caller: issue [#2170](https://github.com/go-to-k/cdkd/issues/2170)'s
|
|
8675
|
-
* review found the same rule being widened BY HAND one module at a time and
|
|
8676
|
-
* missing an instance every round — the change sanitized 1 of 5 readers of
|
|
8677
|
-
* `LockInfo.owner`. One shared definition, imported by everything that renders
|
|
8678
|
-
* such a value, is what stops the next reader from inheriting nothing.
|
|
8679
|
-
*
|
|
8680
|
-
* The stripped class is wider than C0 + DEL, which a first cut used and which
|
|
8681
|
-
* misses every mechanism that actually forges a line:
|
|
8682
|
-
*
|
|
8683
|
-
* - `U+0085` (NEL) and the C1 range — xterm reads `U+009B` as CSI in UTF-8;
|
|
8684
|
-
* - `U+2028` / `U+2029` — this text is PERSISTED and re-rendered by JSON and
|
|
8685
|
-
* web log viewers, where both are line terminators;
|
|
8686
|
-
* - `U+202A`-`U+202E` / `U+2066`-`U+2069` — the Trojan-Source bidi overrides
|
|
8687
|
-
* and isolates, which visually REORDER the command being pasted.
|
|
8688
|
-
*
|
|
8689
|
-
* Known residual, recorded rather than implied away: the invisible formatters
|
|
8690
|
-
* (`U+200B`-`U+200D`, `U+FEFF`) and the bidi MARKS (`U+200E` / `U+200F` /
|
|
8691
|
-
* `U+061C`) survive, as do bare RTL letters, which no denylist can reach. All
|
|
8692
|
-
* of them can only make a rendered name differ visually from its bytes — the
|
|
8693
|
-
* command a user pastes still acts on exactly what is shown, and the blast
|
|
8694
|
-
* radius stays the attacker's own stack name.
|
|
8695
|
-
*
|
|
8696
|
-
* A caller whose value has a KNOWN ASCII charset (a stack name, an AWS region)
|
|
8697
|
-
* should pass `asciiOnly`, which is a positive allowlist and therefore has no
|
|
8698
|
-
* such residual at all.
|
|
8699
|
-
*/
|
|
8700
|
-
function displaySafe(value, opts) {
|
|
8701
|
-
if (value === void 0 || value === null) return "";
|
|
8702
|
-
const text = String(value);
|
|
8703
|
-
return (opts?.asciiOnly ? text.replace(/[^ -~]/g, " ") : text.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029\u202a-\u202e\u2066-\u2069]/g, " ")).trim();
|
|
8704
|
-
}
|
|
8705
|
-
|
|
8706
8734
|
//#endregion
|
|
8707
8735
|
//#region src/state/lock-manager.ts
|
|
8708
8736
|
/**
|
|
@@ -8726,6 +8754,15 @@ const RENEWAL_TTL_FRACTION = 4;
|
|
|
8726
8754
|
/** Floor, so a pathologically small TTL cannot spin the event loop. */
|
|
8727
8755
|
const MIN_RENEWAL_INTERVAL_MS = 1e3;
|
|
8728
8756
|
/**
|
|
8757
|
+
* What a surviving `lock.json` version holds, for the purge warning's
|
|
8758
|
+
* parenthetical.
|
|
8759
|
+
*
|
|
8760
|
+
* The helper makes this per-caller precisely so a reader chasing the warning is
|
|
8761
|
+
* told which object to go and inspect; `lock.json` is the one call site whose
|
|
8762
|
+
* content is NOT a secret, and saying so is more useful than a vague phrase.
|
|
8763
|
+
*/
|
|
8764
|
+
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";
|
|
8765
|
+
/**
|
|
8729
8766
|
* S3-based lock manager using conditional writes (If-None-Match)
|
|
8730
8767
|
*
|
|
8731
8768
|
* Implements distributed locking using S3's If-None-Match: "*" condition
|
|
@@ -8924,6 +8961,8 @@ var LockManager = class {
|
|
|
8924
8961
|
return false;
|
|
8925
8962
|
}
|
|
8926
8963
|
throw retryError;
|
|
8964
|
+
} finally {
|
|
8965
|
+
await this.purgeLockVersions(key, "reap");
|
|
8927
8966
|
}
|
|
8928
8967
|
}
|
|
8929
8968
|
return false;
|
|
@@ -9097,6 +9136,8 @@ var LockManager = class {
|
|
|
9097
9136
|
}
|
|
9098
9137
|
}
|
|
9099
9138
|
throw new LockError(`Failed to release lock for stack '${stackName}' (${region}): ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : void 0);
|
|
9139
|
+
} finally {
|
|
9140
|
+
await this.purgeLockVersions(this.getLockKey(stackName, region), "release");
|
|
9100
9141
|
}
|
|
9101
9142
|
}
|
|
9102
9143
|
/**
|
|
@@ -9160,12 +9201,17 @@ var LockManager = class {
|
|
|
9160
9201
|
const where = `${stackName}${region ? ` (${region})` : ""}`;
|
|
9161
9202
|
const lockInfo = await this.getLockInfo(stackName, region).catch(() => null);
|
|
9162
9203
|
this.logger.warn(lockInfo ? `Force releasing lock for stack: ${where}, owner: ${lockInfo.owner}${lockInfo.operation ? `, operation: ${lockInfo.operation}` : ""}, expired: ${this.isLockExpired(lockInfo)}` : `Force releasing lock for stack: ${where} (no lock body read — absent or unparseable; deleting the object either way, since a lock cdkd cannot read is a lock nothing else can clear)`);
|
|
9163
|
-
const
|
|
9204
|
+
const key = this.getLockKey(stackName, region);
|
|
9205
|
+
const held = this.heldLocks.get(key);
|
|
9164
9206
|
if (held) {
|
|
9165
9207
|
this.stopRenewal(held);
|
|
9166
9208
|
held.releasing = Promise.resolve();
|
|
9167
9209
|
}
|
|
9168
|
-
|
|
9210
|
+
try {
|
|
9211
|
+
await this.deleteLock(stackName, region);
|
|
9212
|
+
} finally {
|
|
9213
|
+
await this.purgeLockVersions(key, "reap");
|
|
9214
|
+
}
|
|
9169
9215
|
}
|
|
9170
9216
|
/**
|
|
9171
9217
|
* Internal method to delete the lock file from S3.
|
|
@@ -9175,6 +9221,17 @@ var LockManager = class {
|
|
|
9175
9221
|
* caller read or wrote. S3 evaluates the condition against the CURRENT
|
|
9176
9222
|
* version, which is the right unit here even on the versioned state bucket:
|
|
9177
9223
|
* the current version IS the live lock.
|
|
9224
|
+
*
|
|
9225
|
+
* EVERY CALLER MUST PAIR THIS WITH {@link purgeLockVersions} (issue
|
|
9226
|
+
* [#2346](https://github.com/go-to-k/cdkd/issues/2346) site 5). The delete
|
|
9227
|
+
* writes a DELETE MARKER on the versioned state bucket and leaves every
|
|
9228
|
+
* prior body readable, so a delete on its own grows the key forever. The
|
|
9229
|
+
* purge is NOT folded in here, for two reasons that are both per-call-site:
|
|
9230
|
+
* the log level differs (see {@link LockPurgePath}), and the takeover path
|
|
9231
|
+
* must purge AFTER its re-acquisition PUT rather than immediately after the
|
|
9232
|
+
* delete. `tests/unit/state/lock-noncurrent-version-purge.test.ts` parses
|
|
9233
|
+
* this file and fails if a caller of this method does not also call the
|
|
9234
|
+
* purge, so a fifth call site cannot quietly skip it.
|
|
9178
9235
|
*/
|
|
9179
9236
|
async deleteLock(stackName, region, etag) {
|
|
9180
9237
|
await this.ensureClientForBucket();
|
|
@@ -9187,6 +9244,98 @@ var LockManager = class {
|
|
|
9187
9244
|
}));
|
|
9188
9245
|
}
|
|
9189
9246
|
/**
|
|
9247
|
+
* Delete the lock key's NONCURRENT versions after a delete marker has been
|
|
9248
|
+
* landed on it (issue [#2346](https://github.com/go-to-k/cdkd/issues/2346)
|
|
9249
|
+
* site 5).
|
|
9250
|
+
*
|
|
9251
|
+
* ## Why the lock key needs this at all
|
|
9252
|
+
*
|
|
9253
|
+
* `cdkd bootstrap` turns VERSIONING ON for the state bucket, so
|
|
9254
|
+
* {@link deleteLock} writes a delete marker and every earlier body stays
|
|
9255
|
+
* readable through `GetObject` with a `VersionId`. Renewal writes one version
|
|
9256
|
+
* every {@link MAX_RENEWAL_INTERVAL_MS} (2 minutes at the default 30-minute
|
|
9257
|
+
* TTL, so a 30-minute deploy mints about fifteen), `deleteState` never
|
|
9258
|
+
* sweeps the lock key, and nothing else ever did — so the chain was
|
|
9259
|
+
* monotonic in stacks EVER deployed. 452 versions were measured on a single
|
|
9260
|
+
* key. Nothing in a `LockInfo` is a secret, so this is bucket cost and
|
|
9261
|
+
* listing noise rather than disclosure; it is still unbounded.
|
|
9262
|
+
*
|
|
9263
|
+
* ## Why the SHARED helper, and why that is safe on a lock
|
|
9264
|
+
*
|
|
9265
|
+
* A per-`VersionId` scheme (remember what this process minted, delete
|
|
9266
|
+
* exactly that) was designed and REJECTED on review. Its hazards were all
|
|
9267
|
+
* created by the mechanism: deleting one's OWN delete marker resurrects a
|
|
9268
|
+
* stale lock whenever a batch partially fails, a per-id delete has no
|
|
9269
|
+
* `IsLatest` guard so it can remove the CURRENT version on a refusal branch,
|
|
9270
|
+
* a `"null"` version id under suspended versioning is a legal delete target,
|
|
9271
|
+
* and the 1000-entry `DeleteObjects` cap forces a re-implementation of the
|
|
9272
|
+
* shared helper's batching. The shared helper's `IsLatest` filter removes
|
|
9273
|
+
* every one of them: it can never touch what is current, which is exactly
|
|
9274
|
+
* the live lock — ours or anyone else's.
|
|
9275
|
+
*
|
|
9276
|
+
* That filter is also what makes it correct to run this from a `finally`,
|
|
9277
|
+
* including on the arms where the delete was REFUSED. Purging noncurrent
|
|
9278
|
+
* versions while our own lock is still the current one is harmless.
|
|
9279
|
+
*
|
|
9280
|
+
* ## Level, and the one UX regression this avoids
|
|
9281
|
+
*
|
|
9282
|
+
* The four sites already purging (rollback journal, bootstrap marker,
|
|
9283
|
+
* transient template, custom-resource response) all warn, because what
|
|
9284
|
+
* survives there may be a secret and they fire on rare paths. `lock.json` is
|
|
9285
|
+
* neither: it holds no secret and `'release'` fires at the tail of EVERY
|
|
9286
|
+
* mutating command. Inheriting WARN would mean a user on the pre-#2340
|
|
9287
|
+
* four-action IAM policy starts seeing a warning after every single command,
|
|
9288
|
+
* about bucket tidiness. Be precise about WHICH user that is: a principal
|
|
9289
|
+
* missing `s3:ListBucketVersions` already gets a warn on every successful
|
|
9290
|
+
* deploy from `deleteRollbackJournal`'s purge, so nothing changes for them.
|
|
9291
|
+
* The one this split protects is the principal who HAS `ListBucketVersions`
|
|
9292
|
+
* but lacks `s3:DeleteObjectVersion` — silent today, and newly noisy without
|
|
9293
|
+
* it. So
|
|
9294
|
+
* release-path failures go to `debug` and only the rare `'reap'` paths warn,
|
|
9295
|
+
* which is the cost profile `docs/state-management.md` means by "only the
|
|
9296
|
+
* cleanup paths that need them".
|
|
9297
|
+
*
|
|
9298
|
+
* A warn-once-per-process dedupe was the other option and is not taken, for
|
|
9299
|
+
* the reason `s3-noncurrent-version-purge.ts` gives for rejecting one: it
|
|
9300
|
+
* needs module-global state, which this repo has been bitten by under
|
|
9301
|
+
* `--stack-concurrency > 1`.
|
|
9302
|
+
*
|
|
9303
|
+
* NEVER THROWS. The helper guarantees that for itself, but
|
|
9304
|
+
* `ensureClientForBucket()` and `ownerParam()` sit outside it, so the wrap
|
|
9305
|
+
* below is what makes the contract true. Only `ensureClientForBucket()` is a
|
|
9306
|
+
* known rejector (`GetBucketLocation`); `ownerParam()` is NOT — an earlier
|
|
9307
|
+
* revision claimed it reached STS, and `src/cli/upload-cfn-template.ts`
|
|
9308
|
+
* records that as false, since `resolveExpectedBucketOwner` wraps every
|
|
9309
|
+
* await and degrades to `undefined`. Naming an unproved mechanism is worse
|
|
9310
|
+
* than naming none. The wrap stays because a caller-supplied logger can
|
|
9311
|
+
* throw and because a future edit can add an awaited call here — the same
|
|
9312
|
+
* reasoning as
|
|
9313
|
+
* `S3StateBackend.purgeNoncurrentVersions`. It matters more here than there:
|
|
9314
|
+
* every call site is a `finally`, and a throw from a `finally` REPLACES the
|
|
9315
|
+
* `LockError` the release was raising. Nothing on this path is
|
|
9316
|
+
* interrupt-aware (`LockManager` starts no interrupt watch and issues no
|
|
9317
|
+
* `withRetry`), so there is no `InterruptedWaitError` for the swallow below
|
|
9318
|
+
* to hide.
|
|
9319
|
+
*/
|
|
9320
|
+
async purgeLockVersions(key, path) {
|
|
9321
|
+
const report = (message) => {
|
|
9322
|
+
try {
|
|
9323
|
+
if (path === "reap") this.logger.warn(message);
|
|
9324
|
+
else this.logger.debug(message);
|
|
9325
|
+
} catch {}
|
|
9326
|
+
};
|
|
9327
|
+
try {
|
|
9328
|
+
await this.ensureClientForBucket();
|
|
9329
|
+
await purgeNoncurrentKeyVersions(this.s3Client, this.config.bucket, [key], {
|
|
9330
|
+
requestFields: await this.ownerParam(),
|
|
9331
|
+
logger: { warn: report },
|
|
9332
|
+
objectDescription: LOCK_OBJECT_DESCRIPTION
|
|
9333
|
+
});
|
|
9334
|
+
} catch (error) {
|
|
9335
|
+
report(`Could not purge noncurrent versions of the lock key '${displaySafe(key)}' in bucket '${this.config.bucket}': the purge could not be started. Their previous versions survive and remain readable via GetObject with a VersionId (${LOCK_OBJECT_DESCRIPTION}). Grant s3:ListBucketVersions and s3:DeleteObjectVersion on the state bucket, or purge the key by hand. Underlying error: ${error instanceof Error ? error.message : String(error)}`);
|
|
9336
|
+
}
|
|
9337
|
+
}
|
|
9338
|
+
/**
|
|
9190
9339
|
* Record a lock this process now holds and start renewing it.
|
|
9191
9340
|
*/
|
|
9192
9341
|
trackHeldLock(args) {
|
|
@@ -21313,7 +21462,7 @@ var CloudControlProvider = class {
|
|
|
21313
21462
|
const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
|
|
21314
21463
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
21315
21464
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
21316
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
21465
|
+
const { ASGProvider } = await import("./asg-provider-B1EEjygi.js").then((n) => n.n);
|
|
21317
21466
|
return withIndeterminateGuard(await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
|
|
21318
21467
|
}
|
|
21319
21468
|
const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
|
|
@@ -33257,5 +33406,5 @@ var DeployEngine = class {
|
|
|
33257
33406
|
};
|
|
33258
33407
|
|
|
33259
33408
|
//#endregion
|
|
33260
|
-
export { DEFAULT_STATE_PREFIX as $,
|
|
33261
|
-
//# sourceMappingURL=deploy-engine-
|
|
33409
|
+
export { DEFAULT_STATE_PREFIX as $, CFN_TEMPLATE_BODY_LIMIT as $n, maskSecretsInError as $t, bold as A, describeAwsFailure as An, NestedStackChildDirectDestroyError as Ar, requireConfigObject as At, secretBearingStateKeyWarning as B, Synthesizer as Bn, isCdkdError as Br, DiffCalculator as Bt, unsupportedFinalSnapshotError as C, getBootstrapMarkerKey as Cn, DeployCancelledError as Cr, assertRegionMatch as Ct, isStatefulRecreateTargetSync as D, validateAssetBucketName as Dn, LocalStartServiceError as Dr, readConfigString as Dt, MULTI_REGION_RECREATE_BLOCKED_TYPES as E, readBootstrapMarkerBody as En, LocalMigrateError as Er, configStringRefusal as Et, yellow as F, partitionSensitiveEnv as Fn, StackHasActiveImportsError as Fr, s3BucketDomainName as Ft, clearOnUpdateRemoval as G, resolveAutoAssetStorage as Gn, isThrottlingError as Gr, TemplateParser as Gt, getCurrentResourceSecrets as H, getDefaultStateBucketName as Hn, withErrorHandling as Hr, describeTypeWithThrottleRetry as Ht, collectDeclaredOutputNames as I, runDockerForeground as In, StackTerminationProtectionError as Ir, s3BucketDualStackDomainName as It, findSilentDropProperties as J, resolveStateBucketWithDefault as Jn, markRedactedCause as Jr, TEMPLATE_SOURCED_RULES as Jt, ProviderRegistry as K, resolveCaptureObservedState as Kn, isTransientServerError as Kr, STATE_SOURCED_CROSS_GENERATION_RULES as Kt, collectPublishedOutputNames as L, runDockerStreaming as Ln, StateError as Lr, s3BucketRegionalDomainName as Lt, gray as M, dockerSpawnEnvWithSensitive as Mn, ProvisioningError as Mr, classifyReplaySecretRegion as Mt, green as N, formatDockerLoginError as Nn, ResourceTimeoutError as Nr, producerRegionsFromState as Nt, renderStatefulReason as O, validateContainerRepoName as On, LockError as Or, replayWarn as Ot, red as P, getDockerCmd as Pn, ResourceUpdateNotSupportedError as Pr, s3BucketArn as Pt, CUSTOM_RESOURCE_RESPONSE_PREFIX as Q, warnDeprecatedNoPrefixCliFlag as Qn, isSingleDynamicReferenceToken as Qt, exportAliasCollisionScrubWarning as R, AssetManifestLoader as Rn, SynthesisError as Rr, s3BucketWebsiteUrl as Rt, refusesFinalSnapshot as S, ensureAssetStorage as Sn, DependencyError as Sr, resolveExplicitPhysicalId as St, extractDeploymentEventError as T, parseBootstrapMarker as Tn, LocalInvokeBuildError as Tr, configBooleanRefusal as Tt, IAMRoleProvider as U, getLegacyStateBucketName as Un, isMarkedNonRetryable as Ur, withRetry as Ut, stateKeySecretExposure as V, synthesisStatusMessage as Vn, normalizeAwsError as Vr, INTRINSIC_KEYS as Vt, collectInlinePolicyNamesManagedBySiblings as W, resolveApp as Wn, isRetryableTransientError as Wr, DagBuilder as Wt, maskDeep as X, resolveUseCdkBootstrapAssets as Xn, __exportAll as Xr, dynamicReferenceTokens as Xt, createMaskedRetryLogger as Y, resolveStateBucketWithDefaultAndSource as Yn, retryClassificationText as Yr, createSecretMasker as Yt, maskerOrIdentity as Z, stateBucketExistenceConfirmed as Zn, errorCauseChain as Zt, PRE_DELETE_SNAPSHOT_TYPES as _, escapeRegExp$1 as _n, setAwsClients as _r, isUnboundTemplateParameter as _t, DeploymentEventsStore as a, rebuildClientForBucketRegion as an, displaySafe as ar, CloudControlProvider as at, createPreDeleteFinalSnapshot as b, BOOTSTRAP_MARKER_PREFIX as bn, ConfigError as br, WAFv2WebACLProvider as bt, replayFailedOperations as c, importableOutputs as cn, canonicalizeRegion as cr, deleteIndeterminateGuards as ct, updatePartialReason as d, stringifyValue as dn, processStackMessages as dr, isTerminationProtectionPropagationError as dt, maskSecretsInText as en, CFN_TEMPLATE_URL_LIMIT as er, beginCommandInterruptScope as et, withResourceDeadline as f, WorkGraph as fn, clearBucketRegionCache as fr, IntrinsicFunctionResolver as ft, ATOMIC_FINAL_SNAPSHOT_TYPES as g, rewriteTemplateAssetReferences as gn, resetAwsClients as gr, getAccountInfo as gt, computeImplicitDeleteEdges as h, loadPublishableAssetManifest as hn, getAwsClients as hr, coerceParameterTypedValue as ht, DeploymentEventsReader as i, S3StateBackend as in, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as ir, startInterruptWatch as it, cyan as j, buildDockerImage as jn, PartialFailureError as jr, requireConfigString as jt, formatResourceLine as k, buildDenyExternalAccessPolicy as kn, MissingCdkCliError as kr, requireConfigArray as kt, replayRollback as l, shouldRetainResource as ln, derivePartitionAndUrlSuffix as lr, deleteSkipReason as lt, IMPLICIT_DELETE_DEPENDENCIES as m, createAssetRedirectResolver as mn, AwsClients as mr, cfnRefValueFromPhysicalId as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, scrubResourceRecord as nn, findLargeInlineResources as nr, interruptWatchListenerCount as nt, planFailedOps as o, exportNamesCarriedFrom as on, expectedOwnerParam as or, slowCcOperationTimeoutMs as ot, maskingRetryLogger as p, buildAssetRedirectMap as pn, resolveBucketRegion as pr, carriesDynamicReference as pt, findActionableSilentDrops as q, resolveSkipPrefix as qn, markNonRetryable as qr, STATE_SOURCED_READBACK_RULES as qt, DeployEngine as r, LockManager as rn, uploadCfnTemplate as rr, isInterruptedWaitError as rt, planRollback as s, importableOutputKeys as sn, PARTITION_TABLE as sr, UNSPECIFIED_SKIP_REASON as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, redactSecretsForState as tn, MIGRATE_TMP_PREFIX as tr, endCommandInterruptScope as tt, updatePartialMessage as u, AssetPublisher as un, AssemblyReader as ur, disableInstanceApiTermination as ut, buildFinalSnapshotIdentifier as v, stripControlChars as vn, AssetError as vr, parameterTypeMayLoseSecretIdentity as vt, makeCanonicalizePropertiesFn as w, isCrossRegionRedirect as wn, DynamicReferenceRegionAmbiguousError as wr, coerceCfnBoolean as wt, isFinalSnapshotError as x, assertAssetBucketRegion as xn, CrossAccountSecretRefusalError as xr, normalizeAwsTagsToCfn as xt, ccRoutedFinalSnapshotError as y, AssetModeResolver as yn, CdkdError as yr, refStateLookupFromResource as yt, isExportAliasCollision as z, getDockerImageBySourceHash as zn, formatError as zr, applyRoleArnIfSet as zt };
|
|
33410
|
+
//# sourceMappingURL=deploy-engine-DmWPwiyQ.js.map
|