@go-to-k/cdkd 0.286.2 → 0.286.3
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-CSEPihOt.js → asg-provider-uaOnRUMe.js} +2 -2
- package/dist/{asg-provider-CSEPihOt.js.map → asg-provider-uaOnRUMe.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-BoSlW08T.js → deploy-engine--rkIGhow.js} +803 -20
- package/dist/deploy-engine--rkIGhow.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-BuZzDR4s.js → program-CG34-sQd.js} +116 -145
- package/dist/{program-BuZzDR4s.js.map → program-CG34-sQd.js.map} +1 -1
- package/dist/{version-Crg_SdRh.js → version-D3md0Lde.js} +2 -2
- package/dist/{version-Crg_SdRh.js.map → version-D3md0Lde.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-BoSlW08T.js.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { _ as withStackName, d as applyDefaultNameForFallback, f as generateResourceName, h as looksLikeCdkdGeneratedName, m as getCurrentStackName, n as getLogger, p as generateResourceNameWithFallback, r as isStdoutReservedForPayload, s as getLiveRenderer } from "./logger-Dw-3y48G.js";
|
|
2
2
|
import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
|
|
3
|
-
import { t as getCdkdVersion } from "./version-
|
|
3
|
+
import { t as getCdkdVersion } from "./version-D3md0Lde.js";
|
|
4
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
|
-
import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
6
|
+
import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetBucketReplicationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
7
7
|
import { CloudControlClient, CreateResourceCommand, DeleteResourceCommand, GetResourceCommand, GetResourceRequestStatusCommand, ListResourcesCommand, UpdateResourceCommand } from "@aws-sdk/client-cloudcontrol";
|
|
8
8
|
import { AttachRolePolicyCommand, CreateRoleCommand, DeleteRoleCommand, DeleteRolePermissionsBoundaryCommand, DeleteRolePolicyCommand, DetachRolePolicyCommand, GetRoleCommand, GetRolePolicyCommand, IAMClient, ListAttachedRolePoliciesCommand, ListInstanceProfilesForRoleCommand, ListRolePoliciesCommand, ListRoleTagsCommand, NoSuchEntityException, PutRolePermissionsBoundaryCommand, PutRolePolicyCommand, RemoveRoleFromInstanceProfileCommand, TagRoleCommand, UntagRoleCommand, UpdateAssumeRolePolicyCommand, UpdateRoleCommand } from "@aws-sdk/client-iam";
|
|
9
9
|
import { SQSClient } from "@aws-sdk/client-sqs";
|
|
@@ -35,6 +35,7 @@ import { KMSClient, ListAliasesCommand } from "@aws-sdk/client-kms";
|
|
|
35
35
|
import { readFile } from "fs/promises";
|
|
36
36
|
import { join as join$1 } from "path";
|
|
37
37
|
import { CreateRepositoryCommand, DescribeImagesCommand as DescribeImagesCommand$1, DescribeRepositoriesCommand, ECRClient, GetAuthorizationTokenCommand, PutImageTagMutabilityCommand } from "@aws-sdk/client-ecr";
|
|
38
|
+
import { inspect } from "node:util";
|
|
38
39
|
import { hostname } from "os";
|
|
39
40
|
import graphlib from "graphlib";
|
|
40
41
|
import { DescribeDBClustersCommand, DescribeDBInstancesCommand, RDSClient } from "@aws-sdk/client-rds";
|
|
@@ -4027,16 +4028,402 @@ function displaySafe(value, opts) {
|
|
|
4027
4028
|
}
|
|
4028
4029
|
|
|
4029
4030
|
//#endregion
|
|
4030
|
-
//#region src/state/s3-
|
|
4031
|
+
//#region src/state/s3-replication-purge-gap.ts
|
|
4032
|
+
/**
|
|
4033
|
+
* Tell the user when S3 REPLICATION has silently kept the bodies that
|
|
4034
|
+
* {@link ../state/s3-noncurrent-version-purge.js | the noncurrent-version
|
|
4035
|
+
* purge} just removed (issue
|
|
4036
|
+
* [#2447](https://github.com/go-to-k/cdkd/issues/2447)).
|
|
4037
|
+
*
|
|
4038
|
+
* ## The hole this closes
|
|
4039
|
+
*
|
|
4040
|
+
* `purgeNoncurrentKeyVersions` exists so that "cdkd deleted the object" means
|
|
4041
|
+
* the body is no longer retrievable. It reaches that by issuing
|
|
4042
|
+
* `DeleteObjects` entries that name a specific `VersionId`.
|
|
4043
|
+
*
|
|
4044
|
+
* **S3 never replicates a version-id delete.** Replication propagates PUTs
|
|
4045
|
+
* and — only when `DeleteMarkerReplication` is enabled — delete markers; a
|
|
4046
|
+
* delete naming a version id is deliberately not replicated, so that a delete
|
|
4047
|
+
* on the source cannot destroy data on the destination. On a state bucket
|
|
4048
|
+
* with Cross-Region (CRR) or Same-Region (SRR) replication the purge is
|
|
4049
|
+
* therefore SOURCE-ONLY: the replica keeps its own copy of every version,
|
|
4050
|
+
* `GetObject` with a `VersionId` still returns the secret, and cdkd reports
|
|
4051
|
+
* the purge as complete because on the only bucket it knows about, it was.
|
|
4052
|
+
*
|
|
4053
|
+
* That is exactly the shape the purge was built to remove — an operation that
|
|
4054
|
+
* reports success while the value stays readable — reproduced one bucket over
|
|
4055
|
+
* and invisible from every signal cdkd emitted. The user is not merely
|
|
4056
|
+
* unprotected, they are REASSURED, which is the direction this repo treats as
|
|
4057
|
+
* a defect rather than a gap.
|
|
4058
|
+
*
|
|
4059
|
+
* ## Why a probe and not only a doc line
|
|
4060
|
+
*
|
|
4061
|
+
* Documentation reaches the reader who goes looking. This reaches the one who
|
|
4062
|
+
* does not, at the moment the false belief would be formed. The whole cost is
|
|
4063
|
+
* one bucket-metadata call per process (see the cache below) and one IAM
|
|
4064
|
+
* action the caller may well not hold.
|
|
4065
|
+
*
|
|
4066
|
+
* ## Degrading is the DEFAULT, not the failure
|
|
4067
|
+
*
|
|
4068
|
+
* The purge's contract is that a missing permission produces a warning and
|
|
4069
|
+
* never an abort, and a detector bolted onto it must not be able to break
|
|
4070
|
+
* that. So this module NEVER THROWS, and treats every non-answer as "we do
|
|
4071
|
+
* not know" rather than as either polarity:
|
|
4072
|
+
*
|
|
4073
|
+
* - `ReplicationConfigurationNotFoundError` — the answer "no replication
|
|
4074
|
+
* configuration exists". S3 reports the ABSENCE of a configuration as an
|
|
4075
|
+
* ERROR, not as an empty response body, so this arm is the ordinary case
|
|
4076
|
+
* for almost every bucket and must not read as a failure.
|
|
4077
|
+
* `ReplicationConfiguration` ABSENT from a 200 response is treated the same
|
|
4078
|
+
* way: the SDK models the field as optional, and a modelled field is not a
|
|
4079
|
+
* promise that the API populates it.
|
|
4080
|
+
* - `AccessDenied` and friends — the caller lacks
|
|
4081
|
+
* `s3:GetReplicationConfiguration`. Logged at DEBUG and never at `warn`:
|
|
4082
|
+
* the overwhelming majority of buckets are not replicated, so warning here
|
|
4083
|
+
* would tell almost every user to grant a permission in order to be told
|
|
4084
|
+
* nothing. The doc names the action for the user who wants the check.
|
|
4085
|
+
* - anything else (throttle, 5xx, network) — DEBUG, and deliberately NOT
|
|
4086
|
+
* cached, so the next purge in the same process retries.
|
|
4087
|
+
*
|
|
4088
|
+
* ## What is cached, what is deduped, and why they are different keys
|
|
4089
|
+
*
|
|
4090
|
+
* The PROBE is cached per (bucket, asserted owner) for the process lifetime, as
|
|
4091
|
+
* the sibling `src/provisioning/create-only-properties.ts` caches its
|
|
4092
|
+
* `DescribeType` lookups and for the same reason: the answer cannot change
|
|
4093
|
+
* mid-run, the value stored is the in-flight PROMISE so concurrent purges share
|
|
4094
|
+
* one call, and only non-transient outcomes are kept. Without it a stack with
|
|
4095
|
+
* thirty custom resources would issue thirty identical `GetBucketReplication`
|
|
4096
|
+
* calls.
|
|
4097
|
+
*
|
|
4098
|
+
* The WARNING is deduped too, but on a DIFFERENT and deliberately wider key —
|
|
4099
|
+
* (bucket, object description, destinations) — with the repeats sent to
|
|
4100
|
+
* `debug`. The two keys answer two questions and must not be merged:
|
|
4101
|
+
*
|
|
4102
|
+
* - the bucket alone is not enough, because `lock-manager.ts` routes this
|
|
4103
|
+
* module's `warn` sink to `debug` on the ordinary release path (it purges a
|
|
4104
|
+
* heartbeat record on every command and must not warn about it). Keyed on the
|
|
4105
|
+
* bucket, that demoted line would consume the one warning the rollback
|
|
4106
|
+
* journal or the response sidecar needed;
|
|
4107
|
+
* - adding the description is still not enough, because two rules covering
|
|
4108
|
+
* different prefixes can name DIFFERENT destinations. Purging a `cdkd/dev/…`
|
|
4109
|
+
* key can match only the first rule; a later `cdkd/prod/…` purge under the
|
|
4110
|
+
* same description matches both, and without the destinations in the key the
|
|
4111
|
+
* user would clean the first replica and never learn the second one holds the
|
|
4112
|
+
* production journal.
|
|
4113
|
+
*
|
|
4114
|
+
* Deduping at all is a departure from the sibling purge module, which
|
|
4115
|
+
* deliberately repeats its own failure warning. The difference is that a
|
|
4116
|
+
* failure RECURS — each one is another object cdkd could not remove — while
|
|
4117
|
+
* this is a fact about a BUCKET, identical every time it is re-derived.
|
|
4118
|
+
* Repeating it thirty times in a deploy adds nothing and erodes the failure
|
|
4119
|
+
* warning standing next to it.
|
|
4120
|
+
*/
|
|
4031
4121
|
/**
|
|
4032
4122
|
* Parenthetical used when a caller names nothing.
|
|
4033
4123
|
*
|
|
4034
|
-
* True of ANY object
|
|
4035
|
-
*
|
|
4036
|
-
*
|
|
4037
|
-
*
|
|
4124
|
+
* True of ANY object the purge is pointed at, which is the bar for a default
|
|
4125
|
+
* here: a caller that forgets to describe its object must still emit a warning
|
|
4126
|
+
* that is correct, just less specific. It deliberately does not guess at
|
|
4127
|
+
* content.
|
|
4128
|
+
*
|
|
4129
|
+
* Lives HERE rather than in `s3-noncurrent-version-purge.ts` only to keep the
|
|
4130
|
+
* import one-directional — the purge imports this module, so a shared constant
|
|
4131
|
+
* defined there and read here would be a cycle. It is the same sentence both
|
|
4132
|
+
* warnings fall back to, and one definition is what stops the two drifting.
|
|
4133
|
+
*/
|
|
4134
|
+
const DEFAULT_PURGED_OBJECT_DESCRIPTION = "the body of an object cdkd has just reported as removed";
|
|
4135
|
+
/**
|
|
4136
|
+
* Cache of the replication probe, holding the in-flight promise.
|
|
4137
|
+
*
|
|
4138
|
+
* Keyed on the bucket AND the `ExpectedBucketOwner` the caller asserts. The
|
|
4139
|
+
* SUCCESS answer does not depend on the owner assertion — two callers
|
|
4140
|
+
* differing only in whether they assert it read the same configuration — but
|
|
4141
|
+
* the FAILURE does: a mismatched-owner 403 is an answer about that caller's
|
|
4142
|
+
* request, and caching it under the bare bucket name silenced the detector for
|
|
4143
|
+
* every correctly-scoped caller in the process. cdkd has two shapes today (the
|
|
4144
|
+
* provider probes without an owner, the state backend and lock manager with
|
|
4145
|
+
* one), so the two are distinguishable in practice.
|
|
4146
|
+
*/
|
|
4147
|
+
const replicationProbeCache = /* @__PURE__ */ new Map();
|
|
4148
|
+
/**
|
|
4149
|
+
* (bucket, object description, destinations) triples already warned about.
|
|
4150
|
+
*
|
|
4151
|
+
* The one thing the warning says that VARIES is `objectDescription`; the rest
|
|
4152
|
+
* is a fact about the bucket, identical every time. So a stack with thirty
|
|
4153
|
+
* custom resources repeated a ~600-character warning thirty times while adding
|
|
4154
|
+
* no information after the first — and warning fatigue erodes the purge's own
|
|
4155
|
+
* failure warning standing next to it.
|
|
4156
|
+
*
|
|
4157
|
+
* Keyed on all three precisely so this cannot become a silence hole; the
|
|
4158
|
+
* module doc above gives the two failures a narrower key produces. Entries are
|
|
4159
|
+
* added only after the warning is actually emitted, so a throwing sink does
|
|
4160
|
+
* not burn a slot.
|
|
4161
|
+
*
|
|
4162
|
+
* RESIDUAL, bounded and recorded: cdkd cannot see whether the caller's sink
|
|
4163
|
+
* DEMOTED the line, so a `lock-manager.ts` release (which routes `warn` to
|
|
4164
|
+
* `debug`) claims the lock's slot and a later reap of a stale lock — which
|
|
4165
|
+
* would have warned — is suppressed. It is bounded to the LOCK description,
|
|
4166
|
+
* whose object the docs describe as carrying no secret, and to one process.
|
|
4167
|
+
*/
|
|
4168
|
+
const warnedWarnings = /* @__PURE__ */ new Set();
|
|
4169
|
+
/** How many destinations the warning names before it truncates. */
|
|
4170
|
+
const MAX_NAMED_DESTINATIONS = 3;
|
|
4171
|
+
/** Shown when a rule carries no `Destination.Bucket` for us to name. */
|
|
4172
|
+
const UNNAMED_DESTINATION = "<destination not named by the replication rule>";
|
|
4173
|
+
/**
|
|
4174
|
+
* Error codes meaning "this principal will never get an answer here".
|
|
4175
|
+
*
|
|
4176
|
+
* Cached like a real answer, because unlike a throttle they do not clear
|
|
4177
|
+
* within the process: retrying on every purge would spend one denied API call
|
|
4178
|
+
* per custom resource for a result that cannot change.
|
|
4179
|
+
*/
|
|
4180
|
+
const PERMANENT_DENIALS = /* @__PURE__ */ new Set([
|
|
4181
|
+
"AccessDenied",
|
|
4182
|
+
"AccessDeniedException",
|
|
4183
|
+
"AllAccessDisabled",
|
|
4184
|
+
"MethodNotAllowed",
|
|
4185
|
+
"NotImplemented"
|
|
4186
|
+
]);
|
|
4187
|
+
/**
|
|
4188
|
+
* Credential-shaped failures that ARRIVE AS 403 and are nonetheless transient.
|
|
4189
|
+
*
|
|
4190
|
+
* Checked BEFORE the blanket 403 rule, which is the only way they can win:
|
|
4191
|
+
* `InvalidAccessKeyId` is the classic blip while an IAM principal or an
|
|
4192
|
+
* assumed-role credential propagates — this repo retries that whole class over
|
|
4193
|
+
* a 47.75 s schedule elsewhere — and an expired or skewed token clears on
|
|
4194
|
+
* refresh. Cached as permanent, ONE such blip on the first purge of a long
|
|
4195
|
+
* deploy would silence the detector for the rest of the process, which is the
|
|
4196
|
+
* "false silence leaves them believing a secret is gone" direction this module
|
|
4197
|
+
* forbids.
|
|
4198
|
+
*/
|
|
4199
|
+
const TRANSIENT_CREDENTIAL_CODES = /* @__PURE__ */ new Set([
|
|
4200
|
+
"InvalidAccessKeyId",
|
|
4201
|
+
"ExpiredToken",
|
|
4202
|
+
"ExpiredTokenException",
|
|
4203
|
+
"InvalidToken",
|
|
4204
|
+
"TokenRefreshRequired",
|
|
4205
|
+
"RequestTimeTooSkewed",
|
|
4206
|
+
"RequestExpired"
|
|
4207
|
+
]);
|
|
4208
|
+
const describe$2 = (error) => error instanceof Error ? error.message : String(error);
|
|
4209
|
+
/**
|
|
4210
|
+
* Every wire error code the value offers, `name` and `Code` alike.
|
|
4211
|
+
*
|
|
4212
|
+
* NO PRECEDENCE, deliberately. An earlier revision collapsed the two to one
|
|
4213
|
+
* string and preferred a specific `name`, which had two failure modes in
|
|
4214
|
+
* opposite directions: preferring `name` unconditionally made the `Code`
|
|
4215
|
+
* fallback dead (`{ name: 'Error', Code: 'AccessDenied' }` classified as
|
|
4216
|
+
* retryable, so a denied principal re-probed on every purge), while
|
|
4217
|
+
* preferring a SPECIFIC `name` let a wrapper's own name hide
|
|
4218
|
+
* `Code: 'InvalidAccessKeyId'` and cache a credential blip as a permanent
|
|
4219
|
+
* denial. Membership is tested against every code the error carries, so
|
|
4220
|
+
* neither field can mask the other. (Verified against real S3: a missing
|
|
4221
|
+
* replication configuration arrives with that string on BOTH fields.)
|
|
4222
|
+
*
|
|
4223
|
+
* Null-safe because a `send` that rejects with `null` used to throw a
|
|
4224
|
+
* TypeError INSIDE the probe's own catch, leaving a REJECTED promise in the
|
|
4225
|
+
* cache forever: every later purge on that bucket then fell into the outer
|
|
4226
|
+
* `catch {}` with no warning and no debug line — a silently dead detector,
|
|
4227
|
+
* which is the worst possible failure for this module.
|
|
4228
|
+
*
|
|
4229
|
+
* This guard and `probe`'s rejection arm both serve one invariant — **no
|
|
4230
|
+
* rejection ever reaches the cache** — and the honest statement of their
|
|
4231
|
+
* relationship, measured one mutation at a time on 2026-09-05, is:
|
|
4232
|
+
*
|
|
4233
|
+
* - removing the ARM alone REDS a case (a rejection whose own `toString`
|
|
4234
|
+
* throws: the guards pass that value through and `describe()` is what
|
|
4235
|
+
* raises), so the arm is independently fenced;
|
|
4236
|
+
* - removing these GUARDS alone leaves the CACHING behaviour identical:
|
|
4237
|
+
* `errorCodes` runs inside `runProbe`'s own catch, so a TypeError here is
|
|
4238
|
+
* caught by the arm and produces the same `unknown` / `retry: true` /
|
|
4239
|
+
* evicted outcome. What DOES change is the debug line's reason — guarded it
|
|
4240
|
+
* reports the rejection (`: null`), unguarded it reports the classifier's
|
|
4241
|
+
* own crash — and that difference is what the `throw null` case asserts, so
|
|
4242
|
+
* the guards are fenced after all.
|
|
4243
|
+
*
|
|
4244
|
+
* They also earn their place structurally: classification is inside the catch
|
|
4245
|
+
* TODAY. An edit that hoists it out — or that adds a caller of `errorCodes`
|
|
4246
|
+
* outside a try — reinstates the original defect, in which a `null` rejection
|
|
4247
|
+
* crashed the classifier and left a REJECTED promise cached for the process,
|
|
4248
|
+
* so every later purge on that bucket fell into the outer `catch {}` with no
|
|
4249
|
+
* warning and no debug line: a silently dead detector.
|
|
4250
|
+
*/
|
|
4251
|
+
const errorCodes = (error) => {
|
|
4252
|
+
if (error === null || typeof error !== "object" && typeof error !== "function") return [];
|
|
4253
|
+
const e = error;
|
|
4254
|
+
const codes = [];
|
|
4255
|
+
if (typeof e.name === "string" && e.name.length > 0) codes.push(e.name);
|
|
4256
|
+
if (typeof e.Code === "string" && e.Code.length > 0) codes.push(e.Code);
|
|
4257
|
+
return codes;
|
|
4258
|
+
};
|
|
4259
|
+
/** True when ANY code the error carries is in `set`. */
|
|
4260
|
+
const hasCode = (error, set) => errorCodes(error).some((code) => set.has(code));
|
|
4261
|
+
const httpStatus = (error) => {
|
|
4262
|
+
if (error === null || typeof error !== "object" && typeof error !== "function") return void 0;
|
|
4263
|
+
const meta = error.$metadata;
|
|
4264
|
+
return typeof meta?.httpStatusCode === "number" ? meta.httpStatusCode : void 0;
|
|
4265
|
+
};
|
|
4266
|
+
/**
|
|
4267
|
+
* Strip the ARN wrapper off `Destination.Bucket`, which S3 returns as
|
|
4268
|
+
* `arn:<partition>:s3:::<name>`.
|
|
4269
|
+
*
|
|
4270
|
+
* Partition-agnostic on purpose: `aws-cn` and `aws-us-gov` produce a
|
|
4271
|
+
* different second segment, and a hard-coded `arn:aws:s3:::` would leave the
|
|
4272
|
+
* whole ARN in the message there rather than mis-parse it — survivable, but
|
|
4273
|
+
* the regex costs nothing. A value that is not ARN-shaped is passed through
|
|
4274
|
+
* unchanged rather than rejected; the field is only ever displayed.
|
|
4275
|
+
*/
|
|
4276
|
+
function destinationName(bucketArn, account) {
|
|
4277
|
+
if (bucketArn === void 0 || bucketArn === "") return UNNAMED_DESTINATION;
|
|
4278
|
+
const name = bucketArn.replace(/^arn:[^:]*:s3:::/, "");
|
|
4279
|
+
return account !== void 0 && account !== "" ? `${name} (account ${account})` : name;
|
|
4280
|
+
}
|
|
4281
|
+
/**
|
|
4282
|
+
* Reduce a `ReplicationConfiguration` to the ENABLED rules and their effective
|
|
4283
|
+
* prefixes.
|
|
4284
|
+
*
|
|
4285
|
+
* Exported for its own unit tests: the mapping from the four filter shapes S3
|
|
4286
|
+
* accepts onto one prefix string is the part with real cases in it, and
|
|
4287
|
+
* pinning it through a stubbed client would test the plumbing instead.
|
|
4288
|
+
*
|
|
4289
|
+
* Over-approximates DELIBERATELY, in the direction of warning:
|
|
4290
|
+
*
|
|
4291
|
+
* - a rule filtering on a TAG (`Filter.Tag`, or `Filter.And.Tags` with no
|
|
4292
|
+
* `Prefix`) cannot be evaluated without reading each object's tag set, so
|
|
4293
|
+
* its prefix reduces to `''` and it covers everything. A false warning
|
|
4294
|
+
* sends the reader to look at a replica; a false silence leaves them
|
|
4295
|
+
* believing a secret is gone.
|
|
4296
|
+
* - a `Disabled` rule is KEPT, flagged rather than dropped. Disabling a rule
|
|
4297
|
+
* stops FUTURE replication; it does not remove what the rule already
|
|
4298
|
+
* copied, so a bucket whose rule covered `cdkd/` last month and is disabled
|
|
4299
|
+
* today still holds every body ever purged. Dropping it was the first cut
|
|
4300
|
+
* and is the reassured-user failure this module exists to remove, arriving
|
|
4301
|
+
* inside the module that removes it. A rule with no `Status` at all is kept
|
|
4302
|
+
* as ENABLED: `Status` is required by the model, so an absent one means the
|
|
4303
|
+
* response was not what we assumed — the same reason the sibling purge
|
|
4304
|
+
* treats an absent `IsLatest` as "assume the unsafe thing is true".
|
|
4305
|
+
*
|
|
4306
|
+
* Pure: it reads only its argument, so no ambient locale, clock or
|
|
4307
|
+
* environment can change the answer.
|
|
4308
|
+
*/
|
|
4309
|
+
function normalizeReplicationRules(config) {
|
|
4310
|
+
const targets = [];
|
|
4311
|
+
for (const rule of config.Rules ?? []) {
|
|
4312
|
+
const prefix = rule.Filter !== void 0 ? rule.Filter.Prefix ?? rule.Filter.And?.Prefix ?? "" : rule.Prefix ?? "";
|
|
4313
|
+
targets.push({
|
|
4314
|
+
prefix,
|
|
4315
|
+
destination: destinationName(rule.Destination?.Bucket, rule.Destination?.Account),
|
|
4316
|
+
enabled: rule.Status !== "Disabled"
|
|
4317
|
+
});
|
|
4318
|
+
}
|
|
4319
|
+
return targets;
|
|
4320
|
+
}
|
|
4321
|
+
async function runProbe(s3Client, bucket, requestFields) {
|
|
4322
|
+
try {
|
|
4323
|
+
const config = (await s3Client.send(new GetBucketReplicationCommand({
|
|
4324
|
+
Bucket: bucket,
|
|
4325
|
+
...requestFields
|
|
4326
|
+
}))).ReplicationConfiguration;
|
|
4327
|
+
if (config === void 0) return { kind: "none" };
|
|
4328
|
+
const rules = normalizeReplicationRules(config);
|
|
4329
|
+
return rules.length === 0 ? { kind: "none" } : {
|
|
4330
|
+
kind: "rules",
|
|
4331
|
+
rules
|
|
4332
|
+
};
|
|
4333
|
+
} catch (error) {
|
|
4334
|
+
if (errorCodes(error).includes("ReplicationConfigurationNotFoundError")) return { kind: "none" };
|
|
4335
|
+
if (hasCode(error, TRANSIENT_CREDENTIAL_CODES)) return {
|
|
4336
|
+
kind: "unknown",
|
|
4337
|
+
reason: describe$2(error),
|
|
4338
|
+
retry: true
|
|
4339
|
+
};
|
|
4340
|
+
const permanent = hasCode(error, PERMANENT_DENIALS) || httpStatus(error) === 403;
|
|
4341
|
+
return {
|
|
4342
|
+
kind: "unknown",
|
|
4343
|
+
reason: describe$2(error),
|
|
4344
|
+
retry: !permanent
|
|
4345
|
+
};
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
function probe(s3Client, bucket, requestFields) {
|
|
4349
|
+
const key = `${bucket}\u0000${requestFields.ExpectedBucketOwner ?? ""}`;
|
|
4350
|
+
const cached = replicationProbeCache.get(key);
|
|
4351
|
+
if (cached) return cached;
|
|
4352
|
+
const pending = runProbe(s3Client, bucket, requestFields).then((result) => {
|
|
4353
|
+
if (result.kind === "unknown" && result.retry) evictIfOwn(key, pending);
|
|
4354
|
+
return result;
|
|
4355
|
+
}, (error) => {
|
|
4356
|
+
evictIfOwn(key, pending);
|
|
4357
|
+
return {
|
|
4358
|
+
kind: "unknown",
|
|
4359
|
+
reason: describe$2(error),
|
|
4360
|
+
retry: true
|
|
4361
|
+
};
|
|
4362
|
+
});
|
|
4363
|
+
replicationProbeCache.set(key, pending);
|
|
4364
|
+
return pending;
|
|
4365
|
+
}
|
|
4366
|
+
/**
|
|
4367
|
+
* Remove a cache entry only when it is still the one we put there.
|
|
4368
|
+
*
|
|
4369
|
+
* The identity check is UNFENCED by a test, deliberately and stated rather
|
|
4370
|
+
* than left to be discovered: the only way a stale probe can settle against a
|
|
4371
|
+
* newer entry under the same key is for something to have cleared the cache
|
|
4372
|
+
* mid-flight, and the sole clearer is `clearReplicationProbeCache()`, a
|
|
4373
|
+
* test-only helper. Writing a test for it would be writing a test for the test
|
|
4374
|
+
* helper. It is kept because the alternative is an unconditional `delete` that
|
|
4375
|
+
* is wrong the moment a non-test clearer appears.
|
|
4376
|
+
*/
|
|
4377
|
+
function evictIfOwn(key, own) {
|
|
4378
|
+
if (replicationProbeCache.get(key) === own) replicationProbeCache.delete(key);
|
|
4379
|
+
}
|
|
4380
|
+
/**
|
|
4381
|
+
* Warn if replication on `bucket` covers any of `keys`.
|
|
4382
|
+
*
|
|
4383
|
+
* `keys` are the keys whose bodies were actually removed (or could not be
|
|
4384
|
+
* settled) — NOT everything the caller asked about. The purge scopes them; see
|
|
4385
|
+
* its call site for why announcing a survival that never happened is worse
|
|
4386
|
+
* than saying nothing.
|
|
4387
|
+
*
|
|
4388
|
+
* NEVER THROWS and never rejects: the caller runs on a cleanup path that must
|
|
4389
|
+
* not abort, and this is a diagnostic bolted onto it.
|
|
4038
4390
|
*/
|
|
4039
|
-
|
|
4391
|
+
async function warnIfPurgeIsReplicated(s3Client, bucket, keys, options = {}) {
|
|
4392
|
+
if (keys.length === 0) return;
|
|
4393
|
+
const emitDebug = (message) => {
|
|
4394
|
+
const sink = options.logger;
|
|
4395
|
+
if (sink?.debug) sink.debug(message);
|
|
4396
|
+
else getLogger().child("s3-replication-check").debug(message);
|
|
4397
|
+
};
|
|
4398
|
+
try {
|
|
4399
|
+
const safeBucket = displaySafe(bucket, { asciiOnly: true });
|
|
4400
|
+
const result = await probe(s3Client, bucket, options.requestFields ?? {});
|
|
4401
|
+
if (result.kind === "none") return;
|
|
4402
|
+
if (result.kind === "unknown") {
|
|
4403
|
+
emitDebug(`Could not determine whether s3://${safeBucket} is replicated (grant s3:GetReplicationConfiguration to enable the check): ${displaySafe(result.reason)}`);
|
|
4404
|
+
return;
|
|
4405
|
+
}
|
|
4406
|
+
const covered = result.rules.filter((rule) => keys.some((key) => key.startsWith(rule.prefix)));
|
|
4407
|
+
if (covered.length === 0) return;
|
|
4408
|
+
const destinations = [...new Set(covered.map((rule) => rule.enabled ? rule.destination : `${rule.destination} (rule currently Disabled -- it stops FUTURE replication, not what it already copied)`))].sort();
|
|
4409
|
+
const warnKey = [
|
|
4410
|
+
bucket,
|
|
4411
|
+
options.objectDescription ?? "",
|
|
4412
|
+
...destinations
|
|
4413
|
+
].join("\0");
|
|
4414
|
+
if (warnedWarnings.has(warnKey)) {
|
|
4415
|
+
emitDebug(`S3 replication on s3://${safeBucket} still covers purged keys (${options.objectDescription ?? "the body of an object cdkd has just reported as removed"}); already reported once this run for ${destinations.map((d) => displaySafe(d, { asciiOnly: true })).join(", ")}.`);
|
|
4416
|
+
return;
|
|
4417
|
+
}
|
|
4418
|
+
const named = destinations.slice(0, MAX_NAMED_DESTINATIONS).map((d) => displaySafe(d, { asciiOnly: true }));
|
|
4419
|
+
const elided = destinations.length - named.length;
|
|
4420
|
+
(options.logger ?? getLogger().child("s3-replication-check")).warn(`S3 replication is enabled on s3://${safeBucket} and covers the key(s) cdkd just purged. S3 NEVER replicates a version-id delete, so the purge removed those versions from THIS bucket only — the copies in the destination bucket survive and remain readable there via GetObject with a VersionId (${options.objectDescription ?? "the body of an object cdkd has just reported as removed"}). cdkd cannot delete them. Remove them in the destination bucket yourself (aws s3api list-object-versions, then delete-object --version-id), or narrow the replication rule so it excludes the prefixes cdkd purges under. Destination(s): ${named.join(", ")}` + (elided > 0 ? ` (and ${elided} more; aws s3api get-bucket-replication --bucket ${safeBucket} lists them all)` : ""));
|
|
4421
|
+
warnedWarnings.add(warnKey);
|
|
4422
|
+
} catch {}
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4425
|
+
//#endregion
|
|
4426
|
+
//#region src/state/s3-noncurrent-version-purge.ts
|
|
4040
4427
|
/**
|
|
4041
4428
|
* `objectDescription` for the custom-resource response sidecar.
|
|
4042
4429
|
*
|
|
@@ -4110,17 +4497,27 @@ async function purgeNoncurrentKeyVersions(s3Client, bucket, keys, options = {})
|
|
|
4110
4497
|
const prefixes = options.listPrefix !== void 0 ? [options.listPrefix] : keys;
|
|
4111
4498
|
const failed = /* @__PURE__ */ new Map();
|
|
4112
4499
|
const unknown = { n: 0 };
|
|
4500
|
+
const purged = /* @__PURE__ */ new Set();
|
|
4501
|
+
const unsettledBodies = /* @__PURE__ */ new Set();
|
|
4113
4502
|
for (const prefix of prefixes) try {
|
|
4114
|
-
await purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields, failed, unknown);
|
|
4503
|
+
await purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields, failed, unknown, purged, unsettledBodies);
|
|
4115
4504
|
} catch (error) {
|
|
4116
4505
|
const affected = options.listPrefix !== void 0 ? keys : [prefix];
|
|
4117
|
-
for (const key of affected)
|
|
4506
|
+
for (const key of affected) {
|
|
4507
|
+
recordFailure(failed, key, describe$1(error));
|
|
4508
|
+
unsettledBodies.add(key);
|
|
4509
|
+
}
|
|
4118
4510
|
}
|
|
4119
4511
|
if (failed.size > 0) {
|
|
4120
4512
|
const named = [...failed.entries()].slice(0, MAX_NAMED_FAILURES).map(([key, reasons]) => displaySafe(`${key} (${reasons.join("; ")})`));
|
|
4121
4513
|
const elided = failed.size - named.length;
|
|
4122
|
-
logger.warn(`Could not purge noncurrent versions of ${failed.size} key(s) in s3://${bucket}. Their previous versions survive and remain readable via GetObject with a VersionId (${options.objectDescription ??
|
|
4514
|
+
logger.warn(`Could not purge noncurrent versions of ${failed.size} key(s) in s3://${displaySafe(bucket, { asciiOnly: true })}. Their previous versions survive and remain readable via GetObject with a VersionId (${options.objectDescription ?? "the body of an object cdkd has just reported as removed"}). Grant s3:ListBucketVersions and s3:DeleteObjectVersion on the state bucket, or purge the key(s) by hand. Failures: ${named.join(", ")}` + (elided > 0 ? ` (and ${elided} more)` : ""));
|
|
4123
4515
|
}
|
|
4516
|
+
await warnIfPurgeIsReplicated(s3Client, bucket, [.../* @__PURE__ */ new Set([...purged, ...unsettledBodies])], {
|
|
4517
|
+
requestFields,
|
|
4518
|
+
logger,
|
|
4519
|
+
...options.objectDescription !== void 0 && { objectDescription: options.objectDescription }
|
|
4520
|
+
});
|
|
4124
4521
|
}
|
|
4125
4522
|
/**
|
|
4126
4523
|
* Paginate `ListObjectVersions` under one prefix and delete every returned
|
|
@@ -4135,7 +4532,7 @@ async function purgeNoncurrentKeyVersions(s3Client, bucket, keys, options = {})
|
|
|
4135
4532
|
* id is NOT filtered out on its own, because a bucket whose versioning was
|
|
4136
4533
|
* SUSPENDED can carry a genuine noncurrent `'null'` version holding the body.
|
|
4137
4534
|
*/
|
|
4138
|
-
async function purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields, failed, unknown) {
|
|
4535
|
+
async function purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields, failed, unknown, purged, unsettledBodies) {
|
|
4139
4536
|
let keyMarker;
|
|
4140
4537
|
let versionIdMarker;
|
|
4141
4538
|
do {
|
|
@@ -4147,15 +4544,30 @@ async function purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields,
|
|
|
4147
4544
|
...versionIdMarker !== void 0 && { VersionIdMarker: versionIdMarker }
|
|
4148
4545
|
}));
|
|
4149
4546
|
const stale = [];
|
|
4150
|
-
|
|
4547
|
+
const entries = [...(resp.Versions ?? []).map((entry) => ({
|
|
4548
|
+
entry,
|
|
4549
|
+
hasBody: true
|
|
4550
|
+
})), ...(resp.DeleteMarkers ?? []).map((entry) => ({
|
|
4551
|
+
entry,
|
|
4552
|
+
hasBody: false
|
|
4553
|
+
}))];
|
|
4554
|
+
for (const { entry, hasBody } of entries) {
|
|
4151
4555
|
if (entry.Key === void 0 || !wanted.has(entry.Key)) continue;
|
|
4152
|
-
if (entry.IsLatest === void 0)
|
|
4556
|
+
if (entry.IsLatest === void 0) {
|
|
4557
|
+
recordFailure(failed, entry.Key, `version ${entry.VersionId ?? "<unknown>"}: listing omitted IsLatest, so the entry was left alone rather than risk deleting a current version`);
|
|
4558
|
+
if (hasBody) unsettledBodies.add(entry.Key);
|
|
4559
|
+
}
|
|
4153
4560
|
if (entry.IsLatest !== false) continue;
|
|
4154
|
-
if (!entry.VersionId)
|
|
4561
|
+
if (!entry.VersionId) {
|
|
4562
|
+
recordFailure(failed, entry.Key, `listing returned a noncurrent entry with no VersionId, so it could not be deleted`);
|
|
4563
|
+
if (hasBody) unsettledBodies.add(entry.Key);
|
|
4564
|
+
continue;
|
|
4565
|
+
}
|
|
4155
4566
|
stale.push({
|
|
4156
4567
|
Key: entry.Key,
|
|
4157
4568
|
VersionId: entry.VersionId
|
|
4158
4569
|
});
|
|
4570
|
+
if (hasBody) purged.add(entry.Key);
|
|
4159
4571
|
}
|
|
4160
4572
|
for (let i = 0; i < stale.length; i += DELETE_BATCH_SIZE) {
|
|
4161
4573
|
const batch = stale.slice(i, i + DELETE_BATCH_SIZE);
|
|
@@ -4182,7 +4594,10 @@ async function purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields,
|
|
|
4182
4594
|
}
|
|
4183
4595
|
}
|
|
4184
4596
|
if (resp.IsTruncated === true && resp.NextKeyMarker === void 0) {
|
|
4185
|
-
for (const key of wanted) if (key.startsWith(prefix))
|
|
4597
|
+
for (const key of wanted) if (key.startsWith(prefix)) {
|
|
4598
|
+
recordFailure(failed, key, TRUNCATED_NO_MARKER);
|
|
4599
|
+
unsettledBodies.add(key);
|
|
4600
|
+
}
|
|
4186
4601
|
}
|
|
4187
4602
|
keyMarker = resp.IsTruncated === true ? resp.NextKeyMarker : void 0;
|
|
4188
4603
|
versionIdMarker = keyMarker !== void 0 ? resp.NextVersionIdMarker : void 0;
|
|
@@ -6073,6 +6488,374 @@ function mergeEnv(overrides) {
|
|
|
6073
6488
|
else merged[k] = v;
|
|
6074
6489
|
return merged;
|
|
6075
6490
|
}
|
|
6491
|
+
/** Replaces a redacted argv VALUE wherever this module masks one. */
|
|
6492
|
+
const REDACTED_ARGV_VALUE = "***";
|
|
6493
|
+
/**
|
|
6494
|
+
* `docker` argv flags whose NEXT token is a `KEY=VALUE` pair built from
|
|
6495
|
+
* user-supplied template data, and whose VALUE therefore must not reach a
|
|
6496
|
+
* user-visible error string:
|
|
6497
|
+
*
|
|
6498
|
+
* - `-e` / `--env` — a container's `Environment.Variables` (Lambda) or
|
|
6499
|
+
* `ContainerDefinition.Environment` (ECS), plus `--env-vars` overrides.
|
|
6500
|
+
* Values that cdkd classifies as sensitive never get here at all
|
|
6501
|
+
* ({@link partitionSensitiveEnv} emits those as a value-less `-e KEY`),
|
|
6502
|
+
* but everything else does: connection strings, endpoints, API base URLs.
|
|
6503
|
+
* - `--opt` — `DockerVolumeConfiguration.DriverOpts`, which for the `local`
|
|
6504
|
+
* driver carries mount options (`o=addr=…,username=…,password=…`).
|
|
6505
|
+
* - `--label` — `DockerVolumeConfiguration.Labels`, user-authored metadata.
|
|
6506
|
+
*
|
|
6507
|
+
* A flag NOT in this list keeps its value. For most of the argv that is
|
|
6508
|
+
* because the value is cdkd-authored or infrastructure-shaped — a container
|
|
6509
|
+
* id, an image ref, a `--subnet` CIDR, a `--format` template — and is the
|
|
6510
|
+
* diagnostic.
|
|
6511
|
+
*
|
|
6512
|
+
* That is NOT true of the whole remainder, and the residual is recorded here
|
|
6513
|
+
* rather than quietly implied: `--health-cmd`, `--ulimit`, `--link`,
|
|
6514
|
+
* `--entrypoint`, `--workdir` and the positional image + container command are
|
|
6515
|
+
* all template-supplied and still echoed. They stay unmasked deliberately —
|
|
6516
|
+
* each is a COMMAND or a structural knob whose text IS what an operator reads
|
|
6517
|
+
* the failure for, and none is a documented place to put a secret, unlike
|
|
6518
|
+
* `Environment` / `Secrets` / `DriverOpts`. Revisit per flag if a real leak is
|
|
6519
|
+
* found through one; do not widen the set on suspicion, since every addition
|
|
6520
|
+
* trades away diagnostic text.
|
|
6521
|
+
*/
|
|
6522
|
+
const ARGV_VALUE_BEARING_FLAGS = /* @__PURE__ */ new Set([
|
|
6523
|
+
"-e",
|
|
6524
|
+
"--env",
|
|
6525
|
+
"--opt",
|
|
6526
|
+
"--label"
|
|
6527
|
+
]);
|
|
6528
|
+
/**
|
|
6529
|
+
* Structural, whitespace-delimited form of {@link ARGV_VALUE_BEARING_FLAGS}
|
|
6530
|
+
* for scanning a STRING that embeds a space-joined argv. Keyed on the FLAG's
|
|
6531
|
+
* position — never on the secret's value, so an unrelated literal that merely
|
|
6532
|
+
* coincides with a value is left alone.
|
|
6533
|
+
*
|
|
6534
|
+
* What each piece actually buys, since one of them was mis-credited in review:
|
|
6535
|
+
* the `(^|\s)` lead is what stops `-e` matching inside `--env` (there is no
|
|
6536
|
+
* `m` flag, so `^` is start-of-INPUT), NOT the alternation order — reordering
|
|
6537
|
+
* the branches leaves every test green. The order is kept as belt and braces
|
|
6538
|
+
* only. The mandatory `\s+` after the flag IS load-bearing: it is what stops
|
|
6539
|
+
* `-easy` / `--environment` matching. The KEY is `[^\s=]*` rather than `+` so
|
|
6540
|
+
* an EMPTY key (`-e =value`) is masked too.
|
|
6541
|
+
*/
|
|
6542
|
+
const ARGV_VALUE_TOKEN_RE = /(^|\s)(--env|--label|--opt|-e)(\s+)([^\s=]*)=\S*/g;
|
|
6543
|
+
/**
|
|
6544
|
+
* Return a copy of `args` with the VALUE of every
|
|
6545
|
+
* {@link ARGV_VALUE_BEARING_FLAGS} pair replaced by `***`. The KEY survives —
|
|
6546
|
+
* "which variable" is the diagnostic, "what it was set to" is the disclosure.
|
|
6547
|
+
*
|
|
6548
|
+
* A value-less `-e KEY` (the form {@link partitionSensitiveEnv} emits for a
|
|
6549
|
+
* sensitive key) has nothing to mask and is returned unchanged. `args` is
|
|
6550
|
+
* never mutated: the redacted copy is for DISPLAY only, never for `spawn`.
|
|
6551
|
+
*/
|
|
6552
|
+
function redactDockerArgvValues(args) {
|
|
6553
|
+
const out = [];
|
|
6554
|
+
for (let i = 0; i < args.length; i++) {
|
|
6555
|
+
const cur = args[i];
|
|
6556
|
+
const next = args[i + 1];
|
|
6557
|
+
if (ARGV_VALUE_BEARING_FLAGS.has(cur) && typeof next === "string") {
|
|
6558
|
+
const eqIdx = next.indexOf("=");
|
|
6559
|
+
if (eqIdx >= 0) {
|
|
6560
|
+
out.push(cur, `${next.substring(0, eqIdx)}=${REDACTED_ARGV_VALUE}`);
|
|
6561
|
+
i++;
|
|
6562
|
+
continue;
|
|
6563
|
+
}
|
|
6564
|
+
}
|
|
6565
|
+
out.push(cur);
|
|
6566
|
+
}
|
|
6567
|
+
return out;
|
|
6568
|
+
}
|
|
6569
|
+
/**
|
|
6570
|
+
* Redact argv-borne values inside an error TEXT before it reaches the user.
|
|
6571
|
+
*
|
|
6572
|
+
* **Wrap EVERY `execFile`-derived docker failure text in this**, including one
|
|
6573
|
+
* whose argv carries no user data today — `execFile` puts the WHOLE command
|
|
6574
|
+
* line into `err.message` (`Command failed: <file> <args joined by ' '>\n<stderr>`,
|
|
6575
|
+
* measured on Node 24), so any argv that later gains a `-e` pair starts leaking
|
|
6576
|
+
* with no edit to the error site. That silent-gap shape is exactly what
|
|
6577
|
+
* [#2440](https://github.com/go-to-k/cdkd/issues/2440) reported: the debug log
|
|
6578
|
+
* one screen earlier redacted the same argv and the error path did not.
|
|
6579
|
+
*
|
|
6580
|
+
* Two passes, both structural (positional), never value-based — matching on a
|
|
6581
|
+
* secret's VALUE would also blank an unrelated string that happens to equal it:
|
|
6582
|
+
*
|
|
6583
|
+
* 1. **Exact command-line substitution** (only when `args` is given). The raw
|
|
6584
|
+
* `args.join(' ')` is replaced by {@link redactDockerArgvValues}' rendering
|
|
6585
|
+
* of the same array. This is the pass that matters, because it is the only
|
|
6586
|
+
* one that can mask a value CONTAINING WHITESPACE (`-e CFG={"a": 1}`), which
|
|
6587
|
+
* no token scan of a space-joined string can delimit.
|
|
6588
|
+
* 2. **Token scan** ({@link ARGV_VALUE_TOKEN_RE}) over the result. Catches an
|
|
6589
|
+
* argv echoed in a shape pass 1 cannot see — a future Node message format,
|
|
6590
|
+
* a docker stderr quoting the flag back, or a call site with no `args` to
|
|
6591
|
+
* hand. Deliberately fail-loud rather than fail-open: a `-e X=y` occurring
|
|
6592
|
+
* in unrelated stderr prose loses its value and keeps its key.
|
|
6593
|
+
*
|
|
6594
|
+
* Idempotent (`-e KEY=***` re-masks to itself), so double-wrapping is safe.
|
|
6595
|
+
*
|
|
6596
|
+
* **Call sites should use a composer** ({@link describeDockerFailure} and its
|
|
6597
|
+
* siblings) rather than this function: the composers take a REQUIRED `args`,
|
|
6598
|
+
* which is what makes the redaction impossible to forget. This is the
|
|
6599
|
+
* primitive they are built from, exported so the four passes can be tested
|
|
6600
|
+
* against Node's real message shapes directly — it has no other caller in
|
|
6601
|
+
* `src/`, and that is deliberate rather than an oversight.
|
|
6602
|
+
*/
|
|
6603
|
+
function redactDockerArgvInText(text, args) {
|
|
6604
|
+
let out = text;
|
|
6605
|
+
if (args && args.length > 0) {
|
|
6606
|
+
const maskedArgs = redactDockerArgvValues(args);
|
|
6607
|
+
const raw = args.join(" ");
|
|
6608
|
+
const masked = maskedArgs.join(" ");
|
|
6609
|
+
if (masked !== raw) out = out.split(raw).join(masked);
|
|
6610
|
+
for (let i = 0; i < args.length; i++) {
|
|
6611
|
+
const rawArg = args[i];
|
|
6612
|
+
const maskedArg = maskedArgs[i];
|
|
6613
|
+
if (maskedArg === rawArg) continue;
|
|
6614
|
+
if (!isSubstitutableToken(rawArg)) continue;
|
|
6615
|
+
out = out.split(rawArg).join(maskedArg);
|
|
6616
|
+
const escapedRaw = nodeQuotedRendering(rawArg);
|
|
6617
|
+
const escapedMasked = nodeQuotedRendering(maskedArg);
|
|
6618
|
+
if (escapedRaw !== void 0 && escapedMasked !== void 0 && escapedRaw !== rawArg) out = out.split(escapedRaw).join(escapedMasked);
|
|
6619
|
+
}
|
|
6620
|
+
out = repairSpawnRefusal(out, args, maskedArgs);
|
|
6621
|
+
}
|
|
6622
|
+
return out.replace(ARGV_VALUE_TOKEN_RE, (_match, lead, flag, gap, key) => `${lead}${flag}${gap}${key}=${REDACTED_ARGV_VALUE}`);
|
|
6623
|
+
}
|
|
6624
|
+
/**
|
|
6625
|
+
* Shortest VALUE that pass 1b will substitute as a bare token.
|
|
6626
|
+
*
|
|
6627
|
+
* Pass 1b's needle is the whole `KEY=VALUE` element, and it is replaced
|
|
6628
|
+
* EVERYWHERE in the text — so a tiny needle is a liability rather than a
|
|
6629
|
+
* protection. The empty-key case makes that concrete: a non-sensitive
|
|
6630
|
+
* `{ Name: '', Value: '1' }` produces the two-character token `=1`, and
|
|
6631
|
+
* substituting that rewrites every `=1` in the message (`--cpus=1`,
|
|
6632
|
+
* `status=1`, a path segment). Below this floor the value is not worth
|
|
6633
|
+
* protecting by substring match, and passes 1 and 3 still cover it in the
|
|
6634
|
+
* message shapes that carry a `-e ` prefix or the joined command line.
|
|
6635
|
+
*/
|
|
6636
|
+
const MIN_SUBSTITUTABLE_VALUE_LENGTH = 4;
|
|
6637
|
+
/** Is this `KEY=VALUE` element long enough to substitute as a bare token? */
|
|
6638
|
+
function isSubstitutableToken(arg) {
|
|
6639
|
+
const eqIdx = arg.indexOf("=");
|
|
6640
|
+
if (eqIdx < 0) return false;
|
|
6641
|
+
return arg.length - eqIdx - 1 >= MIN_SUBSTITUTABLE_VALUE_LENGTH;
|
|
6642
|
+
}
|
|
6643
|
+
/**
|
|
6644
|
+
* Render `value` the way Node renders an argv element it quotes back at you —
|
|
6645
|
+
* i.e. with the SAME function Node used, `util.inspect`, minus the quote
|
|
6646
|
+
* characters it chose.
|
|
6647
|
+
*
|
|
6648
|
+
* This started as a hand-rolled `\xNN` escaper and it was WRONG for 16 of the
|
|
6649
|
+
* first 128 code points. Measured against real `execFile` rejections on Node
|
|
6650
|
+
* 24.19.0 (this repo, 2026-09-05): hand-rolled matched 4 of 13 cases,
|
|
6651
|
+
* `inspect` matched 13 of 13.
|
|
6652
|
+
*
|
|
6653
|
+
* input Node emits hand-rolled built
|
|
6654
|
+
* LF `K=a\nb\x00S` `K=a\x0ab\x00S` (Node uses a short escape)
|
|
6655
|
+
* DEL `K=a\x7Fb\x00S` `K=a\x7fb\x00S` (Node uses UPPERCASE hex)
|
|
6656
|
+
* backslash `K=a\\b\x00S` `K=a\b\x00S` (not escaped at all)
|
|
6657
|
+
*
|
|
6658
|
+
* Every miss printed the whole secret. The trigger for this path is a NUL in
|
|
6659
|
+
* a value — binary-ish data, which almost always carries a second control
|
|
6660
|
+
* byte or a backslash — so the table missed the REALISTIC case and hit only
|
|
6661
|
+
* the synthetic NUL-alone one its own fixture happened to use.
|
|
6662
|
+
*
|
|
6663
|
+
* `slice(1, -1)` is safe across the quote Node picks: `inspect` switches
|
|
6664
|
+
* between `'`, `"` and a backtick depending on content and escapes whichever
|
|
6665
|
+
* it chose; because this is the same call Node made, the rendering matches
|
|
6666
|
+
* whatever it picked (verified for a value containing a single quote, a
|
|
6667
|
+
* double quote, both, and a backtick).
|
|
6668
|
+
*
|
|
6669
|
+
* The general lesson, and why this is no longer a table: do not re-implement
|
|
6670
|
+
* another program's formatter — call it.
|
|
6671
|
+
*/
|
|
6672
|
+
function nodeQuotedRendering(value) {
|
|
6673
|
+
const rendered = inspect(value);
|
|
6674
|
+
const quote = rendered[0];
|
|
6675
|
+
if (quote === void 0 || !`'"\``.includes(quote)) return void 0;
|
|
6676
|
+
if (rendered.length < 2 || !rendered.endsWith(quote)) return void 0;
|
|
6677
|
+
const inner = rendered.slice(1, -1);
|
|
6678
|
+
return inner.includes(`${quote} +`) ? void 0 : inner;
|
|
6679
|
+
}
|
|
6680
|
+
/**
|
|
6681
|
+
* Node's refusal to spawn, which QUOTES ONE argv element and names its INDEX:
|
|
6682
|
+
*
|
|
6683
|
+
* The argument 'args[2]' must be a string without null bytes. Received '…'
|
|
6684
|
+
*
|
|
6685
|
+
* The one message shape where the index tells us exactly which of OUR args is
|
|
6686
|
+
* being echoed — so the clause can be REWRITTEN from cdkd's own copy of the
|
|
6687
|
+
* element rather than searched for. That matters because Node does not print
|
|
6688
|
+
* the element whole: it truncates near 200 characters and renders a
|
|
6689
|
+
* newline-bearing value as concatenated chunks, so no needle can match it.
|
|
6690
|
+
*
|
|
6691
|
+
* Global, and scanned to exhaustion rather than bailing on the first match.
|
|
6692
|
+
* Anchoring at `^` was an earlier attempt at forgery resistance and it was
|
|
6693
|
+
* WORSE: it made a refusal anywhere but position 0 unrepairable, which is a
|
|
6694
|
+
* LEAK, and a leak beats a truncated message every time. Pinning Node's
|
|
6695
|
+
* literal prefix keeps the forgery bar high without that trade.
|
|
6696
|
+
*
|
|
6697
|
+
* Pinning the wording has its own cost: if Node rewords the message the repair
|
|
6698
|
+
* silently stops firing. That is why `tests/unit/utils/docker-cmd.test.ts`
|
|
6699
|
+
* drives this path from a REAL rejection — a reword turns CI red instead of
|
|
6700
|
+
* turning the redaction off.
|
|
6701
|
+
*/
|
|
6702
|
+
const SPAWN_REFUSAL_RE = /The argument 'args\[(\d+)\]'[^']*?Received /g;
|
|
6703
|
+
/**
|
|
6704
|
+
* The quoted element Node prints after `Received `.
|
|
6705
|
+
*
|
|
6706
|
+
* Three shapes, and the third is the one that matters. `inspect` renders a
|
|
6707
|
+
* newline-bearing value as chunks joined by ` +`, and Node then SLICES the
|
|
6708
|
+
* whole rendering at 128 characters and appends `...` — so the last chunk is
|
|
6709
|
+
* usually UNTERMINATED:
|
|
6710
|
+
*
|
|
6711
|
+
* Received 'DB_URL=AAA…\n' +
|
|
6712
|
+
* 'hunter2SECRETCCC...
|
|
6713
|
+
*
|
|
6714
|
+
* A pattern that only accepts complete chunks stops after the second `'` and
|
|
6715
|
+
* leaves that tail in place. Measured on a real rejection: 272 of 288 probe
|
|
6716
|
+
* shapes leaked the secret verbatim, and the end-of-string version this
|
|
6717
|
+
* replaced did not — the bound was a regression, not a hardening, until the
|
|
6718
|
+
* trailing open chunk was admitted.
|
|
6719
|
+
*
|
|
6720
|
+
* The separator is Node's own (` +\n `) with HORIZONTAL space only, and an
|
|
6721
|
+
* open chunk runs to end of LINE. Both are deliberate: `\s*\+\s*` and an
|
|
6722
|
+
* unbounded tail let a crafted value swallow the diagnostic lines that follow
|
|
6723
|
+
* the clause.
|
|
6724
|
+
*/
|
|
6725
|
+
const QUOTED_CHUNK = String.raw`'(?:[^'\\\n]|\\.)*'|"(?:[^"\\\n]|\\.)*"|\`(?:[^\`\\\n]|\\.)*\``;
|
|
6726
|
+
/** An opening quote whose closing one was truncated away; bounded to its line. */
|
|
6727
|
+
const OPEN_CHUNK = String.raw`['"\`](?:[^\\\n]|\\.)*`;
|
|
6728
|
+
/** Node's chunk join: horizontal space, `+`, at most one newline, horizontal space. */
|
|
6729
|
+
const CHUNK_SEPARATOR = String.raw`[^\S\n]*\+[^\S\n]*\n?[^\S\n]*`;
|
|
6730
|
+
const QUOTED_ELEMENT_RE = new RegExp(`^(?:(?:${QUOTED_CHUNK})(?:${CHUNK_SEPARATOR}(?:${QUOTED_CHUNK}))*(?:${CHUNK_SEPARATOR}(?:${OPEN_CHUNK}))?|(?:${OPEN_CHUNK}))`);
|
|
6731
|
+
/**
|
|
6732
|
+
* Replace the quoted argv element in a spawn-refusal message with the masked
|
|
6733
|
+
* rendering of the arg its OWN index names.
|
|
6734
|
+
*
|
|
6735
|
+
* Known cosmetic effect: the replacement prints the FULL key where Node had
|
|
6736
|
+
* truncated, so a pathologically long env-var NAME makes the message longer
|
|
6737
|
+
* than Node's ~200 characters. Keys are not secret, and the width is not worth
|
|
6738
|
+
* a second truncation rule with its own edge cases.
|
|
6739
|
+
*/
|
|
6740
|
+
function repairSpawnRefusal(text, args, maskedArgs) {
|
|
6741
|
+
const scanner = new RegExp(SPAWN_REFUSAL_RE.source, "g");
|
|
6742
|
+
let out = "";
|
|
6743
|
+
let cursor = 0;
|
|
6744
|
+
for (let match = scanner.exec(text); match !== null; match = scanner.exec(text)) {
|
|
6745
|
+
if (match.index < cursor) continue;
|
|
6746
|
+
const index = Number(match[1]);
|
|
6747
|
+
const rawArg = args[index];
|
|
6748
|
+
const maskedArg = maskedArgs[index];
|
|
6749
|
+
if (rawArg !== void 0 && maskedArg === rawArg) continue;
|
|
6750
|
+
const clauseEnd = match.index + match[0].length;
|
|
6751
|
+
const quoted = QUOTED_ELEMENT_RE.exec(text.slice(clauseEnd));
|
|
6752
|
+
if (quoted === null) continue;
|
|
6753
|
+
const replacement = maskedArg === void 0 ? `'${REDACTED_ARGV_VALUE}'` : inspect(maskedArg);
|
|
6754
|
+
out += text.slice(cursor, clauseEnd) + replacement;
|
|
6755
|
+
cursor = clauseEnd + quoted[0].length;
|
|
6756
|
+
scanner.lastIndex = cursor;
|
|
6757
|
+
}
|
|
6758
|
+
return out + text.slice(cursor);
|
|
6759
|
+
}
|
|
6760
|
+
/**
|
|
6761
|
+
* Compose a user-visible description of a `child_process` rejection from a
|
|
6762
|
+
* docker call, ALREADY REDACTED against that call's own argv.
|
|
6763
|
+
*
|
|
6764
|
+
* Moved here from `src/local/invoke-agentcore-watch-loop.ts` in issue #2440's
|
|
6765
|
+
* review round 3, and the move is the point: `args` is REQUIRED, so a call
|
|
6766
|
+
* site cannot obtain the text without handing over the argv to redact it
|
|
6767
|
+
* with. That is a stronger guarantee than any text fence over the call sites
|
|
6768
|
+
* — which is what the round-2 reviewers demonstrated, by writing four
|
|
6769
|
+
* spellings of an unredacted read that the fence could not see.
|
|
6770
|
+
*
|
|
6771
|
+
* Shape differs from the `stderr || message` composition the other sites use,
|
|
6772
|
+
* and deliberately so: `err.stderr` is where docker writes its actionable
|
|
6773
|
+
* diagnostics, while `err.message` carries the exit status, so the AgentCore
|
|
6774
|
+
* soft-reload path APPENDS rather than prefers — without stderr the wrapped
|
|
6775
|
+
* error would only say "Command failed with exit code N".
|
|
6776
|
+
*/
|
|
6777
|
+
function describeDockerExecFailure(error, args) {
|
|
6778
|
+
const message = thrownMessageText(error) || safeStringify(error);
|
|
6779
|
+
const stderrText = capturedStreamText(error, "stderr");
|
|
6780
|
+
return redactDockerArgvInText(stderrText ? `${message}\n${stderrText}` : message, args);
|
|
6781
|
+
}
|
|
6782
|
+
/**
|
|
6783
|
+
* A captured stream of a `child_process` rejection as trimmed text, or `''`.
|
|
6784
|
+
*
|
|
6785
|
+
* Takes `unknown`, not `Error`, and duck-types the field. Every composer here
|
|
6786
|
+
* is called from a `catch`, where the value is whatever was thrown — and the
|
|
6787
|
+
* shapes the call sites actually see are plain objects
|
|
6788
|
+
* (`{ stderr, message }`), `SpawnError`, and cross-realm `Error`s that
|
|
6789
|
+
* `instanceof` misses. An earlier revision narrowed this to `Error` and the
|
|
6790
|
+
* standard composer then wrapped non-Errors in a FRESH `Error`, which has no
|
|
6791
|
+
* `.stderr` at all — so the whole diagnostic silently became
|
|
6792
|
+
* `'[object Object]'` for exactly the shape most of the call sites throw.
|
|
6793
|
+
*
|
|
6794
|
+
* `execFile` hands back a string under the default encoding and a `Buffer`
|
|
6795
|
+
* under `encoding: 'buffer'`; `ArrayBuffer.isView` covers a plain
|
|
6796
|
+
* `Uint8Array` too.
|
|
6797
|
+
*/
|
|
6798
|
+
function capturedStreamText(error, field) {
|
|
6799
|
+
try {
|
|
6800
|
+
const raw = error?.[field];
|
|
6801
|
+
if (typeof raw === "string") return raw.trim();
|
|
6802
|
+
if (ArrayBuffer.isView(raw)) return Buffer.from(raw.buffer, raw.byteOffset, raw.byteLength).toString("utf8").trim();
|
|
6803
|
+
} catch {}
|
|
6804
|
+
return "";
|
|
6805
|
+
}
|
|
6806
|
+
/** A thrown value's `message` as text, or `''`. Duck-typed, for the same reason. */
|
|
6807
|
+
function thrownMessageText(error) {
|
|
6808
|
+
try {
|
|
6809
|
+
const raw = error?.["message"];
|
|
6810
|
+
return typeof raw === "string" ? raw : "";
|
|
6811
|
+
} catch {
|
|
6812
|
+
return "";
|
|
6813
|
+
}
|
|
6814
|
+
}
|
|
6815
|
+
/**
|
|
6816
|
+
* `String(value)` that cannot itself throw. A rejection with a null prototype
|
|
6817
|
+
* (or a `toString` that throws) would otherwise blow up INSIDE a `catch` — and
|
|
6818
|
+
* two of these call sites are in `cleanupEcsRun`, where an exception aborts
|
|
6819
|
+
* the remaining volume / network teardown and leaks real Docker resources.
|
|
6820
|
+
*/
|
|
6821
|
+
function safeStringify(error) {
|
|
6822
|
+
try {
|
|
6823
|
+
return String(error);
|
|
6824
|
+
} catch {
|
|
6825
|
+
return "[unstringifiable rejection]";
|
|
6826
|
+
}
|
|
6827
|
+
}
|
|
6828
|
+
/**
|
|
6829
|
+
* The STANDARD composer for a docker failure text: the captured stderr if
|
|
6830
|
+
* there is any, else the error's own message, else its string form —
|
|
6831
|
+
* ALREADY REDACTED against that call's argv.
|
|
6832
|
+
*
|
|
6833
|
+
* Use this at every site that wraps a docker `execFile` / spawn rejection.
|
|
6834
|
+
* `args` is REQUIRED, which is the whole point: a call site cannot obtain the
|
|
6835
|
+
* text without handing over the argv to redact it with, so the guarantee is
|
|
6836
|
+
* type-checked rather than fenced. Issue #2440's review spent three rounds
|
|
6837
|
+
* showing that a text fence over hand-composed sites is evadable — an
|
|
6838
|
+
* intermediate variable, an inline cast, a destructure, a computed member, a
|
|
6839
|
+
* concat — while this shape has nothing to evade.
|
|
6840
|
+
*
|
|
6841
|
+
* Prefer stderr over message because docker writes its actionable diagnostic
|
|
6842
|
+
* there, and `execFile`'s message is mostly the command line plus an exit
|
|
6843
|
+
* status. {@link describeDockerExecFailure} keeps BOTH, for a caller whose
|
|
6844
|
+
* wrapper text needs the status as well.
|
|
6845
|
+
*/
|
|
6846
|
+
function describeDockerFailure(error, args) {
|
|
6847
|
+
return redactDockerArgvInText(capturedStreamText(error, "stderr") || thrownMessageText(error) || safeStringify(error), args);
|
|
6848
|
+
}
|
|
6849
|
+
/**
|
|
6850
|
+
* Composer for a captured-output failure where the diagnostic may be on
|
|
6851
|
+
* STDOUT rather than stderr (`runDockerStreaming`'s non-zero-exit path, whose
|
|
6852
|
+
* `SpawnError` carries both). `fallback` is used when neither stream said
|
|
6853
|
+
* anything. Redacted, and `args` is required, for the same reason as
|
|
6854
|
+
* {@link describeDockerFailure}.
|
|
6855
|
+
*/
|
|
6856
|
+
function describeDockerCapturedOutput(error, args, fallback) {
|
|
6857
|
+
return redactDockerArgvInText(capturedStreamText(error, "stderr") || capturedStreamText(error, "stdout") || fallback, args);
|
|
6858
|
+
}
|
|
6076
6859
|
|
|
6077
6860
|
//#endregion
|
|
6078
6861
|
//#region src/assets/docker-build.ts
|
|
@@ -22525,7 +23308,7 @@ var CloudControlProvider = class {
|
|
|
22525
23308
|
const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
|
|
22526
23309
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
22527
23310
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
22528
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
23311
|
+
const { ASGProvider } = await import("./asg-provider-uaOnRUMe.js").then((n) => n.n);
|
|
22529
23312
|
return withIndeterminateGuard(await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
|
|
22530
23313
|
}
|
|
22531
23314
|
const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
|
|
@@ -29614,7 +30397,7 @@ function isStatefulRecreateTargetForReplace(resourceType, recordedProperties) {
|
|
|
29614
30397
|
function renderStatefulReason(reason) {
|
|
29615
30398
|
switch (reason) {
|
|
29616
30399
|
case "always": return "destroy loses all data in the resource";
|
|
29617
|
-
case "has-objects": return "S3 bucket is
|
|
30400
|
+
case "has-objects": return "S3 bucket is not provably empty";
|
|
29618
30401
|
case "has-retention": return "log group retains data (RetentionInDays > 0)";
|
|
29619
30402
|
case "has-log-events": return "log group is not provably empty";
|
|
29620
30403
|
case null: return "(not stateful)";
|
|
@@ -34818,5 +35601,5 @@ var DeployEngine = class {
|
|
|
34818
35601
|
};
|
|
34819
35602
|
|
|
34820
35603
|
//#endregion
|
|
34821
|
-
export { beginCommandInterruptScope as $,
|
|
34822
|
-
//# sourceMappingURL=deploy-engine
|
|
35604
|
+
export { beginCommandInterruptScope as $, synthesisStatusMessage as $n, withErrorHandling as $r, maskSecretsInError as $t, formatResourceLine as A, ensureAssetStorage as An, AssetError as Ar, requireConfigString as At, isExportAliasCollision as B, describeDockerCapturedOutput as Bn, LockError as Br, INTRINSIC_KEYS as Bt, unsupportedFinalSnapshotError as C, loadPublishableAssetManifest as Cn, processStackMessages as Cr, coerceCfnBoolean as Ct, isStatefulRecreateTargetForReplace as D, AssetModeResolver as Dn, getAwsClients as Dr, replayWarn as Dt, MULTI_REGION_RECREATE_BLOCKED_TYPES as E, stripControlChars as En, AwsClients as Er, readConfigString as Et, red as F, validateAssetBucketName as Fn, DeployCancelledError as Fr, s3BucketDualStackDomainName as Ft, collectInlinePolicyNamesManagedBySiblings as G, getDockerCmd as Gn, ResourceUpdateNotSupportedError as Gr, STATE_SOURCED_CROSS_GENERATION_RULES as Gt, stateKeySecretExposure as H, describeDockerFailure as Hn, PartialFailureError as Hr, withRetry as Ht, yellow as I, validateContainerRepoName as In, DynamicReferenceRegionAmbiguousError as Ir, s3BucketRegionalDomainName as It, findActionableSilentDrops as J, runDockerForeground as Jn, StateError as Jr, carriesSecretMask as Jt, clearOnUpdateRemoval as K, partitionSensitiveEnv as Kn, StackHasActiveImportsError as Kr, STATE_SOURCED_READBACK_RULES as Kt, collectDeclaredOutputNames as L, buildDenyExternalAccessPolicy as Ln, IntrinsicResolutionRefusalError as Lr, s3BucketWebsiteUrl as Lt, cyan as M, isCrossRegionRedirect as Mn, ConfigError as Mr, producerRegionsFromState as Mt, gray as N, parseBootstrapMarker as Nn, CrossAccountSecretRefusalError as Nr, s3BucketArn as Nt, isStatefulRecreateTargetSync as O, BOOTSTRAP_MARKER_PREFIX as On, resetAwsClients as Or, requireConfigArray as Ot, green as P, readBootstrapMarkerBody as Pn, DependencyError as Pr, s3BucketDomainName as Pt, maskerOrIdentity as Q, Synthesizer as Qn, normalizeAwsError as Qr, isSingleDynamicReferenceToken as Qt, collectPublishedOutputNames as R, describeAwsFailure as Rn, LocalInvokeBuildError as Rr, applyRoleArnIfSet as Rt, refusesFinalSnapshot as S, createAssetRedirectResolver as Sn, AssemblyReader as Sr, assertRegionMatch as St, extractDeploymentEventError as T, escapeRegExp$1 as Tn, resolveBucketRegion as Tr, configStringRefusal as Tt, getCurrentResourceSecrets as U, dockerSpawnEnvWithSensitive as Un, ProvisioningError as Ur, DagBuilder as Ut, secretBearingStateKeyWarning as V, describeDockerExecFailure as Vn, NestedStackChildDirectDestroyError as Vr, describeTypeWithThrottleRetry as Vt, IAMRoleProvider as W, formatDockerLoginError as Wn, ResourceTimeoutError as Wr, TemplateParser as Wt, createMaskedRetryLogger as X, AssetManifestLoader as Xn, formatError as Xr, dynamicReferenceTokens as Xt, findSilentDropProperties as Y, runDockerStreaming as Yn, SynthesisError as Yr, createSecretMasker as Yt, maskDeep as Z, getDockerImageBySourceHash as Zn, isCdkdError as Zr, errorCauseChain as Zt, PRE_DELETE_SNAPSHOT_TYPES as _, shouldRetainResource as _n, displaySafe as _r, parameterTypeMayLoseSecretIdentity as _t, DeploymentEventsStore as a, markRedactedCause as ai, LockManager as an, resolveSkipPrefix as ar, slowCcOperationTimeoutMs as at, createPreDeleteFinalSnapshot as b, WorkGraph as bn, canonicalizeRegion as br, normalizeAwsTagsToCfn as bt, replayFailedOperations as c, UNRENDERABLE as cn, resolveUseCdkBootstrapAssets as cr, deleteSkipReason as ct, updatePartialReason as d, forceQuitRecoveryClause as dn, CFN_TEMPLATE_BODY_LIMIT as dr, IntrinsicFunctionResolver as dt, isMarkedNonRetryable as ei, maskSecretsInText as en, getDefaultStateBucketName as er, endCommandInterruptScope as et, withResourceDeadline as f, CUSTOM_RESOURCE_RESPONSE_PREFIX as fn, CFN_TEMPLATE_URL_LIMIT as fr, carriesDynamicReference as ft, ATOMIC_FINAL_SNAPSHOT_TYPES as g, importableOutputs as gn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as gr, isUnboundTemplateParameter as gt, computeImplicitDeleteEdges as h, importableOutputKeys as hn, uploadCfnTemplate as hr, getAccountInfo as ht, DeploymentEventsReader as i, markNonRetryable as ii, scrubResourceRecord as in, resolveCaptureObservedState as ir, CloudControlProvider as it, bold as j, getBootstrapMarkerKey as jn, CdkdError as jr, classifyReplaySecretRegion as jt, renderStatefulReason as k, assertAssetBucketRegion as kn, setAwsClients as kr, requireConfigObject as kt, replayRollback as l, buildForceUnlockCommand as ln, stateBucketExistenceConfirmed as lr, disableInstanceApiTermination as lt, IMPLICIT_DELETE_DEPENDENCIES as m, exportNamesCarriedFrom as mn, findLargeInlineResources as mr, coerceParameterTypedValue as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, isThrottlingError as ni, recoverMaskedOutput as nn, resolveApp as nr, isInterruptedWaitError as nt, planFailedOps as o, retryClassificationText as oi, S3StateBackend as on, resolveStateBucketWithDefault as or, UNSPECIFIED_SKIP_REASON as ot, maskingRetryLogger as p, DEFAULT_STATE_PREFIX as pn, MIGRATE_TMP_PREFIX as pr, cfnRefValueFromPhysicalId as pt, ProviderRegistry as q, redactDockerArgvValues as qn, StackTerminationProtectionError as qr, TEMPLATE_SOURCED_RULES as qt, DeployEngine as r, isTransientServerError as ri, redactSecretsForState as rn, resolveAutoAssetStorage as rr, startInterruptWatch as rt, planRollback as s, __exportAll as si, rebuildClientForBucketRegion as sn, resolveStateBucketWithDefaultAndSource as sr, deleteIndeterminateGuards as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, isRetryableTransientError as ti, recordMaskOnlyValue as tn, getLegacyStateBucketName as tr, interruptWatchListenerCount as tt, updatePartialMessage as u, buildLockContentionMessage as un, warnDeprecatedNoPrefixCliFlag as ur, isTerminationProtectionPropagationError as ut, buildFinalSnapshotIdentifier as v, AssetPublisher as vn, expectedOwnerParam as vr, refStateLookupFromResource as vt, makeCanonicalizePropertiesFn as w, rewriteTemplateAssetReferences as wn, clearBucketRegionCache as wr, configBooleanRefusal as wt, isFinalSnapshotError as x, buildAssetRedirectMap as xn, derivePartitionAndUrlSuffix as xr, resolveExplicitPhysicalId as xt, ccRoutedFinalSnapshotError as y, stringifyValue as yn, PARTITION_TABLE as yr, WAFv2WebACLProvider as yt, exportAliasCollisionScrubWarning as z, buildDockerImage as zn, LocalStartServiceError as zr, DiffCalculator as zt };
|
|
35605
|
+
//# sourceMappingURL=deploy-engine--rkIGhow.js.map
|