@go-to-k/cdkd 0.290.19 → 0.290.21
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-ac412LEP.js → asg-provider-B8GAVns4.js} +2 -2
- package/dist/{asg-provider-ac412LEP.js.map → asg-provider-B8GAVns4.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-D7mwWwGs.js → deploy-engine-KnCjibgq.js} +426 -36
- package/dist/deploy-engine-KnCjibgq.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-BOAEIjF5.js → program-BLyj1ijG.js} +99 -37
- package/dist/{program-BOAEIjF5.js.map → program-BLyj1ijG.js.map} +1 -1
- package/dist/{version-BOnyvRN8.js → version-CGcB3KHZ.js} +2 -2
- package/dist/{version-BOnyvRN8.js.map → version-CGcB3KHZ.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-D7mwWwGs.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { C as displaySafe, S as displayIdent, d as applyDefaultNameForFallback, f as explicitNamePropertyFor, g as looksLikeCdkdGeneratedName, h as getCurrentStackName, m as generateResourceNameWithFallback, n as getLogger, p as generateResourceName, r as isStdoutReservedForPayload, s as getLiveRenderer, v as withStackName, w as truncateCodePoints, x as UNRENDERABLE, y as withoutGeneratedFallbackName } from "./logger-C17H06QA.js";
|
|
1
|
+
import { C as displaySafe, S as displayIdent, b as STACK_REF_MAX_CODE_POINTS, 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, w as truncateCodePoints, x as UNRENDERABLE, y as withoutGeneratedFallbackName } from "./logger-C17H06QA.js";
|
|
2
2
|
import { a as setAssumedRoleCredentials, i as getPreAssumeEnvCredentials, n as awsClientDefaults, o as setPreAssumeEnvCredentials, r as getAssumedRoleCredentials, t as SECOND_ROLE_PUBLISH_REFUSAL } from "./aws-client-defaults-BoIxyCir.js";
|
|
3
|
-
import { t as getCdkdVersion } from "./version-
|
|
3
|
+
import { t as getCdkdVersion } from "./version-CGcB3KHZ.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";
|
|
@@ -4139,6 +4139,77 @@ async function expectedOwnerParam(client) {
|
|
|
4139
4139
|
return owner ? { ExpectedBucketOwner: owner } : {};
|
|
4140
4140
|
}
|
|
4141
4141
|
|
|
4142
|
+
//#endregion
|
|
4143
|
+
//#region src/utils/s3-listing-keys.ts
|
|
4144
|
+
/**
|
|
4145
|
+
* URL-safe reading of an S3 listing's key-bearing fields (issue
|
|
4146
|
+
* [#3313](https://github.com/go-to-k/cdkd/issues/3313)).
|
|
4147
|
+
*
|
|
4148
|
+
* **Why this exists at all.** `ListObjectsV2` and `ListObjectVersions` answer in
|
|
4149
|
+
* XML, and an XML parser normalises a CARRIAGE RETURN to a LINE FEED per the
|
|
4150
|
+
* specification (XML 1.0 section 2.11). So a key containing a CR comes back
|
|
4151
|
+
* with an LF in its place, and anything the caller then does with that
|
|
4152
|
+
* value — a `DeleteObject`, a `GetObject`, a membership test against a key
|
|
4153
|
+
* cdkd constructed — addresses a key that does not exist.
|
|
4154
|
+
*
|
|
4155
|
+
* MEASURED against real S3 (us-east-1, 2026-09-17), not read off the spec:
|
|
4156
|
+
*
|
|
4157
|
+
* ```
|
|
4158
|
+
* PutObject key `a b` -> listed as `a+b` (FORM-style, not %20)
|
|
4159
|
+
* PutObject key `a+b` -> listed as `a%2Bb`
|
|
4160
|
+
* PutObject key containing CR -> stored
|
|
4161
|
+
* ListObjectsV2, no EncodingType -> the CR comes back as \n
|
|
4162
|
+
* ListObjectsV2, EncodingType=url -> ...%0D..., which decodes to the real CR
|
|
4163
|
+
* ```
|
|
4164
|
+
*
|
|
4165
|
+
* The delete that follows such a listing is the dangerous half: it SUCCEEDS
|
|
4166
|
+
* and removes nothing, so a sweep counts it and moves on. That is how
|
|
4167
|
+
* `s3-noncurrent-version-purge.ts` — the sweep that removes state versions
|
|
4168
|
+
* carrying a secret plaintext — can report a clean run over a surviving one.
|
|
4169
|
+
*
|
|
4170
|
+
* **What is and is NOT encoded**, measured in the same run, because getting
|
|
4171
|
+
* this backwards breaks pagination rather than fixing anything:
|
|
4172
|
+
*
|
|
4173
|
+
* | field | under `EncodingType: 'url'` | so |
|
|
4174
|
+
* | --- | --- | --- |
|
|
4175
|
+
* | `Contents[].Key`, `Versions[].Key`, `DeleteMarkers[].Key` | encoded | DECODE |
|
|
4176
|
+
* | `CommonPrefixes[].Prefix` | encoded | DECODE |
|
|
4177
|
+
* | `NextKeyMarker` | encoded | DECODE — the next request sends the RAW value |
|
|
4178
|
+
* | `NextVersionIdMarker` | NOT encoded | pass back verbatim (a version id is opaque) |
|
|
4179
|
+
* | `NextContinuationToken` | opaque, NOT encoded | send back VERBATIM; decoding corrupts it |
|
|
4180
|
+
* | `Prefix`, `Delimiter` | ENCODED (AWS lists them) | decode IF read; no cdkd site reads them |
|
|
4181
|
+
*
|
|
4182
|
+
* A helper that decoded `NextContinuationToken` along with the rest would
|
|
4183
|
+
* produce a token S3 rejects, which is why the two are named separately here
|
|
4184
|
+
* rather than "decode every string on the response".
|
|
4185
|
+
*
|
|
4186
|
+
* **NUL is out of scope and that is a measurement, not an omission**: S3
|
|
4187
|
+
* refuses to store a key containing one (`PutObject` fails), so no listing can
|
|
4188
|
+
* deliver it. An ESC survives verbatim — the transformation is specific to the
|
|
4189
|
+
* line-ending class, so a guard looking for "control characters" would miss
|
|
4190
|
+
* the one character that actually moves.
|
|
4191
|
+
*/
|
|
4192
|
+
/**
|
|
4193
|
+
* Decode one key-bearing field of a listing taken with `EncodingType: 'url'`.
|
|
4194
|
+
*
|
|
4195
|
+
* Returns `undefined` unchanged so a caller can pass an optional field
|
|
4196
|
+
* straight through.
|
|
4197
|
+
*
|
|
4198
|
+
* `decodeURIComponent` throws on a malformed sequence (`%` not followed by two
|
|
4199
|
+
* hex digits). That cannot arise from S3's own encoding, but this function is
|
|
4200
|
+
* the ONE place a raw response value is trusted, so it refuses rather than
|
|
4201
|
+
* guesses: a key it cannot decode is one a later delete would address wrongly,
|
|
4202
|
+
* which is the defect this module exists to close.
|
|
4203
|
+
*/
|
|
4204
|
+
function decodeListingKey(value) {
|
|
4205
|
+
if (value === void 0) return void 0;
|
|
4206
|
+
try {
|
|
4207
|
+
return decodeURIComponent(value.replace(/\+/g, "%20"));
|
|
4208
|
+
} catch (error) {
|
|
4209
|
+
throw new Error(`S3 returned a listing key that is not valid URL encoding: ${JSON.stringify(value)}. cdkd takes every listing with EncodingType='url' so a key containing a carriage return survives the XML round-trip; a value that will not decode cannot be addressed safely, so this listing is refused rather than acted on.`, { cause: error });
|
|
4210
|
+
}
|
|
4211
|
+
}
|
|
4212
|
+
|
|
4142
4213
|
//#endregion
|
|
4143
4214
|
//#region src/state/s3-replication-purge-gap.ts
|
|
4144
4215
|
/**
|
|
@@ -4653,6 +4724,7 @@ async function purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields,
|
|
|
4653
4724
|
Bucket: bucket,
|
|
4654
4725
|
...requestFields,
|
|
4655
4726
|
Prefix: prefix,
|
|
4727
|
+
EncodingType: "url",
|
|
4656
4728
|
...keyMarker !== void 0 && { KeyMarker: keyMarker },
|
|
4657
4729
|
...versionIdMarker !== void 0 && { VersionIdMarker: versionIdMarker }
|
|
4658
4730
|
}));
|
|
@@ -4665,22 +4737,29 @@ async function purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields,
|
|
|
4665
4737
|
hasBody: false
|
|
4666
4738
|
}))];
|
|
4667
4739
|
for (const { entry, hasBody } of entries) {
|
|
4668
|
-
|
|
4740
|
+
let decodedKey;
|
|
4741
|
+
try {
|
|
4742
|
+
decodedKey = decodeListingKey(entry.Key);
|
|
4743
|
+
} catch {
|
|
4744
|
+
if (hasBody && entry.Key !== void 0) unsettledBodies.add(`${entry.Key} [key not decodable; shown as S3 returned it]`);
|
|
4745
|
+
continue;
|
|
4746
|
+
}
|
|
4747
|
+
if (decodedKey === void 0 || !wanted.has(decodedKey)) continue;
|
|
4669
4748
|
if (entry.IsLatest === void 0) {
|
|
4670
|
-
recordFailure(failed,
|
|
4671
|
-
if (hasBody) unsettledBodies.add(
|
|
4749
|
+
recordFailure(failed, decodedKey, `version ${entry.VersionId ?? "<unknown>"}: listing omitted IsLatest, so the entry was left alone rather than risk deleting a current version`);
|
|
4750
|
+
if (hasBody) unsettledBodies.add(decodedKey);
|
|
4672
4751
|
}
|
|
4673
4752
|
if (entry.IsLatest !== false) continue;
|
|
4674
4753
|
if (!entry.VersionId) {
|
|
4675
|
-
recordFailure(failed,
|
|
4676
|
-
if (hasBody) unsettledBodies.add(
|
|
4754
|
+
recordFailure(failed, decodedKey, `listing returned a noncurrent entry with no VersionId, so it could not be deleted`);
|
|
4755
|
+
if (hasBody) unsettledBodies.add(decodedKey);
|
|
4677
4756
|
continue;
|
|
4678
4757
|
}
|
|
4679
4758
|
stale.push({
|
|
4680
|
-
Key:
|
|
4759
|
+
Key: decodedKey,
|
|
4681
4760
|
VersionId: entry.VersionId
|
|
4682
4761
|
});
|
|
4683
|
-
if (hasBody) purged.add(
|
|
4762
|
+
if (hasBody) purged.add(decodedKey);
|
|
4684
4763
|
}
|
|
4685
4764
|
for (let i = 0; i < stale.length; i += DELETE_BATCH_SIZE) {
|
|
4686
4765
|
const batch = stale.slice(i, i + DELETE_BATCH_SIZE);
|
|
@@ -4712,7 +4791,7 @@ async function purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields,
|
|
|
4712
4791
|
unsettledBodies.add(key);
|
|
4713
4792
|
}
|
|
4714
4793
|
}
|
|
4715
|
-
keyMarker = resp.IsTruncated === true ? resp.NextKeyMarker : void 0;
|
|
4794
|
+
keyMarker = resp.IsTruncated === true ? decodeListingKey(resp.NextKeyMarker) : void 0;
|
|
4716
4795
|
versionIdMarker = keyMarker !== void 0 ? resp.NextVersionIdMarker : void 0;
|
|
4717
4796
|
} while (keyMarker !== void 0);
|
|
4718
4797
|
}
|
|
@@ -10387,10 +10466,11 @@ var S3StateBackend = class {
|
|
|
10387
10466
|
Bucket: this.config.bucket,
|
|
10388
10467
|
...await this.ownerParam(),
|
|
10389
10468
|
Prefix: prefix,
|
|
10469
|
+
EncodingType: "url",
|
|
10390
10470
|
...continuationToken && { ContinuationToken: continuationToken }
|
|
10391
10471
|
}));
|
|
10392
10472
|
for (const obj of response.Contents ?? []) {
|
|
10393
|
-
const key = obj.Key;
|
|
10473
|
+
const key = decodeListingKey(obj.Key);
|
|
10394
10474
|
if (!key) continue;
|
|
10395
10475
|
if (!key.endsWith("/state.json")) continue;
|
|
10396
10476
|
const segments = key.slice(prefix.length).split("/");
|
|
@@ -10483,9 +10563,13 @@ var S3StateBackend = class {
|
|
|
10483
10563
|
Bucket: this.config.bucket,
|
|
10484
10564
|
...await this.ownerParam(),
|
|
10485
10565
|
Prefix: keyPrefix,
|
|
10566
|
+
EncodingType: "url",
|
|
10486
10567
|
...continuationToken && { ContinuationToken: continuationToken }
|
|
10487
10568
|
}));
|
|
10488
|
-
for (const obj of response.Contents ?? [])
|
|
10569
|
+
for (const obj of response.Contents ?? []) {
|
|
10570
|
+
const decoded = decodeListingKey(obj.Key);
|
|
10571
|
+
if (decoded) keys.push(decoded);
|
|
10572
|
+
}
|
|
10489
10573
|
continuationToken = response.IsTruncated ? response.NextContinuationToken : void 0;
|
|
10490
10574
|
} while (continuationToken);
|
|
10491
10575
|
return keys;
|
|
@@ -10515,6 +10599,7 @@ var S3StateBackend = class {
|
|
|
10515
10599
|
Bucket: this.config.bucket,
|
|
10516
10600
|
...await this.ownerParam(),
|
|
10517
10601
|
Prefix: keyPrefix,
|
|
10602
|
+
EncodingType: "url",
|
|
10518
10603
|
...continuationToken && { ContinuationToken: continuationToken }
|
|
10519
10604
|
}));
|
|
10520
10605
|
for (const obj of response.Contents ?? []) {
|
|
@@ -10528,7 +10613,7 @@ var S3StateBackend = class {
|
|
|
10528
10613
|
continue;
|
|
10529
10614
|
}
|
|
10530
10615
|
objects.push({
|
|
10531
|
-
key: obj.Key,
|
|
10616
|
+
key: decodeListingKey(obj.Key) ?? obj.Key,
|
|
10532
10617
|
lastModified: obj.LastModified,
|
|
10533
10618
|
size: obj.Size
|
|
10534
10619
|
});
|
|
@@ -20529,6 +20614,11 @@ function findUnrecognizedProperties(resourceType, templateProperties) {
|
|
|
20529
20614
|
|
|
20530
20615
|
//#endregion
|
|
20531
20616
|
//#region src/state/malformed-resources-bag.ts
|
|
20617
|
+
/**
|
|
20618
|
+
* The identifier cap every message here uses unless a caller says otherwise.
|
|
20619
|
+
* See {@link safeIdentifier} for why it is a parameter at all.
|
|
20620
|
+
*/
|
|
20621
|
+
const IDENT_CAP_DEFAULT = 128;
|
|
20532
20622
|
/** The error code every malformed-record refusal carries, whatever its class. */
|
|
20533
20623
|
const STATE_RESOURCES_MALFORMED = "STATE_RESOURCES_MALFORMED";
|
|
20534
20624
|
/**
|
|
@@ -20600,16 +20690,27 @@ function hasReadableOutputs(state) {
|
|
|
20600
20690
|
* `shellQuote` composition every message in THIS module needs and that one
|
|
20601
20691
|
* must not have.
|
|
20602
20692
|
*/
|
|
20603
|
-
function safeIdentifier(value) {
|
|
20693
|
+
function safeIdentifier(value, maxCodePoints = IDENT_CAP_DEFAULT) {
|
|
20604
20694
|
const safe = displaySafe(value, { asciiOnly: true });
|
|
20605
20695
|
if (!safe) return UNRENDERABLE;
|
|
20606
|
-
const { text, truncated } = truncateCodePoints(safe,
|
|
20696
|
+
const { text, truncated } = truncateCodePoints(safe, maxCodePoints);
|
|
20607
20697
|
return truncated ? `${text}...` : text;
|
|
20608
20698
|
}
|
|
20609
|
-
|
|
20610
|
-
|
|
20611
|
-
|
|
20612
|
-
|
|
20699
|
+
/**
|
|
20700
|
+
* The DIAGNOSIS sentence itself, with no identity and no remedy in it.
|
|
20701
|
+
*
|
|
20702
|
+
* ONE spelling, for the reason {@link malformedStateDetail} gives for existing
|
|
20703
|
+
* at all: two copies of a diagnosis are what drift. It is extracted because
|
|
20704
|
+
* {@link malformedDestroyResourcesRefusalMessage} needs the same sentence
|
|
20705
|
+
* under an identity it does NOT trust, where the detail's substituted
|
|
20706
|
+
* `cdkd state show` command must not appear — a difference in the COMMAND, not
|
|
20707
|
+
* in the diagnosis.
|
|
20708
|
+
*/
|
|
20709
|
+
const MALFORMED_RESOURCES_DIAGNOSIS = "has no readable 'resources' map — the record is malformed or truncated. Both 'cdkd deploy' and 'cdkd destroy' REFUSE such a record rather than acting on it: they read the same map, an unreadable one is indistinguishable from an empty stack, and acting on that reading would make a deploy re-CREATE every resource and a destroy delete none of them.";
|
|
20710
|
+
function malformedStateDetail(stackName, region, maxCodePoints = IDENT_CAP_DEFAULT) {
|
|
20711
|
+
const stack = safeIdentifier(stackName, maxCodePoints);
|
|
20712
|
+
const reg = safeIdentifier(region, maxCodePoints);
|
|
20713
|
+
return `State for ${shellQuote(stack)} (${shellQuote(reg)}) ${MALFORMED_RESOURCES_DIAGNOSIS} Inspect it with: cdkd state show ${shellQuote(stack)} --stack-region ${shellQuote(reg)} --json`;
|
|
20613
20714
|
}
|
|
20614
20715
|
/**
|
|
20615
20716
|
* For a READ-ONLY command: give the record an empty resource bag so the
|
|
@@ -20667,6 +20768,192 @@ function malformedStateRefusalMessage(stackName, region) {
|
|
|
20667
20768
|
return `${malformedStateDetail(stackName, region)} This command can WRITE state, so it refuses rather than continuing: saving over a record whose resource map could not be read would replace the evidence with a well-formed empty one and lose it permanently. Repair or remove the record first.`;
|
|
20668
20769
|
}
|
|
20669
20770
|
/**
|
|
20771
|
+
* The DESTROY refusal text for the `resources` bag (issue
|
|
20772
|
+
* [#3161](https://github.com/go-to-k/cdkd/issues/3161)).
|
|
20773
|
+
*
|
|
20774
|
+
* A SECOND `resources` refusal beside {@link malformedStateRefusalMessage},
|
|
20775
|
+
* for the reason every split in this module is its own: that text says saving
|
|
20776
|
+
* over the record "would replace the evidence with a well-formed empty one",
|
|
20777
|
+
* which is not what a destroy does. A destroy DELETES the record, and it does
|
|
20778
|
+
* so down a path the other four refusing commands never take.
|
|
20779
|
+
*
|
|
20780
|
+
* What the runner does with the bag is COUNT it —
|
|
20781
|
+
* `Object.keys(state.resources).length` — and route on the count. The count is
|
|
20782
|
+
* the list of what to delete, so an unreadable bag counts ZERO and the run
|
|
20783
|
+
* takes the EMPTY-STACK FAST PATH, which deletes `state.json` outright. Every
|
|
20784
|
+
* resource the record named stays live in AWS, now unreferenced, and the
|
|
20785
|
+
* destroy reports success.
|
|
20786
|
+
*
|
|
20787
|
+
* **Reading the bag as EMPTY — the read-only repair — IS that outcome rather
|
|
20788
|
+
* than an alternative to it**, which is why no repair is available here.
|
|
20789
|
+
* Measured rather than reasoned: `[]`, `5` and `true` all enumerate no keys,
|
|
20790
|
+
* so they already ARE the repaired-to-`{}` shape, and each one reaches the
|
|
20791
|
+
* fast path today. The same measurement
|
|
20792
|
+
* {@link refuseMalformedResourceProperties} records one container down.
|
|
20793
|
+
*
|
|
20794
|
+
* It NAMES `cdkd state orphan`, which the sibling refusals do not, and
|
|
20795
|
+
* that answers the objection go-to-k/cdkd#3161 raises against refusing at all
|
|
20796
|
+
* — that a cleanup command refusing leaves the user with no supported way to
|
|
20797
|
+
* tear the stack down. Proceeding would not tear anything down either, and
|
|
20798
|
+
* that is per-shape rather than a slogan: `[]` / a number / a boolean name no
|
|
20799
|
+
* resource at all, while a STRING names one fabricated logical id per
|
|
20800
|
+
* character whose ENTRY is a single character — so `resourceType` and
|
|
20801
|
+
* `physicalId` are both `undefined` and `ProviderRegistry.getProviderFor`
|
|
20802
|
+
* throws before any provider is selected, which means no AWS delete is issued
|
|
20803
|
+
* and no live resource can be addressed. (Measured 2026-09-17 against the real
|
|
20804
|
+
* registry: `Cannot read properties of undefined (reading 'startsWith')`, out
|
|
20805
|
+
* of the `isCustomResource` test — a routing failure, NOT a `provider.delete`
|
|
20806
|
+
* rejection, which an earlier revision of this note claimed.) What a forced
|
|
20807
|
+
* run would do is therefore bounded by the record: on the first three shapes
|
|
20808
|
+
* it deletes `state.json` and nothing else, and on a string every fabricated
|
|
20809
|
+
* id fails and `errorCount > 0` keeps a record — but keeps it LAUNDERED, since
|
|
20810
|
+
* the preserve-write spreads the bag (`{ ...state.resources }`) and `'ab'`
|
|
20811
|
+
* comes back as a well-formed `{"0":"a","1":"b"}`. So "it preserves the
|
|
20812
|
+
* evidence" would be the wrong defence of proceeding; the string shape reaches
|
|
20813
|
+
* the same laundering by another route, which strengthens the refusal rather
|
|
20814
|
+
* than weakening it (review round 2 of go-to-k/cdkd#3332). The only outcome
|
|
20815
|
+
* worth offering is the record's removal, and `cdkd state orphan` is the
|
|
20816
|
+
* supported command for it, leaving the live resources in place. It reads
|
|
20817
|
+
* `state.resources` nowhere, which is what makes the pointer true and is
|
|
20818
|
+
* fenced in `tests/unit/state/malformed-resources-bag.test.ts`.
|
|
20819
|
+
*
|
|
20820
|
+
* **That remedy is a TEMPLATE, not a substituted command, and the asymmetry
|
|
20821
|
+
* with the `cdkd state show` line above it is the point.** `state orphan`
|
|
20822
|
+
* DELETES a record; `state show` reads one. The `region` this function is
|
|
20823
|
+
* handed comes from `destroy-runner.ts`'s
|
|
20824
|
+
* `state.region ?? ctx.baseRegion`, and `state.region` is RECORD-BODY content
|
|
20825
|
+
* that `getState` does not check against the key it loaded from — measured
|
|
20826
|
+
* 2026-09-17: a record planted at `.../us-east-1/state.json` carrying
|
|
20827
|
+
* `"region": "eu-west-1"` rendered a pasteable
|
|
20828
|
+
* `cdkd state orphan <stack> --stack-region eu-west-1`, aiming a destructive
|
|
20829
|
+
* command at a DIFFERENT region's record for the same stack. That is the
|
|
20830
|
+
* misdirection class {@link stackClause} records for stack names, one field
|
|
20831
|
+
* over. Substituting into the read-only `state show` line is the pre-existing
|
|
20832
|
+
* behaviour of {@link malformedStateDetail} and is left alone; what this lane
|
|
20833
|
+
* must not add is a destructive one. The divergence itself — the runner also
|
|
20834
|
+
* LOCKS and DELETES against the body region — is go-to-k/cdkd#3328.
|
|
20835
|
+
*
|
|
20836
|
+
* **A template is not enough on its own, because the name the reader would
|
|
20837
|
+
* type into it comes from the clause ABOVE.** `safeIdentifier` composes
|
|
20838
|
+
* `displaySafe` with a cap, and `displaySafe` TRIMS — so a planted
|
|
20839
|
+
* `resources["..."]` record keyed `"prod-api "` opens this message as
|
|
20840
|
+
* `State for 'prod-api' (...)`, byte-identical to a HEALTHY sibling spelled
|
|
20841
|
+
* `prod-api`. An operator who then orphans "the record the line above names"
|
|
20842
|
+
* deletes the intact one. That is exactly the identity failure `displayIdent`
|
|
20843
|
+
* exists for and {@link namedPropertyBagsClause} applies to logical ids. So
|
|
20844
|
+
* the remedy sentence is GATED on the identity rendering EXACTLY: when it does
|
|
20845
|
+
* not, the text names no removal target at all and opens on
|
|
20846
|
+
* {@link stackClause}'s no-identity form, ending on
|
|
20847
|
+
* {@link inspectCommand}'s TEMPLATE rather than on a command built from the
|
|
20848
|
+
* very identity it just called untrustworthy. The same call
|
|
20849
|
+
* `buildForceUnlockCommand` makes when a value would render misleadingly.
|
|
20850
|
+
*
|
|
20851
|
+
* **Rendering exactly is not the same as being TRUE, and the EXACT arm must
|
|
20852
|
+
* not claim it is.** `exact` answers a question about this message's own text;
|
|
20853
|
+
* it says nothing about PROVENANCE. A planted `"region": "eu-west-1"` on a
|
|
20854
|
+
* `.../us-east-1/` key is an ordinary region string, so it renders exactly and
|
|
20855
|
+
* the arm fires — and an earlier revision then said to run `cdkd state orphan`
|
|
20856
|
+
* "with the stack and region this message NAMES", which is the attacker's
|
|
20857
|
+
* region (review round 2 of go-to-k/cdkd#3332: templating had removed the
|
|
20858
|
+
* paste, not the aim). The arm therefore points at the record's S3 KEY, which
|
|
20859
|
+
* `getState` resolved and a record body cannot forge, and says outright that
|
|
20860
|
+
* the region printed above is body-derived and need not match it. The
|
|
20861
|
+
* divergence is go-to-k/cdkd#3328.
|
|
20862
|
+
*
|
|
20863
|
+
* **The cap this arm measures against is `STACK_REF_MAX_CODE_POINTS`, not the
|
|
20864
|
+
* 128 every other text in this module uses**, and it is threaded rather than
|
|
20865
|
+
* hard-coded because the VERDICT turns on it. At 128 an ordinary multi-level
|
|
20866
|
+
* CDK nested child (`<root>~<...NestedStackResource><hash>~<...>`, measured
|
|
20867
|
+
* past 150 code points) truncates, fails `exact`, and takes the withhold arm
|
|
20868
|
+
* on a HEALTHY record — which is fail-safe but makes the fallback the common
|
|
20869
|
+
* path for exactly the nested destroys this lane added a guard to (review
|
|
20870
|
+
* round 2 of go-to-k/cdkd#3332). The bound is still a bound: a planted
|
|
20871
|
+
* multi-kilobyte name is truncated at 1152 and lands in the withhold arm.
|
|
20872
|
+
*
|
|
20873
|
+
* Identifiers are sanitized and THEN shell-quoted, for the reasons
|
|
20874
|
+
* {@link safeIdentifier}'s note gives.
|
|
20875
|
+
*/
|
|
20876
|
+
function malformedDestroyResourcesRefusalMessage(stackName, region) {
|
|
20877
|
+
const cap = STACK_REF_MAX_CODE_POINTS;
|
|
20878
|
+
const exact = safeIdentifier(stackName, cap) === stackName && safeIdentifier(region, cap) === region;
|
|
20879
|
+
return `${exact ? malformedStateDetail(stackName, region, cap) : `${stackClause(void 0, void 0)} ${MALFORMED_RESOURCES_DIAGNOSIS}`} This command DELETES state, so it refuses rather than continuing: the resource map IS the list of what to delete, so an unreadable one counts as ZERO resources and the run takes the empty-stack fast path, which removes state.json and reports success while every resource the record named is still live in AWS and no longer referenced by anything. Reading the bag as EMPTY is that same outcome rather than an alternative to it, so there is no repair available here. Repair or remove the record first. ${exact ? "To drop the record deliberately and leave the live resources standing, run 'cdkd state orphan' against the stack and the region THE RECORD'S S3 KEY holds — spelled out rather than pasteable, because that command DELETES a record and the region printed above is read from the record's own BODY, which need not match its key. Confirm the key with 'cdkd state list --long' — a legacy record shows none, and for one of those the flag must be OMITTED or it selects nothing — then: cdkd state orphan <stack> --stack-region <region>" : `This record's stack name or region does NOT render exactly — what is printed above is a sanitized form, and another record may render identically — so this message names no target and offers no command against one. List the records as stored with 'cdkd state list --long', which prints a name needing sanitizing in quoted form, and act on the one whose key matches. Inspect it with: ${inspectCommand(void 0, void 0)}`}`;
|
|
20880
|
+
}
|
|
20881
|
+
/**
|
|
20882
|
+
* For `cdkd destroy` / `cdkd state destroy`: refuse a record whose `resources`
|
|
20883
|
+
* bag cannot be read (issue
|
|
20884
|
+
* [#3161](https://github.com/go-to-k/cdkd/issues/3161)).
|
|
20885
|
+
*
|
|
20886
|
+
* The `resources` twin of {@link refuseMalformedOutputsForDestroy}, and a
|
|
20887
|
+
* SEPARATE call from it for the reason that one is separate from
|
|
20888
|
+
* {@link refuseMalformedOutputs}: a record can be malformed in either
|
|
20889
|
+
* container alone and the refusal must name the one that is broken.
|
|
20890
|
+
*
|
|
20891
|
+
* CALL IT AT THE TOP OF THE DESTROY, **above the `resourceCount` read**. The
|
|
20892
|
+
* placement rule is {@link repairMalformedResourcesForReadOnly}'s and it is
|
|
20893
|
+
* not decoration here: the empty-stack fast path sits immediately below that
|
|
20894
|
+
* read and DELETES the record, so a guard written anywhere below it refuses a
|
|
20895
|
+
* record that is already gone.
|
|
20896
|
+
*/
|
|
20897
|
+
function refuseMalformedResourcesForDestroy(state, stackName, region) {
|
|
20898
|
+
if (hasReadableResources(state)) return;
|
|
20899
|
+
throw markNonRetryable(new CdkdError(malformedDestroyResourcesRefusalMessage(stackName, region), STATE_RESOURCES_MALFORMED));
|
|
20900
|
+
}
|
|
20901
|
+
/**
|
|
20902
|
+
* The DEPLOY refusal text for the `resources` bag (issue
|
|
20903
|
+
* [#3161](https://github.com/go-to-k/cdkd/issues/3161)).
|
|
20904
|
+
*
|
|
20905
|
+
* A THIRD `resources` text, and not {@link malformedStateRefusalMessage}'s for
|
|
20906
|
+
* the same reason the destroy one is not: that text describes the SAVE as the
|
|
20907
|
+
* harm, and here the save is the last thing that happens rather than the
|
|
20908
|
+
* first. An unreadable map reads as zero RECORDED resources, so the change
|
|
20909
|
+
* calculation plans every resource the template declares as a CREATE and the
|
|
20910
|
+
* deploy re-provisions the whole stack against live AWS — colliding on every
|
|
20911
|
+
* deterministic name and duplicating the rest — before saving a well-formed
|
|
20912
|
+
* record over the only evidence anything was wrong. An operator told their
|
|
20913
|
+
* record would be "replaced with a well-formed empty one" would not know that
|
|
20914
|
+
* running anyway duplicates their stack.
|
|
20915
|
+
*
|
|
20916
|
+
* **It must be true under `cdkd deploy --dry-run` as well**, which it is: the
|
|
20917
|
+
* guard sits at the state LOAD, above the diff and above the `if
|
|
20918
|
+
* (this.options.dryRun)` return, so a dry run reaches it. Refusing there is
|
|
20919
|
+
* the decision {@link malformedResourcePropertiesRefusalMessage} records for
|
|
20920
|
+
* the sibling container, for the same reason — the repaired PREVIEW is
|
|
20921
|
+
* available one command over from `cdkd diff`, which repairs this bag and
|
|
20922
|
+
* warns, so refusing here costs nothing that is not already offered, while a
|
|
20923
|
+
* plausible `--dry-run` plan followed by a refusal the moment the flag comes
|
|
20924
|
+
* off would be the worst arm of all.
|
|
20925
|
+
*
|
|
20926
|
+
* Identifiers are sanitized and THEN shell-quoted and the command is emitted
|
|
20927
|
+
* LAST, for the reasons {@link safeIdentifier}'s note gives.
|
|
20928
|
+
*/
|
|
20929
|
+
function malformedDeployResourcesRefusalMessage(stackName, region) {
|
|
20930
|
+
return `${malformedStateDetail(stackName, region)} 'cdkd deploy' can WRITE state and AWS resources, so it refuses rather than continuing — under '--dry-run' too, because the plan a dry run would print is the wrong one: an unreadable map reads as ZERO recorded resources, so every resource the template declares is planned as a CREATE and the deploy re-provisions a stack that already exists, colliding on each deterministic name and duplicating the rest, then saves a well-formed record over the only evidence anything was wrong. Reading the bag as EMPTY produces that same plan rather than avoiding it. Nothing was provisioned and no state was written FOR THIS STACK. Repair or remove the record first; 'cdkd diff' previews the stack with this map read as EMPTY and warns that it did.`;
|
|
20931
|
+
}
|
|
20932
|
+
/**
|
|
20933
|
+
* For `cdkd deploy`: refuse a record whose `resources` bag cannot be read
|
|
20934
|
+
* (issue [#3161](https://github.com/go-to-k/cdkd/issues/3161)).
|
|
20935
|
+
*
|
|
20936
|
+
* The gap go-to-k/cdkd#3317's review named and left: that lane closed the
|
|
20937
|
+
* per-entry `properties` container at `DiffCalculator.calculateDiff`, and
|
|
20938
|
+
* {@link unreadableResourcePropertyBags} deliberately returns `[]` for a
|
|
20939
|
+
* record whose ROOT bag is unreadable — so `"resources": "abcdef"` reached the
|
|
20940
|
+
* deploy diff, enumerated two fabricated logical ids, and re-created the
|
|
20941
|
+
* stack. {@link refuseMalformedState}'s callers are `import.ts`, `orphan.ts`
|
|
20942
|
+
* and `rollback.ts`, none of which is on this path.
|
|
20943
|
+
*
|
|
20944
|
+
* CALL IT AT THE LOAD, beside {@link refuseMalformedOutputs} — the placement
|
|
20945
|
+
* rule {@link repairMalformedResourcesForReadOnly}'s note records. Not at
|
|
20946
|
+
* `calculateDiff`: the engine's own load dominates that call AND the twelve
|
|
20947
|
+
* reads between them — five, measured 2026-09-17 over comment-stripped source;
|
|
20948
|
+
* re-derive rather than trusting the figure — the first of which
|
|
20949
|
+
* (`Object.keys(currentState.resources)` in a debug line) is where a `null`
|
|
20950
|
+
* bag raised the bare `TypeError` #3018 exists to remove.
|
|
20951
|
+
*/
|
|
20952
|
+
function refuseMalformedResourcesForDeploy(state, stackName, region) {
|
|
20953
|
+
if (hasReadableResources(state)) return;
|
|
20954
|
+
throw markNonRetryable(new CdkdError(malformedDeployResourcesRefusalMessage(stackName, region), STATE_RESOURCES_MALFORMED));
|
|
20955
|
+
}
|
|
20956
|
+
/**
|
|
20670
20957
|
* The warning a view emits when it emptied one or more {@link
|
|
20671
20958
|
* RenderedStateContainer}s it could not walk (go-to-k/cdkd#3187).
|
|
20672
20959
|
*
|
|
@@ -20942,7 +21229,7 @@ function malformedExportNamesWarning(stackName, region) {
|
|
|
20942
21229
|
*/
|
|
20943
21230
|
function refuseMalformedOutputs(state, stackName, region) {
|
|
20944
21231
|
if (hasReadableOutputs(state)) return;
|
|
20945
|
-
throw new CdkdError(malformedOutputsRefusalMessage(stackName, region), STATE_RESOURCES_MALFORMED);
|
|
21232
|
+
throw markNonRetryable(new CdkdError(malformedOutputsRefusalMessage(stackName, region), STATE_RESOURCES_MALFORMED));
|
|
20946
21233
|
}
|
|
20947
21234
|
/**
|
|
20948
21235
|
* The DESTROY refusal text (issue
|
|
@@ -31442,7 +31729,7 @@ var CloudControlProvider = class {
|
|
|
31442
31729
|
const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
|
|
31443
31730
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
31444
31731
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
31445
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
31732
|
+
const { ASGProvider } = await import("./asg-provider-B8GAVns4.js").then((n) => n.n);
|
|
31446
31733
|
const asgProvider = new ASGProvider();
|
|
31447
31734
|
return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
|
|
31448
31735
|
}
|
|
@@ -41338,9 +41625,11 @@ function deepEqualValue(a, b) {
|
|
|
41338
41625
|
* escaping silently, so the authority on "where is this applied" is a grep the
|
|
41339
41626
|
* test performs, not a number anybody has to maintain.
|
|
41340
41627
|
*/
|
|
41341
|
-
function crossStackReadsForPartialSave(previous, recordedImports, recordedOutputReads) {
|
|
41342
|
-
const
|
|
41343
|
-
const
|
|
41628
|
+
function crossStackReadsForPartialSave(previous, recordedImports, recordedOutputReads, normalizeName) {
|
|
41629
|
+
const importKey = (e) => `${e.sourceStack}\u0000${canonicalizeRegion(e.sourceRegion)}\u0000${normalizeName(e.exportName)}`;
|
|
41630
|
+
const outputReadKey = (e) => `${normalizeName(e.sourceStack)}\u0000${canonicalizeRegion(e.sourceRegion)}\u0000${normalizeName(e.outputName)}`;
|
|
41631
|
+
const imports = unionCrossStackReads(previous.imports, recordedImports, importKey);
|
|
41632
|
+
const outputReads = unionCrossStackReads(previous.outputReads, recordedOutputReads, outputReadKey);
|
|
41344
41633
|
return {
|
|
41345
41634
|
...imports.length > 0 && { imports },
|
|
41346
41635
|
...outputReads.length > 0 && { outputReads }
|
|
@@ -41754,7 +42043,13 @@ var DeployEngine = class DeployEngine {
|
|
|
41754
42043
|
* only outputs-substituted references: a literal output equal to a secret
|
|
41755
42044
|
* nothing resolved is not recorded, and is not touched.
|
|
41756
42045
|
*
|
|
41757
|
-
* Run by every {@link redactOutputs}
|
|
42046
|
+
* Run by every {@link redactOutputs}, AND by
|
|
42047
|
+
* {@link crossStackReadKeyNormalizer} (issue
|
|
42048
|
+
* [#3289](https://github.com/go-to-k/cdkd/issues/3289)), which needs the
|
|
42049
|
+
* same bag and runs BEFORE the persist walk. Calling it twice in one save
|
|
42050
|
+
* is safe and MEASURED rather than assumed: the entry fold re-sets equal
|
|
42051
|
+
* values, and `recordResolvedPair` leaves a pair alone when the plaintext
|
|
42052
|
+
* is unchanged, so no CONFLICTING marker is invented. (issue
|
|
41758
42053
|
* [#2814](https://github.com/go-to-k/cdkd/issues/2814)), because a part the
|
|
41759
42054
|
* drain cap stopped waiting for records into its pass map LATE: a secret
|
|
41760
42055
|
* that arrives between the pass and the final save is then still a needle
|
|
@@ -42080,14 +42375,108 @@ var DeployEngine = class DeployEngine {
|
|
|
42080
42375
|
state: scrubResourceRecord(entry.state, (sameResource ? this.perResourceSecrets.get(entry.logicalId) : void 0) ?? /* @__PURE__ */ new Map(), sameResource ? this.perResourceTemplateProps.get(entry.logicalId) : void 0)
|
|
42081
42376
|
};
|
|
42082
42377
|
});
|
|
42378
|
+
const outputs = this.redactOutputs(state.outputs);
|
|
42379
|
+
const crossStackReads = this.redactCrossStackReads(state);
|
|
42083
42380
|
return {
|
|
42084
42381
|
...state,
|
|
42085
42382
|
resources,
|
|
42086
|
-
outputs
|
|
42087
|
-
...orphans === void 0 ? {} : { orphans }
|
|
42383
|
+
outputs,
|
|
42384
|
+
...orphans === void 0 ? {} : { orphans },
|
|
42385
|
+
...crossStackReads
|
|
42088
42386
|
};
|
|
42089
42387
|
}
|
|
42090
42388
|
/**
|
|
42389
|
+
* Redact the TEMPLATE-DERIVED names in `state.imports` / `state.outputReads`
|
|
42390
|
+
* (issue [#3289](https://github.com/go-to-k/cdkd/issues/3289)).
|
|
42391
|
+
*
|
|
42392
|
+
* Both lists rode the `...state` spread unredacted, so a reference whose name
|
|
42393
|
+
* an `Fn::Sub` assembled around a resolved secret persisted that secret in
|
|
42394
|
+
* plaintext, durably. The fields are NOT symmetric and the issue as filed
|
|
42395
|
+
* named the wrong ones, so the provenance of each is stated here rather than
|
|
42396
|
+
* left to be re-derived:
|
|
42397
|
+
*
|
|
42398
|
+
* - `imports[].exportName` IS template-derived (`resolveValue` on the
|
|
42399
|
+
* `Fn::ImportValue` argument), so it leaks. Its `sourceStack` /
|
|
42400
|
+
* `sourceRegion` are NOT: `recordImport` takes them from the exports index
|
|
42401
|
+
* entry or the state scan, i.e. from the producer's own record.
|
|
42402
|
+
* - `outputReads[]` has TWO leaking fields: `outputName` AND `sourceStack`,
|
|
42403
|
+
* both `resolveValue` on `Fn::GetStackOutput` arguments. `sourceRegion` is
|
|
42404
|
+
* template-derived too but passes `isClientSafeRegion` before it can be
|
|
42405
|
+
* recorded, and it is read STRUCTURALLY (`producerRegionsFromState` keys a
|
|
42406
|
+
* secret-region decision on it), so it is deliberately left alone.
|
|
42407
|
+
*
|
|
42408
|
+
* REDACTION HAPPENS HERE, at the persist choke point, and must not move to
|
|
42409
|
+
* record time: `crossStackReadsForPartialSave` / `unionCrossStackReads` dedup
|
|
42410
|
+
* on `${sourceStack}\0${region}\0${name}`, so changing a value mid-run makes
|
|
42411
|
+
* the union write BOTH spellings, and its first-seen-wins merge would keep
|
|
42412
|
+
* whichever arrived first.
|
|
42413
|
+
*
|
|
42414
|
+
* `outputReads[].sourceStack` is both a leak and the literal match key
|
|
42415
|
+
* `findDownstreamConsumers` compares against, so redacting it necessarily
|
|
42416
|
+
* stops that match. That reader reports rather than drops — see
|
|
42417
|
+
* `recreate-downstream-consumers.ts`; a consumer vanishing from a DATA-LOSS
|
|
42418
|
+
* prompt is a worse failure than one it cannot name precisely.
|
|
42419
|
+
*
|
|
42420
|
+
* The union bag is every secret THIS deploy resolved, because a
|
|
42421
|
+
* cross-stack reference can sit in a resource property (recorded per logical
|
|
42422
|
+
* id) or in an Output (recorded in the outputs pass), and the entry carries
|
|
42423
|
+
* no logical id to narrow it by.
|
|
42424
|
+
*/
|
|
42425
|
+
redactCrossStackReads(state) {
|
|
42426
|
+
if (state.imports === void 0 && state.outputReads === void 0) return {};
|
|
42427
|
+
const secrets = this.allRecordedSecrets();
|
|
42428
|
+
const redact = (value) => redactSecretsForState(value, secrets);
|
|
42429
|
+
return {
|
|
42430
|
+
...state.imports === void 0 ? {} : { imports: state.imports.map((entry) => ({
|
|
42431
|
+
...entry,
|
|
42432
|
+
exportName: redact(entry.exportName)
|
|
42433
|
+
})) },
|
|
42434
|
+
...state.outputReads === void 0 ? {} : { outputReads: state.outputReads.map((entry) => ({
|
|
42435
|
+
...entry,
|
|
42436
|
+
sourceStack: redact(entry.sourceStack),
|
|
42437
|
+
outputName: redact(entry.outputName)
|
|
42438
|
+
})) }
|
|
42439
|
+
};
|
|
42440
|
+
}
|
|
42441
|
+
/**
|
|
42442
|
+
* The name normalizer `crossStackReadsForPartialSave` keys its union on
|
|
42443
|
+
* (issue [#3289](https://github.com/go-to-k/cdkd/issues/3289)).
|
|
42444
|
+
*
|
|
42445
|
+
* It applies the SAME redaction the persist path applies, so a name this run
|
|
42446
|
+
* holds in plaintext and the name the previous record holds redacted produce
|
|
42447
|
+
* ONE key. Without it the union keeps both and the persist redaction makes
|
|
42448
|
+
* them identical duplicates. The parameter's own doc carries the measurement.
|
|
42449
|
+
*
|
|
42450
|
+
* Returns identity when this deploy resolved no secret, which keeps the key
|
|
42451
|
+
* byte-identical to the pre-#3289 one for every stack that has none.
|
|
42452
|
+
*/
|
|
42453
|
+
crossStackReadKeyNormalizer() {
|
|
42454
|
+
this.absorbOutputsPassSecrets();
|
|
42455
|
+
const secrets = this.allRecordedSecrets();
|
|
42456
|
+
if (secrets.size === 0) return (name) => name;
|
|
42457
|
+
return (name) => redactSecretsForState(name, secrets);
|
|
42458
|
+
}
|
|
42459
|
+
/**
|
|
42460
|
+
* Every secret this deploy resolved, in one bag.
|
|
42461
|
+
*
|
|
42462
|
+
* `perResourceSecrets` is keyed by logical id and `outputSecrets` holds the
|
|
42463
|
+
* outputs pass, which is the right scoping for a RECORD (a resource's own
|
|
42464
|
+
* secrets redact that resource). A cross-stack read entry carries no logical
|
|
42465
|
+
* id, so it has nothing to be scoped BY — the reference may have sat in any
|
|
42466
|
+
* resource's property or in an Output.
|
|
42467
|
+
*
|
|
42468
|
+
* Over-redaction here is the safe direction and is bounded the same way the
|
|
42469
|
+
* value scan always is: `MIN_NEEDLE_LENGTH` keeps a short plaintext from
|
|
42470
|
+
* matching a substring, and an export name that genuinely equals another
|
|
42471
|
+
* resource's secret is a name that should not be persisted either.
|
|
42472
|
+
*/
|
|
42473
|
+
allRecordedSecrets() {
|
|
42474
|
+
const all = /* @__PURE__ */ new Map();
|
|
42475
|
+
for (const bag of this.perResourceSecrets.values()) for (const [plaintext, expression] of bag) all.set(plaintext, expression);
|
|
42476
|
+
for (const [plaintext, expression] of this.outputSecrets) all.set(plaintext, expression);
|
|
42477
|
+
return all;
|
|
42478
|
+
}
|
|
42479
|
+
/**
|
|
42091
42480
|
* Stamp `parentStack` / `parentLogicalId` / `parentRegion` (schema v6+)
|
|
42092
42481
|
* onto a state object that's about to be saved, when this engine was
|
|
42093
42482
|
* constructed with `options.parentStackInfo` (= it's deploying a
|
|
@@ -42330,6 +42719,7 @@ var DeployEngine = class DeployEngine {
|
|
|
42330
42719
|
};
|
|
42331
42720
|
const currentEtag = currentStateData?.etag;
|
|
42332
42721
|
refuseMalformedOutputs(currentState, stackName, this.stackRegion);
|
|
42722
|
+
refuseMalformedResourcesForDeploy(currentState, stackName, this.stackRegion);
|
|
42333
42723
|
const migrationPending = currentStateData?.migrationPending ?? false;
|
|
42334
42724
|
this.logger.debug(`Loaded current state: ${Object.keys(currentState.resources).length} resources`);
|
|
42335
42725
|
if (this.options.onCurrentStateLoaded) await this.options.onCurrentStateLoaded(stackName, currentStateData?.state);
|
|
@@ -42430,7 +42820,7 @@ var DeployEngine = class DeployEngine {
|
|
|
42430
42820
|
outputs: outputsChanged ? outputsToPersist : persistedOutputs,
|
|
42431
42821
|
...exportNamesToPersist === void 0 ? exportNamesCarriedFrom(currentState) : { exportNames: [...exportNamesToPersist] },
|
|
42432
42822
|
...this.skippedOutputs && { skippedOutputs: { ...this.skippedOutputs } },
|
|
42433
|
-
...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads),
|
|
42823
|
+
...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads, this.crossStackReadKeyNormalizer()),
|
|
42434
42824
|
lastModified: Date.now()
|
|
42435
42825
|
};
|
|
42436
42826
|
const saveOptions = {};
|
|
@@ -42561,7 +42951,7 @@ var DeployEngine = class DeployEngine {
|
|
|
42561
42951
|
...exportNamesCarriedFrom(currentState),
|
|
42562
42952
|
...skippedOutputsCarriedFrom(currentState),
|
|
42563
42953
|
...orphansCarriedFrom(currentState),
|
|
42564
|
-
...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads),
|
|
42954
|
+
...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads, this.crossStackReadKeyNormalizer()),
|
|
42565
42955
|
lastModified: Date.now()
|
|
42566
42956
|
};
|
|
42567
42957
|
const migrate = pendingMigration;
|
|
@@ -42695,7 +43085,7 @@ var DeployEngine = class DeployEngine {
|
|
|
42695
43085
|
...exportNamesCarriedFrom(currentState),
|
|
42696
43086
|
...skippedOutputsCarriedFrom(currentState),
|
|
42697
43087
|
...orphansCarriedFrom(currentState),
|
|
42698
|
-
...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads),
|
|
43088
|
+
...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads, this.crossStackReadKeyNormalizer()),
|
|
42699
43089
|
lastModified: Date.now()
|
|
42700
43090
|
};
|
|
42701
43091
|
const migrate = pendingMigration;
|
|
@@ -42736,7 +43126,7 @@ var DeployEngine = class DeployEngine {
|
|
|
42736
43126
|
...exportNamesCarriedFrom(currentState),
|
|
42737
43127
|
...skippedOutputsCarriedFrom(currentState),
|
|
42738
43128
|
...orphansAfterRollback(currentState, rollbackOrphans),
|
|
42739
|
-
...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads),
|
|
43129
|
+
...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads, this.crossStackReadKeyNormalizer()),
|
|
42740
43130
|
lastModified: Date.now()
|
|
42741
43131
|
};
|
|
42742
43132
|
await this.stateBackend.saveState(stackName, this.stackRegion, this.withParentInfo(postRollbackState), { ...currentEtag !== void 0 && { expectedEtag: currentEtag } });
|
|
@@ -42755,7 +43145,7 @@ var DeployEngine = class DeployEngine {
|
|
|
42755
43145
|
...exportNamesCarriedFrom(currentState),
|
|
42756
43146
|
...skippedOutputsCarriedFrom(currentState),
|
|
42757
43147
|
...orphansAfterRollback(currentState, rollbackOrphans),
|
|
42758
|
-
...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads),
|
|
43148
|
+
...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads, this.crossStackReadKeyNormalizer()),
|
|
42759
43149
|
lastModified: Date.now()
|
|
42760
43150
|
};
|
|
42761
43151
|
await this.stateBackend.saveState(stackName, this.stackRegion, this.withParentInfo(postRollbackState), { ...freshEtag !== void 0 && { expectedEtag: freshEtag } });
|
|
@@ -42828,7 +43218,7 @@ var DeployEngine = class DeployEngine {
|
|
|
42828
43218
|
...exportNamesCarriedFrom(currentState),
|
|
42829
43219
|
...skippedOutputsCarriedFrom(currentState),
|
|
42830
43220
|
...orphansCarriedFrom(currentState),
|
|
42831
|
-
...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads),
|
|
43221
|
+
...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads, this.crossStackReadKeyNormalizer()),
|
|
42832
43222
|
lastModified: Date.now()
|
|
42833
43223
|
});
|
|
42834
43224
|
try {
|
|
@@ -42972,7 +43362,7 @@ var DeployEngine = class DeployEngine {
|
|
|
42972
43362
|
recordEvent: (event) => this.recordEvent(event),
|
|
42973
43363
|
finalSnapshotClients: this.options.finalSnapshotClients,
|
|
42974
43364
|
skipFinalSnapshot: this.options.skipFinalSnapshot,
|
|
42975
|
-
importedProducerRegions: producerRegionsFromState(crossStackReadsForPartialSave(previousState, this.recordedImports, this.recordedOutputReads))
|
|
43365
|
+
importedProducerRegions: producerRegionsFromState(crossStackReadsForPartialSave(previousState, this.recordedImports, this.recordedOutputReads, this.crossStackReadKeyNormalizer()))
|
|
42976
43366
|
};
|
|
42977
43367
|
}
|
|
42978
43368
|
/**
|
|
@@ -44333,5 +44723,5 @@ var DeployEngine = class DeployEngine {
|
|
|
44333
44723
|
};
|
|
44334
44724
|
|
|
44335
44725
|
//#endregion
|
|
44336
|
-
export { secretBearing as $,
|
|
44337
|
-
//# sourceMappingURL=deploy-engine-
|
|
44726
|
+
export { secretBearing as $, withErrorHandling as $i, LockManager as $n, getDefaultStateBucketName as $r, s3BucketArn as $t, extractDeploymentEventError as A, CdkdError as Ai, findActionableSilentDrops as An, parseBootstrapMarker as Ar, isImpossibleEmptyStoredAttribute as At, toCfnInteger as B, MalformedProducerRecordRefusalError as Bi, carriesSecretMask as Bn, dockerSpawnEnvWithSensitive as Br, coerceCfnBoolean as Bt, buildFinalSnapshotIdentifier as C, clearBucketRegionCache as Ci, refuseMalformedOutputs as Cn, rewriteTemplateAssetReferences as Cr, isTerminationProtectionPropagationError as Ct, refusesFinalSnapshot as D, resetAwsClients as Di, repairMalformedOutputsForReadOnly as Dn, ensureAssetStorage as Dr, cfnRefValueFromPhysicalId as Dt, isFinalSnapshotError as E, getAwsClients as Ei, refuseMalformedState as En, assertAssetBucketRegion as Er, carriesFetchableDynamicReference as Et, isStatefulRecreateTargetSync as F, DynamicReferenceRegionAmbiguousError as Fi, TemplateParser as Fn, describeAwsFailure as Fr, isSettledInstanceState as Ft, gray as G, ResourceUpdateNotSupportedError as Gi, isSingleDynamicReferenceToken as Gn, runDockerForeground as Gr, readConfigString as Gt, formatResourceLine as H, PartialFailureError as Hi, dynamicReferenceTokens as Hn, getDockerCmd as Hr, configBooleanRefusal as Ht, renderStatefulReason as I, IntrinsicResolutionRefusalError as Ii, STATE_SOURCED_BASELINE_RULES as In, buildDockerImage as Ir, WAFv2WebACLProvider as It, yellow as J, StateError as Ji, maskSecretsInText as Jn, stripControlChars as Jr, requireConfigObject as Jt, green as K, StackHasActiveImportsError as Ki, markSameGenerationBag as Kn, runDockerStreaming as Kr, replayWarn as Kt, WARM_THROUGHPUT_MEMBERS as L, LocalInvokeBuildError as Li, STATE_SOURCED_CROSS_GENERATION_RULES as Ln, describeDockerCapturedOutput as Lr, normalizeAwsTagsToCfn as Lt, planOrphanAdoption as M, CrossAccountSecretRefusalError as Mi, describeTypeWithThrottleRetry as Mn, validateAssetBucketName as Mr, isUnboundTemplateParameter as Mt, MULTI_REGION_RECREATE_BLOCKED_TYPES as N, DependencyError as Ni, withRetry as Nn, validateContainerRepoName as Nr, parameterTypeMayLoseSecretIdentity as Nt, unsupportedFinalSnapshotError as O, setAwsClients as Oi, repairMalformedResourcePropertiesForReadOnly as On, getBootstrapMarkerKey as Or, coerceParameterTypedValue as Ot, isStatefulRecreateTargetForReplace as P, DeployCancelledError as Pi, DagBuilder as Pn, buildDenyExternalAccessPolicy as Pr, refStateLookupFromResource as Pt, isExportAliasCollision as Q, normalizeAwsError as Qi, scrubResourceRecord as Qn, synthesisStatusMessage as Qr, withSharedDrainBudget as Qt, coerceWarmThroughput as R, LocalStartServiceError as Ri, STATE_SOURCED_READBACK_RULES as Rn, describeDockerExecFailure as Rr, resolveExplicitPhysicalId as Rt, PRE_DELETE_SNAPSHOT_TYPES as S, processStackMessages as Si, refuseMalformedNestedChildOutputs as Sn, loadPublishableAssetManifest as Sr, disableInstanceApiTermination as St, createPreDeleteFinalSnapshot as T, AwsClients as Ti, refuseMalformedResourcesForDestroy as Tn, BOOTSTRAP_MARKER_PREFIX as Tr, carriesDynamicReference as Tt, bold as U, ProvisioningError as Ui, errorCauseChain as Un, partitionSensitiveEnv as Ur, configIntegerRefusal as Ut, toFiniteNumber as V, NestedStackChildDirectDestroyError as Vi, createSecretMasker as Vn, formatDockerLoginError as Vr, coerceCfnInteger as Vt, cyan as W, ResourceTimeoutError as Wi, identityKeyFor as Wn, redactDockerArgvValues as Wr, configStringRefusal as Wt, collectPublishedOutputNames as X, formatError as Xi, recoverMaskedOutput as Xn, getDockerImageBySourceHash as Xr, classifyReplaySecretRegion as Xt, collectDeclaredOutputNames as Y, SynthesisError as Yi, recordMaskOnlyValue as Yn, AssetManifestLoader as Yr, requireConfigString as Yt, exportAliasCollisionScrubWarning as Z, isCdkdError as Zi, redactSecretsForState as Zn, Synthesizer as Zr, producerRegionsFromState as Zt, isSecretBearingReferenceString as _, expectedOwnerParam as _i, malformedRenderedContainersWarning as _n, AssetPublisher as _r, isWaitAbandonedError as _t, DeploymentEventsStore as a, markRedactedCause as aa, resolveStateBucketWithDefault as ai, applyCallerIdentityCredentials as an, formatLockExpiry as ar, clearOnUpdateRemoval as at, bindingSkippedOutputs as b, derivePartitionAndUrlSuffix as bi, malformedStateRefusalMessage as bn, buildAssetRedirectMap as br, deleteIndeterminateGuards as bt, replayFailedOperations as c, stateBucketExistenceConfirmed as ci, INTRINSIC_KEYS as cn, DEFAULT_STATE_PREFIX as cr, createMaskedRetryLogger as ct, updatePartialReason as d, CFN_TEMPLATE_URL_LIMIT as di, hasReadableResources as dn, importableOutputKeys as dr, beginCommandInterruptScope as dt, isMarkedNonRetryable as ea, getLegacyStateBucketName as ei, s3BucketDomainName as en, S3StateBackend as er, secretBearingStateKeyWarning as et, withResourceDeadline as f, MIGRATE_TMP_PREFIX as fi, malformedExportNamesWarning as fn, importableOutputs as fr, endCommandInterruptScope as ft, bagHoldsSecretExpression as g, decodeListingKey as gi, malformedOutputsWarning as gn, shouldRetainResource as gr, CloudControlProvider as gt, computeImplicitDeleteEdges as h, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as hi, malformedOutputsRefusalMessage as hn, orphansCarriedFrom as hr, startInterruptWatch as ht, DeploymentEventsReader as i, markNonRetryable as ia, resolveSkipPrefix as ii, applyRoleArnIfSet as in, forceQuitRecoveryClause as ir, collectInlinePolicyNamesManagedBySiblings as it, makeSiblingClaimReader as j, ConfigError as ji, findSilentDropProperties as jn, readBootstrapMarkerBody as jr, isNamelessDynamicReferenceError as jt, makeCanonicalizePropertiesFn as k, AssetError as ki, repairMalformedResourcesForReadOnly as kn, isCrossRegionRedirect as kr, getAccountInfo as kt, replayRollback as l, warnDeprecatedNoPrefixCliFlag as li, STATE_RESOURCES_MALFORMED as ln, exportNamesCarriedFrom as lr, maskDeep as lt, IMPLICIT_DELETE_DEPENDENCIES as m, uploadCfnTemplate as mi, malformedLocalOutputsWarning as mn, orphansAfterRollback as mr, isInterruptedWaitError as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, isThrottlingError as na, resolveAutoAssetStorage as ni, s3BucketRegionalDomainName as nn, buildForceUnlockCommand as nr, getCurrentResourceSecrets as nt, planFailedOps as o, retryClassificationText as oa, resolveStateBucketWithDefaultAndSource as oi, callerEnvCredentials as on, shellQuote as or, ProviderRegistry as ot, maskingRetryLogger as p, findLargeInlineResources as pi, malformedExportSourceWarning as pn, isReadableBag as pr, interruptWatchListenerCount as pt, red as q, StackTerminationProtectionError as qi, maskSecretsInError as qn, escapeRegExp$1 as qr, requireConfigArray as qt, DeployEngine as r, isTransientServerError as ra, resolveCaptureObservedState as ri, s3BucketWebsiteUrl as rn, buildLockContentionMessage as rr, IAMRoleProvider as rt, planRollback as s, __exportAll as sa, resolveUseCdkBootstrapAssets as si, DiffCalculator as sn, CUSTOM_RESOURCE_RESPONSE_PREFIX as sr, wouldReturnToSdkProvider as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, isRetryableTransientError as ta, resolveApp as ti, s3BucketDualStackDomainName as tn, rebuildClientForBucketRegion as tr, secretSafeKeyDisplay as tt, updatePartialMessage as u, CFN_TEMPLATE_BODY_LIMIT as ui, hasReadableOutputs as un, hasReadableExportSet as ur, maskerOrIdentity as ut, keptWholeReasonText as v, PARTITION_TABLE as vi, malformedResourcePropertiesWarning as vn, stringifyValue as vr, slowCcOperationTimeoutMs as vt, ccRoutedFinalSnapshotError as w, resolveBucketRegion as wi, refuseMalformedOutputsForDestroy as wn, AssetModeResolver as wr, IntrinsicFunctionResolver as wt, ATOMIC_FINAL_SNAPSHOT_TYPES as x, AssemblyReader as xi, producerRecordKey as xn, createAssetRedirectResolver as xr, deleteSkipReason as xt, mergeNoChangeOutputs as y, canonicalizeRegion as yi, malformedResourcesWarning as yn, WorkGraph as yr, UNSPECIFIED_SKIP_REASON as yt, isWarmThroughputDecrease as z, LockError as zi, TEMPLATE_SOURCED_RULES as zn, describeDockerFailure as zr, assertRegionMatch as zt };
|
|
44727
|
+
//# sourceMappingURL=deploy-engine-KnCjibgq.js.map
|