@go-to-k/cdkd 0.291.8 → 0.291.10
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-B3RJYZnx.js → asg-provider-BY-tj21g.js} +2 -2
- package/dist/{asg-provider-B3RJYZnx.js.map → asg-provider-BY-tj21g.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-eLdrQQ2R.js → deploy-engine-BomFOZF4.js} +902 -205
- package/dist/deploy-engine-BomFOZF4.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-Fgtmg1PN.js → program-BfCsjuyU.js} +551 -257
- package/dist/{program-Fgtmg1PN.js.map → program-BfCsjuyU.js.map} +1 -1
- package/dist/{version-D_XcHZoy.js → version-5OUy_OlQ.js} +2 -2
- package/dist/{version-D_XcHZoy.js.map → version-5OUy_OlQ.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-eLdrQQ2R.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { C as displayAwsMessage, D as truncateCodePoints, E as isPasteableIdent, S as UNRENDERABLE, T as displaySafe, d as applyDefaultNameForFallback, f as explicitNamePropertyFor, g as looksLikeCdkdGeneratedName, h as getCurrentStackName, m as generateResourceNameWithFallback, n as getLogger, p as generateResourceName, r as isStdoutReservedForPayload, s as getLiveRenderer, v as withStackName, w as displayIdent, x as STACK_REF_MAX_CODE_POINTS, y as withoutGeneratedFallbackName } from "./logger-GASr9bAF.js";
|
|
2
2
|
import { a as setAssumedRoleCredentials, c as currentStackAwsScope, i as getPreAssumeEnvCredentials, l as runInStackAwsScope, n as awsClientDefaults, o as setPreAssumeEnvCredentials, r as getAssumedRoleCredentials, s as ambientRegion, t as SECOND_ROLE_PUBLISH_REFUSAL } from "./aws-client-defaults-jISU4PHo.js";
|
|
3
|
-
import { t as getCdkdVersion } from "./version-
|
|
3
|
+
import { t as getCdkdVersion } from "./version-5OUy_OlQ.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";
|
|
@@ -10,7 +10,7 @@ import { SQSClient } from "@aws-sdk/client-sqs";
|
|
|
10
10
|
import { PublishCommand, SNSClient } from "@aws-sdk/client-sns";
|
|
11
11
|
import { GetFunctionCommand, GetFunctionUrlConfigCommand, InvokeCommand, LambdaClient, UpdateFunctionConfigurationCommand, waitUntilFunctionActiveV2, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
|
|
12
12
|
import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
|
|
13
|
-
import { CreateSnapshotCommand, DescribeAvailabilityZonesCommand, DescribeImagesCommand, DescribeInstancesCommand, DescribeLaunchTemplatesCommand, DescribeRouteTablesCommand, DescribeSecurityGroupsCommand, DescribeSnapshotsCommand, DescribeSubnetsCommand, DescribeVpcsCommand, DescribeVpnGatewaysCommand, EC2Client, ModifyInstanceAttributeCommand } from "@aws-sdk/client-ec2";
|
|
13
|
+
import { CreateSnapshotCommand, DeleteVolumeCommand, DescribeAvailabilityZonesCommand, DescribeImagesCommand, DescribeInstancesCommand, DescribeLaunchTemplatesCommand, DescribeRouteTablesCommand, DescribeSecurityGroupsCommand, DescribeSnapshotsCommand, DescribeSubnetsCommand, DescribeVolumesCommand, DescribeVpcsCommand, DescribeVpnGatewaysCommand, EC2Client, ModifyInstanceAttributeCommand } from "@aws-sdk/client-ec2";
|
|
14
14
|
import { DescribeTableCommand, DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
15
15
|
import { CloudFormationClient, CreateChangeSetCommand, DeleteStackCommand, DescribeChangeSetCommand, DescribeStacksCommand, DescribeTypeCommand, GetTemplateCommand, ListExportsCommand, waitUntilChangeSetCreateComplete } from "@aws-sdk/client-cloudformation";
|
|
16
16
|
import { APIGatewayClient, GetRestApiCommand } from "@aws-sdk/client-api-gateway";
|
|
@@ -35,9 +35,10 @@ import { GetHostedZoneCommand, ListHostedZonesByNameCommand, Route53Client } fro
|
|
|
35
35
|
import { DescribeListenersCommand, DescribeLoadBalancersCommand, ElasticLoadBalancingV2Client } from "@aws-sdk/client-elastic-load-balancing-v2";
|
|
36
36
|
import { KMSClient, ListAliasesCommand } from "@aws-sdk/client-kms";
|
|
37
37
|
import { readFile } from "fs/promises";
|
|
38
|
-
import { isAbsolute as isAbsolute$1, resolve as resolve$1 } from "path";
|
|
38
|
+
import { isAbsolute as isAbsolute$1, relative, resolve as resolve$1, sep } from "path";
|
|
39
39
|
import { CreateRepositoryCommand, DescribeImagesCommand as DescribeImagesCommand$1, DescribeRepositoriesCommand, ECRClient, GetAuthorizationTokenCommand, PutImageTagMutabilityCommand } from "@aws-sdk/client-ecr";
|
|
40
40
|
import { inspect } from "node:util";
|
|
41
|
+
import { realpathSync as realpathSync$1 } from "fs";
|
|
41
42
|
import { hostname } from "os";
|
|
42
43
|
import graphlib from "graphlib";
|
|
43
44
|
import { DescribeDBClustersCommand, DescribeDBInstancesCommand, RDSClient } from "@aws-sdk/client-rds";
|
|
@@ -1230,6 +1231,182 @@ function isUpdateUnsupportedError(error, logicalId) {
|
|
|
1230
1231
|
return false;
|
|
1231
1232
|
}
|
|
1232
1233
|
|
|
1234
|
+
//#endregion
|
|
1235
|
+
//#region src/utils/pasteable-command.ts
|
|
1236
|
+
/**
|
|
1237
|
+
* The way cdkd builds a `cdkd ...` command it tells an operator to PASTE.
|
|
1238
|
+
*
|
|
1239
|
+
* Thirteen message modules route through it today. The CATEGORIES it does not
|
|
1240
|
+
* cover yet are at the end of this header — categories, not a census: the
|
|
1241
|
+
* population is what go-to-k/cdkd#3436's greps return against the tree of the
|
|
1242
|
+
* day, and a list here would be read as complete and go stale.
|
|
1243
|
+
*
|
|
1244
|
+
* go-to-k/cdkd#3363 measured the class this module closes, and
|
|
1245
|
+
* [#3436](https://github.com/go-to-k/cdkd/issues/3436) records it repo-wide.
|
|
1246
|
+
* Three shapes, each of which a per-site discipline kept re-introducing:
|
|
1247
|
+
*
|
|
1248
|
+
* 1. **A command inside prose quotes.** `drop it whole with '<command>'` puts
|
|
1249
|
+
* the command in a `'...'` span, so an interpolated value carrying `'`
|
|
1250
|
+
* CLOSES that wrapper when the span is pasted WITH its quotes and the rest
|
|
1251
|
+
* runs as shell: `'cdkd state orphan S --state-bucket 'b; printf X; #''`
|
|
1252
|
+
* printed `X`. Quoting the value does not help — the wrapper is what
|
|
1253
|
+
* inverts. So a command is printed UNWRAPPED on its own labelled line, and
|
|
1254
|
+
* this module returns the text for exactly that.
|
|
1255
|
+
*
|
|
1256
|
+
* **UNWRAPPED always; LAST only when the message prints ONE command.** A
|
|
1257
|
+
* message offering a READ and a DESTRUCTIVE template prints one command per
|
|
1258
|
+
* LINE, and there the read ends a line while the TEMPLATE is last — the
|
|
1259
|
+
* rule `.claude/rules/state-malformed-containers.md` states for
|
|
1260
|
+
* go-to-k/cdkd#3516, and a shape this module already serves twice:
|
|
1261
|
+
* `deploy-engine.ts`'s `Inspect it with:` / `Drop the record with:` pair,
|
|
1262
|
+
* and `destroy-runner.ts`'s through `hintFor`. In both, the read command is
|
|
1263
|
+
* correctly NOT last. Collapsing such a pair onto one line is the defect
|
|
1264
|
+
* that rule names: the read must end a line, and the template must be last
|
|
1265
|
+
* with no substituted value after it.
|
|
1266
|
+
* 2. **A bare `<placeholder>`.** `<name>` is two redirections, not a word:
|
|
1267
|
+
* `<name` reads stdin from a file and `>` truncates whatever word follows.
|
|
1268
|
+
* {@link commandHole} quotes it, so it pastes as one literal argument.
|
|
1269
|
+
* 3. **A value whose quote context the PROSE already flipped.** An English
|
|
1270
|
+
* apostrophe earlier in the sentence (`this stack's name`) opens a shell
|
|
1271
|
+
* quote that closes at the value's own opening quote, leaving the value
|
|
1272
|
+
* bare. This module cannot reach that one — a value in prose is not a
|
|
1273
|
+
* command — which is why the rule it belongs to is "a shell-quoted value
|
|
1274
|
+
* goes on a labelled trailing line, never inside a sentence", and why
|
|
1275
|
+
* deleting apostrophes is NOT the remedy (the next sentence re-opens it).
|
|
1276
|
+
*
|
|
1277
|
+
* What this module owns is the ARGUMENT side of shape 1 and shape 2: every
|
|
1278
|
+
* user-controlled value is sanitized, gated on rendering EXACTLY, and either
|
|
1279
|
+
* shell-quoted or replaced by a quoted hole. A value sanitizing would ALTER is
|
|
1280
|
+
* never printed in its altered spelling — that addresses a DIFFERENT record
|
|
1281
|
+
* than the message means — and never silently dropped, which would resolve the
|
|
1282
|
+
* ambient default instead.
|
|
1283
|
+
*
|
|
1284
|
+
* {@link shellQuote} and {@link commandHole} live here rather than in
|
|
1285
|
+
* `../state/lock-contention-message.js`, which still re-exports them for its
|
|
1286
|
+
* own callers: `src/utils/**` imports nothing from `src/state/**`, so the
|
|
1287
|
+
* shared helper could not reach them the other way round.
|
|
1288
|
+
*
|
|
1289
|
+
* ## The CATEGORIES not covered yet, as of go-to-k/cdkd#3436's first half
|
|
1290
|
+
*
|
|
1291
|
+
* Re-derive the members with that issue's greps rather than trusting a list:
|
|
1292
|
+
* these are kinds of site, and each kind has more members than the examples.
|
|
1293
|
+
*
|
|
1294
|
+
* - **Its own copy of the gate.** `buildForceUnlockCommand`
|
|
1295
|
+
* (`state/lock-contention-message.ts`), the `cdkd orphan` properties refusal
|
|
1296
|
+
* (`state/malformed-resources-bag.ts`), `orphanCommandFor` (`cli/commands/
|
|
1297
|
+
* export.ts`), and others in `deployment/deploy-engine.ts`,
|
|
1298
|
+
* `deployment/rollback-executor.ts` and `cli/commands/gc.ts`. They behave the
|
|
1299
|
+
* same way; they are not this function, so a rule change reaches them only by
|
|
1300
|
+
* hand.
|
|
1301
|
+
* - **A command in prose quotes with a RAW value**, outside the modules
|
|
1302
|
+
* migrated here — `provisioning/providers/**` (S3 Tables, Route 53, DynamoDB),
|
|
1303
|
+
* `cli/config-loader.ts` and `cli/commands/orphan.ts` are where the greps land
|
|
1304
|
+
* today.
|
|
1305
|
+
* - **The `cdkd drift` sites of
|
|
1306
|
+
* [#3307](https://github.com/go-to-k/cdkd/issues/3307)**, which take their own
|
|
1307
|
+
* gate in that PR. Note a `cdkd drift` command also appears OUTSIDE that file.
|
|
1308
|
+
* - **A spelled-out template or usage synopsis printing a BARE `<hole>`**, and
|
|
1309
|
+
* the source fence that would find them.
|
|
1310
|
+
*/
|
|
1311
|
+
/**
|
|
1312
|
+
* Quote a value for a pasteable shell command.
|
|
1313
|
+
*
|
|
1314
|
+
* EXPORTED since issue [#2610]: `src/provisioning/replacement-protection-advice.ts`
|
|
1315
|
+
* prints `aws <service> ...` recovery commands naming a resource's physical id,
|
|
1316
|
+
* which is the same hazard one directory over. A second spelling of this
|
|
1317
|
+
* predicate is how the two would come to disagree about which values need
|
|
1318
|
+
* quoting -- the reason `display-safe.ts`'s header gives for not widening a
|
|
1319
|
+
* rule by hand. It is a pure function of its argument and imports nothing.
|
|
1320
|
+
*/
|
|
1321
|
+
function shellQuote(value) {
|
|
1322
|
+
return /^[A-Za-z0-9._/@:+-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* A placeholder for a value a pasteable command could not name, QUOTED.
|
|
1326
|
+
*
|
|
1327
|
+
* A bare `<profile>` is two shell redirections, not a word: pasted, `<profile`
|
|
1328
|
+
* reads stdin from a file named `profile` and `>` sends stdout to whatever
|
|
1329
|
+
* word follows. While the hole was the command's LAST word that was only a
|
|
1330
|
+
* syntax error; with flags appended after it (`--profile <profile>
|
|
1331
|
+
* --state-bucket b`) it ran the command with `--state-bucket` swallowed as a
|
|
1332
|
+
* redirect target and `b` as the profile — a delete against the ambient
|
|
1333
|
+
* bucket, with nothing printed (measured by the maintainer, M4 of the
|
|
1334
|
+
* go-to-k/cdkd#3363 review). Quoted, it pastes as one literal argument and
|
|
1335
|
+
* nothing else — no redirection, no swallowed flag. It is not refused: an
|
|
1336
|
+
* UNFILLED hole runs as that literal value (a stack named `<stack>`, a prefix
|
|
1337
|
+
* `<prefix>`).
|
|
1338
|
+
*/
|
|
1339
|
+
function commandHole(name) {
|
|
1340
|
+
return `'<${name}>'`;
|
|
1341
|
+
}
|
|
1342
|
+
/**
|
|
1343
|
+
* WHY a value must become a hole, or `undefined` when it may be NAMED.
|
|
1344
|
+
*
|
|
1345
|
+
* FIRST MATCH WINS, in the order written: `empty`, `altered`, `too-long`,
|
|
1346
|
+
* `option-shaped`, `pattern-shaped` (m21 of the go-to-k/cdkd#3499 review). A
|
|
1347
|
+
* value can satisfy several — `-\u001b[x` is both option-shaped and altered —
|
|
1348
|
+
* and the caller renders ONE sentence, so the order decides which. It runs
|
|
1349
|
+
* cheapest-and-most-fundamental first: a value that does not survive rendering
|
|
1350
|
+
* cannot be reasoned about as a command argument at all, so saying "this would
|
|
1351
|
+
* be read as an option" about a spelling that is not what is stored would be
|
|
1352
|
+
* the more misleading of the two true sentences. The order is also the order
|
|
1353
|
+
* the two functions this replaced already applied, which is what keeps the
|
|
1354
|
+
* refactor behaviour-preserving.
|
|
1355
|
+
*/
|
|
1356
|
+
function withholdReason(value, opts) {
|
|
1357
|
+
if (value === "") return "empty";
|
|
1358
|
+
const safe = displaySafe(value, { asciiOnly: true });
|
|
1359
|
+
if (safe !== value) return "altered";
|
|
1360
|
+
if (truncateCodePoints(safe, 1152).truncated) return "too-long";
|
|
1361
|
+
if (value.startsWith("-")) return "option-shaped";
|
|
1362
|
+
if (opts?.patternMatched === true && (value.includes("*") || value.includes("/"))) return "pattern-shaped";
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Build a pasteable `cdkd` command: every user-controlled value shell-quoted
|
|
1366
|
+
* behind an exactness gate, every placeholder quoted, nothing wrapped.
|
|
1367
|
+
*
|
|
1368
|
+
* `extraFlags` is where a caller appends the flags that pin the command to its
|
|
1369
|
+
* own account and key space — in practice `recoveryCommandFlags(recovery)`,
|
|
1370
|
+
* whose `exact` the caller folds into its own decision. They are appended
|
|
1371
|
+
* verbatim, LAST, because they are built by the same rules one layer up.
|
|
1372
|
+
*/
|
|
1373
|
+
function pasteableCommand(verb, args = [], extraFlags = []) {
|
|
1374
|
+
let exact = true;
|
|
1375
|
+
const withheld = [];
|
|
1376
|
+
const parts = [verb];
|
|
1377
|
+
for (const arg of args) {
|
|
1378
|
+
if ("literal" in arg) {
|
|
1379
|
+
parts.push(arg.literal);
|
|
1380
|
+
continue;
|
|
1381
|
+
}
|
|
1382
|
+
if (!("value" in arg)) {
|
|
1383
|
+
if ("flag" in arg) parts.push(arg.flag);
|
|
1384
|
+
parts.push(commandHole(arg.hole));
|
|
1385
|
+
exact = false;
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1388
|
+
const reason = withholdReason(arg.value, arg.opts);
|
|
1389
|
+
let rendered;
|
|
1390
|
+
if (reason === void 0) rendered = shellQuote(arg.value);
|
|
1391
|
+
else {
|
|
1392
|
+
rendered = commandHole(arg.hole);
|
|
1393
|
+
exact = false;
|
|
1394
|
+
withheld.push({
|
|
1395
|
+
hole: arg.hole,
|
|
1396
|
+
reason
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
if ("flag" in arg) parts.push(arg.flag);
|
|
1400
|
+
parts.push(rendered);
|
|
1401
|
+
}
|
|
1402
|
+
parts.push(...extraFlags);
|
|
1403
|
+
return {
|
|
1404
|
+
command: parts.join(" "),
|
|
1405
|
+
exact,
|
|
1406
|
+
withheld
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1233
1410
|
//#endregion
|
|
1234
1411
|
//#region src/utils/error-handler.ts
|
|
1235
1412
|
/**
|
|
@@ -1736,14 +1913,18 @@ var ResourceUpdateNotSupportedError = class ResourceUpdateNotSupportedError exte
|
|
|
1736
1913
|
* redeploy, then retry the destroy. A future `--remove-protection`
|
|
1737
1914
|
* flag (separate scope) will provide an explicit one-shot bypass.
|
|
1738
1915
|
*
|
|
1739
|
-
* Note: `cdkd state destroy` (
|
|
1916
|
+
* Note: `cdkd state destroy` (the synth-free destroy) does NOT honor
|
|
1740
1917
|
* `terminationProtection` — the flag is a CDK property not persisted
|
|
1741
1918
|
* in cdkd's state.json. Use `cdkd destroy` when synth is available.
|
|
1742
1919
|
*/
|
|
1743
1920
|
var StackTerminationProtectionError = class StackTerminationProtectionError extends CdkdError {
|
|
1744
1921
|
stackName;
|
|
1745
1922
|
constructor(stackName, cause) {
|
|
1746
|
-
super(`Stack '${stackName}' has terminationProtection: true and cannot be destroyed. Set terminationProtection: false in the CDK code, redeploy, then retry
|
|
1923
|
+
super(`Stack '${stackName}' has terminationProtection: true and cannot be destroyed. Set terminationProtection: false in the CDK code, redeploy, then retry the destroy.\nRetry with: ${pasteableCommand("cdkd destroy", [{
|
|
1924
|
+
value: stackName,
|
|
1925
|
+
hole: "stack",
|
|
1926
|
+
opts: { patternMatched: true }
|
|
1927
|
+
}]).command}`, "STACK_TERMINATION_PROTECTION", cause);
|
|
1747
1928
|
this.stackName = stackName;
|
|
1748
1929
|
this.name = "StackTerminationProtectionError";
|
|
1749
1930
|
Object.setPrototypeOf(this, StackTerminationProtectionError.prototype);
|
|
@@ -1773,8 +1954,11 @@ var StackTerminationProtectionError = class StackTerminationProtectionError exte
|
|
|
1773
1954
|
* 1. `cdkd destroy <parent>` — the normal cascading-destroy path; the
|
|
1774
1955
|
* parent's reverse-DAG walks into the child via
|
|
1775
1956
|
* `NestedStackProvider.delete` and removes both layers atomically.
|
|
1776
|
-
* 2. `cdkd state destroy <child>` —
|
|
1777
|
-
* coupling check.
|
|
1957
|
+
* 2. `cdkd state destroy <child>` — the SYNTH-FREE destroy, with no parent
|
|
1958
|
+
* coupling check. It deletes the child's AWS resources and then its
|
|
1959
|
+
* record; "state-only" named the wrong thing, and this message's own text
|
|
1960
|
+
* was corrected away from that reading (go-to-k/cdkd#3499 review nits).
|
|
1961
|
+
* `cdkd state orphan` is the record-only command. The state-driven entry point intentionally
|
|
1778
1962
|
* bypasses this guard for the same reason `cdkd state destroy`
|
|
1779
1963
|
* bypasses `terminationProtection`: it's the "I know what I'm
|
|
1780
1964
|
* doing" path for cleaning up state when synth is unavailable or
|
|
@@ -1786,7 +1970,16 @@ var NestedStackChildDirectDestroyError = class NestedStackChildDirectDestroyErro
|
|
|
1786
1970
|
parentLogicalId;
|
|
1787
1971
|
constructor(stackName, parentStack, parentLogicalId, cause) {
|
|
1788
1972
|
const logicalIdSuffix = parentLogicalId ? ` (parent's logical id: ${parentLogicalId})` : "";
|
|
1789
|
-
|
|
1973
|
+
const cascadeCommand = pasteableCommand("cdkd destroy", [{
|
|
1974
|
+
value: parentStack,
|
|
1975
|
+
hole: "parent",
|
|
1976
|
+
opts: { patternMatched: true }
|
|
1977
|
+
}]).command;
|
|
1978
|
+
const childDestroyCommand = pasteableCommand("cdkd state destroy", [{
|
|
1979
|
+
value: stackName,
|
|
1980
|
+
hole: "stack"
|
|
1981
|
+
}]).command;
|
|
1982
|
+
super(`Stack '${stackName}' is a nested child of '${parentStack}'${logicalIdSuffix}; directly destroying a nested stack is not supported. Either cascade-delete this child along with its parent, or destroy the child on its own — which deletes its AWS resources and its record, and leaves the parent's reference dangling (the synth-free escape hatch).\nCascade-delete with: ${cascadeCommand}\nDestroy the child alone with: ${childDestroyCommand}`, "NESTED_STACK_CHILD_DIRECT_DESTROY", cause);
|
|
1790
1983
|
this.stackName = stackName;
|
|
1791
1984
|
this.parentStack = parentStack;
|
|
1792
1985
|
if (parentLogicalId !== void 0) this.parentLogicalId = parentLogicalId;
|
|
@@ -3165,6 +3358,86 @@ function assemblyPathEscape(base, bound, candidate) {
|
|
|
3165
3358
|
return resolved;
|
|
3166
3359
|
}
|
|
3167
3360
|
/**
|
|
3361
|
+
* A letter or digit that neither draws as a blank nor reads as a quote.
|
|
3362
|
+
* `\p{L}` alone admits both, so two classes are carved out: the
|
|
3363
|
+
* default-ignorables, which hold letters that draw as a blank (the Hangul
|
|
3364
|
+
* fillers U+3164 and U+FFA0), and the quote-shaped letters — the Spacing
|
|
3365
|
+
* Modifier Letters block (U+02BA reads as `"`, U+02BC as `'`) plus the ones
|
|
3366
|
+
* outside it (U+0374, U+0559, U+07F4-U+07F5, U+A78B-U+A78C, and the halfwidth
|
|
3367
|
+
* sound marks U+FF9E-U+FF9F). Not all of `\p{Lm}`: U+30FC is in it, and it is
|
|
3368
|
+
* an ordinary character of a Japanese directory name.
|
|
3369
|
+
*/
|
|
3370
|
+
const VISIBLE_LETTER = new RegExp(String.raw`^(?![\p{Default_Ignorable_Code_Point}\u02b0-\u02ff\u0374\u0559\u07f4\u07f5\ua78b\ua78c\uff9e\uff9f])[\p{L}\p{N}]$`, "u");
|
|
3371
|
+
/**
|
|
3372
|
+
* A combining mark, which counts only DIRECTLY after a visible letter (or a
|
|
3373
|
+
* mark that itself counted): on a space or on punctuation it draws on its own,
|
|
3374
|
+
* and U+030B / U+030E there look like a quote.
|
|
3375
|
+
*/
|
|
3376
|
+
const COMBINING_MARK = /^\p{M}$/u;
|
|
3377
|
+
/**
|
|
3378
|
+
* The ASCII a bare path may carry besides letters and digits. Everything else —
|
|
3379
|
+
* any whitespace, any quote, any other symbol — takes the boundary, which
|
|
3380
|
+
* costs a legitimate path nothing but a pair of quotes.
|
|
3381
|
+
*/
|
|
3382
|
+
const BARE_PUNCTUATION = /^[/\\._~+@:=-]$/;
|
|
3383
|
+
/**
|
|
3384
|
+
* Classify each code point: `bare` (allowed in a bare path), `shown` (shown as
|
|
3385
|
+
* itself inside the boundary), or `escaped`. An ALLOWLIST, because a denylist
|
|
3386
|
+
* has to enumerate every character that draws as a blank or reads as a quote,
|
|
3387
|
+
* and JS `\s` alone already misses U+2800 and U+3164.
|
|
3388
|
+
*
|
|
3389
|
+
* Each test is ONE character and this walks the value — never `^(...)+$` over
|
|
3390
|
+
* the whole of it: alternatives that overlap (ASCII letters are in both
|
|
3391
|
+
* classes) under a quantifier backtrack exponentially on a long path that
|
|
3392
|
+
* fails near its end, which hangs the process uncatchably.
|
|
3393
|
+
*/
|
|
3394
|
+
function classify(chars) {
|
|
3395
|
+
let afterLetter = false;
|
|
3396
|
+
return chars.map((ch) => {
|
|
3397
|
+
if (COMBINING_MARK.test(ch)) return afterLetter ? "bare" : "escaped";
|
|
3398
|
+
afterLetter = VISIBLE_LETTER.test(ch);
|
|
3399
|
+
if (afterLetter || BARE_PUNCTUATION.test(ch)) return "bare";
|
|
3400
|
+
return ch >= " " && ch <= "~" ? "shown" : "escaped";
|
|
3401
|
+
});
|
|
3402
|
+
}
|
|
3403
|
+
/**
|
|
3404
|
+
* Render a filesystem path that an assembly chose, or that embeds a value it
|
|
3405
|
+
* chose, into cdkd's own prose (go-to-k/cdkd#3509). The caller writes NO
|
|
3406
|
+
* quotes around the result.
|
|
3407
|
+
*
|
|
3408
|
+
* `displaySafe` alone is not enough inside quotes of cdkd's: it is a denylist
|
|
3409
|
+
* of control characters and passes `'`, so a value carrying one closed cdkd's
|
|
3410
|
+
* quote and wrote a clause of its own into the refusal. This keeps a plain path
|
|
3411
|
+
* bare and puts any other one inside a JSON string literal. Inside it, `"` and
|
|
3412
|
+
* `\` are escaped as JSON escapes them, and so is every character that is
|
|
3413
|
+
* neither printable ASCII nor a visible letter — a curly or fullwidth quote
|
|
3414
|
+
* that could pass for the boundary's own closing `"`, and a blank that could
|
|
3415
|
+
* pass for a space. The result stays valid JSON: `JSON.parse` returns the
|
|
3416
|
+
* sanitized value.
|
|
3417
|
+
*
|
|
3418
|
+
* Deliberately NOT `displayIdent`, which go-to-k/cdkd#3506 used for a Stage
|
|
3419
|
+
* path: that one is ASCII-only and capped at 255 code points, and a legitimate
|
|
3420
|
+
* path is neither — a non-ASCII directory name would render as spaces, naming
|
|
3421
|
+
* a path that does not exist. Here nothing is truncated and a non-ASCII letter
|
|
3422
|
+
* is shown as itself. A legitimate path with a space or any symbol outside
|
|
3423
|
+
* `/ \ . _ ~ + @ : = -` (`/Users/me/My Project/cdk.out`,
|
|
3424
|
+
* `C:\Program Files (x86)\app`) renders quoted, and a non-letter non-ASCII
|
|
3425
|
+
* character in it (an emoji, `©`) is shown as its `\u` escape.
|
|
3426
|
+
*/
|
|
3427
|
+
function displayAssemblyPath(value) {
|
|
3428
|
+
const clean = displaySafe(value);
|
|
3429
|
+
const chars = Array.from(clean);
|
|
3430
|
+
const kinds = classify(chars);
|
|
3431
|
+
if (clean === value && chars.length > 0 && kinds.every((k) => k === "bare")) return clean;
|
|
3432
|
+
let body = "";
|
|
3433
|
+
chars.forEach((ch, i) => {
|
|
3434
|
+
if (ch === "\"" || ch === "\\") body += `\\${ch}`;
|
|
3435
|
+
else if (kinds[i] !== "escaped") body += ch;
|
|
3436
|
+
else for (let u = 0; u < ch.length; u++) body += `\\u${ch.charCodeAt(u).toString(16).padStart(4, "0")}`;
|
|
3437
|
+
});
|
|
3438
|
+
return `"${body}"`;
|
|
3439
|
+
}
|
|
3440
|
+
/**
|
|
3168
3441
|
* The shared tail of every containment refusal: what the value resolved to,
|
|
3169
3442
|
* what it escaped, and why that means the assembly is not CDK-generated. Each
|
|
3170
3443
|
* call site supplies its own subject ("Stack 'X' has templateFile='...' which
|
|
@@ -3173,23 +3446,25 @@ function assemblyPathEscape(base, bound, candidate) {
|
|
|
3173
3446
|
* other than loading the file — `renderNestedTemplateTreeDefect` says "deploy"
|
|
3174
3447
|
* or "diff", matching its own sibling refusals.
|
|
3175
3448
|
*
|
|
3176
|
-
* Every
|
|
3177
|
-
* `AssemblyReader`'s own refusals give
|
|
3178
|
-
* FOR a hand-modified assembly, so the
|
|
3179
|
-
* `dir` (below a Stage it derives from
|
|
3180
|
-
* attacker-chosen, and `formatError`
|
|
3181
|
-
* its own `message`.
|
|
3449
|
+
* Every path goes through {@link displayAssemblyPath}, with no quotes of this
|
|
3450
|
+
* function's own, for the reason `AssemblyReader`'s own refusals give
|
|
3451
|
+
* (go-to-k/cdkd#3277): this text exists FOR a hand-modified assembly, so the
|
|
3452
|
+
* candidate, the resolved path and even `dir` (below a Stage it derives from
|
|
3453
|
+
* the manifest's `directoryName`) are all attacker-chosen, and `formatError`
|
|
3454
|
+
* sanitizes only an error's `cause`, never its own `message`.
|
|
3182
3455
|
*/
|
|
3183
3456
|
function renderAssemblyPathEscape(escape, dir, action = "load", provenanceOverride) {
|
|
3184
3457
|
const provenance = provenanceOverride ?? `CDK emits assembly paths that stay inside the assembly directory; one that leaves it indicates the synth output was hand-modified or generated by a non-CDK toolchain. Refusing to ${action}.`;
|
|
3185
3458
|
const base = path.resolve(dir);
|
|
3186
3459
|
const realBase = resolveThroughLinks(base) ?? base;
|
|
3460
|
+
const shownPath = displayAssemblyPath(escape.path);
|
|
3461
|
+
const shownBase = displayAssemblyPath(base);
|
|
3187
3462
|
if (escape.escape === "symlink") {
|
|
3188
|
-
if (escape.realPath === realBase) return `resolves to
|
|
3189
|
-
return `resolves to
|
|
3463
|
+
if (escape.realPath === realBase) return `resolves to ${shownPath}, a symbolic link to the directory ${shownBase} itself rather than to a file inside it. ${provenance}`;
|
|
3464
|
+
return `resolves to ${shownPath}, which leads through a symbolic link to ${displayAssemblyPath(escape.realPath)}, outside ${shownBase}. ${provenance}`;
|
|
3190
3465
|
}
|
|
3191
|
-
if (escape.path === base) return `names the directory
|
|
3192
|
-
return `resolves to
|
|
3466
|
+
if (escape.path === base) return `names the directory ${shownBase} itself rather than a file inside it. ${provenance}`;
|
|
3467
|
+
return `resolves to ${shownPath}, outside ${shownBase}. ${provenance}`;
|
|
3193
3468
|
}
|
|
3194
3469
|
|
|
3195
3470
|
//#endregion
|
|
@@ -7817,12 +8092,25 @@ function formatDockerLoginError(stderr, endpoint) {
|
|
|
7817
8092
|
return trimmed;
|
|
7818
8093
|
}
|
|
7819
8094
|
/**
|
|
7820
|
-
* Env vars the
|
|
7821
|
-
*
|
|
7822
|
-
*
|
|
7823
|
-
*
|
|
7824
|
-
*
|
|
7825
|
-
*
|
|
8095
|
+
* Env vars the container CLI itself reads to decide how / where to run — the
|
|
8096
|
+
* binary {@link getDockerCmd} resolves, i.e. `docker` or whatever `CDK_DOCKER`
|
|
8097
|
+
* names (podman / nerdctl / finch). A resolved ECS secret (or SecureString)
|
|
8098
|
+
* whose NAME collides with one of these must NOT override it in the client's
|
|
8099
|
+
* own process environment: a secret named `DOCKER_HOST` (or podman's
|
|
8100
|
+
* `CONTAINER_HOST`) would redirect the client to a different daemon, and `PATH`
|
|
8101
|
+
* would break locating the binary. See issues
|
|
8102
|
+
* https://github.com/go-to-k/cdkd/issues/2183 and
|
|
8103
|
+
* https://github.com/go-to-k/cdkd/issues/2188.
|
|
8104
|
+
*
|
|
8105
|
+
* RULE for additions: anything a supported container CLIENT (or a credential /
|
|
8106
|
+
* connection helper it execs) reads to decide WHAT CODE IT LOADS, WHAT IT
|
|
8107
|
+
* TRUSTS, or WHERE / HOW IT CONNECTS. The docker CLI's documented set is kept
|
|
8108
|
+
* whole, behaviour toggles included; beyond it, a var that only tunes
|
|
8109
|
+
* behaviour (a storage driver, a snapshotter, a temp dir, an experimental
|
|
8110
|
+
* toggle) is OUT, because dropping a colliding secret costs the user that
|
|
8111
|
+
* secret. The operator's OWN value is never touched — the spawn
|
|
8112
|
+
* env starts from `process.env` — so adding a key costs only a secret of that
|
|
8113
|
+
* name.
|
|
7826
8114
|
*/
|
|
7827
8115
|
const DOCKER_CLIENT_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
7828
8116
|
"PATH",
|
|
@@ -7848,6 +8136,39 @@ const DOCKER_CLIENT_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
|
7848
8136
|
"GLIBC_TUNABLES",
|
|
7849
8137
|
"GCONV_PATH",
|
|
7850
8138
|
"BASH_ENV",
|
|
8139
|
+
"SHELLOPTS",
|
|
8140
|
+
"PS4",
|
|
8141
|
+
"PYTHONPATH",
|
|
8142
|
+
"PYTHONHOME",
|
|
8143
|
+
"PYTHONUSERBASE",
|
|
8144
|
+
"PYTHONPYCACHEPREFIX",
|
|
8145
|
+
"PYTHONPLATLIBDIR",
|
|
8146
|
+
"PYTHONWARNINGS",
|
|
8147
|
+
"BROWSER",
|
|
8148
|
+
"REQUESTS_CA_BUNDLE",
|
|
8149
|
+
"CURL_CA_BUNDLE",
|
|
8150
|
+
"HTTPLIB2_CA_CERTS",
|
|
8151
|
+
"SSLKEYLOGFILE",
|
|
8152
|
+
"GCE_METADATA_HOST",
|
|
8153
|
+
"GCE_METADATA_ROOT",
|
|
8154
|
+
"GCE_METADATA_IP",
|
|
8155
|
+
"NODE_OPTIONS",
|
|
8156
|
+
"NODE_PATH",
|
|
8157
|
+
"NODE_COMPILE_CACHE",
|
|
8158
|
+
"NODE_EXTRA_CA_CERTS",
|
|
8159
|
+
"NODE_TLS_REJECT_UNAUTHORIZED",
|
|
8160
|
+
"RUBYOPT",
|
|
8161
|
+
"RUBYLIB",
|
|
8162
|
+
"GEM_PATH",
|
|
8163
|
+
"GEM_HOME",
|
|
8164
|
+
"RUBYGEMS_GEMDEPS",
|
|
8165
|
+
"PERL5OPT",
|
|
8166
|
+
"PERL5LIB",
|
|
8167
|
+
"PERLLIB",
|
|
8168
|
+
"PERL5DB",
|
|
8169
|
+
"OPENSSL_CONF",
|
|
8170
|
+
"OPENSSL_MODULES",
|
|
8171
|
+
"OPENSSL_ENGINES",
|
|
7851
8172
|
"SSH_AUTH_SOCK",
|
|
7852
8173
|
"SSH_ASKPASS",
|
|
7853
8174
|
"SSH_ASKPASS_REQUIRE",
|
|
@@ -7864,6 +8185,40 @@ const DOCKER_CLIENT_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
|
7864
8185
|
"AWS_CONTAINER_CREDENTIALS_FULL_URI",
|
|
7865
8186
|
"AWS_ROLE_ARN",
|
|
7866
8187
|
"AWS_EC2_METADATA_SERVICE_ENDPOINT",
|
|
8188
|
+
"AWS_ECR_CACHE_DIR",
|
|
8189
|
+
"CONTAINER_HOST",
|
|
8190
|
+
"CONTAINER_CONNECTION",
|
|
8191
|
+
"CONTAINER_SSHKEY",
|
|
8192
|
+
"PODMAN_CONNECTIONS_CONF",
|
|
8193
|
+
"CONTAINER_PROXY",
|
|
8194
|
+
"CONTAINERS_SSH_CONF",
|
|
8195
|
+
"CONTAINERS_CONF",
|
|
8196
|
+
"CONTAINERS_CONF_OVERRIDE",
|
|
8197
|
+
"CONTAINERS_REGISTRIES_CONF",
|
|
8198
|
+
"CONTAINERS_REGISTRIES_CONF_OVERRIDE",
|
|
8199
|
+
"REGISTRIES_CONFIG_PATH",
|
|
8200
|
+
"CONTAINERS_STORAGE_CONF",
|
|
8201
|
+
"CONTAINERS_STORAGE_CONF_OVERRIDE",
|
|
8202
|
+
"CONTAINERS_POLICY_JSON",
|
|
8203
|
+
"STORAGE_OPTS",
|
|
8204
|
+
"CONTAINERS_HELPER_BINARY_DIR",
|
|
8205
|
+
"REGISTRY_AUTH_FILE",
|
|
8206
|
+
"DBUS_SESSION_BUS_ADDRESS",
|
|
8207
|
+
"NOTIFY_SOCKET",
|
|
8208
|
+
"XDG_CONFIG_HOME",
|
|
8209
|
+
"XDG_RUNTIME_DIR",
|
|
8210
|
+
"APPDATA",
|
|
8211
|
+
"PROGRAMDATA",
|
|
8212
|
+
"PROGRAMFILES",
|
|
8213
|
+
"LOCALAPPDATA",
|
|
8214
|
+
"SSH",
|
|
8215
|
+
"CONTAINERD_ADDRESS",
|
|
8216
|
+
"CONTAINERD_NAMESPACE",
|
|
8217
|
+
"NERDCTL_TOML",
|
|
8218
|
+
"CNI_PATH",
|
|
8219
|
+
"NETCONFPATH",
|
|
8220
|
+
"ROOTLESSKIT_STATE_DIR",
|
|
8221
|
+
"NERDCTL_LOG_FILE",
|
|
7867
8222
|
"SSL_CERT_FILE",
|
|
7868
8223
|
"SSL_CERT_DIR",
|
|
7869
8224
|
"GODEBUG",
|
|
@@ -7882,10 +8237,16 @@ const DOCKER_CLIENT_ENV_KEYS_UPPER = new Set([...DOCKER_CLIENT_ENV_KEYS].map((k)
|
|
|
7882
8237
|
* family aws-sdk-go-v2 (and so `docker-credential-ecr-login`) honours — a
|
|
7883
8238
|
* secret named `AWS_ENDPOINT_URL_ECR` walks around the exact
|
|
7884
8239
|
* `AWS_ENDPOINT_URL` entry and redirects a request signed with the operator's
|
|
7885
|
-
* real credentials (#2186 round 4).
|
|
7886
|
-
*
|
|
7887
|
-
*
|
|
7888
|
-
*
|
|
8240
|
+
* real credentials (#2186 round 4). `CLOUDSDK_` is gcloud's (#3599): every
|
|
8241
|
+
* gcloud property is settable as `CLOUDSDK_<SECTION>_<NAME>`, which covers
|
|
8242
|
+
* its token host, API endpoint overrides, proxy, CA bundle and account, and
|
|
8243
|
+
* the `docker-credential-gcloud` wrapper execs `$CLOUDSDK_PYTHON
|
|
8244
|
+
* $CLOUDSDK_PYTHON_ARGS`. `BASH_FUNC_` is bash's exported-function family.
|
|
8245
|
+
* No plausible secret name collides with these, apart from the one listed in
|
|
8246
|
+
* {@link DOCKER_CLIENT_ENV_PREFIX_EXEMPTIONS}. Matched by prefix rather than
|
|
8247
|
+
* enumerated (issue #2183 review). `SSH_` was a prefix here and was demoted to
|
|
8248
|
+
* an EXACT enumeration in {@link DOCKER_CLIENT_ENV_KEYS} (#2186 review round
|
|
8249
|
+
* 3): the family is not
|
|
7889
8250
|
* uniformly dangerous and is not growing, while the prefix broke realistic,
|
|
7890
8251
|
* currently-working secrets (`SSH_PRIVATE_KEY`, GitLab CI's canonical
|
|
7891
8252
|
* deploy-key spelling). Exported so the test fence can assert the EXACT
|
|
@@ -7895,19 +8256,37 @@ const DOCKER_CLIENT_ENV_KEYS_UPPER = new Set([...DOCKER_CLIENT_ENV_KEYS].map((k)
|
|
|
7895
8256
|
const DOCKER_CLIENT_ENV_PREFIXES = [
|
|
7896
8257
|
"LD_",
|
|
7897
8258
|
"DYLD_",
|
|
7898
|
-
"AWS_ENDPOINT_URL_"
|
|
8259
|
+
"AWS_ENDPOINT_URL_",
|
|
8260
|
+
"CLOUDSDK_",
|
|
8261
|
+
"BASH_FUNC_"
|
|
7899
8262
|
];
|
|
7900
8263
|
/**
|
|
8264
|
+
* Exact names inside a {@link DOCKER_CLIENT_ENV_PREFIXES} family that are still
|
|
8265
|
+
* delivered. An entry must be a realistic secret name AND harmless to the
|
|
8266
|
+
* helper that reads it. `CLOUDSDK_AUTH_ACCESS_TOKEN` is gcloud's own variable
|
|
8267
|
+
* for a caller-supplied access token (#3599). Given to `docker-credential-gcloud`,
|
|
8268
|
+
* it only changes which token gcloud hands docker, with no network call of its
|
|
8269
|
+
* own. gcloud's `auth docker-helper` answers only for a registry in its own
|
|
8270
|
+
* supported list unless `artifacts/allow_unrecognized_registry` is set, and
|
|
8271
|
+
* that property, the token host, the universe domain and every other variable
|
|
8272
|
+
* that could redirect or weaken the exchange stay refused by the `CLOUDSDK_`
|
|
8273
|
+
* prefix. Stored upper-case and matched case-insensitively. Never exempt an
|
|
8274
|
+
* exact {@link DOCKER_CLIENT_ENV_KEYS} member: the exact list wins.
|
|
8275
|
+
*/
|
|
8276
|
+
const DOCKER_CLIENT_ENV_PREFIX_EXEMPTIONS = /* @__PURE__ */ new Set(["CLOUDSDK_AUTH_ACCESS_TOKEN"]);
|
|
8277
|
+
/**
|
|
7901
8278
|
* Is `key` the name of a var the docker client reads? Case-INSENSITIVE, because
|
|
7902
8279
|
* Windows environment lookups are, so a lowercase `docker_host` must be caught
|
|
7903
8280
|
* too (issue #2183). Matches the exact denylist OR a prefixed family — the
|
|
7904
8281
|
* prefix families are fail-closed on the whole prefix, so an unlisted `LD_*` /
|
|
7905
|
-
* `DYLD_*` / `AWS_ENDPOINT_URL_*`
|
|
7906
|
-
* rather than reaching the client
|
|
8282
|
+
* `DYLD_*` / `AWS_ENDPOINT_URL_*` / `CLOUDSDK_*` / `BASH_FUNC_*` secret is
|
|
8283
|
+
* dropped (with a rename warning) rather than reaching the client, except for
|
|
8284
|
+
* a name in {@link DOCKER_CLIENT_ENV_PREFIX_EXEMPTIONS}.
|
|
7907
8285
|
*/
|
|
7908
8286
|
function isDockerClientEnvKey(key) {
|
|
7909
8287
|
const upper = key.toUpperCase();
|
|
7910
8288
|
if (DOCKER_CLIENT_ENV_KEYS_UPPER.has(upper)) return true;
|
|
8289
|
+
if (DOCKER_CLIENT_ENV_PREFIX_EXEMPTIONS.has(upper)) return false;
|
|
7911
8290
|
return DOCKER_CLIENT_ENV_PREFIXES.some((prefix) => upper.startsWith(prefix));
|
|
7912
8291
|
}
|
|
7913
8292
|
/**
|
|
@@ -9115,6 +9494,62 @@ function dockerSubject(assetId) {
|
|
|
9115
9494
|
return assetId === void 0 ? "A Docker asset" : `Docker asset '${displaySafe(assetId)}'`;
|
|
9116
9495
|
}
|
|
9117
9496
|
/**
|
|
9497
|
+
* Refuse a Docker asset whose build context the bundled `cdk-local` engine
|
|
9498
|
+
* would take from outside the assembly (issue
|
|
9499
|
+
* [#3503](https://github.com/go-to-k/cdkd/issues/3503)).
|
|
9500
|
+
*
|
|
9501
|
+
* `cdk-local`'s own `buildDockerImage` spells the context (and the
|
|
9502
|
+
* `executable` arm's cwd) as `${cdkOutDir}/${source.directory}` with no
|
|
9503
|
+
* containment, and cdkd cannot hand it a pre-resolved path without changing
|
|
9504
|
+
* the image tag it derives from `source`. So this judges the path the engine
|
|
9505
|
+
* WILL open, before the engine runs, through the same
|
|
9506
|
+
* {@link resolveDockerContextDirectory} cdkd's own build uses.
|
|
9507
|
+
*
|
|
9508
|
+
* An ABSOLUTE value is judged by the engine's spelling, not honoured: the
|
|
9509
|
+
* concatenation folds it UNDER the manifest directory, so its leading
|
|
9510
|
+
* separators are stripped and the remainder takes the relative arm. That
|
|
9511
|
+
* keeps a symlink inside the assembly from becoming an escape through an
|
|
9512
|
+
* absolute spelling, and keeps the honour-and-warn arm, which describes a
|
|
9513
|
+
* build this engine never performs, out of these paths.
|
|
9514
|
+
*/
|
|
9515
|
+
function assertCdkLocalDockerContextContained(opts) {
|
|
9516
|
+
const { manifestDir, source, assetOutdir, wrapError } = opts;
|
|
9517
|
+
const directory = source.directory;
|
|
9518
|
+
if (!directory) return;
|
|
9519
|
+
const asEngineJoinsIt = isAbsolute$1(directory) ? directory.replace(/^[/\\]+/, "") : directory;
|
|
9520
|
+
resolveDockerContextDirectory({
|
|
9521
|
+
manifestDir,
|
|
9522
|
+
directory: asEngineJoinsIt,
|
|
9523
|
+
wrapError,
|
|
9524
|
+
assetOutdir,
|
|
9525
|
+
sink: source.executable && source.executable.length > 0 ? "run this asset's source.executable with that directory as its working directory" : "send that directory to docker build as the context of an image cdkd then runs locally"
|
|
9526
|
+
});
|
|
9527
|
+
const engineSpelled = `${manifestDir}/${asEngineJoinsIt}`;
|
|
9528
|
+
const physical = tryRealpathNative(engineSpelled);
|
|
9529
|
+
if (physical === void 0) return;
|
|
9530
|
+
const bound = tryRealpathNative(assetOutdir) ?? resolve$1(assetOutdir);
|
|
9531
|
+
const rel = relative(bound, physical);
|
|
9532
|
+
if (rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute$1(rel)) return;
|
|
9533
|
+
throw wrapError(`asset source.directory='${displaySafe(directory)}' which ` + renderAssemblyPathEscape({
|
|
9534
|
+
contained: false,
|
|
9535
|
+
escape: "symlink",
|
|
9536
|
+
path: engineSpelled,
|
|
9537
|
+
realPath: physical
|
|
9538
|
+
}, assetOutdir, "build it"));
|
|
9539
|
+
}
|
|
9540
|
+
/**
|
|
9541
|
+
* `realpath(3)`, or `undefined` when the path does not resolve. `.native` is
|
|
9542
|
+
* load-bearing: the JavaScript `realpathSync` folds `..` lexically first,
|
|
9543
|
+
* which is the very reading the check above exists to avoid.
|
|
9544
|
+
*/
|
|
9545
|
+
function tryRealpathNative(p) {
|
|
9546
|
+
try {
|
|
9547
|
+
return realpathSync$1.native(p);
|
|
9548
|
+
} catch {
|
|
9549
|
+
return;
|
|
9550
|
+
}
|
|
9551
|
+
}
|
|
9552
|
+
/**
|
|
9118
9553
|
* Build a Docker image from a CDK asset source. Returns the local image
|
|
9119
9554
|
* tag the caller should use for `docker tag` / `docker push` (publisher)
|
|
9120
9555
|
* or `docker run` (local-invoke).
|
|
@@ -9667,7 +10102,14 @@ function isCrossRegionRedirect(error) {
|
|
|
9667
10102
|
*/
|
|
9668
10103
|
async function assertAssetBucketRegion(s3Client, bucketName, expectedRegion, accountId, cause) {
|
|
9669
10104
|
const want = canonicalizeRegion(expectedRegion);
|
|
9670
|
-
const remedy = `Either bootstrap ${want} with an asset-bucket name unique to it
|
|
10105
|
+
const remedy = `Either bootstrap ${want} with an asset-bucket name unique to it, or run this against the bucket's own region.\nBootstrap with: ${pasteableCommand("cdkd bootstrap", [{
|
|
10106
|
+
flag: "--region",
|
|
10107
|
+
value: want,
|
|
10108
|
+
hole: "region"
|
|
10109
|
+
}, {
|
|
10110
|
+
flag: "--asset-bucket",
|
|
10111
|
+
hole: "name"
|
|
10112
|
+
}]).command}`;
|
|
9671
10113
|
let actual;
|
|
9672
10114
|
const fromHeader = readBucketRegionHeader(cause);
|
|
9673
10115
|
if (fromHeader) actual = canonicalizeRegion(fromHeader);
|
|
@@ -9831,7 +10273,11 @@ function parseBootstrapMarker(body, markerKey) {
|
|
|
9831
10273
|
* (bucket-squatting defense, design §5).
|
|
9832
10274
|
*/
|
|
9833
10275
|
async function verifyAssetStorageExists(marker, accountId, region, opts = {}) {
|
|
9834
|
-
const rebootstrapHint = `
|
|
10276
|
+
const rebootstrapHint = `cdkd never silently falls back to CDK bootstrap asset storage once a region is opted in.\nRecreate it with: ${pasteableCommand("cdkd bootstrap", [{
|
|
10277
|
+
flag: "--region",
|
|
10278
|
+
value: region,
|
|
10279
|
+
hole: "region"
|
|
10280
|
+
}]).command}`;
|
|
9835
10281
|
const clientOpts = {
|
|
9836
10282
|
region,
|
|
9837
10283
|
...opts.profile && { profile: opts.profile }
|
|
@@ -9915,7 +10361,11 @@ async function ensureAssetStorage(options) {
|
|
|
9915
10361
|
const conflicts = [];
|
|
9916
10362
|
if (options.assetBucketName && options.assetBucketName !== existingMarker.assetBucket) conflicts.push(`asset bucket '${existingMarker.assetBucket}' (requested '${options.assetBucketName}')`);
|
|
9917
10363
|
if (options.containerRepoName && options.containerRepoName !== existingMarker.containerRepo) conflicts.push(`container repo '${existingMarker.containerRepo}' (requested '${options.containerRepoName}')`);
|
|
9918
|
-
if (conflicts.length > 0) throw new CdkdError(`Region '${region}' is already bootstrapped with ${conflicts.join(" and ")}. Changing asset storage names would strand the existing storage and every published asset in it —
|
|
10364
|
+
if (conflicts.length > 0) throw new CdkdError(`Region '${region}' is already bootstrapped with ${conflicts.join(" and ")}. Changing asset storage names would strand the existing storage and every published asset in it — tear the region's asset storage down first, then re-run bootstrap with the new names.\nTear it down with: ${pasteableCommand("cdkd bootstrap", [{ literal: "--destroy" }, {
|
|
10365
|
+
flag: "--region",
|
|
10366
|
+
value: region,
|
|
10367
|
+
hole: "region"
|
|
10368
|
+
}]).command}`, "ASSET_STORAGE_NAME_CONFLICT");
|
|
9919
10369
|
}
|
|
9920
10370
|
const assetBucket = options.assetBucketName ?? existingMarker?.assetBucket ?? getCdkdAssetBucketName(accountId, region);
|
|
9921
10371
|
const containerRepo = options.containerRepoName ?? existingMarker?.containerRepo ?? getCdkdContainerRepoName(accountId, region);
|
|
@@ -10105,7 +10555,11 @@ var AssetModeResolver = class {
|
|
|
10105
10555
|
}
|
|
10106
10556
|
if (!this.legacyNoticeShownRegions.has(region) && !this.suppressLegacyNotice) {
|
|
10107
10557
|
this.legacyNoticeShownRegions.add(region);
|
|
10108
|
-
this.logger.info(`Assets for region '${region}' are published to the CDK bootstrap bucket/repo, which 'cdk gc' may garbage-collect (cdkd-deployed stacks have no CloudFormation stack for gc to scan).
|
|
10558
|
+
this.logger.info(`Assets for region '${region}' are published to the CDK bootstrap bucket/repo, which 'cdk gc' may garbage-collect (cdkd-deployed stacks have no CloudFormation stack for gc to scan). Create cdkd-owned asset storage that 'cdk gc' never touches:\nBootstrap with: ${pasteableCommand("cdkd bootstrap", [{
|
|
10559
|
+
flag: "--region",
|
|
10560
|
+
value: region,
|
|
10561
|
+
hole: "region"
|
|
10562
|
+
}]).command}`);
|
|
10109
10563
|
}
|
|
10110
10564
|
return { mode: "legacy" };
|
|
10111
10565
|
}
|
|
@@ -10165,7 +10619,11 @@ var AssetModeResolver = class {
|
|
|
10165
10619
|
};
|
|
10166
10620
|
} catch (error) {
|
|
10167
10621
|
const message = error instanceof Error ? error.message : String(error);
|
|
10168
|
-
this.logger.warn(`Failed to auto-create cdkd asset storage for region '${region}': ${message} Falling back to the CDK bootstrap destinations for this run —
|
|
10622
|
+
this.logger.warn(`Failed to auto-create cdkd asset storage for region '${region}': ${message} Falling back to the CDK bootstrap destinations for this run — opt the region in with S3/ECR create permissions.\nBootstrap with: ${pasteableCommand("cdkd bootstrap", [{
|
|
10623
|
+
flag: "--region",
|
|
10624
|
+
value: region,
|
|
10625
|
+
hole: "region"
|
|
10626
|
+
}]).command}`);
|
|
10169
10627
|
return null;
|
|
10170
10628
|
} finally {
|
|
10171
10629
|
s3Client?.destroy();
|
|
@@ -11406,19 +11864,6 @@ function formatDuration(ms) {
|
|
|
11406
11864
|
return `${Math.floor(seconds / 60)}m${seconds % 60}s`;
|
|
11407
11865
|
}
|
|
11408
11866
|
/**
|
|
11409
|
-
* Quote a value for a pasteable shell command.
|
|
11410
|
-
*
|
|
11411
|
-
* EXPORTED since issue [#2610]: `src/provisioning/replacement-protection-advice.ts`
|
|
11412
|
-
* prints `aws <service> ...` recovery commands naming a resource's physical id,
|
|
11413
|
-
* which is the same hazard one directory over. A second spelling of this
|
|
11414
|
-
* predicate is how the two would come to disagree about which values need
|
|
11415
|
-
* quoting -- the reason `display-safe.ts`'s header gives for not widening a
|
|
11416
|
-
* rule by hand. It is a pure function of its argument and imports nothing.
|
|
11417
|
-
*/
|
|
11418
|
-
function shellQuote(value) {
|
|
11419
|
-
return /^[A-Za-z0-9._/@:+-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
|
|
11420
|
-
}
|
|
11421
|
-
/**
|
|
11422
11867
|
* The `cdkd force-unlock ...` line, carrying every flag that decides which
|
|
11423
11868
|
* lock object it resolves to.
|
|
11424
11869
|
*/
|
|
@@ -11432,24 +11877,6 @@ function buildForceUnlockCommand(stackName, region, recovery) {
|
|
|
11432
11877
|
return [safeRegion === void 0 ? `cdkd force-unlock ${shellQuote(safeStack)}` : `cdkd force-unlock ${shellQuote(safeStack)} --stack-region ${shellQuote(safeRegion)}`, ...recoveryFlags.flags].join(" ");
|
|
11433
11878
|
}
|
|
11434
11879
|
/**
|
|
11435
|
-
* A placeholder for a value a pasteable command could not name, QUOTED.
|
|
11436
|
-
*
|
|
11437
|
-
* A bare `<profile>` is two shell redirections, not a word: pasted, `<profile`
|
|
11438
|
-
* reads stdin from a file named `profile` and `>` sends stdout to whatever
|
|
11439
|
-
* word follows. While the hole was the command's LAST word that was only a
|
|
11440
|
-
* syntax error; with flags appended after it (`--profile <profile>
|
|
11441
|
-
* --state-bucket b`) it ran the command with `--state-bucket` swallowed as a
|
|
11442
|
-
* redirect target and `b` as the profile — a delete against the ambient
|
|
11443
|
-
* bucket, with nothing printed (measured by the maintainer, M4 of the
|
|
11444
|
-
* go-to-k/cdkd#3363 review). Quoted, it pastes as one literal argument and
|
|
11445
|
-
* nothing else — no redirection, no swallowed flag. It is not refused: an
|
|
11446
|
-
* UNFILLED hole runs as that literal value (a stack named `<stack>`, a prefix
|
|
11447
|
-
* `<prefix>`).
|
|
11448
|
-
*/
|
|
11449
|
-
function commandHole(name) {
|
|
11450
|
-
return `'<${name}>'`;
|
|
11451
|
-
}
|
|
11452
|
-
/**
|
|
11453
11880
|
* The flags that pin a pasteable state command to the caller's account and
|
|
11454
11881
|
* bucket: `--profile`, the resolved `--state-bucket`, and a NON-default
|
|
11455
11882
|
* `--state-prefix` (see {@link LockRecoveryContext} for why each is emitted
|
|
@@ -16779,6 +17206,40 @@ function hasPlainPrototype(value) {
|
|
|
16779
17206
|
return proto === Object.prototype || proto === null;
|
|
16780
17207
|
}
|
|
16781
17208
|
/**
|
|
17209
|
+
* Is this a `Date` the value scan may return BY IDENTITY (issue
|
|
17210
|
+
* [#2427](https://github.com/go-to-k/cdkd/issues/2427))?
|
|
17211
|
+
*
|
|
17212
|
+
* The scan redacts what PERSISTS, and `JSON.stringify` persists an unmodified
|
|
17213
|
+
* `Date` as `Date.prototype.toJSON`'s ISO timestamp (or `null`) — never its own
|
|
17214
|
+
* entries. So the only thing to scan is that timestamp — the walk runs it
|
|
17215
|
+
* through the string arms and keeps the `Date` only when they leave it alone —
|
|
17216
|
+
* and rebuilding it just threw the timestamp away. "Unmodified" is three
|
|
17217
|
+
* conjuncts, each closing a shape whose persisted form is NOT that timestamp:
|
|
17218
|
+
* the EXACT prototype, a real `[[DateValue]]` (the `getTime` brand check, which
|
|
17219
|
+
* a `Proxy` or an `Object.create(Date.prototype)` fails), and NO own keys — an
|
|
17220
|
+
* own `toJSON` (enumerable or not) is what `JSON.stringify` would call instead.
|
|
17221
|
+
* No SDK readback or `JSON.parse` produces any of those; they are refused
|
|
17222
|
+
* because the identity return must mean exactly "persists as a timestamp".
|
|
17223
|
+
*
|
|
17224
|
+
* Deliberately NOT "any non-plain object". `JSON.stringify` persists an
|
|
17225
|
+
* arbitrary class instance's OWN ENUMERABLE fields, which is exactly what the
|
|
17226
|
+
* rebuild scans, so returning one by identity would persist a plaintext held in
|
|
17227
|
+
* such a field — a disclosure, traded for fidelity. A `Uint8Array` persists as
|
|
17228
|
+
* the same `{"0":...}` either way. The EXACT prototype (not `instanceof`) keeps
|
|
17229
|
+
* a `Date` subclass on the rebuild: its prototype can override `toJSON`, which
|
|
17230
|
+
* the rebuild drops and identity would honour. A cross-realm `Date` fails the
|
|
17231
|
+
* test too, which is the old flattening rather than a leak.
|
|
17232
|
+
*/
|
|
17233
|
+
function isOrdinaryDate(value) {
|
|
17234
|
+
if (Object.getPrototypeOf(value) !== Date.prototype) return false;
|
|
17235
|
+
try {
|
|
17236
|
+
Date.prototype.getTime.call(value);
|
|
17237
|
+
} catch {
|
|
17238
|
+
return false;
|
|
17239
|
+
}
|
|
17240
|
+
return Reflect.ownKeys(value).length === 0;
|
|
17241
|
+
}
|
|
17242
|
+
/**
|
|
16782
17243
|
* Structural equality over the JSON shapes this module walks (issue #2012).
|
|
16783
17244
|
*
|
|
16784
17245
|
* Own ENUMERABLE keys only, and a key count on both sides, so an inherited
|
|
@@ -17379,21 +17840,22 @@ function asIndex(marks, index) {
|
|
|
17379
17840
|
*
|
|
17380
17841
|
* KEEPING A NON-PLAIN LEAF INTACT takes the prototype guard on the object arm,
|
|
17381
17842
|
* NOT that leaf rule, and an earlier revision of this comment claimed the
|
|
17382
|
-
* opposite — measured wrong. The scan's own walk
|
|
17843
|
+
* opposite — measured wrong. The scan's own walk rebuilt objects and turned a
|
|
17383
17844
|
* `Date` the provider readback carries (`LastModified`) into `{}`; that
|
|
17384
|
-
* flattening
|
|
17385
|
-
* (issue #2427). The object arm runs FIRST here and
|
|
17386
|
-
* `Date`, so without `hasPlainPrototype` this walk did
|
|
17387
|
-
* newly extending #2427 to the EMPTY-map path, where
|
|
17388
|
-
* `drainObservedCaptures` baseline lives and where
|
|
17389
|
-
* the result to the live resource. With the guard
|
|
17390
|
-
* through to `refused`. That is the position passes' own
|
|
17391
|
-
* bag by identity: their object arm carried no prototype
|
|
17392
|
-
*
|
|
17393
|
-
*
|
|
17394
|
-
*
|
|
17395
|
-
*
|
|
17396
|
-
*
|
|
17845
|
+
* flattening predated this module's derived needles on the POPULATED-map path
|
|
17846
|
+
* (issue #2427, since fixed in the scan). The object arm runs FIRST here and
|
|
17847
|
+
* `isPlainObject` admits a `Date`, so without `hasPlainPrototype` this walk did
|
|
17848
|
+
* the flattening ITSELF — newly extending #2427 to the EMPTY-map path, where
|
|
17849
|
+
* the unchanged-resource `drainObservedCaptures` baseline lives and where
|
|
17850
|
+
* `cdkd drift --revert` pushes the result to the live resource. With the guard
|
|
17851
|
+
* a non-plain leaf falls through to `refused`. That is the position passes' own
|
|
17852
|
+
* answer, which is the bag by identity: their object arm carried no prototype
|
|
17853
|
+
* guard of its own until issue
|
|
17854
|
+
* [#2869](https://github.com/go-to-k/cdkd/issues/2869), so a non-plain leaf
|
|
17855
|
+
* whose source subtree carries a reference WAS already flattened one function
|
|
17856
|
+
* earlier and this guard could only keep a `{}` intact. Both halves are guarded
|
|
17857
|
+
* now, and the VALUE scan's own walk keeps a `Date` by identity since issue
|
|
17858
|
+
* #2427, so `scanned` holds the same instance.
|
|
17397
17859
|
*
|
|
17398
17860
|
* The net effect is byte-identical to the FIRST ordering on every input where
|
|
17399
17861
|
* the un-certification did not fire — which is the whole point: it keeps that
|
|
@@ -17947,6 +18409,12 @@ function redactSecretsForState(bag, secrets, source, rules = TEMPLATE_DERIVED_RU
|
|
|
17947
18409
|
}
|
|
17948
18410
|
if (Array.isArray(value)) return value.map(walk);
|
|
17949
18411
|
if (value !== null && typeof value === "object") {
|
|
18412
|
+
if (isOrdinaryDate(value)) {
|
|
18413
|
+
const persisted = value.toJSON();
|
|
18414
|
+
if (typeof persisted !== "string") return value;
|
|
18415
|
+
const scanned = walk(persisted);
|
|
18416
|
+
return scanned === persisted ? value : scanned;
|
|
18417
|
+
}
|
|
17950
18418
|
const out = Object.create(null);
|
|
17951
18419
|
for (const [k, v] of Object.entries(value)) out[k] = walk(v);
|
|
17952
18420
|
return out;
|
|
@@ -33964,6 +34432,221 @@ function isTerminationProtectionPropagationError(message) {
|
|
|
33964
34432
|
return /may not be terminated|disableApiTermination/i.test(message);
|
|
33965
34433
|
}
|
|
33966
34434
|
|
|
34435
|
+
//#endregion
|
|
34436
|
+
//#region src/provisioning/wait-abandoned.ts
|
|
34437
|
+
/**
|
|
34438
|
+
* The "cdkd stopped watching an operation that may still be running" marker
|
|
34439
|
+
* (issue [#3236](https://github.com/go-to-k/cdkd/issues/3236), widened by
|
|
34440
|
+
* [#3253](https://github.com/go-to-k/cdkd/issues/3253)).
|
|
34441
|
+
*
|
|
34442
|
+
* `CloudControlWaitAbandonedError` is raised in `cloud-control-provider.ts`,
|
|
34443
|
+
* and `ec2-volume-delete.ts` marks its own abandoned volume-delete wait (issue
|
|
34444
|
+
* [#3455](https://github.com/go-to-k/cdkd/issues/3455)) with the same marker,
|
|
34445
|
+
* but FIVE call sites have to refuse it, and three of them live in other
|
|
34446
|
+
* files. Four are GOVERNED by
|
|
34447
|
+
* `tests/unit/provisioning/wait-abandoned-guard-population.test.ts`:
|
|
34448
|
+
*
|
|
34449
|
+
* - `cloud-control-provider.ts`'s own `delete()` catch,
|
|
34450
|
+
* - `deploy-engine.ts`'s replacement delete-then-CREATE arm,
|
|
34451
|
+
* - `deploy-engine.ts`'s template-removal delete arm,
|
|
34452
|
+
* - `destroy-runner.ts`'s per-resource delete loop.
|
|
34453
|
+
*
|
|
34454
|
+
* The FIFTH is `cleanupFailedCreateRemnant`, and it is outside that population
|
|
34455
|
+
* by construction rather than by oversight: its partner classifier is the
|
|
34456
|
+
* REGEX helper `isNotFoundMessage`, which carries no literal needles, so no
|
|
34457
|
+
* needle-driven scan can find it. It has a behavioural case instead, and it
|
|
34458
|
+
* needs the guard for a sharper reason than the other four — that regex is
|
|
34459
|
+
* case-INSENSITIVE, so `getaddrinfo ENOTFOUND ...` matches `/not\s*found/i`.
|
|
34460
|
+
*
|
|
34461
|
+
* Each decides "the resource is already gone" by SUBSTRING-matching the error
|
|
34462
|
+
* message, and each then DROPS the state row. An abandoned wait says the
|
|
34463
|
+
* opposite — the delete may still be running — and its message interpolates
|
|
34464
|
+
* the LOGICAL ID and the last-seen IDENTIFIER, both user- or template-chosen,
|
|
34465
|
+
* so a resource named `PageNotFound` satisfies those needles. Round 1 of
|
|
34466
|
+
* go-to-k/cdkd#3249 guarded the first site with a local `instanceof` and left
|
|
34467
|
+
* the other three, which is what this module exists to fix: ONE predicate the
|
|
34468
|
+
* three foreign sites can import without importing the provider.
|
|
34469
|
+
*
|
|
34470
|
+
* **A marker rather than an exported class + `instanceof`**, for the reason
|
|
34471
|
+
* `markNonRetryable` is one: `deploy-engine.ts` reaches the provider only
|
|
34472
|
+
* through `ProviderRegistry`, so importing the class there would add an edge
|
|
34473
|
+
* into an already dense ring, and a marker survives a wrapper that copies a
|
|
34474
|
+
* message without preserving a prototype.
|
|
34475
|
+
*
|
|
34476
|
+
* **NOT `markNonRetryable`**, though the mechanism is identical: a DELETE or
|
|
34477
|
+
* UPDATE abandonment is deliberately left RETRYABLE so the destroy runner can
|
|
34478
|
+
* re-issue an idempotent delete, and only CREATE is marked. Reusing that
|
|
34479
|
+
* marker would either make the guards miss DELETE — the case that drops a
|
|
34480
|
+
* state row — or make a DELETE non-retryable and lose the recovery.
|
|
34481
|
+
*
|
|
34482
|
+
* A LEAF: no imports, ever. All four consumers sit on the deploy engine ->
|
|
34483
|
+
* rollback executor -> registry -> provider ring, and a new edge into it from
|
|
34484
|
+
* any of them would close a cycle.
|
|
34485
|
+
*/
|
|
34486
|
+
const WAIT_ABANDONED_MARKER = Symbol.for("cdkd.waitAbandoned");
|
|
34487
|
+
/**
|
|
34488
|
+
* The `.cause` walk depth, the same bound `retryable-errors.ts` uses. cdkd
|
|
34489
|
+
* wraps errors, so the marked error is routinely one or two hops down.
|
|
34490
|
+
*/
|
|
34491
|
+
const MAX_CAUSE_CHAIN_DEPTH = 5;
|
|
34492
|
+
/**
|
|
34493
|
+
* Stamp an error as an abandoned wait.
|
|
34494
|
+
*
|
|
34495
|
+
* `E extends Error`, not `object`: the reader below is a PROTOTYPE-CHAIN
|
|
34496
|
+
* lookup, so marking a class or a shared prototype would silently mark every
|
|
34497
|
+
* instance of it. A non-extensible (frozen / sealed) error is returned
|
|
34498
|
+
* UNMARKED rather than allowed to throw — callers use this inline, so a
|
|
34499
|
+
* `TypeError` raised here would REPLACE the error the caller meant to raise.
|
|
34500
|
+
* Both rules are `markNonRetryable`'s, for its reasons.
|
|
34501
|
+
*/
|
|
34502
|
+
function markWaitAbandoned(error) {
|
|
34503
|
+
if (!Object.isExtensible(error)) return error;
|
|
34504
|
+
Object.defineProperty(error, WAIT_ABANDONED_MARKER, {
|
|
34505
|
+
value: true,
|
|
34506
|
+
enumerable: false,
|
|
34507
|
+
configurable: true,
|
|
34508
|
+
writable: false
|
|
34509
|
+
});
|
|
34510
|
+
return error;
|
|
34511
|
+
}
|
|
34512
|
+
/**
|
|
34513
|
+
* True when the error, or anything in its bounded `.cause` chain, was marked
|
|
34514
|
+
* by {@link markWaitAbandoned}.
|
|
34515
|
+
*
|
|
34516
|
+
* Every already-deleted classifier must test this BEFORE its substring match,
|
|
34517
|
+
* and must treat a true answer as "not already deleted" — i.e. re-throw. The
|
|
34518
|
+
* ordering is the whole point: a message-first guard has already decided by
|
|
34519
|
+
* the time it would consult the marker.
|
|
34520
|
+
*/
|
|
34521
|
+
function isWaitAbandonedError(error) {
|
|
34522
|
+
let current = error;
|
|
34523
|
+
for (let depth = 0; depth < MAX_CAUSE_CHAIN_DEPTH && current != null; depth++) {
|
|
34524
|
+
if (current[WAIT_ABANDONED_MARKER] === true) return true;
|
|
34525
|
+
current = current.cause;
|
|
34526
|
+
}
|
|
34527
|
+
return false;
|
|
34528
|
+
}
|
|
34529
|
+
|
|
34530
|
+
//#endregion
|
|
34531
|
+
//#region src/provisioning/ec2-volume-delete.ts
|
|
34532
|
+
/**
|
|
34533
|
+
* Delete a Cloud-Control-routed `AWS::EC2::Volume` with EC2 `DeleteVolume`
|
|
34534
|
+
* instead of Cloud Control `DeleteResource` (issue
|
|
34535
|
+
* [#3455](https://github.com/go-to-k/cdkd/issues/3455)).
|
|
34536
|
+
*
|
|
34537
|
+
* WHY THE CLOUD CONTROL DELETE IS BYPASSED. The registry delete handler for
|
|
34538
|
+
* this type holds `ec2:CreateSnapshot` (it implements CloudFormation's
|
|
34539
|
+
* `DeletionPolicy: Snapshot` itself), and it has been observed taking that
|
|
34540
|
+
* snapshot on a plain Cloud Control delete, which carries no deletion policy
|
|
34541
|
+
* at all: CloudTrail shows a `CreateSnapshot` from `cloudformation.amazonaws.com`
|
|
34542
|
+
* in the same second as cdkd's `DeleteResource`, then the handler's
|
|
34543
|
+
* `DeleteVolume` while that snapshot was still pending. Both times it
|
|
34544
|
+
* happened, the volume then sat in `deleting` for well over the 15-minute
|
|
34545
|
+
* wait, the delete failed, and the handler's snapshot was left behind
|
|
34546
|
+
* untracked. What makes the handler do it is not known, so the only fix that
|
|
34547
|
+
* does not depend on the trigger is not to invoke the handler. The EC2 call is
|
|
34548
|
+
* the same one the handler makes; cdkd's own final snapshot (when the policy
|
|
34549
|
+
* asks for one) is taken and waited to `completed` before this runs.
|
|
34550
|
+
*
|
|
34551
|
+
* CONTRACT, matching the Cloud Control delete it replaces:
|
|
34552
|
+
* - Resolves only once the volume is GONE (`InvalidVolume.NotFound`, or state
|
|
34553
|
+
* `deleted`) — returning means "deleted" to every caller.
|
|
34554
|
+
* - `DeleteVolume`'s own errors propagate UNCHANGED, so the caller's
|
|
34555
|
+
* existing already-deleted arm (region-checked) sees `InvalidVolume.NotFound`
|
|
34556
|
+
* exactly as it saw Cloud Control's `NotFound`, and every other failure
|
|
34557
|
+
* takes the caller's ordinary wrap.
|
|
34558
|
+
* - `IncorrectState` from `DeleteVolume` on a volume that is already
|
|
34559
|
+
* `deleting`, `deleted` or gone (a re-run after an interrupted destroy, or
|
|
34560
|
+
* a race with one) is waited on rather than failed. If the describe that
|
|
34561
|
+
* classifies it fails, the ORIGINAL `IncorrectState` is rethrown, carrying
|
|
34562
|
+
* the describe failure as its `cause`.
|
|
34563
|
+
* - A TRANSIENT describe failure during the wait (a throttle, a 5xx, a
|
|
34564
|
+
* socket error — `isTransientFailure`, the Cloud Control poll's own
|
|
34565
|
+
* classifier) is re-polled for up to `transientGraceMs` of unbroken
|
|
34566
|
+
* failures, the same grace the Cloud Control wait gives its status poll.
|
|
34567
|
+
* - Every way the wait can end without seeing the volume gone — the
|
|
34568
|
+
* `maxWaitMs` deadline, the grace running out, a non-transient describe
|
|
34569
|
+
* failure — throws an error marked as an abandoned wait
|
|
34570
|
+
* (`wait-abandoned.ts`). Every already-deleted classifier refuses that
|
|
34571
|
+
* marker structurally, and they must: they match SUBSTRINGS of a message
|
|
34572
|
+
* that interpolates the user's logical id (`PageNotFound` would match). So
|
|
34573
|
+
* the state record is KEPT, and the error stays retryable, like a Cloud
|
|
34574
|
+
* Control DELETE abandonment, because a re-issued `DeleteVolume` is
|
|
34575
|
+
* idempotent.
|
|
34576
|
+
*/
|
|
34577
|
+
async function deleteEc2VolumeDirect(client, volumeId, logicalId, opts) {
|
|
34578
|
+
const now = opts.now ?? Date.now;
|
|
34579
|
+
try {
|
|
34580
|
+
await client.send(new DeleteVolumeCommand({ VolumeId: volumeId }));
|
|
34581
|
+
opts.logger.debug(`DeleteVolume accepted for ${logicalId} (${volumeId})`);
|
|
34582
|
+
} catch (error) {
|
|
34583
|
+
if (errorName(error) !== "IncorrectState") throw error;
|
|
34584
|
+
let state;
|
|
34585
|
+
try {
|
|
34586
|
+
state = await readVolumeState(client, volumeId);
|
|
34587
|
+
} catch (describeError) {
|
|
34588
|
+
throw withCause(error, describeError);
|
|
34589
|
+
}
|
|
34590
|
+
if (!(state === void 0 || state === "deleted" || state === "deleting")) throw error;
|
|
34591
|
+
opts.logger.debug(`${logicalId} (${volumeId}) is already ${state ?? "gone"}; waiting for it to go`);
|
|
34592
|
+
}
|
|
34593
|
+
const abandoned = (reason, cause) => markWaitAbandoned(new ProvisioningError(`DELETE of ${logicalId} (${volumeId}) was accepted by EC2, but ${reason}. The state record is kept; re-run the command once the volume is gone.`, "AWS::EC2::Volume", logicalId, volumeId, cause instanceof Error ? cause : void 0));
|
|
34594
|
+
const deadline = now() + opts.maxWaitMs;
|
|
34595
|
+
let interval = 2e3;
|
|
34596
|
+
let outageStartedAt;
|
|
34597
|
+
for (;;) {
|
|
34598
|
+
let state;
|
|
34599
|
+
let polled = false;
|
|
34600
|
+
try {
|
|
34601
|
+
state = await readVolumeState(client, volumeId);
|
|
34602
|
+
polled = true;
|
|
34603
|
+
outageStartedAt = void 0;
|
|
34604
|
+
} catch (pollError) {
|
|
34605
|
+
if (!opts.isTransientFailure(pollError)) throw abandoned(`cdkd could not read the volume's state: ${describeAwsFailure(pollError).summary}`, pollError);
|
|
34606
|
+
outageStartedAt ??= now();
|
|
34607
|
+
const outageMs = now() - outageStartedAt;
|
|
34608
|
+
if (outageMs >= opts.transientGraceMs) throw abandoned(`cdkd could not read the volume's state for ${Math.round(outageMs / 1e3)}s: ` + describeAwsFailure(pollError).summary, pollError);
|
|
34609
|
+
opts.logger.debug(`Could not read ${logicalId} (${volumeId}) while waiting for its delete; re-polling: ${describeAwsFailure(pollError).detail}`);
|
|
34610
|
+
}
|
|
34611
|
+
if (polled && (state === void 0 || state === "deleted")) return;
|
|
34612
|
+
if (now() >= deadline) throw abandoned(polled ? `the volume was still '${state}' after ${Math.round(opts.maxWaitMs / 1e3)}s` : `cdkd could not read the volume's state before the ${Math.round(opts.maxWaitMs / 1e3)}s wait ran out`);
|
|
34613
|
+
if (polled) opts.logger.debug(`Waiting for ${logicalId} (${volumeId}) to be deleted (state: ${state})`);
|
|
34614
|
+
await opts.sleep(interval);
|
|
34615
|
+
interval = Math.min(Math.ceil(interval * 1.5), 1e4);
|
|
34616
|
+
}
|
|
34617
|
+
}
|
|
34618
|
+
/**
|
|
34619
|
+
* Rethrow `original` carrying `cause`, without replacing it: the caller's
|
|
34620
|
+
* classification (the idempotent arm, the retry classifier) must see the
|
|
34621
|
+
* `DeleteVolume` failure, not the describe that tried to explain it. A
|
|
34622
|
+
* non-extensible error, or one that already has a cause, is returned as-is.
|
|
34623
|
+
*/
|
|
34624
|
+
function withCause(original, cause) {
|
|
34625
|
+
if (typeof original === "object" && original !== null && Object.isExtensible(original) && original.cause === void 0) Object.defineProperty(original, "cause", {
|
|
34626
|
+
value: cause,
|
|
34627
|
+
configurable: true,
|
|
34628
|
+
writable: true,
|
|
34629
|
+
enumerable: false
|
|
34630
|
+
});
|
|
34631
|
+
return original;
|
|
34632
|
+
}
|
|
34633
|
+
/** The volume's state, or `undefined` when EC2 reports it does not exist. */
|
|
34634
|
+
async function readVolumeState(client, volumeId) {
|
|
34635
|
+
try {
|
|
34636
|
+
const volume = (await client.send(new DescribeVolumesCommand({ VolumeIds: [volumeId] }))).Volumes?.[0];
|
|
34637
|
+
return volume === void 0 ? void 0 : volume.State ?? "unknown";
|
|
34638
|
+
} catch (error) {
|
|
34639
|
+
if (errorName(error) === "InvalidVolume.NotFound") return void 0;
|
|
34640
|
+
throw error;
|
|
34641
|
+
}
|
|
34642
|
+
}
|
|
34643
|
+
/** The error's `name`, or `''` — never throws, whatever was rejected. */
|
|
34644
|
+
function errorName(error) {
|
|
34645
|
+
if (typeof error !== "object" || error === null) return "";
|
|
34646
|
+
const name = error.name;
|
|
34647
|
+
return typeof name === "string" ? name : "";
|
|
34648
|
+
}
|
|
34649
|
+
|
|
33967
34650
|
//#endregion
|
|
33968
34651
|
//#region src/deployment/delete-outcome.ts
|
|
33969
34652
|
/**
|
|
@@ -34834,99 +35517,6 @@ function slowCcOperationTimeoutMs(resourceType, operation) {
|
|
|
34834
35517
|
}
|
|
34835
35518
|
}
|
|
34836
35519
|
|
|
34837
|
-
//#endregion
|
|
34838
|
-
//#region src/provisioning/wait-abandoned.ts
|
|
34839
|
-
/**
|
|
34840
|
-
* The "cdkd stopped watching an operation that may still be running" marker
|
|
34841
|
-
* (issue [#3236](https://github.com/go-to-k/cdkd/issues/3236), widened by
|
|
34842
|
-
* [#3253](https://github.com/go-to-k/cdkd/issues/3253)).
|
|
34843
|
-
*
|
|
34844
|
-
* `CloudControlWaitAbandonedError` is raised in `cloud-control-provider.ts`,
|
|
34845
|
-
* but FIVE call sites have to refuse it, and three of them live in other
|
|
34846
|
-
* files. Four are GOVERNED by
|
|
34847
|
-
* `tests/unit/provisioning/wait-abandoned-guard-population.test.ts`:
|
|
34848
|
-
*
|
|
34849
|
-
* - `cloud-control-provider.ts`'s own `delete()` catch,
|
|
34850
|
-
* - `deploy-engine.ts`'s replacement delete-then-CREATE arm,
|
|
34851
|
-
* - `deploy-engine.ts`'s template-removal delete arm,
|
|
34852
|
-
* - `destroy-runner.ts`'s per-resource delete loop.
|
|
34853
|
-
*
|
|
34854
|
-
* The FIFTH is `cleanupFailedCreateRemnant`, and it is outside that population
|
|
34855
|
-
* by construction rather than by oversight: its partner classifier is the
|
|
34856
|
-
* REGEX helper `isNotFoundMessage`, which carries no literal needles, so no
|
|
34857
|
-
* needle-driven scan can find it. It has a behavioural case instead, and it
|
|
34858
|
-
* needs the guard for a sharper reason than the other four — that regex is
|
|
34859
|
-
* case-INSENSITIVE, so `getaddrinfo ENOTFOUND ...` matches `/not\s*found/i`.
|
|
34860
|
-
*
|
|
34861
|
-
* Each decides "the resource is already gone" by SUBSTRING-matching the error
|
|
34862
|
-
* message, and each then DROPS the state row. An abandoned wait says the
|
|
34863
|
-
* opposite — the delete may still be running — and its message interpolates
|
|
34864
|
-
* the LOGICAL ID and the last-seen IDENTIFIER, both user- or template-chosen,
|
|
34865
|
-
* so a resource named `PageNotFound` satisfies those needles. Round 1 of
|
|
34866
|
-
* go-to-k/cdkd#3249 guarded the first site with a local `instanceof` and left
|
|
34867
|
-
* the other three, which is what this module exists to fix: ONE predicate the
|
|
34868
|
-
* three foreign sites can import without importing the provider.
|
|
34869
|
-
*
|
|
34870
|
-
* **A marker rather than an exported class + `instanceof`**, for the reason
|
|
34871
|
-
* `markNonRetryable` is one: `deploy-engine.ts` reaches the provider only
|
|
34872
|
-
* through `ProviderRegistry`, so importing the class there would add an edge
|
|
34873
|
-
* into an already dense ring, and a marker survives a wrapper that copies a
|
|
34874
|
-
* message without preserving a prototype.
|
|
34875
|
-
*
|
|
34876
|
-
* **NOT `markNonRetryable`**, though the mechanism is identical: a DELETE or
|
|
34877
|
-
* UPDATE abandonment is deliberately left RETRYABLE so the destroy runner can
|
|
34878
|
-
* re-issue an idempotent delete, and only CREATE is marked. Reusing that
|
|
34879
|
-
* marker would either make the guards miss DELETE — the case that drops a
|
|
34880
|
-
* state row — or make a DELETE non-retryable and lose the recovery.
|
|
34881
|
-
*
|
|
34882
|
-
* A LEAF: no imports, ever. All four consumers sit on the deploy engine ->
|
|
34883
|
-
* rollback executor -> registry -> provider ring, and a new edge into it from
|
|
34884
|
-
* any of them would close a cycle.
|
|
34885
|
-
*/
|
|
34886
|
-
const WAIT_ABANDONED_MARKER = Symbol.for("cdkd.waitAbandoned");
|
|
34887
|
-
/**
|
|
34888
|
-
* The `.cause` walk depth, the same bound `retryable-errors.ts` uses. cdkd
|
|
34889
|
-
* wraps errors, so the marked error is routinely one or two hops down.
|
|
34890
|
-
*/
|
|
34891
|
-
const MAX_CAUSE_CHAIN_DEPTH = 5;
|
|
34892
|
-
/**
|
|
34893
|
-
* Stamp an error as an abandoned wait.
|
|
34894
|
-
*
|
|
34895
|
-
* `E extends Error`, not `object`: the reader below is a PROTOTYPE-CHAIN
|
|
34896
|
-
* lookup, so marking a class or a shared prototype would silently mark every
|
|
34897
|
-
* instance of it. A non-extensible (frozen / sealed) error is returned
|
|
34898
|
-
* UNMARKED rather than allowed to throw — callers use this inline, so a
|
|
34899
|
-
* `TypeError` raised here would REPLACE the error the caller meant to raise.
|
|
34900
|
-
* Both rules are `markNonRetryable`'s, for its reasons.
|
|
34901
|
-
*/
|
|
34902
|
-
function markWaitAbandoned(error) {
|
|
34903
|
-
if (!Object.isExtensible(error)) return error;
|
|
34904
|
-
Object.defineProperty(error, WAIT_ABANDONED_MARKER, {
|
|
34905
|
-
value: true,
|
|
34906
|
-
enumerable: false,
|
|
34907
|
-
configurable: true,
|
|
34908
|
-
writable: false
|
|
34909
|
-
});
|
|
34910
|
-
return error;
|
|
34911
|
-
}
|
|
34912
|
-
/**
|
|
34913
|
-
* True when the error, or anything in its bounded `.cause` chain, was marked
|
|
34914
|
-
* by {@link markWaitAbandoned}.
|
|
34915
|
-
*
|
|
34916
|
-
* Every already-deleted classifier must test this BEFORE its substring match,
|
|
34917
|
-
* and must treat a true answer as "not already deleted" — i.e. re-throw. The
|
|
34918
|
-
* ordering is the whole point: a message-first guard has already decided by
|
|
34919
|
-
* the time it would consult the marker.
|
|
34920
|
-
*/
|
|
34921
|
-
function isWaitAbandonedError(error) {
|
|
34922
|
-
let current = error;
|
|
34923
|
-
for (let depth = 0; depth < MAX_CAUSE_CHAIN_DEPTH && current != null; depth++) {
|
|
34924
|
-
if (current[WAIT_ABANDONED_MARKER] === true) return true;
|
|
34925
|
-
current = current.cause;
|
|
34926
|
-
}
|
|
34927
|
-
return false;
|
|
34928
|
-
}
|
|
34929
|
-
|
|
34930
35520
|
//#endregion
|
|
34931
35521
|
//#region src/provisioning/cloud-control-provider.ts
|
|
34932
35522
|
/**
|
|
@@ -35607,7 +36197,7 @@ var CloudControlProvider = class {
|
|
|
35607
36197
|
const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
|
|
35608
36198
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
35609
36199
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
35610
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
36200
|
+
const { ASGProvider } = await import("./asg-provider-BY-tj21g.js").then((n) => n.n);
|
|
35611
36201
|
const asgProvider = new ASGProvider();
|
|
35612
36202
|
return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
|
|
35613
36203
|
}
|
|
@@ -35617,8 +36207,20 @@ var CloudControlProvider = class {
|
|
|
35617
36207
|
const protectionEntry = ccProtectionProperty(resourceType);
|
|
35618
36208
|
if (protectionEntry) await this.disableCcProtection(logicalId, physicalId, resourceType, protectionEntry);
|
|
35619
36209
|
}
|
|
36210
|
+
const volumeEc2Client = resourceType === "AWS::EC2::Volume" ? await this.ec2ClientInCcRegion(resourceType, logicalId, physicalId) : void 0;
|
|
35620
36211
|
const maxAttempts = isProtectedEc2Instance ? 5 : 1;
|
|
35621
36212
|
for (let attempt = 1;; attempt++) try {
|
|
36213
|
+
if (volumeEc2Client !== void 0) {
|
|
36214
|
+
await deleteEc2VolumeDirect(volumeEc2Client, physicalId, logicalId, {
|
|
36215
|
+
logger: this.logger,
|
|
36216
|
+
sleep: (ms) => this.sleep(ms),
|
|
36217
|
+
maxWaitMs: Math.max(this.MAX_WAIT_TIME_MS, slowCcOperationTimeoutMs(resourceType, "DELETE")),
|
|
36218
|
+
transientGraceMs: this.POLL_TRANSIENT_GRACE_MS,
|
|
36219
|
+
isTransientFailure: isTransientPollFailure
|
|
36220
|
+
});
|
|
36221
|
+
this.logger.debug(`Deleted resource ${logicalId}`);
|
|
36222
|
+
return withIndeterminateGuard(void 0, indeterminateGuard);
|
|
36223
|
+
}
|
|
35622
36224
|
const deleteResponse = await this.cloudControlClient.send(new DeleteResourceCommand({
|
|
35623
36225
|
TypeName: resourceType,
|
|
35624
36226
|
Identifier: physicalId
|
|
@@ -35646,6 +36248,32 @@ var CloudControlProvider = class {
|
|
|
35646
36248
|
}
|
|
35647
36249
|
}
|
|
35648
36250
|
/**
|
|
36251
|
+
* The EC2 client for a delete this provider issues through EC2 instead of
|
|
36252
|
+
* Cloud Control (issue #3455), refused unless it targets the SAME region as
|
|
36253
|
+
* the Cloud Control client the region pre-flight vetted.
|
|
36254
|
+
*
|
|
36255
|
+
* `getAwsClients()` is read at call time while `cloudControlClient` was
|
|
36256
|
+
* captured at construction, so the two are not the same object and could
|
|
36257
|
+
* name different regions. The comparison is load-bearing rather than
|
|
36258
|
+
* defensive: a volume id is region-scoped, so a wrong-region `DeleteVolume`
|
|
36259
|
+
* answers `InvalidVolume.NotFound`, which the caller's already-deleted arm
|
|
36260
|
+
* would take as success and drop the record of a live volume.
|
|
36261
|
+
*/
|
|
36262
|
+
async ec2ClientInCcRegion(resourceType, logicalId, physicalId) {
|
|
36263
|
+
const ec2 = getAwsClients().ec2;
|
|
36264
|
+
const resolve = async (client) => {
|
|
36265
|
+
try {
|
|
36266
|
+
return canonicalizeRegion((await client.config.region())?.trim());
|
|
36267
|
+
} catch {
|
|
36268
|
+
return;
|
|
36269
|
+
}
|
|
36270
|
+
};
|
|
36271
|
+
const ccRegion = await resolve(this.cloudControlClient);
|
|
36272
|
+
const ec2Region = await resolve(ec2);
|
|
36273
|
+
if (ccRegion === void 0 || ccRegion === "" || ccRegion !== ec2Region) throw markNonRetryable(new ProvisioningError(`Refusing to delete ${logicalId} (${resourceType}, ${physicalId}): the EC2 client targets region '${ec2Region ?? "unknown"}' but the Cloud Control client targets '${ccRegion ?? "unknown"}', so cdkd cannot show the delete would reach this volume. Re-run with --region set to the stack's region.`, resourceType, logicalId, physicalId));
|
|
36274
|
+
return ec2;
|
|
36275
|
+
}
|
|
36276
|
+
/**
|
|
35649
36277
|
* Refuse a Cloud Control call whose target region cannot be shown to be the
|
|
35650
36278
|
* one the state record was written in (issue #2301).
|
|
35651
36279
|
*
|
|
@@ -45173,6 +45801,53 @@ function updatePartialMessage(reason) {
|
|
|
45173
45801
|
//#endregion
|
|
45174
45802
|
//#region src/deployment/rollback-executor.ts
|
|
45175
45803
|
/**
|
|
45804
|
+
* Rollback executor (issue #1183).
|
|
45805
|
+
*
|
|
45806
|
+
* The reusable engine that reverts a list of {@link CompletedOperation}s —
|
|
45807
|
+
* extracted from `DeployEngine` so BOTH callers drive identical semantics:
|
|
45808
|
+
*
|
|
45809
|
+
* - `DeployEngine` runs it in-process after a failed deploy (automatic
|
|
45810
|
+
* rollback, unchanged behavior except the two fixes below).
|
|
45811
|
+
* - The standalone `cdkd rollback` command runs it against a persisted
|
|
45812
|
+
* rollback journal (issue #1183 §journal), so a `--no-rollback` /
|
|
45813
|
+
* interrupted / partially-failed-auto-rollback deploy can be reverted
|
|
45814
|
+
* later.
|
|
45815
|
+
*
|
|
45816
|
+
* The executor deliberately depends only on `ProviderRegistry`, the stack
|
|
45817
|
+
* region, a logger, and an optional event recorder + per-op state-save
|
|
45818
|
+
* hook. It does NOT touch `DagBuilder` / `DiffCalculator` / the synthesizer
|
|
45819
|
+
* / `ExportIndexStore`, so the command can construct it without any of the
|
|
45820
|
+
* engine's synth-side collaborators (rollback never publishes
|
|
45821
|
+
* outputs/exports).
|
|
45822
|
+
*
|
|
45823
|
+
* Two deliberate behavior fixes vs. the pre-extraction in-process path
|
|
45824
|
+
* (both are pre-existing gaps; fixing them once benefits both callers):
|
|
45825
|
+
*
|
|
45826
|
+
* 1. **DeletionPolicy on CREATE rollback** — rolling a CREATE back IS a
|
|
45827
|
+
* delete as far as the policy is concerned (CloudFormation semantics), so
|
|
45828
|
+
* the CURRENT state record's `DeletionPolicy` decides what happens:
|
|
45829
|
+
* - `Retain` → ORPHANED (removed from state, left in AWS). The policy
|
|
45830
|
+
* says KEEP the resource, so cdkd does.
|
|
45831
|
+
* - `Snapshot` → final snapshot, THEN delete (issue #1358), through the
|
|
45832
|
+
* same mechanism matrix as the deploy engine's
|
|
45833
|
+
* `prepareFinalSnapshotForDelete`: atomic delete parameter for the
|
|
45834
|
+
* SDK-routed `ATOMIC_FINAL_SNAPSHOT_TYPES`, an explicit pre-delete
|
|
45835
|
+
* snapshot for `PRE_DELETE_SNAPSHOT_TYPES`, refusal for every other
|
|
45836
|
+
* Snapshot shape (cc-api routing included) unless
|
|
45837
|
+
* `--skip-final-snapshot` opts into the data loss. This arm ORPHANED
|
|
45838
|
+
* alongside `Retain` until #1358: when this file was written cdkd could
|
|
45839
|
+
* not create a final snapshot at all, so leaving the resource behind
|
|
45840
|
+
* was the only non-destructive option — but it silently handed the user
|
|
45841
|
+
* an untracked, billing resource that state no longer knew about.
|
|
45842
|
+
* `src/provisioning/final-snapshot.ts` (#1352 / #1353) removed the
|
|
45843
|
+
* constraint, so the policy is now honored literally.
|
|
45844
|
+
* - `RetainExceptOnCreate` (which exists precisely to allow cleanup of
|
|
45845
|
+
* failed creates) and absent / `Delete` → DELETE.
|
|
45846
|
+
* 2. **Idempotent replay skip rules** — so a partially-failed rollback can
|
|
45847
|
+
* be re-run safely (also harmless for the in-process caller, which
|
|
45848
|
+
* replays each op exactly once).
|
|
45849
|
+
*/
|
|
45850
|
+
/**
|
|
45176
45851
|
* Issue [#1762](https://github.com/go-to-k/cdkd/issues/1762): turn a
|
|
45177
45852
|
* `{ outcome: 'skipped' }` delete into a thrown error at every rollback delete
|
|
45178
45853
|
* arm.
|
|
@@ -46499,7 +47174,14 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
46499
47174
|
createLayer: createProvisionedBy
|
|
46500
47175
|
}) && !deletedNewFirst && createResult.physicalId === current.physicalId;
|
|
46501
47176
|
if (adoptedLiveNewResource) {
|
|
46502
|
-
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
|
|
47177
|
+
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 inspect the drift and run 'cdkd deploy' to reconcile, or rename the resource to make the replacement reversible.\nInspect it with: ${pasteableCommand("cdkd drift", [{
|
|
47178
|
+
value: stackName,
|
|
47179
|
+
hole: "stack"
|
|
47180
|
+
}, {
|
|
47181
|
+
flag: "--stack-region",
|
|
47182
|
+
value: ctx.region,
|
|
47183
|
+
hole: "region"
|
|
47184
|
+
}]).command}`);
|
|
46503
47185
|
result.warnings++;
|
|
46504
47186
|
}
|
|
46505
47187
|
const { observedProperties: _staleObserved, ...prevRecord } = prev;
|
|
@@ -48227,27 +48909,6 @@ var DeployEngine = class DeployEngine {
|
|
|
48227
48909
|
const foreignReads = reads.filter((read) => !isLocal(read));
|
|
48228
48910
|
const hasRefStateRead = reads.some((read) => read.kind === "ref-state-key" && isLocal(read));
|
|
48229
48911
|
/**
|
|
48230
|
-
* A logical id rendered inside the single-quoted command below.
|
|
48231
|
-
*
|
|
48232
|
-
* cdkd validates no logical-id charset, so the id can contain a `'` — and
|
|
48233
|
-
* the line it lands in is meant to be COPY-PASTED into a shell, where an
|
|
48234
|
-
* unescaped one closes the quoting early and the rest of the command
|
|
48235
|
-
* reparses as something else. POSIX single-quote escaping (`'` becomes
|
|
48236
|
-
* `'\\''`) is the fix: close, emit an escaped quote, reopen.
|
|
48237
|
-
*
|
|
48238
|
-
* cdkd never EXECUTES this string — it is advice in an error message — so
|
|
48239
|
-
* this is about the pasted command being correct, not about injection into
|
|
48240
|
-
* cdkd itself (issue #2847 round-5 review).
|
|
48241
|
-
*
|
|
48242
|
-
* SINCE go-to-k/cdkd#3435 no id reaching this arm can contain a `'`:
|
|
48243
|
-
* `isPasteableIdent` admits `[A-Za-z0-9][A-Za-z0-9~_.-]*` only, and the
|
|
48244
|
-
* command is not emitted for anything else. Kept anyway rather than
|
|
48245
|
-
* deleted — dropping a guard on the strength of a NEW guard one line up is
|
|
48246
|
-
* how the pair later comes apart, and this one costs a `replaceAll` on a
|
|
48247
|
-
* short string.
|
|
48248
|
-
*/
|
|
48249
|
-
const quoteSafe = (id) => id.replaceAll("'", `'\\''`);
|
|
48250
|
-
/**
|
|
48251
48912
|
* A logical id rendered as a NAME rather than as a command argument
|
|
48252
48913
|
* (go-to-k/cdkd#3435 security round).
|
|
48253
48914
|
*
|
|
@@ -48262,11 +48923,24 @@ var DeployEngine = class DeployEngine {
|
|
|
48262
48923
|
/** CloudFormation's own logical-id length bound. */
|
|
48263
48924
|
const CFN_LOGICAL_ID_MAX_LENGTH = 255;
|
|
48264
48925
|
const parts = [];
|
|
48926
|
+
/** Pasteable commands, emitted LAST and one per line (go-to-k/cdkd#3436). */
|
|
48927
|
+
const commandLines = [];
|
|
48265
48928
|
if (localTargets.length > 0) {
|
|
48266
48929
|
const pasteableHere = (id) => isPasteableIdent(id) && id.length <= CFN_LOGICAL_ID_MAX_LENGTH;
|
|
48267
48930
|
const pasteable = localTargets.filter(pasteableHere);
|
|
48268
48931
|
const withheld = localTargets.filter((id) => !pasteableHere(id));
|
|
48269
|
-
if (pasteable.length > 0)
|
|
48932
|
+
if (pasteable.length > 0) {
|
|
48933
|
+
parts.push(`Re-import the record that HOLDS the mask (command(s) below).`);
|
|
48934
|
+
for (const id of pasteable) commandLines.push(`Re-import with: ${pasteableCommand("cdkd import", [
|
|
48935
|
+
{ hole: "stack" },
|
|
48936
|
+
{
|
|
48937
|
+
flag: "--resource",
|
|
48938
|
+
value: `${id}=<physicalId>`,
|
|
48939
|
+
hole: "resource"
|
|
48940
|
+
},
|
|
48941
|
+
{ literal: "--force" }
|
|
48942
|
+
]).command}`);
|
|
48943
|
+
}
|
|
48270
48944
|
if (withheld.length > 0) parts.push(`Re-import the record that HOLDS the mask for ${withheld.map(shown).join(", ")}, but the command is withheld: that is not a plain CloudFormation logical id, so a pasted 'cdkd import <stack> --resource <id>=<physicalId> --force' could be reshaped by the shell or name a different resource. Read the id from 'cdkd state show' and quote it yourself.`);
|
|
48271
48945
|
}
|
|
48272
48946
|
if (unclearableTargets.length > 0) parts.push(`Do NOT re-import ${unclearableTargets.map(shown).join(", ")}: a custom resource's import records no attributes, so the masked bag is carried forward unchanged and the refusal repeats. Cause (1) above is the one that applies to it.`);
|
|
@@ -48275,7 +48949,7 @@ var DeployEngine = class DeployEngine {
|
|
|
48275
48949
|
parts.push(`${subject} through ANOTHER stack (an Fn::ImportValue, an Fn::GetStackOutput, or a nested stack's Outputs), whose masked record lives in that stack's state — re-importing anything in this stack cannot clear it; act on the producer stack instead.`);
|
|
48276
48950
|
}
|
|
48277
48951
|
if (hasRefStateRead) parts.push("A 'Ref <LogicalId> (state key <Key>)' entry above is CDKD's own read, not one the template can stop making: CloudFormation defines that resource type's Ref value as that state key rather than the physical id, so the record must be repaired (re-import it, or let a deploy create or update the resource) — the \"stop reading it\" remedy does not apply to such an entry.");
|
|
48278
|
-
return parts.join(" ");
|
|
48952
|
+
return [parts.join(" "), ...commandLines].join("\n");
|
|
48279
48953
|
}
|
|
48280
48954
|
/**
|
|
48281
48955
|
* Refuse to provision a resource whose resolution served a REDACTED attribute
|
|
@@ -48318,7 +48992,7 @@ var DeployEngine = class DeployEngine {
|
|
|
48318
48992
|
refuseRedactedAttributeReads(logicalId, resourceType, context) {
|
|
48319
48993
|
const reads = context.redactedAttributeReads;
|
|
48320
48994
|
if (reads === void 0 || reads.length === 0) return;
|
|
48321
|
-
throw new ProvisioningError(`Cannot resolve ${reads.map((read) => read.display).join(", ")} for ${logicalId}: cdkd's recorded state holds only the redaction mask there, and the value is not recoverable from state. There are two ways a record comes to hold the mask. (1) A custom resource handler declared its response NoEcho: true — the value is generated by the handler, so cdkd has nothing to re-derive it from and must not write the literal mask to AWS. Remedies: force that custom resource to update (change one of its properties, e.g. a nonce / version property) so its handler runs again and supplies the value in this same run; or stop setting NoEcho on that response. If the value comes from ANOTHER stack, the producer and this stack must deploy in ONE run (cdkd deploy --all) with the producer's custom resource actually running — re-deploying the producer by itself does not help, because it re-masks the value on the way into its own state. (2) The resource was adopted by 'cdkd import' through the Cloud Control fallback, which records only the attributes the type's CloudFormation schema declares read-only and masks the rest. Either the attribute named above is not one of them — CloudFormation would reject an Fn::GetAtt naming it too, so stop reading it — or cdkd could not read that schema and masked the whole model, which the import warned about when it happened.
|
|
48995
|
+
throw new ProvisioningError(`Cannot resolve ${reads.map((read) => read.display).join(", ")} for ${logicalId}: cdkd's recorded state holds only the redaction mask there, and the value is not recoverable from state. There are two ways a record comes to hold the mask. (1) A custom resource handler declared its response NoEcho: true — the value is generated by the handler, so cdkd has nothing to re-derive it from and must not write the literal mask to AWS. Remedies: force that custom resource to update (change one of its properties, e.g. a nonce / version property) so its handler runs again and supplies the value in this same run; or stop setting NoEcho on that response. If the value comes from ANOTHER stack, the producer and this stack must deploy in ONE run (cdkd deploy --all) with the producer's custom resource actually running — re-deploying the producer by itself does not help, because it re-masks the value on the way into its own state. (2) The resource was adopted by 'cdkd import' through the Cloud Control fallback, which records only the attributes the type's CloudFormation schema declares read-only and masks the rest. Either the attribute named above is not one of them — CloudFormation would reject an Fn::GetAtt naming it too, so stop reading it — or cdkd could not read that schema and masked the whole model, which the import warned about when it happened. If that warning named a missing cloudformation:DescribeType permission, grant it first. See https://github.com/go-to-k/cdkd/issues/2449. ${DeployEngine.maskedRecordRemedyFor(reads, context.resources)}`, resourceType, logicalId);
|
|
48322
48996
|
}
|
|
48323
48997
|
redactOperationsForJournal(operations) {
|
|
48324
48998
|
return operations.map((op) => {
|
|
@@ -48841,7 +49515,13 @@ var DeployEngine = class DeployEngine {
|
|
|
48841
49515
|
const journal = await this.stateBackend.loadRollbackJournal(stackName, this.stackRegion);
|
|
48842
49516
|
if (journal && journal.segments.length > 0) {
|
|
48843
49517
|
const failedOnly = journal.segments.every((s) => s.operations.length === 0) && journal.segments.some((s) => (s.failedOperations?.length ?? 0) > 0);
|
|
48844
|
-
this.logger.info(failedOnly ? `A previous deploy of '${stackName}' failed and was automatically rolled back. The failed resource may be partially applied —
|
|
49518
|
+
this.logger.info(failedOnly ? `A previous deploy of '${stackName}' failed and was automatically rolled back. The failed resource may be partially applied — revert it, or continue deploying to fix forward (a successful deploy clears this note).\nRevert it with: ${pasteableCommand("cdkd rollback", [{
|
|
49519
|
+
value: stackName,
|
|
49520
|
+
hole: "stack"
|
|
49521
|
+
}, { literal: "--revert-failed" }]).command}` : `A previous deploy of '${stackName}' failed or was interrupted. Revert it, or continue deploying to fix forward.\nRevert it with: ${pasteableCommand("cdkd rollback", [{
|
|
49522
|
+
value: stackName,
|
|
49523
|
+
hole: "stack"
|
|
49524
|
+
}]).command}`);
|
|
48845
49525
|
}
|
|
48846
49526
|
} catch {}
|
|
48847
49527
|
this.stampReasonlessParameterRefusals(currentState.resources, template);
|
|
@@ -49470,7 +50150,10 @@ var DeployEngine = class DeployEngine {
|
|
|
49470
50150
|
return;
|
|
49471
50151
|
}
|
|
49472
50152
|
await this.writeRollbackJournalSegment(stackName, [], failedOperations, "auto-rollback-clean", initialDeploy);
|
|
49473
|
-
this.logger.info(`The automatic rollback restored the pre-deploy state. The failed resource's pre-failure record was kept — if it was left partially applied,
|
|
50153
|
+
this.logger.info(`The automatic rollback restored the pre-deploy state. The failed resource's pre-failure record was kept — if it was left partially applied, revert it.\nRevert it with: ${pasteableCommand("cdkd rollback", [{
|
|
50154
|
+
value: stackName,
|
|
50155
|
+
hole: "stack"
|
|
50156
|
+
}, { literal: "--revert-failed" }]).command}`);
|
|
49474
50157
|
}
|
|
49475
50158
|
/** Build the {@link RollbackExecutorContext} from the engine's fields. */
|
|
49476
50159
|
rollbackExecutorContext(previousState) {
|
|
@@ -50199,7 +50882,21 @@ var DeployEngine = class DeployEngine {
|
|
|
50199
50882
|
const skipPrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
|
|
50200
50883
|
renderer.removeTask(logicalId);
|
|
50201
50884
|
this.logger.info(`${skipPrefix}${formatResourceLine("skipped", logicalId, resourceType, `skipped (${deleteSkipped})`)}`);
|
|
50202
|
-
this.logger.warn(deleteSkippedMessage(logicalId, currentResource.physicalId, deleteSkipped, "while removing it from the template") + `. Its cdkd state record was KEPT, so the next 'cdkd deploy' re-attempts the delete. Repair the record first (
|
|
50885
|
+
this.logger.warn(deleteSkippedMessage(logicalId, currentResource.physicalId, deleteSkipped, "while removing it from the template") + `. Its cdkd state record was KEPT, so the next 'cdkd deploy' re-attempts the delete. Repair the record first (for a nested stack it is the CHILD's own state, whose other resources may already be gone), or delete the resource by hand and drop the record.\nInspect it with: ${pasteableCommand("cdkd state show", [{
|
|
50886
|
+
value: stackName,
|
|
50887
|
+
hole: "stack"
|
|
50888
|
+
}, {
|
|
50889
|
+
flag: "--stack-region",
|
|
50890
|
+
value: this.stackRegion,
|
|
50891
|
+
hole: "region"
|
|
50892
|
+
}]).command}\nDrop the record with: ${pasteableCommand("cdkd state orphan", [{
|
|
50893
|
+
value: stackName,
|
|
50894
|
+
hole: "stack"
|
|
50895
|
+
}, {
|
|
50896
|
+
flag: "--stack-region",
|
|
50897
|
+
value: this.stackRegion,
|
|
50898
|
+
hole: "region"
|
|
50899
|
+
}]).command}`);
|
|
50203
50900
|
if (counts) counts.deleteSkipped++;
|
|
50204
50901
|
return { deleteSkipped };
|
|
50205
50902
|
}
|
|
@@ -50857,5 +51554,5 @@ var DeployEngine = class DeployEngine {
|
|
|
50857
51554
|
};
|
|
50858
51555
|
|
|
50859
51556
|
//#endregion
|
|
50860
|
-
export { yellow as $,
|
|
50861
|
-
//# sourceMappingURL=deploy-engine-
|
|
51557
|
+
export { yellow as $, markNonRetryable as $a, expectedOwnerParam as $i, repairMalformedResourcePropertiesForReadOnly as $n, AssetModeResolver as $r, requireConfigObject as $t, createPreDeleteFinalSnapshot as A, LocalInvokeBuildError as Aa, getDefaultStateBucketName as Ai, malformedOutputsRefusalMessage as An, buildForceUnlockCommand as Ar, carriesDynamicReference as At, isStatefulRecreateTargetSync as B, StackTerminationProtectionError as Ba, warnDeprecatedNoPrefixCliFlag as Bi, refuseMalformedOrphansForDestroy as Bn, importableOutputKeys as Br, isSettledInstanceState as Bt, namesAnyDeclaredParameter as C, CdkdError as Ca, runDockerStreaming as Ci, isReadableResourceEntry as Cn, recoverMaskedOutput as Cr, UNSPECIFIED_SKIP_REASON as Ct, PRE_DELETE_SNAPSHOT_TYPES as D, DeployCancelledError as Da, getDockerImageBySourceHash as Di, malformedOrphanRecordsWarning as Dn, S3StateBackend as Dr, disableInstanceApiTermination as Dt, ATOMIC_FINAL_SNAPSHOT_TYPES as E, DependencyError as Ea, AssetManifestLoader as Ei, malformedLocalOutputsWarning as En, LockManager as Er, isWaitAbandonedError as Et, extractDeploymentEventError as F, PartialFailureError as Fa, resolveSkipPrefix as Fi, malformedResourcesWarning as Fn, DEFAULT_STATE_PREFIX as Fr, isImpossibleEmptyStoredAttribute as Ft, toCfnInteger as G, normalizeAwsError as Ga, findLargeInlineResources as Gi, refuseMalformedResourceEntries as Gn, shouldRetainResource as Gr, coerceCfnBoolean as Gt, WARM_THROUGHPUT_MEMBERS as H, SynthesisError as Ha, CFN_TEMPLATE_BODY_LIMIT as Hi, refuseMalformedOutputs as Hn, isReadableBag as Hr, normalizeAwsTagsToCfn as Ht, makeSiblingClaimReader as I, ProvisioningError as Ia, resolveStateBucketWithDefault as Ii, malformedStateRefusalMessage as In, exportNamesCarriedFrom as Ir, isNamelessDynamicReferenceError as It, bold as J, shellQuote as Ja, injectiveKey as Ji, refuseMalformedResourcesForDestroy as Jn, WorkGraph as Jr, configIntegerRefusal as Jt, toFiniteNumber as K, withErrorHandling as Ka, uploadCfnTemplate as Ki, refuseMalformedResourceEntriesForOrphan as Kn, AssetPublisher as Kr, coerceCfnInteger as Kt, planOrphanAdoption as L, ResourceTimeoutError as La, resolveStateBucketWithDefaultAndSource as Li, refuseDivergentRecordRegionForDestroy as Ln, hasReadableExportSet as Lr, isUnboundTemplateParameter as Lt, refusesFinalSnapshot as M, LockError as Ma, resolveApp as Mi, malformedRenderedContainersWarning as Mn, forceQuitRecoveryClause as Mr, cfnRefValueFromPhysicalId as Mt, unsupportedFinalSnapshotError as N, MalformedProducerRecordRefusalError as Na, resolveAutoAssetStorage as Ni, malformedResourceEntriesWarning as Nn, formatLockExpiry as Nr, coerceParameterTypedValue as Nt, buildFinalSnapshotIdentifier as O, DynamicReferenceRegionAmbiguousError as Oa, Synthesizer as Oi, malformedOrphansRefusalMessage as On, rebuildClientForBucketRegion as Or, isTerminationProtectionPropagationError as Ot, makeCanonicalizePropertiesFn as P, NestedStackChildDirectDestroyError as Pa, resolveCaptureObservedState as Pi, malformedResourcePropertiesWarning as Pn, CUSTOM_RESOURCE_RESPONSE_PREFIX as Pr, getAccountInfo as Pt, red as Q, isTransientServerError as Qa, decodeListingKey as Qi, repairMalformedResourceEntriesForReadOnly as Qn, rewriteTemplateAssetReferences as Qr, requireConfigArray as Qt, MULTI_REGION_RECREATE_BLOCKED_TYPES as R, ResourceUpdateNotSupportedError as Ra, resolveUseCdkBootstrapAssets as Ri, refuseMalformedNestedChildOutputs as Rn, hasReasonlessBaselineRefusal as Rr, parameterTypeMayLoseSecretIdentity as Rt, computeParameterTaint as S, AssetError as Sa, runDockerForeground as Si, hasReadableResources as Sn, recordMaskOnlyValue as Sr, slowCcOperationTimeoutMs as St, resourcesNamingDeclaredParameter as T, CrossAccountSecretRefusalError as Ta, stripControlChars as Ti, malformedExportSourceWarning as Tn, scrubResourceRecord as Tr, deleteSkipReason as Tt, coerceWarmThroughput as U, formatError as Ua, CFN_TEMPLATE_URL_LIMIT as Ui, refuseMalformedOutputsForDestroy as Un, orphansAfterRollback as Ur, resolveExplicitPhysicalId as Ut, renderStatefulReason as V, StateError as Va, ssmResolvedValueType as Vi, refuseMalformedOrphansForOrphan as Vn, importableOutputs as Vr, WAFv2WebACLProvider as Vt, isWarmThroughputDecrease as W, isCdkdError as Wa, MIGRATE_TMP_PREFIX as Wi, refuseMalformedResourceAttributesForOrphan as Wn, orphansCarriedFrom as Wr, assertRegionMatch as Wt, gray as X, isRetryableTransientError as Xa, producerCoordinateKey as Xi, repairMalformedOrphansForReadOnly as Xn, createAssetRedirectResolver as Xr, readConfigString as Xt, cyan as Y, isMarkedNonRetryable as Ya, injectiveKeyPrefix as Yi, refuseMalformedState as Yn, buildAssetRedirectMap as Yr, configStringRefusal as Yt, green as Z, isThrottlingError as Za, producerRecordKey as Zi, repairMalformedOutputsForReadOnly as Zn, loadPublishableAssetManifest as Zr, replayWarn as Zt, bagHoldsSecretExpression as _, AwsClients as _a, dockerSpawnEnvWithSensitive as _i, UNREADABLE_ORPHANS_CONTAINER_ROW as _n, identityKeyFor as _r, endCommandInterruptScope as _t, DeploymentEventsStore as a, globMatches as aa, parseBootstrapMarker as ai, s3BucketDomainName as an, describeTypeWithThrottleRetry as ar, secretBearingStateKeyWarning as at, mergeNoChangeOutputs as b, runWithStackAwsClients as ba, partitionSensitiveEnv as bi, hasReadableOrphans as bn, maskSecretsInError as br, startInterruptWatch as bt, replayFailedOperations as c, hasOwnKey as ca, validateContainerRepoName as ci, s3BucketWebsiteUrl as cn, TemplateParser as cr, IAMRoleProvider as ct, updatePartialMessage as d, ownValue as da, buildDockerImage as di, applyCallerIdentityCredentials as dn, STATE_SOURCED_READBACK_RULES as dr, ProviderRegistry as dt, PARTITION_TABLE as ea, BOOTSTRAP_MARKER_PREFIX as ei, requireConfigString as en, markRedactedCause as eo, repairMalformedResourcesForReadOnly as er, collectDeclaredOutputNames as et, updatePartialReason as f, absoluteAssemblyPathEscape as fa, resolveDockerContextDirectory as fi, callerEnvCredentials as fn, TEMPLATE_SOURCED_RULES as fr, wouldReturnToSdkProvider as ft, computeImplicitDeleteEdges as g, resolveBucketRegion as ga, describeDockerFailure as gi, STATE_RESOURCES_MALFORMED as gn, errorCauseChain as gr, beginCommandInterruptScope as gt, IMPLICIT_DELETE_DEPENDENCIES as h, clearBucketRegionCache as ha, describeDockerExecFailure as hi, INTRINSIC_KEYS as hn, dynamicReferenceTokens as hr, maskerOrIdentity as ht, DeploymentEventsReader as i, failedStageNote as ia, isCrossRegionRedirect as ii, s3BucketArn as in, __exportAll as io, findSilentDropProperties as ir, secretBearing as it, isFinalSnapshotError as j, LocalStartServiceError as ja, getLegacyStateBucketName as ji, malformedOutputsWarning as jn, buildLockContentionMessage as jr, carriesFetchableDynamicReference as jt, ccRoutedFinalSnapshotError as k, IntrinsicResolutionRefusalError as ka, synthesisStatusMessage as ki, malformedOrphansWarning as kn, UNREPRODUCIBLE_LOCK_CLAUSE as kr, IntrinsicFunctionResolver as kt, replayRollback as l, hasPlainPrototype$1 as la, buildDenyExternalAccessPolicy as li, applyRoleArnIfSet as ln, STATE_SOURCED_BASELINE_RULES as lr, collectInlinePolicyNamesManagedBySiblings as lt, maskingRetryLogger as m, resolveAssemblyPath as ma, describeDockerCapturedOutput as mi, DiffCalculator as mn, createSecretMasker as mr, maskDeep as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, derivePartitionAndUrlSuffix as na, ensureAssetStorage as ni, producerRegionsFromState as nn, describeAwsFailure as no, safeStackName as nr, exportAliasCollisionScrubWarning as nt, planFailedOps as o, processStackMessages as oa, readBootstrapMarkerBody as oi, s3BucketDualStackDomainName as on, withRetry as or, secretSafeKeyDisplay as ot, withResourceDeadline as p, renderAssemblyPathEscape as pa, warnManifestExecutable as pi, envCredentialsAreAssumedRole as pn, carriesSecretMask as pr, createMaskedRetryLogger as pt, formatResourceLine as q, pasteableCommand as qa, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as qi, refuseMalformedResourcePropertiesForOrphan as qn, stringifyValue as qr, configBooleanRefusal as qt, DeployEngine as r, AssemblyReader as ra, getBootstrapMarkerKey as ri, withSharedDrainBudget as rn, safeStringify$1 as ro, findActionableSilentDrops as rr, isExportAliasCollision as rt, planRollback as s, defineOwnKey as sa, validateAssetBucketName as si, s3BucketRegionalDomainName as sn, DagBuilder as sr, getCurrentResourceSecrets as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, canonicalizeRegion as ta, assertAssetBucketRegion as ti, classifyReplaySecretRegion as tn, retryClassificationText as to, safeRegion as tr, collectPublishedOutputNames as tt, resolveReplacementOldType as u, nullPrototypeRecord as ua, assertCdkLocalDockerContextContained as ui, AWS_CREDENTIAL_ENV_KEYS as un, STATE_SOURCED_CROSS_GENERATION_RULES as ur, clearOnUpdateRemoval as ut, isSecretBearingReferenceString as v, getAwsClients as va, formatDockerLoginError as vi, UNREADABLE_RESOURCES_MAP_ROW as vn, isSingleDynamicReferenceToken as vr, interruptWatchListenerCount as vt, reachableDivergentParameters as w, ConfigError as wa, escapeRegExp$1 as wi, malformedExportNamesWarning as wn, redactSecretsForState as wr, deleteIndeterminateGuards as wt, bindingSkippedOutputs as x, setAwsClients as xa, redactDockerArgvValues as xi, hasReadableOutputs as xn, maskSecretsInText as xr, CloudControlProvider as xt, keptWholeReasonText as y, resetAwsClients as ya, getDockerCmd as yi, displayLogicalId as yn, markSameGenerationBag as yr, isInterruptedWaitError as yt, isStatefulRecreateTargetForReplace as z, StackHasActiveImportsError as za, stateBucketExistenceConfirmed as zi, refuseMalformedOrphans as zn, hasUnverifiableParameterRefusal as zr, refStateLookupFromResource as zt };
|
|
51558
|
+
//# sourceMappingURL=deploy-engine-BomFOZF4.js.map
|