@go-to-k/cdkd 0.284.37 → 0.284.39
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-BbWubpjP.js → asg-provider-Db6lnsaA.js} +2 -2
- package/dist/{asg-provider-BbWubpjP.js.map → asg-provider-Db6lnsaA.js.map} +1 -1
- package/dist/cli.js +372 -55
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-C62UGXid.js → deploy-engine-rKBFVifY.js} +60 -13
- package/dist/deploy-engine-rKBFVifY.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-C62UGXid.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as startInterruptWatch, $t as
|
|
2
|
+
import { $ as startInterruptWatch, $n as CdkdError, $t as WorkGraph, A as renderStatefulReason, An as resolveStateBucketWithDefault, At as redactSecretsForState, B as exportAliasCollisionScrubWarning, Bn as expectedOwnerParam, Bt as DiffCalculator, C as isFinalSnapshotError, Cn as synthesisStatusMessage, Cr as isMarkedNonRetryable, Ct as TEMPLATE_SOURCED_RULES, D as extractDeploymentEventError, Dn as resolveAutoAssetStorage, Dt as isSingleDynamicReferenceToken, E as makeCanonicalizePropertiesFn, En as resolveApp, Er as markNonRetryable, Et as errorCauseChain, F as green, Fn as CFN_TEMPLATE_BODY_LIMIT, Ft as s3BucketDomainName, G as collectInlinePolicyNamesManagedBySiblings, Gn as processStackMessages, Gt as TemplateParser, H as secretBearingStateKeyWarning, Hn as canonicalizeRegion, Ht as describeTypeWithThrottleRetry, I as red, In as CFN_TEMPLATE_URL_LIMIT, It as s3BucketDualStackDomainName, J as findActionableSilentDrops, Jn as AwsClients, Jt as S3StateBackend, K as clearOnUpdateRemoval, Kt as LockManager, L as yellow, Ln as MIGRATE_TMP_PREFIX, Lt as s3BucketRegionalDomainName, M as bold, Mn as resolveUseCdkBootstrapAssets, Mt as classifyReplaySecretRegion, N as cyan, Nn as stateBucketExistenceConfirmed, Nt as producerRegionsFromState, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as resolveCaptureObservedState, Ot as maskSecretsInError, P as gray, Pn as warnDeprecatedNoPrefixCliFlag, Pt as s3BucketArn, Q as isInterruptedWaitError, Qt as stringifyValue, R as collectDeclaredOutputNames, Rn as findLargeInlineResources, Rt as s3BucketWebsiteUrl, S as createPreDeleteFinalSnapshot, Sn as Synthesizer, Sr as withErrorHandling, St as STATE_SOURCED_READBACK_RULES, T as unsupportedFinalSnapshotError, Tn as getLegacyStateBucketName, Tr as isThrottlingError, Tt as dynamicReferenceTokens, U as stateKeySecretExposure, Un as derivePartitionAndUrlSuffix, Ut as withRetry, V as isExportAliasCollision, Vn as PARTITION_TABLE, Vt as INTRINSIC_KEYS, W as IAMRoleProvider, Wn as AssemblyReader, Wt as DagBuilder, X as beginCommandInterruptScope, Xn as resetAwsClients, Xt as shouldRetainResource, Y as findSilentDropProperties, Yn as getAwsClients, Yt as rebuildClientForBucketRegion, Z as endCommandInterruptScope, Zn as setAwsClients, Zt as AssetPublisher, _ as computeImplicitDeleteEdges, _n as getDockerCmd, _t as replayWarn, a as DeploymentEventsStore, an as stripControlChars, ar as LocalInvokeBuildError$1, at as carriesDynamicReference, b as buildFinalSnapshotIdentifier, bn as AssetManifestLoader, bt as requireConfigString, c as replayFailedOperations, cn as ensureAssetStorage, ct as refStateLookupFromResource, d as updatePartialReason, dn as readBootstrapMarkerBody, dr as PartialFailureError, dt as resolveExplicitPhysicalId, en as buildAssetRedirectMap, et as CloudControlProvider, f as UNSPECIFIED_SKIP_REASON, fn as validateAssetBucketName, fr as ProvisioningError, ft as assertRegionMatch, g as IMPLICIT_DELETE_DEPENDENCIES, gn as formatDockerLoginError, gr as StackTerminationProtectionError, gt as readConfigString, h as maskingRetryLogger, hn as buildDockerImage, hr as StackHasActiveImportsError, ht as configStringRefusal, i as DeploymentEventsReader, in as escapeRegExp, ir as DynamicReferenceRegionAmbiguousError, it as IntrinsicFunctionResolver, j as formatResourceLine, jn as resolveStateBucketWithDefaultAndSource, jt as scrubResourceRecord, k as isStatefulRecreateTargetSync, kn as resolveSkipPrefix, kt as maskSecretsInText, l as replayRollback, ln as getBootstrapMarkerKey, lr as MissingCdkCliError, lt as WAFv2WebACLProvider, m as withResourceDeadline, mn as buildDenyExternalAccessPolicy, mr as ResourceUpdateNotSupportedError, mt as configBooleanRefusal, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as loadPublishableAssetManifest, nt as disableInstanceApiTermination, o as planFailedOps, on as AssetModeResolver, or as LocalMigrateError, ot as cfnRefValueFromPhysicalId, p as deleteSkipReason, pn as validateContainerRepoName, pr as ResourceTimeoutError, pt as coerceCfnBoolean, q as ProviderRegistry, qn as resolveBucketRegion, qt as displaySafe, r as DeployEngine, rn as rewriteTemplateAssetReferences, rr as DeployCancelledError, rt as isTerminationProtectionPropagationError, s as planRollback, sn as BOOTSTRAP_MARKER_PREFIX, sr as LocalStartServiceError, st as getAccountInfo, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as createAssetRedirectResolver, tr as CrossAccountSecretRefusalError, tt as slowCcOperationTimeoutMs, u as updatePartialMessage, un as parseBootstrapMarker, ur as NestedStackChildDirectDestroyError, ut as normalizeAwsTagsToCfn, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as runDockerForeground, vt as requireConfigArray, w as refusesFinalSnapshot, wn as getDefaultStateBucketName, wr as isRetryableTransientError, wt as createSecretMasker, x as ccRoutedFinalSnapshotError, xn as getDockerImageBySourceHash, xr as normalizeAwsError, xt as STATE_SOURCED_CROSS_GENERATION_RULES, y as PRE_DELETE_SNAPSHOT_TYPES, yn as runDockerStreaming, yt as requireConfigObject, z as collectPublishedOutputNames, zn as uploadCfnTemplate, zt as applyRoleArnIfSet } from "./deploy-engine-rKBFVifY.js";
|
|
3
3
|
import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, f as getCurrentStackName, h as withStackName, i as runStackBuffered, m as withSkipPrefix, n as getLogger, o as PATTERN_B_NAME_OPTIONS, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-zRrlbaQt.js";
|
|
4
|
-
import { t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as ASGProvider } from "./asg-provider-Db6lnsaA.js";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
7
|
import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -77,6 +77,23 @@ import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
|
77
77
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
78
78
|
import { WebSocketServer } from "ws";
|
|
79
79
|
|
|
80
|
+
//#region src/state/state-prefix.ts
|
|
81
|
+
/**
|
|
82
|
+
* The default S3 key prefix for cdkd state.
|
|
83
|
+
*
|
|
84
|
+
* Homed in the STATE layer rather than in `src/cli/commands/state-file-keys.ts`
|
|
85
|
+
* (which re-exports it, so its four existing importers are unchanged) because
|
|
86
|
+
* `src/state/lock-contention-message.ts` needs it to decide whether a recovery
|
|
87
|
+
* hint should spell `--state-prefix` at all, and a `src/state/**` module
|
|
88
|
+
* importing from `src/cli/commands/**` inverts the layering — the CLI sits
|
|
89
|
+
* ABOVE the state layer in the 7-layer architecture, not below it.
|
|
90
|
+
*
|
|
91
|
+
* Note this is only the DEFAULT. Other commands accept `--state-prefix`, so
|
|
92
|
+
* whole-bucket listings deliberately do not scope to it.
|
|
93
|
+
*/
|
|
94
|
+
const DEFAULT_STATE_PREFIX = "cdkd";
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
80
97
|
//#region src/cli/commands/state-file-keys.ts
|
|
81
98
|
/**
|
|
82
99
|
* Shared helpers for enumerating and describing cdkd state-bucket keys.
|
|
@@ -94,20 +111,6 @@ import { WebSocketServer } from "ws";
|
|
|
94
111
|
*/
|
|
95
112
|
const STATE_FILE_SUFFIX = "/state.json";
|
|
96
113
|
/**
|
|
97
|
-
* The prefix `cdkd bootstrap` writes under, and the one `gc` /
|
|
98
|
-
* `bootstrap --destroy` configure their own state backend with.
|
|
99
|
-
*
|
|
100
|
-
* Shared here rather than spelled per command for the same reason this module
|
|
101
|
-
* exists at all: `bootstrap-destroy.ts` and `gc.ts` each held their own copy,
|
|
102
|
-
* and it is now also the `knownPrefix` argument to {@link describeStateKey},
|
|
103
|
-
* where a copy that drifted from the backend's actual prefix would silently
|
|
104
|
-
* send every key back to the shape heuristic instead of the exact depth rule.
|
|
105
|
-
*
|
|
106
|
-
* Note this is only the DEFAULT. Other commands accept `--state-prefix`, so
|
|
107
|
-
* the whole-bucket listings deliberately do not scope to it.
|
|
108
|
-
*/
|
|
109
|
-
const DEFAULT_STATE_PREFIX = "cdkd";
|
|
110
|
-
/**
|
|
111
114
|
* Segments left under a known prefix for the legacy `{prefix}/{stack}` layout.
|
|
112
115
|
*
|
|
113
116
|
* The twin of `S3StateBackend`'s `LEGACY_KEY_DEPTH`, which counts the
|
|
@@ -58162,6 +58165,104 @@ var ECRProvider = class {
|
|
|
58162
58165
|
}
|
|
58163
58166
|
};
|
|
58164
58167
|
|
|
58168
|
+
//#endregion
|
|
58169
|
+
//#region src/state/lock-contention-message.ts
|
|
58170
|
+
/**
|
|
58171
|
+
* The message every fail-fast lock-contention site raises.
|
|
58172
|
+
*
|
|
58173
|
+
* Issue #2161 made six commands throw on `acquireLock` returning `false`
|
|
58174
|
+
* instead of running under a foreign lock. Issue #2170 is the follow-up: the
|
|
58175
|
+
* message those sites raise asked the user to decide "is another process
|
|
58176
|
+
* active?" while printing none of the evidence that would answer it, and the
|
|
58177
|
+
* recovery command it suggested could resolve against the wrong AWS account.
|
|
58178
|
+
*
|
|
58179
|
+
* Both matter more than they look, because of WHICH locks reach this message.
|
|
58180
|
+
* `LockManager.acquireLock` reaps an EXPIRED foreign lock and retries, so a
|
|
58181
|
+
* `false` return means the lock is LIVE — in practice a running `cdkd deploy`.
|
|
58182
|
+
* The user who follows a bare `cdkd force-unlock <stack>` suggestion therefore
|
|
58183
|
+
* deletes a live owner's lock and reproduces issue #2161's harm by hand, which
|
|
58184
|
+
* is the outcome #2161 exists to prevent.
|
|
58185
|
+
*
|
|
58186
|
+
* Centralising the text here also settles the third finding: the nine sites
|
|
58187
|
+
* had drifted to three spellings (`for stack` / `for nested stack` /
|
|
58188
|
+
* `for nested-stack child`), so a user grepping CI logs for one of them found
|
|
58189
|
+
* two of three. `subject` now varies only the noun.
|
|
58190
|
+
*/
|
|
58191
|
+
/**
|
|
58192
|
+
* Stand-in for a value with nothing renderable left after sanitization. Named
|
|
58193
|
+
* rather than inlined so the message and the command-suppression branch cannot
|
|
58194
|
+
* disagree about what "unrenderable" looks like.
|
|
58195
|
+
*/
|
|
58196
|
+
const UNRENDERABLE = "<unrenderable>";
|
|
58197
|
+
/** Render `expiresIn` without implying more precision than a clock skew allows. */
|
|
58198
|
+
function formatRemaining(ms) {
|
|
58199
|
+
if (!Number.isFinite(ms)) return "at an unknown time";
|
|
58200
|
+
if (ms <= 0) return "already expired";
|
|
58201
|
+
const minutes = Math.round(ms / 6e4);
|
|
58202
|
+
if (minutes < 1) return "in under a minute";
|
|
58203
|
+
return `in ~${minutes}m`;
|
|
58204
|
+
}
|
|
58205
|
+
function shellQuote(value) {
|
|
58206
|
+
return /^[A-Za-z0-9._/@:+-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
|
|
58207
|
+
}
|
|
58208
|
+
/**
|
|
58209
|
+
* The `cdkd force-unlock ...` line, carrying every flag that decides which
|
|
58210
|
+
* lock object it resolves to.
|
|
58211
|
+
*/
|
|
58212
|
+
function buildForceUnlockCommand(stackName, region, recovery) {
|
|
58213
|
+
const safeStack = displaySafe(stackName, { asciiOnly: true });
|
|
58214
|
+
const stackIsExact = safeStack === stackName;
|
|
58215
|
+
const safeRegion = region === void 0 ? void 0 : displaySafe(region, { asciiOnly: true });
|
|
58216
|
+
if (!safeStack || safeRegion === "" || !stackIsExact || !(region === void 0 || safeRegion === region)) return "";
|
|
58217
|
+
const parts = [safeRegion === void 0 ? `cdkd force-unlock ${shellQuote(safeStack)}` : `cdkd force-unlock ${shellQuote(safeStack)} --stack-region ${shellQuote(safeRegion)}`];
|
|
58218
|
+
if (recovery?.profile) parts.push(`--profile ${shellQuote(recovery.profile)}`);
|
|
58219
|
+
if (recovery?.stateBucket) parts.push(`--state-bucket ${shellQuote(recovery.stateBucket)}`);
|
|
58220
|
+
if (recovery?.statePrefix && recovery.statePrefix !== "cdkd") parts.push(`--state-prefix ${shellQuote(recovery.statePrefix)}`);
|
|
58221
|
+
return parts.join(" ");
|
|
58222
|
+
}
|
|
58223
|
+
/**
|
|
58224
|
+
* The force-quit banner's recovery sentence.
|
|
58225
|
+
*
|
|
58226
|
+
* Exported so the two `destroy-runner.ts` banners do not each decide what to
|
|
58227
|
+
* say when {@link buildForceUnlockCommand} suppresses — a banner ending in a
|
|
58228
|
+
* bare `run: ` is the shape the review found, and two copies of the branch is
|
|
58229
|
+
* how the next one drifts. Returns a leading-space clause so the caller can
|
|
58230
|
+
* concatenate it unconditionally.
|
|
58231
|
+
*/
|
|
58232
|
+
function forceQuitRecoveryClause(stackName, region, recovery) {
|
|
58233
|
+
const command = buildForceUnlockCommand(stackName, region, recovery);
|
|
58234
|
+
return command ? ` If the next run reports a lock, run: ${command}` : " Inspect the lock object directly: the name or region recorded for this stack cannot be reproduced safely on a command line, so any command shown here would address a different lock.";
|
|
58235
|
+
}
|
|
58236
|
+
/**
|
|
58237
|
+
* Build the contention message, reading the holder's identity best-effort.
|
|
58238
|
+
*
|
|
58239
|
+
* The `getLockInfo` read is one GetObject and is deliberately NOT allowed to
|
|
58240
|
+
* fail the command: the caller is already on its way to throwing, and turning
|
|
58241
|
+
* a contention refusal into an S3 error would lose the reason. A failed or
|
|
58242
|
+
* absent read degrades to the evidence-free wording rather than to a crash.
|
|
58243
|
+
*/
|
|
58244
|
+
async function buildLockContentionMessage(args) {
|
|
58245
|
+
const { lockManager, stackName, region, subject = "stack", recovery, heldClause, suffix } = args;
|
|
58246
|
+
let held = heldClause ?? "another cdkd process holds it";
|
|
58247
|
+
let sawHolder = false;
|
|
58248
|
+
try {
|
|
58249
|
+
const info = await lockManager.getLockInfo(stackName, region);
|
|
58250
|
+
if (info) {
|
|
58251
|
+
const operation = info.operation ? `, operation: ${displaySafe(info.operation)}` : "";
|
|
58252
|
+
const expires = formatRemaining(info.expiresAt - Date.now());
|
|
58253
|
+
const owner = displaySafe(info.owner);
|
|
58254
|
+
const holder = owner ? `held by ${owner}${operation}` : `held by an unnamed holder`;
|
|
58255
|
+
held = `${heldClause ? `${heldClause} — ` : ""}${holder}, expires ${expires}`;
|
|
58256
|
+
if (owner) sawHolder = true;
|
|
58257
|
+
}
|
|
58258
|
+
} catch {}
|
|
58259
|
+
const advice = sawHolder ? `That process is still running — wait for it to finish. Only if you are certain it is gone` : `Wait for it to finish, or if you are certain no other process is active`;
|
|
58260
|
+
const recoveryCommand = buildForceUnlockCommand(stackName, region, recovery);
|
|
58261
|
+
const head = `Could not acquire lock for ${subject} '${displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>"}' (${displaySafe(region, { asciiOnly: true }) || "<unrenderable>"}) — ${held}.` + (suffix ? ` ${suffix}` : "");
|
|
58262
|
+
if (!recoveryCommand) return `${head} ${advice}. No recovery command can be shown: the name or region recorded for this lock contains characters that cannot be reproduced safely on a command line, so any command shown here would address a different lock — inspect the lock object directly.`;
|
|
58263
|
+
return `${head} ${advice}, run: ${recoveryCommand}`;
|
|
58264
|
+
}
|
|
58265
|
+
|
|
58165
58266
|
//#endregion
|
|
58166
58267
|
//#region src/cli/commands/destroy-runner.ts
|
|
58167
58268
|
/**
|
|
@@ -58298,9 +58399,64 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
58298
58399
|
const regionForState = state.region ?? ctx.baseRegion;
|
|
58299
58400
|
if (resourceCount === 0) {
|
|
58300
58401
|
logger.info(`Stack ${stackName} has no resources, cleaning up state...`);
|
|
58301
|
-
|
|
58302
|
-
|
|
58402
|
+
let emptyLockHeld = false;
|
|
58403
|
+
let emptyInterrupted = false;
|
|
58404
|
+
const emptySigintHandler = () => {
|
|
58405
|
+
if (emptyInterrupted) {
|
|
58406
|
+
if (emptyLockHeld) {
|
|
58407
|
+
ctx.lockManager.releaseLock(stackName, regionForState).catch(() => {});
|
|
58408
|
+
process.stderr.write(`\nForce-quit: stack lock may not be released.${forceQuitRecoveryClause(stackName, regionForState, {
|
|
58409
|
+
profile: ctx.profile,
|
|
58410
|
+
stateBucket: ctx.stateBucket,
|
|
58411
|
+
statePrefix: ctx.statePrefix
|
|
58412
|
+
})}\n`);
|
|
58413
|
+
}
|
|
58414
|
+
process.exit(130);
|
|
58415
|
+
}
|
|
58416
|
+
emptyInterrupted = true;
|
|
58417
|
+
try {
|
|
58418
|
+
process.stderr.write(`\nInterrupt received - finishing the state cleanup for ${stackName} (press Ctrl-C again to force-quit)\n`);
|
|
58419
|
+
} catch {}
|
|
58420
|
+
};
|
|
58421
|
+
process.setMaxListeners(Math.max(process.getMaxListeners(), 100));
|
|
58422
|
+
process.on("SIGINT", emptySigintHandler);
|
|
58423
|
+
let emptyAcquired;
|
|
58424
|
+
try {
|
|
58425
|
+
emptyAcquired = await ctx.lockManager.acquireLock(stackName, regionForState, void 0, "destroy");
|
|
58426
|
+
} catch (acquireErr) {
|
|
58427
|
+
process.removeListener("SIGINT", emptySigintHandler);
|
|
58428
|
+
throw acquireErr;
|
|
58429
|
+
}
|
|
58430
|
+
if (!emptyAcquired) {
|
|
58431
|
+
process.removeListener("SIGINT", emptySigintHandler);
|
|
58432
|
+
throw new Error(await buildLockContentionMessage({
|
|
58433
|
+
lockManager: ctx.lockManager,
|
|
58434
|
+
stackName,
|
|
58435
|
+
region: regionForState,
|
|
58436
|
+
recovery: {
|
|
58437
|
+
profile: ctx.profile,
|
|
58438
|
+
stateBucket: ctx.stateBucket,
|
|
58439
|
+
statePrefix: ctx.statePrefix
|
|
58440
|
+
}
|
|
58441
|
+
}));
|
|
58442
|
+
}
|
|
58443
|
+
emptyLockHeld = true;
|
|
58444
|
+
try {
|
|
58445
|
+
const recheck = await ctx.stateBackend.getState(stackName, regionForState);
|
|
58446
|
+
if (!(!recheck || Object.keys(recheck.state.resources).length === 0)) throw new Error(`Stack '${stackName}' (${regionForState}) was empty when this run started but now has ${Object.keys(recheck.state.resources).length} resource(s) — another cdkd process deployed into it. Re-run the destroy to act on the current state.`);
|
|
58447
|
+
await ctx.stateBackend.deleteState(stackName, regionForState);
|
|
58448
|
+
logger.info(`${green("✓")} State deleted`);
|
|
58449
|
+
} finally {
|
|
58450
|
+
try {
|
|
58451
|
+
await ctx.lockManager.releaseLock(stackName, regionForState);
|
|
58452
|
+
} catch (releaseErr) {
|
|
58453
|
+
logger.warn(`Failed to release lock after empty-state cleanup: ${releaseErr instanceof Error ? releaseErr.message : String(releaseErr)}`);
|
|
58454
|
+
} finally {
|
|
58455
|
+
process.removeListener("SIGINT", emptySigintHandler);
|
|
58456
|
+
}
|
|
58457
|
+
}
|
|
58303
58458
|
result.skippedEmpty = true;
|
|
58459
|
+
result.interrupted ||= emptyInterrupted;
|
|
58304
58460
|
return result;
|
|
58305
58461
|
}
|
|
58306
58462
|
const needsStrongRefCheck = !!(state.outputs && Object.keys(state.outputs).length > 0);
|
|
@@ -58335,19 +58491,40 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
58335
58491
|
const stackRegion = state.region;
|
|
58336
58492
|
let destroyProviderRegistry = ctx.providerRegistry;
|
|
58337
58493
|
let destroyAwsClients;
|
|
58494
|
+
let regionSwitched = false;
|
|
58495
|
+
const restoreBaseRegionAndClients = () => {
|
|
58496
|
+
if (!regionSwitched) return;
|
|
58497
|
+
regionSwitched = false;
|
|
58498
|
+
const switchedClients = destroyAwsClients;
|
|
58499
|
+
destroyAwsClients = void 0;
|
|
58500
|
+
process.env["AWS_REGION"] = ctx.baseRegion;
|
|
58501
|
+
process.env["AWS_DEFAULT_REGION"] = ctx.baseRegion;
|
|
58502
|
+
setAwsClients(ctx.baseAwsClients);
|
|
58503
|
+
try {
|
|
58504
|
+
switchedClients?.destroy();
|
|
58505
|
+
} catch (destroyError) {
|
|
58506
|
+
logger.debug(`Failed to destroy region-scoped AWS clients: ${destroyError instanceof Error ? destroyError.message : String(destroyError)}`);
|
|
58507
|
+
}
|
|
58508
|
+
};
|
|
58338
58509
|
if (stackRegion && stackRegion !== ctx.baseRegion) {
|
|
58339
58510
|
logger.info(`Stack region: ${stackRegion}`);
|
|
58340
58511
|
process.env["AWS_REGION"] = stackRegion;
|
|
58341
58512
|
process.env["AWS_DEFAULT_REGION"] = stackRegion;
|
|
58342
|
-
|
|
58343
|
-
|
|
58344
|
-
|
|
58345
|
-
|
|
58346
|
-
|
|
58347
|
-
|
|
58348
|
-
|
|
58349
|
-
|
|
58350
|
-
|
|
58513
|
+
regionSwitched = true;
|
|
58514
|
+
try {
|
|
58515
|
+
destroyAwsClients = new AwsClients({
|
|
58516
|
+
region: stackRegion,
|
|
58517
|
+
...ctx.profile && { profile: ctx.profile }
|
|
58518
|
+
});
|
|
58519
|
+
setAwsClients(destroyAwsClients);
|
|
58520
|
+
destroyProviderRegistry = new ProviderRegistry();
|
|
58521
|
+
registerAllProviders(destroyProviderRegistry);
|
|
58522
|
+
destroyProviderRegistry.setCustomResourceResponseBucket(ctx.stateBucket);
|
|
58523
|
+
if (ctx.allowUnsupportedTypes?.length) destroyProviderRegistry.allowUnsupportedTypes(ctx.allowUnsupportedTypes);
|
|
58524
|
+
} catch (setupError) {
|
|
58525
|
+
restoreBaseRegionAndClients();
|
|
58526
|
+
throw setupError;
|
|
58527
|
+
}
|
|
58351
58528
|
}
|
|
58352
58529
|
const renderer = getLiveRenderer();
|
|
58353
58530
|
let draining = false;
|
|
@@ -58356,7 +58533,11 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
58356
58533
|
if (draining) {
|
|
58357
58534
|
if (lockHeld) {
|
|
58358
58535
|
ctx.lockManager.releaseLock(stackName, regionForState).catch(() => {});
|
|
58359
|
-
process.stderr.write(`\nForce-quit: stack lock may not be released
|
|
58536
|
+
process.stderr.write(`\nForce-quit: stack lock may not be released.${forceQuitRecoveryClause(stackName, regionForState, {
|
|
58537
|
+
profile: ctx.profile,
|
|
58538
|
+
stateBucket: ctx.stateBucket,
|
|
58539
|
+
statePrefix: ctx.statePrefix
|
|
58540
|
+
})}\n`);
|
|
58360
58541
|
}
|
|
58361
58542
|
process.exit(130);
|
|
58362
58543
|
}
|
|
@@ -58367,10 +58548,20 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
58367
58548
|
};
|
|
58368
58549
|
process.setMaxListeners(Math.max(process.getMaxListeners(), 100));
|
|
58369
58550
|
process.on("SIGINT", sigintHandler);
|
|
58370
|
-
logger.info(`\nAcquiring lock for stack ${stackName}...`);
|
|
58371
58551
|
try {
|
|
58372
|
-
|
|
58552
|
+
logger.info(`\nAcquiring lock for stack ${stackName}...`);
|
|
58553
|
+
if (!await ctx.lockManager.acquireLock(stackName, regionForState, void 0, "destroy")) throw new Error(await buildLockContentionMessage({
|
|
58554
|
+
lockManager: ctx.lockManager,
|
|
58555
|
+
stackName,
|
|
58556
|
+
region: regionForState,
|
|
58557
|
+
recovery: {
|
|
58558
|
+
profile: ctx.profile,
|
|
58559
|
+
stateBucket: ctx.stateBucket,
|
|
58560
|
+
statePrefix: ctx.statePrefix
|
|
58561
|
+
}
|
|
58562
|
+
}));
|
|
58373
58563
|
} catch (error) {
|
|
58564
|
+
restoreBaseRegionAndClients();
|
|
58374
58565
|
process.removeListener("SIGINT", sigintHandler);
|
|
58375
58566
|
throw error;
|
|
58376
58567
|
}
|
|
@@ -58382,6 +58573,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
58382
58573
|
} catch (releaseErr) {
|
|
58383
58574
|
logger.warn(`Failed to release lock after strong-ref refusal/failure: ${releaseErr instanceof Error ? releaseErr.message : String(releaseErr)}`);
|
|
58384
58575
|
}
|
|
58576
|
+
restoreBaseRegionAndClients();
|
|
58385
58577
|
process.removeListener("SIGINT", sigintHandler);
|
|
58386
58578
|
};
|
|
58387
58579
|
let consumers;
|
|
@@ -58422,8 +58614,8 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
58422
58614
|
}
|
|
58423
58615
|
});
|
|
58424
58616
|
};
|
|
58425
|
-
renderer.start();
|
|
58426
58617
|
try {
|
|
58618
|
+
renderer.start();
|
|
58427
58619
|
logger.info("Building dependency graph...");
|
|
58428
58620
|
const template = {
|
|
58429
58621
|
AWSTemplateFormatVersion: "2010-09-09",
|
|
@@ -58693,14 +58885,9 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
58693
58885
|
await ctx.lockManager.releaseLock(stackName, regionForState);
|
|
58694
58886
|
} finally {
|
|
58695
58887
|
process.removeListener("SIGINT", sigintHandler);
|
|
58888
|
+
restoreBaseRegionAndClients();
|
|
58696
58889
|
}
|
|
58697
58890
|
result.interrupted ||= draining;
|
|
58698
|
-
if (destroyAwsClients) {
|
|
58699
|
-
destroyAwsClients.destroy();
|
|
58700
|
-
process.env["AWS_REGION"] = ctx.baseRegion;
|
|
58701
|
-
process.env["AWS_DEFAULT_REGION"] = ctx.baseRegion;
|
|
58702
|
-
setAwsClients(ctx.baseAwsClients);
|
|
58703
|
-
}
|
|
58704
58891
|
}
|
|
58705
58892
|
return result;
|
|
58706
58893
|
}
|
|
@@ -58966,6 +59153,7 @@ var NestedStackProvider = class {
|
|
|
58966
59153
|
skipConfirmation: true,
|
|
58967
59154
|
...ctx.exportIndexStore && { exportIndexStore: ctx.exportIndexStore },
|
|
58968
59155
|
...ctx.destroyOptions?.profile && { profile: ctx.destroyOptions.profile },
|
|
59156
|
+
...ctx.destroyOptions?.statePrefix !== void 0 && { statePrefix: ctx.destroyOptions.statePrefix },
|
|
58969
59157
|
...deleteContext?.removeProtection === true && { removeProtection: true },
|
|
58970
59158
|
...(ctx.destroyOptions?.skipFinalSnapshot === true || ctx.options?.skipFinalSnapshot === true) && { skipFinalSnapshot: true },
|
|
58971
59159
|
...ctx.destroyOptions?.resourceWarnAfterMs !== void 0 && { resourceWarnAfterMs: ctx.destroyOptions.resourceWarnAfterMs },
|
|
@@ -59887,7 +60075,7 @@ async function deployCommand(stacks, options) {
|
|
|
59887
60075
|
let deployInterrupted = false;
|
|
59888
60076
|
const topLevelSigintHandler = () => {
|
|
59889
60077
|
if (deployInterrupted) {
|
|
59890
|
-
process.stderr.write("\nForce-quit: stack locks may not be released. If the next run reports a lock, run: cdkd force-unlock <stackName>\n");
|
|
60078
|
+
process.stderr.write("\nForce-quit: stack locks may not be released. If the next run reports a lock, run this for EACH stack it names (the region-qualified form — see the message that run prints): cdkd force-unlock <stackName> --stack-region <region>\n");
|
|
59891
60079
|
process.exit(130);
|
|
59892
60080
|
}
|
|
59893
60081
|
process.stderr.write("\nInterrupted — waiting for in-progress operations to complete...\n");
|
|
@@ -60154,6 +60342,10 @@ async function deployCommand(stacks, options) {
|
|
|
60154
60342
|
dagBuilder,
|
|
60155
60343
|
diffCalculator,
|
|
60156
60344
|
options: deployEngineOptions,
|
|
60345
|
+
destroyOptions: {
|
|
60346
|
+
statePrefix: options.statePrefix,
|
|
60347
|
+
...options.profile && { profile: options.profile }
|
|
60348
|
+
},
|
|
60157
60349
|
...assetRedirect && { assetRedirect }
|
|
60158
60350
|
}, () => stackDeployEngine.deploy(stackInfo.stackName, stackInfo.template));
|
|
60159
60351
|
logger.info(`\n${bold("Deployment Summary:")}`);
|
|
@@ -66124,6 +66316,11 @@ function setAtPath(target, path, value) {
|
|
|
66124
66316
|
*/
|
|
66125
66317
|
async function runAccept(reports, stateBackend, stateConfig, awsClients, options) {
|
|
66126
66318
|
const logger = getLogger();
|
|
66319
|
+
const lockRecovery = {
|
|
66320
|
+
profile: options.profile,
|
|
66321
|
+
stateBucket: stateConfig.bucket,
|
|
66322
|
+
statePrefix: stateConfig.prefix
|
|
66323
|
+
};
|
|
66127
66324
|
printAcceptPlan(reports);
|
|
66128
66325
|
if (options.dryRun) {
|
|
66129
66326
|
logger.info("--dry-run: state will NOT be written. Re-run without --dry-run to apply.");
|
|
@@ -66146,7 +66343,12 @@ async function runAccept(reports, stateBackend, stateConfig, awsClients, options
|
|
|
66146
66343
|
skipped: () => []
|
|
66147
66344
|
}));
|
|
66148
66345
|
if (driftedOutcomes.length === 0) continue;
|
|
66149
|
-
await lockManager.acquireLock(report.stackName, report.region, owner, "drift-accept")
|
|
66346
|
+
if (!await lockManager.acquireLock(report.stackName, report.region, owner, "drift-accept")) throw new Error(await buildLockContentionMessage({
|
|
66347
|
+
lockManager,
|
|
66348
|
+
stackName: report.stackName,
|
|
66349
|
+
region: report.region,
|
|
66350
|
+
recovery: lockRecovery
|
|
66351
|
+
}));
|
|
66150
66352
|
try {
|
|
66151
66353
|
const resources = { ...report.state.resources };
|
|
66152
66354
|
for (const outcome of driftedOutcomes) {
|
|
@@ -66743,6 +66945,11 @@ function deepEqualUnordered(a, b) {
|
|
|
66743
66945
|
*/
|
|
66744
66946
|
async function runRevert(reports, providerRegistry, stateBackend, stateConfig, awsClients, options) {
|
|
66745
66947
|
const logger = getLogger();
|
|
66948
|
+
const lockRecovery = {
|
|
66949
|
+
profile: options.profile,
|
|
66950
|
+
stateBucket: stateConfig.bucket,
|
|
66951
|
+
statePrefix: stateConfig.prefix
|
|
66952
|
+
};
|
|
66746
66953
|
printRevertPlan(reports);
|
|
66747
66954
|
if (options.dryRun) {
|
|
66748
66955
|
logger.info("--dry-run: AWS will NOT be modified. Re-run without --dry-run to apply.");
|
|
@@ -66770,11 +66977,16 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
|
|
|
66770
66977
|
skipped: () => []
|
|
66771
66978
|
}));
|
|
66772
66979
|
if (driftedOutcomes.length === 0) continue;
|
|
66773
|
-
await lockManager.acquireLock(report.stackName, report.region, owner, "drift-revert")
|
|
66980
|
+
if (!await lockManager.acquireLock(report.stackName, report.region, owner, "drift-revert")) throw new Error(await buildLockContentionMessage({
|
|
66981
|
+
lockManager,
|
|
66982
|
+
stackName: report.stackName,
|
|
66983
|
+
region: report.region,
|
|
66984
|
+
recovery: lockRecovery
|
|
66985
|
+
}));
|
|
66774
66986
|
const narrowedByLogicalId = /* @__PURE__ */ new Map();
|
|
66775
|
-
const revertSecretResolvers = new DriftSecretResolvers(report.region);
|
|
66776
|
-
const revertProducerRegions = producerRegionsFromState(report.state);
|
|
66777
66987
|
try {
|
|
66988
|
+
const revertSecretResolvers = new DriftSecretResolvers(report.region);
|
|
66989
|
+
const revertProducerRegions = producerRegionsFromState(report.state);
|
|
66778
66990
|
await runWithConcurrency(driftedOutcomes.map((outcome) => async () => {
|
|
66779
66991
|
const stateResource = report.state.resources[outcome.logicalId];
|
|
66780
66992
|
if (!stateResource) {
|
|
@@ -67397,6 +67609,7 @@ async function destroyCommand(stackArgs, options) {
|
|
|
67397
67609
|
exportIndexStore,
|
|
67398
67610
|
destroyOptions: {
|
|
67399
67611
|
...options.profile && { profile: options.profile },
|
|
67612
|
+
statePrefix: options.statePrefix,
|
|
67400
67613
|
...options.removeProtection === true && { removeProtection: true },
|
|
67401
67614
|
...options.skipFinalSnapshot === true && { skipFinalSnapshot: true },
|
|
67402
67615
|
...options.resourceWarnAfter?.globalMs !== void 0 && { resourceWarnAfterMs: options.resourceWarnAfter.globalMs },
|
|
@@ -67412,6 +67625,7 @@ async function destroyCommand(stackArgs, options) {
|
|
|
67412
67625
|
baseRegion: region,
|
|
67413
67626
|
...options.profile && { profile: options.profile },
|
|
67414
67627
|
stateBucket,
|
|
67628
|
+
statePrefix: options.statePrefix,
|
|
67415
67629
|
skipConfirmation: options.yes || options.force,
|
|
67416
67630
|
removeProtection: options.removeProtection === true,
|
|
67417
67631
|
skipFinalSnapshot: options.skipFinalSnapshot === true,
|
|
@@ -68985,6 +69199,11 @@ async function exportCommand(stackArg, options) {
|
|
|
68985
69199
|
bucket: stateBucket,
|
|
68986
69200
|
prefix: options.statePrefix
|
|
68987
69201
|
};
|
|
69202
|
+
const lockRecovery = {
|
|
69203
|
+
profile: options.profile,
|
|
69204
|
+
stateBucket,
|
|
69205
|
+
statePrefix: options.statePrefix
|
|
69206
|
+
};
|
|
68988
69207
|
const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
|
|
68989
69208
|
...options.region && { region: options.region },
|
|
68990
69209
|
...options.profile && { profile: options.profile }
|
|
@@ -69060,7 +69279,12 @@ async function exportCommand(stackArg, options) {
|
|
|
69060
69279
|
}
|
|
69061
69280
|
const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
|
|
69062
69281
|
if (!options.dryRun) {
|
|
69063
|
-
if (!await lockManager.acquireLock(resolvedStackName, targetRegion, owner, "export")) throw new Error(
|
|
69282
|
+
if (!await lockManager.acquireLock(resolvedStackName, targetRegion, owner, "export")) throw new Error(await buildLockContentionMessage({
|
|
69283
|
+
lockManager,
|
|
69284
|
+
stackName: resolvedStackName,
|
|
69285
|
+
region: targetRegion,
|
|
69286
|
+
recovery: lockRecovery
|
|
69287
|
+
}));
|
|
69064
69288
|
}
|
|
69065
69289
|
try {
|
|
69066
69290
|
if (nestedStackRows.length > 0) {
|
|
@@ -69080,6 +69304,7 @@ async function exportCommand(stackArg, options) {
|
|
|
69080
69304
|
if (missingNested.length > 0) throw new Error(`Template requires parameter(s) without defaults: ${missingNested.join(", ")}. Pass each one as --parameter Key=Value (or set a Default in the CDK code).`);
|
|
69081
69305
|
const childOverrides = parseCfnChildStackNameOverrides(options.cfnChildStackName);
|
|
69082
69306
|
if ((await runPerStackImportLoop({
|
|
69307
|
+
lockRecovery,
|
|
69083
69308
|
rootStackName: resolvedStackName,
|
|
69084
69309
|
rootRegion: targetRegion,
|
|
69085
69310
|
rootStackInfoNestedTemplates: rootNestedTemplatePaths,
|
|
@@ -70976,7 +71201,15 @@ async function runPerStackImportLoop(args) {
|
|
|
70976
71201
|
try {
|
|
70977
71202
|
await deps.lockManager.acquireLockWithRetry(node.stackName, node.region, deps.lockOwner, "export", CHILD_LOCK_RETRY_MAX_ATTEMPTS, CHILD_LOCK_RETRY_DELAY_MS);
|
|
70978
71203
|
} catch (err) {
|
|
70979
|
-
throw new Error(
|
|
71204
|
+
throw new Error(await buildLockContentionMessage({
|
|
71205
|
+
lockManager: deps.lockManager,
|
|
71206
|
+
stackName: node.stackName,
|
|
71207
|
+
region: node.region,
|
|
71208
|
+
subject: "nested-stack child",
|
|
71209
|
+
heldClause: "another cdkd process held it through the retry window",
|
|
71210
|
+
recovery: args.lockRecovery,
|
|
71211
|
+
suffix: "No CloudFormation changeset has been submitted; cdkd state is unchanged."
|
|
71212
|
+
}), { cause: err instanceof Error ? err : void 0 });
|
|
70980
71213
|
}
|
|
70981
71214
|
acquiredChildLocks.push({
|
|
70982
71215
|
stackName: node.stackName,
|
|
@@ -71912,8 +72145,12 @@ async function stateOrphanCommand(stackArgs, options) {
|
|
|
71912
72145
|
}
|
|
71913
72146
|
if (!options.force) {
|
|
71914
72147
|
for (const target of targets) if (await setup.lockManager.isLocked(stackName, target.region)) {
|
|
71915
|
-
const where = target.region
|
|
71916
|
-
throw new Error(`Stack '${stackName}' (${where}) is locked. Run
|
|
72148
|
+
const where = target.region ? displaySafe(target.region, { asciiOnly: true }) || "<unrenderable>" : "(legacy)";
|
|
72149
|
+
throw new Error(`Stack '${displaySafe(stackName, { asciiOnly: true })}' (${where}) is locked. Run: ${buildForceUnlockCommand(stackName, target.region, {
|
|
72150
|
+
profile: options.profile,
|
|
72151
|
+
stateBucket: setup.bucket,
|
|
72152
|
+
statePrefix: options.statePrefix
|
|
72153
|
+
}) || "a command cdkd cannot show safely: the recorded name or region would not survive a command line intact, so inspect the lock object directly"} first, or pass --force to remove anyway.`);
|
|
71917
72154
|
}
|
|
71918
72155
|
}
|
|
71919
72156
|
if (!options.yes && !options.force) {
|
|
@@ -71933,9 +72170,13 @@ async function stateOrphanCommand(stackArgs, options) {
|
|
|
71933
72170
|
}
|
|
71934
72171
|
for (const target of targets) {
|
|
71935
72172
|
if (target.region) {
|
|
72173
|
+
await warnOnLiveForeignLock(setup.lockManager, stackName, target.region, logger);
|
|
71936
72174
|
await setup.stateBackend.deleteState(stackName, target.region);
|
|
71937
72175
|
await setup.lockManager.forceReleaseLock(stackName, target.region);
|
|
71938
|
-
} else
|
|
72176
|
+
} else {
|
|
72177
|
+
await warnOnLiveForeignLock(setup.lockManager, stackName, void 0, logger);
|
|
72178
|
+
await setup.lockManager.forceReleaseLock(stackName, void 0);
|
|
72179
|
+
}
|
|
71939
72180
|
logger.info(`✓ Removed state for stack: ${formatStackRef(target)}`);
|
|
71940
72181
|
}
|
|
71941
72182
|
}
|
|
@@ -72067,6 +72308,7 @@ async function stateDestroyCommand(stackArgs, options) {
|
|
|
72067
72308
|
exportIndexStore: setup.exportIndexStore,
|
|
72068
72309
|
destroyOptions: {
|
|
72069
72310
|
...options.profile && { profile: options.profile },
|
|
72311
|
+
statePrefix: options.statePrefix,
|
|
72070
72312
|
...options.removeProtection === true && { removeProtection: true },
|
|
72071
72313
|
...options.skipFinalSnapshot === true && { skipFinalSnapshot: true },
|
|
72072
72314
|
...options.resourceWarnAfter?.globalMs !== void 0 && { resourceWarnAfterMs: options.resourceWarnAfter.globalMs },
|
|
@@ -72082,6 +72324,7 @@ async function stateDestroyCommand(stackArgs, options) {
|
|
|
72082
72324
|
baseRegion: setup.region,
|
|
72083
72325
|
...options.profile && { profile: options.profile },
|
|
72084
72326
|
stateBucket: setup.bucket,
|
|
72327
|
+
statePrefix: options.statePrefix,
|
|
72085
72328
|
skipConfirmation: options.yes || options.all === true,
|
|
72086
72329
|
removeProtection: options.removeProtection === true,
|
|
72087
72330
|
skipFinalSnapshot: options.skipFinalSnapshot === true,
|
|
@@ -72443,7 +72686,12 @@ async function stateRefreshObservedCommand(stackArgs, options) {
|
|
|
72443
72686
|
if (!target.region) throw new Error(`Stack '${target.stackName}' has only a legacy state record without a region. Run 'cdkd deploy ${target.stackName}' (or any cdkd write) first to migrate it to the region-scoped layout, then re-run refresh-observed.`);
|
|
72444
72687
|
const counts = await refreshObservedForStack(target.stackName, target.region, setup.stateBackend, setup.lockManager, providerRegistry, {
|
|
72445
72688
|
dryRun: options.dryRun ?? false,
|
|
72446
|
-
logger
|
|
72689
|
+
logger,
|
|
72690
|
+
lockRecovery: {
|
|
72691
|
+
profile: options.profile,
|
|
72692
|
+
stateBucket: setup.bucket,
|
|
72693
|
+
statePrefix: options.statePrefix
|
|
72694
|
+
}
|
|
72447
72695
|
});
|
|
72448
72696
|
totalRefreshed += counts.refreshed;
|
|
72449
72697
|
totalUnsupported += counts.unsupported;
|
|
@@ -72470,6 +72718,34 @@ async function stateRefreshObservedCommand(stackArgs, options) {
|
|
|
72470
72718
|
*/
|
|
72471
72719
|
const NO_RECORDED_SECRETS = /* @__PURE__ */ new Map();
|
|
72472
72720
|
/**
|
|
72721
|
+
* Warn before `cdkd state orphan` force-releases a lock that is still LIVE.
|
|
72722
|
+
*
|
|
72723
|
+
* `forceReleaseLock` is unconditional by design — a stuck lock must never make
|
|
72724
|
+
* a state record unremovable — but it deletes an in-flight `cdkd deploy`'s lock
|
|
72725
|
+
* just as readily as a stale one, and it did so silently (issue #2171). The
|
|
72726
|
+
* write it enables has already happened by the time anyone notices, so the
|
|
72727
|
+
* only useful thing is to name the owner at the moment of the release.
|
|
72728
|
+
*
|
|
72729
|
+
* Best-effort in both directions: an expired lock is not worth a line, and a
|
|
72730
|
+
* failed read must not block the removal the user asked for.
|
|
72731
|
+
*
|
|
72732
|
+
* (Issue #2171's own text called this command `cdkd state rm`; no such command
|
|
72733
|
+
* exists -- this file registers it as `orphan`.)
|
|
72734
|
+
*/
|
|
72735
|
+
async function warnOnLiveForeignLock(lockManager, stackName, region, logger) {
|
|
72736
|
+
try {
|
|
72737
|
+
const info = await lockManager.getLockInfo(stackName, region);
|
|
72738
|
+
if (!info) return;
|
|
72739
|
+
const expiryKnown = Number.isFinite(info.expiresAt);
|
|
72740
|
+
if (expiryKnown && info.expiresAt <= Date.now()) return;
|
|
72741
|
+
const safeStack = displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>";
|
|
72742
|
+
const where = region ? `${safeStack} (${displaySafe(region, { asciiOnly: true }) || "<unrenderable>"})` : `${safeStack} (legacy lock key)`;
|
|
72743
|
+
const owner = info.owner;
|
|
72744
|
+
const operation = info.operation ? `, operation: ${info.operation}` : "";
|
|
72745
|
+
logger.warn(`Force-releasing a LIVE lock on ${where} ${owner ? `held by ${owner}${operation}` : "held by an unnamed holder"}. ` + (owner && expiryKnown ? `That process is still running and will keep writing; its next state write ` : expiryKnown ? `The lock has not expired, so a process may still be writing; its next state write ` : `The lock records no readable expiry, so a process may still be writing; its next state write `) + `may recreate the record being removed here.`);
|
|
72746
|
+
} catch {}
|
|
72747
|
+
}
|
|
72748
|
+
/**
|
|
72473
72749
|
* Refresh the `observedProperties` of every resource in one stack
|
|
72474
72750
|
* record. Returns counts so the caller can aggregate across `--all`.
|
|
72475
72751
|
*
|
|
@@ -72479,7 +72755,7 @@ const NO_RECORDED_SECRETS = /* @__PURE__ */ new Map();
|
|
|
72479
72755
|
* for the underlying SDK reads.
|
|
72480
72756
|
*/
|
|
72481
72757
|
async function refreshObservedForStack(stackName, region, stateBackend, lockManager, providerRegistry, opts) {
|
|
72482
|
-
const { logger } = opts;
|
|
72758
|
+
const { logger, lockRecovery } = opts;
|
|
72483
72759
|
const result = await stateBackend.getState(stackName, region);
|
|
72484
72760
|
if (!result) throw new Error(`No state found for stack '${stackName}' (${region}). Run 'cdkd state list' to see available stacks.`);
|
|
72485
72761
|
const { state, etag, migrationPending } = result;
|
|
@@ -72517,7 +72793,12 @@ async function refreshObservedForStack(stackName, region, stateBackend, lockMana
|
|
|
72517
72793
|
};
|
|
72518
72794
|
}
|
|
72519
72795
|
const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
|
|
72520
|
-
await lockManager.acquireLock(stackName, region, owner, "state-refresh-observed")
|
|
72796
|
+
if (!await lockManager.acquireLock(stackName, region, owner, "state-refresh-observed")) throw new Error(await buildLockContentionMessage({
|
|
72797
|
+
lockManager,
|
|
72798
|
+
stackName,
|
|
72799
|
+
region,
|
|
72800
|
+
recovery: lockRecovery
|
|
72801
|
+
}));
|
|
72521
72802
|
try {
|
|
72522
72803
|
let refreshed = 0;
|
|
72523
72804
|
let unsupported = 0;
|
|
@@ -72860,7 +73141,10 @@ async function rollbackCommand(stackArg, options) {
|
|
|
72860
73141
|
awsClients: setup.awsClients,
|
|
72861
73142
|
stateBucket: setup.bucket,
|
|
72862
73143
|
exportIndexStore: setup.exportIndexStore,
|
|
72863
|
-
destroyOptions: {
|
|
73144
|
+
destroyOptions: {
|
|
73145
|
+
...options.profile && { profile: options.profile },
|
|
73146
|
+
statePrefix: options.statePrefix
|
|
73147
|
+
}
|
|
72864
73148
|
}, () => withStackName(stackName, async () => {
|
|
72865
73149
|
let failedOpFailures = 0;
|
|
72866
73150
|
let failedOpWarnings = 0;
|
|
@@ -74896,7 +75180,18 @@ async function orphanCommand(pathArgs, options) {
|
|
|
74896
75180
|
const targetRegion = await pickStackRegion(stateBackend, stackInfo.stackName, stackInfo.region, options.stackRegion);
|
|
74897
75181
|
logger.info(`Target: ${stackInfo.stackName} (${targetRegion}); orphaning ${orphanLogicalIds.length} resource(s): ${orphanLogicalIds.join(", ")}`);
|
|
74898
75182
|
const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
|
|
74899
|
-
if (!options.dryRun)
|
|
75183
|
+
if (!options.dryRun) {
|
|
75184
|
+
if (!await lockManager.acquireLock(stackInfo.stackName, targetRegion, owner, "orphan")) throw new Error(await buildLockContentionMessage({
|
|
75185
|
+
lockManager,
|
|
75186
|
+
stackName: stackInfo.stackName,
|
|
75187
|
+
region: targetRegion,
|
|
75188
|
+
recovery: {
|
|
75189
|
+
profile: options.profile,
|
|
75190
|
+
stateBucket,
|
|
75191
|
+
statePrefix: options.statePrefix
|
|
75192
|
+
}
|
|
75193
|
+
}));
|
|
75194
|
+
}
|
|
74900
75195
|
try {
|
|
74901
75196
|
const stateData = await stateBackend.getState(stackInfo.stackName, targetRegion);
|
|
74902
75197
|
if (!stateData) throw new Error(`No state found for stack '${stackInfo.stackName}' (${targetRegion}). Nothing to orphan. (Did the stack get deployed?)`);
|
|
@@ -75381,6 +75676,11 @@ async function importCommand(stackArg, options) {
|
|
|
75381
75676
|
bucket: stateBucket,
|
|
75382
75677
|
prefix: options.statePrefix
|
|
75383
75678
|
};
|
|
75679
|
+
const lockRecovery = {
|
|
75680
|
+
profile: options.profile,
|
|
75681
|
+
stateBucket,
|
|
75682
|
+
statePrefix: options.statePrefix
|
|
75683
|
+
};
|
|
75384
75684
|
const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
|
|
75385
75685
|
...options.region && { region: options.region },
|
|
75386
75686
|
...options.profile && { profile: options.profile }
|
|
@@ -75481,7 +75781,16 @@ async function importCommand(stackArg, options) {
|
|
|
75481
75781
|
}
|
|
75482
75782
|
for (const overrideId of overrides.keys()) if (!templateLogicalIds.has(overrideId)) throw new Error(`--resource / --resource-mapping references logical ID '${overrideId}' which is not in the synthesized template for stack '${stackInfo.stackName}'. Available IDs: ${[...templateLogicalIds].join(", ")}`);
|
|
75483
75783
|
const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
|
|
75484
|
-
await lockManager.acquireLock(stackInfo.stackName, targetRegion, owner, "import")
|
|
75784
|
+
if (!await lockManager.acquireLock(stackInfo.stackName, targetRegion, owner, "import")) throw new Error(await buildLockContentionMessage({
|
|
75785
|
+
lockManager,
|
|
75786
|
+
stackName: stackInfo.stackName,
|
|
75787
|
+
region: targetRegion,
|
|
75788
|
+
recovery: {
|
|
75789
|
+
profile: options.profile,
|
|
75790
|
+
stateBucket,
|
|
75791
|
+
statePrefix: options.statePrefix
|
|
75792
|
+
}
|
|
75793
|
+
}));
|
|
75485
75794
|
try {
|
|
75486
75795
|
const rows = [];
|
|
75487
75796
|
for (const { logicalId, resource } of resources) {
|
|
@@ -75545,6 +75854,7 @@ async function importCommand(stackArg, options) {
|
|
|
75545
75854
|
logger.info(`✓ State written: ${stackInfo.stackName} (${targetRegion})`);
|
|
75546
75855
|
logger.info(` ${importedRows.length} resource(s) imported. Run 'cdkd diff' to see how the imported state lines up with the template.`);
|
|
75547
75856
|
if (migrationCfnStackName && migrationTree && accountIdForNestedSynth) await importNestedStackChildrenRecursive({
|
|
75857
|
+
lockRecovery,
|
|
75548
75858
|
parentStackName: stackInfo.stackName,
|
|
75549
75859
|
parentRegion: targetRegion,
|
|
75550
75860
|
parentNestedTemplates: stackInfo.nestedTemplates ?? {},
|
|
@@ -76210,7 +76520,13 @@ async function importNestedStackChildrenRecursive(args) {
|
|
|
76210
76520
|
const childRewritten = rewriteTemplateAssetReferences(childTemplate, assetRedirect);
|
|
76211
76521
|
if (childRewritten > 0) logger.info(`Note: ${childRewritten} asset reference(s) in nested stack ${childStackName} are recorded in state at their pre-rewrite (CDK bootstrap) values, so the next 'cdkd deploy' repoints any resource that still holds them to cdkd asset storage.`);
|
|
76212
76522
|
}
|
|
76213
|
-
await lockManager.acquireLock(childStackName, childRegion, lockOwner, "import")
|
|
76523
|
+
if (!await lockManager.acquireLock(childStackName, childRegion, lockOwner, "import")) throw new Error(await buildLockContentionMessage({
|
|
76524
|
+
lockManager,
|
|
76525
|
+
stackName: childStackName,
|
|
76526
|
+
region: childRegion,
|
|
76527
|
+
subject: "nested stack",
|
|
76528
|
+
recovery: args.lockRecovery
|
|
76529
|
+
}));
|
|
76214
76530
|
try {
|
|
76215
76531
|
const childResources = collectImportableResources(childTemplate);
|
|
76216
76532
|
const childTemplateLogicalIds = new Set(childResources.map((r) => r.logicalId));
|
|
@@ -76252,6 +76568,7 @@ async function importNestedStackChildrenRecursive(args) {
|
|
|
76252
76568
|
await stateBackend.saveState(childStackName, childRegion, childStackState);
|
|
76253
76569
|
logger.info(`✓ Nested stack state written: ${childStackName} (${childRegion}) — ${rows.filter((r) => r.outcome === "imported").length} resource(s) imported.`);
|
|
76254
76570
|
if (childTreeNode.nested.size > 0) await importNestedStackChildrenRecursive({
|
|
76571
|
+
lockRecovery: args.lockRecovery,
|
|
76255
76572
|
parentStackName: childStackName,
|
|
76256
76573
|
parentRegion: childRegion,
|
|
76257
76574
|
parentNestedTemplates: indexGrandchildTemplatePaths(childTemplate, childTemplatePath),
|
|
@@ -86125,7 +86442,7 @@ function createMigrateCommand() {
|
|
|
86125
86442
|
*/
|
|
86126
86443
|
function buildProgram() {
|
|
86127
86444
|
const program = new Command();
|
|
86128
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.
|
|
86445
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.39");
|
|
86129
86446
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
86130
86447
|
const { profile } = actionCommand.optsWithGlobals();
|
|
86131
86448
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|