@go-to-k/cdkd 0.284.53 → 0.284.55
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-Ciurx_UV.js → asg-provider-nddX-RVm.js} +2 -2
- package/dist/{asg-provider-Ciurx_UV.js.map → asg-provider-nddX-RVm.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-DOP4V6eK.js → deploy-engine-B1tSPv8F.js} +114 -12
- package/dist/deploy-engine-B1tSPv8F.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-9yMSf-LQ.js → program-CTreQYc-.js} +301 -59
- package/dist/{program-9yMSf-LQ.js.map → program-CTreQYc-.js.map} +1 -1
- package/dist/{version-CaIqvSUG.js → version-DD5yHFTJ.js} +2 -2
- package/dist/{version-CaIqvSUG.js.map → version-DD5yHFTJ.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-DOP4V6eK.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as endCommandInterruptScope, $n as processStackMessages, $t as exportNamesCarriedFrom, A as renderStatefulReason, An as Synthesizer, Ar as withErrorHandling, At as isSingleDynamicReferenceToken, B as exportAliasCollisionScrubWarning, Bn as resolveUseCdkBootstrapAssets, Bt as s3BucketRegionalDomainName, C as isFinalSnapshotError, Cn as formatDockerLoginError, Cr as StackHasActiveImportsError, Ct as requireConfigString, D as extractDeploymentEventError, Dn as runDockerStreaming, Dt as createSecretMasker, E as makeCanonicalizePropertiesFn, En as runDockerForeground, Et as TEMPLATE_SOURCED_RULES, F as green, Fn as resolveAutoAssetStorage, Ft as classifyReplaySecretRegion, G as collectInlinePolicyNamesManagedBySiblings, Gn as MIGRATE_TMP_PREFIX, Gt as describeTypeWithThrottleRetry, H as secretBearingStateKeyWarning, Hn as warnDeprecatedNoPrefixCliFlag, Ht as applyRoleArnIfSet, I as red, In as resolveCaptureObservedState, It as producerRegionsFromState, J as findActionableSilentDrops, Jn as expectedOwnerParam, Jt as TemplateParser, K as clearOnUpdateRemoval, Kn as findLargeInlineResources, Kt as withRetry, L as yellow, Ln as resolveSkipPrefix, Lt as s3BucketArn, M as bold, Mn as getDefaultStateBucketName, Mr as isRetryableTransientError, Mt as maskSecretsInText, N as cyan, Nn as getLegacyStateBucketName, Nr as isThrottlingError, Nt as redactSecretsForState, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as AssetManifestLoader, Ot as dynamicReferenceTokens, P as gray, Pn as resolveApp, Pr as markNonRetryable, Pt as scrubResourceRecord, Q as beginCommandInterruptScope, Qn as AssemblyReader, Qt as rebuildClientForBucketRegion, R as collectDeclaredOutputNames, Rn as resolveStateBucketWithDefault, Rt as s3BucketDomainName, S as createPreDeleteFinalSnapshot, Sn as dockerSpawnEnvWithSensitive, Sr as ResourceUpdateNotSupportedError, St as requireConfigObject, T as unsupportedFinalSnapshotError, Tn as partitionSensitiveEnv, Tt as STATE_SOURCED_READBACK_RULES, U as stateKeySecretExposure, Un as CFN_TEMPLATE_BODY_LIMIT, Ut as DiffCalculator, V as isExportAliasCollision, Vn as stateBucketExistenceConfirmed, Vt as s3BucketWebsiteUrl, W as IAMRoleProvider, Wn as CFN_TEMPLATE_URL_LIMIT, Wt as INTRINSIC_KEYS, X as CUSTOM_RESOURCE_RESPONSE_PREFIX, Xn as canonicalizeRegion, Xt as displaySafe, Y as findSilentDropProperties, Yn as PARTITION_TABLE, Yt as LockManager, Z as DEFAULT_STATE_PREFIX, Zn as derivePartitionAndUrlSuffix, Zt as S3StateBackend, _ as computeImplicitDeleteEdges, _n as readBootstrapMarkerBody, _r as MissingCdkCliError, _t as configBooleanRefusal, a as DeploymentEventsStore, an as WorkGraph, ar as setAwsClients, at as disableInstanceApiTermination, b as buildFinalSnapshotIdentifier, bn as buildDenyExternalAccessPolicy, br as ProvisioningError, bt as replayWarn, c as replayFailedOperations, cn as loadPublishableAssetManifest, ct as carriesDynamicReference, d as updatePartialReason, dn as stripControlChars, dr as DeployCancelledError, dt as refStateLookupFromResource, en as importableOutputKeys, et as interruptWatchListenerCount, f as UNSPECIFIED_SKIP_REASON, fn as AssetModeResolver, fr as DynamicReferenceRegionAmbiguousError, ft as WAFv2WebACLProvider, g as IMPLICIT_DELETE_DEPENDENCIES, gn as parseBootstrapMarker, gt as coerceCfnBoolean, h as maskingRetryLogger, hn as getBootstrapMarkerKey, hr as LocalStartServiceError, ht as assertRegionMatch, i as DeploymentEventsReader, in as stringifyValue, ir as resetAwsClients, it as slowCcOperationTimeoutMs, j as formatResourceLine, jn as synthesisStatusMessage, jr as isMarkedNonRetryable, jt as maskSecretsInError, k as isStatefulRecreateTargetSync, kn as getDockerImageBySourceHash, kr as normalizeAwsError, kt as errorCauseChain, l as replayRollback, ln as rewriteTemplateAssetReferences, lr as CrossAccountSecretRefusalError, lt as cfnRefValueFromPhysicalId, m as withResourceDeadline, mn as ensureAssetStorage, mr as LocalMigrateError, mt as resolveExplicitPhysicalId, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as shouldRetainResource, nr as AwsClients, nt as startInterruptWatch, o as planFailedOps, on as buildAssetRedirectMap, ot as isTerminationProtectionPropagationError, p as deleteSkipReason, pn as BOOTSTRAP_MARKER_PREFIX, pr as LocalInvokeBuildError$1, pt as normalizeAwsTagsToCfn, q as ProviderRegistry, qn as uploadCfnTemplate, qt as DagBuilder, r as DeployEngine, rn as AssetPublisher, rr as getAwsClients, rt as CloudControlProvider, s as planRollback, sn as createAssetRedirectResolver, sr as CdkdError, st as IntrinsicFunctionResolver, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as importableOutputs, tr as resolveBucketRegion, tt as isInterruptedWaitError, u as updatePartialMessage, un as escapeRegExp, ut as getAccountInfo, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as validateAssetBucketName, vr as NestedStackChildDirectDestroyError, vt as configStringRefusal, w as refusesFinalSnapshot, wn as getDockerCmd, wr as StackTerminationProtectionError, wt as STATE_SOURCED_CROSS_GENERATION_RULES, x as ccRoutedFinalSnapshotError, xn as buildDockerImage, xr as ResourceTimeoutError, xt as requireConfigArray, y as PRE_DELETE_SNAPSHOT_TYPES, yn as validateContainerRepoName, yr as PartialFailureError, yt as readConfigString, z as collectPublishedOutputNames, zn as resolveStateBucketWithDefaultAndSource, zt as s3BucketDualStackDomainName } from "./deploy-engine-B1tSPv8F.js";
|
|
2
2
|
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";
|
|
3
|
-
import { t as getCdkdVersion } from "./version-
|
|
4
|
-
import { t as ASGProvider } from "./asg-provider-
|
|
3
|
+
import { t as getCdkdVersion } from "./version-DD5yHFTJ.js";
|
|
4
|
+
import { t as ASGProvider } from "./asg-provider-nddX-RVm.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,23 +77,6 @@ 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
|
|
97
80
|
//#region src/cli/commands/state-file-keys.ts
|
|
98
81
|
/**
|
|
99
82
|
* Shared helpers for enumerating and describing cdkd state-bucket keys.
|
|
@@ -2072,6 +2055,15 @@ function createListCommand() {
|
|
|
2072
2055
|
*/
|
|
2073
2056
|
function forwardSigtermToSigint() {
|
|
2074
2057
|
beginCommandInterruptScope();
|
|
2058
|
+
try {
|
|
2059
|
+
return installSigtermForwarder();
|
|
2060
|
+
} catch (error) {
|
|
2061
|
+
endCommandInterruptScope();
|
|
2062
|
+
throw error;
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
/** The registration half of {@link forwardSigtermToSigint}, after its scope is open. */
|
|
2066
|
+
function installSigtermForwarder() {
|
|
2075
2067
|
const handler = () => {
|
|
2076
2068
|
if (process.listenerCount("SIGINT") === 0) process.exit(143);
|
|
2077
2069
|
process.emit("SIGINT", "SIGINT");
|
|
@@ -2082,6 +2074,117 @@ function forwardSigtermToSigint() {
|
|
|
2082
2074
|
endCommandInterruptScope();
|
|
2083
2075
|
};
|
|
2084
2076
|
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Register a command-scoped SIGINT handler for the destroy commands
|
|
2079
|
+
* (issue #2117).
|
|
2080
|
+
*
|
|
2081
|
+
* `cdkd deploy` has had one of these since issue #1348 (`deploy.ts`'s
|
|
2082
|
+
* `topLevelSigintHandler`). The destroy side had none, so the ONLY channel
|
|
2083
|
+
* carrying "the user interrupted" from `runDestroyForStack` back to the
|
|
2084
|
+
* `--all` loop was `DestroyRunResult.interrupted` — a boolean assigned ONCE,
|
|
2085
|
+
* inside the runner's `try`, after its level loop. Every instant where the
|
|
2086
|
+
* runner's `draining` flag flips AFTER that assignment is an instant where
|
|
2087
|
+
* `cdkd destroy --all` went on to delete the NEXT stack after the user asked
|
|
2088
|
+
* it to stop.
|
|
2089
|
+
*
|
|
2090
|
+
* The runner's outer `finally` re-syncs `result.interrupted ||= draining` to
|
|
2091
|
+
* cover the widest such window (renderer teardown, state flush, lock release),
|
|
2092
|
+
* and that line is marked TACTICAL in its own comment: it narrows the window
|
|
2093
|
+
* rather than removing it. Two gaps survive it, and neither is reachable from
|
|
2094
|
+
* inside the runner at all:
|
|
2095
|
+
*
|
|
2096
|
+
* 1. The runner removes its SIGINT listener in its inner `finally`, BEFORE the
|
|
2097
|
+
* re-sync line and its `return`, so from there on nothing of the runner's is
|
|
2098
|
+
* armed. This watch covers that stretch — by RECORDING once a graceful owner
|
|
2099
|
+
* is gone and the stack is done, and by QUITTING in the mirror-image window
|
|
2100
|
+
* at the START of a stack, where the runner has not armed yet and could not
|
|
2101
|
+
* act on a recorded flag (see `gracefulOwnerArmed` below).
|
|
2102
|
+
* 2. Everything the `--all` loop itself does between two stacks — the
|
|
2103
|
+
* `RUN_FINISHED` event, `eventRecorder.finalize`, `purgeEventsAfterDestroy`
|
|
2104
|
+
* — runs with no runner handler armed at all.
|
|
2105
|
+
*
|
|
2106
|
+
* A handler that lives for the whole command closes both, because it is armed
|
|
2107
|
+
* in every window rather than in the ones the runner happens to span. The two
|
|
2108
|
+
* flags answer different questions and both are kept: `result.interrupted` is
|
|
2109
|
+
* the PER-STACK outcome (it decides that stack's state preservation, its
|
|
2110
|
+
* summary line and its `--purge-events` skip), while this one is the
|
|
2111
|
+
* COMMAND-level "stop" the loop wants.
|
|
2112
|
+
*
|
|
2113
|
+
* Note on the counterpart contract: `deploy-engine.ts` removes its own SIGINT
|
|
2114
|
+
* handler before releasing its lock, which is safe only because `deploy.ts`'s
|
|
2115
|
+
* top-level handler outlives it. The destroy side now has the same property
|
|
2116
|
+
* for the same reason — `destroy-runner.ts`'s `process.removeListener('SIGINT',
|
|
2117
|
+
* sigintHandler)` is no longer the last handler standing, because this one is
|
|
2118
|
+
* disposed only in the command's outermost `finally`.
|
|
2119
|
+
*
|
|
2120
|
+
* @param opts.command the user-facing command name used in the force-quit
|
|
2121
|
+
* notice (`cdkd destroy` / `cdkd state destroy`).
|
|
2122
|
+
*/
|
|
2123
|
+
function watchCommandInterrupt(opts) {
|
|
2124
|
+
let interrupted = false;
|
|
2125
|
+
let stacksInFlight = 0;
|
|
2126
|
+
/**
|
|
2127
|
+
* Is some OTHER listener going to handle this signal gracefully?
|
|
2128
|
+
*
|
|
2129
|
+
* Being inside `runStack` is NOT the same question, and assuming it was is a
|
|
2130
|
+
* regression this fix already shipped once: `runDestroyForStack` registers
|
|
2131
|
+
* its own handler about two hundred lines in, AFTER the strong-reference
|
|
2132
|
+
* pre-flight scan (an S3 LIST plus a GET per stack) and the per-stack
|
|
2133
|
+
* confirm prompt. Staying silent across that window swallowed the signal
|
|
2134
|
+
* outright — no notice, no exit, no escalation on a repeat — and because
|
|
2135
|
+
* this watch IS a listener, `interrupt-watch.ts`'s last-listener force-quit
|
|
2136
|
+
* could no longer fire there either. The runner then began with `draining`
|
|
2137
|
+
* false and destroyed the whole stack the user had just asked to stop, which
|
|
2138
|
+
* is issue #2117's own defect one window further in.
|
|
2139
|
+
*
|
|
2140
|
+
* Counting listeners answers the real question, and the two exclusions are
|
|
2141
|
+
* what make the count mean "graceful owner":
|
|
2142
|
+
*
|
|
2143
|
+
* - our own handler, which is not an answer to itself;
|
|
2144
|
+
* - `interrupt-watch.ts`'s shared handler, which is a LATCH rather than a
|
|
2145
|
+
* graceful owner — deferring to it is exactly the swallow above.
|
|
2146
|
+
*
|
|
2147
|
+
* A provider's transient wait listener (CustomResource / ACM / CloudFront /
|
|
2148
|
+
* Route53) does count, and that is correct rather than a false positive:
|
|
2149
|
+
* those exist only DURING a provider wait, which is strictly inside the
|
|
2150
|
+
* runner handler's own lifetime, so one cannot be present while the runner's
|
|
2151
|
+
* is absent.
|
|
2152
|
+
*/
|
|
2153
|
+
function gracefulOwnerArmed() {
|
|
2154
|
+
return process.listeners("SIGINT").filter((l) => l !== handler).length - interruptWatchListenerCount() > 0;
|
|
2155
|
+
}
|
|
2156
|
+
const handler = () => {
|
|
2157
|
+
if (stacksInFlight > 0 && gracefulOwnerArmed()) {
|
|
2158
|
+
interrupted = true;
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2161
|
+
if (interrupted) {
|
|
2162
|
+
process.stderr.write("\nForce-quit.\n");
|
|
2163
|
+
process.exit(130);
|
|
2164
|
+
}
|
|
2165
|
+
interrupted = true;
|
|
2166
|
+
if (stacksInFlight > 0) {
|
|
2167
|
+
process.stderr.write(`\nInterrupted before ${opts.command} armed its per-stack teardown — quitting now. Nothing was deleted and no stack lock is held.\n`);
|
|
2168
|
+
process.exit(130);
|
|
2169
|
+
}
|
|
2170
|
+
process.stderr.write(`\nInterrupted — ${opts.command} will stop before starting another stack (press Ctrl-C again to force-quit)...\n`);
|
|
2171
|
+
};
|
|
2172
|
+
process.on("SIGINT", handler);
|
|
2173
|
+
return {
|
|
2174
|
+
interrupted: () => interrupted,
|
|
2175
|
+
async runStack(fn) {
|
|
2176
|
+
stacksInFlight += 1;
|
|
2177
|
+
try {
|
|
2178
|
+
return await fn();
|
|
2179
|
+
} finally {
|
|
2180
|
+
stacksInFlight -= 1;
|
|
2181
|
+
}
|
|
2182
|
+
},
|
|
2183
|
+
dispose: () => {
|
|
2184
|
+
process.removeListener("SIGINT", handler);
|
|
2185
|
+
}
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2085
2188
|
|
|
2086
2189
|
//#endregion
|
|
2087
2190
|
//#region src/deployment/recreate-targets.ts
|
|
@@ -68228,7 +68331,14 @@ async function destroyCommand(stackArgs, options) {
|
|
|
68228
68331
|
...options.profile && { profile: options.profile }
|
|
68229
68332
|
});
|
|
68230
68333
|
setAwsClients(awsClients);
|
|
68231
|
-
const
|
|
68334
|
+
const interruptWatch = watchCommandInterrupt({ command: "cdkd destroy" });
|
|
68335
|
+
let unforwardSigterm;
|
|
68336
|
+
try {
|
|
68337
|
+
unforwardSigterm = forwardSigtermToSigint();
|
|
68338
|
+
} catch (error) {
|
|
68339
|
+
interruptWatch.dispose();
|
|
68340
|
+
throw error;
|
|
68341
|
+
}
|
|
68232
68342
|
try {
|
|
68233
68343
|
const stateConfig = {
|
|
68234
68344
|
bucket: stateBucket,
|
|
@@ -68288,6 +68398,7 @@ async function destroyCommand(stackArgs, options) {
|
|
|
68288
68398
|
let totalErrors = 0;
|
|
68289
68399
|
let totalSkipped = 0;
|
|
68290
68400
|
let interrupted = false;
|
|
68401
|
+
const runInterrupted = () => interrupted || interruptWatch.interrupted();
|
|
68291
68402
|
let stackNames;
|
|
68292
68403
|
if (options.all) stackNames = candidateStacks.map((s) => s.stackName);
|
|
68293
68404
|
else if (stackPatterns.length > 0) stackNames = matchStacks(candidateStacks, stackPatterns).map((s) => s.stackName);
|
|
@@ -68364,6 +68475,7 @@ async function destroyCommand(stackArgs, options) {
|
|
|
68364
68475
|
totalErrors++;
|
|
68365
68476
|
continue;
|
|
68366
68477
|
}
|
|
68478
|
+
if (runInterrupted()) break;
|
|
68367
68479
|
const eventRecorder = startRunRecorder({
|
|
68368
68480
|
backend: stateBackend,
|
|
68369
68481
|
stackName,
|
|
@@ -68372,7 +68484,7 @@ async function destroyCommand(stackArgs, options) {
|
|
|
68372
68484
|
});
|
|
68373
68485
|
let destroyRunResult = "SUCCEEDED";
|
|
68374
68486
|
try {
|
|
68375
|
-
const result = await withNestedStackContext({
|
|
68487
|
+
const result = await interruptWatch.runStack(() => withNestedStackContext({
|
|
68376
68488
|
stateBackend,
|
|
68377
68489
|
lockManager,
|
|
68378
68490
|
providerRegistry,
|
|
@@ -68411,7 +68523,7 @@ async function destroyCommand(stackArgs, options) {
|
|
|
68411
68523
|
...options.resourceWarnAfter?.perTypeMs && { resourceWarnAfterByType: options.resourceWarnAfter.perTypeMs },
|
|
68412
68524
|
...options.resourceTimeout?.perTypeMs && { resourceTimeoutByType: options.resourceTimeout.perTypeMs },
|
|
68413
68525
|
eventRecorder
|
|
68414
|
-
}));
|
|
68526
|
+
})));
|
|
68415
68527
|
totalErrors += result.errorCount;
|
|
68416
68528
|
totalSkipped += result.skippedCount;
|
|
68417
68529
|
if (result.interrupted) interrupted = true;
|
|
@@ -68438,15 +68550,16 @@ async function destroyCommand(stackArgs, options) {
|
|
|
68438
68550
|
await purgeEventsAfterDestroy(new DeploymentEventsReader(stateBackend), stackName, stackTargetRegion, {
|
|
68439
68551
|
purgeEvents: options.purgeEvents,
|
|
68440
68552
|
runResult: destroyRunResult,
|
|
68441
|
-
interrupted
|
|
68553
|
+
interrupted: runInterrupted()
|
|
68442
68554
|
}, logger);
|
|
68443
|
-
if (
|
|
68555
|
+
if (runInterrupted()) break;
|
|
68444
68556
|
}
|
|
68445
68557
|
if (totalErrors > 0) throw new PartialFailureError(`Destroy completed with ${totalErrors} resource error(s). State preserved — inspect 'cdkd state show <stack>' and re-run 'cdkd destroy' to retry. If the same resource keeps failing, 'cdkd state orphan <stack>' removes the state record without deleting AWS resources.`);
|
|
68446
|
-
if (
|
|
68558
|
+
if (runInterrupted()) throw new PartialFailureError(`Destroy interrupted by Ctrl-C. State preserved — re-run 'cdkd destroy' to finish.`);
|
|
68447
68559
|
if (totalSkipped > 0) throw new PartialFailureError(`Destroy skipped ${totalSkipped} entr${totalSkipped === 1 ? "y" : "ies"} cdkd could not address, so the underlying resources may still exist in AWS. A skipped nested stack counts as ONE entry and may cover several of its own resources — the per-stack summaries above give the exact breakdown. State preserved (the records are kept). Repair the physicalId in state.json and re-run 'cdkd destroy', or delete the resources by hand and drop the records with 'cdkd state orphan <stack>'.`);
|
|
68448
68560
|
} finally {
|
|
68449
68561
|
unforwardSigterm();
|
|
68562
|
+
interruptWatch.dispose();
|
|
68450
68563
|
awsClients.destroy();
|
|
68451
68564
|
}
|
|
68452
68565
|
}
|
|
@@ -73014,7 +73127,14 @@ async function stateDestroyCommand(stackArgs, options) {
|
|
|
73014
73127
|
registerAllProviders(providerRegistry);
|
|
73015
73128
|
providerRegistry.setCustomResourceResponseBucket(setup.bucket);
|
|
73016
73129
|
if (options.allowUnsupportedTypes?.length) providerRegistry.allowUnsupportedTypes(options.allowUnsupportedTypes);
|
|
73017
|
-
const
|
|
73130
|
+
const interruptWatch = watchCommandInterrupt({ command: "cdkd state destroy" });
|
|
73131
|
+
let unforwardSigterm;
|
|
73132
|
+
try {
|
|
73133
|
+
unforwardSigterm = forwardSigtermToSigint();
|
|
73134
|
+
} catch (error) {
|
|
73135
|
+
interruptWatch.dispose();
|
|
73136
|
+
throw error;
|
|
73137
|
+
}
|
|
73018
73138
|
try {
|
|
73019
73139
|
const stateRefs = await setup.stateBackend.listStacks();
|
|
73020
73140
|
const knownStackNames = new Set(stateRefs.map((r) => r.stackName));
|
|
@@ -73050,6 +73170,7 @@ async function stateDestroyCommand(stackArgs, options) {
|
|
|
73050
73170
|
let totalErrors = 0;
|
|
73051
73171
|
let totalSkipped = 0;
|
|
73052
73172
|
let interrupted = false;
|
|
73173
|
+
const runInterrupted = () => interrupted || interruptWatch.interrupted();
|
|
73053
73174
|
for (const stackName of stackNames) {
|
|
73054
73175
|
const refs = stateRefs.filter((r) => r.stackName === stackName);
|
|
73055
73176
|
let targets;
|
|
@@ -73071,7 +73192,8 @@ async function stateDestroyCommand(stackArgs, options) {
|
|
|
73071
73192
|
logger.warn(`No state found for stack ${stackName}${ref.region ? ` in ${ref.region}` : ""}, skipping`);
|
|
73072
73193
|
continue;
|
|
73073
73194
|
}
|
|
73074
|
-
|
|
73195
|
+
if (runInterrupted()) break;
|
|
73196
|
+
const result = await interruptWatch.runStack(() => withNestedStackContext({
|
|
73075
73197
|
stateBackend: setup.stateBackend,
|
|
73076
73198
|
lockManager: setup.lockManager,
|
|
73077
73199
|
providerRegistry,
|
|
@@ -73109,19 +73231,20 @@ async function stateDestroyCommand(stackArgs, options) {
|
|
|
73109
73231
|
...options.resourceTimeout?.globalMs !== void 0 && { resourceTimeoutMs: options.resourceTimeout.globalMs },
|
|
73110
73232
|
...options.resourceWarnAfter?.perTypeMs && { resourceWarnAfterByType: options.resourceWarnAfter.perTypeMs },
|
|
73111
73233
|
...options.resourceTimeout?.perTypeMs && { resourceTimeoutByType: options.resourceTimeout.perTypeMs }
|
|
73112
|
-
}));
|
|
73234
|
+
})));
|
|
73113
73235
|
totalErrors += result.errorCount;
|
|
73114
73236
|
totalSkipped += result.skippedCount;
|
|
73115
73237
|
if (result.interrupted) interrupted = true;
|
|
73116
|
-
if (
|
|
73238
|
+
if (runInterrupted()) break;
|
|
73117
73239
|
}
|
|
73118
|
-
if (
|
|
73240
|
+
if (runInterrupted()) break;
|
|
73119
73241
|
}
|
|
73120
73242
|
if (totalErrors > 0) throw new PartialFailureError(`Destroy completed with ${totalErrors} resource error(s). State preserved — inspect 'cdkd state show <stack>' and re-run 'cdkd state destroy' to retry. If the same resource keeps failing, 'cdkd state orphan <stack>' removes the state record without deleting AWS resources.`);
|
|
73121
|
-
if (
|
|
73243
|
+
if (runInterrupted()) throw new PartialFailureError(`Destroy interrupted by Ctrl-C. State preserved — re-run 'cdkd state destroy' to finish.`);
|
|
73122
73244
|
if (totalSkipped > 0) throw new PartialFailureError(`Destroy skipped ${totalSkipped} entr${totalSkipped === 1 ? "y" : "ies"} cdkd could not address, so the underlying resources may still exist in AWS. A skipped nested stack counts as ONE entry and may cover several of its own resources — the per-stack summaries above give the exact breakdown. State preserved (the records are kept). Repair the physicalId in state.json and re-run 'cdkd state destroy', or delete the resources by hand and drop the records with 'cdkd state orphan <stack>'.`);
|
|
73123
73245
|
} finally {
|
|
73124
73246
|
unforwardSigterm();
|
|
73247
|
+
interruptWatch.dispose();
|
|
73125
73248
|
setup.dispose();
|
|
73126
73249
|
}
|
|
73127
73250
|
}
|
|
@@ -74705,9 +74828,19 @@ function looksLikeEcrHostWithForeignSuffix(imageUri) {
|
|
|
74705
74828
|
*
|
|
74706
74829
|
* - Names come from the region's bootstrap marker, never recomputed from
|
|
74707
74830
|
* the naming convention (custom-name compatibility, issue #1011). No
|
|
74708
|
-
* marker →
|
|
74831
|
+
* marker → no ASSET storage in scope; since issue #2052 the run continues
|
|
74832
|
+
* to the state-bucket placeholder sweep rather than ending, and is a
|
|
74833
|
+
* friendly no-op only when neither side has anything. CDK bootstrap
|
|
74709
74834
|
* storage (`cdk-hnb659fds-*`) is never touched — that stays `cdk gc`'s
|
|
74710
74835
|
* job.
|
|
74836
|
+
* - Abandoned `custom-resource-responses/{requestId}.json` placeholders in
|
|
74837
|
+
* the STATE bucket are collected too (issue #2052). Two things make that
|
|
74838
|
+
* the one arm needing its own guard: it is gc's only DESTRUCTIVE call
|
|
74839
|
+
* against the state bucket, and it is ACCOUNT-scoped where every other arm
|
|
74840
|
+
* is region-scoped (the keys carry no region). It matches the producer's
|
|
74841
|
+
* own key shape rather than sweeping the prefix by age alone, so a stack
|
|
74842
|
+
* deployed with a colliding `--state-prefix` cannot have its `state.json`
|
|
74843
|
+
* collected.
|
|
74711
74844
|
* - The reference scan lists EVERY state file in the WHOLE state bucket
|
|
74712
74845
|
* (any `--state-prefix`), and a state file that fails to JSON-parse
|
|
74713
74846
|
* aborts the whole run — deleting on partial knowledge is how a live
|
|
@@ -75162,12 +75295,98 @@ async function listS3Candidates(s3Client, bucket, accountId, refs, cutoffMs, log
|
|
|
75162
75295
|
return candidates;
|
|
75163
75296
|
}
|
|
75164
75297
|
/**
|
|
75165
|
-
*
|
|
75166
|
-
*
|
|
75167
|
-
*
|
|
75168
|
-
*
|
|
75169
|
-
*
|
|
75170
|
-
|
|
75298
|
+
* List abandoned custom-resource response placeholders (issue #2052).
|
|
75299
|
+
*
|
|
75300
|
+
* `CustomResourceProvider` PUTs an empty object at
|
|
75301
|
+
* `custom-resource-responses/{requestId}.json` before each invocation so the
|
|
75302
|
+
* handler has a pre-signed URL to write to. The happy paths clean up after
|
|
75303
|
+
* themselves — the issue #2033 replay path via `cleanupResponseObject`, and the
|
|
75304
|
+
* FAILED-response arm via `getCustomResourceResponse` / `pollS3Response` — but
|
|
75305
|
+
* three shapes leave the key behind and NOTHING collected them:
|
|
75306
|
+
*
|
|
75307
|
+
* - an interrupted deploy (Ctrl-C / SIGTERM / a cancelled CI job) between the
|
|
75308
|
+
* PUT and any cleanup;
|
|
75309
|
+
* - any throw on a path that does not reach a cleanup call;
|
|
75310
|
+
* - a LATE handler PUT, landing after cdkd stopped polling. This is the only
|
|
75311
|
+
* one with CONTENT: an empty body is a storage leak, but a real
|
|
75312
|
+
* CloudFormation `Data` payload sitting at a key nothing collects is also a
|
|
75313
|
+
* data-retention question.
|
|
75314
|
+
*
|
|
75315
|
+
* ## Why age alone is a sufficient staleness rule here
|
|
75316
|
+
*
|
|
75317
|
+
* The requirement is that a key belonging to an IN-FLIGHT concurrent run is
|
|
75318
|
+
* never collected, and two independent things already guarantee it:
|
|
75319
|
+
*
|
|
75320
|
+
* 1. gc refuses outright while ANY stack holds a lock (the lock guard in
|
|
75321
|
+
* `gcCommand`), and every deploy that can write one of these keys holds
|
|
75322
|
+
* one for its whole duration. A concurrent run does not get this far.
|
|
75323
|
+
* 2. `--older-than` (default 30d) is applied to each object's own
|
|
75324
|
+
* `LastModified`, which for a placeholder is the moment of the PUT that
|
|
75325
|
+
* opened the invocation.
|
|
75326
|
+
*
|
|
75327
|
+
* So this deliberately adds no clock of its own. The two are not ranked, and an
|
|
75328
|
+
* earlier version of this note wrongly said (1) was the one that mattered: for
|
|
75329
|
+
* a deploy that STARTS between the lock scan and this listing, (1) has already
|
|
75330
|
+
* run and only (2) covers it. They close different windows.
|
|
75331
|
+
*
|
|
75332
|
+
* ## What this widens, stated rather than implied
|
|
75333
|
+
*
|
|
75334
|
+
* This is gc's first DESTRUCTIVE call against the state bucket — until now it
|
|
75335
|
+
* only ever read from it. And unlike every other arm of this command it is
|
|
75336
|
+
* ACCOUNT-scoped, not region-scoped: placeholder keys carry no region, so
|
|
75337
|
+
* `cdkd gc --region us-east-1` collects ones written by deploys into any
|
|
75338
|
+
* region. Both are deliberate (the objects are cdkd's own transient scratch,
|
|
75339
|
+
* and a region-scoped sweep could never reach most of them), and both are why
|
|
75340
|
+
* the key-shape filter below is not optional.
|
|
75341
|
+
*
|
|
75342
|
+
* ## Scope
|
|
75343
|
+
*
|
|
75344
|
+
* Only the DEFAULT prefix. `ProviderRegistry` can be configured with another
|
|
75345
|
+
* `responsePrefix`, and nothing persists which one a past deploy used, so gc
|
|
75346
|
+
* cannot discover a non-default layout — under-collecting, which is the safe
|
|
75347
|
+
* direction, and the reason the constant is shared with the producer rather
|
|
75348
|
+
* than re-spelled here.
|
|
75349
|
+
*/
|
|
75350
|
+
/**
|
|
75351
|
+
* The only key shape `CustomResourceProvider` can mint under the prefix.
|
|
75352
|
+
*
|
|
75353
|
+
* `getResponseKey` builds `{prefix}/{requestId}.json` from
|
|
75354
|
+
* `cdkd-${Date.now()}-${Math.random().toString(36).substring(7)}`.
|
|
75355
|
+
*
|
|
75356
|
+
* The trailing group is `*`, NOT `+`, and that is a measurement rather than
|
|
75357
|
+
* caution: `substring(7)` returns the EMPTY string whenever the base-36
|
|
75358
|
+
* rendering is shorter than eight characters (`(0.5).toString(36)` is `'0.i'`),
|
|
75359
|
+
* so `cdkd-1756180000000-.json` is a real key this provider writes. A `+` here
|
|
75360
|
+
* would skip exactly those and the sweep would silently under-collect — a
|
|
75361
|
+
* shape filter defeating the sweep it is meant to protect.
|
|
75362
|
+
*
|
|
75363
|
+
* Why filter at all: `--state-prefix` is free-form and unvalidated, so a stack
|
|
75364
|
+
* deployed with `--state-prefix custom-resource-responses` puts its
|
|
75365
|
+
* `state.json`, `lock.json`, `rollback-journal.json` and `deployments/*` INSIDE
|
|
75366
|
+
* this prefix. gc builds its own backend on `DEFAULT_STATE_PREFIX` and cannot
|
|
75367
|
+
* see that collision, so an age-only sweep would delete live state and orphan
|
|
75368
|
+
* every resource in the stack. Matching the producer's own shape is what keeps
|
|
75369
|
+
* this command's "bias every ambiguity toward NOT deleting" posture true on the
|
|
75370
|
+
* one path that deletes from the STATE bucket.
|
|
75371
|
+
*/
|
|
75372
|
+
const RESPONSE_PLACEHOLDER_KEY = /^cdkd-\d+-[0-9a-z]*\.json$/;
|
|
75373
|
+
async function listResponsePlaceholderCandidates(stateBackend, cutoffMs, logger) {
|
|
75374
|
+
const objects = await stateBackend.listRawObjects(`${CUSTOM_RESOURCE_RESPONSE_PREFIX}/`);
|
|
75375
|
+
const candidates = [];
|
|
75376
|
+
for (const obj of objects) {
|
|
75377
|
+
const leaf = obj.key.slice(`${CUSTOM_RESOURCE_RESPONSE_PREFIX}/`.length);
|
|
75378
|
+
if (!RESPONSE_PLACEHOLDER_KEY.test(leaf)) {
|
|
75379
|
+
logger.debug(`gc: keeping ${obj.key} — not a cdkd response placeholder`);
|
|
75380
|
+
continue;
|
|
75381
|
+
}
|
|
75382
|
+
if (obj.lastModified.getTime() >= cutoffMs) {
|
|
75383
|
+
logger.debug(`gc: keeping ${obj.key} — newer than the --older-than cutoff`);
|
|
75384
|
+
continue;
|
|
75385
|
+
}
|
|
75386
|
+
candidates.push(obj);
|
|
75387
|
+
}
|
|
75388
|
+
return candidates;
|
|
75389
|
+
}
|
|
75171
75390
|
async function listEcrCandidates(ecrClient, repositoryName, refs, cutoffMs, logger) {
|
|
75172
75391
|
const candidates = [];
|
|
75173
75392
|
let nextToken;
|
|
@@ -75235,7 +75454,7 @@ function formatAge(date) {
|
|
|
75235
75454
|
async function promptGcConfirm(input) {
|
|
75236
75455
|
const logger = getLogger();
|
|
75237
75456
|
logger.warn("");
|
|
75238
|
-
logger.warn(`cdkd gc will delete the following
|
|
75457
|
+
logger.warn(`cdkd gc will delete the following (region: ${input.region}):`);
|
|
75239
75458
|
for (const line of input.planLines) logger.warn(` - ${line}`);
|
|
75240
75459
|
if (input.yes) return true;
|
|
75241
75460
|
if (process.stdin.isTTY !== true) throw new CdkdError("The gc confirmation prompt cannot run in a non-interactive environment. Pass --yes / -y to confirm the deletion, or run the command from a real terminal.", "NON_INTERACTIVE_CONFIRM");
|
|
@@ -75351,32 +75570,47 @@ async function gcCommand(options) {
|
|
|
75351
75570
|
}
|
|
75352
75571
|
throw error;
|
|
75353
75572
|
}
|
|
75354
|
-
if (markerBody === null) {
|
|
75355
|
-
const probed = rawMarkerKey === markerKey ? markerKey : `${markerKey}, ${rawMarkerKey}`;
|
|
75356
|
-
logger.info(`No bootstrap marker for region '${region}' (${probed}) — the region is not opted in to cdkd asset storage; nothing to garbage-collect. (CDK bootstrap storage is 'cdk gc' territory.)`);
|
|
75357
|
-
return;
|
|
75358
|
-
}
|
|
75359
|
-
const marker = parseBootstrapMarker(markerBody, resolvedMarkerKey);
|
|
75360
75573
|
const lockKeys = await listAllLockKeys(stateBackend);
|
|
75361
75574
|
if (lockKeys.length > 0) {
|
|
75362
75575
|
const listing = lockKeys.map((k) => ` - ${describeStateKey(k, LOCK_FILE_SUFFIX, DEFAULT_STATE_PREFIX)} [${k}]`).join("\n");
|
|
75363
75576
|
throw new CdkdError(`Refusing to gc while ${lockKeys.length} stack(s) hold an active lock (a deploy in flight may have published assets whose state write has not landed yet):\n${listing}\nWait for the operation(s) to finish — or clear a stale lock with 'cdkd force-unlock <stack>' — then re-run 'cdkd gc'.`, "GC_LOCKED");
|
|
75364
75577
|
}
|
|
75365
|
-
const refs = await scanReferencedAssets(stateBackend, marker, logger);
|
|
75366
75578
|
const cutoffMs = Date.now() - options.olderThan;
|
|
75367
|
-
const
|
|
75368
|
-
|
|
75369
|
-
|
|
75370
|
-
|
|
75579
|
+
const responseCandidates = await listResponsePlaceholderCandidates(stateBackend, cutoffMs, logger);
|
|
75580
|
+
if (markerBody === null) {
|
|
75581
|
+
const probed = rawMarkerKey === markerKey ? markerKey : `${markerKey}, ${rawMarkerKey}`;
|
|
75582
|
+
if (responseCandidates.length === 0) {
|
|
75583
|
+
logger.info(`No bootstrap marker for region '${region}' (${probed}) — the region is not opted in to cdkd asset storage; nothing to garbage-collect. (CDK bootstrap storage is 'cdk gc' territory.)`);
|
|
75584
|
+
return;
|
|
75585
|
+
}
|
|
75586
|
+
logger.info(`No bootstrap marker for region '${region}' (${probed}) — the region is not opted in to cdkd asset storage, so no assets are in scope. (CDK bootstrap storage is 'cdk gc' territory.) Continuing with the state bucket's abandoned custom-resource response placeholders.`);
|
|
75587
|
+
}
|
|
75588
|
+
const marker = markerBody === null ? null : parseBootstrapMarker(markerBody, resolvedMarkerKey);
|
|
75589
|
+
let s3Candidates = [];
|
|
75590
|
+
let ecrCandidates = [];
|
|
75591
|
+
if (marker !== null) {
|
|
75592
|
+
const refs = await scanReferencedAssets(stateBackend, marker, logger);
|
|
75593
|
+
s3Candidates = await listS3Candidates(awsClients.s3, marker.assetBucket, accountId, refs, cutoffMs, logger);
|
|
75594
|
+
ecrCandidates = await listEcrCandidates(ecrClient, marker.containerRepo, refs, cutoffMs, logger);
|
|
75595
|
+
}
|
|
75596
|
+
if (marker !== null && s3Candidates.length === 0 && ecrCandidates.length === 0 && responseCandidates.length === 0) {
|
|
75597
|
+
logger.info(`Nothing to garbage-collect in region '${region}': every object in ${marker.assetBucket} / image in ${marker.containerRepo} is either referenced by a deployed stack or newer than the --older-than cutoff, and no abandoned custom-resource response placeholder is older than it either.`);
|
|
75371
75598
|
return;
|
|
75372
75599
|
}
|
|
75373
75600
|
const s3Bytes = s3Candidates.reduce((sum, c) => sum + c.size, 0);
|
|
75374
75601
|
const ecrBytes = ecrCandidates.reduce((sum, c) => sum + c.size, 0);
|
|
75375
|
-
const
|
|
75376
|
-
const
|
|
75602
|
+
const responseBytes = responseCandidates.reduce((sum, c) => sum + c.size, 0);
|
|
75603
|
+
const assetBucket = marker !== null ? marker.assetBucket : "";
|
|
75604
|
+
const containerRepo = marker !== null ? marker.containerRepo : "";
|
|
75605
|
+
const planLines = [
|
|
75606
|
+
...s3Candidates.map((c) => `s3://${assetBucket}/${c.key} (${formatBytes(c.size)}, ${formatAge(c.lastModified)})`),
|
|
75607
|
+
...ecrCandidates.map((c) => `${containerRepo}${c.tags.length > 0 ? `:${c.tags.join(",")}` : ""}@${c.digest} (${formatBytes(c.size)}, ${formatAge(c.pushedAt)})`),
|
|
75608
|
+
...responseCandidates.map((c) => `s3://${bucketName}/${c.key} (${formatBytes(c.size)}, ${formatAge(c.lastModified)}) [abandoned custom-resource response]`)
|
|
75609
|
+
];
|
|
75610
|
+
const totals = `Total: ${s3Candidates.length} S3 object(s) (${formatBytes(s3Bytes)}) + ${ecrCandidates.length} ECR image(s) (${formatBytes(ecrBytes)}) + ${responseCandidates.length} custom-resource response placeholder(s) (${formatBytes(responseBytes)}) = ${formatBytes(s3Bytes + ecrBytes + responseBytes)} reclaimable`;
|
|
75377
75611
|
if (options.dryRun) {
|
|
75378
75612
|
logger.info("");
|
|
75379
|
-
logger.info(`Dry run — the following
|
|
75613
|
+
logger.info(`Dry run — the following would be deleted in ${region}:`);
|
|
75380
75614
|
for (const line of planLines) logger.info(` - ${line}`);
|
|
75381
75615
|
logger.info(totals);
|
|
75382
75616
|
logger.info("Dry run: nothing deleted. Re-run without --dry-run to delete.");
|
|
@@ -75390,15 +75624,23 @@ async function gcCommand(options) {
|
|
|
75390
75624
|
logger.info("gc cancelled — nothing deleted.");
|
|
75391
75625
|
return;
|
|
75392
75626
|
}
|
|
75393
|
-
if (s3Candidates.length > 0) {
|
|
75627
|
+
if (marker !== null && s3Candidates.length > 0) {
|
|
75394
75628
|
await deleteS3Candidates(awsClients.s3, marker.assetBucket, accountId, s3Candidates);
|
|
75395
75629
|
logger.info(`✓ Deleted ${s3Candidates.length} object(s) (${formatBytes(s3Bytes)}) from ${marker.assetBucket}`);
|
|
75396
75630
|
}
|
|
75397
|
-
if (ecrCandidates.length > 0) {
|
|
75631
|
+
if (marker !== null && ecrCandidates.length > 0) {
|
|
75398
75632
|
await deleteEcrCandidates(ecrClient, marker.containerRepo, ecrCandidates);
|
|
75399
75633
|
logger.info(`✓ Deleted ${ecrCandidates.length} image(s) (${formatBytes(ecrBytes)}) from ${marker.containerRepo}`);
|
|
75400
75634
|
}
|
|
75401
|
-
|
|
75635
|
+
if (responseCandidates.length > 0) {
|
|
75636
|
+
try {
|
|
75637
|
+
await stateBackend.deleteRawObjects(responseCandidates.map((c) => c.key));
|
|
75638
|
+
} catch (deleteError) {
|
|
75639
|
+
throw new CdkdError(`Failed to delete ${responseCandidates.length} abandoned custom-resource response placeholder(s) from ${bucketName}: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`, "GC_DELETE_FAILED");
|
|
75640
|
+
}
|
|
75641
|
+
logger.info(`✓ Deleted ${responseCandidates.length} abandoned custom-resource response placeholder(s) (${formatBytes(responseBytes)}) from ${bucketName}`);
|
|
75642
|
+
}
|
|
75643
|
+
logger.info(`\n✓ gc completed: ${formatBytes(s3Bytes + ecrBytes + responseBytes)} reclaimed`);
|
|
75402
75644
|
} finally {
|
|
75403
75645
|
ecrClient.destroy();
|
|
75404
75646
|
markerS3Client.destroy();
|
|
@@ -75409,7 +75651,7 @@ async function gcCommand(options) {
|
|
|
75409
75651
|
* Create the `cdkd gc` command (upstream `cdk gc` parity naming).
|
|
75410
75652
|
*/
|
|
75411
75653
|
function createGcCommand() {
|
|
75412
|
-
const cmd = new Command("gc").description("Garbage-collect unreferenced objects / images from ONE region's cdkd-owned asset storage (asset bucket + container-asset ECR repo). References are collected from every cdkd state file; CDK bootstrap storage is never touched (use cdk gc
|
|
75654
|
+
const cmd = new Command("gc").description("Garbage-collect unreferenced objects / images from ONE region's cdkd-owned asset storage (asset bucket + container-asset ECR repo), plus abandoned custom-resource response placeholders in the state bucket (account-wide). References are collected from every cdkd state file; CDK bootstrap storage is never touched (use cdk gc).").option("--state-bucket <bucket>", "S3 bucket holding cdkd state (default: cdkd-state-{accountId})").addOption(new Option("--older-than <duration>", "Never delete an object / image newer than this age, even when unreferenced (protects in-flight publishes and recent rollback targets). Accepts <n>d / <n>h.").default(DEFAULT_OLDER_THAN_MS, "30d").argParser(parseOlderThan)).option("--dry-run", "Print the reclaim plan (per-item list + totals) without deleting", false).addOption(new Option("--region <region>", "Region whose cdkd asset storage to garbage-collect (defaults to AWS_REGION, then AWS_DEFAULT_REGION, then your AWS profile's region, then us-east-1)")).action(withErrorHandling(async (options) => {
|
|
75413
75655
|
await gcCommand(options);
|
|
75414
75656
|
}));
|
|
75415
75657
|
commonOptions.forEach((opt) => cmd.addOption(opt));
|
|
@@ -87262,4 +87504,4 @@ function buildProgram() {
|
|
|
87262
87504
|
|
|
87263
87505
|
//#endregion
|
|
87264
87506
|
export { buildProgram };
|
|
87265
|
-
//# sourceMappingURL=program-
|
|
87507
|
+
//# sourceMappingURL=program-CTreQYc-.js.map
|