@go-to-k/cdkd 0.290.2 → 0.290.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{asg-provider-BsC_XndT.js → asg-provider-NffO_bpL.js} +20 -15
- package/dist/asg-provider-NffO_bpL.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-Nn9j2GPK.js → deploy-engine-DD7JE_Ic.js} +746 -115
- package/dist/deploy-engine-DD7JE_Ic.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{logger-DTEUzYP_.js → logger-th1myfgh.js} +63 -2
- package/dist/logger-th1myfgh.js.map +1 -0
- package/dist/{program-z-ESK5sd.js → program-20PIJFu9.js} +268 -102
- package/dist/{program-z-ESK5sd.js.map → program-20PIJFu9.js.map} +1 -1
- package/dist/{version-7XwrjRzX.js → version-BUcQz4SU.js} +2 -2
- package/dist/{version-7XwrjRzX.js.map → version-BUcQz4SU.js.map} +1 -1
- package/package.json +1 -1
- package/dist/asg-provider-BsC_XndT.js.map +0 -1
- package/dist/deploy-engine-Nn9j2GPK.js.map +0 -1
- package/dist/logger-DTEUzYP_.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { b as
|
|
1
|
+
import { b as displayIdent, d as applyDefaultNameForFallback, f as explicitNamePropertyFor, g as looksLikeCdkdGeneratedName, h as getCurrentStackName, m as generateResourceNameWithFallback, n as getLogger, p as generateResourceName, r as isStdoutReservedForPayload, s as getLiveRenderer, v as withStackName, x as displaySafe, y as UNRENDERABLE } from "./logger-th1myfgh.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-BUcQz4SU.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";
|
|
@@ -17,7 +17,7 @@ import { APIGatewayClient, GetRestApiCommand } from "@aws-sdk/client-api-gateway
|
|
|
17
17
|
import { DescribeApiDestinationCommand, DescribeConnectionCommand, EventBridgeClient } from "@aws-sdk/client-eventbridge";
|
|
18
18
|
import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
|
|
19
19
|
import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
|
|
20
|
-
import { CloudFrontClient, GetCloudFrontOriginAccessIdentityCommand } from "@aws-sdk/client-cloudfront";
|
|
20
|
+
import { CloudFrontClient, GetCloudFrontOriginAccessIdentityCommand, GetDistributionCommand } from "@aws-sdk/client-cloudfront";
|
|
21
21
|
import { CloudWatchClient } from "@aws-sdk/client-cloudwatch";
|
|
22
22
|
import { CloudWatchLogsClient } from "@aws-sdk/client-cloudwatch-logs";
|
|
23
23
|
import { BedrockAgentCoreControlClient } from "@aws-sdk/client-bedrock-agentcore-control";
|
|
@@ -1148,7 +1148,15 @@ function formatDuration$1(ms) {
|
|
|
1148
1148
|
* `Outputs.<Key>` the child does not declare rather than letting
|
|
1149
1149
|
* `guardedPhysicalIdFallback` serve the synthetic
|
|
1150
1150
|
* `arn:cdkd-local:...` physical id — that id starts with `arn:`, so the
|
|
1151
|
-
* #1103 ARN-shape guard passes it
|
|
1151
|
+
* #1103 ARN-shape guard passes it; and the LIVE-READ refusals of issue
|
|
1152
|
+
* [#3096](https://github.com/go-to-k/cdkd/issues/3096)
|
|
1153
|
+
* (`refuseUnservedAttribute`, raised by the `AWS::EC2::Instance` address /
|
|
1154
|
+
* DNS / zone arm, the `AWS::EC2::VPC` `DefaultSecurityGroup` arm and the
|
|
1155
|
+
* `AWS::CloudFront::Distribution` `DomainName` arm when the record omits
|
|
1156
|
+
* the attribute and the live read finds nothing or fails), plus the
|
|
1157
|
+
* read-less `AWS::RDS::DBProxy` / `DBProxyEndpoint` `VpcId` refusal beside
|
|
1158
|
+
* them — each replacing a physical id the attribute's value type can never
|
|
1159
|
+
* accept.
|
|
1152
1160
|
* 2. NOT reachable from it, and thrown as this class only so that "deliberate
|
|
1153
1161
|
* refusal" is a property of the THROW rather than of the one catch that
|
|
1154
1162
|
* inspects it: `resolveSplit`'s two refusals of a non-string value (#1874),
|
|
@@ -1177,11 +1185,17 @@ function formatDuration$1(ms) {
|
|
|
1177
1185
|
* subclass, never on this class.
|
|
1178
1186
|
*
|
|
1179
1187
|
* The class is deliberately NOT `markNonRetryable` at construction, unlike
|
|
1180
|
-
* {@link ResourceUpdateNotSupportedError}:
|
|
1188
|
+
* {@link ResourceUpdateNotSupportedError}: two kinds of throw site are
|
|
1181
1189
|
* genuinely time-dependent — the fabricated-account guard, where
|
|
1182
1190
|
* `getAccountInfo` caches a fabricated answer for only 10s precisely so a
|
|
1183
|
-
* later attempt can heal
|
|
1184
|
-
*
|
|
1191
|
+
* later attempt can heal, and since issue #3096 the LIVE-READ refusals
|
|
1192
|
+
* (`refuseUnservedAttribute`), where a `pending` instance settles seconds
|
|
1193
|
+
* later and a failed describe can succeed on the next attempt — so a
|
|
1194
|
+
* constructor-level marker would wrongly DECLARE those terminal (whether
|
|
1195
|
+
* `withRetry` then retries one is the classifiers' call, and that helper's
|
|
1196
|
+
* doc comment says what they actually see). Every OTHER
|
|
1197
|
+
* site marks at its own `throw` (the #3096 `DBProxy` `VpcId` refusal
|
|
1198
|
+
* included — it has no live read, so it decides from the record alone): each
|
|
1185
1199
|
* decides from inputs a retry cannot change (a persisted state record, an
|
|
1186
1200
|
* attribute-name suffix, a CLI flag, an already-resolved value's type, a
|
|
1187
1201
|
* template's literal `RoleArn`, a child stack's declared output names), and
|
|
@@ -12731,7 +12745,13 @@ const STATE_SOURCED_READBACK_RULES = {
|
|
|
12731
12745
|
* `observedProperties`, where a mask is a REGRESSION rather than a refusal
|
|
12732
12746
|
* (`cdkd export` blocks such a record and the rollback replay refuses the
|
|
12733
12747
|
* operation). A caller declares this constant when it knows its bag becomes a
|
|
12734
|
-
* drift baseline and nothing else
|
|
12748
|
+
* drift baseline and nothing else — and since issue
|
|
12749
|
+
* [#2939](https://github.com/go-to-k/cdkd/issues/2939) the two `cdkd drift`
|
|
12750
|
+
* writers make that call per record: `--accept`'s new baseline and
|
|
12751
|
+
* `--revert`'s narrowed delta take THIS constant when the record carries
|
|
12752
|
+
* `observedProperties` (the bag lands there) and
|
|
12753
|
+
* {@link STATE_SOURCED_READBACK_RULES} otherwise (the bag lands in
|
|
12754
|
+
* `properties`), the *** rows of the write-site table above.
|
|
12735
12755
|
*/
|
|
12736
12756
|
const STATE_SOURCED_BASELINE_RULES = {
|
|
12737
12757
|
descendArrays: false,
|
|
@@ -12944,7 +12964,8 @@ function isPlainObject$2(value) {
|
|
|
12944
12964
|
}
|
|
12945
12965
|
/**
|
|
12946
12966
|
* Stands in for a source part the skeleton cannot know — an `Fn::Join` element
|
|
12947
|
-
* that is itself an intrinsic
|
|
12967
|
+
* that is itself an intrinsic or any other non-scalar or `null` part (see
|
|
12968
|
+
* {@link joinPartLiteralText}), or an `Fn::Sub` `${...}` variable.
|
|
12948
12969
|
*
|
|
12949
12970
|
* Built from {@link DYNAMIC_REFERENCE_INNER_CHAR} rather than `.` because a
|
|
12950
12971
|
* recorded expression's INNER text never contains `}`: the resolver matches
|
|
@@ -13013,7 +13034,7 @@ const MAX_SKELETON_CANDIDATE_LENGTH = 512;
|
|
|
13013
13034
|
* between them. Measured on a ~120-char candidate, before the collapse:
|
|
13014
13035
|
* 4 adjacent wildcards 39ms, 5 ~1s, 6 20s, 8 did not finish in two minutes. Two
|
|
13015
13036
|
* legal CFn shapes CDK can emit produce ADJACENT ones — an `Fn::Join` with an
|
|
13016
|
-
* EMPTY delimiter and consecutive
|
|
13037
|
+
* EMPTY delimiter and consecutive intrinsic (or `null`) parts, and an `Fn::Sub` with
|
|
13017
13038
|
* adjacent `${a}${b}` variables — and the collapse takes both to a single
|
|
13018
13039
|
* wildcard. It does NOT close the class, which is why the cap exists beside it.
|
|
13019
13040
|
*
|
|
@@ -13095,7 +13116,8 @@ function anchoredSkeletonPattern(segments) {
|
|
|
13095
13116
|
}
|
|
13096
13117
|
/**
|
|
13097
13118
|
* A source part the skeleton cannot know — an `Fn::Join` element that is
|
|
13098
|
-
* itself an intrinsic
|
|
13119
|
+
* itself an intrinsic or any other non-scalar or `null` part (see
|
|
13120
|
+
* {@link joinPartLiteralText}), or an `Fn::Sub` `${...}` variable. The segment form of
|
|
13099
13121
|
* {@link SKELETON_WILDCARD}: what a part IS, before {@link anchoredSkeletonPattern}
|
|
13100
13122
|
* — the one speller — turns it into the wildcard.
|
|
13101
13123
|
*/
|
|
@@ -13112,13 +13134,51 @@ const UNKNOWN_PART = Symbol("unknown intrinsic part");
|
|
|
13112
13134
|
*/
|
|
13113
13135
|
const UNKNOWN_PART_PLACEHOLDER = "\0";
|
|
13114
13136
|
/**
|
|
13137
|
+
* The literal text an `Fn::Join` PART contributes, or `undefined` when the
|
|
13138
|
+
* part is not knowable from the template (issue #3055).
|
|
13139
|
+
*
|
|
13140
|
+
* The resolver joins every part as `String(await resolveValue(part))`, and
|
|
13141
|
+
* `resolveValue` returns a NUMBER or BOOLEAN unchanged, so such a part
|
|
13142
|
+
* contributes exactly `String(part)` -- `8080` joins as `8080`, `true` as
|
|
13143
|
+
* `true`. A string part is its raw text here as it always was (a
|
|
13144
|
+
* `{{resolve:` inside it is the token the readers look for, not text this
|
|
13145
|
+
* function resolves). Reading only strings as literal made the other two a
|
|
13146
|
+
* wildcard for the skeleton arm (a candidate differing at that position could
|
|
13147
|
+
* match) and a placeholder for the frame arm (whose rendered frame then no
|
|
13148
|
+
* longer equals the leaf's text around the middle, so the arm refuses) -- and,
|
|
13149
|
+
* through the frame arm's rewrite, for the nested-stack parameter recorder's
|
|
13150
|
+
* `frameSpellingOf`, which carries a framed parameter to the child only when
|
|
13151
|
+
* that arm wrote it. The recorder's own rendering of such a part does not
|
|
13152
|
+
* decide the case: a part outside the token leaves its one span and service
|
|
13153
|
+
* prefix the same either way.
|
|
13154
|
+
*
|
|
13155
|
+
* `null` stays unknowable although the resolver would join it as `null`, as it
|
|
13156
|
+
* was before this function existed: it is not a part a CloudFormation template
|
|
13157
|
+
* can carry meaningfully. Being a wildcard, it can still let the skeleton arm
|
|
13158
|
+
* match a lone candidate spelling something else at that position -- the
|
|
13159
|
+
* arm's existing wrong-reference residual, not narrowed here. The literal
|
|
13160
|
+
* reading reaches that same residual in a few more places: a source that left
|
|
13161
|
+
* two candidates matching as a wildcard can now leave ONE, which the arm
|
|
13162
|
+
* accepts even when the leaf's own reference was never recorded (a public
|
|
13163
|
+
* `ssm` parameter). An object or array part (an intrinsic, a list, or any
|
|
13164
|
+
* other object) stays {@link UNKNOWN_PART}.
|
|
13165
|
+
*/
|
|
13166
|
+
function joinPartLiteralText(part) {
|
|
13167
|
+
if (typeof part === "string") return part;
|
|
13168
|
+
if (typeof part === "number" || typeof part === "boolean") return String(part);
|
|
13169
|
+
}
|
|
13170
|
+
/**
|
|
13115
13171
|
* The text of an `Fn::Join` / `Fn::Sub` source in order: a literal part as its
|
|
13116
|
-
* RAW text
|
|
13172
|
+
* RAW text (a number or boolean `Fn::Join` part as the text the resolver joins
|
|
13173
|
+
* it as, {@link joinPartLiteralText}), an unknowable part as
|
|
13174
|
+
* {@link UNKNOWN_PART}. ONE parser for the three
|
|
13117
13175
|
* readers of that shape — {@link intrinsicSkeletonPattern}, which hands every
|
|
13118
|
-
* segment to {@link anchoredSkeletonPattern} to spell as a regex,
|
|
13176
|
+
* segment to {@link anchoredSkeletonPattern} to spell as a regex,
|
|
13119
13177
|
* {@link positionByIntrinsicFrame}, which needs the
|
|
13120
|
-
* literal text VERBATIM to know where a token's frame begins and ends
|
|
13121
|
-
*
|
|
13178
|
+
* literal text VERBATIM to know where a token's frame begins and ends, and
|
|
13179
|
+
* `frameSpellingOf` in {@link recordNestedStackParameterExpressions}, which
|
|
13180
|
+
* renders the same text to find the token a carried frame spells — so
|
|
13181
|
+
* they cannot disagree about what a source says (issues #2745, #3062).
|
|
13122
13182
|
*
|
|
13123
13183
|
* `undefined` for any source this cannot describe, per the pattern reader's
|
|
13124
13184
|
* doc: a delimiter that is itself an intrinsic, a non-array `Fn::Join`, a
|
|
@@ -13136,7 +13196,7 @@ function intrinsicSkeletonSegments(source) {
|
|
|
13136
13196
|
const segments = [];
|
|
13137
13197
|
parts.forEach((part, index) => {
|
|
13138
13198
|
if (index > 0) segments.push(delimiter);
|
|
13139
|
-
segments.push(
|
|
13199
|
+
segments.push(joinPartLiteralText(part) ?? UNKNOWN_PART);
|
|
13140
13200
|
});
|
|
13141
13201
|
return segments;
|
|
13142
13202
|
}
|
|
@@ -13390,9 +13450,10 @@ function positionByCrossStackSource(bag, source, secrets) {
|
|
|
13390
13450
|
*
|
|
13391
13451
|
* One residual is worth naming rather than leaving to be rediscovered: a single
|
|
13392
13452
|
* WRONG candidate can win only when this leaf's own expression is in NEITHER
|
|
13393
|
-
* store —
|
|
13394
|
-
*
|
|
13395
|
-
*
|
|
13453
|
+
* store — an `ssm` reference whose `Type` came back unclassifiable
|
|
13454
|
+
* (deliberately unpinned per #1901) and which then lost the value collapse, or
|
|
13455
|
+
* a PUBLIC `ssm` reference (stored resolved, #1901) whose value coincides with
|
|
13456
|
+
* a recorded secret's plaintext. `recordedSecretExpressions` is process-wide, so the winner
|
|
13396
13457
|
* could in principle come from another resource, and condition 3 cannot refuse
|
|
13397
13458
|
* one the pass never recorded.
|
|
13398
13459
|
*
|
|
@@ -13401,7 +13462,7 @@ function positionByCrossStackSource(bag, source, secrets) {
|
|
|
13401
13462
|
* `resolveReplayProps` RE-RESOLVES the persisted expression against AWS and
|
|
13402
13463
|
* hands the result to `provider.update`, so a rollback replays the wrong secret
|
|
13403
13464
|
* — immediately, if the two references already resolve to different values. It
|
|
13404
|
-
* is narrow (unclassifiable
|
|
13465
|
+
* is narrow (unclassifiable or public `ssm` leaves only) but it is not cosmetic, and an
|
|
13405
13466
|
* earlier draft of this paragraph called it "a spurious UPDATE rather than a
|
|
13406
13467
|
* disclosure", which understated it.
|
|
13407
13468
|
*
|
|
@@ -16981,6 +17042,7 @@ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
16981
17042
|
* releases. Neither counter feeds a control decision; they are reporting only.
|
|
16982
17043
|
*/
|
|
16983
17044
|
async function withRetry(operation, logicalId, opts = {}) {
|
|
17045
|
+
const shownId = displaySafe(logicalId, { asciiOnly: true }) || "<unrenderable>";
|
|
16984
17046
|
const maxRetries = opts.maxRetries ?? 8;
|
|
16985
17047
|
const initialDelayMs = opts.initialDelayMs ?? 1e3;
|
|
16986
17048
|
const maxDelayMs = opts.maxDelayMs ?? 8e3;
|
|
@@ -17014,7 +17076,7 @@ async function withRetry(operation, logicalId, opts = {}) {
|
|
|
17014
17076
|
if (propagationRetries > 0) spent.push(`${propagationRetries} IAM-propagation ${propagationRetries === 1 ? "retry" : "retries"} over ${(propagationSleptMs / 1e3).toFixed(2)}s of propagation backoff${budgetExhausted ? " (the full propagation budget)" : ""}`);
|
|
17015
17077
|
if (nameCooldownRetries > 0) spent.push(`${nameCooldownRetries} name-cooldown ${nameCooldownRetries === 1 ? "retry" : "retries"} over ${(nameCooldownSleptMs / 1e3).toFixed(2)}s waiting for the name to be released${nameCooldownBudgetExhausted ? " (the full name-cooldown budget)" : ""}`);
|
|
17016
17078
|
if (serverErrorRetries > 0) spent.push(`${serverErrorRetries} transient server-error ${serverErrorRetries === 1 ? "retry" : "retries"} (HTTP 5xx)`);
|
|
17017
|
-
const summary = () => `${
|
|
17079
|
+
const summary = () => `${shownId}: gave up after ${spent.join(" and ")} - ${message}` + formatRetryClassificationSignals(error);
|
|
17018
17080
|
try {
|
|
17019
17081
|
opts.logger?.warn?.(summary());
|
|
17020
17082
|
} catch {}
|
|
@@ -17023,7 +17085,7 @@ async function withRetry(operation, logicalId, opts = {}) {
|
|
|
17023
17085
|
}
|
|
17024
17086
|
const delay = propagation ? Math.min(250 * Math.pow(2, attempt), IAM_PROPAGATION_MAX_DELAY_MS) : nameCooldown ? Math.min(NAME_COOLDOWN_INITIAL_DELAY_MS * Math.pow(2, attempt), NAME_COOLDOWN_MAX_DELAY_MS) : Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs);
|
|
17025
17087
|
const backoffThroughThisAttemptMs = propagation ? propagationSleptMs + delay : propagationSleptMs;
|
|
17026
|
-
opts.logger?.debug(` ⏳ Retrying ${
|
|
17088
|
+
opts.logger?.debug(` ⏳ Retrying ${shownId} in ${delay / 1e3}s (attempt ${attempt + 1}/${attemptLimit}${propagation ? `, ${(backoffThroughThisAttemptMs / 1e3).toFixed(2)}s backoff through this attempt` : ""}) - ${message}`);
|
|
17027
17089
|
for (let waited = 0; waited < delay; waited += 1e3) {
|
|
17028
17090
|
if (opts.isInterrupted?.()) throw opts.onInterrupted ? opts.onInterrupted() : /* @__PURE__ */ new Error("Interrupted");
|
|
17029
17091
|
await sleep(Math.min(1e3, delay - waited));
|
|
@@ -20675,14 +20737,17 @@ async function assumeRoleForCrossAccountStateRead(roleArn) {
|
|
|
20675
20737
|
* up the assumed-role credentials automatically without touching the
|
|
20676
20738
|
* client construction sites.
|
|
20677
20739
|
*
|
|
20678
|
-
* **
|
|
20679
|
-
*
|
|
20680
|
-
*
|
|
20681
|
-
*
|
|
20682
|
-
*
|
|
20683
|
-
*
|
|
20684
|
-
*
|
|
20685
|
-
*
|
|
20740
|
+
* **What the assumed role must carry.** Unlike `cdk deploy`, cdkd does
|
|
20741
|
+
* NOT route through CloudFormation. There is no cfn-exec-role to
|
|
20742
|
+
* delegate to. Every IAM / EC2 / Lambda / etc. API call is issued from
|
|
20743
|
+
* the cdkd process directly. The role you pass to `--role-arn` (or set
|
|
20744
|
+
* in `CDKD_ROLE_ARN`) must therefore carry the actions for the resource
|
|
20745
|
+
* types being deployed plus cdkd's own bookkeeping set (Cloud Control,
|
|
20746
|
+
* `cloudformation:DescribeType` / `ListExports` / `DescribeStacks`,
|
|
20747
|
+
* `sts:GetCallerIdentity`, the state and asset storage) — i.e. exactly what the
|
|
20748
|
+
* caller's own principal would otherwise have needed, not
|
|
20749
|
+
* `AdministratorAccess`. CDK CLI's `cdk-hnb659fds-deploy-role-*` is NOT
|
|
20750
|
+
* sufficient — that role only carries CFn + asset-publish permissions.
|
|
20686
20751
|
*
|
|
20687
20752
|
* Default session duration is 1 hour. For longer-running deploys, the
|
|
20688
20753
|
* caller should re-issue the cdkd command (the in-flight credentials
|
|
@@ -22663,6 +22728,36 @@ var WAFv2WebACLProvider = class {
|
|
|
22663
22728
|
}
|
|
22664
22729
|
};
|
|
22665
22730
|
|
|
22731
|
+
//#endregion
|
|
22732
|
+
//#region src/provisioning/ec2-instance-state.ts
|
|
22733
|
+
/**
|
|
22734
|
+
* The ONE spelling of "has this EC2 instance SETTLED?" (issue
|
|
22735
|
+
* [#3096](https://github.com/go-to-k/cdkd/issues/3096) review).
|
|
22736
|
+
*
|
|
22737
|
+
* Two readers must agree on it or a record and a resolution disagree about
|
|
22738
|
+
* the same instance: `EC2Provider`'s `describedInstanceAttributes` decides
|
|
22739
|
+
* whether a missing public member is RECORDED as the known `''` (settled) or
|
|
22740
|
+
* OMITTED (still `pending`, issue #3077), and the resolver's
|
|
22741
|
+
* `AWS::EC2::Instance` live arm decides, for an omitted member, whether to
|
|
22742
|
+
* SERVE that same `''` or REFUSE. Each carried the comparison inline; this
|
|
22743
|
+
* module is the shared predicate so a change to one side cannot leave the
|
|
22744
|
+
* other on the old rule.
|
|
22745
|
+
*
|
|
22746
|
+
* `pending` is the only state in which EC2 has not finished assigning the
|
|
22747
|
+
* launch-time members; every other reported state — `running`, `stopping`,
|
|
22748
|
+
* `stopped`, `shutting-down`, `terminated` — is a settled one whose public
|
|
22749
|
+
* pair, when absent, is genuinely absent (a private-subnet instance, or a
|
|
22750
|
+
* stopped one whose address was released). NO state at all is treated like
|
|
22751
|
+
* `pending`: a read-back that returned no instance, or a shape without one,
|
|
22752
|
+
* says nothing about having settled.
|
|
22753
|
+
*
|
|
22754
|
+
* A LEAF: no imports, so both `src/provisioning/**` and `src/deployment/**`
|
|
22755
|
+
* can take it without adding an edge between the two layers.
|
|
22756
|
+
*/
|
|
22757
|
+
function isSettledInstanceState(stateName) {
|
|
22758
|
+
return stateName !== void 0 && stateName !== "pending";
|
|
22759
|
+
}
|
|
22760
|
+
|
|
22666
22761
|
//#endregion
|
|
22667
22762
|
//#region src/deployment/intrinsic-function-resolver.ts
|
|
22668
22763
|
/**
|
|
@@ -23195,6 +23290,19 @@ function withoutProducerRegions(context) {
|
|
|
23195
23290
|
return rest;
|
|
23196
23291
|
}
|
|
23197
23292
|
/**
|
|
23293
|
+
* Masked log twins registered this pass, per pass bag (issue #3100;
|
|
23294
|
+
* `rememberLogTwin` / `logTwinOfProduct`). Log-only: nothing that resolves or
|
|
23295
|
+
* persists a value reads it.
|
|
23296
|
+
*
|
|
23297
|
+
* MODULE scope, not instance scope: a region-pinned sibling resolver
|
|
23298
|
+
* (`resolverForProducerRegion`) resolves on behalf of the consumer with the
|
|
23299
|
+
* consumer's bag, so an instance-local registry left the sibling's twin where
|
|
23300
|
+
* the consumer's `Fn::Join` / `Fn::Sub` never looked. The key is still the
|
|
23301
|
+
* pass's own bag object, so scope does not widen: another pass holds another
|
|
23302
|
+
* bag and cannot reach these entries, and they die with the bag.
|
|
23303
|
+
*/
|
|
23304
|
+
const LOG_TWINS_BY_PASS = /* @__PURE__ */ new WeakMap();
|
|
23305
|
+
/**
|
|
23198
23306
|
* Does `value` carry a CloudFormation dynamic reference anywhere inside it?
|
|
23199
23307
|
*
|
|
23200
23308
|
* Used as the identity fast path of {@link
|
|
@@ -23313,10 +23421,37 @@ const recordedSecretExpressions = {
|
|
|
23313
23421
|
* lookup (PrivateIp / PublicIp / PrivateDnsName / PublicDnsName /
|
|
23314
23422
|
* AvailabilityZone). Keyed by `${physicalId}#${attributeName}`. The IP /
|
|
23315
23423
|
* DNS attributes are not derivable from the instance id, so they are read
|
|
23316
|
-
* back from AWS once per (instance, attribute) and memoized for the
|
|
23317
|
-
* lifetime
|
|
23318
|
-
|
|
23319
|
-
|
|
23424
|
+
* back from AWS once per (instance, attribute) and memoized for the PROCESS
|
|
23425
|
+
* lifetime — one `cdkd deploy` per CLI process, so "the deploy" in practice;
|
|
23426
|
+
* nothing in `src/` calls `resetAccountInfoCache`, tests do. Only a VALUE is cached — a settled instance's address, or the
|
|
23427
|
+
* known-empty `''` a settled instance reports for a public member it has
|
|
23428
|
+
* none of — never a refusal: a `pending` instance is re-described on the
|
|
23429
|
+
* next resolution, when it may have settled (issue #3096).
|
|
23430
|
+
*/
|
|
23431
|
+
const cachedEc2InstanceAttributes = Object.create(null);
|
|
23432
|
+
/**
|
|
23433
|
+
* The two sibling caches of {@link cachedEc2InstanceAttributes} (issue
|
|
23434
|
+
* #3096), each holding a value the resolver read LIVE because the state
|
|
23435
|
+
* record omitted it — `definedAttributes` drops a member the provider could
|
|
23436
|
+
* not read back (issue #3077), so the `Fn::GetAtt` falls out of the flat
|
|
23437
|
+
* lookup and into `constructAttribute`'s per-type arm. Keyed by physical id
|
|
23438
|
+
* (AWS-minted, unique across accounts and regions, and the value — a default
|
|
23439
|
+
* security group id, a distribution hostname — never changes); like the
|
|
23440
|
+
* instance cache, a refusal caches nothing. Same process lifetime, cleared
|
|
23441
|
+
* together with it by {@link resetAccountInfoCache}. The RDS `DBProxy` / `DBProxyEndpoint`
|
|
23442
|
+
* `VpcId` arms have no cache because they have no live read: `AwsClients`
|
|
23443
|
+
* exposes no RDS client and this file imports none, so those two arms refuse
|
|
23444
|
+
* outright (see `refuseUnservedAttribute`'s callers).
|
|
23445
|
+
*
|
|
23446
|
+
* All three are NULL-PROTOTYPE objects (#3096 delta review, measured): the
|
|
23447
|
+
* key is a state-record physical id, and on a plain `{}` a record holding
|
|
23448
|
+
* `constructor` / `__proto__` / `hasOwnProperty` read a FUNCTION out of
|
|
23449
|
+
* `Object.prototype` as the cached value — served with zero AWS calls, ahead
|
|
23450
|
+
* of every shape guard below. `Object.create(null)` has nothing to read; the
|
|
23451
|
+
* `delete`-based clears in `resetAccountInfoCache` keep the prototype.
|
|
23452
|
+
*/
|
|
23453
|
+
const cachedVpcDefaultSecurityGroups = Object.create(null);
|
|
23454
|
+
const cachedCloudFrontDomainNames = Object.create(null);
|
|
23320
23455
|
/**
|
|
23321
23456
|
* The region this call answers for: the caller's override, else the ambient one.
|
|
23322
23457
|
*
|
|
@@ -25228,7 +25363,44 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
25228
25363
|
this.logger.warn(`Failed to fetch VPC Ipv6CidrBlocks for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
25229
25364
|
return [];
|
|
25230
25365
|
}
|
|
25231
|
-
case "DefaultSecurityGroup":
|
|
25366
|
+
case "DefaultSecurityGroup": {
|
|
25367
|
+
if (!/^vpc-[0-9a-f]+$/.test(physicalId)) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, DefaultSecurityGroup] for AWS::EC2::VPC: the state record's physical id "${displaySafe(this.maskThenStripThenMask(physicalId, context)).slice(0, 64)}" is not a VPC id (vpc-<hex>), so cdkd will not use it as an EC2 filter value. Repair the record (cdkd import, or re-create the VPC) and deploy again.`));
|
|
25368
|
+
const cachedGroupId = cachedVpcDefaultSecurityGroups[physicalId];
|
|
25369
|
+
if (cachedGroupId !== void 0) return cachedGroupId;
|
|
25370
|
+
let groupId;
|
|
25371
|
+
try {
|
|
25372
|
+
groupId = (await this.clientsForRegion(this.explicitRegion).ec2.send(new DescribeSecurityGroupsCommand({ Filters: [{
|
|
25373
|
+
Name: "vpc-id",
|
|
25374
|
+
Values: [physicalId]
|
|
25375
|
+
}, {
|
|
25376
|
+
Name: "group-name",
|
|
25377
|
+
Values: ["default"]
|
|
25378
|
+
}] }))).SecurityGroups?.[0]?.GroupId;
|
|
25379
|
+
} catch (err) {
|
|
25380
|
+
this.refuseUnservedAttribute({
|
|
25381
|
+
logicalId,
|
|
25382
|
+
attributeName,
|
|
25383
|
+
resourceType,
|
|
25384
|
+
physicalId,
|
|
25385
|
+
context,
|
|
25386
|
+
observed: this.describeFailureObserved("DescribeSecurityGroups", err, context),
|
|
25387
|
+
remedy: "Fix the read (the vpc-id / group-name filtered DescribeSecurityGroups permission, or the region) and deploy again."
|
|
25388
|
+
});
|
|
25389
|
+
}
|
|
25390
|
+
if (groupId) {
|
|
25391
|
+
cachedVpcDefaultSecurityGroups[physicalId] = groupId;
|
|
25392
|
+
return groupId;
|
|
25393
|
+
}
|
|
25394
|
+
this.refuseUnservedAttribute({
|
|
25395
|
+
logicalId,
|
|
25396
|
+
attributeName,
|
|
25397
|
+
resourceType,
|
|
25398
|
+
physicalId,
|
|
25399
|
+
context,
|
|
25400
|
+
observed: "DescribeSecurityGroups found no group named \"default\" in the VPC",
|
|
25401
|
+
remedy: "Check the VPC in the console, or reference the attribute from a later deploy."
|
|
25402
|
+
});
|
|
25403
|
+
}
|
|
25232
25404
|
default: return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
|
|
25233
25405
|
}
|
|
25234
25406
|
if (resourceType === "AWS::IAM::Policy") switch (attributeName) {
|
|
@@ -25423,9 +25595,11 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
25423
25595
|
const cacheKey = `${physicalId}#${attributeName}`;
|
|
25424
25596
|
const cached = cachedEc2InstanceAttributes[cacheKey];
|
|
25425
25597
|
if (cached !== void 0) return cached;
|
|
25598
|
+
let value;
|
|
25599
|
+
let stateName;
|
|
25426
25600
|
try {
|
|
25427
25601
|
const instance = (await this.clientsForRegion(this.explicitRegion).ec2.send(new DescribeInstancesCommand({ InstanceIds: [physicalId] }))).Reservations?.[0]?.Instances?.[0];
|
|
25428
|
-
|
|
25602
|
+
stateName = instance?.State?.Name;
|
|
25429
25603
|
switch (attributeName) {
|
|
25430
25604
|
case "PrivateIp":
|
|
25431
25605
|
value = instance?.PrivateIpAddress;
|
|
@@ -25441,15 +25615,36 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
25441
25615
|
break;
|
|
25442
25616
|
case "AvailabilityZone": value = instance?.Placement?.AvailabilityZone;
|
|
25443
25617
|
}
|
|
25444
|
-
if (value !== void 0 && value !== null && value !== "") {
|
|
25445
|
-
cachedEc2InstanceAttributes[cacheKey] = value;
|
|
25446
|
-
return value;
|
|
25447
|
-
}
|
|
25448
|
-
this.logger.warn(`DescribeInstances(${physicalId}) returned no ${this.maskSecretsForLog(attributeName, context)}; returning physical ID`);
|
|
25449
25618
|
} catch (err) {
|
|
25450
|
-
this.
|
|
25619
|
+
this.refuseUnservedAttribute({
|
|
25620
|
+
logicalId,
|
|
25621
|
+
attributeName,
|
|
25622
|
+
resourceType,
|
|
25623
|
+
physicalId,
|
|
25624
|
+
context,
|
|
25625
|
+
observed: this.describeFailureObserved("DescribeInstances", err, context),
|
|
25626
|
+
remedy: "Fix the read (the ec2:DescribeInstances permission, or the region) and deploy again."
|
|
25627
|
+
});
|
|
25451
25628
|
}
|
|
25452
|
-
|
|
25629
|
+
if (value !== void 0 && value !== null && value !== "") {
|
|
25630
|
+
cachedEc2InstanceAttributes[cacheKey] = value;
|
|
25631
|
+
return value;
|
|
25632
|
+
}
|
|
25633
|
+
const settled = isSettledInstanceState(stateName);
|
|
25634
|
+
if (settled && (attributeName === "PublicIp" || attributeName === "PublicDnsName")) {
|
|
25635
|
+
cachedEc2InstanceAttributes[cacheKey] = "";
|
|
25636
|
+
return "";
|
|
25637
|
+
}
|
|
25638
|
+
const observedState = stateName === void 0 ? "no instance state" : `state ${stateName}`;
|
|
25639
|
+
this.refuseUnservedAttribute({
|
|
25640
|
+
logicalId,
|
|
25641
|
+
attributeName,
|
|
25642
|
+
resourceType,
|
|
25643
|
+
physicalId,
|
|
25644
|
+
context,
|
|
25645
|
+
observed: `DescribeInstances reports no ${this.maskSecretsForLog(attributeName, context)} yet (${observedState})`,
|
|
25646
|
+
remedy: settled ? "Check the instance in the console; a terminated or stopped instance has no such attribute to serve." : "Deploy without --no-wait so the instance is running before its attributes are read, or reference the attribute from a later deploy once it is."
|
|
25647
|
+
});
|
|
25453
25648
|
}
|
|
25454
25649
|
default: return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
|
|
25455
25650
|
}
|
|
@@ -25467,9 +25662,115 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
25467
25662
|
if (attributeName === "LaunchTemplateId") return physicalId;
|
|
25468
25663
|
return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
|
|
25469
25664
|
}
|
|
25665
|
+
if (resourceType === "AWS::CloudFront::Distribution") switch (attributeName) {
|
|
25666
|
+
case "Id": return physicalId;
|
|
25667
|
+
case "DomainName": {
|
|
25668
|
+
if (!/^[A-Z0-9]+$/.test(physicalId)) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, DomainName] for AWS::CloudFront::Distribution: the state record's physical id "${displaySafe(this.maskThenStripThenMask(physicalId, context)).slice(0, 64)}" is not a distribution id (upper-case alphanumerics), so cdkd will not look it up. Repair the record (cdkd import, or re-create the distribution) and deploy again.`));
|
|
25669
|
+
const cachedDomainName = cachedCloudFrontDomainNames[physicalId];
|
|
25670
|
+
if (cachedDomainName !== void 0) return cachedDomainName;
|
|
25671
|
+
let domainName;
|
|
25672
|
+
try {
|
|
25673
|
+
domainName = (await this.clientsForRegion(this.explicitRegion).cloudFront.send(new GetDistributionCommand({ Id: physicalId }))).Distribution?.DomainName;
|
|
25674
|
+
} catch (err) {
|
|
25675
|
+
this.refuseUnservedAttribute({
|
|
25676
|
+
logicalId,
|
|
25677
|
+
attributeName,
|
|
25678
|
+
resourceType,
|
|
25679
|
+
physicalId,
|
|
25680
|
+
context,
|
|
25681
|
+
observed: this.describeFailureObserved("GetDistribution", err, context),
|
|
25682
|
+
remedy: "Fix the read (the cloudfront:GetDistribution permission) and deploy again."
|
|
25683
|
+
});
|
|
25684
|
+
}
|
|
25685
|
+
if (domainName) {
|
|
25686
|
+
cachedCloudFrontDomainNames[physicalId] = domainName;
|
|
25687
|
+
return domainName;
|
|
25688
|
+
}
|
|
25689
|
+
this.refuseUnservedAttribute({
|
|
25690
|
+
logicalId,
|
|
25691
|
+
attributeName,
|
|
25692
|
+
resourceType,
|
|
25693
|
+
physicalId,
|
|
25694
|
+
context,
|
|
25695
|
+
observed: "GetDistribution reports no DomainName",
|
|
25696
|
+
remedy: "Check the distribution in the console, or reference the attribute from a later deploy."
|
|
25697
|
+
});
|
|
25698
|
+
}
|
|
25699
|
+
default: return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
|
|
25700
|
+
}
|
|
25701
|
+
if (resourceType === "AWS::RDS::DBProxy" || resourceType === "AWS::RDS::DBProxyEndpoint") {
|
|
25702
|
+
if (attributeName === "VpcId") throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.maskSecretsForLog(attributeName, context)}] for ${resourceType}: the state record holds no VpcId for it (the read-back that would have recorded it reported none), and the physical id "${displaySafe(this.maskThenStripThenMask(physicalId, context))}" is a name, not a VPC id, so cdkd refuses to substitute it. Update the resource so its next deploy records the value, or reference the VPC directly.`));
|
|
25703
|
+
return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
|
|
25704
|
+
}
|
|
25470
25705
|
return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
|
|
25471
25706
|
}
|
|
25472
25707
|
/**
|
|
25708
|
+
* Refuse an attribute the state record omits and a live read could not
|
|
25709
|
+
* serve (issue [#3096](https://github.com/go-to-k/cdkd/issues/3096)).
|
|
25710
|
+
*
|
|
25711
|
+
* The arms that call this all share one shape: the provider recorded no
|
|
25712
|
+
* value because AWS had not assigned one yet or the read-back failed (issue
|
|
25713
|
+
* #3077 made the key ABSENT rather than `''`), the resolver re-reads it
|
|
25714
|
+
* live, and the read finds nothing — or fails. Before #3096 every such arm
|
|
25715
|
+
* answered the PHYSICAL ID: an `i-...` where an IP address or a DNS name
|
|
25716
|
+
* belongs, a `vpc-...` in a security-group slot, a distribution id where a
|
|
25717
|
+
* hostname belongs, a proxy name in a `VpcId` position. Loud only where AWS
|
|
25718
|
+
* rejects the shape; an Output, an export or a free-text property carried it
|
|
25719
|
+
* silently. The physical id can never be the value for these attributes,
|
|
25720
|
+
* so the honest answer is a refusal naming the resource, the attribute, what
|
|
25721
|
+
* was observed and the remedy.
|
|
25722
|
+
*
|
|
25723
|
+
* DELIBERATELY NOT `markNonRetryable`, unlike every other refusal in this
|
|
25724
|
+
* file but the fabricated-account guard: the verdict is TIME-DEPENDENT. A
|
|
25725
|
+
* `pending` instance settles seconds later, and a failed describe can
|
|
25726
|
+
* succeed on the next attempt, so cdkd must not DECLARE it terminal — the
|
|
25727
|
+
* marker is that declaration, and `withRetry` honours it ahead of every
|
|
25728
|
+
* classifier. Leaving it off promises no retry: `isRetryableTransientError`
|
|
25729
|
+
* reads throttle names and HTTP statuses off the `cause` chain — which this
|
|
25730
|
+
* refusal deliberately does not carry, see below — and everything else by
|
|
25731
|
+
* message substring, and this message matches nothing on its own. So the
|
|
25732
|
+
* nested-stack replay (`NestedStackProvider.create` runs the child deploy
|
|
25733
|
+
* inside the parent's `withRetry`) re-runs the child only when an
|
|
25734
|
+
* interpolated hole happens to carry a pattern — the `logicalId` (the #1838
|
|
25735
|
+
* hazard: `DependencyViolation` and two other bare words), or the error
|
|
25736
|
+
* CLASS name `describeFailureObserved` puts in `observed`, which none of
|
|
25737
|
+
* these three reads raises — and then re-describes and can heal.
|
|
25738
|
+
* Measured, not designed: the fabricated-account guard sits in exactly the
|
|
25739
|
+
* same place. Threading a sanitized SDK error as `cause` so a throttled
|
|
25740
|
+
* describe classifies as transient was considered and left out — a
|
|
25741
|
+
* `CdkdError`'s `cause` is rendered at default verbosity by `formatError`
|
|
25742
|
+
* (`Caused by:`), so the clone would need its own fence. What the
|
|
25743
|
+
* refusal does at each consumer: a resource property fails the resource; a
|
|
25744
|
+
* stack Output is caught per-output by `DeployEngine.
|
|
25745
|
+
* handleOutputResolutionFailure`, skipped and recorded in `skippedOutputs`
|
|
25746
|
+
* (#2740), then re-resolved on the next deploy; an `Fn::Sub` re-raises it
|
|
25747
|
+
* (#1740); a `Conditions` entry still absorbs it to `false`
|
|
25748
|
+
* (`evaluateConditions`).
|
|
25749
|
+
*
|
|
25750
|
+
* `observed` and `remedy` are cdkd-authored sentences built by the caller
|
|
25751
|
+
* from an instance STATE name, an error CLASS name or a fixed phrase — never
|
|
25752
|
+
* AWS's raw message, which `describeFailureObserved` keeps at debug (the
|
|
25753
|
+
* per-reader rule in `.claude/rules/provider-resource-identity.md`: a
|
|
25754
|
+
* denied describe quotes the caller's account, role and session).
|
|
25755
|
+
*/
|
|
25756
|
+
refuseUnservedAttribute(site) {
|
|
25757
|
+
const { logicalId, attributeName, resourceType, physicalId, context, observed, remedy } = site;
|
|
25758
|
+
throw new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.maskSecretsForLog(attributeName, context)}] for ${resourceType}: ${observed}. The physical id "${displaySafe(this.maskThenStripThenMask(physicalId, context))}" is not a usable ${this.maskSecretsForLog(attributeName, context)}, so cdkd refuses to substitute it. ${remedy}`);
|
|
25759
|
+
}
|
|
25760
|
+
/**
|
|
25761
|
+
* The `observed` clause of {@link refuseUnservedAttribute} for a live read
|
|
25762
|
+
* that THREW: names the read and the error's CLASS at default verbosity and
|
|
25763
|
+
* puts AWS's own text behind `--verbose`, since a denied describe quotes the
|
|
25764
|
+
* caller's account, role and session into its message.
|
|
25765
|
+
*/
|
|
25766
|
+
describeFailureObserved(read, err, context) {
|
|
25767
|
+
const name = displaySafe(err instanceof Error && err.name ? err.name : "Error", { asciiOnly: true }).slice(0, 64).trim() || "Error";
|
|
25768
|
+
const status = err?.$metadata?.httpStatusCode;
|
|
25769
|
+
const errorClass = typeof status === "number" && Number.isInteger(status) ? `${name}, HTTP ${status}` : name;
|
|
25770
|
+
this.logger.debug(`${read} failed (${errorClass}): ${this.maskSecretsForLog(err instanceof Error ? err.message : String(err), context)}`);
|
|
25771
|
+
return `${read} failed (${errorClass}); re-run with --verbose for the AWS error text`;
|
|
25772
|
+
}
|
|
25773
|
+
/**
|
|
25473
25774
|
* Shared unknown-attribute physicalId fallback (issues #1106 / #1111).
|
|
25474
25775
|
*
|
|
25475
25776
|
* Applied by BOTH the final unknown-type fallback of
|
|
@@ -25501,9 +25802,9 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
25501
25802
|
const expectsUrlShape = attributeName.endsWith("Url") && !/^https?:\/\//.test(physicalId);
|
|
25502
25803
|
if (expectsArnShape || expectsUrlShape) {
|
|
25503
25804
|
const expectedShape = expectsArnShape ? "an ARN (arn:...)" : "a URL (http(s)://...)";
|
|
25504
|
-
throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.maskSecretsForLog(attributeName, context)}] for ${resourceType}: attributes are not enriched for this resource type, and the physical ID fallback "${this.
|
|
25805
|
+
throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.maskSecretsForLog(attributeName, context)}] for ${resourceType}: attributes are not enriched for this resource type, and the physical ID fallback "${displaySafe(this.maskThenStripThenMask(physicalId, context))}" is not ${expectedShape}. CloudFormation would return a different value here, so falling back to the physical ID would silently produce a wrong value (e.g. in stack Outputs). Avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${this.maskSecretsForLog(attributeName, context)}.`));
|
|
25505
25806
|
}
|
|
25506
|
-
if (this.strictGetAtt) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.maskSecretsForLog(attributeName, context)}] for ${resourceType}: attributes are not enriched for this resource type, and --strict-getatt rejects the physical ID fallback "${this.
|
|
25807
|
+
if (this.strictGetAtt) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.maskSecretsForLog(attributeName, context)}] for ${resourceType}: attributes are not enriched for this resource type, and --strict-getatt rejects the physical ID fallback "${displaySafe(this.maskThenStripThenMask(physicalId, context))}" (which may not be the value CloudFormation would return). Drop --strict-getatt to fall back with a warning, avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${this.maskSecretsForLog(attributeName, context)}.`));
|
|
25507
25808
|
this.physicalIdFallbackCount++;
|
|
25508
25809
|
this.logger.warn(`Unknown attribute ${this.maskSecretsForLog(attributeName, context)} for resource type ${resourceType}, returning physical ID`);
|
|
25509
25810
|
return physicalId;
|
|
@@ -25518,12 +25819,36 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
25518
25819
|
let values = rawValues;
|
|
25519
25820
|
if (!Array.isArray(values)) values = await this.resolveValue(values, context);
|
|
25520
25821
|
if (!Array.isArray(values)) throw new Error(`Fn::Join's second argument must be a list (an array literal or a list-returning intrinsic such as Fn::Cidr / Fn::GetAZs / Fn::Split / a Ref to a list-typed parameter — any List<...> type or CommaDelimitedList), but resolved to ${typeof values}`);
|
|
25521
|
-
|
|
25522
|
-
|
|
25523
|
-
|
|
25524
|
-
|
|
25525
|
-
|
|
25526
|
-
|
|
25822
|
+
const literalList = Array.isArray(rawValues);
|
|
25823
|
+
const parts = (await allSettledKeepingFirstRejection(values.map(async (v) => {
|
|
25824
|
+
if (typeof v === "string") return {
|
|
25825
|
+
...v.includes("{{resolve:") ? await this.resolveDynamicReferencesWithLogTwin(v, v, context) : {
|
|
25826
|
+
result: v,
|
|
25827
|
+
twin: v
|
|
25828
|
+
},
|
|
25829
|
+
product: !literalList
|
|
25830
|
+
};
|
|
25831
|
+
const raw = await this.resolveValue(v, context);
|
|
25832
|
+
const resolved = String(raw);
|
|
25833
|
+
return {
|
|
25834
|
+
result: resolved,
|
|
25835
|
+
twin: resolved,
|
|
25836
|
+
product: true,
|
|
25837
|
+
raw: { value: raw }
|
|
25838
|
+
};
|
|
25839
|
+
}), (pending) => this.warnAbandonedParts(pending))).map((part) => {
|
|
25840
|
+
if (!part.product) return part;
|
|
25841
|
+
if (part.raw) return {
|
|
25842
|
+
result: part.result,
|
|
25843
|
+
twin: this.productLogTwin(part.raw.value, context)
|
|
25844
|
+
};
|
|
25845
|
+
return this.logTwinOfProduct(part, context);
|
|
25846
|
+
});
|
|
25847
|
+
let result = parts.map((part) => part.result).join(delimiter);
|
|
25848
|
+
let twin = parts.map((part) => part.twin).join(delimiter);
|
|
25849
|
+
if (result.includes("{{resolve:")) ({result, twin} = await this.resolveDynamicReferencesWithLogTwin(result, twin, context));
|
|
25850
|
+
this.rememberLogTwin(context, result, twin);
|
|
25851
|
+
this.logger.debug(`Resolved Fn::Join: ${this.maskSecretsForLog(this.logTwinText(result, twin, context), context)}`);
|
|
25527
25852
|
return result;
|
|
25528
25853
|
}
|
|
25529
25854
|
/**
|
|
@@ -25666,11 +25991,19 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
25666
25991
|
async resolveSub(subArgs, context) {
|
|
25667
25992
|
let template;
|
|
25668
25993
|
const variables = Object.create(null);
|
|
25994
|
+
const variableTwins = Object.create(null);
|
|
25669
25995
|
if (Array.isArray(subArgs)) {
|
|
25670
25996
|
const [templateString, variableMap] = subArgs;
|
|
25671
25997
|
template = templateString;
|
|
25672
25998
|
if (typeof variableMap !== "object" || variableMap === null) throw markNonRetryable(/* @__PURE__ */ new Error(`Fn::Sub: the second element must be a variable map, got ${variableMap === null ? "null" : typeof variableMap}`));
|
|
25673
|
-
for (const [key, val] of Object.entries(variableMap))
|
|
25999
|
+
for (const [key, val] of Object.entries(variableMap)) if (typeof val === "string") {
|
|
26000
|
+
const resolved = val.includes("{{resolve:") ? await this.resolveDynamicReferencesWithLogTwin(val, val, context) : {
|
|
26001
|
+
result: val,
|
|
26002
|
+
twin: val
|
|
26003
|
+
};
|
|
26004
|
+
variables[key] = resolved.result;
|
|
26005
|
+
variableTwins[key] = resolved.twin;
|
|
26006
|
+
} else variables[key] = await this.resolveValue(val, context);
|
|
25674
26007
|
} else template = subArgs;
|
|
25675
26008
|
const replacements = [];
|
|
25676
26009
|
const matches = template.matchAll(/\$\{(!)?([^}]*)\}/g);
|
|
@@ -25678,31 +26011,39 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
25678
26011
|
const isEscaped = match[1] === "!";
|
|
25679
26012
|
const varNameStr = match[2];
|
|
25680
26013
|
if (isEscaped) {
|
|
26014
|
+
const escapedLiteral = `\${${varNameStr ?? ""}}`;
|
|
25681
26015
|
replacements.push({
|
|
25682
26016
|
match: match[0],
|
|
25683
|
-
replacement:
|
|
26017
|
+
replacement: escapedLiteral,
|
|
26018
|
+
twin: escapedLiteral
|
|
25684
26019
|
});
|
|
25685
26020
|
continue;
|
|
25686
26021
|
}
|
|
25687
26022
|
if (!varNameStr) {
|
|
25688
26023
|
replacements.push({
|
|
25689
26024
|
match: match[0],
|
|
25690
|
-
replacement: match[0]
|
|
26025
|
+
replacement: match[0],
|
|
26026
|
+
twin: match[0]
|
|
25691
26027
|
});
|
|
25692
26028
|
continue;
|
|
25693
26029
|
}
|
|
25694
26030
|
let replacement;
|
|
25695
|
-
|
|
25696
|
-
|
|
26031
|
+
let twinReplacement;
|
|
26032
|
+
if (varNameStr in variables) {
|
|
26033
|
+
replacement = String(variables[varNameStr]);
|
|
26034
|
+
twinReplacement = varNameStr in variableTwins ? variableTwins[varNameStr] : this.productLogTwin(variables[varNameStr], context);
|
|
26035
|
+
} else {
|
|
25697
26036
|
const pseudoValue = await this.resolvePseudoParameter(varNameStr, context);
|
|
25698
26037
|
if (pseudoValue !== void 0) replacement = String(pseudoValue);
|
|
25699
26038
|
else try {
|
|
25700
26039
|
const value = await this.resolveRef(varNameStr, context);
|
|
25701
26040
|
replacement = String(value);
|
|
26041
|
+
twinReplacement = this.productLogTwin(value, context);
|
|
25702
26042
|
} catch (refError) {
|
|
25703
26043
|
if (varNameStr.includes(".")) try {
|
|
25704
26044
|
const value = await this.resolveGetAtt(varNameStr, context);
|
|
25705
26045
|
replacement = String(value);
|
|
26046
|
+
twinReplacement = this.productLogTwin(value, context);
|
|
25706
26047
|
} catch (getAttError) {
|
|
25707
26048
|
if (getAttError instanceof IntrinsicResolutionRefusalError) throw getAttError;
|
|
25708
26049
|
this.rethrowStructuralSubFailure(varNameStr, getAttError, context);
|
|
@@ -25719,7 +26060,8 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
25719
26060
|
}
|
|
25720
26061
|
replacements.push({
|
|
25721
26062
|
match: match[0],
|
|
25722
|
-
replacement
|
|
26063
|
+
replacement,
|
|
26064
|
+
twin: twinReplacement ?? replacement
|
|
25723
26065
|
});
|
|
25724
26066
|
}
|
|
25725
26067
|
let cursor = 0;
|
|
@@ -25727,8 +26069,14 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
25727
26069
|
const entry = replacements[cursor++];
|
|
25728
26070
|
return entry ? entry.replacement : whole;
|
|
25729
26071
|
});
|
|
25730
|
-
|
|
25731
|
-
|
|
26072
|
+
let twinCursor = 0;
|
|
26073
|
+
let twin = template.replace(/\$\{(!)?([^}]*)\}/g, (whole) => {
|
|
26074
|
+
const entry = replacements[twinCursor++];
|
|
26075
|
+
return entry ? entry.twin : whole;
|
|
26076
|
+
});
|
|
26077
|
+
if (result.includes("{{resolve:")) ({result, twin} = await this.resolveDynamicReferencesWithLogTwin(result, twin, context));
|
|
26078
|
+
this.rememberLogTwin(context, result, twin);
|
|
26079
|
+
this.logger.debug(`Resolved Fn::Sub: ${this.maskSecretsForLog(this.logTwinText(result, twin, context), context)}`);
|
|
25732
26080
|
return result;
|
|
25733
26081
|
}
|
|
25734
26082
|
/**
|
|
@@ -25890,7 +26238,8 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
25890
26238
|
throw markNonRetryable(new IntrinsicResolutionRefusalError(`Fn::Split: the value to split${sourceClause} must be a string, got ${resolvedValue === null ? "null" : typeof resolvedValue}. Fn::Split accepts only a string; check the value or the intrinsic that produced it.`));
|
|
25891
26239
|
}
|
|
25892
26240
|
const result = resolvedValue.split(delimiter);
|
|
25893
|
-
this.
|
|
26241
|
+
const pieceTwins = this.splitLogTwins(resolvedValue, delimiter, result, context);
|
|
26242
|
+
this.logger.debug(`Resolved Fn::Split: split by "${delimiter}" -> ${JSON.stringify(this.maskValueLeaves(pieceTwins, context))}`);
|
|
25894
26243
|
return result;
|
|
25895
26244
|
}
|
|
25896
26245
|
/**
|
|
@@ -26651,8 +27000,9 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26651
27000
|
const resolvedValue = await this.resolveValue(value, context);
|
|
26652
27001
|
if (typeof resolvedValue !== "string") throw new Error(`Fn::Base64: value must resolve to a string, got ${typeof resolvedValue}`);
|
|
26653
27002
|
const result = Buffer.from(resolvedValue).toString("base64");
|
|
26654
|
-
|
|
26655
|
-
|
|
27003
|
+
const inputLogText = this.logTextOfLeaf(resolvedValue, context);
|
|
27004
|
+
if (context.recordedSecretValues && (inputLogText !== resolvedValue || this.maskSecretsForLog(resolvedValue, context) !== resolvedValue)) recordMaskOnlyValue(context.recordedSecretValues, result);
|
|
27005
|
+
this.logger.debug(`Resolved Fn::Base64: ${this.maskSecretsForLog(inputLogText, context)} -> ${this.maskSecretsForLog(inputLogText !== resolvedValue ? "***" : result, context)}`);
|
|
26656
27006
|
return result;
|
|
26657
27007
|
}
|
|
26658
27008
|
/**
|
|
@@ -26757,6 +27107,155 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26757
27107
|
return masked;
|
|
26758
27108
|
}
|
|
26759
27109
|
/**
|
|
27110
|
+
* Is `value` WHOLE a secret either bag holds (issue #3100)? Asked of a
|
|
27111
|
+
* resolution PRODUCT only — never of a template literal — so an exact match
|
|
27112
|
+
* at any length is a verdict about what was written, not a floorless needle.
|
|
27113
|
+
*/
|
|
27114
|
+
isRecordedSecretForLog(value, context) {
|
|
27115
|
+
return context?.recordedSecretValues?.has(value) === true || context?.inheritedSecrets?.has(value) === true;
|
|
27116
|
+
}
|
|
27117
|
+
/**
|
|
27118
|
+
* The text a `Resolved Fn::Join:` / `Resolved Fn::Sub:` line prints (issue
|
|
27119
|
+
* #3100): the twin through the needle mask — unless the needle mask ALSO
|
|
27120
|
+
* fires on the value itself, in which case the whole line is masked. The
|
|
27121
|
+
* twin splits the text at the spans it masked, so a 4+ character recorded
|
|
27122
|
+
* secret that overlaps one of them (or IS the whole value) is no longer a
|
|
27123
|
+
* contiguous needle in the twin, and masking the twin alone would print the
|
|
27124
|
+
* part of it outside the span. The value's own needle mask cannot be merged
|
|
27125
|
+
* with the twin's positions, so the line gives both up for `***`: never
|
|
27126
|
+
* more text than either mask alone would print. Returns the text BEFORE the
|
|
27127
|
+
* needle mask; each log line wraps it in `maskSecretsForLog` itself, so the
|
|
27128
|
+
* resolver's mask-coverage checker sees a masker at the site.
|
|
27129
|
+
*/
|
|
27130
|
+
logTwinText(result, twin, context) {
|
|
27131
|
+
return twin !== result && this.maskSecretsForLog(result, context) !== result ? "***" : twin;
|
|
27132
|
+
}
|
|
27133
|
+
/**
|
|
27134
|
+
* The log twins of `Fn::Split`'s pieces (issue #3100), each registered for
|
|
27135
|
+
* the pass so an outer `Fn::Join` over the pieces keeps the mask. A source
|
|
27136
|
+
* that is itself a recorded or inherited secret counts as a twin of `***`;
|
|
27137
|
+
* a source with neither is its pieces' own twin.
|
|
27138
|
+
*
|
|
27139
|
+
* The source's twin is split by the same delimiter. When both splits give
|
|
27140
|
+
* the same number of pieces they are paired by position; when they do not —
|
|
27141
|
+
* a secret carrying the delimiter, or a delimiter that occurs in the mask
|
|
27142
|
+
* itself — no pairing can be trusted, so every piece is masked whole. Every
|
|
27143
|
+
* masked piece is registered by VALUE, so an equal string elsewhere in the
|
|
27144
|
+
* pass is masked too: the over-masking direction `logTwinOfProduct` accepts.
|
|
27145
|
+
*/
|
|
27146
|
+
splitLogTwins(value, delimiter, pieces, context) {
|
|
27147
|
+
const bag = this.logTwinBag(context);
|
|
27148
|
+
const sourceTwin = this.isRecordedSecretForLog(value, context) ? "***" : bag ? LOG_TWINS_BY_PASS.get(bag)?.get(value) : void 0;
|
|
27149
|
+
if (sourceTwin === void 0) return pieces;
|
|
27150
|
+
const twinPieces = sourceTwin.split(delimiter);
|
|
27151
|
+
const aligned = twinPieces.length === pieces.length && !"***".includes(delimiter);
|
|
27152
|
+
return pieces.map((piece, index) => {
|
|
27153
|
+
const twin = this.logTwinText(piece, aligned ? twinPieces[index] ?? "***" : "***", context);
|
|
27154
|
+
this.rememberLogTwin(context, piece, twin);
|
|
27155
|
+
return twin;
|
|
27156
|
+
});
|
|
27157
|
+
}
|
|
27158
|
+
/**
|
|
27159
|
+
* The log twin of a resolution product as `String(value)` renders it (issue
|
|
27160
|
+
* #3100). A LIST is stringified element by element exactly as
|
|
27161
|
+
* `Array.prototype.join` does (`null` / `undefined` as the empty string,
|
|
27162
|
+
* nested lists recursively, comma-separated), so each element keeps its own
|
|
27163
|
+
* twin: `String()` over the whole list produced a string no registered twin
|
|
27164
|
+
* matched. No cycle guard: a product is JSON-sourced state or a fresh
|
|
27165
|
+
* resolution result, and neither can hold a self-referencing list.
|
|
27166
|
+
*/
|
|
27167
|
+
productLogTwin(value, context) {
|
|
27168
|
+
if (Array.isArray(value)) return value.map((element) => element === null || element === void 0 ? "" : this.productLogTwin(element, context)).join(",");
|
|
27169
|
+
const text = String(value);
|
|
27170
|
+
return this.logTwinOfProduct({
|
|
27171
|
+
result: text,
|
|
27172
|
+
twin: text
|
|
27173
|
+
}, context).twin;
|
|
27174
|
+
}
|
|
27175
|
+
/**
|
|
27176
|
+
* The pre-mask log text of a string LEAF a debug line prints (issue #3100):
|
|
27177
|
+
* the leaf treated as a resolution product — masked whole when it is a
|
|
27178
|
+
* recorded secret, otherwise the masked twin this pass registered for it —
|
|
27179
|
+
* and then `logTwinText`'s whole-line guard. The caller still wraps the
|
|
27180
|
+
* result in `maskSecretsForLog`. Shared by every caller of `maskValueLeaves`
|
|
27181
|
+
* (the `Fn::Select`, `Fn::Split`, `Fn::Equals`, `Fn::FindInMap`,
|
|
27182
|
+
* `Fn::GetAZs` and `Fn::Cidr` lines and the VPC `Ipv6CidrBlocks` line among
|
|
27183
|
+
* them) and by the `Fn::Base64` line, so a string one
|
|
27184
|
+
* write already masked by position is not printed in the clear by the next
|
|
27185
|
+
* intrinsic that logs it. `maskValueLeaves` also masks values interpolated
|
|
27186
|
+
* into THROWN messages (the `Fn::Cidr` argument refusals, for one), so those
|
|
27187
|
+
* messages take the same position mask: a short secret an earlier write put
|
|
27188
|
+
* into the value is `***` there too, the direction the mask at the throw
|
|
27189
|
+
* already takes for a whole secret.
|
|
27190
|
+
*/
|
|
27191
|
+
logTextOfLeaf(value, context) {
|
|
27192
|
+
return this.logTwinText(value, this.logTwinOfProduct({
|
|
27193
|
+
result: value,
|
|
27194
|
+
twin: value
|
|
27195
|
+
}, context).twin, context);
|
|
27196
|
+
}
|
|
27197
|
+
/** The bag a pass's log twins are keyed by (issue #3100): the recorded one, else the inherited one. */
|
|
27198
|
+
logTwinBag(context) {
|
|
27199
|
+
return context?.recordedSecretValues ?? context?.inheritedSecrets;
|
|
27200
|
+
}
|
|
27201
|
+
/**
|
|
27202
|
+
* The log twin of a RESOLUTION PRODUCT — an intrinsic part, variable or
|
|
27203
|
+
* placeholder — for issue #3100. Masked whole when its value is a recorded
|
|
27204
|
+
* secret. Otherwise a part that carries no mask of its own takes the masked
|
|
27205
|
+
* twin an earlier write of this pass registered for that exact string
|
|
27206
|
+
* (`rememberLogTwin`) — how a Join part that is itself an `Fn::Sub`, or a
|
|
27207
|
+
* `Fn::Select` over a reference-bearing string, keeps the inner mask. A part
|
|
27208
|
+
* whose OWN masked twin disagrees with the registered one (a list element
|
|
27209
|
+
* still spelling a reference) is masked whole, since the two span sets
|
|
27210
|
+
* cannot be merged; one that agrees keeps its twin.
|
|
27211
|
+
*
|
|
27212
|
+
* The registry is keyed by VALUE, so a product equal to a registered string
|
|
27213
|
+
* from another provenance takes its mask too. That over-masks a line whose
|
|
27214
|
+
* text holds a recorded secret at a position some write in this pass put
|
|
27215
|
+
* one. The registry holds masked twins only, so a lookup never unmasks.
|
|
27216
|
+
*
|
|
27217
|
+
* The whole-secret check reads the bags as they stand when the product is
|
|
27218
|
+
* placed, before the enclosing Join / Sub runs its final substitution. A
|
|
27219
|
+
* secret that substitution records is therefore not matched against an
|
|
27220
|
+
* earlier product equal to it. Such a product did not come from that secret:
|
|
27221
|
+
* a parameter holding one arrives in the inherited bag before its `Ref`
|
|
27222
|
+
* resolves, so what stays printed is a public value that coincides with it.
|
|
27223
|
+
*/
|
|
27224
|
+
logTwinOfProduct(part, context) {
|
|
27225
|
+
if (this.isRecordedSecretForLog(part.result, context)) return {
|
|
27226
|
+
result: part.result,
|
|
27227
|
+
twin: "***"
|
|
27228
|
+
};
|
|
27229
|
+
const bag = this.logTwinBag(context);
|
|
27230
|
+
const registered = bag ? LOG_TWINS_BY_PASS.get(bag)?.get(part.result) : void 0;
|
|
27231
|
+
if (registered === void 0 || registered === part.twin) return part;
|
|
27232
|
+
return {
|
|
27233
|
+
result: part.result,
|
|
27234
|
+
twin: part.twin === part.result ? registered : "***"
|
|
27235
|
+
};
|
|
27236
|
+
}
|
|
27237
|
+
/**
|
|
27238
|
+
* Register a MASKED `twin` as the log twin of `result` for this pass (issue
|
|
27239
|
+
* #3100). An unmasked twin is not registered, so a later literal that
|
|
27240
|
+
* resolves to the same string cannot replace an earlier write's mask, and
|
|
27241
|
+
* two DIFFERENT masked twins for one string register `***` for the whole
|
|
27242
|
+
* string, since their spans cannot be merged. Scoped to the pass's bag
|
|
27243
|
+
* through a `WeakMap`, like the cross-stack associations, so it dies with
|
|
27244
|
+
* the bag and another pass's strings cannot be reached.
|
|
27245
|
+
*/
|
|
27246
|
+
rememberLogTwin(context, result, twin) {
|
|
27247
|
+
if (twin === result) return;
|
|
27248
|
+
const bag = this.logTwinBag(context);
|
|
27249
|
+
if (!bag) return;
|
|
27250
|
+
let twins = LOG_TWINS_BY_PASS.get(bag);
|
|
27251
|
+
if (!twins) {
|
|
27252
|
+
twins = /* @__PURE__ */ new Map();
|
|
27253
|
+
LOG_TWINS_BY_PASS.set(bag, twins);
|
|
27254
|
+
}
|
|
27255
|
+
const existing = twins.get(result);
|
|
27256
|
+
twins.set(result, existing === void 0 || existing === twin ? twin : "***");
|
|
27257
|
+
}
|
|
27258
|
+
/**
|
|
26760
27259
|
* A copy of `value` with every string LEAF (and every object KEY) masked, for
|
|
26761
27260
|
* a caller about to ENCODE it into a message (issue
|
|
26762
27261
|
* [#2759](https://github.com/go-to-k/cdkd/issues/2759)).
|
|
@@ -26788,7 +27287,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26788
27287
|
maskValueLeaves(value, context) {
|
|
26789
27288
|
const done = /* @__PURE__ */ new Map();
|
|
26790
27289
|
const walk = (node) => {
|
|
26791
|
-
if (typeof node === "string") return this.maskSecretsForLog(node, context);
|
|
27290
|
+
if (typeof node === "string") return this.maskSecretsForLog(this.logTextOfLeaf(node, context), context);
|
|
26792
27291
|
if (node === null || typeof node !== "object") return node;
|
|
26793
27292
|
const memo = done.get(node);
|
|
26794
27293
|
if (memo !== void 0) return memo;
|
|
@@ -26800,7 +27299,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26800
27299
|
}
|
|
26801
27300
|
const out = Object.create(null);
|
|
26802
27301
|
done.set(node, out);
|
|
26803
|
-
for (const [key, child] of Object.entries(node)) out[this.maskSecretsForLog(key, context)] = walk(child);
|
|
27302
|
+
for (const [key, child] of Object.entries(node)) out[this.maskSecretsForLog(this.logTextOfLeaf(key, context), context)] = walk(child);
|
|
26804
27303
|
return out;
|
|
26805
27304
|
};
|
|
26806
27305
|
return walk(value);
|
|
@@ -26846,8 +27345,32 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26846
27345
|
return this.maskSecretsForLog(stripControlChars(this.maskSecretsForLog(value, context)), context);
|
|
26847
27346
|
}
|
|
26848
27347
|
async resolveDynamicReferences(value, context) {
|
|
27348
|
+
return (await this.resolveDynamicReferencesWithLogTwin(value, value, context)).result;
|
|
27349
|
+
}
|
|
27350
|
+
/**
|
|
27351
|
+
* {@link resolveDynamicReferences}, also returning the LOG TWIN of the result
|
|
27352
|
+
* (issue [#3100](https://github.com/go-to-k/cdkd/issues/3100), see
|
|
27353
|
+
* `LogTwin`). `logTwin` is the caller's twin of `value`, which may already
|
|
27354
|
+
* mask spans an earlier write put there.
|
|
27355
|
+
*
|
|
27356
|
+
* Every arm that writes into `result` writes into the twin too, replacing
|
|
27357
|
+
* the SAME token: a secret verdict writes {@link SECRET_MASK}, anything else
|
|
27358
|
+
* the value itself. The verdict is the one each arm already holds — the
|
|
27359
|
+
* fresh lookup's `isSecret`, the cache entry's own `secret` (never the
|
|
27360
|
+
* process-wide store, for the #1933 reason the cache-hit arm states), and
|
|
27361
|
+
* for a region-pinned sibling the twin the sibling itself returns. An arm
|
|
27362
|
+
* that `continue`s without writing leaves the token in both.
|
|
27363
|
+
*
|
|
27364
|
+
* A token whose text an earlier write masked (`{{resolve:${Pw}}}` over a
|
|
27365
|
+
* secret `Pw`) is not found in the twin, so the twin keeps the masked token
|
|
27366
|
+
* where the value holds its resolution. What the twin cannot carry is a
|
|
27367
|
+
* NEEDLE match that straddles a masked span, which is why the log lines go
|
|
27368
|
+
* through `logTwinText` rather than masking the twin alone.
|
|
27369
|
+
*/
|
|
27370
|
+
async resolveDynamicReferencesWithLogTwin(value, logTwin, context) {
|
|
26849
27371
|
const pattern = /\{\{resolve:([^}]+)\}\}/g;
|
|
26850
27372
|
let result = value;
|
|
27373
|
+
let twin = logTwin;
|
|
26851
27374
|
let match;
|
|
26852
27375
|
const matches = [];
|
|
26853
27376
|
while ((match = pattern.exec(value)) !== null) matches.push({
|
|
@@ -26861,8 +27384,9 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26861
27384
|
const regionVerdict = classifyReplaySecretRegion(fullMatch, this.explicitRegion ?? this.resolverRegion, context?.producerRegions);
|
|
26862
27385
|
if (regionVerdict.kind === "ambiguous") throw markNonRetryable(new DynamicReferenceRegionAmbiguousError(`Refusing to resolve the secret reference ${this.maskSecretsForLog(fullMatch, context)}: it names '${this.maskSecretsForLog(regionVerdict.secretName, context)}' without a region, and this stack reads from ${regionVerdict.foreignProducerRegions.join(", ")} as well as its own region. cdkd cannot tell which one must answer, and resolving against the wrong one yields a different secret. Spell the reference as a full ARN to say which region owns it.`));
|
|
26863
27386
|
if (regionVerdict.kind === "named-region") {
|
|
26864
|
-
const foreign = await this.resolverForProducerRegion(regionVerdict.region).
|
|
26865
|
-
result = result.replace(fullMatch, () => foreign);
|
|
27387
|
+
const foreign = await this.resolverForProducerRegion(regionVerdict.region).resolveDynamicReferencesWithLogTwin(fullMatch, fullMatch, withoutProducerRegions(context));
|
|
27388
|
+
result = result.replace(fullMatch, () => foreign.result);
|
|
27389
|
+
twin = twin.replace(fullMatch, () => foreign.twin);
|
|
26866
27390
|
continue;
|
|
26867
27391
|
}
|
|
26868
27392
|
const cached = this.cachedDynamicReferences.get(fullMatch);
|
|
@@ -26872,6 +27396,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26872
27396
|
if (context?.recordedSecretValues) recordResolvedPair(context.recordedSecretValues, fullMatch, cached.value);
|
|
26873
27397
|
}
|
|
26874
27398
|
result = result.replace(fullMatch, () => cached.value);
|
|
27399
|
+
twin = twin.replace(fullMatch, () => cached.secret && cached.value ? "***" : cached.value);
|
|
26875
27400
|
continue;
|
|
26876
27401
|
}
|
|
26877
27402
|
const parts = inner.split(":");
|
|
@@ -26921,8 +27446,13 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
26921
27446
|
if (service === "secretsmanager" || service === "ssm-secure") this.pinSecretVerdict(fullMatch, true);
|
|
26922
27447
|
}
|
|
26923
27448
|
result = result.replace(fullMatch, () => resolved);
|
|
27449
|
+
twin = twin.replace(fullMatch, () => isSecret && resolved ? "***" : resolved);
|
|
26924
27450
|
}
|
|
26925
|
-
|
|
27451
|
+
if (context) this.rememberLogTwin(context, result, twin);
|
|
27452
|
+
return {
|
|
27453
|
+
result,
|
|
27454
|
+
twin
|
|
27455
|
+
};
|
|
26926
27456
|
}
|
|
26927
27457
|
/**
|
|
26928
27458
|
* Resolve a Secrets Manager dynamic reference
|
|
@@ -28486,7 +29016,7 @@ var CloudControlProvider = class {
|
|
|
28486
29016
|
const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
|
|
28487
29017
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
28488
29018
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
28489
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
29019
|
+
const { ASGProvider } = await import("./asg-provider-NffO_bpL.js").then((n) => n.n);
|
|
28490
29020
|
const asgProvider = new ASGProvider();
|
|
28491
29021
|
return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
|
|
28492
29022
|
}
|
|
@@ -33684,12 +34214,20 @@ const WARM_THROUGHPUT_MEMBERS = ["ReadUnitsPerSecond", "WriteUnitsPerSecond"];
|
|
|
33684
34214
|
*
|
|
33685
34215
|
* The accepted STRING set is `Number()`'s, which is WIDER than a decimal
|
|
33686
34216
|
* integer: `'0x1e'`, `'0o36'`, `'1e3'`, `'30.5'` and `' 30 '` all coerce.
|
|
33687
|
-
*
|
|
33688
|
-
*
|
|
33689
|
-
*
|
|
33690
|
-
*
|
|
33691
|
-
*
|
|
33692
|
-
*
|
|
34217
|
+
* CloudFormation accepts only the LAST of those for an `Integer`-typed
|
|
34218
|
+
* property — measured on `AWS::Logs::LogGroup.RetentionInDays`, issue
|
|
34219
|
+
* [#2698](https://github.com/go-to-k/cdkd/issues/2698) — and
|
|
34220
|
+
* {@link toCfnInteger} is the reader that matches that measurement. It matters
|
|
34221
|
+
* at the CALLERS that FORWARD the result to AWS rather than merely compare it:
|
|
34222
|
+
* the log-group provider switched; the DynamoDB capacity / throughput
|
|
34223
|
+
* forwarders (`coerceWarmThroughput` below, and the `GlobalTable` provider's
|
|
34224
|
+
* `ProvisionedThroughput` / `OnDemandThroughput` readers) still read through
|
|
34225
|
+
* THIS helper, because the measurement covers one resource handler and
|
|
34226
|
+
* DynamoDB's has not been A/B'd — issue
|
|
34227
|
+
* [#3135](https://github.com/go-to-k/cdkd/issues/3135) holds that pass. A
|
|
34228
|
+
* GUARD caller (`stateful-types.ts`'s `has-retention`, the DynamoDB
|
|
34229
|
+
* already-matches / decrease tests) is safe on the wider set: over-acceptance
|
|
34230
|
+
* there can only produce MORE refusals or skip fewer calls.
|
|
33693
34231
|
*
|
|
33694
34232
|
* EXPORTED, and not because warm throughput needs it exported. This exact rule
|
|
33695
34233
|
* was hand-written three times — here, as `dynamodb-table-provider.ts`'s
|
|
@@ -33710,6 +34248,59 @@ function toFiniteNumber(value) {
|
|
|
33710
34248
|
}
|
|
33711
34249
|
}
|
|
33712
34250
|
/**
|
|
34251
|
+
* A CloudFormation-borne `Integer` property, read the way CloudFormation reads
|
|
34252
|
+
* it, or `undefined` when CloudFormation would REJECT the value.
|
|
34253
|
+
*
|
|
34254
|
+
* The sibling of {@link toFiniteNumber} for the callers that FORWARD the number
|
|
34255
|
+
* to AWS. That helper is `Number()`, which accepts hex, octal, binary,
|
|
34256
|
+
* exponent and decimal-point spellings; CloudFormation does not, and a
|
|
34257
|
+
* provider forwarding `Number('0x1e')` sends `30` for a template CloudFormation
|
|
34258
|
+
* would have refused outright. Measured live on
|
|
34259
|
+
* `AWS::Logs::LogGroup.RetentionInDays` (us-east-1, 2026-09-14, issue
|
|
34260
|
+
* [#2698](https://github.com/go-to-k/cdkd/issues/2698); a one-resource stack
|
|
34261
|
+
* updated per spelling, live value read back with `logs describe-log-groups`):
|
|
34262
|
+
*
|
|
34263
|
+
* ```
|
|
34264
|
+
* template spelling CloudFormation live retention after
|
|
34265
|
+
* 30 (JSON number) accepted 30
|
|
34266
|
+
* "60" accepted 60
|
|
34267
|
+
* "+30" accepted 30
|
|
34268
|
+
* " 30 " (padded) accepted 30
|
|
34269
|
+
* " " (whitespace only) accepted, treated as ABSENT none (policy REMOVED)
|
|
34270
|
+
* "" (empty string) accepted, treated as ABSENT none (policy REMOVED)
|
|
34271
|
+
* property absent accepted none (policy REMOVED)
|
|
34272
|
+
* "0x1e" REJECTED (handler) unchanged
|
|
34273
|
+
* "1e3" REJECTED (handler) unchanged
|
|
34274
|
+
* "30.5" REJECTED (handler) unchanged
|
|
34275
|
+
* "30.0" REJECTED (handler) unchanged
|
|
34276
|
+
* "abc" REJECTED (handler) unchanged
|
|
34277
|
+
* true / false REJECTED (handler) unchanged
|
|
34278
|
+
* 0 / "0" REJECTED (handler: enum) unchanged
|
|
34279
|
+
* null REJECTED at update-stack unchanged
|
|
34280
|
+
* ```
|
|
34281
|
+
*
|
|
34282
|
+
* So the accepted string grammar is `optional sign + decimal digits`, with
|
|
34283
|
+
* surrounding whitespace TRIMMED — `/^\s*[+-]?\d+\s*$/`, then `Number()`. The
|
|
34284
|
+
* digits grammar is `config-shape.ts`'s {@link coerceCfnInteger}, reused rather
|
|
34285
|
+
* than respelled so the two cannot drift on it; the ONE difference is the
|
|
34286
|
+
* trim, and it is a measured difference, not a relaxation: that helper's own
|
|
34287
|
+
* measurement (a NESTED `GenerateSecretString.PasswordLength`, 2026-09-13)
|
|
34288
|
+
* saw `" 12 "` REJECTED where this one saw `" 30 "` accepted. Per-handler
|
|
34289
|
+
* behaviour varies on padding, so each helper carries its own measurement and
|
|
34290
|
+
* a caller picks by the property it forwards.
|
|
34291
|
+
*
|
|
34292
|
+
* What this helper does NOT decide: the empty / whitespace-only strings above
|
|
34293
|
+
* are CloudFormation's spelling of "property absent", which is a statement
|
|
34294
|
+
* about the PROPERTY, not about the number — they answer `undefined` here, and
|
|
34295
|
+
* the caller decides whether `undefined` means "absent" or "unusable" from the
|
|
34296
|
+
* raw value (`logs-loggroup-provider.ts`'s `assertUsableRetention` is the
|
|
34297
|
+
* reference). `0` / `'0'` parse to `0` here; whether zero is usable is again the
|
|
34298
|
+
* property's question (the log-group handler rejects it by enum).
|
|
34299
|
+
*/
|
|
34300
|
+
function toCfnInteger(value) {
|
|
34301
|
+
return coerceCfnInteger(typeof value === "string" ? value.trim() : value);
|
|
34302
|
+
}
|
|
34303
|
+
/**
|
|
33713
34304
|
* Coerce a CFn `WarmThroughput` block to the numeric shape the SDK's `Long`
|
|
33714
34305
|
* fields accept, PER MEMBER.
|
|
33715
34306
|
*
|
|
@@ -36008,6 +36599,45 @@ function effectiveProvisionedBy(record, fallbackProvisionedBy) {
|
|
|
36008
36599
|
return record?.provisionedBy ?? fallbackProvisionedBy;
|
|
36009
36600
|
}
|
|
36010
36601
|
/**
|
|
36602
|
+
* One spelling of "this value came from a rollback-journal record, and is
|
|
36603
|
+
* about to be interpolated into a message a terminal will render" (issue
|
|
36604
|
+
* #3092). The journal is a sibling of `state.json` in the same bucket,
|
|
36605
|
+
* writable by anyone with `s3:PutObject` and validated no more than an
|
|
36606
|
+
* unchecked cast; cdkd's output is line-oriented, so an injected newline
|
|
36607
|
+
* invents a line that reads like a real one. This executor runs under the
|
|
36608
|
+
* standalone `cdkd rollback` AND under the deploy engine's automatic
|
|
36609
|
+
* rollback, so its lines print on every failed deploy.
|
|
36610
|
+
*
|
|
36611
|
+
* `displayIdent`: the ASCII allowlist with `UNRENDERABLE` for a value that
|
|
36612
|
+
* sanitizes to nothing, a length cap, and a visible boundary (a JSON-quoted
|
|
36613
|
+
* rendering) for a value that is not a plain identifier -- the all-ASCII
|
|
36614
|
+
* `X (AWS::RDS::DBInstance) -- already reverted` the allowlist lets through.
|
|
36615
|
+
* A logical id, a CFn resource type and a change type all have known
|
|
36616
|
+
* charsets. Call it for those; `grep safe(` answers the scope.
|
|
36617
|
+
*
|
|
36618
|
+
* NOT for a value that is USED rather than shown -- the ids passed to a
|
|
36619
|
+
* provider call, the keys into `stateResources`, the `msg` a classifier reads.
|
|
36620
|
+
* NOT for the `reason` / `survivorReason` strings handed to `ctx.recordEvent`:
|
|
36621
|
+
* those are PERSISTED into `deployments/*.jsonl` raw and `cdkd events`
|
|
36622
|
+
* sanitizes them on the way out, so sanitizing here would put a display
|
|
36623
|
+
* transform on a stored value and double it at render (the go-to-k/cdkd#2170
|
|
36624
|
+
* direction). NOT for free-form error text either: an SDK message legitimately
|
|
36625
|
+
* carries non-ASCII, so a site rendering one calls `displaySafe()` directly and
|
|
36626
|
+
* takes the DENYLIST, as `formatError` does for a `cause`.
|
|
36627
|
+
*/
|
|
36628
|
+
function safe(value) {
|
|
36629
|
+
return displayIdent(value);
|
|
36630
|
+
}
|
|
36631
|
+
/**
|
|
36632
|
+
* The one shape of `op.logicalId` this executor will print INSIDE a command it
|
|
36633
|
+
* invites the user to paste (`cdkd rollback --orphan <id>`): CloudFormation's
|
|
36634
|
+
* own logical-id charset and length. Stricter than "`safe()` is the identity
|
|
36635
|
+
* on it" on purpose -- `~user` and `=x` are plain identifiers the shell
|
|
36636
|
+
* expands before cdkd sees them (issue #3092 review). Not a display rule: a
|
|
36637
|
+
* legitimate id the executor merely SHOWS still goes through `safe()`.
|
|
36638
|
+
*/
|
|
36639
|
+
const PASTEABLE_LOGICAL_ID = /^[A-Za-z0-9]{1,255}$/;
|
|
36640
|
+
/**
|
|
36011
36641
|
* `UpdateReplacePolicy: Snapshot` on a rollback's delete-of-the-NEW-resource
|
|
36012
36642
|
* (issue #1354): honor it where it costs nothing — an atomic-final-snapshot
|
|
36013
36643
|
* type on the SDK route gets a generated identifier threaded into the delete
|
|
@@ -36104,7 +36734,7 @@ function rollbackRetainsNewResource(record) {
|
|
|
36104
36734
|
*/
|
|
36105
36735
|
function retainedSurvivorMessages(logicalId, resourceType, survivorPhysicalId, stateClause) {
|
|
36106
36736
|
return {
|
|
36107
|
-
warn: ` ⚠ ${logicalId} (${resourceType}) has UpdateReplacePolicy: Retain — the replacement's new physical resource (${survivorPhysicalId}) is RETAINED by this rollback and is no longer tracked by cdkd: it keeps running and incurring cost, and \`cdkd destroy\` will not remove it. Delete it yourself once you no longer need it. ${stateClause}`,
|
|
36737
|
+
warn: ` ⚠ ${safe(logicalId)} (${safe(resourceType)}) has UpdateReplacePolicy: Retain — the replacement's new physical resource (${displaySafe(survivorPhysicalId)}) is RETAINED by this rollback and is no longer tracked by cdkd: it keeps running and incurring cost, and \`cdkd destroy\` will not remove it. Delete it yourself once you no longer need it. ${displaySafe(stateClause)}`,
|
|
36108
36738
|
reason: `UpdateReplacePolicy: Retain kept the replacement's new ${resourceType} (${survivorPhysicalId}); it is live, still billing, and no longer tracked by cdkd. ${stateClause}`
|
|
36109
36739
|
};
|
|
36110
36740
|
}
|
|
@@ -36655,7 +37285,7 @@ function redactRollbackRecord(record, secrets, journaledProps) {
|
|
|
36655
37285
|
}
|
|
36656
37286
|
async function updateWithRollbackRetry(provider, args, logicalId, logger, isInterrupted, secrets) {
|
|
36657
37287
|
if (provider.disableOuterRetry) return await withCurrentResourceSecrets(secrets, () => provider.update(...args));
|
|
36658
|
-
return await withRetry(() => withCurrentResourceSecrets(secrets, () => provider.update(...args)), logicalId, {
|
|
37288
|
+
return await withRetry(() => withCurrentResourceSecrets(secrets, () => provider.update(...args)), safe(logicalId), {
|
|
36659
37289
|
logger: maskingRetryLogger(logger, secrets),
|
|
36660
37290
|
...isInterrupted && {
|
|
36661
37291
|
isInterrupted,
|
|
@@ -36747,13 +37377,14 @@ async function updateWithRollbackRetry(provider, args, logicalId, logger, isInte
|
|
|
36747
37377
|
async function createWithRollbackRetry(provider, create, logicalId, logger, isInterrupted, secrets, outer) {
|
|
36748
37378
|
if (provider.disableOuterRetry) return await create();
|
|
36749
37379
|
const maskedLogger = maskingRetryLogger(logger, secrets);
|
|
36750
|
-
|
|
37380
|
+
const shownId = safe(logicalId);
|
|
37381
|
+
return await withRetry(() => withRetry(create, shownId, {
|
|
36751
37382
|
logger: maskedLogger,
|
|
36752
37383
|
...isInterrupted && {
|
|
36753
37384
|
isInterrupted,
|
|
36754
37385
|
onInterrupted: () => /* @__PURE__ */ new Error("Rollback interrupted while retrying the replay re-create")
|
|
36755
37386
|
}
|
|
36756
|
-
}),
|
|
37387
|
+
}), shownId, {
|
|
36757
37388
|
...RECREATE_RETRY_SCHEDULE,
|
|
36758
37389
|
logger: maskedLogger,
|
|
36759
37390
|
...isInterrupted && {
|
|
@@ -36836,18 +37467,18 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
36836
37467
|
try {
|
|
36837
37468
|
switch (action) {
|
|
36838
37469
|
case "unrecoverable-delete":
|
|
36839
|
-
logger.warn(` Rollback: Cannot restore deleted resource ${op.logicalId} (${op.resourceType}) — resource has already been deleted`);
|
|
37470
|
+
logger.warn(` Rollback: Cannot restore deleted resource ${safe(op.logicalId)} (${safe(op.resourceType)}) — resource has already been deleted`);
|
|
36840
37471
|
result.warnings++;
|
|
36841
37472
|
return;
|
|
36842
37473
|
case "skip-already-done":
|
|
36843
|
-
logger.debug(` Rollback: ${op.logicalId} already reverted, skipping`);
|
|
37474
|
+
logger.debug(` Rollback: ${safe(op.logicalId)} already reverted, skipping`);
|
|
36844
37475
|
return;
|
|
36845
37476
|
case "skip-mismatch":
|
|
36846
|
-
logger.warn(` Rollback: Skipping ${op.logicalId} — its physical id changed since the failed deploy (replaced by a later attempt); manual attention may be required`);
|
|
37477
|
+
logger.warn(` Rollback: Skipping ${safe(op.logicalId)} — its physical id changed since the failed deploy (replaced by a later attempt); manual attention may be required`);
|
|
36847
37478
|
result.warnings++;
|
|
36848
37479
|
return;
|
|
36849
37480
|
case "skip-absent":
|
|
36850
|
-
logger.warn(` Rollback: Cannot restore ${op.logicalId} — resource no longer in state, skipping`);
|
|
37481
|
+
logger.warn(` Rollback: Cannot restore ${safe(op.logicalId)} — resource no longer in state, skipping`);
|
|
36851
37482
|
result.warnings++;
|
|
36852
37483
|
return;
|
|
36853
37484
|
case "orphan-flag":
|
|
@@ -36856,7 +37487,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
36856
37487
|
const orphanFlagProvisionedBy = effectiveProvisionedBy(record, op.provisionedBy);
|
|
36857
37488
|
createRollbackRoute = orphanFlagProvisionedBy;
|
|
36858
37489
|
delete stateResources[op.logicalId];
|
|
36859
|
-
logger.info(` Rollback: Orphaning created resource ${op.logicalId} (--orphan)`);
|
|
37490
|
+
logger.info(` Rollback: Orphaning created resource ${safe(op.logicalId)} (--orphan)`);
|
|
36860
37491
|
await afterOp?.(op.logicalId);
|
|
36861
37492
|
ctx.recordEvent?.({
|
|
36862
37493
|
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
@@ -36870,7 +37501,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
36870
37501
|
reason: `--orphan left ${op.logicalId} (${op.resourceType}) in AWS as ${record.physicalId} and dropped it from state; it is live, still billing, and no longer tracked by cdkd.`
|
|
36871
37502
|
}
|
|
36872
37503
|
});
|
|
36873
|
-
} else logger.info(` Rollback: Leaving ${op.logicalId} at its new state (--orphan)`);
|
|
37504
|
+
} else logger.info(` Rollback: Leaving ${safe(op.logicalId)} at its new state (--orphan)`);
|
|
36874
37505
|
return;
|
|
36875
37506
|
case "orphan-retain": {
|
|
36876
37507
|
const record = stateResources[op.logicalId];
|
|
@@ -36886,7 +37517,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
36886
37517
|
onOrphan?.(orphaned);
|
|
36887
37518
|
}
|
|
36888
37519
|
delete stateResources[op.logicalId];
|
|
36889
|
-
logger.info(` Rollback: Leaving ${op.logicalId} (${op.resourceType}) in AWS (DeletionPolicy: Retain) — removed from state`);
|
|
37520
|
+
logger.info(` Rollback: Leaving ${safe(op.logicalId)} (${safe(op.resourceType)}) in AWS (DeletionPolicy: Retain) — removed from state`);
|
|
36890
37521
|
await afterOp?.(op.logicalId);
|
|
36891
37522
|
ctx.recordEvent?.({
|
|
36892
37523
|
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
@@ -36905,7 +37536,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
36905
37536
|
case "delete":
|
|
36906
37537
|
case "delete-with-final-snapshot": {
|
|
36907
37538
|
if (!op.physicalId) {
|
|
36908
|
-
logger.warn(` Rollback: Cannot delete ${op.logicalId} — no physical ID recorded`);
|
|
37539
|
+
logger.warn(` Rollback: Cannot delete ${safe(op.logicalId)} — no physical ID recorded`);
|
|
36909
37540
|
result.warnings++;
|
|
36910
37541
|
return;
|
|
36911
37542
|
}
|
|
@@ -36915,7 +37546,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
36915
37546
|
const takeFinalSnapshot = snapshotPolicy && ctx.skipFinalSnapshot !== true;
|
|
36916
37547
|
let finalSnapshotIdentifier;
|
|
36917
37548
|
if (takeFinalSnapshot) finalSnapshotIdentifier = await prepareCreateRollbackFinalSnapshot(op, deleteProvisionedBy, ctx);
|
|
36918
|
-
logger.info(` Rollback: Deleting created resource ${op.logicalId} (${op.resourceType})` + (takeFinalSnapshot ? " — DeletionPolicy: Snapshot" : "") + (snapshotPolicy && !takeFinalSnapshot ? " — DeletionPolicy: Snapshot NOT taken (--skip-final-snapshot)" : ""));
|
|
37549
|
+
logger.info(` Rollback: Deleting created resource ${safe(op.logicalId)} (${safe(op.resourceType)})` + (takeFinalSnapshot ? " — DeletionPolicy: Snapshot" : "") + (snapshotPolicy && !takeFinalSnapshot ? " — DeletionPolicy: Snapshot NOT taken (--skip-final-snapshot)" : ""));
|
|
36919
37550
|
const { provider } = ctx.providerRegistry.getProviderFor({
|
|
36920
37551
|
resourceType: op.resourceType,
|
|
36921
37552
|
provisionedBy: deleteProvisionedBy
|
|
@@ -36925,7 +37556,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
36925
37556
|
...finalSnapshotIdentifier !== void 0 && { finalSnapshotIdentifier }
|
|
36926
37557
|
}), op.logicalId, op.physicalId, "while rolling back its CREATE");
|
|
36927
37558
|
delete stateResources[op.logicalId];
|
|
36928
|
-
logger.info(` Rollback: ${op.logicalId} deleted successfully`);
|
|
37559
|
+
logger.info(` Rollback: ${safe(op.logicalId)} deleted successfully`);
|
|
36929
37560
|
await afterOp?.(op.logicalId);
|
|
36930
37561
|
ctx.recordEvent?.({
|
|
36931
37562
|
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
@@ -36940,7 +37571,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
36940
37571
|
case "reverse-replacement-readopt": {
|
|
36941
37572
|
const current = stateResources[op.logicalId];
|
|
36942
37573
|
const prev = op.previousState;
|
|
36943
|
-
logger.info(` Rollback: Reversing replacement of ${op.logicalId} (${op.resourceType}) — deleting the new resource and re-adopting the retained old one (${prev.physicalId})`);
|
|
37574
|
+
logger.info(` Rollback: Reversing replacement of ${safe(op.logicalId)} (${safe(op.resourceType)}) — deleting the new resource and re-adopting the retained old one (${displaySafe(prev.physicalId)})`);
|
|
36944
37575
|
/**
|
|
36945
37576
|
* Set when this arm ORPHANS the replacement's new copy. Read at the
|
|
36946
37577
|
* `ROLLBACK_RESOURCE_SUCCEEDED` event below, which is the only channel
|
|
@@ -36974,7 +37605,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
36974
37605
|
}), op.logicalId, current.physicalId, "while reversing its replacement (re-adopting the retained old resource)");
|
|
36975
37606
|
}
|
|
36976
37607
|
stateResources[op.logicalId] = prev;
|
|
36977
|
-
logger.info(` Rollback: ${op.logicalId} restored to the retained old resource`);
|
|
37608
|
+
logger.info(` Rollback: ${safe(op.logicalId)} restored to the retained old resource`);
|
|
36978
37609
|
await afterOp?.(op.logicalId);
|
|
36979
37610
|
const survivorProvisionedBy = current.provisionedBy;
|
|
36980
37611
|
ctx.recordEvent?.({
|
|
@@ -36998,8 +37629,8 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
36998
37629
|
const resolvedPrevProps = await resolveReplayProps(prev.properties, resolver, secrets, ctx, op.logicalId) ?? {};
|
|
36999
37630
|
refuseMaskedReplayBaseline(resolvedPrevProps, op.logicalId);
|
|
37000
37631
|
recordNestedStackParameterExpressions(secrets, op.resourceType, resolvedPrevProps, prev.properties, STATE_DERIVED_RULES);
|
|
37001
|
-
logger.info(` Rollback: Reversing replacement of ${op.logicalId} (${op.resourceType}) — re-creating the old resource and deleting the new one`);
|
|
37002
|
-
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.`);
|
|
37632
|
+
logger.info(` Rollback: Reversing replacement of ${safe(op.logicalId)} (${safe(op.resourceType)}) — re-creating the old resource and deleting the new one`);
|
|
37633
|
+
if (STATEFUL_TYPES.has(op.resourceType)) logger.warn(` ⚠ ${safe(op.logicalId)} (${safe(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.`);
|
|
37003
37634
|
const { provider: createProvider } = ctx.providerRegistry.getProviderFor({
|
|
37004
37635
|
resourceType: op.resourceType,
|
|
37005
37636
|
provisionedBy: prev.provisionedBy
|
|
@@ -37018,8 +37649,8 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
37018
37649
|
} catch (createError) {
|
|
37019
37650
|
const msg = createError instanceof Error ? createError.message : String(createError);
|
|
37020
37651
|
if (!isNameCollisionError(msg)) throw createError;
|
|
37021
|
-
if (rollbackRetainsNewResource(current)) throw markNonRetryable(new CdkdError(maskSecretsInText(`Cannot reverse the replacement of ${op.logicalId} (${op.resourceType}): the re-create of the old resource (${prev.physicalId}) collided with the name still held by the new one (${current.physicalId}), and UpdateReplacePolicy: Retain pins that new resource in place, so cdkd will not delete it to free the name. Delete the new resource yourself, or remove UpdateReplacePolicy: Retain, then re-run \`cdkd rollback\` — the journal is kept, so the revert resumes from here. To leave THIS resource alone and let the rest of the rollback proceed, re-run with
|
|
37022
|
-
logger.info(` Rollback: re-create collided with the new resource's name — deleting the new resource (${current.physicalId}) first...`);
|
|
37652
|
+
if (rollbackRetainsNewResource(current)) throw markNonRetryable(new CdkdError(maskSecretsInText(`Cannot reverse the replacement of ${safe(op.logicalId)} (${safe(op.resourceType)}): the re-create of the old resource (${safe(prev.physicalId)}) collided with the name still held by the new one (${safe(current.physicalId)}), and UpdateReplacePolicy: Retain pins that new resource in place, so cdkd will not delete it to free the name. Delete the new resource yourself, or remove UpdateReplacePolicy: Retain, then re-run \`cdkd rollback\` — the journal is kept, so the revert resumes from here. To leave THIS resource alone and let the rest of the rollback proceed, re-run with ` + (typeof op.logicalId === "string" && PASTEABLE_LOGICAL_ID.test(op.logicalId) ? `\`cdkd rollback --orphan ${op.logicalId}\`: one op failure stops the ` : "`cdkd rollback --orphan <id>` (the id is withheld: it is not a plain CloudFormation logical id, so a pasted command could be reshaped by the shell or name a different resource -- read it from `cdkd events`): one op failure stops the ") + `segment loop, so a single pinned resource otherwise halts every OLDER segment too. Underlying collision: ${displaySafe(msg)}`, secrets), "NAMED_REPLACEMENT_COLLISION", maskSecretsInError(createError instanceof Error ? createError : void 0, secrets)));
|
|
37653
|
+
logger.info(` Rollback: re-create collided with the new resource's name — deleting the new resource (${displaySafe(current.physicalId)}) first...`);
|
|
37023
37654
|
{
|
|
37024
37655
|
const finalSnapshotIdentifier = rollbackFinalSnapshotId(op.resourceType, current, op.provisionedBy);
|
|
37025
37656
|
throwIfDeleteSkipped(await resolveNewDeleteProvider().delete(op.logicalId, current.physicalId, op.resourceType, current.properties, {
|
|
@@ -37036,12 +37667,12 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
37036
37667
|
interruptedMessage: "Rollback interrupted while waiting for the old name to release"
|
|
37037
37668
|
});
|
|
37038
37669
|
} catch (recreateError) {
|
|
37039
|
-
throw new Error(maskSecretsInText(`Failed to re-create the old ${op.logicalId} after the new resource (${current.physicalId}) was already deleted: ${recreateError instanceof Error ? recreateError.message : String(recreateError)}. The resource is now absent — fix forward with 'cdkd deploy'.`, secrets), { cause: maskSecretsInError(recreateError instanceof Error ? recreateError : void 0, secrets) });
|
|
37670
|
+
throw new Error(maskSecretsInText(`Failed to re-create the old ${safe(op.logicalId)} after the new resource (${current.physicalId}) was already deleted: ${displaySafe(recreateError instanceof Error ? recreateError.message : String(recreateError))}. The resource is now absent — fix forward with 'cdkd deploy'.`, secrets), { cause: maskSecretsInError(recreateError instanceof Error ? recreateError : void 0, secrets) });
|
|
37040
37671
|
}
|
|
37041
37672
|
}
|
|
37042
37673
|
const adoptedLiveNewResource = !deletedNewFirst && createResult.physicalId === current.physicalId;
|
|
37043
37674
|
if (adoptedLiveNewResource) {
|
|
37044
|
-
logger.warn(` ⚠ ${op.logicalId} (${op.resourceType}): the re-create returned the LIVE new resource (${current.physicalId}) instead of re-creating the old one — its Create API is name-idempotent and the new resource still holds the same user-supplied name. Skipping the delete-new step (it would delete that very resource). The old resource's ORIGINAL properties may NOT have been re-applied; state now records the pre-replacement properties, so run 'cdkd drift ${stackName}' to inspect and 'cdkd deploy' to reconcile, or rename the resource to make the replacement reversible.`);
|
|
37675
|
+
logger.warn(` ⚠ ${safe(op.logicalId)} (${safe(op.resourceType)}): the re-create returned the LIVE new resource (${displaySafe(current.physicalId)}) instead of re-creating the old one — its Create API is name-idempotent and the new resource still holds the same user-supplied name. Skipping the delete-new step (it would delete that very resource). The old resource's ORIGINAL properties may NOT have been re-applied; state now records the pre-replacement properties, so run 'cdkd drift ${stackName}' to inspect and 'cdkd deploy' to reconcile, or rename the resource to make the replacement reversible.`);
|
|
37045
37676
|
result.warnings++;
|
|
37046
37677
|
}
|
|
37047
37678
|
const { observedProperties: _staleObserved, ...prevRecord } = prev;
|
|
@@ -37065,11 +37696,11 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
37065
37696
|
...finalSnapshotIdentifier !== void 0 && { finalSnapshotIdentifier }
|
|
37066
37697
|
}), op.logicalId, current.physicalId, "while deleting the new resource after re-creating the old one");
|
|
37067
37698
|
} catch (deleteError) {
|
|
37068
|
-
logger.warn(maskSecretsInText(` Rollback: old ${op.logicalId} re-created, but deleting the new resource (${current.physicalId}) failed: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}. Delete it manually — it is no longer tracked in state.`, secrets));
|
|
37699
|
+
logger.warn(maskSecretsInText(` Rollback: old ${safe(op.logicalId)} re-created, but deleting the new resource (${displaySafe(current.physicalId)}) failed: ${displaySafe(deleteError instanceof Error ? deleteError.message : String(deleteError))}. Delete it manually — it is no longer tracked in state.`, secrets));
|
|
37069
37700
|
survivorReason = `The replacement's new ${op.resourceType} (${current.physicalId}) could not be deleted after the old resource was re-created: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}. It is live, still billing, and no longer tracked by cdkd — delete it yourself.`;
|
|
37070
37701
|
result.warnings++;
|
|
37071
37702
|
}
|
|
37072
|
-
logger.info(adoptedLiveNewResource ? ` Rollback: ${op.logicalId} adopted the live resource (${createResult.physicalId}) — replacement NOT fully reversed (name-idempotent Create API)` : ` Rollback: ${op.logicalId} replacement reversed (old resource re-created as ${createResult.physicalId})`);
|
|
37703
|
+
logger.info(adoptedLiveNewResource ? ` Rollback: ${safe(op.logicalId)} adopted the live resource (${createResult.physicalId}) — replacement NOT fully reversed (name-idempotent Create API)` : ` Rollback: ${safe(op.logicalId)} replacement reversed (old resource re-created as ${createResult.physicalId})`);
|
|
37073
37704
|
const survivorProvisionedBy = current.provisionedBy;
|
|
37074
37705
|
ctx.recordEvent?.({
|
|
37075
37706
|
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
@@ -37088,18 +37719,18 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
37088
37719
|
}
|
|
37089
37720
|
case "revert": {
|
|
37090
37721
|
if (!op.previousState) {
|
|
37091
|
-
logger.warn(` Rollback: Cannot restore ${op.logicalId} — no previous state available`);
|
|
37722
|
+
logger.warn(` Rollback: Cannot restore ${safe(op.logicalId)} — no previous state available`);
|
|
37092
37723
|
result.warnings++;
|
|
37093
37724
|
return;
|
|
37094
37725
|
}
|
|
37095
37726
|
const previousState = op.previousState;
|
|
37096
37727
|
const current = stateResources[op.logicalId];
|
|
37097
37728
|
if (!current) {
|
|
37098
|
-
logger.warn(` Rollback: Cannot restore ${op.logicalId} — resource not found in current state`);
|
|
37729
|
+
logger.warn(` Rollback: Cannot restore ${safe(op.logicalId)} — resource not found in current state`);
|
|
37099
37730
|
result.warnings++;
|
|
37100
37731
|
return;
|
|
37101
37732
|
}
|
|
37102
|
-
logger.info(` Rollback: Restoring ${op.logicalId} (${op.resourceType}) to previous state`);
|
|
37733
|
+
logger.info(` Rollback: Restoring ${safe(op.logicalId)} (${safe(op.resourceType)}) to previous state`);
|
|
37103
37734
|
const { provider } = ctx.providerRegistry.getProviderFor({
|
|
37104
37735
|
resourceType: op.resourceType,
|
|
37105
37736
|
provisionedBy: op.provisionedBy
|
|
@@ -37121,8 +37752,8 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
37121
37752
|
], op.logicalId, logger, isInterrupted, secrets);
|
|
37122
37753
|
stateResources[op.logicalId] = redactRollbackRecord(recordAfterRollbackUpdate(previousState, revertResult), secrets, previousState.properties);
|
|
37123
37754
|
const rollbackPartial = updatePartialReason(revertResult);
|
|
37124
|
-
if (rollbackPartial !== void 0) logger.warn(maskSecretsInText(` Rollback: ${op.logicalId} restored, ${updatePartialMessage(rollbackPartial)}`, secrets));
|
|
37125
|
-
else logger.info(` Rollback: ${op.logicalId} restored successfully`);
|
|
37755
|
+
if (rollbackPartial !== void 0) logger.warn(maskSecretsInText(` Rollback: ${safe(op.logicalId)} restored, ${updatePartialMessage(rollbackPartial)}`, secrets));
|
|
37756
|
+
else logger.info(` Rollback: ${safe(op.logicalId)} restored successfully`);
|
|
37126
37757
|
await afterOp?.(op.logicalId);
|
|
37127
37758
|
ctx.recordEvent?.({
|
|
37128
37759
|
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
@@ -37137,7 +37768,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
37137
37768
|
}
|
|
37138
37769
|
}
|
|
37139
37770
|
} catch (rollbackError) {
|
|
37140
|
-
logger.warn(maskSecretsInText(` Rollback failed for ${op.logicalId} (${op.changeType}): ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`, secrets));
|
|
37771
|
+
logger.warn(maskSecretsInText(` Rollback failed for ${safe(op.logicalId)} (${safe(op.changeType)}): ${displaySafe(rollbackError instanceof Error ? rollbackError.message : String(rollbackError))}`, secrets));
|
|
37141
37772
|
logger.warn(" Continuing with remaining rollback operations...");
|
|
37142
37773
|
result.failures++;
|
|
37143
37774
|
const failedRoute = createRollbackRoute ?? op.provisionedBy;
|
|
@@ -37199,14 +37830,14 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
|
|
|
37199
37830
|
try {
|
|
37200
37831
|
switch (action) {
|
|
37201
37832
|
case "skip-failed-noop":
|
|
37202
|
-
logger.info(` Rollback: failed ${op.changeType} of ${op.logicalId} (${op.resourceType}) left nothing to revert, skipping`);
|
|
37833
|
+
logger.info(` Rollback: failed ${safe(op.changeType)} of ${safe(op.logicalId)} (${safe(op.resourceType)}) left nothing to revert, skipping`);
|
|
37203
37834
|
break;
|
|
37204
37835
|
case "skip-failed-unknown":
|
|
37205
|
-
logger.warn(` Rollback: failed CREATE of ${op.logicalId} (${op.resourceType}) recorded no physical id — if it was partially created in AWS, delete it manually`);
|
|
37836
|
+
logger.warn(` Rollback: failed CREATE of ${safe(op.logicalId)} (${safe(op.resourceType)}) recorded no physical id — if it was partially created in AWS, delete it manually`);
|
|
37206
37837
|
result.warnings++;
|
|
37207
37838
|
break;
|
|
37208
37839
|
case "skip-failed-absent":
|
|
37209
|
-
logger.warn(` Rollback: cannot revert failed UPDATE of ${op.logicalId} — no previous state available, skipping`);
|
|
37840
|
+
logger.warn(` Rollback: cannot revert failed UPDATE of ${safe(op.logicalId)} — no previous state available, skipping`);
|
|
37210
37841
|
result.warnings++;
|
|
37211
37842
|
break;
|
|
37212
37843
|
case "orphan-failed-create-retain": {
|
|
@@ -37223,7 +37854,7 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
|
|
|
37223
37854
|
options.onOrphan?.(orphaned);
|
|
37224
37855
|
}
|
|
37225
37856
|
delete stateResources[op.logicalId];
|
|
37226
|
-
logger.info(` Rollback: leaving partially-created ${op.logicalId} (${op.resourceType}) in AWS (DeletionPolicy: Retain) — removed from state`);
|
|
37857
|
+
logger.info(` Rollback: leaving partially-created ${safe(op.logicalId)} (${safe(op.resourceType)}) in AWS (DeletionPolicy: Retain) — removed from state`);
|
|
37227
37858
|
await options.afterOp?.(op.logicalId);
|
|
37228
37859
|
ctx.recordEvent?.({
|
|
37229
37860
|
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
@@ -37243,7 +37874,7 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
|
|
|
37243
37874
|
const takeFinalSnapshot = snapshotPolicy && ctx.skipFinalSnapshot !== true;
|
|
37244
37875
|
let finalSnapshotIdentifier;
|
|
37245
37876
|
if (takeFinalSnapshot) finalSnapshotIdentifier = await prepareCreateRollbackFinalSnapshot(op, deleteProvisionedBy, ctx);
|
|
37246
|
-
logger.info(` Rollback: deleting partially-created ${op.logicalId} (${op.resourceType}) (--revert-failed)` + (takeFinalSnapshot ? " — DeletionPolicy: Snapshot" : "") + (snapshotPolicy && !takeFinalSnapshot ? " — DeletionPolicy: Snapshot NOT taken (--skip-final-snapshot)" : ""));
|
|
37877
|
+
logger.info(` Rollback: deleting partially-created ${safe(op.logicalId)} (${safe(op.resourceType)}) (--revert-failed)` + (takeFinalSnapshot ? " — DeletionPolicy: Snapshot" : "") + (snapshotPolicy && !takeFinalSnapshot ? " — DeletionPolicy: Snapshot NOT taken (--skip-final-snapshot)" : ""));
|
|
37247
37878
|
const { provider } = ctx.providerRegistry.getProviderFor({
|
|
37248
37879
|
resourceType: op.resourceType,
|
|
37249
37880
|
provisionedBy: deleteProvisionedBy
|
|
@@ -37267,7 +37898,7 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
|
|
|
37267
37898
|
case "revert-failed-update": {
|
|
37268
37899
|
const current = stateResources[op.logicalId];
|
|
37269
37900
|
const prev = op.previousState;
|
|
37270
|
-
logger.info(` Rollback: force-reverting failed UPDATE of ${op.logicalId} (${op.resourceType}) to its pre-deploy properties (--revert-failed; remote state is unknown)`);
|
|
37901
|
+
logger.info(` Rollback: force-reverting failed UPDATE of ${safe(op.logicalId)} (${safe(op.resourceType)}) to its pre-deploy properties (--revert-failed; remote state is unknown)`);
|
|
37271
37902
|
const { provider } = ctx.providerRegistry.getProviderFor({
|
|
37272
37903
|
resourceType: op.resourceType,
|
|
37273
37904
|
provisionedBy: op.provisionedBy ?? current.provisionedBy
|
|
@@ -37289,8 +37920,8 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
|
|
|
37289
37920
|
], op.logicalId, logger, options.isInterrupted, secrets);
|
|
37290
37921
|
stateResources[op.logicalId] = redactRollbackRecord(recordAfterRollbackUpdate(prev, revertFailedResult), secrets, prev.properties);
|
|
37291
37922
|
const revertFailedPartial = updatePartialReason(revertFailedResult);
|
|
37292
|
-
if (revertFailedPartial !== void 0) logger.warn(maskSecretsInText(` Rollback: ${op.logicalId} reverted, ${updatePartialMessage(revertFailedPartial)}`, secrets));
|
|
37293
|
-
else logger.info(` Rollback: ${op.logicalId} reverted successfully`);
|
|
37923
|
+
if (revertFailedPartial !== void 0) logger.warn(maskSecretsInText(` Rollback: ${safe(op.logicalId)} reverted, ${updatePartialMessage(revertFailedPartial)}`, secrets));
|
|
37924
|
+
else logger.info(` Rollback: ${safe(op.logicalId)} reverted successfully`);
|
|
37294
37925
|
await options.afterOp?.(op.logicalId);
|
|
37295
37926
|
ctx.recordEvent?.({
|
|
37296
37927
|
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
@@ -37305,7 +37936,7 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
|
|
|
37305
37936
|
}
|
|
37306
37937
|
}
|
|
37307
37938
|
} catch (revertError) {
|
|
37308
|
-
logger.warn(maskSecretsInText(` Rollback failed for failed-op ${op.logicalId} (${op.changeType}): ${revertError instanceof Error ? revertError.message : String(revertError)}`, secrets));
|
|
37939
|
+
logger.warn(maskSecretsInText(` Rollback failed for failed-op ${safe(op.logicalId)} (${safe(op.changeType)}): ${displaySafe(revertError instanceof Error ? revertError.message : String(revertError))}`, secrets));
|
|
37309
37940
|
result.failures++;
|
|
37310
37941
|
pending.add(op);
|
|
37311
37942
|
const failedRoute = createRollbackRoute ?? op.provisionedBy;
|
|
@@ -41041,5 +41672,5 @@ var DeployEngine = class DeployEngine {
|
|
|
41041
41672
|
};
|
|
41042
41673
|
|
|
41043
41674
|
//#endregion
|
|
41044
|
-
export {
|
|
41045
|
-
//# sourceMappingURL=deploy-engine-
|
|
41675
|
+
export { secretSafeKeyDisplay as $, parseBootstrapMarker as $n, ConfigError as $r, DiffCalculator as $t, planOrphanAdoption as A, formatLockExpiry as An, resolveUseCdkBootstrapAssets as Ar, WAFv2WebACLProvider as At, formatResourceLine as B, AssetPublisher as Bn, PARTITION_TABLE as Br, replayWarn as Bt, createPreDeleteFinalSnapshot as C, isThrottlingError as Ci, scrubResourceRecord as Cn, getLegacyStateBucketName as Cr, cfnRefValueFromPhysicalId as Ct, makeCanonicalizePropertiesFn as D, retryClassificationText as Di, buildForceUnlockCommand as Dn, resolveSkipPrefix as Dr, parameterTypeMayLoseSecretIdentity as Dt, unsupportedFinalSnapshotError as E, markRedactedCause as Ei, rebuildClientForBucketRegion as En, resolveCaptureObservedState as Er, isUnboundTemplateParameter as Et, WARM_THROUGHPUT_MEMBERS as F, importableOutputKeys as Fn, MIGRATE_TMP_PREFIX as Fr, coerceCfnInteger as Ft, red as G, loadPublishableAssetManifest as Gn, clearBucketRegionCache as Gr, producerRegionsFromState as Gt, cyan as H, WorkGraph as Hn, derivePartitionAndUrlSuffix as Hr, requireConfigObject as Ht, coerceWarmThroughput as I, importableOutputs as In, findLargeInlineResources as Ir, configBooleanRefusal as It, collectPublishedOutputNames as J, BOOTSTRAP_MARKER_PREFIX as Jn, getAwsClients as Jr, s3BucketDomainName as Jt, yellow as K, rewriteTemplateAssetReferences as Kn, resolveBucketRegion as Kr, withSharedDrainBudget as Kt, isWarmThroughputDecrease as L, orphansAfterRollback as Ln, uploadCfnTemplate as Lr, configIntegerRefusal as Lt, isStatefulRecreateTargetForReplace as M, CUSTOM_RESOURCE_RESPONSE_PREFIX as Mn, warnDeprecatedNoPrefixCliFlag as Mr, resolveExplicitPhysicalId as Mt, isStatefulRecreateTargetSync as N, DEFAULT_STATE_PREFIX as Nn, CFN_TEMPLATE_BODY_LIMIT as Nr, assertRegionMatch as Nt, extractDeploymentEventError as O, __exportAll as Oi, buildLockContentionMessage as On, resolveStateBucketWithDefault as Or, refStateLookupFromResource as Ot, renderStatefulReason as P, exportNamesCarriedFrom as Pn, CFN_TEMPLATE_URL_LIMIT as Pr, coerceCfnBoolean as Pt, secretBearingStateKeyWarning as Q, isCrossRegionRedirect as Qn, CdkdError as Qr, applyRoleArnIfSet as Qt, toCfnInteger as R, orphansCarriedFrom as Rn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as Rr, configStringRefusal as Rt, ccRoutedFinalSnapshotError as S, isRetryableTransientError as Si, redactSecretsForState as Sn, getDefaultStateBucketName as Sr, carriesDynamicReference as St, refusesFinalSnapshot as T, markNonRetryable as Ti, S3StateBackend as Tn, resolveAutoAssetStorage as Tr, getAccountInfo as Tt, gray as U, buildAssetRedirectMap as Un, AssemblyReader as Ur, requireConfigString as Ut, bold as V, stringifyValue as Vn, canonicalizeRegion as Vr, requireConfigArray as Vt, green as W, createAssetRedirectResolver as Wn, processStackMessages as Wr, classifyReplaySecretRegion as Wt, isExportAliasCollision as X, ensureAssetStorage as Xn, setAwsClients as Xr, s3BucketRegionalDomainName as Xt, exportAliasCollisionScrubWarning as Y, assertAssetBucketRegion as Yn, resetAwsClients as Yr, s3BucketDualStackDomainName as Yt, secretBearing as Z, getBootstrapMarkerKey as Zn, AssetError as Zr, s3BucketWebsiteUrl as Zt, isSecretBearingReferenceString as _, formatError as _i, markSameGenerationBag as _n, stripControlChars as _r, deleteIndeterminateGuards as _t, DeploymentEventsStore as a, LocalInvokeBuildError as ai, DagBuilder as an, buildDockerImage as ar, wouldReturnToSdkProvider as at, PRE_DELETE_SNAPSHOT_TYPES as b, withErrorHandling as bi, recordMaskOnlyValue as bn, Synthesizer as br, isTerminationProtectionPropagationError as bt, replayFailedOperations as c, NestedStackChildDirectDestroyError as ci, STATE_SOURCED_CROSS_GENERATION_RULES as cn, describeDockerFailure as cr, maskerOrIdentity as ct, updatePartialReason as d, ResourceTimeoutError as di, carriesSecretMask as dn, getDockerCmd as dr, interruptWatchListenerCount as dt, CrossAccountSecretRefusalError as ei, INTRINSIC_KEYS as en, readBootstrapMarkerBody as er, getCurrentResourceSecrets as et, withResourceDeadline as f, ResourceUpdateNotSupportedError as fi, createSecretMasker as fn, partitionSensitiveEnv as fr, isInterruptedWaitError as ft, bagHoldsSecretExpression as g, SynthesisError as gi, isSingleDynamicReferenceToken as gn, escapeRegExp$1 as gr, UNSPECIFIED_SKIP_REASON as gt, computeImplicitDeleteEdges as h, StateError as hi, identityKeyFor as hn, runDockerStreaming as hr, slowCcOperationTimeoutMs as ht, DeploymentEventsReader as i, IntrinsicResolutionRefusalError as ii, withRetry as in, describeAwsFailure as ir, ProviderRegistry as it, MULTI_REGION_RECREATE_BLOCKED_TYPES as j, shellQuote as jn, stateBucketExistenceConfirmed as jr, normalizeAwsTagsToCfn as jt, makeSiblingClaimReader as k, forceQuitRecoveryClause as kn, resolveStateBucketWithDefaultAndSource as kr, isSettledInstanceState as kt, replayRollback as l, PartialFailureError as li, STATE_SOURCED_READBACK_RULES as ln, dockerSpawnEnvWithSensitive as lr, beginCommandInterruptScope as lt, IMPLICIT_DELETE_DEPENDENCIES as m, StackTerminationProtectionError as mi, errorCauseChain as mn, runDockerForeground as mr, CloudControlProvider as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, DeployCancelledError as ni, findSilentDropProperties as nn, validateContainerRepoName as nr, collectInlinePolicyNamesManagedBySiblings as nt, planFailedOps as o, LocalStartServiceError as oi, TemplateParser as on, describeDockerCapturedOutput as or, createMaskedRetryLogger as ot, maskingRetryLogger as p, StackHasActiveImportsError as pi, dynamicReferenceTokens as pn, redactDockerArgvValues as pr, startInterruptWatch as pt, collectDeclaredOutputNames as q, AssetModeResolver as qn, AwsClients as qr, s3BucketArn as qt, DeployEngine as r, DynamicReferenceRegionAmbiguousError as ri, describeTypeWithThrottleRetry as rn, buildDenyExternalAccessPolicy as rr, clearOnUpdateRemoval as rt, planRollback as s, LockError as si, STATE_SOURCED_BASELINE_RULES as sn, describeDockerExecFailure as sr, maskDeep as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, DependencyError as ti, findActionableSilentDrops as tn, validateAssetBucketName as tr, IAMRoleProvider as tt, updatePartialMessage as u, ProvisioningError as ui, TEMPLATE_SOURCED_RULES as un, formatDockerLoginError as ur, endCommandInterruptScope as ut, bindingSkippedOutputs as v, isCdkdError as vi, maskSecretsInError as vn, AssetManifestLoader as vr, deleteSkipReason as vt, isFinalSnapshotError as w, isTransientServerError as wi, LockManager as wn, resolveApp as wr, coerceParameterTypedValue as wt, buildFinalSnapshotIdentifier as x, isMarkedNonRetryable as xi, recoverMaskedOutput as xn, synthesisStatusMessage as xr, IntrinsicFunctionResolver as xt, ATOMIC_FINAL_SNAPSHOT_TYPES as y, normalizeAwsError as yi, maskSecretsInText as yn, getDockerImageBySourceHash as yr, disableInstanceApiTermination as yt, toFiniteNumber as z, shouldRetainResource as zn, expectedOwnerParam as zr, readConfigString as zt };
|
|
41676
|
+
//# sourceMappingURL=deploy-engine-DD7JE_Ic.js.map
|