@go-to-k/cdkd 0.285.14 → 0.285.16
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-BvIH9Ivw.js → asg-provider-DoP-32eU.js} +2 -2
- package/dist/{asg-provider-BvIH9Ivw.js.map → asg-provider-DoP-32eU.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-CA50haPO.js → deploy-engine-DuFUy9D0.js} +446 -76
- package/dist/deploy-engine-DuFUy9D0.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-BSanOkkV.js → program-C-xfsrSP.js} +112 -140
- package/dist/{program-BSanOkkV.js.map → program-C-xfsrSP.js.map} +1 -1
- package/dist/{version-DP5kGlzj.js → version-C_ZRHikq.js} +2 -2
- package/dist/{version-DP5kGlzj.js.map → version-C_ZRHikq.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-CA50haPO.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
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-Dw-3y48G.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-C_ZRHikq.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";
|
|
@@ -7927,6 +7927,140 @@ function parseRollbackJournal(bodyString, stackName) {
|
|
|
7927
7927
|
};
|
|
7928
7928
|
}
|
|
7929
7929
|
|
|
7930
|
+
//#endregion
|
|
7931
|
+
//#region src/state/state-prefix.ts
|
|
7932
|
+
/**
|
|
7933
|
+
* The default S3 key prefix for cdkd state.
|
|
7934
|
+
*
|
|
7935
|
+
* Homed in the STATE layer rather than in `src/cli/commands/state-file-keys.ts`
|
|
7936
|
+
* (which re-exports it, so its four existing importers are unchanged) because
|
|
7937
|
+
* `src/state/lock-contention-message.ts` needs it to decide whether a recovery
|
|
7938
|
+
* hint should spell `--state-prefix` at all, and a `src/state/**` module
|
|
7939
|
+
* importing from `src/cli/commands/**` inverts the layering — the CLI sits
|
|
7940
|
+
* ABOVE the state layer in the 7-layer architecture, not below it.
|
|
7941
|
+
*
|
|
7942
|
+
* Note this is only the DEFAULT. Other commands accept `--state-prefix`, so
|
|
7943
|
+
* whole-bucket listings deliberately do not scope to it.
|
|
7944
|
+
*/
|
|
7945
|
+
const DEFAULT_STATE_PREFIX = "cdkd";
|
|
7946
|
+
/**
|
|
7947
|
+
* The state-bucket prefix `CustomResourceProvider` PUTs its response
|
|
7948
|
+
* placeholders under, one object per invocation
|
|
7949
|
+
* (`custom-resource-responses/{requestId}.json`).
|
|
7950
|
+
*
|
|
7951
|
+
* Homed here for the same layering reason as {@link DEFAULT_STATE_PREFIX}: the
|
|
7952
|
+
* PRODUCER is `src/provisioning/providers/custom-resource-provider.ts` and the
|
|
7953
|
+
* COLLECTOR is `src/cli/commands/gc.ts`, so a copy in either would be a copy
|
|
7954
|
+
* the other could drift from — and the two spellings would then disagree about
|
|
7955
|
+
* which objects exist, which is the only way a sweeper can miss the family it
|
|
7956
|
+
* was written for (issue #2052). `src/cli/commands/state-file-keys.ts`
|
|
7957
|
+
* re-exports it so gc reads it alongside the other state-key constants.
|
|
7958
|
+
*
|
|
7959
|
+
* Note this is only the DEFAULT: `ProviderRegistry` can be configured with a
|
|
7960
|
+
* different `responsePrefix`, so a sweep scoped to this value is a sweep of the
|
|
7961
|
+
* default layout. gc has no access to a non-default one — nothing persists it —
|
|
7962
|
+
* which is stated at the sweep's own call site rather than implied here.
|
|
7963
|
+
*/
|
|
7964
|
+
const CUSTOM_RESOURCE_RESPONSE_PREFIX = "custom-resource-responses";
|
|
7965
|
+
|
|
7966
|
+
//#endregion
|
|
7967
|
+
//#region src/state/lock-contention-message.ts
|
|
7968
|
+
/**
|
|
7969
|
+
* The message every fail-fast lock-contention site raises.
|
|
7970
|
+
*
|
|
7971
|
+
* Issue #2161 made six commands throw on `acquireLock` returning `false`
|
|
7972
|
+
* instead of running under a foreign lock. Issue #2170 is the follow-up: the
|
|
7973
|
+
* message those sites raise asked the user to decide "is another process
|
|
7974
|
+
* active?" while printing none of the evidence that would answer it, and the
|
|
7975
|
+
* recovery command it suggested could resolve against the wrong AWS account.
|
|
7976
|
+
*
|
|
7977
|
+
* Both matter more than they look, because of WHICH locks reach this message.
|
|
7978
|
+
* `LockManager.acquireLock` reaps an EXPIRED foreign lock and retries, so a
|
|
7979
|
+
* `false` return means the lock is LIVE — in practice a running `cdkd deploy`.
|
|
7980
|
+
* The user who follows a bare `cdkd force-unlock <stack>` suggestion therefore
|
|
7981
|
+
* deletes a live owner's lock and reproduces issue #2161's harm by hand, which
|
|
7982
|
+
* is the outcome #2161 exists to prevent.
|
|
7983
|
+
*
|
|
7984
|
+
* Centralising the text here also settles the third finding: the nine sites
|
|
7985
|
+
* had drifted to three spellings (`for stack` / `for nested stack` /
|
|
7986
|
+
* `for nested-stack child`), so a user grepping CI logs for one of them found
|
|
7987
|
+
* two of three. `subject` now varies only the noun.
|
|
7988
|
+
*/
|
|
7989
|
+
/**
|
|
7990
|
+
* Stand-in for a value with nothing renderable left after sanitization. Named
|
|
7991
|
+
* rather than inlined so the message and the command-suppression branch cannot
|
|
7992
|
+
* disagree about what "unrenderable" looks like.
|
|
7993
|
+
*/
|
|
7994
|
+
const UNRENDERABLE = "<unrenderable>";
|
|
7995
|
+
/** Render `expiresIn` without implying more precision than a clock skew allows. */
|
|
7996
|
+
function formatRemaining(ms) {
|
|
7997
|
+
if (!Number.isFinite(ms)) return "at an unknown time";
|
|
7998
|
+
if (ms <= 0) return "already expired";
|
|
7999
|
+
const minutes = Math.round(ms / 6e4);
|
|
8000
|
+
if (minutes < 1) return "in under a minute";
|
|
8001
|
+
return `in ~${minutes}m`;
|
|
8002
|
+
}
|
|
8003
|
+
function shellQuote(value) {
|
|
8004
|
+
return /^[A-Za-z0-9._/@:+-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
|
|
8005
|
+
}
|
|
8006
|
+
/**
|
|
8007
|
+
* The `cdkd force-unlock ...` line, carrying every flag that decides which
|
|
8008
|
+
* lock object it resolves to.
|
|
8009
|
+
*/
|
|
8010
|
+
function buildForceUnlockCommand(stackName, region, recovery) {
|
|
8011
|
+
const safeStack = displaySafe(stackName, { asciiOnly: true });
|
|
8012
|
+
const stackIsExact = safeStack === stackName;
|
|
8013
|
+
const safeRegion = region === void 0 ? void 0 : displaySafe(region, { asciiOnly: true });
|
|
8014
|
+
if (!safeStack || safeRegion === "" || !stackIsExact || !(region === void 0 || safeRegion === region)) return "";
|
|
8015
|
+
const parts = [safeRegion === void 0 ? `cdkd force-unlock ${shellQuote(safeStack)}` : `cdkd force-unlock ${shellQuote(safeStack)} --stack-region ${shellQuote(safeRegion)}`];
|
|
8016
|
+
if (recovery?.profile) parts.push(`--profile ${shellQuote(recovery.profile)}`);
|
|
8017
|
+
if (recovery?.stateBucket) parts.push(`--state-bucket ${shellQuote(recovery.stateBucket)}`);
|
|
8018
|
+
if (recovery?.statePrefix && recovery.statePrefix !== "cdkd") parts.push(`--state-prefix ${shellQuote(recovery.statePrefix)}`);
|
|
8019
|
+
return parts.join(" ");
|
|
8020
|
+
}
|
|
8021
|
+
/**
|
|
8022
|
+
* The force-quit banner's recovery sentence.
|
|
8023
|
+
*
|
|
8024
|
+
* Exported so the two `destroy-runner.ts` banners do not each decide what to
|
|
8025
|
+
* say when {@link buildForceUnlockCommand} suppresses — a banner ending in a
|
|
8026
|
+
* bare `run: ` is the shape the review found, and two copies of the branch is
|
|
8027
|
+
* how the next one drifts. Returns a leading-space clause so the caller can
|
|
8028
|
+
* concatenate it unconditionally.
|
|
8029
|
+
*/
|
|
8030
|
+
function forceQuitRecoveryClause(stackName, region, recovery) {
|
|
8031
|
+
const command = buildForceUnlockCommand(stackName, region, recovery);
|
|
8032
|
+
return command ? ` If the next run reports a lock, run: ${command}` : " Inspect the lock object directly: the name or region recorded for this stack cannot be reproduced safely on a command line, so any command shown here would address a different lock.";
|
|
8033
|
+
}
|
|
8034
|
+
/**
|
|
8035
|
+
* Build the contention message, reading the holder's identity best-effort.
|
|
8036
|
+
*
|
|
8037
|
+
* The `getLockInfo` read is one GetObject and is deliberately NOT allowed to
|
|
8038
|
+
* fail the command: the caller is already on its way to throwing, and turning
|
|
8039
|
+
* a contention refusal into an S3 error would lose the reason. A failed or
|
|
8040
|
+
* absent read degrades to the evidence-free wording rather than to a crash.
|
|
8041
|
+
*/
|
|
8042
|
+
async function buildLockContentionMessage(args) {
|
|
8043
|
+
const { lockManager, stackName, region, subject = "stack", recovery, heldClause, suffix } = args;
|
|
8044
|
+
let held = heldClause ?? "another cdkd process holds it";
|
|
8045
|
+
let sawHolder = false;
|
|
8046
|
+
try {
|
|
8047
|
+
const info = await lockManager.getLockInfo(stackName, region);
|
|
8048
|
+
if (info) {
|
|
8049
|
+
const operation = info.operation ? `, operation: ${displaySafe(info.operation)}` : "";
|
|
8050
|
+
const expires = formatRemaining(info.expiresAt - Date.now());
|
|
8051
|
+
const owner = displaySafe(info.owner);
|
|
8052
|
+
const holder = owner ? `held by ${owner}${operation}` : `held by an unnamed holder`;
|
|
8053
|
+
held = `${heldClause ? `${heldClause} — ` : ""}${holder}, expires ${expires}`;
|
|
8054
|
+
if (owner) sawHolder = true;
|
|
8055
|
+
}
|
|
8056
|
+
} catch {}
|
|
8057
|
+
const advice = sawHolder ? `That process is still running — wait for it to finish. Only if you are certain it is gone` : `Wait for it to finish, or if you are certain no other process is active`;
|
|
8058
|
+
const recoveryCommand = buildForceUnlockCommand(stackName, region, recovery);
|
|
8059
|
+
const head = `Could not acquire lock for ${subject} '${displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>"}' (${displaySafe(region, { asciiOnly: true }) || "<unrenderable>"}) — ${held}.` + (suffix ? ` ${suffix}` : "");
|
|
8060
|
+
if (!recoveryCommand) return `${head} ${advice}. No recovery command can be shown: the name or region recorded for this lock contains characters that cannot be reproduced safely on a command line, so any command shown here would address a different lock — inspect the lock object directly.`;
|
|
8061
|
+
return `${head} ${advice}, run: ${recoveryCommand}`;
|
|
8062
|
+
}
|
|
8063
|
+
|
|
7930
8064
|
//#endregion
|
|
7931
8065
|
//#region src/utils/bucket-region-client.ts
|
|
7932
8066
|
/**
|
|
@@ -8013,6 +8147,23 @@ const LEGACY_KEY_DEPTH = 2;
|
|
|
8013
8147
|
/** The `version: 2` region-prefixed key. */
|
|
8014
8148
|
const NEW_KEY_DEPTH = 3;
|
|
8015
8149
|
/**
|
|
8150
|
+
* Does a legacy record classified by {@link LegacyStateProbe} belong to an
|
|
8151
|
+
* operation targeting `region`?
|
|
8152
|
+
*
|
|
8153
|
+
* Free function, and shared by both consumers on purpose: the delete sweep
|
|
8154
|
+
* needs the probe's KIND as well as this verdict (to warn when a record may
|
|
8155
|
+
* have been left behind), and duplicating the mapping at that call site is
|
|
8156
|
+
* how the two would drift apart again.
|
|
8157
|
+
*/
|
|
8158
|
+
function legacyProbeBelongsTo(probe, region) {
|
|
8159
|
+
switch (probe.kind) {
|
|
8160
|
+
case "region": return probe.region === region;
|
|
8161
|
+
case "no-region": return true;
|
|
8162
|
+
case "absent":
|
|
8163
|
+
case "unreadable": return false;
|
|
8164
|
+
}
|
|
8165
|
+
}
|
|
8166
|
+
/**
|
|
8016
8167
|
* S3-based state backend using conditional writes for optimistic locking.
|
|
8017
8168
|
*
|
|
8018
8169
|
* State keys are region-scoped (`{prefix}/{stackName}/{region}/state.json`)
|
|
@@ -8178,7 +8329,7 @@ var S3StateBackend = class {
|
|
|
8178
8329
|
await this.ensureClientForBucket();
|
|
8179
8330
|
const newKey = this.getStateKey(stackName, region);
|
|
8180
8331
|
if (await this.headObject(newKey)) return true;
|
|
8181
|
-
return this.
|
|
8332
|
+
return this.legacyBelongsToRegion(stackName, region);
|
|
8182
8333
|
}
|
|
8183
8334
|
/**
|
|
8184
8335
|
* Get state for a stack, transparently falling back to the legacy key.
|
|
@@ -8310,7 +8461,12 @@ var S3StateBackend = class {
|
|
|
8310
8461
|
...await this.ownerParam(),
|
|
8311
8462
|
Key: this.getStateKey(stackName, region)
|
|
8312
8463
|
}));
|
|
8313
|
-
|
|
8464
|
+
const legacyProbe = await this.probeLegacyState(stackName);
|
|
8465
|
+
if (legacyProbe.kind === "unreadable") {
|
|
8466
|
+
const safeName = this.displayName(stackName);
|
|
8467
|
+
this.logger.warn(`Could not read the legacy state record for '${safeName}' while cleaning up (${legacyProbe.reason}). If one exists it was left in place. Re-run with --verbose for the details.`);
|
|
8468
|
+
}
|
|
8469
|
+
if (legacyProbeBelongsTo(legacyProbe, region)) {
|
|
8314
8470
|
await this.s3Client.send(new DeleteObjectCommand({
|
|
8315
8471
|
Bucket: this.config.bucket,
|
|
8316
8472
|
...await this.ownerParam(),
|
|
@@ -8330,6 +8486,48 @@ var S3StateBackend = class {
|
|
|
8330
8486
|
}
|
|
8331
8487
|
}
|
|
8332
8488
|
/**
|
|
8489
|
+
* Delete a legacy state file that names no region (issue #2537).
|
|
8490
|
+
*
|
|
8491
|
+
* `deleteState` cannot serve this case: it takes a region, keys the primary
|
|
8492
|
+
* delete off it, and sweeps the legacy key only when that key's own `region`
|
|
8493
|
+
* field matches. A `version: 1` blob whose body carries no `region` at all
|
|
8494
|
+
* has nothing to match, so it falls through every branch there — which is
|
|
8495
|
+
* how `cdkd state orphan` came to report a removal it never performed.
|
|
8496
|
+
*
|
|
8497
|
+
* Unconditional, and deliberately so — but NOT because the caller's ref
|
|
8498
|
+
* proves the body names no region. `listStacks` derives that ref from
|
|
8499
|
+
* `readLegacyRegion`, which also returns undefined on a swallowed 403 / 503
|
|
8500
|
+
* / unparseable body, so a record that DOES name a region can surface
|
|
8501
|
+
* region-less. The delete is still right there: the caller
|
|
8502
|
+
* (`cdkd state orphan` with no `--stack-region`) means "every record for
|
|
8503
|
+
* this name", and this key is one of them. A caller that means something
|
|
8504
|
+
* narrower must not use this method.
|
|
8505
|
+
*
|
|
8506
|
+
* No rollback-journal sweep: journal keys exist only in the region-scoped
|
|
8507
|
+
* layout (see `getRollbackJournalKey`), so a region-less record can have
|
|
8508
|
+
* none.
|
|
8509
|
+
*/
|
|
8510
|
+
async deleteLegacyState(stackName) {
|
|
8511
|
+
await this.ensureClientForBucket();
|
|
8512
|
+
const key = this.getLegacyStateKey(stackName);
|
|
8513
|
+
try {
|
|
8514
|
+
const safeStack = displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>";
|
|
8515
|
+
this.logger.debug(`Deleting legacy state: ${safeStack} (${displaySafe(key, { asciiOnly: true }) || "<unrenderable>"})`);
|
|
8516
|
+
await this.s3Client.send(new DeleteObjectCommand({
|
|
8517
|
+
Bucket: this.config.bucket,
|
|
8518
|
+
...await this.ownerParam(),
|
|
8519
|
+
Key: key
|
|
8520
|
+
}));
|
|
8521
|
+
this.logger.debug(`Legacy state deleted: ${safeStack}`);
|
|
8522
|
+
} catch (error) {
|
|
8523
|
+
const normalized = normalizeAwsError(error, {
|
|
8524
|
+
bucket: this.config.bucket,
|
|
8525
|
+
operation: "DeleteObject"
|
|
8526
|
+
});
|
|
8527
|
+
throw new StateError(`Failed to delete legacy state for stack '${displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>"}': ${normalized.message}`, normalized);
|
|
8528
|
+
}
|
|
8529
|
+
}
|
|
8530
|
+
/**
|
|
8333
8531
|
* List all stacks with state in the bucket.
|
|
8334
8532
|
*
|
|
8335
8533
|
* Returns one `{stackName, region}` pair per state file. Both layouts
|
|
@@ -8708,29 +8906,85 @@ var S3StateBackend = class {
|
|
|
8708
8906
|
}
|
|
8709
8907
|
}
|
|
8710
8908
|
/**
|
|
8711
|
-
* Read the legacy
|
|
8712
|
-
*
|
|
8713
|
-
* entries during `listStacks`.
|
|
8909
|
+
* Read the legacy key and classify what is there — {@link LegacyStateProbe}
|
|
8910
|
+
* says what each answer means and which consumer acts on it.
|
|
8714
8911
|
*/
|
|
8715
|
-
|
|
8912
|
+
/**
|
|
8913
|
+
* A stack name safe to put in a log line. Names reach this class from S3
|
|
8914
|
+
* key segments, which anyone able to write the bucket controls, so every
|
|
8915
|
+
* message that renders one goes through here (issue #2170's class).
|
|
8916
|
+
*/
|
|
8917
|
+
displayName(stackName) {
|
|
8918
|
+
return displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>";
|
|
8919
|
+
}
|
|
8920
|
+
async probeLegacyState(stackName) {
|
|
8716
8921
|
try {
|
|
8717
8922
|
const response = await this.s3Client.send(new GetObjectCommand({
|
|
8718
8923
|
Bucket: this.config.bucket,
|
|
8719
8924
|
...await this.ownerParam(),
|
|
8720
8925
|
Key: this.getLegacyStateKey(stackName)
|
|
8721
8926
|
}));
|
|
8722
|
-
if (!response.Body)
|
|
8927
|
+
if (!response.Body) {
|
|
8928
|
+
this.logger.debug(`Legacy state probe for '${this.displayName(stackName)}': response carried no body`);
|
|
8929
|
+
return {
|
|
8930
|
+
kind: "unreadable",
|
|
8931
|
+
reason: "the response carried no body"
|
|
8932
|
+
};
|
|
8933
|
+
}
|
|
8723
8934
|
const bodyString = await response.Body.transformToString();
|
|
8724
|
-
const
|
|
8725
|
-
return
|
|
8935
|
+
const raw = JSON.parse(bodyString).region;
|
|
8936
|
+
if (!raw) return { kind: "no-region" };
|
|
8937
|
+
if (typeof raw !== "string") {
|
|
8938
|
+
this.logger.debug(`Legacy state probe for '${this.displayName(stackName)}': 'region' is ${typeof raw}, not a string`);
|
|
8939
|
+
return {
|
|
8940
|
+
kind: "unreadable",
|
|
8941
|
+
reason: `its 'region' field is ${typeof raw}, not a string`
|
|
8942
|
+
};
|
|
8943
|
+
}
|
|
8944
|
+
return {
|
|
8945
|
+
kind: "region",
|
|
8946
|
+
region: raw
|
|
8947
|
+
};
|
|
8726
8948
|
} catch (error) {
|
|
8727
|
-
if (isNoSuchKey(error)) return
|
|
8728
|
-
|
|
8729
|
-
|
|
8949
|
+
if (isNoSuchKey(error)) return { kind: "absent" };
|
|
8950
|
+
const { detail } = describeAwsFailure(error);
|
|
8951
|
+
this.logger.debug(`Could not read legacy state region for '${this.displayName(stackName)}': ${displaySafe(detail, { asciiOnly: true }) || "<unrenderable>"}`);
|
|
8952
|
+
return {
|
|
8953
|
+
kind: "unreadable",
|
|
8954
|
+
reason: displaySafe(error instanceof Error && error.name ? error.name : "an unknown error", { asciiOnly: true }) || "<unrenderable>"
|
|
8955
|
+
};
|
|
8730
8956
|
}
|
|
8731
8957
|
}
|
|
8732
|
-
|
|
8733
|
-
|
|
8958
|
+
/**
|
|
8959
|
+
* The region a legacy record should be listed under, or `undefined` when it
|
|
8960
|
+
* names none / could not be read. Preserves `listStacks`'s behaviour across
|
|
8961
|
+
* the {@link probeLegacyState} split.
|
|
8962
|
+
*/
|
|
8963
|
+
async readLegacyRegion(stackName) {
|
|
8964
|
+
const probe = await this.probeLegacyState(stackName);
|
|
8965
|
+
return probe.kind === "region" ? probe.region : void 0;
|
|
8966
|
+
}
|
|
8967
|
+
/**
|
|
8968
|
+
* Whether an operation targeting `region` owns the legacy record — the
|
|
8969
|
+
* DELETE-side counterpart of `tryGetLegacy`'s read gate, and issue #2550.
|
|
8970
|
+
*
|
|
8971
|
+
* The two must agree. `tryGetLegacy` accepts a body that names NO region
|
|
8972
|
+
* from any region (`if (state.region && state.region !== region)` is false
|
|
8973
|
+
* when the field is absent), so `cdkd destroy` reads such a record, deletes
|
|
8974
|
+
* the AWS resources, and finishes. The old equality test here answered
|
|
8975
|
+
* `undefined === 'us-east-1'` — false — so the record survived a successful
|
|
8976
|
+
* destroy, kept appearing in `cdkd state list`, and the next deploy of that
|
|
8977
|
+
* name planned updates against resources that were gone.
|
|
8978
|
+
*
|
|
8979
|
+
* `unreadable` is deliberately NOT treated as `no-region`: a 403, a 503 or a
|
|
8980
|
+
* malformed body says nothing about who owns the record, and a read that
|
|
8981
|
+
* failed must never authorise a delete. It collapsed into the same
|
|
8982
|
+
* `undefined` as the other two before, which is why the one-line fix — make
|
|
8983
|
+
* `undefined` match — was wrong: it would also have made `stateExists`
|
|
8984
|
+
* report state for a stack that has none, since `absent` reads the same way.
|
|
8985
|
+
*/
|
|
8986
|
+
async legacyBelongsToRegion(stackName, region) {
|
|
8987
|
+
return legacyProbeBelongsTo(await this.probeLegacyState(stackName), region);
|
|
8734
8988
|
}
|
|
8735
8989
|
/**
|
|
8736
8990
|
* Try to read the legacy `version: 1` state. Returns null when the legacy
|
|
@@ -16367,7 +16621,7 @@ function normalizeAwsTagsToCfn(tags) {
|
|
|
16367
16621
|
* through `update()` and surface as a hard AWS rejection, so we sanitize
|
|
16368
16622
|
* the wire-layer payload while keeping the read-side placeholder
|
|
16369
16623
|
* intact. This is the Class 2 pattern from
|
|
16370
|
-
* `docs/provider-
|
|
16624
|
+
* `docs/provider-rules.md#readcurrentstate-for-drift-detection`.
|
|
16371
16625
|
*/
|
|
16372
16626
|
function sanitizeDescription(value) {
|
|
16373
16627
|
if (value === void 0 || value === null) return void 0;
|
|
@@ -22124,7 +22378,7 @@ var CloudControlProvider = class {
|
|
|
22124
22378
|
const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
|
|
22125
22379
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
22126
22380
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
22127
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
22381
|
+
const { ASGProvider } = await import("./asg-provider-DoP-32eU.js").then((n) => n.n);
|
|
22128
22382
|
return withIndeterminateGuard(await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
|
|
22129
22383
|
}
|
|
22130
22384
|
const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
|
|
@@ -23309,42 +23563,6 @@ function interruptWatchListenerCount() {
|
|
|
23309
23563
|
return sharedSigintHandler === void 0 ? 0 : 1;
|
|
23310
23564
|
}
|
|
23311
23565
|
|
|
23312
|
-
//#endregion
|
|
23313
|
-
//#region src/state/state-prefix.ts
|
|
23314
|
-
/**
|
|
23315
|
-
* The default S3 key prefix for cdkd state.
|
|
23316
|
-
*
|
|
23317
|
-
* Homed in the STATE layer rather than in `src/cli/commands/state-file-keys.ts`
|
|
23318
|
-
* (which re-exports it, so its four existing importers are unchanged) because
|
|
23319
|
-
* `src/state/lock-contention-message.ts` needs it to decide whether a recovery
|
|
23320
|
-
* hint should spell `--state-prefix` at all, and a `src/state/**` module
|
|
23321
|
-
* importing from `src/cli/commands/**` inverts the layering — the CLI sits
|
|
23322
|
-
* ABOVE the state layer in the 7-layer architecture, not below it.
|
|
23323
|
-
*
|
|
23324
|
-
* Note this is only the DEFAULT. Other commands accept `--state-prefix`, so
|
|
23325
|
-
* whole-bucket listings deliberately do not scope to it.
|
|
23326
|
-
*/
|
|
23327
|
-
const DEFAULT_STATE_PREFIX = "cdkd";
|
|
23328
|
-
/**
|
|
23329
|
-
* The state-bucket prefix `CustomResourceProvider` PUTs its response
|
|
23330
|
-
* placeholders under, one object per invocation
|
|
23331
|
-
* (`custom-resource-responses/{requestId}.json`).
|
|
23332
|
-
*
|
|
23333
|
-
* Homed here for the same layering reason as {@link DEFAULT_STATE_PREFIX}: the
|
|
23334
|
-
* PRODUCER is `src/provisioning/providers/custom-resource-provider.ts` and the
|
|
23335
|
-
* COLLECTOR is `src/cli/commands/gc.ts`, so a copy in either would be a copy
|
|
23336
|
-
* the other could drift from — and the two spellings would then disagree about
|
|
23337
|
-
* which objects exist, which is the only way a sweeper can miss the family it
|
|
23338
|
-
* was written for (issue #2052). `src/cli/commands/state-file-keys.ts`
|
|
23339
|
-
* re-exports it so gc reads it alongside the other state-key constants.
|
|
23340
|
-
*
|
|
23341
|
-
* Note this is only the DEFAULT: `ProviderRegistry` can be configured with a
|
|
23342
|
-
* different `responsePrefix`, so a sweep scoped to this value is a sweep of the
|
|
23343
|
-
* default layout. gc has no access to a non-default one — nothing persists it —
|
|
23344
|
-
* which is stated at the sweep's own call site rather than implied here.
|
|
23345
|
-
*/
|
|
23346
|
-
const CUSTOM_RESOURCE_RESPONSE_PREFIX = "custom-resource-responses";
|
|
23347
|
-
|
|
23348
23566
|
//#endregion
|
|
23349
23567
|
//#region src/provisioning/masked-retry-logger.ts
|
|
23350
23568
|
/**
|
|
@@ -27945,7 +28163,7 @@ function isCustomResource(resourceType) {
|
|
|
27945
28163
|
* live value on removal. Route every optional mutable field of a
|
|
27946
28164
|
* merge-semantics API through this helper, with `clearValue` set to the
|
|
27947
28165
|
* property's CFn default or the SDK-documented clear sentinel — see
|
|
27948
|
-
* docs/provider-
|
|
28166
|
+
* docs/provider-rules.md#update-removal-semantics-clear-on-removal for the per-field checklist.
|
|
27949
28167
|
*
|
|
27950
28168
|
* Returns `newValue` when present, the `clearValue` when the field was
|
|
27951
28169
|
* present before and is now absent (removal), and `undefined` when it was
|
|
@@ -28962,9 +29180,16 @@ function formatResourceLine(op, logicalId, resourceType, verbOverride) {
|
|
|
28962
29180
|
*
|
|
28963
29181
|
* To avoid an accidental data-loss footgun, cdkd refuses to recreate
|
|
28964
29182
|
* any resource whose type is in {@link STATEFUL_TYPES} unless the user
|
|
28965
|
-
* ALSO passes `--force-stateful-recreation`.
|
|
28966
|
-
*
|
|
28967
|
-
*
|
|
29183
|
+
* ALSO passes `--force-stateful-recreation`. On the `--recreate-via-*`
|
|
29184
|
+
* and `--replace` opt-ins that is a two-flag protection, mirroring
|
|
29185
|
+
* `--remove-protection`'s pattern (see
|
|
29186
|
+
* `src/cli/commands/destroy-runner.ts`). It is NOT only that shape any
|
|
29187
|
+
* more: the guard also runs on replacement paths a plain `cdkd deploy`
|
|
29188
|
+
* reaches with no flag at all (a property-driven replacement, and the
|
|
29189
|
+
* update-failure fallback's Cloud Control trigger — issue [#2514]),
|
|
29190
|
+
* where `--force-stateful-recreation` is the ONLY flag involved. See
|
|
29191
|
+
* {@link isStatefulRecreateTargetForReplace} for the mid-deploy variant
|
|
29192
|
+
* those paths use.
|
|
28968
29193
|
*
|
|
28969
29194
|
* The list is hand-curated and intentionally **conservative**: every
|
|
28970
29195
|
* type here carries user data that the AWS service does NOT
|
|
@@ -28972,18 +29197,78 @@ function formatResourceLine(op, logicalId, resourceType, verbOverride) {
|
|
|
28972
29197
|
* AWS service treats as ephemeral (e.g. Lambda Function, IAM Role)
|
|
28973
29198
|
* are NOT in this list — recreate is cheap.
|
|
28974
29199
|
*
|
|
28975
|
-
* Two entries
|
|
28976
|
-
* the
|
|
29200
|
+
* Two entries carry a CONDITION instead of counting unconditionally.
|
|
29201
|
+
* The condition is what the guard evaluates; failing it is not a
|
|
29202
|
+
* finding that the resource holds no data (see the LogGroup note):
|
|
28977
29203
|
*
|
|
28978
29204
|
* - `AWS::S3::Bucket`: empty buckets are safe to recreate. The
|
|
28979
|
-
* deploy engine probes `s3:
|
|
29205
|
+
* deploy engine probes `s3:ListObjectVersions` at plan time and only
|
|
28980
29206
|
* refuses when the bucket has at least one object.
|
|
28981
|
-
* - `AWS::Logs::LogGroup`:
|
|
28982
|
-
*
|
|
28983
|
-
*
|
|
29207
|
+
* - `AWS::Logs::LogGroup`: the deploy engine refuses only when
|
|
29208
|
+
* `RetentionInDays > 0`. NOTE this is a KNOWN GAP, not a statement
|
|
29209
|
+
* that the rest are empty: an unset or zero retention is CloudWatch
|
|
29210
|
+
* Logs' "never expire", the most data-bearing setting there is, and
|
|
29211
|
+
* `LogsLogGroupProvider` writes `0` for precisely that. Issue
|
|
29212
|
+
* [#2558] tracks it; the predicate is left as-is here.
|
|
28984
29213
|
*
|
|
28985
29214
|
* Both conditional checks live in {@link isStatefulRecreateTarget};
|
|
28986
29215
|
* the bare {@link STATEFUL_TYPES} set is the type-only first-cut.
|
|
29216
|
+
*
|
|
29217
|
+
* **Lower bound, enforced by a test** (issue [#2514]'s review round):
|
|
29218
|
+
* every type in `final-snapshot.ts`'s `ATOMIC_FINAL_SNAPSHOT_TYPES` ∪
|
|
29219
|
+
* `PRE_DELETE_SNAPSHOT_TYPES` must appear here. That union is the
|
|
29220
|
+
* CloudFormation-documented `DeletionPolicy: Snapshot`-capable list, and
|
|
29221
|
+
* CloudFormation permits the attribute exactly where deleting the resource
|
|
29222
|
+
* destroys data worth capturing first — so membership there is an
|
|
29223
|
+
* AWS-authored statement that the type is data-bearing. The types that were
|
|
29224
|
+
* in that union and NOT here (`AWS::Redshift::Cluster`,
|
|
29225
|
+
* `AWS::ElastiCache::ReplicationGroup`, `AWS::ElastiCache::CacheCluster`)
|
|
29226
|
+
* meant cdkd took a final snapshot before a `cdkd destroy` of them
|
|
29227
|
+
* while replacing them mid-deploy with no consent flag at all.
|
|
29228
|
+
* `tests/unit/provisioning/stateful-types.test.ts` pins the subset
|
|
29229
|
+
* relation so a future addition to either snapshot set cannot land without
|
|
29230
|
+
* the guard entry.
|
|
29231
|
+
*
|
|
29232
|
+
* The relation is deliberately ONE-directional: most data-bearing types
|
|
29233
|
+
* (S3, DynamoDB, LogGroup, ECR, …) have no snapshot API at all and
|
|
29234
|
+
* CloudFormation rejects `DeletionPolicy: Snapshot` on them, so this set is
|
|
29235
|
+
* a strict superset and the reverse containment would be wrong.
|
|
29236
|
+
*
|
|
29237
|
+
* **Second lower bound, also enforced by a test**: every resource type
|
|
29238
|
+
* registered to a provider whose `delete()` consults
|
|
29239
|
+
* `DeleteContext.forceDataDelete` must appear here. That field is set ONLY
|
|
29240
|
+
* by the replacement / recreate delete sites under
|
|
29241
|
+
* `--force-stateful-recreation`, so a provider reading it has already
|
|
29242
|
+
* declared its delete destroys user data — which makes the guard list's
|
|
29243
|
+
* agreement checkable rather than hand-curated.
|
|
29244
|
+
* `AWS::S3Express::DirectoryBucket` was the one type on the wrong side.
|
|
29245
|
+
*
|
|
29246
|
+
* **Third bound, and the first that reads the population the other two are
|
|
29247
|
+
* blind to** (issue [#2553]): both bounds above are derived from
|
|
29248
|
+
* `src/provisioning/providers/**` and `register-providers.ts`, so both can
|
|
29249
|
+
* only see types cdkd has an SDK PROVIDER for — 134 of them, against the
|
|
29250
|
+
* 1371 tier-2 types that have none and whose replacement routes through
|
|
29251
|
+
* Cloud Control's DELETE. That is the population issue [#2514] was filed
|
|
29252
|
+
* about, and until now it had never been swept.
|
|
29253
|
+
* `scripts/audit-stateful-candidates.ts` reads every tier-2 type's
|
|
29254
|
+
* CloudFormation registry schema and PROPOSES the ones that declare a
|
|
29255
|
+
* createOnly property (so a rename is a replacement a plain `cdkd deploy`
|
|
29256
|
+
* reaches with no flag) AND fire a data-bearing signal. Each proposal must
|
|
29257
|
+
* end up either on this list or in that script's `NOT_GUARDED` map with a
|
|
29258
|
+
* reason; `tests/unit/scripts/stateful-candidates.test.ts` fails on any that
|
|
29259
|
+
* is in neither, and on a `NOT_GUARDED` entry the derivation has stopped
|
|
29260
|
+
* proposing. Unlike the two bounds above the signals are HEURISTIC — no AWS
|
|
29261
|
+
* artifact states "deleting this destroys user data" — so this bound makes
|
|
29262
|
+
* the widening CHECKABLE rather than proven, which is the property a hand
|
|
29263
|
+
* pass over 1371 types could not have.
|
|
29264
|
+
*
|
|
29265
|
+
* Neither fence can see a provider whose delete destroys data with NO opt-in
|
|
29266
|
+
* at all — an unconditional empty (`S3TablesProvider.deleteTableBucket`,
|
|
29267
|
+
* `S3VectorsProvider.deleteVectorBucket`) or a plainly destructive API call
|
|
29268
|
+
* (`KMSProvider`'s `ScheduleKeyDeletion`,
|
|
29269
|
+
* `CodeCommitRepositoryProvider`'s `DeleteRepository`). Those types are
|
|
29270
|
+
* hand-added with the reason at the entry, and a provider added that way
|
|
29271
|
+
* should be reviewed for whether it wants a `forceDataDelete` gate too.
|
|
28987
29272
|
*/
|
|
28988
29273
|
const STATEFUL_TYPES = /* @__PURE__ */ new Set([
|
|
28989
29274
|
"AWS::RDS::DBInstance",
|
|
@@ -28994,21 +29279,96 @@ const STATEFUL_TYPES = /* @__PURE__ */ new Set([
|
|
|
28994
29279
|
"AWS::Neptune::DBCluster",
|
|
28995
29280
|
"AWS::DynamoDB::Table",
|
|
28996
29281
|
"AWS::DynamoDB::GlobalTable",
|
|
29282
|
+
"AWS::Redshift::Cluster",
|
|
29283
|
+
"AWS::ElastiCache::CacheCluster",
|
|
29284
|
+
"AWS::ElastiCache::ReplicationGroup",
|
|
28997
29285
|
"AWS::EFS::FileSystem",
|
|
28998
29286
|
"AWS::FSx::FileSystem",
|
|
28999
29287
|
"AWS::S3::Bucket",
|
|
29288
|
+
"AWS::S3Express::DirectoryBucket",
|
|
29289
|
+
"AWS::S3Tables::TableBucket",
|
|
29290
|
+
"AWS::S3Tables::Table",
|
|
29291
|
+
"AWS::S3Tables::Namespace",
|
|
29292
|
+
"AWS::S3Vectors::VectorBucket",
|
|
29000
29293
|
"AWS::ECR::Repository",
|
|
29001
29294
|
"AWS::EC2::Volume",
|
|
29295
|
+
"AWS::EMR::Cluster",
|
|
29002
29296
|
"AWS::Kinesis::Stream",
|
|
29003
29297
|
"AWS::Elasticsearch::Domain",
|
|
29004
29298
|
"AWS::OpenSearchService::Domain",
|
|
29005
29299
|
"AWS::Cognito::UserPool",
|
|
29006
29300
|
"AWS::SecretsManager::Secret",
|
|
29007
29301
|
"AWS::SSM::Parameter",
|
|
29302
|
+
"AWS::KMS::Key",
|
|
29303
|
+
"AWS::KMS::ReplicaKey",
|
|
29304
|
+
"AWS::CodeCommit::Repository",
|
|
29008
29305
|
"AWS::Glue::Database",
|
|
29009
29306
|
"AWS::Glue::Table",
|
|
29010
29307
|
"AWS::Logs::LogGroup",
|
|
29011
|
-
"AWS::CloudFront::Distribution"
|
|
29308
|
+
"AWS::CloudFront::Distribution",
|
|
29309
|
+
"AWS::Cassandra::Table",
|
|
29310
|
+
"AWS::DocDBElastic::Cluster",
|
|
29311
|
+
"AWS::Lightsail::Database",
|
|
29312
|
+
"AWS::Timestream::Database",
|
|
29313
|
+
"AWS::Timestream::Table",
|
|
29314
|
+
"AWS::Timestream::InfluxDBCluster",
|
|
29315
|
+
"AWS::Timestream::InfluxDBInstance",
|
|
29316
|
+
"AWS::ODB::CloudAutonomousVmCluster",
|
|
29317
|
+
"AWS::ODB::CloudVmCluster",
|
|
29318
|
+
"AWS::RedshiftServerless::Namespace",
|
|
29319
|
+
"AWS::RedshiftServerless::Snapshot",
|
|
29320
|
+
"AWS::NeptuneGraph::Graph",
|
|
29321
|
+
"AWS::NeptuneGraph::GraphSnapshot",
|
|
29322
|
+
"AWS::MemoryDB::Cluster",
|
|
29323
|
+
"AWS::MemoryDB::MultiRegionCluster",
|
|
29324
|
+
"AWS::ElastiCache::ServerlessCache",
|
|
29325
|
+
"AWS::MSK::Cluster",
|
|
29326
|
+
"AWS::MSK::ServerlessCluster",
|
|
29327
|
+
"AWS::MSK::Channel",
|
|
29328
|
+
"AWS::AmazonMQ::Broker",
|
|
29329
|
+
"AWS::OSIS::Pipeline",
|
|
29330
|
+
"AWS::KinesisVideo::Stream",
|
|
29331
|
+
"AWS::Events::Archive",
|
|
29332
|
+
"AWS::OpenSearchServerless::Collection",
|
|
29333
|
+
"AWS::OpenSearchServerless::Index",
|
|
29334
|
+
"AWS::OpenSearchServerless::CollectionIndex",
|
|
29335
|
+
"AWS::Kendra::Index",
|
|
29336
|
+
"AWS::QBusiness::Index",
|
|
29337
|
+
"AWS::QBusiness::Application",
|
|
29338
|
+
"AWS::Rekognition::Collection",
|
|
29339
|
+
"AWS::Location::GeofenceCollection",
|
|
29340
|
+
"AWS::S3Vectors::Index",
|
|
29341
|
+
"AWS::Bedrock::KnowledgeBase",
|
|
29342
|
+
"AWS::Bedrock::DataAutomationLibrary",
|
|
29343
|
+
"AWS::Lightsail::Bucket",
|
|
29344
|
+
"AWS::S3Outposts::Bucket",
|
|
29345
|
+
"AWS::HealthImaging::Datastore",
|
|
29346
|
+
"AWS::HealthLake::FHIRDatastore",
|
|
29347
|
+
"AWS::SES::MailManagerArchive",
|
|
29348
|
+
"AWS::WorkspacesInstances::Volume",
|
|
29349
|
+
"AWS::IoTAnalytics::Channel",
|
|
29350
|
+
"AWS::IoTAnalytics::Datastore",
|
|
29351
|
+
"AWS::IoTAnalytics::Dataset",
|
|
29352
|
+
"AWS::CodeArtifact::Domain",
|
|
29353
|
+
"AWS::CodeArtifact::Repository",
|
|
29354
|
+
"AWS::ECR::PublicRepository",
|
|
29355
|
+
"AWS::Backup::BackupVault",
|
|
29356
|
+
"AWS::Backup::LogicallyAirGappedBackupVault",
|
|
29357
|
+
"AWS::EKS::Cluster",
|
|
29358
|
+
"AWS::SageMaker::Cluster",
|
|
29359
|
+
"AWS::SageMaker::Domain",
|
|
29360
|
+
"AWS::Cases::Domain",
|
|
29361
|
+
"AWS::CustomerProfiles::Domain",
|
|
29362
|
+
"AWS::DataZone::Domain",
|
|
29363
|
+
"AWS::CleanRooms::IdMappingTable",
|
|
29364
|
+
"AWS::CleanRooms::IntermediateTable",
|
|
29365
|
+
"AWS::CloudFront::KeyValueStore",
|
|
29366
|
+
"AWS::Connect::DataTable",
|
|
29367
|
+
"AWS::AppConfig::ConfigurationProfile",
|
|
29368
|
+
"AWS::AIOps::InvestigationGroup",
|
|
29369
|
+
"AWS::Rbin::Rule",
|
|
29370
|
+
"AWS::SMSVOICE::PhoneNumber",
|
|
29371
|
+
"AWS::SMSVOICE::SenderId"
|
|
29012
29372
|
]);
|
|
29013
29373
|
/**
|
|
29014
29374
|
* Multi-region resource types — `--recreate-via-cc-api` refuses these
|
|
@@ -29025,7 +29385,7 @@ const STATEFUL_TYPES = /* @__PURE__ */ new Set([
|
|
|
29025
29385
|
const MULTI_REGION_RECREATE_BLOCKED_TYPES = /* @__PURE__ */ new Set(["AWS::DynamoDB::GlobalTable"]);
|
|
29026
29386
|
/**
|
|
29027
29387
|
* Cheap, synchronous read of the resource's recorded properties only.
|
|
29028
|
-
* For `AWS::S3::Bucket` this returns `null` — the live `
|
|
29388
|
+
* For `AWS::S3::Bucket` this returns `null` — the live `ListObjectVersions`
|
|
29029
29389
|
* probe to distinguish empty buckets (safe to recreate) from
|
|
29030
29390
|
* non-empty (data loss) lives in
|
|
29031
29391
|
* `src/deployment/recreate-targets.ts#probeStatefulRecreateTargetsAsync`
|
|
@@ -29048,17 +29408,26 @@ function isStatefulRecreateTargetSync(resourceType, recordedProperties) {
|
|
|
29048
29408
|
return "always";
|
|
29049
29409
|
}
|
|
29050
29410
|
/**
|
|
29051
|
-
* Conservative variant for the
|
|
29411
|
+
* Conservative variant for the deploy engine's mid-deploy guard sites, which
|
|
29412
|
+
* between them serve the paths below — only one of which is reached by a flag:
|
|
29413
|
+
*
|
|
29414
|
+
* - property-driven replacement (an immutable / createOnly property changed
|
|
29415
|
+
* in the template) — fires on a plain `cdkd deploy`, no flag;
|
|
29416
|
+
* - the update-failure fallback's Cloud Control trigger (an
|
|
29417
|
+
* `UnsupportedActionException` / "does not support UPDATE" rejection) —
|
|
29418
|
+
* also no flag (issue [#2514]);
|
|
29419
|
+
* - the same fallback's `--replace` trigger (an SDK provider's typed
|
|
29420
|
+
* `ResourceUpdateNotSupportedError`).
|
|
29052
29421
|
*
|
|
29053
|
-
*
|
|
29054
|
-
*
|
|
29422
|
+
* All of them catch the rejection or classify the diff while the deploy is
|
|
29423
|
+
* already in flight, so — unlike the `--recreate-via-*` pre-flight, which
|
|
29055
29424
|
* runs {@link probeStatefulRecreateTargetsAsync} (`s3:ListObjectVersions`) —
|
|
29056
29425
|
* there is no opportunity to probe an `AWS::S3::Bucket`'s object count. The
|
|
29057
29426
|
* sync check returns `null` for S3 (it defers to that async probe), which would
|
|
29058
29427
|
* let a NON-EMPTY bucket be DELETE + CREATEd (data loss) without
|
|
29059
29428
|
* `--force-stateful-recreation`. To stay fail-safe, treat a deferred S3 bucket
|
|
29060
29429
|
* as stateful here: the user must pass `--force-stateful-recreation` to replace
|
|
29061
|
-
* ANY S3 bucket
|
|
29430
|
+
* ANY S3 bucket on any of those paths, empty or not. Every other type matches
|
|
29062
29431
|
* {@link isStatefulRecreateTargetSync} exactly (the LogGroup retention check is
|
|
29063
29432
|
* fully resolvable from recorded properties, so no conservatism is needed there).
|
|
29064
29433
|
*/
|
|
@@ -31039,7 +31408,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
31039
31408
|
refuseMaskedReplayBaseline(resolvedPrevProps, op.logicalId);
|
|
31040
31409
|
recordNestedStackParameterExpressions(secrets, op.resourceType, resolvedPrevProps, prev.properties, STATE_DERIVED_RULES);
|
|
31041
31410
|
logger.info(` Rollback: Reversing replacement of ${op.logicalId} (${op.resourceType}) — re-creating the old resource and deleting the new one`);
|
|
31042
|
-
if (STATEFUL_TYPES.has(op.resourceType)) logger.warn(` ⚠ ${op.logicalId} (${op.resourceType}) is a stateful type — the old physical resource's data was destroyed by the replacement and
|
|
31411
|
+
if (STATEFUL_TYPES.has(op.resourceType)) logger.warn(` ⚠ ${op.logicalId} (${op.resourceType}) is a stateful type — the old physical resource's data was destroyed by the replacement and is NOT recovered by this rollback; the re-created resource starts empty.`);
|
|
31043
31412
|
const { provider: createProvider } = ctx.providerRegistry.getProviderFor({
|
|
31044
31413
|
resourceType: op.resourceType,
|
|
31045
31414
|
provisionedBy: prev.provisionedBy
|
|
@@ -33606,7 +33975,7 @@ var DeployEngine = class {
|
|
|
33606
33975
|
const statefulReason = isStatefulRecreateTargetForReplace(resourceType, currentProps);
|
|
33607
33976
|
if (statefulReason && this.options.forceStatefulRecreation !== true) {
|
|
33608
33977
|
const immutableProps = change.propertyChanges?.filter((pc) => pc.requiresReplacement).map((pc) => pc.path).join(", ");
|
|
33609
|
-
throw new CdkdError(`${logicalId} (${resourceType}) requires replacement (immutable property changed: ${immutableProps}) but it is a stateful resource — ${renderStatefulReason(statefulReason)}. Re-run with --force-stateful-recreation to confirm the data loss, or change the resource definition to avoid the immutable-property change.`, "STATEFUL_REPLACE_BLOCKED");
|
|
33978
|
+
throw markNonRetryable(new CdkdError(`${logicalId} (${resourceType}) requires replacement (immutable property changed: ${immutableProps}) but it is a stateful resource — ${renderStatefulReason(statefulReason)}. Re-run with --force-stateful-recreation to confirm the data loss, or change the resource definition to avoid the immutable-property change.`, "STATEFUL_REPLACE_BLOCKED"));
|
|
33610
33979
|
}
|
|
33611
33980
|
}
|
|
33612
33981
|
let replacementReason;
|
|
@@ -33749,9 +34118,10 @@ var DeployEngine = class {
|
|
|
33749
34118
|
const ccUnsupported = msg.includes("UnsupportedActionException") || msg.includes("does not support UPDATE");
|
|
33750
34119
|
const replaceOptIn = updateError instanceof ResourceUpdateNotSupportedError && this.options.replace === true;
|
|
33751
34120
|
if (ccUnsupported || replaceOptIn) {
|
|
33752
|
-
|
|
33753
|
-
|
|
33754
|
-
|
|
34121
|
+
const statefulReason = isStatefulRecreateTargetForReplace(resourceType, currentProps);
|
|
34122
|
+
if (statefulReason && this.options.forceStatefulRecreation !== true) {
|
|
34123
|
+
const retainNote = updateReplacePolicy === "Retain" ? " Note: UpdateReplacePolicy: Retain does NOT protect this path — the replacement deletes the old resource regardless." : "";
|
|
34124
|
+
throw markNonRetryable(new CdkdError((replaceOptIn ? `--replace would DELETE + CREATE the stateful resource ${logicalId} (${resourceType}) — ${renderStatefulReason(statefulReason)}. Re-run with --force-stateful-recreation to confirm the data loss, or change the resource definition to avoid the immutable-property change.` : `${logicalId} (${resourceType}) cannot be updated in place by the provisioning layer it routes through, so applying this change would DELETE + CREATE it — but it is a stateful resource: ${renderStatefulReason(statefulReason)}. Re-run with --force-stateful-recreation to confirm the data loss, or change the resource definition to avoid the update.`) + retainNote, "STATEFUL_REPLACE_BLOCKED", updateError instanceof Error ? updateError : void 0));
|
|
33755
34125
|
}
|
|
33756
34126
|
this.logger.info(`UPDATE not supported for ${logicalId} (${resourceType}), replacing (DELETE → CREATE)`);
|
|
33757
34127
|
const fallbackFinalSnapshotId = await this.prepareFinalSnapshotForDelete(logicalId, resourceType, currentResource, template?.Resources?.[logicalId]?.UpdateReplacePolicy ?? currentResource.updateReplacePolicy);
|
|
@@ -34252,5 +34622,5 @@ var DeployEngine = class {
|
|
|
34252
34622
|
};
|
|
34253
34623
|
|
|
34254
34624
|
//#endregion
|
|
34255
|
-
export {
|
|
34256
|
-
//# sourceMappingURL=deploy-engine-
|
|
34625
|
+
export { endCommandInterruptScope as $, resolveCaptureObservedState as $n, isThrottlingError as $r, maskSecretsInText as $t, bold as A, getBootstrapMarkerKey as An, DeployCancelledError as Ar, classifyReplaySecretRegion as At, secretBearingStateKeyWarning as B, formatDockerLoginError as Bn, ProvisioningError as Br, describeTypeWithThrottleRetry as Bt, unsupportedFinalSnapshotError as C, rewriteTemplateAssetReferences as Cn, resetAwsClients as Cr, configBooleanRefusal as Ct, isStatefulRecreateTargetSync as D, BOOTSTRAP_MARKER_PREFIX as Dn, ConfigError as Dr, requireConfigArray as Dt, MULTI_REGION_RECREATE_BLOCKED_TYPES as E, AssetModeResolver as En, CdkdError as Er, replayWarn as Et, yellow as F, validateContainerRepoName as Fn, LocalStartServiceError as Fr, s3BucketRegionalDomainName as Ft, clearOnUpdateRemoval as G, AssetManifestLoader as Gn, StateError as Gr, STATE_SOURCED_READBACK_RULES as Gt, getCurrentResourceSecrets as H, partitionSensitiveEnv as Hn, ResourceUpdateNotSupportedError as Hr, DagBuilder as Ht, collectDeclaredOutputNames as I, buildDenyExternalAccessPolicy as In, LockError as Ir, s3BucketWebsiteUrl as It, findSilentDropProperties as J, synthesisStatusMessage as Jn, isCdkdError as Jr, createSecretMasker as Jt, ProviderRegistry as K, getDockerImageBySourceHash as Kn, SynthesisError as Kr, TEMPLATE_SOURCED_RULES as Kt, collectPublishedOutputNames as L, describeAwsFailure as Ln, MissingCdkCliError as Lr, applyRoleArnIfSet as Lt, gray as M, parseBootstrapMarker as Mn, IntrinsicResolutionRefusalError as Mr, s3BucketArn as Mt, green as N, readBootstrapMarkerBody as Nn, LocalInvokeBuildError as Nr, s3BucketDomainName as Nt, renderStatefulReason as O, assertAssetBucketRegion as On, CrossAccountSecretRefusalError as Or, requireConfigObject as Ot, red as P, validateAssetBucketName as Pn, LocalMigrateError as Pr, s3BucketDualStackDomainName as Pt, beginCommandInterruptScope as Q, resolveAutoAssetStorage as Qn, isRetryableTransientError as Qr, maskSecretsInError as Qt, exportAliasCollisionScrubWarning as R, buildDockerImage as Rn, NestedStackChildDirectDestroyError as Rr, DiffCalculator as Rt, refusesFinalSnapshot as S, loadPublishableAssetManifest as Sn, getAwsClients as Sr, coerceCfnBoolean as St, extractDeploymentEventError as T, stripControlChars as Tn, AssetError as Tr, readConfigString as Tt, IAMRoleProvider as U, runDockerForeground as Un, StackHasActiveImportsError as Ur, TemplateParser as Ut, stateKeySecretExposure as V, getDockerCmd as Vn, ResourceTimeoutError as Vr, withRetry as Vt, collectInlinePolicyNamesManagedBySiblings as W, runDockerStreaming as Wn, StackTerminationProtectionError as Wr, STATE_SOURCED_CROSS_GENERATION_RULES as Wt, maskDeep as X, getLegacyStateBucketName as Xn, withErrorHandling as Xr, errorCauseChain as Xt, createMaskedRetryLogger as Y, getDefaultStateBucketName as Yn, normalizeAwsError as Yr, dynamicReferenceTokens as Yt, maskerOrIdentity as Z, resolveApp as Zn, isMarkedNonRetryable as Zr, isSingleDynamicReferenceToken as Zt, PRE_DELETE_SNAPSHOT_TYPES as _, AssetPublisher as _n, AssemblyReader as _r, refStateLookupFromResource as _t, DeploymentEventsStore as a, S3StateBackend as an, warnDeprecatedNoPrefixCliFlag as ar, UNSPECIFIED_SKIP_REASON as at, createPreDeleteFinalSnapshot as b, buildAssetRedirectMap as bn, resolveBucketRegion as br, resolveExplicitPhysicalId as bt, replayFailedOperations as c, buildForceUnlockCommand as cn, MIGRATE_TMP_PREFIX as cr, disableInstanceApiTermination as ct, updatePartialReason as d, CUSTOM_RESOURCE_RESPONSE_PREFIX as dn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as dr, carriesDynamicReference as dt, isTransientServerError as ei, recordMaskOnlyValue as en, resolveSkipPrefix as er, interruptWatchListenerCount as et, withResourceDeadline as f, DEFAULT_STATE_PREFIX as fn, displaySafe as fr, cfnRefValueFromPhysicalId as ft, ATOMIC_FINAL_SNAPSHOT_TYPES as g, shouldRetainResource as gn, derivePartitionAndUrlSuffix as gr, parameterTypeMayLoseSecretIdentity as gt, computeImplicitDeleteEdges as h, importableOutputs as hn, canonicalizeRegion as hr, isUnboundTemplateParameter as ht, DeploymentEventsReader as i, __exportAll as ii, LockManager as in, stateBucketExistenceConfirmed as ir, slowCcOperationTimeoutMs as it, cyan as j, isCrossRegionRedirect as jn, DynamicReferenceRegionAmbiguousError as jr, producerRegionsFromState as jt, formatResourceLine as k, ensureAssetStorage as kn, DependencyError as kr, requireConfigString as kt, replayRollback as l, buildLockContentionMessage as ln, findLargeInlineResources as lr, isTerminationProtectionPropagationError as lt, IMPLICIT_DELETE_DEPENDENCIES as m, importableOutputKeys as mn, PARTITION_TABLE as mr, getAccountInfo as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, markRedactedCause as ni, redactSecretsForState as nn, resolveStateBucketWithDefaultAndSource as nr, startInterruptWatch as nt, planFailedOps as o, rebuildClientForBucketRegion as on, CFN_TEMPLATE_BODY_LIMIT as or, deleteIndeterminateGuards as ot, maskingRetryLogger as p, exportNamesCarriedFrom as pn, expectedOwnerParam as pr, coerceParameterTypedValue as pt, findActionableSilentDrops as q, Synthesizer as qn, formatError as qr, carriesSecretMask as qt, DeployEngine as r, retryClassificationText as ri, scrubResourceRecord as rn, resolveUseCdkBootstrapAssets as rr, CloudControlProvider as rt, planRollback as s, UNRENDERABLE as sn, CFN_TEMPLATE_URL_LIMIT as sr, deleteSkipReason as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, markNonRetryable as ti, recoverMaskedOutput as tn, resolveStateBucketWithDefault as tr, isInterruptedWaitError as tt, updatePartialMessage as u, forceQuitRecoveryClause as un, uploadCfnTemplate as ur, IntrinsicFunctionResolver as ut, buildFinalSnapshotIdentifier as v, stringifyValue as vn, processStackMessages as vr, WAFv2WebACLProvider as vt, makeCanonicalizePropertiesFn as w, escapeRegExp$1 as wn, setAwsClients as wr, configStringRefusal as wt, isFinalSnapshotError as x, createAssetRedirectResolver as xn, AwsClients as xr, assertRegionMatch as xt, ccRoutedFinalSnapshotError as y, WorkGraph as yn, clearBucketRegionCache as yr, normalizeAwsTagsToCfn as yt, isExportAliasCollision as z, dockerSpawnEnvWithSensitive as zn, PartialFailureError as zr, INTRINSIC_KEYS as zt };
|
|
34626
|
+
//# sourceMappingURL=deploy-engine-DuFUy9D0.js.map
|